diff --git a/AriasServerUtils/ASUModSystem.cs b/AriasServerUtils/ASUModSystem.cs index 84c74af..e2775d2 100644 --- a/AriasServerUtils/ASUModSystem.cs +++ b/AriasServerUtils/ASUModSystem.cs @@ -76,10 +76,11 @@ namespace AriasServerUtils api.Event.ServerRunPhase(EnumServerRunPhase.Shutdown, OnShutdown); api.Event.Timer(OnCheckModDirty, 20); api.Event.Timer(OnCheckPlayerCooldowns, 1); + api.Event.Timer(RTPFactory.HandleRTPChecking, 1); api.Event.PlayerDeath += OnPlayerDeath; api.Event.PlayerJoin += OnPlayerJoin; api.Event.PlayerDisconnect += OnPlayerDC; - api.Event.ChunkColumnLoaded += RTPFactory.ChunkColumnGenerated; + api.Event.ChunkColumnLoaded += RTPFactory.ChunkLoaded; //api.Event.PlayerLeave += OnPlayerDC; diff --git a/AriasServerUtils/RTPFactory.cs b/AriasServerUtils/RTPFactory.cs index 42c0b51..8155b8c 100644 --- a/AriasServerUtils/RTPFactory.cs +++ b/AriasServerUtils/RTPFactory.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Data; +using System.Linq; using System.Numerics; using AriasServerUtils; using Vintagestory.API.Common; @@ -15,6 +16,7 @@ public class RTPFactory { private static List RTPCache = new(); + private static List ChunkChecks = new(); /* if (pPos == null) @@ -41,93 +43,58 @@ public class RTPFactory */ /// - /// This function searches for a safe position, honoring the max RTP distance in the global configuration + /// This function searches for a safe position /// /// The player to be teleported - /// A random position +/- max distance from current position. - public static PlayerPosition GetRandomPosition(IServerPlayer isp, int maxDistance) + /// A safe position if able to be found + public static PlayerPosition GetSafePosition(RTPData data, RTPPosition position) { Random rng = ServerUtilities.rng; + IServerPlayer isp = data.player; EntityPos vPos = isp.Entity.Pos; BlockPos bPos = new BlockPos(isp.Entity.Pos.Dimension); IServerWorldAccessor iswa = isp.Entity.World as IServerWorldAccessor; int tries = 10000; PlayerPosition PPos = PlayerPosition.from(isp.Entity); - PlayerPosition original = PlayerPosition.from(isp.Entity); + //PlayerPosition original = PlayerPosition.from(isp.Entity); - while (tries-- > 0) + + // Generate random X and Z within max RTP distance + bPos.X = position.x; + bPos.Z = position.z; + bPos.Y = 255; + + Block lastAboveLast = null; + Block lastBlock = null; + Block curBlock; + + // Scan downwards to find a valid landing spot + for (int i = 255; i > 0; i--) { - int ixMax = (int)vPos.X + maxDistance; - int ixMin = (int)vPos.X - maxDistance; - int izMax = (int)vPos.Z + maxDistance; - int izMin = (int)vPos.Z - maxDistance; + bPos.Y = i; + curBlock = iswa.BlockAccessor.GetBlock(bPos); - // Generate random X and Z within max RTP distance - bPos.X = (int)vPos.X + rng.Next(ixMin, ixMax); - bPos.Z = (int)vPos.Z + rng.Next(izMin, izMax); - bPos.Y = 255; - - Block lastAboveLast = null; - Block lastBlock = null; - Block curBlock; - // Ensure the chunk is loaded before accessing blocks - int chunkX = bPos.X / 32; - int chunkY = bPos.Y / 32; - int chunkZ = bPos.Z / 32; - var chunk = iswa.ChunkProvider.GetUnpackedChunkFast(chunkX, chunkY, chunkZ); - - if (chunk == null) + if (curBlock.MatterState == EnumMatterState.Solid) { - // Chunk doesn't exist, or isn't loaded. - // Teleport the player to the position up in the sky. That way the chunk does get loaded and we can proceed. - PPos.X = bPos.X; - PPos.Y = bPos.Y; - PPos.Z = bPos.Z; + if (lastBlock != null && lastBlock.BlockMaterial == EnumBlockMaterial.Air && + lastAboveLast != null && lastAboveLast.BlockMaterial == EnumBlockMaterial.Air) + { + // Found a valid spot: curBlock is solid, lastBlock & lastAboveLast are gas (air) + PPos.X = bPos.X; + PPos.Y = bPos.Y + 1; + PPos.Z = bPos.Z; - PPos.Merge(isp.Entity); + ServerUtilities.SendMessageTo(isp, Lang.Get($"{ServerUtilities.MOD_ID}:rtp-found", tries)); + + return PPos; + } } - // Scan downwards to find a valid landing spot - for (int i = 255; i > 0; i--) - { - bPos.Y = i; - if (i / 32 != chunkY) - { - chunkY = i / 32; - chunk = iswa.ChunkProvider.GetUnpackedChunkFast(chunkX, chunkY, chunkZ); - if (chunk == null) - { - - PPos.Y = i; - PPos.Merge(isp.Entity); - } - } - curBlock = iswa.BlockAccessor.GetBlock(bPos); - - if (curBlock.MatterState == EnumMatterState.Solid) - { - if (lastBlock != null && lastBlock.BlockMaterial == EnumBlockMaterial.Air && - lastAboveLast != null && lastAboveLast.BlockMaterial == EnumBlockMaterial.Air) - { - // Found a valid spot: curBlock is solid, lastBlock & lastAboveLast are gas (air) - PPos.X = bPos.X; - PPos.Y = bPos.Y + 1; - PPos.Z = bPos.Z; - - ServerUtilities.SendMessageTo(isp, Lang.Get($"{ServerUtilities.MOD_ID}:rtp-found", tries)); - - return PPos; - } - } - - lastAboveLast = lastBlock; - lastBlock = curBlock; - } + lastAboveLast = lastBlock; + lastBlock = curBlock; } - original.Merge(isp.Entity); - return null; // Return null if no valid position is found after retries } @@ -138,7 +105,7 @@ public class RTPFactory /// Max distance +/- the current position. public static void TryRTP(IServerPlayer isp, int maxDistance) { - var data = new RTPData(isp, maxDistance, 1000, PlayerPosition.from(isp)); + var data = new RTPData(isp, maxDistance, 100, PlayerPosition.from(isp.Entity)); RTPCache.Add(data); } @@ -147,10 +114,75 @@ public class RTPFactory return MathF.Sqrt(MathF.Pow(pos2.X - pos1.X, 2) + MathF.Pow(pos2.Y - pos1.Y, 2)); } - internal static void ChunkColumnGenerated(Vec2i chunkCoord, IWorldChunk[] chunks) + /// + /// Fired automatically by the internal game timer. This function will handle checking for a RTP Location + /// + /// NOTE: This function will only cause the chunks in question to be force loaded long enough to check their blocks and make sure it is safe to land there. + /// + internal static void HandleRTPChecking() { - throw new NotImplementedException(); + // We want to now loop over the entire cache list. + // We'll then generate a position to check + // We'll also then check the loaded status of the chunk + foreach (var rtp in RTPCache) + { + // Check for any chunks still being checked. + int num = ChunkChecks.Select(x => x.rtp.player.PlayerUID == rtp.player.PlayerUID).Count(); + if (num > 0) continue; + + // Generate a new position + var position = rtp.MakeNewPosition(); + + // Get the world handle, then get chunk size + var worldManager = ServerUtilities.API.WorldManager; + var chunkSize = worldManager.ChunkSize; + + // Generate a chunk load check object. + RTPChunk chunk = new RTPChunk(); + chunk.ChunkX = position.x / chunkSize; + chunk.ChunkZ = position.z / chunkSize; + chunk.dim = position.dimension; + chunk.rtp = rtp; + + // Load the chunk + worldManager.LoadChunkColumnForDimension(chunk.ChunkX, chunk.ChunkZ, chunk.dim); + + // Log the request + ChunkChecks.Add(chunk); + } } + + internal static void ChunkLoaded(Vec2i chunkCoord, IWorldChunk[] chunks) + { + // Get the chunk from the stack + var chunk = ChunkChecks.Where(x => x.ChunkX == chunkCoord.X && x.ChunkZ == chunkCoord.Y).First(); + + // Attempt to find a landing point. + var data = chunk.rtp; + var pos = GetSafePosition(data, data.LastPosition); + + if (pos == null) + { + // Let this get checked again + } + else + { + // Found! Perform teleport and remove the RTP Check + RTPCache.Remove(data); + pos.Merge(data.player.Entity); + } + + // Remove this check + ChunkChecks.Remove(chunk); + } +} + +public class RTPChunk +{ + public int ChunkX; + public int ChunkZ; + public int dim; + public RTPData rtp; } public class RTPData @@ -159,6 +191,7 @@ public class RTPData public int NumTriesRemaining; public int MaxDistance; public PlayerPosition StartPosition; + public RTPPosition LastPosition; public RTPData(IServerPlayer isp, int maxDistance, int tries, PlayerPosition playerPosition) { @@ -171,18 +204,19 @@ public class RTPData public RTPPosition MakeNewPosition() { NumTriesRemaining--; + LastPosition = new RTPPosition((int)player.Entity.Pos.X, (int)player.Entity.Pos.Z, MaxDistance, player.Entity.Pos.Dimension); - return new RTPPosition((int)player.Entity.Pos.X, (int)player.Entity.Pos.Z, MaxDistance, player.Entity.Pos.Dimension); + return LastPosition; } } public class RTPPosition { - int x; - int y; - int z; + public int x; + public int y; + public int z; - int dimension; + public int dimension; public RTPPosition(int x, int z, int maxDist, int dim) { diff --git a/AriasServerUtils/modinfo.json b/AriasServerUtils/modinfo.json index 3cf221e..23ebf07 100644 --- a/AriasServerUtils/modinfo.json +++ b/AriasServerUtils/modinfo.json @@ -3,8 +3,8 @@ "modid": "ariasserverutils", "name": "Aria's Server Utilities", "authors": ["zontreck"], - "description": "A collection of server utilities\n\nBuild Date: 03-07-2025 @ 1:32 PM MST", - "version": "1.0.6-dev.8", + "description": "A collection of server utilities\n\nBuild Date: 03-10-2025 @ 10:01 AM MST", + "version": "1.0.6-dev.9", "dependencies": { "game": "" }