mirror of
https://github.com/Minestom/Minestom.git
synced 2025-01-06 16:37:38 +01:00
Entity viewable tracking (#1494)
This commit is contained in:
parent
91a344aa92
commit
c04028336d
@ -49,7 +49,7 @@ public abstract class Chunk implements Block.Getter, Block.Setter, Biome.Getter,
|
|||||||
private boolean readOnly;
|
private boolean readOnly;
|
||||||
|
|
||||||
protected volatile boolean loaded = true;
|
protected volatile boolean loaded = true;
|
||||||
private final ChunkView viewers;
|
private final Viewable viewable;
|
||||||
|
|
||||||
// Path finding
|
// Path finding
|
||||||
protected PFColumnarSpace columnarSpace;
|
protected PFColumnarSpace columnarSpace;
|
||||||
@ -65,7 +65,9 @@ public abstract class Chunk implements Block.Getter, Block.Setter, Biome.Getter,
|
|||||||
this.shouldGenerate = shouldGenerate;
|
this.shouldGenerate = shouldGenerate;
|
||||||
this.minSection = instance.getDimensionType().getMinY() / CHUNK_SECTION_SIZE;
|
this.minSection = instance.getDimensionType().getMinY() / CHUNK_SECTION_SIZE;
|
||||||
this.maxSection = (instance.getDimensionType().getMinY() + instance.getDimensionType().getHeight()) / CHUNK_SECTION_SIZE;
|
this.maxSection = (instance.getDimensionType().getMinY() + instance.getDimensionType().getHeight()) / CHUNK_SECTION_SIZE;
|
||||||
this.viewers = new ChunkView(instance, toPosition());
|
final List<SharedInstance> shared = instance instanceof InstanceContainer instanceContainer ?
|
||||||
|
instanceContainer.getSharedInstances() : List.of();
|
||||||
|
this.viewable = instance.getEntityTracker().viewable(shared, chunkX, chunkZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -267,17 +269,17 @@ public abstract class Chunk implements Block.Getter, Block.Setter, Biome.Getter,
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean addViewer(@NotNull Player player) {
|
public boolean addViewer(@NotNull Player player) {
|
||||||
throw new UnsupportedOperationException("Chunk does not support manual viewers");
|
return viewable.addViewer(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean removeViewer(@NotNull Player player) {
|
public boolean removeViewer(@NotNull Player player) {
|
||||||
throw new UnsupportedOperationException("Chunk does not support manual viewers");
|
return viewable.removeViewer(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Set<Player> getViewers() {
|
public @NotNull Set<Player> getViewers() {
|
||||||
return viewers.set;
|
return viewable.getViewers();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
package net.minestom.server.instance;
|
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
|
||||||
import net.minestom.server.MinecraftServer;
|
|
||||||
import net.minestom.server.coordinate.Point;
|
|
||||||
import net.minestom.server.entity.Player;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
final class ChunkView {
|
|
||||||
private final Instance instance;
|
|
||||||
private final Point point;
|
|
||||||
final Set<Player> set = new SetImpl();
|
|
||||||
|
|
||||||
private int lastReferenceCount;
|
|
||||||
|
|
||||||
ChunkView(Instance instance, Point point) {
|
|
||||||
this.instance = instance;
|
|
||||||
this.point = point;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Collection<Player> references() {
|
|
||||||
Int2ObjectOpenHashMap<Player> entityMap = new Int2ObjectOpenHashMap<>(lastReferenceCount);
|
|
||||||
collectPlayers(instance, entityMap);
|
|
||||||
if (instance instanceof InstanceContainer container && !container.getSharedInstances().isEmpty()) {
|
|
||||||
for (Instance shared : container.getSharedInstances()) {
|
|
||||||
collectPlayers(shared, entityMap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.lastReferenceCount = entityMap.size();
|
|
||||||
return entityMap.values();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void collectPlayers(Instance instance, Int2ObjectOpenHashMap<Player> map) {
|
|
||||||
instance.getEntityTracker().nearbyEntitiesByChunkRange(point, MinecraftServer.getChunkViewDistance(),
|
|
||||||
EntityTracker.Target.PLAYERS, (player) -> map.putIfAbsent(player.getEntityId(), player));
|
|
||||||
}
|
|
||||||
|
|
||||||
final class SetImpl extends AbstractSet<Player> {
|
|
||||||
@Override
|
|
||||||
public @NotNull Iterator<Player> iterator() {
|
|
||||||
return references().iterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int size() {
|
|
||||||
return references().size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void forEach(Consumer<? super Player> action) {
|
|
||||||
references().forEach(action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,6 @@
|
|||||||
package net.minestom.server.instance;
|
package net.minestom.server.instance;
|
||||||
|
|
||||||
|
import net.minestom.server.Viewable;
|
||||||
import net.minestom.server.coordinate.Point;
|
import net.minestom.server.coordinate.Point;
|
||||||
import net.minestom.server.entity.Entity;
|
import net.minestom.server.entity.Entity;
|
||||||
import net.minestom.server.entity.ExperienceOrb;
|
import net.minestom.server.entity.ExperienceOrb;
|
||||||
@ -55,7 +56,7 @@ public sealed interface EntityTracker permits EntityTrackerImpl {
|
|||||||
* Gets the entities within a chunk range.
|
* Gets the entities within a chunk range.
|
||||||
*/
|
*/
|
||||||
<T extends Entity> void nearbyEntitiesByChunkRange(@NotNull Point point, int chunkRange,
|
<T extends Entity> void nearbyEntitiesByChunkRange(@NotNull Point point, int chunkRange,
|
||||||
@NotNull Target<T> target, @NotNull Consumer<T> query);
|
@NotNull Target<T> target, @NotNull Consumer<T> query);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the entities within a range.
|
* Gets the entities within a range.
|
||||||
@ -74,6 +75,12 @@ public sealed interface EntityTracker permits EntityTrackerImpl {
|
|||||||
return entities(Target.ENTITIES);
|
return entities(Target.ENTITIES);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull Viewable viewable(@NotNull List<@NotNull SharedInstance> sharedInstances, int chunkX, int chunkZ);
|
||||||
|
|
||||||
|
default @NotNull Viewable viewable(int chunkX, int chunkZ) {
|
||||||
|
return viewable(List.of(), chunkX, chunkZ);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the type of entity you want to retrieve.
|
* Represents the type of entity you want to retrieve.
|
||||||
*
|
*
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
package net.minestom.server.instance;
|
package net.minestom.server.instance;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||||
import net.minestom.server.MinecraftServer;
|
import net.minestom.server.MinecraftServer;
|
||||||
|
import net.minestom.server.Viewable;
|
||||||
import net.minestom.server.coordinate.Point;
|
import net.minestom.server.coordinate.Point;
|
||||||
|
import net.minestom.server.coordinate.Vec;
|
||||||
import net.minestom.server.entity.Entity;
|
import net.minestom.server.entity.Entity;
|
||||||
|
import net.minestom.server.entity.Player;
|
||||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@ -11,16 +15,15 @@ import org.jetbrains.annotations.UnmodifiableView;
|
|||||||
import space.vectrix.flare.fastutil.Int2ObjectSyncMap;
|
import space.vectrix.flare.fastutil.Int2ObjectSyncMap;
|
||||||
import space.vectrix.flare.fastutil.Long2ObjectSyncMap;
|
import space.vectrix.flare.fastutil.Long2ObjectSyncMap;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.*;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import static net.minestom.server.instance.Chunk.CHUNK_SIZE_X;
|
||||||
|
import static net.minestom.server.instance.Chunk.CHUNK_SIZE_Z;
|
||||||
import static net.minestom.server.utils.chunk.ChunkUtils.*;
|
import static net.minestom.server.utils.chunk.ChunkUtils.*;
|
||||||
|
|
||||||
final class EntityTrackerImpl implements EntityTracker {
|
final class EntityTrackerImpl implements EntityTracker {
|
||||||
@ -168,6 +171,12 @@ final class EntityTrackerImpl implements EntityTracker {
|
|||||||
return (Set<T>) entries[target.ordinal()].entitiesView;
|
return (Set<T>) entries[target.ordinal()].entitiesView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Viewable viewable(@NotNull List<@NotNull SharedInstance> sharedInstances, int chunkX, int chunkZ) {
|
||||||
|
var entry = entries[Target.PLAYERS.ordinal()];
|
||||||
|
return entry.viewers.computeIfAbsent(new ChunkViewKey(sharedInstances, chunkX, chunkZ), ChunkView::new);
|
||||||
|
}
|
||||||
|
|
||||||
private <T extends Entity> void difference(Point oldPoint, Point newPoint,
|
private <T extends Entity> void difference(Point oldPoint, Point newPoint,
|
||||||
@NotNull Target<T> target, @NotNull Update<T> update) {
|
@NotNull Target<T> target, @NotNull Update<T> update) {
|
||||||
final TargetEntry<Entity> entry = entries[target.ordinal()];
|
final TargetEntry<Entity> entry = entries[target.ordinal()];
|
||||||
@ -185,12 +194,24 @@ final class EntityTrackerImpl implements EntityTracker {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
record ChunkViewKey(List<SharedInstance> sharedInstances, int chunkX, int chunkZ) {
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) return true;
|
||||||
|
if (!(obj instanceof ChunkViewKey key)) return false;
|
||||||
|
return sharedInstances == key.sharedInstances &&
|
||||||
|
chunkX == key.chunkX &&
|
||||||
|
chunkZ == key.chunkZ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static final class TargetEntry<T extends Entity> {
|
static final class TargetEntry<T extends Entity> {
|
||||||
private final EntityTracker.Target<T> target;
|
private final EntityTracker.Target<T> target;
|
||||||
private final Set<T> entities = ConcurrentHashMap.newKeySet(); // Thread-safe since exposed
|
private final Set<T> entities = ConcurrentHashMap.newKeySet(); // Thread-safe since exposed
|
||||||
private final Set<T> entitiesView = Collections.unmodifiableSet(entities);
|
private final Set<T> entitiesView = Collections.unmodifiableSet(entities);
|
||||||
// Chunk index -> entities inside it
|
// Chunk index -> entities inside it
|
||||||
final Long2ObjectSyncMap<List<T>> chunkEntities = Long2ObjectSyncMap.hashmap();
|
final Long2ObjectSyncMap<List<T>> chunkEntities = Long2ObjectSyncMap.hashmap();
|
||||||
|
final Map<ChunkViewKey, ChunkView> viewers = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
TargetEntry(Target<T> target) {
|
TargetEntry(Target<T> target) {
|
||||||
this.target = target;
|
this.target = target;
|
||||||
@ -209,4 +230,70 @@ final class EntityTrackerImpl implements EntityTracker {
|
|||||||
if (entities != null) entities.remove(entity);
|
if (entities != null) entities.remove(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final class ChunkView implements Viewable {
|
||||||
|
private final ChunkViewKey key;
|
||||||
|
private final int chunkX, chunkZ;
|
||||||
|
private final Point point;
|
||||||
|
final Set<Player> set = new SetImpl();
|
||||||
|
private int lastReferenceCount;
|
||||||
|
|
||||||
|
private ChunkView(ChunkViewKey key) {
|
||||||
|
this.key = key;
|
||||||
|
|
||||||
|
this.chunkX = key.chunkX;
|
||||||
|
this.chunkZ = key.chunkZ;
|
||||||
|
|
||||||
|
this.point = new Vec(CHUNK_SIZE_X * chunkX, 0, CHUNK_SIZE_Z * chunkZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean addViewer(@NotNull Player player) {
|
||||||
|
throw new UnsupportedOperationException("Chunk does not support manual viewers");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeViewer(@NotNull Player player) {
|
||||||
|
throw new UnsupportedOperationException("Chunk does not support manual viewers");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Set<@NotNull Player> getViewers() {
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<Player> references() {
|
||||||
|
Int2ObjectOpenHashMap<Player> entityMap = new Int2ObjectOpenHashMap<>(lastReferenceCount);
|
||||||
|
collectPlayers(EntityTrackerImpl.this, entityMap);
|
||||||
|
if (!key.sharedInstances.isEmpty()) {
|
||||||
|
for (SharedInstance instance : key.sharedInstances) {
|
||||||
|
collectPlayers(instance.getEntityTracker(), entityMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.lastReferenceCount = entityMap.size();
|
||||||
|
return entityMap.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collectPlayers(EntityTracker tracker, Int2ObjectOpenHashMap<Player> map) {
|
||||||
|
tracker.nearbyEntitiesByChunkRange(point, MinecraftServer.getChunkViewDistance(),
|
||||||
|
EntityTracker.Target.PLAYERS, (player) -> map.putIfAbsent(player.getEntityId(), player));
|
||||||
|
}
|
||||||
|
|
||||||
|
final class SetImpl extends AbstractSet<Player> {
|
||||||
|
@Override
|
||||||
|
public @NotNull Iterator<Player> iterator() {
|
||||||
|
return references().iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return references().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void forEach(Consumer<? super Player> action) {
|
||||||
|
references().forEach(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
package net.minestom.server.instance;
|
package net.minestom.server.instance;
|
||||||
|
|
||||||
import net.minestom.server.MinecraftServer;
|
import net.minestom.server.MinecraftServer;
|
||||||
import net.minestom.testing.Env;
|
|
||||||
import net.minestom.testing.EnvTest;
|
|
||||||
import net.minestom.server.coordinate.Pos;
|
import net.minestom.server.coordinate.Pos;
|
||||||
import net.minestom.server.network.packet.server.play.ChunkDataPacket;
|
import net.minestom.server.network.packet.server.play.ChunkDataPacket;
|
||||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||||
|
import net.minestom.testing.Env;
|
||||||
|
import net.minestom.testing.EnvTest;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.ValueSource;
|
import org.junit.jupiter.params.provider.ValueSource;
|
||||||
@ -30,7 +30,8 @@ public class ChunkViewerIntegrationTest {
|
|||||||
assertEquals(0, chunk.getViewers().size());
|
assertEquals(0, chunk.getViewers().size());
|
||||||
|
|
||||||
var player = env.createPlayer(instance, new Pos(0, 40, 0));
|
var player = env.createPlayer(instance, new Pos(0, 40, 0));
|
||||||
assertEquals(1, chunk.getViewers().size());
|
assertEquals(1, chunk.getViewers().size(), sharedInstance ?
|
||||||
|
"Chunk viewer set must include players from shared instance" : "Instance should have 1 viewer");
|
||||||
assertEquals(player, chunk.getViewers().iterator().next());
|
assertEquals(player, chunk.getViewers().iterator().next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
package net.minestom.server.instance;
|
package net.minestom.server.instance;
|
||||||
|
|
||||||
import net.minestom.server.MinecraftServer;
|
import net.minestom.server.MinecraftServer;
|
||||||
import net.minestom.testing.Env;
|
|
||||||
import net.minestom.testing.EnvTest;
|
|
||||||
import net.minestom.server.coordinate.Pos;
|
import net.minestom.server.coordinate.Pos;
|
||||||
import net.minestom.server.entity.Entity;
|
import net.minestom.server.entity.Entity;
|
||||||
import net.minestom.server.entity.EntityType;
|
import net.minestom.server.entity.EntityType;
|
||||||
import net.minestom.server.entity.Player;
|
import net.minestom.server.entity.Player;
|
||||||
import net.minestom.server.network.packet.server.SendablePacket;
|
import net.minestom.server.network.packet.server.SendablePacket;
|
||||||
import net.minestom.server.network.player.PlayerConnection;
|
import net.minestom.server.network.player.PlayerConnection;
|
||||||
|
import net.minestom.testing.Env;
|
||||||
|
import net.minestom.testing.EnvTest;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
@ -17,6 +17,7 @@ import java.util.UUID;
|
|||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||||
|
|
||||||
@EnvTest
|
@EnvTest
|
||||||
public class EntityTrackerIntegrationTest {
|
public class EntityTrackerIntegrationTest {
|
||||||
@ -35,6 +36,7 @@ public class EntityTrackerIntegrationTest {
|
|||||||
public void updateNewViewer(Player player) {
|
public void updateNewViewer(Player player) {
|
||||||
viewersCount.incrementAndGet();
|
viewersCount.incrementAndGet();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateOldViewer(Player player) {
|
public void updateOldViewer(Player player) {
|
||||||
viewersCount.decrementAndGet();
|
viewersCount.decrementAndGet();
|
||||||
@ -66,6 +68,7 @@ public class EntityTrackerIntegrationTest {
|
|||||||
public void updateNewViewer(Player player) {
|
public void updateNewViewer(Player player) {
|
||||||
viewersCount.incrementAndGet();
|
viewersCount.incrementAndGet();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateOldViewer(Player player) {
|
public void updateOldViewer(Player player) {
|
||||||
viewersCount.decrementAndGet();
|
viewersCount.decrementAndGet();
|
||||||
@ -83,6 +86,49 @@ public class EntityTrackerIntegrationTest {
|
|||||||
assertEquals(1, viewersCount.get());
|
assertEquals(1, viewersCount.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void viewable(Env env) {
|
||||||
|
final Instance instance = env.createFlatInstance();
|
||||||
|
final Pos spawnPos = new Pos(0, 41, 0);
|
||||||
|
var viewable = instance.getEntityTracker().viewable(spawnPos.chunkX(), spawnPos.chunkZ());
|
||||||
|
assertEquals(0, viewable.getViewers().size());
|
||||||
|
|
||||||
|
final Player player = env.createPlayer(instance, spawnPos);
|
||||||
|
assertEquals(1, viewable.getViewers().size());
|
||||||
|
assertSame(viewable, instance.getEntityTracker().viewable(spawnPos.chunkX(), spawnPos.chunkZ()));
|
||||||
|
|
||||||
|
player.teleport(new Pos(10_000, 41, 0)).join();
|
||||||
|
assertEquals(0, viewable.getViewers().size());
|
||||||
|
|
||||||
|
player.teleport(spawnPos).join();
|
||||||
|
assertEquals(1, viewable.getViewers().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void viewableShared(Env env) {
|
||||||
|
final InstanceContainer instance = (InstanceContainer) env.createFlatInstance();
|
||||||
|
var shared = env.process().instance().createSharedInstance(instance);
|
||||||
|
var sharedList = instance.getSharedInstances();
|
||||||
|
|
||||||
|
final Pos spawnPos = new Pos(0, 41, 0);
|
||||||
|
var viewable = instance.getEntityTracker().viewable(sharedList, spawnPos.chunkX(), spawnPos.chunkZ());
|
||||||
|
assertEquals(0, viewable.getViewers().size());
|
||||||
|
|
||||||
|
final Player player = env.createPlayer(instance, spawnPos);
|
||||||
|
assertEquals(1, viewable.getViewers().size());
|
||||||
|
assertSame(viewable, instance.getEntityTracker().viewable(sharedList, spawnPos.chunkX(), spawnPos.chunkZ()));
|
||||||
|
|
||||||
|
player.setInstance(shared).join();
|
||||||
|
assertEquals(1, viewable.getViewers().size());
|
||||||
|
|
||||||
|
player.teleport(new Pos(10_000, 41, 0)).join();
|
||||||
|
assertEquals(0, viewable.getViewers().size());
|
||||||
|
|
||||||
|
var shared2 = env.process().instance().createSharedInstance(instance);
|
||||||
|
player.setInstance(shared2, spawnPos).join();
|
||||||
|
assertEquals(1, viewable.getViewers().size());
|
||||||
|
}
|
||||||
|
|
||||||
private Player createTestPlayer() {
|
private Player createTestPlayer() {
|
||||||
return new Player(UUID.randomUUID(), "TestPlayer", new PlayerConnection() {
|
return new Player(UUID.randomUUID(), "TestPlayer", new PlayerConnection() {
|
||||||
@Override
|
@Override
|
||||||
|
Loading…
Reference in New Issue
Block a user