mirror of
https://github.com/Sollace/Unicopia.git
synced 2024-11-23 21:38:00 +01:00
Update respawn logic
This commit is contained in:
parent
3c37c7136d
commit
c70f3dcc5f
6 changed files with 117 additions and 118 deletions
|
@ -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()))) {
|
||||
Race suppressedRace = getSuppressedRace();
|
||||
if (suppressedRace != Race.UNSET) {
|
||||
setSpecies(suppressedRace);
|
||||
}
|
||||
}
|
||||
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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
return mutable.up().toImmutable();
|
||||
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)) {
|
||||
mutable.move(0, -1, 0);
|
||||
}
|
||||
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;
|
||||
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);
|
||||
}
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ public record MsgRequestSpeciesChange (
|
|||
});
|
||||
}
|
||||
|
||||
player.onSpawn();
|
||||
player.forceRespawnOnRaceChange();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue