Update respawn logic

This commit is contained in:
Sollace 2024-10-04 18:31:33 +01:00
parent 3c37c7136d
commit c70f3dcc5f
No known key found for this signature in database
GPG key ID: E52FACE7B5C773DB
6 changed files with 117 additions and 118 deletions

View file

@ -365,24 +365,33 @@ public class Pony extends Living<PlayerEntity> implements Copyable<Pony>, Update
return Optional.of(asEntity().getUuid());
}
public void forceRespawnOnRaceChange() {
if (isSpawnInvalid(getOrigin())) {
BlockPos respawnPos = entity.getWorldSpawnPos((ServerWorld)asWorld(), getOrigin());
if (!isSpawnInvalid(respawnPos)) {
Vec3d pos = respawnPos.toBottomCenterPos();
entity.updatePosition(pos.x, pos.y, pos.z);
}
}
onSpawn();
}
public void onSpawn() {
if (entity.getWorld() instanceof ServerWorld sw && sw.getServer().getSaveProperties().getGameMode() != GameMode.ADVENTURE) {
boolean mustAvoidSun = getObservedSpecies() == Race.BAT && MeteorlogicalUtil.isPositionExposedToSun(sw, getOrigin());
boolean mustAvoidAir = getCompositeRace().includes(Race.SEAPONY) && !sw.getFluidState(getOrigin()).isIn(FluidTags.WATER);
if (mustAvoidSun || mustAvoidAir) {
SpawnLocator.selectSpawnPosition(sw, entity, mustAvoidAir, mustAvoidSun);
if ((mustAvoidAir && !sw.getFluidState(getOrigin()).isIn(FluidTags.WATER))
|| (mustAvoidSun && MeteorlogicalUtil.isPositionExposedToSun(sw, getOrigin()))) {
if (isSpawnInvalid(getOrigin())) {
Race suppressedRace = getSuppressedRace();
if (suppressedRace != Race.UNSET) {
setSpecies(suppressedRace);
}
}
}
}
ticksSunImmunity = INITIAL_SUN_IMMUNITY;
}
public boolean isSpawnInvalid(BlockPos pos) {
return (entity.getWorld() instanceof ServerWorld sw && sw.getDimension().hasSkyLight() && sw.getServer().getSaveProperties().getGameMode() != GameMode.ADVENTURE)
&& ((getCompositeRace().includes(Race.BAT) && MeteorlogicalUtil.isPositionExposedToSun(asWorld(), pos))
|| (getCompositeRace().includes(Race.SEAPONY) && !asWorld().getFluidState(pos).isIn(FluidTags.WATER)));
}
@Override
public boolean beforeUpdate() {
if (compositeRace.includes(Race.UNSET) || entity.age % 2 == 0) {

View file

@ -1,25 +1,23 @@
package com.minelittlepony.unicopia.entity.player;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.util.MeteorlogicalUtil;
import net.fabricmc.fabric.api.util.TriState;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.fluid.FluidState;
import net.minecraft.registry.tag.FluidTags;
import net.minecraft.server.network.SpawnLocating;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.*;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.Heightmap;
import net.minecraft.world.chunk.WorldChunk;
public class SpawnLocator extends SpawnLocating {
private static BlockPos findSafeSpawnLocation(ServerWorld world, int x, int z, boolean avoidAir) {
if (!avoidAir) {
return findOverworldSpawn(world, x, z);
}
public class SpawnLocator {
// Modified from SpawnLocating.findOverworldSpawn
private static BlockPos findOverworldSpawn(ServerWorld world, int x, int z, boolean avoidAir) {
boolean hasCeiling = world.getDimension().hasCeiling();
WorldChunk chunk = world.getChunk(ChunkSectionPos.getSectionCoord(x), ChunkSectionPos.getSectionCoord(z));
int startHeight = hasCeiling
@ -38,93 +36,89 @@ public class SpawnLocator extends SpawnLocating {
for (int y = startHeight + 1; y >= world.getBottomY(); --y) {
mutable.set(x, y, z);
BlockState state = world.getBlockState(mutable);
FluidState fluid = state.getFluidState();
if (fluid.isEmpty()) {
continue;
}
if (!fluid.isIn(FluidTags.WATER)) {
break;
}
if (!Block.isFaceFullSquare(state.getCollisionShape(world, mutable), Direction.UP)) {
var pass = checkAtmosphere(state.getFluidState(), avoidAir);
if (pass == TriState.TRUE) {
continue;
} else if (pass == TriState.FALSE) {
return null;
}
if (Block.isFaceFullSquare(state.getCollisionShape(world, mutable), Direction.UP)) {
return mutable.up().toImmutable();
}
}
return null;
}
private static BlockPos findAdjustedOverworldSpawn(ServerWorld world, PlayerEntity entity, Box box, BlockPos basePos,
int x, int z,
int spawnRadius, boolean avoidAir, boolean avoidSun) {
BlockPos spawnPos = SpawnLocator.findOverworldSpawn(world, x, z, avoidAir);
if (spawnPos == null) {
return null;
}
if (avoidSun && MeteorlogicalUtil.isPositionExposedToSun(world, spawnPos)) {
spawnPos = findUndergroundSpaceBelow(world, entity, basePos, spawnRadius, box, spawnPos);
if (MeteorlogicalUtil.isPositionExposedToSun(world, spawnPos)) {
return null;
}
}
if (!checkAtmosphere(world, spawnPos, avoidAir)) {
return null;
}
return spawnPos;
}
public static BlockPos findAdjustedOverworldSpawn(ServerWorld world, PlayerEntity entity, Box box, BlockPos basePos, int x, int z, Operation<BlockPos> operation) {
boolean avoidSun = Pony.of(entity).getCompositeRace().includes(Race.BAT);
boolean avoidAir = Pony.of(entity).getCompositeRace().includes(Race.SEAPONY);
if (!(avoidSun || avoidAir)) {
return operation.call(world, x, z);
}
int spawnRadius = Math.max(16, world.getServer().getSpawnRadius(world));
return findAdjustedOverworldSpawn(world, entity, box, basePos, x, z, spawnRadius, avoidAir, avoidSun);
}
private static TriState checkAtmosphere(FluidState state, boolean avoidAir) {
if (avoidAir) {
if (state.isEmpty()) {
return TriState.TRUE;
}
if (!state.isIn(FluidTags.WATER)) {
return TriState.FALSE;
}
}
return state.isEmpty() ? TriState.DEFAULT : TriState.FALSE;
}
private static boolean checkAtmosphere(ServerWorld world, BlockPos pos, boolean avoidAir) {
if (avoidAir) {
return world.getFluidState(pos).isIn(FluidTags.WATER);
}
return world.getFluidState(pos).isEmpty();
}
public static void selectSpawnPosition(ServerWorld world, PlayerEntity entity, boolean avoidAir, boolean avoidSun) {
BlockPos spawnPos = world.getSpawnPos();
int spawnRadius = Math.min(
MathHelper.floor(world.getWorldBorder().getDistanceInsideBorder(spawnPos.getX(), spawnPos.getZ())),
Math.max(0, world.getServer().getSpawnRadius(world))
);
long l = spawnRadius * 2 + 1;
long m = l * l;
int spawnArea = m > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)m;
int offsetMultiplier = spawnArea <= 16 ? spawnArea - 1 : 17;
int rng = Random.create().nextInt(spawnArea);
BlockPos.Mutable mutable = new BlockPos.Mutable();
for (int attempt = 0; attempt < spawnArea; attempt++) {
int tile = (rng + offsetMultiplier * attempt) % spawnArea;
int x = tile % (spawnRadius * 2 + 1);
int z = tile / (spawnRadius * 2 + 1);
BlockPos candidatePos = findSafeSpawnLocation(world,
spawnPos.getX() + x - spawnRadius,
spawnPos.getZ() + z - spawnRadius,
avoidAir
);
if (candidatePos == null) {
continue;
}
mutable.set(candidatePos);
mutable.move(0, -1, 0);
while (!world.isAir(mutable) && mutable.getY() >= spawnPos.getY() - spawnRadius * 2 && !world.isOutOfHeightLimit(mutable)) {
private static BlockPos findUndergroundSpaceBelow(ServerWorld world, PlayerEntity entity, BlockPos basePos, int spawnRadius, Box box, BlockPos pos) {
BlockPos.Mutable mutable = pos.mutableCopy();
mutable.move(0, -1, 0);
// move to ground
while (!world.isSpaceEmpty(entity, box.offset(mutable.toBottomCenterPos()))
&& mutable.getY() >= basePos.getY() - spawnRadius * 2
&& mutable.getY() > world.getBottomY() + 1) {
mutable.move(Direction.DOWN);
}
while (world.isAir(mutable) && mutable.getY() >= spawnPos.getY() - spawnRadius * 2 && !world.isOutOfHeightLimit(mutable)) {
mutable.move(0, -1, 0);
}
if (!checkAtmosphere(world, mutable, avoidAir)) {
continue;
}
if (!world.isAir(mutable)) {
mutable.move(0, 1, 0);
}
if (!checkAtmosphere(world, mutable, avoidAir)) {
continue;
}
entity.refreshPositionAndAngles(mutable, 0, 0);
if (!world.isSpaceEmpty(entity)) {
continue;
}
if (avoidSun && MeteorlogicalUtil.isPositionExposedToSun(world, mutable)) {
continue;
}
break;
// move down until we find a place we can stand
while (world.isSpaceEmpty(entity, box.offset(mutable.down().toBottomCenterPos()))
&& mutable.getY() >= basePos.getY() - spawnRadius * 2
&& mutable.getY() > world.getBottomY() + 1) {
mutable.move(Direction.DOWN);
}
return mutable.toImmutable();
}
}

View file

@ -5,10 +5,6 @@ import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import com.minelittlepony.unicopia.EquineContext;
import com.minelittlepony.unicopia.InteractionManager;
import com.minelittlepony.unicopia.entity.player.Pony;
import net.minecraft.entity.Entity;
@ -36,14 +32,4 @@ abstract class MixinPlayerManager {
}
}
}
@Inject(method = "respawnPlayer", at = @At("HEAD"))
private void beforeRespawnPlayer(ServerPlayerEntity player, boolean alive, CallbackInfoReturnable<ServerPlayerEntity> info) {
InteractionManager.getInstance().setEquineContext(EquineContext.of(player));
}
@Inject(method = "respawnPlayer", at = @At("RETURN"))
private void afterRespawnPlayer(ServerPlayerEntity player, boolean alive, CallbackInfoReturnable<ServerPlayerEntity> info) {
InteractionManager.getInstance().clearEquineContext();
}
}

View file

@ -7,9 +7,13 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import com.minelittlepony.unicopia.entity.Equine;
import com.minelittlepony.unicopia.entity.duck.ServerPlayerEntityDuck;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.entity.player.SpawnLocator;
import com.minelittlepony.unicopia.server.world.UGameRules;
import com.mojang.datafixers.util.Either;
@ -18,9 +22,11 @@ import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.screen.ScreenHandlerListener;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.text.Text;
import net.minecraft.util.Unit;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Box;
@Mixin(ServerPlayerEntity.class)
abstract class MixinServerPlayerEntity extends PlayerEntity implements ScreenHandlerListener, Equine.Container<Pony>, ServerPlayerEntityDuck {
@ -36,9 +42,10 @@ abstract class MixinServerPlayerEntity extends PlayerEntity implements ScreenHan
get().copyFrom(((Equine.Container<Pony>)oldPlayer).get(), alive);
}
@Inject(method = "trySleep(Lnet/minecraft/util/math/BlockPos;)Lcom/mojang/datafixers/util/Either;",
at = @At(value = "FIELD", target = "net/minecraft/entity/player/PlayerEntity$SleepFailureReason.NOT_POSSIBLE_NOW:Lnet/minecraft/entity/player/PlayerEntity$SleepFailureReason;"),
cancellable = true)
@Inject(method = "trySleep(Lnet/minecraft/util/math/BlockPos;)Lcom/mojang/datafixers/util/Either;", at = @At(
value = "FIELD",
target = "net/minecraft/entity/player/PlayerEntity$SleepFailureReason.NOT_POSSIBLE_NOW:Lnet/minecraft/entity/player/PlayerEntity$SleepFailureReason;"
), cancellable = true)
private void onTrySleep(BlockPos pos, CallbackInfoReturnable<Either<PlayerEntity.SleepFailureReason, Unit>> info) {
if (get().getSpecies().isNocturnal() && get().asWorld().getGameRules().getBoolean(UGameRules.DO_NOCTURNAL_BAT_PONIES)) {
((PlayerEntity)this).sendMessage(Text.translatable("block.unicopia.bed.no_sleep.nocturnal"), true);
@ -57,4 +64,12 @@ abstract class MixinServerPlayerEntity extends PlayerEntity implements ScreenHan
private void onStartRiding(Entity entity, boolean force, CallbackInfoReturnable<Boolean> info) {
get().getPhysics().cancelFlight(true);
}
@WrapOperation(method = "getWorldSpawnPos", at = @At(
value = "INVOKE",
target = "net/minecraft/server/network/SpawnLocating.findOverworldSpawn(Lnet/minecraft/server/world/ServerWorld;II)Lnet/minecraft/util/math/BlockPos;"
))
private BlockPos adjustSpawnPosition(ServerWorld world, int x, int z, Operation<BlockPos> operation, ServerWorld unused, BlockPos basePos, @Local Box box) {
return SpawnLocator.findAdjustedOverworldSpawn(world, this, box, basePos, x, z, operation);
}
}

View file

@ -16,23 +16,18 @@ import net.minecraft.block.BlockState;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.StructureWorldAccess;
import net.minecraft.world.World;
@Mixin(ServerWorld.class)
abstract class MixinServerWorld extends World implements StructureWorldAccess, NocturnalSleepManager.Source {
abstract class MixinServerWorld implements StructureWorldAccess, NocturnalSleepManager.Source {
private NocturnalSleepManager nocturnalSleepManager;
MixinServerWorld() { super(null, null, null, null, null, false, false, 0, 0); }
@Inject(method = "onBlockChanged", at = @At("HEAD"))
private void onOnBlockChanged(BlockPos pos, BlockState oldState, BlockState newState, CallbackInfo info) {
((BlockDestructionManager.Source)this).getDestructionManager().onBlockChanged(pos, oldState, newState);
}
@ModifyConstant(method = "sendSleepingStatus()V", constant = @Constant(
stringValue = "sleep.skipping_night"
))
@ModifyConstant(method = "sendSleepingStatus()V", constant = @Constant(stringValue = "sleep.skipping_night"))
private String modifySleepingMessage(String initial) {
return getNocturnalSleepManager().getTimeSkippingMessage(initial);
}

View file

@ -48,7 +48,7 @@ public record MsgRequestSpeciesChange (
});
}
player.onSpawn();
player.forceRespawnOnRaceChange();
}
}