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);
|
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)];
|
DyeColor color = DyeColor.values()[random.nextInt(DyeColor.values().length)];
|
||||||
return this.getDefaultState().with(DYE_COLOR, color);
|
return this.getDefaultState().with(DYE_COLOR, color);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +1,20 @@
|
||||||
package quimufu.colourful_portals.entity;
|
package quimufu.colourful_portals.entity;
|
||||||
|
|
||||||
import net.minecraft.block.BlockState;
|
|
||||||
import net.minecraft.block.Blocks;
|
|
||||||
import net.minecraft.client.world.ClientWorld;
|
import net.minecraft.client.world.ClientWorld;
|
||||||
import net.minecraft.entity.Entity;
|
import net.minecraft.entity.Entity;
|
||||||
import net.minecraft.entity.EntityType;
|
import net.minecraft.entity.EntityType;
|
||||||
import net.minecraft.entity.LivingEntity;
|
import net.minecraft.entity.LivingEntity;
|
||||||
import net.minecraft.entity.projectile.thrown.ThrownItemEntity;
|
import net.minecraft.entity.projectile.thrown.ThrownItemEntity;
|
||||||
import net.minecraft.item.Item;
|
import net.minecraft.item.Item;
|
||||||
import net.minecraft.particle.ParticleTypes;
|
|
||||||
import net.minecraft.predicate.entity.EntityPredicates;
|
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.server.world.ServerWorld;
|
||||||
import net.minecraft.sound.SoundCategory;
|
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.hit.HitResult;
|
||||||
import net.minecraft.util.math.*;
|
import net.minecraft.util.math.Vec3d;
|
||||||
import net.minecraft.world.Heightmap;
|
|
||||||
import net.minecraft.world.TeleportTarget;
|
|
||||||
import net.minecraft.world.World;
|
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.ColourfulPortalsMod;
|
||||||
import quimufu.colourful_portals.config.ColourfulPortalConfig;
|
import quimufu.colourful_portals.util.TeleportParticleHelper;
|
||||||
import quimufu.colourful_portals.portal.PortalHelper;
|
import quimufu.colourful_portals.util.TeleportHelper;
|
||||||
import quimufu.colourful_portals.util.AdditionalMath;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -69,25 +52,13 @@ public class ColourfulPearlEntity
|
||||||
|
|
||||||
entities.stream()
|
entities.stream()
|
||||||
.filter(e -> e.squaredDistanceTo(this) < 25)
|
.filter(e -> e.squaredDistanceTo(this) < 25)
|
||||||
.forEach(this::spawnTeleportationParticles);
|
.forEach(entity -> TeleportParticleHelper.spawnTeleportationParticles(entity, getWorld()));
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCollision(HitResult hitResult) {
|
protected void onCollision(HitResult hitResult) {
|
||||||
if (getWorld().isClient()) {
|
if (!(getWorld() instanceof ServerWorld serverWorld)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isRemoved()) {
|
if (isRemoved()) {
|
||||||
|
@ -97,172 +68,14 @@ public class ColourfulPearlEntity
|
||||||
.getOtherEntities(this, this.getBoundingBox().expand(5), EntityPredicates.EXCEPT_SPECTATOR
|
.getOtherEntities(this, this.getBoundingBox().expand(5), EntityPredicates.EXCEPT_SPECTATOR
|
||||||
.and(entity -> entity instanceof LivingEntity)
|
.and(entity -> entity instanceof LivingEntity)
|
||||||
.and(entity -> !entity.getType().isIn(ColourfulPortalsMod.COLOURFUL_PEARL_NOT_TELEPORTABLE)));
|
.and(entity -> !entity.getType().isIn(ColourfulPortalsMod.COLOURFUL_PEARL_NOT_TELEPORTABLE)));
|
||||||
|
|
||||||
entities.stream()
|
entities.stream()
|
||||||
.filter(e -> e.squaredDistanceTo(this) < 25)
|
.filter(e -> e.squaredDistanceTo(this) < 25)
|
||||||
.forEach(this::tryTeleport);
|
.forEach(entity -> {
|
||||||
|
Vec3d pos = this.getPos();
|
||||||
|
TeleportHelper.markForTeleport(entity,pos.toVector3f(), serverWorld);
|
||||||
|
});
|
||||||
this.discard();
|
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;
|
package quimufu.colourful_portals.general_util;
|
||||||
|
|
||||||
import net.minecraft.util.math.random.Random;
|
|
||||||
|
import org.joml.Random;
|
||||||
|
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
|
|
@ -1,25 +1,74 @@
|
||||||
package quimufu.colourful_portals.item;
|
package quimufu.colourful_portals.item;
|
||||||
|
|
||||||
|
import net.minecraft.entity.LivingEntity;
|
||||||
import net.minecraft.entity.player.PlayerEntity;
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
import net.minecraft.item.Item;
|
import net.minecraft.item.Item;
|
||||||
import net.minecraft.item.ItemStack;
|
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.SoundCategory;
|
||||||
|
import net.minecraft.sound.SoundEvent;
|
||||||
import net.minecraft.sound.SoundEvents;
|
import net.minecraft.sound.SoundEvents;
|
||||||
import net.minecraft.stat.Stats;
|
import net.minecraft.stat.Stats;
|
||||||
import net.minecraft.util.Hand;
|
import net.minecraft.util.Hand;
|
||||||
import net.minecraft.util.TypedActionResult;
|
import net.minecraft.util.TypedActionResult;
|
||||||
|
import net.minecraft.util.UseAction;
|
||||||
import net.minecraft.world.World;
|
import net.minecraft.world.World;
|
||||||
|
import net.minecraft.world.event.GameEvent;
|
||||||
import quimufu.colourful_portals.entity.ColourfulPearlEntity;
|
import quimufu.colourful_portals.entity.ColourfulPearlEntity;
|
||||||
|
import quimufu.colourful_portals.util.TeleportParticleHelper;
|
||||||
|
import quimufu.colourful_portals.util.TeleportHelper;
|
||||||
|
|
||||||
public class ColourfulPearlItem
|
public class ColourfulPearlItem
|
||||||
extends Item {
|
extends Item {
|
||||||
|
private static final int MAX_USE_TIME = 5;
|
||||||
|
|
||||||
public ColourfulPearlItem(Item.Settings settings) {
|
public ColourfulPearlItem(Item.Settings settings) {
|
||||||
super(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
|
@Override
|
||||||
public TypedActionResult<ItemStack> use(World world, PlayerEntity user, Hand hand) {
|
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);
|
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));
|
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);
|
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": [
|
"mixins": [
|
||||||
"BlockChangeAndEntityMovementMixin",
|
"BlockChangeAndEntityMovementMixin",
|
||||||
"ServerPlayerEntityAccessor",
|
"ServerPlayerEntityAccessor",
|
||||||
"ServerPlayNetworkHandlerAccessor"
|
"ServerPlayNetworkHandlerAccessor",
|
||||||
|
"ServerWorldAccessor"
|
||||||
],
|
],
|
||||||
"injectors": {
|
"injectors": {
|
||||||
"defaultRequire": 1
|
"defaultRequire": 1
|
||||||
|
|
Loading…
Reference in New Issue
Block a user