diff --git a/src/main/java/quimufu/colourful_portals/ColourfulAirBlock.java b/src/main/java/quimufu/colourful_portals/ColourfulAirBlock.java index a97264a..f04b29c 100644 --- a/src/main/java/quimufu/colourful_portals/ColourfulAirBlock.java +++ b/src/main/java/quimufu/colourful_portals/ColourfulAirBlock.java @@ -131,7 +131,7 @@ public class ColourfulAirBlock extends Block implements FluidFillable { return this.getDefaultState().with(DYE_COLOR, color); } - public BlockState getRandomState(Random random) { + public BlockState getRandomState(org.joml.Random random) { DyeColor color = DyeColor.values()[random.nextInt(DyeColor.values().length)]; return this.getDefaultState().with(DYE_COLOR, color); } diff --git a/src/main/java/quimufu/colourful_portals/entity/ColourfulPearlEntity.java b/src/main/java/quimufu/colourful_portals/entity/ColourfulPearlEntity.java index 12d3274..28fe997 100644 --- a/src/main/java/quimufu/colourful_portals/entity/ColourfulPearlEntity.java +++ b/src/main/java/quimufu/colourful_portals/entity/ColourfulPearlEntity.java @@ -1,37 +1,20 @@ package quimufu.colourful_portals.entity; -import net.minecraft.block.BlockState; -import net.minecraft.block.Blocks; import net.minecraft.client.world.ClientWorld; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.projectile.thrown.ThrownItemEntity; import net.minecraft.item.Item; -import net.minecraft.particle.ParticleTypes; import net.minecraft.predicate.entity.EntityPredicates; -import net.minecraft.registry.Registries; -import net.minecraft.registry.RegistryKey; -import net.minecraft.registry.RegistryKeys; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; import net.minecraft.sound.SoundCategory; -import net.minecraft.sound.SoundEvents; -import net.minecraft.util.Identifier; import net.minecraft.util.hit.HitResult; -import net.minecraft.util.math.*; -import net.minecraft.world.Heightmap; -import net.minecraft.world.TeleportTarget; +import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; -import net.minecraft.world.border.WorldBorder; -import net.minecraft.world.dimension.DimensionType; -import org.jetbrains.annotations.Nullable; -import org.joml.Vector3f; import quimufu.colourful_portals.ColourfulPortalsMod; -import quimufu.colourful_portals.config.ColourfulPortalConfig; -import quimufu.colourful_portals.portal.PortalHelper; -import quimufu.colourful_portals.util.AdditionalMath; +import quimufu.colourful_portals.util.TeleportParticleHelper; +import quimufu.colourful_portals.util.TeleportHelper; import java.util.List; @@ -69,25 +52,13 @@ public class ColourfulPearlEntity entities.stream() .filter(e -> e.squaredDistanceTo(this) < 25) - .forEach(this::spawnTeleportationParticles); - } - } - - private void spawnTeleportationParticles(Entity entity) { - for (int i = 0; i < 64; ++i) { - Box box = entity.getBoundingBox(); - float xOffset = (float) ((AdditionalMath.root(random.nextFloat() * 2 - 1, 3) / 2) * box.getLengthX() * 2); - float yOffset = (float) (AdditionalMath.root(random.nextFloat(), 3) * box.getLengthY() * 2); - float zOffset = (float) ((AdditionalMath.root(random.nextFloat() * 2 - 1, 3) / 2) * box.getLengthZ() * 2); - - Vector3f offset = new Vector3f(xOffset, yOffset, zOffset); - addHitParticle(this.getWorld(), entity, offset); + .forEach(entity -> TeleportParticleHelper.spawnTeleportationParticles(entity, getWorld())); } } @Override protected void onCollision(HitResult hitResult) { - if (getWorld().isClient()) { + if (!(getWorld() instanceof ServerWorld serverWorld)) { return; } if (isRemoved()) { @@ -97,172 +68,14 @@ public class ColourfulPearlEntity .getOtherEntities(this, this.getBoundingBox().expand(5), EntityPredicates.EXCEPT_SPECTATOR .and(entity -> entity instanceof LivingEntity) .and(entity -> !entity.getType().isIn(ColourfulPortalsMod.COLOURFUL_PEARL_NOT_TELEPORTABLE))); + entities.stream() .filter(e -> e.squaredDistanceTo(this) < 25) - .forEach(this::tryTeleport); + .forEach(entity -> { + Vec3d pos = this.getPos(); + TeleportHelper.markForTeleport(entity,pos.toVector3f(), serverWorld); + }); this.discard(); } - private void tryTeleport(Entity entity) { - if (!(getWorld() instanceof ServerWorld serverWorld)) { - return; - } - if (entity == null || !canTeleportEntityTo(entity, serverWorld)) { - return; - } - if (entity.hasVehicle()) { - entity.detach(); - } - TeleportTarget target = findTarget(serverWorld, entity, null); - while (target == null || !targetValid(target, entity)) { - target = findTarget(serverWorld, entity, target); - } - prepareTarget(target, entity); - - if (entity instanceof ServerPlayerEntity serverPlayerEntity) { - if (serverPlayerEntity.networkHandler.isConnectionOpen()) { - - Entity entityAfterTeleport = entity.teleportTo(target); - if (entityAfterTeleport == null) { - return; - } - entityAfterTeleport.onLanding(); - serverPlayerEntity.clearCurrentExplosion(); - entityAfterTeleport.damage(this.getDamageSources().fall(), 5.0f); - serverPlayerEntity.playSoundToPlayer(SoundEvents.ENTITY_PLAYER_TELEPORT, SoundCategory.PLAYERS, 1F, 0.75F); - } - } else { - Entity entityAfterTeleport = entity.teleportTo(target); - if (entityAfterTeleport == null) { - return; - } - entityAfterTeleport.onLanding(); - serverWorld.playSound(entityAfterTeleport, entityAfterTeleport.getBlockPos(), - SoundEvents.ENTITY_PLAYER_TELEPORT, SoundCategory.NEUTRAL, - 1F, 0.75F); - } - } - - private void addHitParticle(World world, Entity entity, Vector3f offset) { - Vector3f endPos = randomInside(this.getDimensions(null).getBoxAt(entity.getPos())) - .add(offset); - world.addParticle(ParticleTypes.PORTAL, - endPos.x, endPos.y, endPos.z, - -offset.x, -offset.y, -offset.z); - } - - private Vector3f randomInside(Box box) { - return box.getMinPos().toVector3f() - .add(((float) box.getLengthX()) * random.nextFloat(), - ((float) box.getLengthY()) * random.nextFloat(), - ((float) box.getLengthZ()) * random.nextFloat()); - } - - private boolean targetValid(TeleportTarget target, Entity entity) { - Box boundingBox = entity.getDimensions(entity.getPose()) - .getBoxAt(target.pos()) - .stretch(0, -1, 0); - boolean foundAir = false; - boolean foundNonAir = false; - for (BlockPos pos : PortalHelper.blockPosInBox(boundingBox)) { - BlockState blockState = target.world().getBlockState(pos); - if (!blockState.isIn(ColourfulPortalsMod.COLOURFUL_PEARL_REPLACEABLE_BLOCK_TAG)) { - ColourfulPortalsMod.LOGGER.info("invalid location at {}, block {}", pos, Registries.BLOCK.getId(blockState.getBlock())); - return false; - } - if (blockState.isAir()) { - foundAir = true; - } else { - foundNonAir = true; - } - } - return foundAir && foundNonAir; - } - - - private void prepareTarget(TeleportTarget target, Entity entity) { - Box entityBody = entity.getDimensions(entity.getPose()).getBoxAt(target.pos()); - double minY = entityBody.getMinPos().getY(); - Box floor = entityBody.withMinY(minY - 1).withMaxY(minY - 0.5); - for (BlockPos pos : PortalHelper.blockPosInBox(entityBody)) { - target.world().setBlockState(pos, ColourfulPortalsMod.COLOURFUL_AIR.getRandomState(random)); - } - for (BlockPos pos : PortalHelper.blockPosInBox(floor)) { - target.world().setBlockState(pos, Blocks.STONE.getDefaultState()); - } - - } - - private @Nullable TeleportTarget findTarget(ServerWorld serverWorld, Entity entity, TeleportTarget target) { - if (target != null - //position makes sense to retry - && !isEmptyPosition(target) - //prevents potential infinite loop if position does have weird block compositions - && random.nextInt(target.world().getLogicalHeight() * 2) != 0) { - return new TeleportTarget(target.world(), - target.pos().withAxis(Direction.Axis.Y, getTargetY(target.world())), - target.velocity(), - target.yaw(), - target.pitch(), - target.postDimensionTransition()); - } - //re-use targetWorld to prevent dimensions with less valid spawning positions from becoming less likely - ServerWorld targetWorld = target == null ? getTargetWorld(serverWorld) : target.world(); - if (targetWorld == null) { - return null; - } - //adjust position within target - Vec3d minPos = Vec3d.of(getTargetPos(serverWorld, targetWorld, getPos())); - int width = MathHelper.ceil(entity.getBoundingBox().getLengthX()); - int depth = MathHelper.ceil(entity.getBoundingBox().getLengthZ()); - Vec3d targetLocation = minPos.add((width % 2) * 0.5F, 0, (depth % 2) * 0.5F); - - return new TeleportTarget(targetWorld, targetLocation, entity.getVelocity(), entity.getYaw(), entity.getPitch(), TeleportTarget.NO_OP); - } - - private static boolean isEmptyPosition(TeleportTarget target) { - return target.world().getTopY(Heightmap.Type.WORLD_SURFACE, (int) target.pos().x, (int) target.pos().z) == target.world().getBottomY(); - } - - private @Nullable ServerWorld getTargetWorld(ServerWorld serverWorld) { - if (this.getRandom().nextDouble() < ColourfulPortalConfig.pearlSameDimensionLikelihood) { - return serverWorld; - } - - MinecraftServer server = serverWorld.getServer(); - ColourfulPortalConfig.addMissingDimensionsToConfig(server); - Identifier targetWorldId = ColourfulPortalsMod.DIMENSION_WEIGHTS_COLOURFUL_PEARL.getWeighted(random); - - return server.getWorld(RegistryKey.of(RegistryKeys.WORLD, targetWorldId)); - } - - private BlockPos getTargetPos(ServerWorld fromWorld, ServerWorld toWorld, Vec3d pos) { - - double distance = ColourfulPortalConfig.minPearlDistance + - getRandom().nextDouble() * - (ColourfulPortalConfig.maxPearlDistance - ColourfulPortalConfig.minPearlDistance); - - double angle = Math.PI * 2 * getRandom().nextDouble(); - int targetY = getTargetY(toWorld); - - Vec3d target = new Vec3d(pos.getX() + Math.cos(angle) * distance, targetY, pos.getZ() + Math.sin(angle) * distance); - - WorldBorder worldBorder = toWorld.getWorldBorder(); - double d = DimensionType.getCoordinateScaleFactor(fromWorld.getDimension(), toWorld.getDimension()); - return worldBorder.clamp(target.getX() * d, target.getY(), target.getZ() * d); - } - - private int getTargetY(ServerWorld toWorld) { - return toWorld.getBottomY() + 4 + (getRandom().nextInt(toWorld.getLogicalHeight() - 8)); - } - - private static boolean canTeleportEntityTo(Entity entity, World world) { - if (entity.getWorld().getRegistryKey() == world.getRegistryKey()) { - if (entity instanceof LivingEntity livingEntity) { - return livingEntity.isAlive() && !livingEntity.isSleeping(); - } - return entity.isAlive(); - } - return entity.canUsePortals(true); - } } diff --git a/src/main/java/quimufu/colourful_portals/general_util/WeightedSelector.java b/src/main/java/quimufu/colourful_portals/general_util/WeightedSelector.java index 175a4ff..92d5a40 100644 --- a/src/main/java/quimufu/colourful_portals/general_util/WeightedSelector.java +++ b/src/main/java/quimufu/colourful_portals/general_util/WeightedSelector.java @@ -1,6 +1,7 @@ package quimufu.colourful_portals.general_util; -import net.minecraft.util.math.random.Random; + +import org.joml.Random; import java.util.TreeMap; diff --git a/src/main/java/quimufu/colourful_portals/item/ColourfulPearlItem.java b/src/main/java/quimufu/colourful_portals/item/ColourfulPearlItem.java index 2466c0c..2373307 100644 --- a/src/main/java/quimufu/colourful_portals/item/ColourfulPearlItem.java +++ b/src/main/java/quimufu/colourful_portals/item/ColourfulPearlItem.java @@ -1,25 +1,74 @@ package quimufu.colourful_portals.item; +import net.minecraft.entity.LivingEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; +import net.minecraft.item.ItemUsage; +import net.minecraft.server.world.ServerWorld; import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvent; import net.minecraft.sound.SoundEvents; import net.minecraft.stat.Stats; import net.minecraft.util.Hand; import net.minecraft.util.TypedActionResult; +import net.minecraft.util.UseAction; import net.minecraft.world.World; +import net.minecraft.world.event.GameEvent; import quimufu.colourful_portals.entity.ColourfulPearlEntity; +import quimufu.colourful_portals.util.TeleportParticleHelper; +import quimufu.colourful_portals.util.TeleportHelper; public class ColourfulPearlItem extends Item { + private static final int MAX_USE_TIME = 5; public ColourfulPearlItem(Item.Settings settings) { super(settings); } + @Override + public ItemStack finishUsing(ItemStack stack, World world, LivingEntity user) { + if(user instanceof PlayerEntity player){ + player.getItemCooldownManager().set(this, 50); + } + if (world instanceof ServerWorld serverWorld) { + TeleportHelper.markForTeleport(user, user.getPos().toVector3f(), serverWorld); + } + stack.decrementUnlessCreative(1, user); + user.emitGameEvent(GameEvent.EAT); + return stack; + } + + @Override + public UseAction getUseAction(ItemStack stack) { + return UseAction.DRINK; + } + + @Override + public SoundEvent getDrinkSound() { + //todo + return SoundEvents.ITEM_HONEY_BOTTLE_DRINK; + } + + @Override + public SoundEvent getEatSound() { + //todo + return SoundEvents.ITEM_HONEY_BOTTLE_DRINK; + } + + @Override + public int getMaxUseTime(ItemStack stack, LivingEntity user) { + return MAX_USE_TIME; + } + @Override public TypedActionResult use(World world, PlayerEntity user, Hand hand) { + if (user.isSneaking()) { + TeleportParticleHelper.spawnTeleportationParticles(user, world); + return ItemUsage.consumeHeldItem(world, user, hand); + } + ItemStack stackInHand = user.getStackInHand(hand); world.playSound(null, user.getX(), user.getY(), user.getZ(), SoundEvents.ENTITY_ENDER_PEARL_THROW, SoundCategory.NEUTRAL, 0.5f, 0.4f / (world.getRandom().nextFloat() * 0.4f + 0.8f)); user.getItemCooldownManager().set(this, 50); diff --git a/src/main/java/quimufu/colourful_portals/mixin/ServerWorldAccessor.java b/src/main/java/quimufu/colourful_portals/mixin/ServerWorldAccessor.java new file mode 100644 index 0000000..f3ebb40 --- /dev/null +++ b/src/main/java/quimufu/colourful_portals/mixin/ServerWorldAccessor.java @@ -0,0 +1,14 @@ +package quimufu.colourful_portals.mixin; + +import net.minecraft.entity.Entity; +import net.minecraft.server.world.ServerEntityManager; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.world.EntityList; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(ServerWorld.class) +public interface ServerWorldAccessor { + @Accessor + EntityList getEntityList(); +} diff --git a/src/main/java/quimufu/colourful_portals/util/TeleportHelper.java b/src/main/java/quimufu/colourful_portals/util/TeleportHelper.java new file mode 100644 index 0000000..3f25682 --- /dev/null +++ b/src/main/java/quimufu/colourful_portals/util/TeleportHelper.java @@ -0,0 +1,358 @@ +package quimufu.colourful_portals.util; + +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.mob.MobEntity; +import net.minecraft.registry.Registries; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.ServerTask; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ChunkTicketType; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvents; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.*; +import net.minecraft.world.Heightmap; +import net.minecraft.world.TeleportTarget; +import net.minecraft.world.World; +import net.minecraft.world.border.WorldBorder; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.dimension.DimensionType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.joml.Random; +import org.joml.Vector3f; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import quimufu.colourful_portals.ColourfulPortalsMod; +import quimufu.colourful_portals.config.ColourfulPortalConfig; +import quimufu.colourful_portals.mixin.ServerWorldAccessor; +import quimufu.colourful_portals.portal.PortalHelper; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.UUID; +import java.util.concurrent.ConcurrentSkipListSet; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +public class TeleportHelper extends ServerTask { + + private static final Logger log = LoggerFactory.getLogger(TeleportHelper.class); + public static final int TICK_DELAY = 2; + private final Random random; + private final MinecraftServer minecraftServer; + private final ConcurrentSkipListSet toTeleport = new ConcurrentSkipListSet<>(); + private static TeleportHelper INSTANCE; + private final AtomicBoolean tickScheduled = new AtomicBoolean(false); + private int lastExecTick; + private final ScheduledExecutorService sleeperExecutor = Executors.newSingleThreadScheduledExecutor(); + + private static TeleportHelper getInstance(MinecraftServer server) { + if (INSTANCE == null) { + INSTANCE = new TeleportHelper(server); + } + return INSTANCE; + } + + private TeleportHelper(MinecraftServer server) { + super(server.getTicks(), null); + this.minecraftServer = server; + this.random = new Random(); + } + + + private boolean targetValid(Entity entity, ServerWorld world, Vector3f targetPos, boolean force) { + Box boundingBox = entity.getDimensions(entity.getPose()) + .getBoxAt(new Vec3d(targetPos)) + .stretch(0, -1, 0); + boolean foundAir = false; + boolean foundNonAir = false; + for (BlockPos pos : PortalHelper.blockPosInBox(boundingBox)) { + BlockState blockState = world.getBlockState(pos); + if (!blockState.isIn(ColourfulPortalsMod.COLOURFUL_PEARL_REPLACEABLE_BLOCK_TAG)) { + ColourfulPortalsMod.LOGGER.info("invalid location at {}, block {}", pos, Registries.BLOCK.getId(blockState.getBlock())); + return false; + } + if (blockState.isAir()) { + foundAir = true; + } else { + foundNonAir = true; + } + } + return (foundNonAir) && (foundAir || force); + } + + private @Nullable TeleportTarget getTarget(Entity entity, ServerWorld serverWorld, Vector3f from, ServerWorld targetWorld, BlockPos targetBlockPos) { + minecraftServer.getProfiler().push("getChunk"); + Chunk chunk = targetWorld.getChunk(targetBlockPos); + minecraftServer.getProfiler().pop(); + + if (isEmptyPosition(targetWorld, targetBlockPos, chunk)) { + log.info("empty at {}, retrying", targetBlockPos); + TeleportHelper.markForTeleport(entity, from, serverWorld); + return null; + } + Vector3f targetPos = Vec3d.of(targetBlockPos).toVector3f(); + int width = MathHelper.ceil(entity.getBoundingBox().getLengthX()); + int depth = MathHelper.ceil(entity.getBoundingBox().getLengthZ()); + targetPos.add((width % 2) * 0.5F, 0, (depth % 2) * 0.5F); + minecraftServer.getProfiler().push("valid check"); + int tries = 0; + while (!targetValid(entity, targetWorld, targetPos, tries > targetWorld.getLogicalHeight() * 2)) { + if (tries > targetWorld.getLogicalHeight() * 4) { + break; + } + targetPos.y = getTargetY(targetWorld); + tries++; + } + minecraftServer.getProfiler().pop(); + return new TeleportTarget(targetWorld, new Vec3d(targetPos), entity.getVelocity(), entity.getYaw(), entity.getPitch(), TeleportTarget.NO_OP); + } + + private boolean isEmptyPosition(ServerWorld world, BlockPos target, Chunk chunk) { + minecraftServer.getProfiler().push("sampleHeightmap"); + int height = chunk.sampleHeightmap(Heightmap.Type.WORLD_SURFACE, target.getX() & 0xF, target.getZ() & 0xF) + 1; + minecraftServer.getProfiler().pop(); + return height == world.getBottomY(); + } + + private @Nullable ServerWorld getTargetWorld(ServerWorld serverWorld) { + if (random.nextFloat() < ColourfulPortalConfig.pearlSameDimensionLikelihood) { + return serverWorld; + } + + MinecraftServer server = serverWorld.getServer(); + ColourfulPortalConfig.addMissingDimensionsToConfig(server); + Identifier targetWorldId = ColourfulPortalsMod.DIMENSION_WEIGHTS_COLOURFUL_PEARL.getWeighted(random); + + return server.getWorld(RegistryKey.of(RegistryKeys.WORLD, targetWorldId)); + } + + private BlockPos getTargetPos(ServerWorld fromWorld, ServerWorld toWorld, Vector3f pos) { + + double distance = ColourfulPortalConfig.minPearlDistance + + random.nextFloat() * + (ColourfulPortalConfig.maxPearlDistance - ColourfulPortalConfig.minPearlDistance); + + double angle = Math.PI * 2 * random.nextFloat(); + int targetY = getTargetY(toWorld); + + Vec3d target = new Vec3d(pos.x + Math.cos(angle) * distance, targetY, pos.z + Math.sin(angle) * distance); + + WorldBorder worldBorder = toWorld.getWorldBorder(); + double d = DimensionType.getCoordinateScaleFactor(fromWorld.getDimension(), toWorld.getDimension()); + return worldBorder.clamp(target.getX() * d, target.getY(), target.getZ() * d); + } + + private int getTargetY(ServerWorld toWorld) { + return toWorld.getBottomY() + 4 + (random.nextInt(toWorld.getLogicalHeight() - 8)); + } + + + public void tryTeleport(Entity entity, Vector3f from, ServerWorld serverWorld, BlockPos to, ServerWorld toWorld) { + if (entity == null) { + return; + } + if (entity.hasVehicle()) { + entity.detach(); + } + minecraftServer.getProfiler().push("getTarget"); + TeleportTarget target = getTarget(entity, serverWorld, from, toWorld, to); + minecraftServer.getProfiler().pop(); + if (target == null) { + return; + } + prepareTarget(target, entity); + ((ServerWorldAccessor) serverWorld).getEntityList() + .add(entity); + + minecraftServer.getProfiler().push("teleportTo"); + if (entity instanceof ServerPlayerEntity serverPlayerEntity) { + if (serverPlayerEntity.networkHandler.isConnectionOpen()) { + + Entity entityAfterTeleport = entity.teleportTo(target); + if (entityAfterTeleport == null) { + minecraftServer.getProfiler().pop(); + return; + } + entityAfterTeleport.onLanding(); + serverPlayerEntity.clearCurrentExplosion(); + entityAfterTeleport.damage(entityAfterTeleport.getDamageSources().fall(), 5.0f); + serverPlayerEntity.playSoundToPlayer(SoundEvents.ENTITY_PLAYER_TELEPORT, SoundCategory.PLAYERS, 1F, 0.75F); + } + } else { + Entity entityAfterTeleport = entity.teleportTo(target); + if (entityAfterTeleport == null) { + minecraftServer.getProfiler().pop(); + return; + } + entityAfterTeleport.onLanding(); + serverWorld.playSound(entityAfterTeleport, entityAfterTeleport.getBlockPos(), + SoundEvents.ENTITY_PLAYER_TELEPORT, SoundCategory.NEUTRAL, + 1F, 0.75F); + } + minecraftServer.getProfiler().pop(); + } + + + private void prepareTarget(TeleportTarget target, Entity entity) { + Box entityBody = entity.getDimensions(entity.getPose()).getBoxAt(target.pos()); + double minY = entityBody.getMinPos().getY(); + Box floor = entityBody.withMinY(minY - 1).withMaxY(minY - 0.5); + for (BlockPos pos : PortalHelper.blockPosInBox(entityBody)) { + target.world().setBlockState(pos, ColourfulPortalsMod.COLOURFUL_AIR.getRandomState(random)); + } + for (BlockPos pos : PortalHelper.blockPosInBox(floor)) { + target.world().setBlockState(pos, Blocks.STONE.getDefaultState()); + } + + } + + private static boolean canTeleportEntityTo(Entity entity, World world) { + if (entity.getWorld().getRegistryKey() == world.getRegistryKey()) { + if (entity instanceof LivingEntity livingEntity) { + return livingEntity.isAlive() && !livingEntity.isSleeping(); + } + return entity.isAlive(); + } + return entity.canUsePortals(true); + } + + + private void addTeleportRequest(Entity entity, Vector3f from, + ServerWorld fromWorld, + boolean originalInvulnerability) { + ServerWorld targetWorld = getTargetWorld(fromWorld); + if (targetWorld == null) { + log.info("couldn't find target world"); + return; + } + BlockPos targetBlockPos = getTargetPos(fromWorld, targetWorld, from); + if (entity instanceof MobEntity mobEntity + && !mobEntity.isPersistent() + && !mobEntity.cannotDespawn() + && mobEntity.canImmediatelyDespawn(targetBlockPos.getSquaredDistance(new Vec3d(from))) + && !targetWorld.isChunkLoaded(targetBlockPos)) { + log.info("despawning {} instead of teleporting it OOB", mobEntity.getName().getString()); + + ((ServerWorldAccessor) fromWorld).getEntityList().add(entity); + mobEntity.discard(); + return; + } + if (!canTeleportEntityTo(entity, targetWorld)) { + return; + } + targetWorld.getChunkManager() + .addTicket(ChunkTicketType.PORTAL, new ChunkPos(targetBlockPos), 2, targetBlockPos); + toTeleport.add(new TeleportRequest(entity.getUuid(), + from, + fromWorld.getRegistryKey().getValue(), + originalInvulnerability, + targetBlockPos, + targetWorld.getRegistryKey().getValue() + )); + + if (!tickScheduled.getAndSet(true)) { + long nanosPerTick = minecraftServer.getTickManager().getNanosPerTick(); + sleeperExecutor.schedule(() -> minecraftServer.send(this), nanosPerTick * 16, TimeUnit.NANOSECONDS); + } + } + + @Override + public void run() { + log.info("{}", minecraftServer.getTicks()); + int nextExecIn = (lastExecTick + TICK_DELAY) - minecraftServer.getTicks(); + long nanosPerTick = minecraftServer.getTickManager().getNanosPerTick(); + if (nextExecIn > 0) { + minecraftServer.send(this); + return; + } + this.lastExecTick = minecraftServer.getTicks(); + minecraftServer.getProfiler().push("teleportLoaded"); + teleportLoaded(); + minecraftServer.getProfiler().pop(); + log.info("there are currently {} entities awaiting teleportation", toTeleport.size()); + if (!toTeleport.isEmpty()) { + minecraftServer.send(this); + } else { + tickScheduled.set(false); + } + } + + + private void teleportLoaded() { + Iterator iterator = toTeleport.iterator(); + while (iterator.hasNext()) { + TeleportRequest teleportRequest = iterator.next(); + if (teleportRequest == null) { + //empty for some reason! + iterator.remove(); + log.error("null teleportRequest"); + return; + } + ServerWorld fromWorld = minecraftServer.getWorld(RegistryKey.of(RegistryKeys.WORLD, teleportRequest.fromWorldId)); + if (fromWorld == null) { + iterator.remove(); + log.error("lost from world"); + return; + } + ServerWorld toWorld = minecraftServer.getWorld(RegistryKey.of(RegistryKeys.WORLD, teleportRequest.toWorldId)); + if (toWorld == null) { + iterator.remove(); + log.error("lost to world"); + return; + } + Entity entity = fromWorld.getEntity(teleportRequest.entity); + if (entity == null) { + iterator.remove(); + log.error("lost entity"); + return; + } + int x = teleportRequest.to.getX(); + int z = teleportRequest.to.getZ(); + if (toWorld.getChunkManager().isChunkLoaded(ChunkSectionPos.getSectionCoord(x), ChunkSectionPos.getSectionCoord(z))) { + log.info("chunkLoaded {}", teleportRequest.to); + minecraftServer.getProfiler().push("tryTeleport"); + tryTeleport(entity, teleportRequest.from, fromWorld, teleportRequest.to, toWorld); + minecraftServer.getProfiler().pop(); + iterator.remove(); + return; + } else { + toWorld.getChunkManager() + .addTicket(ChunkTicketType.PORTAL, new ChunkPos(teleportRequest.to), 2, teleportRequest.to); + } + } + + } + + public static void markForTeleport(Entity entity, Vector3f from, ServerWorld serverWorld) { + ((ServerWorldAccessor) serverWorld).getEntityList() + .remove(entity); + boolean originalInvulnerability = entity.isInvulnerable(); + entity.setInvulnerable(true); + getInstance(serverWorld.getServer()) + .addTeleportRequest(entity, from, serverWorld, originalInvulnerability); + + } + + private record TeleportRequest(UUID entity, Vector3f from, + Identifier fromWorldId, + boolean originalInvulnerability, + BlockPos to, + Identifier toWorldId) implements Comparable { + @Override + public int compareTo(@NotNull TeleportHelper.TeleportRequest other) { + Comparator comparator = Comparator + .comparing((TeleportRequest teleportRequest) -> teleportRequest.entity); + return comparator.compare(this, other); + } + } +} diff --git a/src/main/java/quimufu/colourful_portals/util/TeleportParticleHelper.java b/src/main/java/quimufu/colourful_portals/util/TeleportParticleHelper.java new file mode 100644 index 0000000..eab4ddb --- /dev/null +++ b/src/main/java/quimufu/colourful_portals/util/TeleportParticleHelper.java @@ -0,0 +1,36 @@ +package quimufu.colourful_portals.util; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityDimensions; +import net.minecraft.particle.ParticleTypes; +import net.minecraft.util.math.Box; +import net.minecraft.world.World; +import org.joml.Random; +import org.joml.Vector3f; + +public class TeleportParticleHelper { + private static final Random random = new Random(); + + public static void spawnTeleportationParticles(Entity entity, World world) { + for (int i = 0; i < 64; ++i) { + Box box = entity.getBoundingBox(); + float xOffset = (float) ((AdditionalMath.root(random.nextFloat() * 2 - 1, 3) / 2) * box.getLengthX() * 2); + float yOffset = (float) (AdditionalMath.root(random.nextFloat(), 3) * box.getLengthY() * 2); + float zOffset = (float) ((AdditionalMath.root(random.nextFloat() * 2 - 1, 3) / 2) * box.getLengthZ() * 2); + + Vector3f offset = new Vector3f(xOffset, yOffset, zOffset); + EntityDimensions dimensions = EntityDimensions.fixed(0.25f, 0.25f); + Vector3f endPos = randomInside(dimensions.getBoxAt(entity.getPos())).add(offset); + world.addParticle(ParticleTypes.PORTAL, + endPos.x, endPos.y, endPos.z, + -offset.x, -offset.y, -offset.z); + } + } + + private static Vector3f randomInside(Box box) { + return box.getMinPos().toVector3f() + .add(((float) box.getLengthX()) * random.nextFloat(), + ((float) box.getLengthY()) * random.nextFloat(), + ((float) box.getLengthZ()) * random.nextFloat()); + } +} diff --git a/src/main/resources/colourful_portals.mixins.json b/src/main/resources/colourful_portals.mixins.json index 910ea8b..a335edc 100644 --- a/src/main/resources/colourful_portals.mixins.json +++ b/src/main/resources/colourful_portals.mixins.json @@ -5,7 +5,8 @@ "mixins": [ "BlockChangeAndEntityMovementMixin", "ServerPlayerEntityAccessor", - "ServerPlayNetworkHandlerAccessor" + "ServerPlayNetworkHandlerAccessor", + "ServerWorldAccessor" ], "injectors": { "defaultRequire": 1