Volver al índice
src/main/java/ar/com/companeros/body/PlayerSessionTests.java
package ar.com.companeros.body;
import ar.com.companeros.Companeros;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import net.minecraft.core.BlockPos;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.block.Blocks;
import net.minecraftforge.common.util.FakePlayer;
import net.minecraftforge.gametest.GameTestHolder;
import net.minecraftforge.gametest.PrefixGameTestTemplate;
/** Pruebas del ciclo de jugador registrado, separadas de las del cuerpo NPC legado. */
@GameTestHolder(Companeros.ID)
@PrefixGameTestTemplate(false)
public final class PlayerSessionTests {
private static final Set<UUID> FIXTURE_IDS = new HashSet<>();
private static String name(UUID id) { return "Ag_" + id.toString().substring(0, 8); }
private static ServerPlayer create(GameTestHelper helper, UUID id) {
PlayerSessions.install();
var server = helper.getLevel().getServer();
// GameTestPlayerList admite un solo jugador; sólo se limpian UUID creados por este fixture.
for (UUID previous : Set.copyOf(FIXTURE_IDS)) removeFixture(server, previous);
for (int x = 0; x <= 13; x++) for (int z = 0; z <= 13; z++)
helper.setBlock(new BlockPos(x, 1, z), Blocks.DIRT);
ServerPlayer player = PlayerSessions.create(server, id, name(id), helper.getLevel(),
helper.absolutePos(new BlockPos(3, 2, 3)));
FIXTURE_IDS.add(id);
return player;
}
private static void removeFixture(net.minecraft.server.MinecraftServer server, UUID id) {
if (PlayerSessions.isAgent(server, id)) PlayerSessions.setMovementIntent(server, id, 0, 0, false);
PlayerSessions.remove(server, id);
FIXTURE_IDS.remove(id);
}
@GameTest(batch = "body_01_registration", template = "empty")
public void playerIsRegisteredAndHasNoInventedResources(GameTestHelper helper) {
var server = helper.getLevel().getServer();
UUID id = UUID.randomUUID();
try {
ServerPlayer player = create(helper, id);
helper.assertTrue(!(player instanceof FakePlayer), "El cuerpo no es un proxy FakePlayer");
helper.assertTrue(server.getPlayerList().getPlayer(id) == player
&& server.getPlayerList().getPlayers().contains(player)
&& player.serverLevel().players().contains(player), "PlayerList y el nivel registran al jugador");
helper.assertTrue(player.gameMode.getGameModeForPlayer() == GameType.SURVIVAL
&& !player.getAbilities().instabuild && !player.getAbilities().flying, "Es un jugador supervivencia");
helper.assertTrue(player.getInventory().isEmpty(), "Comienza sin regalarle recursos");
helper.assertTrue(player.connection.connection.isConnected()
&& PlayerSessions.deliveredPackets(server, id) > 0, "El canal local recibe la inicialización del jugador");
long before = PlayerSessions.completedPlayerTicks(server, id);
PlayerSessions.tick(server);
long once = PlayerSessions.completedPlayerTicks(server, id);
PlayerSessions.tick(server);
helper.assertTrue(once == before + 1 && PlayerSessions.completedPlayerTicks(server, id) == once,
"Dos llamadas del scheduler no duplican el tick físico");
removeFixture(server, id);
helper.assertTrue(server.getPlayerList().getPlayer(id) == null && !PlayerSessions.isAgent(server, id),
"La salida elimina la sesión y la presencia en PlayerList");
helper.succeed();
} finally { removeFixture(server, id); }
}
@GameTest(batch = "body_02_movement", template = "empty", timeoutTicks = 100)
public void registeredPlayerMovesAndProcessesHungerWithoutHuman(GameTestHelper helper) {
var server = helper.getLevel().getServer();
UUID id = UUID.randomUUID();
ServerPlayer player = create(helper, id);
double initialZ = player.getZ();
player.getFoodData().setSaturation(0);
player.getFoodData().addExhaustion(12);
PlayerSessions.setMovementIntent(server, id, 0, 1, false);
helper.onEachTick(() -> {
if (PlayerSessions.find(server, id) != null) PlayerSessions.setMovementIntent(server, id, 0, 1, false);
});
helper.runAfterDelay(30, () -> {
try {
ServerPlayer current = PlayerSessions.find(server, id);
helper.assertTrue(current != null && current.getZ() > initialZ + .5,
"El jugador se mueve mediante física local sin un cliente humano");
helper.assertTrue(current.getFoodData().getFoodLevel() < 20,
"FoodData procesa el agotamiento vanilla");
helper.assertTrue(PlayerSessions.completedPlayerTicks(server, id) >= 29,
"El scheduler mantiene el tick de jugador");
helper.succeed();
} finally { removeFixture(server, id); }
});
}
@GameTest(batch = "body_03_playerdata", template = "empty")
public void playerDataPersistsAcrossSessionReplacement(GameTestHelper helper) {
var server = helper.getLevel().getServer();
UUID id = UUID.randomUUID();
try {
ServerPlayer first = create(helper, id);
first.getInventory().setItem(0, new ItemStack(Items.STONE, 3));
first.getFoodData().setFoodLevel(12);
first.setHealth(16);
removeFixture(server, id);
ServerPlayer loaded = create(helper, id);
helper.assertTrue(loaded != first && loaded.getUUID().equals(id), "La identidad sobrevive al reemplazo de cuerpo");
helper.assertTrue(loaded.getInventory().countItem(Items.STONE) == 3
&& loaded.getFoodData().getFoodLevel() == 12 && loaded.getHealth() == 16,
"Carga inventario, hambre y salud desde playerdata sin duplicarlos");
helper.succeed();
} finally { removeFixture(server, id); }
}
@GameTest(batch = "body_04_respawn", template = "empty")
public void vanillaRespawnKeepsIdentityAndSingleRegisteredBody(GameTestHelper helper) {
var server = helper.getLevel().getServer();
UUID id = UUID.randomUUID();
try {
ServerPlayer dead = create(helper, id);
dead.hurt(dead.damageSources().genericKill(), 1000);
helper.assertTrue(dead.isDeadOrDying(), "La muerte real afecta al jugador");
ServerPlayer respawned = PlayerSessions.respawn(server, id);
helper.assertTrue(respawned != dead && respawned.getUUID().equals(id) && respawned.isAlive(),
"El respawn vanilla sustituye el cuerpo y conserva UUID");
helper.assertTrue(PlayerSessions.find(server, id) == respawned
&& server.getPlayerList().getPlayer(id) == respawned
&& server.getPlayerList().getPlayers().stream().filter(player -> player.getUUID().equals(id)).count() == 1,
"Existe un único cuerpo registrado después de reaparecer");
helper.assertTrue(respawned.connection.connection.isConnected(), "El nuevo cuerpo conserva el transporte local");
helper.succeed();
} finally { removeFixture(server, id); }
}
@GameTest(batch = "body_05_lease", template = "empty", timeoutTicks = 80)
public void movementLeaseStopsAnAbandonedController(GameTestHelper helper) {
var server = helper.getLevel().getServer();
UUID id = UUID.randomUUID();
ServerPlayer player = create(helper, id);
PlayerSessions.setMovementIntent(server, id, 0, 1, false);
helper.runAfterDelay(20, () -> {
ServerPlayer stopped = PlayerSessions.find(server, id);
if (stopped == null) {
helper.fail("La sesión se perdió antes de vencer la intención");
return;
}
double stoppedZ = stopped.getZ();
helper.runAfterDelay(10, () -> {
try {
ServerPlayer current = PlayerSessions.find(server, id);
helper.assertTrue(current != null && Math.abs(current.getZ() - stoppedZ) < .05
&& current.zza == 0 && current.xxa == 0,
"La intención caducada deja de producir movimiento tras disiparse la inercia");
helper.succeed();
} finally { removeFixture(server, id); }
});
});
}
@GameTest(batch = "body_06_dimension", template = "empty")
public void dimensionTransferPreservesTheRegisteredSession(GameTestHelper helper) {
var server = helper.getLevel().getServer();
UUID id = UUID.randomUUID();
try {
ServerPlayer player = create(helper, id);
var originalLevel = player.serverLevel();
var originalPosition = player.position();
var nether = server.getLevel(Level.NETHER);
helper.assertTrue(nether != null, "El servidor de prueba debe tener dimensión Nether");
// Teleport de fixture: prueba la transición técnica, no proporciona navegación al agente.
player.teleportTo(nether, .5, 96, .5, 0, 0);
helper.assertTrue(player.serverLevel() == nether && nether.players().contains(player)
&& !originalLevel.players().contains(player), "La presencia cambia de nivel sin dejar otro cuerpo");
helper.assertTrue(server.getPlayerList().getPlayer(id) == player && PlayerSessions.find(server, id) == player
&& !player.isChangingDimension(), "UUID, sesión y confirmación de posición siguen sincronizados");
player.teleportTo(originalLevel, originalPosition.x, originalPosition.y, originalPosition.z, 0, 0);
helper.assertTrue(player.serverLevel() == originalLevel && !nether.players().contains(player),
"Puede volver y retirar su presencia de la dimensión anterior");
helper.succeed();
} finally { removeFixture(server, id); }
}
@GameTest(batch = "body_07_fall", template = "empty", timeoutTicks = 160)
public void localPhysicsAppliesRealFallDamage(GameTestHelper helper) {
var server = helper.getLevel().getServer();
UUID id = UUID.randomUUID();
ServerPlayer player = create(helper, id);
player.getFoodData().setFoodLevel(10); // Evita que la regeneración oculte el daño de la prueba.
player.getFoodData().setSaturation(0);
helper.runAfterDelay(65, () -> {
ServerPlayer current = PlayerSessions.find(server, id);
if (current == null) {
helper.fail("La sesión se perdió antes de probar la caída");
return;
}
// Se espera la protección normal de aparición antes de elevar el fixture ocho bloques.
current.teleportTo(current.getX(), current.getY() + 8, current.getZ());
current.setDeltaMovement(net.minecraft.world.phys.Vec3.ZERO);
});
helper.runAfterDelay(110, () -> {
try {
ServerPlayer landed = PlayerSessions.find(server, id);
helper.assertTrue(landed != null && landed.isAlive() && landed.onGround()
&& landed.getHealth() < landed.getMaxHealth(),
"La gravedad, colisión y caída producen daño real al jugador local");
helper.succeed();
} finally { removeFixture(server, id); }
});
}
@GameTest(batch = "body_08_safe_spawn", template = "empty")
public void newSpawnRejectsVoidAndUnsupportedAirBeforeJoining(GameTestHelper helper) {
var server = helper.getLevel().getServer();
var level = helper.getLevel();
PlayerSessions.install();
UUID id = UUID.randomUUID();
BlockPos safe = helper.absolutePos(new BlockPos(3, 2, 3));
helper.setBlock(new BlockPos(3, 1, 3), Blocks.DIRT);
int playersBefore = server.getPlayerList().getPlayers().size();
int sessionsBefore = PlayerSessions.players(server).size();
try {
assertRejectedSpawn(helper, id, new BlockPos(safe.getX(), level.getMinBuildHeight(), safe.getZ()));
assertRejectedSpawn(helper, id, safe.above(6));
helper.assertTrue(server.getPlayerList().getPlayers().size() == playersBefore
&& PlayerSessions.players(server).size() == sessionsBefore
&& server.getPlayerList().getPlayer(id) == null && PlayerSessions.find(server, id) == null,
"Una aparición insegura se rechaza antes de registrar un cuerpo o cambiar participantes");
ServerPlayer player = PlayerSessions.create(server, id, name(id), level, safe);
FIXTURE_IDS.add(id);
helper.assertTrue(player.getInventory().isEmpty() && player.blockPosition().equals(safe)
&& level.getBlockState(safe.below()).is(Blocks.DIRT),
"El mismo UUID puede entrar sobre piso existente sin inventar objetos ni una plataforma");
player.teleportTo(player.getX(), player.getY() + 6, player.getZ());
var savedPosition = player.position();
PlayerSessions.remove(server, id);
ServerPlayer restored = PlayerSessions.create(server, id, name(id), level, safe);
helper.assertTrue(restored != player && restored.position().distanceToSqr(savedPosition) < .0001
&& restored.getInventory().isEmpty(),
"Un playerdata existente conserva una posición legítima en el aire sin recolocarla sobre el piso");
helper.succeed();
} finally { removeFixture(server, id); }
}
private static void assertRejectedSpawn(GameTestHelper helper, UUID id, BlockPos position) {
boolean rejected = false;
try { PlayerSessions.create(helper.getLevel().getServer(), id, name(id), helper.getLevel(), position); }
catch (IllegalArgumentException expected) { rejected = true; }
helper.assertTrue(rejected, "La identidad nueva necesita altura válida y apoyo sólido antes de ingresar");
}
}