WIP: colourful pearls
This commit is contained in:
parent
b4582640b7
commit
562a16c4f8
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
358
src/main/java/quimufu/colourful_portals/util/TeleportHelper.java
Normal file
358
src/main/java/quimufu/colourful_portals/util/TeleportHelper.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -5,7 +5,8 @@
|
|||
"mixins": [
|
||||
"BlockChangeAndEntityMovementMixin",
|
||||
"ServerPlayerEntityAccessor",
|
||||
"ServerPlayNetworkHandlerAccessor"
|
||||
"ServerPlayNetworkHandlerAccessor",
|
||||
"ServerWorldAccessor"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
|
|
Loading…
Reference in New Issue
Block a user