Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Patch]: Fixed issues with teleport handler and added some features #966

Merged
merged 2 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package com.sigmundgranaas.forgero.fabric.gametest;

import com.sigmundgranaas.forgero.minecraft.common.handler.entity.TeleportHandler;

import com.sigmundgranaas.forgero.testutil.PlayerFactory;

import com.sigmundgranaas.forgero.testutil.TestPos;

import net.minecraft.entity.EntityType;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.test.GameTest;
import net.minecraft.test.TestContext;
import net.minecraft.util.math.BlockPos;

import net.minecraft.util.math.Vec3d;

import org.junit.jupiter.api.Assertions;

import static com.sigmundgranaas.forgero.fabric.gametest.AttributeApplicationTest.createFloor;
import static net.fabricmc.fabric.api.gametest.v1.FabricGameTest.EMPTY_STRUCTURE;

public class TeleportHandlerTest {
@GameTest(templateName = EMPTY_STRUCTURE)
public void testRandomTeleport(TestContext context) {
createFloor(context);
TestPos playerPos = TestPos.of(new BlockPos(3, 1, 3), context);
PlayerEntity player = PlayerFactory.builder(context)
.pos(playerPos.absolute())
.build()
.createPlayer();
Vec3d initialPos = player.getPos();

TeleportHandler teleportHandler = new TeleportHandler(true, true, 5, "minecraft:self");
teleportHandler.handle(player);

context.runAtTick(1, () -> {
Vec3d newPos = player.getPos();
Assertions.assertNotEquals(initialPos, newPos, "Player should have teleported");
Assertions.assertTrue(newPos.distanceTo(initialPos) <= 5, "Teleportation distance should be within 5 blocks");
Assertions.assertEquals(initialPos.y, newPos.y, 0.01, "Player should be on the ground");
context.complete();
});
}

@GameTest(templateName = EMPTY_STRUCTURE)
public void testLookDirectionTeleport(TestContext context) {
createFloor(context);
TestPos playerPos = TestPos.of(new BlockPos(3, 1, 3), context);
PlayerEntity player = PlayerFactory.builder(context)
.pos(playerPos.absolute())
.build()
.createPlayer();
player.setYaw(0); // Look towards positive Z
player.setPitch(0);
Vec3d initialPos = player.getPos();

TeleportHandler teleportHandler = new TeleportHandler(false, true, 5, "minecraft:self");
teleportHandler.handle(player);

context.runAtTick(1, () -> {
Vec3d newPos = player.getPos();
Assertions.assertNotEquals(initialPos, newPos, "Player should have teleported");
Assertions.assertTrue(newPos.z > initialPos.z, "Player should have teleported in positive Z direction");
Assertions.assertEquals(initialPos.y, newPos.y, 0.01, "Player should be on the ground");
context.complete();
});
}

@GameTest(templateName = EMPTY_STRUCTURE)
public void testTargetedEntityTeleport(TestContext context) {
createFloor(context);
TestPos playerPos = TestPos.of(new BlockPos(3, 1, 3), context);
PlayerEntity player = PlayerFactory.builder(context)
.pos(playerPos.absolute())
.build()
.createPlayer();
BlockPos initialPlayerPos = player.getBlockPos();

BlockPos initialPigPos = player.getBlockPos().north(2);
EntityType.PIG.spawn(context.getWorld(), null, null, initialPigPos, null, false, false);

TeleportHandler teleportHandler = new TeleportHandler(true, true, 5, "minecraft:targeted_entity");
teleportHandler.onHit(player, context.getWorld(), context.getWorld().getEntitiesByType(EntityType.PIG, player.getBoundingBox().expand(5), e -> true).get(0));

context.runAtTick(1, () -> {
BlockPos newPigPos = context.getWorld().getEntitiesByType(EntityType.PIG, player.getBoundingBox().expand(10), e -> true).get(0).getBlockPos();
Assertions.assertNotEquals(initialPlayerPos.north(2), newPigPos, "Pig should have teleported");
Assertions.assertTrue(initialPigPos.toCenterPos().distanceTo(newPigPos.toCenterPos()) <= 7, "Teleportation distance should be within 5 blocks");
context.complete();
});
}

@GameTest(templateName = EMPTY_STRUCTURE)
public void testAirTeleport(TestContext context) {
TestPos playerPos = TestPos.of(new BlockPos(3, 1, 3), context);
PlayerEntity player = PlayerFactory.builder(context)
.pos(playerPos.absolute())
.build()
.createPlayer();

Vec3d initialPos = player.getPos();
TeleportHandler teleportHandler = new TeleportHandler(true, false, 5, "minecraft:self");
teleportHandler.handle(player);

context.runAtTick(1, () -> {
Vec3d newPos = player.getPos();
Assertions.assertNotEquals(initialPos, newPos, "Player should have moved");
context.complete();
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"com.sigmundgranaas.forgero.fabric.gametest.HitHandlerTests",
"com.sigmundgranaas.forgero.fabric.gametest.StateTests",
"com.sigmundgranaas.forgero.fabric.gametest.EntityPredicateTest",
"com.sigmundgranaas.forgero.fabric.gametest.TeleportHandlerTest",
"com.sigmundgranaas.forgero.fabric.gametest.PredicateTests"
]
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
package com.sigmundgranaas.forgero.minecraft.common.handler.entity;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

import com.google.gson.JsonObject;
import com.sigmundgranaas.forgero.core.Forgero;
import com.sigmundgranaas.forgero.core.property.v2.feature.HandlerBuilder;
import com.sigmundgranaas.forgero.core.property.v2.feature.JsonBuilder;
import lombok.Getter;
import lombok.experimental.Accessors;
import org.apache.logging.log4j.Logger;

import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.entity.Entity;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;

import java.util.Collections;
import java.util.Random;

/**
* Represents a handler that teleports the entity based on the configured parameters.
*
Expand All @@ -38,6 +46,8 @@ public class TeleportHandler implements EntityBasedHandler {
public static final String TYPE = "minecraft:teleport";
public static final JsonBuilder<TeleportHandler> BUILDER = HandlerBuilder.fromObject(TeleportHandler.class, TeleportHandler::fromJson);

private static final int MAX_ATTEMPTS = 10;
private static final Logger LOGGER = Forgero.LOGGER;
private final boolean random;
private final boolean onGround;
private final int maxDistance;
Expand Down Expand Up @@ -83,9 +93,9 @@ public static TeleportHandler fromJson(JsonObject json) {
*/
@Override
public void onHit(Entity source, World world, Entity targetEntity) {
if(this.target.equals("target")){
if ("minecraft:targeted_entity".equals(target)) {
teleportEntity(targetEntity, world);
}else{
} else if ("minecraft:attacker".equals(target) || "minecraft:self".equals(target)) {
teleportEntity(source, world);
}
}
Expand All @@ -97,14 +107,104 @@ public void handle(Entity entity) {

/**
* This method is triggered upon hitting a block.
* Teleports the entity based on the configured parameters.
* Teleports the entity or block based on the configured parameters.
*
* @param source The source entity.
* @param world The world where the event occurred.
* @param pos The targeted entity.
* @param pos The position of the hit block.
*/
@Override
public void onHit(Entity source, World world, BlockPos pos) {
if ("minecraft:self".equals(target)) {
teleportEntity(source, world);
} else if ("minecraft:hit_position".equals(target)) {
teleportToPosition(source, world, pos);
} else if ("minecraft:targeted_block".equals(target)) {
teleportBlock(world, pos);
}
}

private void teleportBlock(World world, BlockPos originalPos) {
BlockState blockState = world.getBlockState(originalPos);

if (blockState.isAir()) {
return; // Don't teleport air blocks
}

for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
BlockPos newPos = getRandomPosition(world, originalPos);

if (isSafeBlockTeleportLocation(world, newPos)) {
// Remove the block from its original position
world.setBlockState(originalPos, Blocks.AIR.getDefaultState());

// Place the block in the new position
world.setBlockState(newPos, blockState);

// If the block has a BlockEntity (like a chest), move its data
BlockEntity blockEntity = world.getBlockEntity(originalPos);
if (blockEntity != null) {
NbtCompound nbt = blockEntity.createNbt();
world.removeBlockEntity(originalPos);
BlockEntity newBlockEntity = world.getBlockEntity(newPos);
if (newBlockEntity != null) {
newBlockEntity.readNbt(nbt);
}
}

return;
}
}
LOGGER.warn("Failed to find a safe teleportation location for block at {} after {} attempts", originalPos, MAX_ATTEMPTS);
}


private BlockPos getRandomPosition(World world, BlockPos originalPos) {
Random random = new Random();
List<BlockPos> validPositions = new ArrayList<>();

for (int x = -maxDistance; x <= maxDistance; x++) {
for (int z = -maxDistance; z <= maxDistance; z++) {
if (onGround) {
for (int y = -maxDistance; y <= maxDistance; y++) {
BlockPos pos = originalPos.add(x, y, z);
if (isValidGroundPosition(world, pos)) {
validPositions.add(pos);
}
}
} else {
int y = random.nextInt(maxDistance * 2 + 1) - maxDistance;
BlockPos pos = originalPos.add(x, y, z);
if (isValidAirPosition(world, pos)) {
validPositions.add(pos);
}
}
}
}

if (validPositions.isEmpty()) {
return originalPos; // Return original position if no valid positions found
}

return validPositions.get(random.nextInt(validPositions.size()));
}

private boolean isValidGroundPosition(World world, BlockPos pos) {
return world.getBlockState(pos).isAir() &&
world.getBlockState(pos.up()).isAir() &&
world.getBlockState(pos.up(2)).isAir() &&
!world.getBlockState(pos.down()).isAir();
}

private boolean isValidAirPosition(World world, BlockPos pos) {
return world.getBlockState(pos).isAir() &&
world.getBlockState(pos.up()).isAir() &&
world.getBlockState(pos.down()).isAir();
}

private boolean isSafeBlockTeleportLocation(World world, BlockPos pos) {
return world.getBlockState(pos).isAir() &&
(world.getBlockState(pos.down()).isSolid() || !onGround);
}

private void teleportEntity(Entity entity, World world) {
Expand All @@ -117,22 +217,50 @@ private void teleportEntity(Entity entity, World world) {

private void teleportRandomly(Entity entity, World world) {
Random random = new Random();
double deltaX = random.nextDouble() * 2 - 1; // Random value between -1 and 1
double deltaZ = random.nextDouble() * 2 - 1; // Random value between -1 and 1
double deltaY = !onGround ? (random.nextDouble() * 2 - 1) : 0; // Random Y if air teleport is allowed
for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
double deltaX = random.nextDouble() * 2 - 1; // Random value between -1 and 1
double deltaZ = random.nextDouble() * 2 - 1; // Random value between -1 and 1
double deltaY = !onGround ? (random.nextDouble() * 2 - 1) : 0; // Random Y if air teleport is allowed

BlockPos newPos = entity.getBlockPos().add((int) (deltaX * maxDistance),
(int) (deltaY * maxDistance),
(int) (deltaZ * maxDistance));
teleportToPosition(entity, world, newPos);
BlockPos newPos = entity.getBlockPos().add((int) (deltaX * maxDistance),
(int) (deltaY * maxDistance),
(int) (deltaZ * maxDistance));

if (tryTeleport(entity, world, newPos)) {
return;
}
}
LOGGER.warn("Failed to find a safe teleportation location for entity {} after {} attempts", entity, MAX_ATTEMPTS);
}

private void teleportToPosition(Entity entity, World world, BlockPos pos) {
if (tryTeleport(entity, world, pos)) {
return;
}
if (tryTeleport(entity, world, pos.up())) {
return;
}
if (tryTeleport(entity, world, pos.down())) {
return;
}
LOGGER.warn("Failed to find a safe teleportation location for entity {} after {} attempts", entity, MAX_ATTEMPTS);
}

private void teleportInLookDirection(Entity entity, World world) {
Vec3d lookVec = entity.getCameraPosVec(0);
BlockPos newPos = entity.getBlockPos().add((int) (lookVec.x * maxDistance),
!onGround ? (int) (lookVec.y * maxDistance) : 0,
(int) (lookVec.z * maxDistance));
teleportToPosition(entity, world, newPos);
for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
BlockPos newPos = entity.getBlockPos().add((int) (lookVec.x * maxDistance),
!onGround ? (int) (lookVec.y * maxDistance) : 0,
(int) (lookVec.z * maxDistance));

if (tryTeleport(entity, world, newPos)) {
return;
}

// Adjust look vector slightly for next attempt
lookVec = lookVec.add(new Vec3d(0.1, 0.1, 0.1)).normalize();
}
LOGGER.warn("Failed to find a safe teleportation location for entity {} after {} attempts", entity, MAX_ATTEMPTS);
}

private boolean isSafeTeleportLocation(World world, BlockPos pos) {
Expand All @@ -142,15 +270,17 @@ private boolean isSafeTeleportLocation(World world, BlockPos pos) {
return false;
}
}
return true;
return world.getBlockState(pos.down()).isSolid() || !onGround;
}

private void teleportToPosition(Entity entity, World world, BlockPos newPos) {
private boolean tryTeleport(Entity entity, World world, BlockPos newPos) {
if (isSafeTeleportLocation(world, newPos)) {
entity.teleport(newPos.getX() + 0.5, newPos.getY(), newPos.getZ() + 0.5);
if (entity instanceof ServerPlayerEntity serverPlayer) {
((ServerPlayerEntity)entity).networkHandler.sendPacket(new PlayerPositionLookS2CPacket(entity.getX(), entity.getY(), entity.getZ(), serverPlayer.getYaw(), serverPlayer.getPitch(), Collections.emptySet(), 0));
serverPlayer.networkHandler.sendPacket(new PlayerPositionLookS2CPacket(entity.getX(), entity.getY(), entity.getZ(), serverPlayer.getYaw(), serverPlayer.getPitch(), Collections.emptySet(), 0));
}
return true;
}
return false;
}
}
Loading