From 873297ca4450f2cf3dabea0f5536bf6aa4dbc8d6 Mon Sep 17 00:00:00 2001
From: Sollace <sollacea@gmail.com>
Date: Thu, 9 Nov 2023 15:07:56 +0000
Subject: [PATCH] Added respawn logic to place seaponies in water

---
 .../unicopia/entity/player/Pony.java          | 12 +--
 .../unicopia/entity/player/SpawnLocator.java  | 82 +++++++++++++------
 2 files changed, 66 insertions(+), 28 deletions(-)

diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java b/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java
index 74dfa609..486e0316 100644
--- a/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java
+++ b/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java
@@ -54,6 +54,7 @@ import net.minecraft.item.ItemStack;
 import net.minecraft.nbt.NbtCompound;
 import net.minecraft.particle.ParticleTypes;
 import net.minecraft.registry.tag.DamageTypeTags;
+import net.minecraft.registry.tag.FluidTags;
 import net.minecraft.server.network.ServerPlayerEntity;
 import net.minecraft.server.world.ServerWorld;
 import net.minecraft.text.Text;
@@ -340,11 +341,12 @@ public class Pony extends Living<PlayerEntity> implements Copyable<Pony>, Update
     }
 
     public void onSpawn() {
-        if (entity.getWorld() instanceof ServerWorld sw
-                && getObservedSpecies() == Race.BAT
-                && sw.getServer().getSaveProperties().getGameMode() != GameMode.ADVENTURE
-                && MeteorlogicalUtil.isPositionExposedToSun(sw, getOrigin())) {
-            SpawnLocator.selectSpawnPosition(sw, entity);
+        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);
+            }
         }
         ticksSunImmunity = INITIAL_SUN_IMMUNITY;
     }
diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/SpawnLocator.java b/src/main/java/com/minelittlepony/unicopia/entity/player/SpawnLocator.java
index 4c49f017..13d30bf3 100644
--- a/src/main/java/com/minelittlepony/unicopia/entity/player/SpawnLocator.java
+++ b/src/main/java/com/minelittlepony/unicopia/entity/player/SpawnLocator.java
@@ -1,37 +1,68 @@
 package com.minelittlepony.unicopia.entity.player;
 
-import java.util.Optional;
-
 import com.minelittlepony.unicopia.util.MeteorlogicalUtil;
 
-import net.minecraft.entity.ai.FuzzyPositions;
+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 {
-
-    public static BlockPos findSafeSpawnLocation(ServerWorld world, int x, int z) {
-        return SpawnLocating.findOverworldSpawn(world, x, z);
-    }
-
-    public static Optional<BlockPos> fuzz(ServerWorld world, BlockPos pos, int horizontal, int vertical) {
-
-        for (int attempt = 0; attempt < 6; attempt++) {
-            BlockPos target = FuzzyPositions.localFuzz(world.random, horizontal, vertical);
-            target = findSafeSpawnLocation(world, target.getX(), target.getZ());
-
-            if (target != null) {
-                return Optional.of(target);
-            }
+    private static BlockPos findSafeSpawnLocation(ServerWorld world, int x, int z, boolean avoidAir) {
+        if (!avoidAir) {
+            return findOverworldSpawn(world, x, z);
         }
 
-        return Optional.empty();
+        boolean hasCeiling = world.getDimension().hasCeiling();
+        WorldChunk chunk = world.getChunk(ChunkSectionPos.getSectionCoord(x), ChunkSectionPos.getSectionCoord(z));
+        int startHeight = hasCeiling
+                ? world.getChunkManager().getChunkGenerator().getSpawnHeight(world)
+                : chunk.sampleHeightmap(Heightmap.Type.MOTION_BLOCKING, x & 0xF, z & 0xF);
+        if (startHeight < world.getBottomY()) {
+            return null;
+        }
+
+        int terrainHeight = chunk.sampleHeightmap(Heightmap.Type.WORLD_SURFACE, x & 0xF, z & 0xF);
+        if (terrainHeight <= startHeight && terrainHeight > chunk.sampleHeightmap(Heightmap.Type.OCEAN_FLOOR, x & 0xF, z & 0xF)) {
+            return null;
+        }
+
+        BlockPos.Mutable mutable = new BlockPos.Mutable();
+        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)) {
+                continue;
+            }
+
+            return mutable.up().toImmutable();
+        }
+        return null;
     }
 
-    public static void selectSpawnPosition(ServerWorld world, PlayerEntity entity) {
+    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())),
@@ -53,7 +84,8 @@ public class SpawnLocator extends SpawnLocating {
 
             BlockPos candidatePos = findSafeSpawnLocation(world,
                     spawnPos.getX() + x - spawnRadius,
-                    spawnPos.getZ() + z - spawnRadius
+                    spawnPos.getZ() + z - spawnRadius,
+                    avoidAir
             );
 
             if (candidatePos == null) {
@@ -70,7 +102,7 @@ public class SpawnLocator extends SpawnLocating {
                 mutable.move(0, -1, 0);
             }
 
-            if (!world.getFluidState(mutable).isEmpty()) {
+            if (!checkAtmosphere(world, mutable, avoidAir)) {
                 continue;
             }
 
@@ -78,13 +110,17 @@ public class SpawnLocator extends SpawnLocating {
                 mutable.move(0, 1, 0);
             }
 
-            if (!world.getFluidState(mutable).isEmpty()) {
+            if (!checkAtmosphere(world, mutable, avoidAir)) {
                 continue;
             }
 
             entity.refreshPositionAndAngles(mutable, 0, 0);
 
-            if (!world.isSpaceEmpty(entity) || MeteorlogicalUtil.isPositionExposedToSun(world, mutable)) {
+            if (!world.isSpaceEmpty(entity)) {
+                continue;
+            }
+
+            if (avoidSun && MeteorlogicalUtil.isPositionExposedToSun(world, mutable)) {
                 continue;
             }