WIP: colourful pearls

This commit is contained in:
QuImUfu 2024-10-11 16:06:40 +02:00
parent b4582640b7
commit 562a16c4f8
8 changed files with 472 additions and 200 deletions

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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<ItemStack> 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);

View File

@ -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();
}

View File

@ -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<TeleportRequest> 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<TeleportRequest> 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<TeleportRequest> {
@Override
public int compareTo(@NotNull TeleportHelper.TeleportRequest other) {
Comparator<TeleportRequest> comparator = Comparator
.comparing((TeleportRequest teleportRequest) -> teleportRequest.entity);
return comparator.compare(this, other);
}
}
}

View File

@ -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());
}
}

View File

@ -5,7 +5,8 @@
"mixins": [
"BlockChangeAndEntityMovementMixin",
"ServerPlayerEntityAccessor",
"ServerPlayNetworkHandlerAccessor"
"ServerPlayNetworkHandlerAccessor",
"ServerWorldAccessor"
],
"injectors": {
"defaultRequire": 1