using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Numerics; using AriasServerUtils; using Vintagestory.API.Common; using Vintagestory.API.Common.Entities; using Vintagestory.API.Config; using Vintagestory.API.MathTools; using Vintagestory.API.Server; using Vintagestory.GameContent; namespace AriasServerUtils; public class RTPFactory { private static List RTPCache = new(); private static List ChunkChecks = new(); /* if (pPos == null) { ps.ActiveCooldowns.Add(CooldownType.RTP, (TimeUtil.DecodeTimeNotation(ServerUtilities.config.Cooldowns.Get(CooldownType.RTP)) / 2) + TimeUtil.GetUnixEpochTimestamp()); ServerUtilities.MarkDirty(); ServerUtilities.SendMessageTo(isp, Lang.Get($"{ServerUtilities.MOD_ID}:rtp-fail")); return TextCommandResult.Success(); } Vec2i origin = new((int)isp.Entity.Pos.X, (int)isp.Entity.Pos.Z); Vec2i npos = new(pPos.X, pPos.Z); float distance = RTPFactory.GetDistance(origin, npos); pPos.Merge(isp.Entity); ps.ActiveCooldowns.Add(CooldownType.RTP, TimeUtil.DecodeTimeNotation(ServerUtilities.config.Cooldowns.Get(CooldownType.RTP)) + TimeUtil.GetUnixEpochTimestamp()); ServerUtilities.MarkDirty(); ServerUtilities.SendMessageTo(isp, Lang.Get($"{ServerUtilities.MOD_ID}:rtp", distance)); */ /// /// This function searches for a safe position /// /// The player to be teleported /// 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); // 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--) { bPos.Y = i; 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; } return null; // Return null if no valid position is found after retries } /// /// This function will schedule a task to perform an RTP. /// /// Player to be teleported /// Max distance +/- the current position. public static void TryRTP(IServerPlayer isp, int maxDistance) { var data = new RTPData(isp, maxDistance, 100, PlayerPosition.from(isp.Entity)); RTPCache.Add(data); } public static float GetDistance(Vec2i pos1, Vec2i pos2) { return MathF.Sqrt(MathF.Pow(pos2.X - pos1.X, 2) + MathF.Pow(pos2.Y - pos1.Y, 2)); } /// /// 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() { // 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) { // Check if this is even a valid check var num = ChunkChecks.Where(x => x.ChunkX == chunkCoord.X && x.ChunkZ == chunkCoord.Y).Count(); if (num == 0) return; // 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 { public IServerPlayer player; public int NumTriesRemaining; public int MaxDistance; public PlayerPosition StartPosition; public RTPPosition LastPosition; public RTPData(IServerPlayer isp, int maxDistance, int tries, PlayerPosition playerPosition) { MaxDistance = maxDistance; player = isp; NumTriesRemaining = tries; StartPosition = playerPosition; } public RTPPosition MakeNewPosition() { NumTriesRemaining--; LastPosition = new RTPPosition((int)player.Entity.Pos.X, (int)player.Entity.Pos.Z, MaxDistance, player.Entity.Pos.Dimension); return LastPosition; } } public class RTPPosition { public int x; public int y; public int z; public int dimension; public RTPPosition(int x, int z, int maxDist, int dim) { int minX = x - maxDist; int maxX = x + maxDist; int minZ = z - maxDist; int maxZ = z + maxDist; this.x = ServerUtilities.rng.Next(minX, maxX); this.y = 1; this.z = ServerUtilities.rng.Next(minZ, maxZ); this.dimension = dim; } PlayerPosition GetPlayerPosition() { return new PlayerPosition { X = x, Y = y, Dimension = dimension, Z = z }; } BlockPos GetBlockPos() { return new BlockPos(new Vec3i(x, y, z), dimension); } }