vanish impl
This commit is contained in:
@@ -13,6 +13,7 @@ import net.kewwbec.networkcore.api.MetricsService;
|
|||||||
import net.kewwbec.networkcore.api.PlayerPresence;
|
import net.kewwbec.networkcore.api.PlayerPresence;
|
||||||
import net.kewwbec.networkcore.api.RpcClient;
|
import net.kewwbec.networkcore.api.RpcClient;
|
||||||
import net.kewwbec.networkcore.api.ServerRegistry;
|
import net.kewwbec.networkcore.api.ServerRegistry;
|
||||||
|
import net.kewwbec.networkcore.api.VanishService;
|
||||||
import net.kewwbec.networkcore.api.events.ServerLeftNetworkEvent;
|
import net.kewwbec.networkcore.api.events.ServerLeftNetworkEvent;
|
||||||
import net.kewwbec.networkcore.bus.RedisMessageBus;
|
import net.kewwbec.networkcore.bus.RedisMessageBus;
|
||||||
import net.kewwbec.networkcore.command.NetworkCoreCommand;
|
import net.kewwbec.networkcore.command.NetworkCoreCommand;
|
||||||
@@ -46,6 +47,11 @@ import net.kewwbec.networkcore.xp.XpSchemaBootstrap;
|
|||||||
import net.kewwbec.networkcore.presence.PlayerPresenceService;
|
import net.kewwbec.networkcore.presence.PlayerPresenceService;
|
||||||
import net.kewwbec.networkcore.registry.RedisServerRegistry;
|
import net.kewwbec.networkcore.registry.RedisServerRegistry;
|
||||||
import net.kewwbec.networkcore.rpc.RpcClientImpl;
|
import net.kewwbec.networkcore.rpc.RpcClientImpl;
|
||||||
|
import net.kewwbec.networkcore.vanish.PlayerListVanishFilter;
|
||||||
|
import net.kewwbec.networkcore.vanish.VanishEnforcer;
|
||||||
|
import net.kewwbec.networkcore.vanish.VanishServiceImpl;
|
||||||
|
|
||||||
|
import com.hypixel.hytale.server.core.event.events.player.PlayerReadyEvent;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
@@ -75,6 +81,7 @@ public final class NetworkCore extends JavaPlugin {
|
|||||||
@Nullable private RefRequestService refRequestService;
|
@Nullable private RefRequestService refRequestService;
|
||||||
@Nullable private LockService lockService;
|
@Nullable private LockService lockService;
|
||||||
@Nullable private ShutdownHandoff shutdownHandoff;
|
@Nullable private ShutdownHandoff shutdownHandoff;
|
||||||
|
@Nullable private VanishServiceImpl vanishService;
|
||||||
|
|
||||||
public NetworkCore(@Nonnull JavaPluginInit init) {
|
public NetworkCore(@Nonnull JavaPluginInit init) {
|
||||||
super(init);
|
super(init);
|
||||||
@@ -164,6 +171,13 @@ public final class NetworkCore extends JavaPlugin {
|
|||||||
this.alertService = new AlertService(messageBus, serverId);
|
this.alertService = new AlertService(messageBus, serverId);
|
||||||
alertService.start();
|
alertService.start();
|
||||||
|
|
||||||
|
this.vanishService = new VanishServiceImpl(messageBus, serverId);
|
||||||
|
vanishService.start();
|
||||||
|
services.register(VanishService.class, vanishService);
|
||||||
|
VanishEnforcer vanishEnforcer = new VanishEnforcer(vanishService);
|
||||||
|
getEventRegistry().registerGlobal(PlayerReadyEvent.class, vanishEnforcer::onPlayerReady);
|
||||||
|
new PlayerListVanishFilter(vanishService).register();
|
||||||
|
|
||||||
this.refRequestService = new RefRequestService(messageBus, serverId);
|
this.refRequestService = new RefRequestService(messageBus, serverId);
|
||||||
refRequestService.start();
|
refRequestService.start();
|
||||||
|
|
||||||
@@ -185,7 +199,7 @@ public final class NetworkCore extends JavaPlugin {
|
|||||||
});
|
});
|
||||||
|
|
||||||
getCommandRegistry().registerCommand(new AlertCommand(alertService));
|
getCommandRegistry().registerCommand(new AlertCommand(alertService));
|
||||||
getCommandRegistry().registerCommand(new GlistCommand(serverRegistry, presenceService));
|
getCommandRegistry().registerCommand(new GlistCommand(serverRegistry, presenceService, vanishService));
|
||||||
getCommandRegistry().registerCommand(new HubCommand(lobbyPicker, refRequestService, serverId));
|
getCommandRegistry().registerCommand(new HubCommand(lobbyPicker, refRequestService, serverId));
|
||||||
getCommandRegistry().registerCommand(new SendCommand(serverRegistry, presenceService, refRequestService));
|
getCommandRegistry().registerCommand(new SendCommand(serverRegistry, presenceService, refRequestService));
|
||||||
getCommandRegistry().registerCommand(new SendAllCommand(serverRegistry, presenceService, refRequestService, serverId));
|
getCommandRegistry().registerCommand(new SendAllCommand(serverRegistry, presenceService, refRequestService, serverId));
|
||||||
@@ -227,6 +241,10 @@ public final class NetworkCore extends JavaPlugin {
|
|||||||
try { alertService.stop(); } catch (RuntimeException ignored) {}
|
try { alertService.stop(); } catch (RuntimeException ignored) {}
|
||||||
alertService = null;
|
alertService = null;
|
||||||
}
|
}
|
||||||
|
if (vanishService != null) {
|
||||||
|
try { vanishService.stop(); } catch (RuntimeException ignored) {}
|
||||||
|
vanishService = null;
|
||||||
|
}
|
||||||
if (presenceService != null) {
|
if (presenceService != null) {
|
||||||
try { presenceService.stop(); } catch (RuntimeException ignored) {}
|
try { presenceService.stop(); } catch (RuntimeException ignored) {}
|
||||||
presenceService = null;
|
presenceService = null;
|
||||||
@@ -288,6 +306,8 @@ public final class NetworkCore extends JavaPlugin {
|
|||||||
@Nullable
|
@Nullable
|
||||||
public NetworkXp getNetworkXp() { return services.get(NetworkXp.class); }
|
public NetworkXp getNetworkXp() { return services.get(NetworkXp.class); }
|
||||||
@Nullable
|
@Nullable
|
||||||
|
public VanishService getVanishService() { return services.get(VanishService.class); }
|
||||||
|
@Nullable
|
||||||
public RefRequestService getRefRequestService() { return refRequestService; }
|
public RefRequestService getRefRequestService() { return refRequestService; }
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package net.kewwbec.networkcore.api;
|
||||||
|
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cross-server vanish state. Source of truth shared via the {@link MessageBus};
|
||||||
|
* each server caches and enforces locally (HiddenPlayersManager + join-broadcast
|
||||||
|
* suppression by consuming plugins).
|
||||||
|
*
|
||||||
|
* <p>Players with the {@code networkstaff.vanish} permission can see vanished
|
||||||
|
* players regardless. Everyone else sees them as if they weren't there.</p>
|
||||||
|
*/
|
||||||
|
public interface VanishService {
|
||||||
|
|
||||||
|
boolean isVanished(@Nonnull UUID playerUuid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle vanish state for {@code playerUuid}. Persists to DB (if configured)
|
||||||
|
* and broadcasts to other servers on the bus.
|
||||||
|
*/
|
||||||
|
void setVanished(@Nonnull UUID playerUuid, boolean vanished);
|
||||||
|
|
||||||
|
/** Snapshot of every UUID currently marked vanished, across the network. */
|
||||||
|
@Nonnull
|
||||||
|
Set<UUID> vanishedPlayers();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience check for "should I pretend this target is offline when showing
|
||||||
|
* results to {@code viewer}?" Returns true iff {@code target} is vanished and
|
||||||
|
* {@code viewer} lacks {@code networkstaff.vanish}. Null viewer is treated as
|
||||||
|
* non-staff (defensive — assume hidden).
|
||||||
|
*/
|
||||||
|
boolean isHiddenFrom(@Nonnull UUID target, @Nullable PlayerRef viewer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribe to vanish state changes. Listener receives {@code (uuid, vanishedNow)}
|
||||||
|
* for every toggle, whether the change originated locally or arrived via the bus
|
||||||
|
* from another server. Fires on the bus-handling thread; consumers that need the
|
||||||
|
* world thread must hop themselves.
|
||||||
|
*/
|
||||||
|
void addListener(@Nonnull BiConsumer<UUID, Boolean> listener);
|
||||||
|
}
|
||||||
@@ -15,6 +15,8 @@ import net.kewwbec.networkcore.api.PlayerLocation;
|
|||||||
import net.kewwbec.networkcore.api.PlayerPresence;
|
import net.kewwbec.networkcore.api.PlayerPresence;
|
||||||
import net.kewwbec.networkcore.api.ServerInfo;
|
import net.kewwbec.networkcore.api.ServerInfo;
|
||||||
import net.kewwbec.networkcore.api.ServerRegistry;
|
import net.kewwbec.networkcore.api.ServerRegistry;
|
||||||
|
import net.kewwbec.networkcore.api.VanishService;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
@@ -36,12 +38,16 @@ public final class GlistCommand extends AbstractPlayerCommand {
|
|||||||
|
|
||||||
private final ServerRegistry registry;
|
private final ServerRegistry registry;
|
||||||
private final PlayerPresence presence;
|
private final PlayerPresence presence;
|
||||||
|
@Nullable private final VanishService vanish;
|
||||||
private final OptionalArg<String> filterArg = withOptionalArg("filter", "Server id or role to filter by", ArgTypes.STRING);
|
private final OptionalArg<String> filterArg = withOptionalArg("filter", "Server id or role to filter by", ArgTypes.STRING);
|
||||||
|
|
||||||
public GlistCommand(@Nonnull ServerRegistry registry, @Nonnull PlayerPresence presence) {
|
public GlistCommand(@Nonnull ServerRegistry registry,
|
||||||
|
@Nonnull PlayerPresence presence,
|
||||||
|
@Nullable VanishService vanish) {
|
||||||
super("glist", "List online players, optionally filtered by server id or role");
|
super("glist", "List online players, optionally filtered by server id or role");
|
||||||
this.registry = registry;
|
this.registry = registry;
|
||||||
this.presence = presence;
|
this.presence = presence;
|
||||||
|
this.vanish = vanish;
|
||||||
requirePermission(NetworkCorePermissions.GLIST);
|
requirePermission(NetworkCorePermissions.GLIST);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,11 +66,13 @@ public final class GlistCommand extends AbstractPlayerCommand {
|
|||||||
Set<String> includedIds = new LinkedHashSet<>();
|
Set<String> includedIds = new LinkedHashSet<>();
|
||||||
for (ServerInfo s : servers) includedIds.add(s.id());
|
for (ServerInfo s : servers) includedIds.add(s.id());
|
||||||
|
|
||||||
|
boolean canSeeVanished = sender.hasPermission(net.kewwbec.networkcore.vanish.VanishEnforcer.PERM_SEE);
|
||||||
Map<String, List<PlayerLocation>> grouped = new LinkedHashMap<>();
|
Map<String, List<PlayerLocation>> grouped = new LinkedHashMap<>();
|
||||||
for (ServerInfo s : servers) grouped.put(s.id(), new ArrayList<>());
|
for (ServerInfo s : servers) grouped.put(s.id(), new ArrayList<>());
|
||||||
int filteredOnline = 0;
|
int filteredOnline = 0;
|
||||||
for (PlayerLocation loc : presence.all()) {
|
for (PlayerLocation loc : presence.all()) {
|
||||||
if (!includedIds.contains(loc.serverId())) continue;
|
if (!includedIds.contains(loc.serverId())) continue;
|
||||||
|
if (!canSeeVanished && vanish != null && vanish.isVanished(loc.uuid())) continue;
|
||||||
grouped.computeIfAbsent(loc.serverId(), k -> new ArrayList<>()).add(loc);
|
grouped.computeIfAbsent(loc.serverId(), k -> new ArrayList<>()).add(loc);
|
||||||
filteredOnline++;
|
filteredOnline++;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package net.kewwbec.networkcore.vanish;
|
||||||
|
|
||||||
|
import com.hypixel.hytale.protocol.Packet;
|
||||||
|
import com.hypixel.hytale.protocol.packets.interface_.AddToServerPlayerList;
|
||||||
|
import com.hypixel.hytale.protocol.packets.interface_.ServerPlayerListPlayer;
|
||||||
|
import com.hypixel.hytale.protocol.packets.interface_.ServerPlayerListUpdate;
|
||||||
|
import com.hypixel.hytale.protocol.packets.interface_.UpdateServerPlayerList;
|
||||||
|
import com.hypixel.hytale.server.core.io.adapter.PacketAdapters;
|
||||||
|
import com.hypixel.hytale.server.core.io.adapter.PlayerPacketFilter;
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
import net.kewwbec.networkcore.api.VanishService;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Outbound packet filter that strips vanished players from the client-visible
|
||||||
|
* tab list. Runs for every outbound {@link AddToServerPlayerList} and
|
||||||
|
* {@link UpdateServerPlayerList}; for non-staff recipients, drops any entries
|
||||||
|
* for vanished UUIDs. If the resulting array is empty the packet is cancelled
|
||||||
|
* entirely.
|
||||||
|
*
|
||||||
|
* <p>For mid-session toggles, see {@link VanishEnforcer#onVanishChanged} —
|
||||||
|
* that side pushes explicit {@code RemoveFromServerPlayerList} /
|
||||||
|
* {@code AddToServerPlayerList} to non-staff clients so the list updates
|
||||||
|
* without waiting for the next engine push.</p>
|
||||||
|
*/
|
||||||
|
public final class PlayerListVanishFilter implements PlayerPacketFilter {
|
||||||
|
|
||||||
|
private final VanishService vanish;
|
||||||
|
|
||||||
|
public PlayerListVanishFilter(@Nonnull VanishService vanish) {
|
||||||
|
this.vanish = vanish;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void register() {
|
||||||
|
PacketAdapters.registerOutbound(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean test(@Nonnull PlayerRef recipient, @Nonnull Packet packet) {
|
||||||
|
if (recipient.hasPermission(VanishEnforcer.PERM_SEE)) return false;
|
||||||
|
|
||||||
|
if (packet instanceof AddToServerPlayerList add) {
|
||||||
|
return filterAdd(add);
|
||||||
|
}
|
||||||
|
if (packet instanceof UpdateServerPlayerList upd) {
|
||||||
|
return filterUpdate(upd);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean filterAdd(@Nonnull AddToServerPlayerList add) {
|
||||||
|
ServerPlayerListPlayer[] players = add.players;
|
||||||
|
if (players == null || players.length == 0) return false;
|
||||||
|
List<ServerPlayerListPlayer> kept = new ArrayList<>(players.length);
|
||||||
|
for (ServerPlayerListPlayer p : players) {
|
||||||
|
if (p == null || p.uuid == null) continue;
|
||||||
|
if (vanish.isVanished(p.uuid)) continue;
|
||||||
|
kept.add(p);
|
||||||
|
}
|
||||||
|
if (kept.isEmpty()) return true;
|
||||||
|
if (kept.size() != players.length) {
|
||||||
|
add.players = kept.toArray(new ServerPlayerListPlayer[0]);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean filterUpdate(@Nonnull UpdateServerPlayerList upd) {
|
||||||
|
ServerPlayerListUpdate[] players = upd.players;
|
||||||
|
if (players == null || players.length == 0) return false;
|
||||||
|
List<ServerPlayerListUpdate> kept = new ArrayList<>(players.length);
|
||||||
|
for (ServerPlayerListUpdate u : players) {
|
||||||
|
if (u == null || u.uuid == null) continue;
|
||||||
|
if (vanish.isVanished(u.uuid)) continue;
|
||||||
|
kept.add(u);
|
||||||
|
}
|
||||||
|
if (kept.isEmpty()) return true;
|
||||||
|
if (kept.size() != players.length) {
|
||||||
|
upd.players = kept.toArray(new ServerPlayerListUpdate[0]);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package net.kewwbec.networkcore.vanish;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bus payload broadcast on every vanish toggle. Carries enough state for any
|
||||||
|
* server to update its cache and enforce locally.
|
||||||
|
*/
|
||||||
|
public final class VanishChangePayload {
|
||||||
|
|
||||||
|
public static final String CHANNEL = "network:vanish";
|
||||||
|
|
||||||
|
/** UUID being vanished/unvanished. */
|
||||||
|
public String uuid;
|
||||||
|
/** New state — true=vanished, false=unvanished. */
|
||||||
|
public boolean vanished;
|
||||||
|
/** Origin server id (for echo suppression). */
|
||||||
|
public String serverId;
|
||||||
|
/** Timestamp (ms since epoch) for staleness checks if we ever add them. */
|
||||||
|
public long ts;
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
package net.kewwbec.networkcore.vanish;
|
||||||
|
|
||||||
|
import com.hypixel.hytale.logger.HytaleLogger;
|
||||||
|
import com.hypixel.hytale.protocol.packets.interface_.AddToServerPlayerList;
|
||||||
|
import com.hypixel.hytale.protocol.packets.interface_.RemoveFromServerPlayerList;
|
||||||
|
import com.hypixel.hytale.protocol.packets.interface_.ServerPlayerListPlayer;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.player.HiddenPlayersManager;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.player.PlayerReadyEvent;
|
||||||
|
import com.hypixel.hytale.server.core.io.PacketHandler;
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
import com.hypixel.hytale.server.core.universe.Universe;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Per-server enforcement of vanish state via {@link HiddenPlayersManager}.
|
||||||
|
* Wired by NetworkCore: on player join we hide existing vanished players from
|
||||||
|
* non-staff arrivals, and on vanish toggles (local or remote via bus) we update
|
||||||
|
* every online player's visibility set.
|
||||||
|
*/
|
||||||
|
public final class VanishEnforcer {
|
||||||
|
|
||||||
|
/** Anyone with this perm sees vanished players and can use /vanish. */
|
||||||
|
public static final String PERM_SEE = "networkstaff.vanish";
|
||||||
|
|
||||||
|
private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
|
||||||
|
|
||||||
|
private final VanishServiceImpl vanish;
|
||||||
|
|
||||||
|
public VanishEnforcer(@Nonnull VanishServiceImpl vanish) {
|
||||||
|
this.vanish = vanish;
|
||||||
|
vanish.addListener(this::onVanishChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Called by NetworkCore on every PlayerReadyEvent. */
|
||||||
|
public void onPlayerReady(@Nonnull PlayerReadyEvent event) {
|
||||||
|
com.hypixel.hytale.component.Ref<com.hypixel.hytale.server.core.universe.world.storage.EntityStore> ref =
|
||||||
|
event.getPlayerRef();
|
||||||
|
if (ref == null || !ref.isValid()) return;
|
||||||
|
com.hypixel.hytale.component.Store<com.hypixel.hytale.server.core.universe.world.storage.EntityStore> store =
|
||||||
|
ref.getStore();
|
||||||
|
if (store == null) return;
|
||||||
|
PlayerRef newcomer = store.getComponent(ref, PlayerRef.getComponentType());
|
||||||
|
if (newcomer == null) return;
|
||||||
|
|
||||||
|
UUID newcomerUuid = newcomer.getUuid();
|
||||||
|
boolean newcomerCanSeeVanished = newcomer.hasPermission(PERM_SEE);
|
||||||
|
|
||||||
|
if (!newcomerCanSeeVanished) {
|
||||||
|
HiddenPlayersManager hpm = newcomer.getHiddenPlayersManager();
|
||||||
|
if (hpm != null) {
|
||||||
|
for (UUID vanishedUuid : vanish.vanishedPlayers()) {
|
||||||
|
if (vanishedUuid.equals(newcomerUuid)) continue;
|
||||||
|
hpm.hidePlayer(vanishedUuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vanish.isVanished(newcomerUuid)) {
|
||||||
|
Universe universe = Universe.get();
|
||||||
|
if (universe == null) return;
|
||||||
|
for (PlayerRef other : universe.getPlayers()) {
|
||||||
|
if (other.getUuid().equals(newcomerUuid)) continue;
|
||||||
|
if (other.hasPermission(PERM_SEE)) continue;
|
||||||
|
HiddenPlayersManager hpm = other.getHiddenPlayersManager();
|
||||||
|
if (hpm != null) hpm.hidePlayer(newcomerUuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onVanishChanged(@Nonnull UUID uuid, boolean vanishedNow) {
|
||||||
|
Universe universe = Universe.get();
|
||||||
|
if (universe == null) return;
|
||||||
|
try {
|
||||||
|
PlayerRef subject = null;
|
||||||
|
for (PlayerRef p : universe.getPlayers()) {
|
||||||
|
if (p.getUuid().equals(uuid)) { subject = p; break; }
|
||||||
|
}
|
||||||
|
for (PlayerRef other : universe.getPlayers()) {
|
||||||
|
if (other.getUuid().equals(uuid)) continue;
|
||||||
|
HiddenPlayersManager hpm = other.getHiddenPlayersManager();
|
||||||
|
if (hpm != null) {
|
||||||
|
if (other.hasPermission(PERM_SEE)) {
|
||||||
|
hpm.showPlayer(uuid);
|
||||||
|
} else if (vanishedNow) {
|
||||||
|
hpm.hidePlayer(uuid);
|
||||||
|
} else {
|
||||||
|
hpm.showPlayer(uuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (other.hasPermission(PERM_SEE)) continue;
|
||||||
|
PacketHandler ph = other.getPacketHandler();
|
||||||
|
if (ph == null) continue;
|
||||||
|
try {
|
||||||
|
if (vanishedNow) {
|
||||||
|
ph.write(new RemoveFromServerPlayerList(new UUID[]{uuid}));
|
||||||
|
} else if (subject != null) {
|
||||||
|
ServerPlayerListPlayer entry = new ServerPlayerListPlayer(
|
||||||
|
subject.getUuid(),
|
||||||
|
subject.getUsername(),
|
||||||
|
subject.getWorldUuid(),
|
||||||
|
0);
|
||||||
|
ph.write(new AddToServerPlayerList(new ServerPlayerListPlayer[]{entry}));
|
||||||
|
}
|
||||||
|
} catch (RuntimeException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
LOGGER.at(Level.WARNING).log("VanishEnforcer onVanishChanged threw: %s", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
package net.kewwbec.networkcore.vanish;
|
||||||
|
|
||||||
|
import com.hypixel.hytale.logger.HytaleLogger;
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
import net.kewwbec.networkcore.api.MessageBus;
|
||||||
|
import net.kewwbec.networkcore.api.VanishService;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In-memory vanish cache, kept consistent across the network via the
|
||||||
|
* {@link MessageBus}. State is not persisted — vanish ends at server restart
|
||||||
|
* (intentional: staff explicitly turn it on each session).
|
||||||
|
*/
|
||||||
|
public final class VanishServiceImpl implements VanishService {
|
||||||
|
|
||||||
|
private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
|
||||||
|
|
||||||
|
private final MessageBus bus;
|
||||||
|
private final String selfServerId;
|
||||||
|
private final Set<UUID> vanished = ConcurrentHashMap.newKeySet();
|
||||||
|
/** Listeners notified on every change, local or remote-via-bus. */
|
||||||
|
private final List<BiConsumer<UUID, Boolean>> listeners = new CopyOnWriteArrayList<>();
|
||||||
|
@Nullable private MessageBus.Subscription sub;
|
||||||
|
|
||||||
|
public VanishServiceImpl(@Nonnull MessageBus bus, @Nonnull String selfServerId) {
|
||||||
|
this.bus = bus;
|
||||||
|
this.selfServerId = selfServerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
sub = bus.subscribe(VanishChangePayload.CHANNEL, VanishChangePayload.class, this::onRemote);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
if (sub != null) {
|
||||||
|
try { sub.close(); } catch (RuntimeException ignored) {}
|
||||||
|
sub = null;
|
||||||
|
}
|
||||||
|
vanished.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addListener(@Nonnull BiConsumer<UUID, Boolean> listener) {
|
||||||
|
listeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isVanished(@Nonnull UUID playerUuid) {
|
||||||
|
return vanished.contains(playerUuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setVanished(@Nonnull UUID playerUuid, boolean vanishedNow) {
|
||||||
|
boolean changed = vanishedNow ? vanished.add(playerUuid) : vanished.remove(playerUuid);
|
||||||
|
if (!changed) return;
|
||||||
|
fireLocal(playerUuid, vanishedNow);
|
||||||
|
publish(playerUuid, vanishedNow);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nonnull
|
||||||
|
public Set<UUID> vanishedPlayers() {
|
||||||
|
return Collections.unmodifiableSet(vanished);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isHiddenFrom(@Nonnull UUID target, @Nullable PlayerRef viewer) {
|
||||||
|
if (!vanished.contains(target)) return false;
|
||||||
|
if (viewer == null) return true;
|
||||||
|
if (viewer.getUuid().equals(target)) return false;
|
||||||
|
return !viewer.hasPermission(VanishEnforcer.PERM_SEE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onRemote(@Nonnull VanishChangePayload p) {
|
||||||
|
if (p.uuid == null || p.serverId == null) return;
|
||||||
|
if (selfServerId.equals(p.serverId)) return;
|
||||||
|
UUID uuid;
|
||||||
|
try { uuid = UUID.fromString(p.uuid); } catch (IllegalArgumentException e) { return; }
|
||||||
|
boolean changed = p.vanished ? vanished.add(uuid) : vanished.remove(uuid);
|
||||||
|
if (changed) fireLocal(uuid, p.vanished);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void publish(@Nonnull UUID uuid, boolean vanishedNow) {
|
||||||
|
try {
|
||||||
|
VanishChangePayload p = new VanishChangePayload();
|
||||||
|
p.uuid = uuid.toString();
|
||||||
|
p.vanished = vanishedNow;
|
||||||
|
p.serverId = selfServerId;
|
||||||
|
p.ts = System.currentTimeMillis();
|
||||||
|
bus.publish(VanishChangePayload.CHANNEL, p);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
LOGGER.at(Level.WARNING).log("Vanish publish failed: %s", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fireLocal(@Nonnull UUID uuid, boolean vanishedNow) {
|
||||||
|
for (BiConsumer<UUID, Boolean> listener : listeners) {
|
||||||
|
try {
|
||||||
|
listener.accept(uuid, vanishedNow);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
LOGGER.at(Level.WARNING).log("Vanish listener threw: %s", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user