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) { PlayerPosition PPos = PlayerPosition.from(data.player.Entity); BlockPos check = new( x: position.x, y: 1, z: position.z, dim: position.dimension ); int height = ServerUtilities.API.World.BlockAccessor.GetTerrainMapheightAt(check); PPos.Y = height + 1; return PPos; } /// /// 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(); ServerUtilities.SendMessageTo(rtp.player, Lang.Get($"{ServerUtilities.MOD_ID}:rtp-progress")); // 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); if (rtp.NumTriesRemaining <= 0) { // Send failure message to the player ServerUtilities.SendMessageTo(rtp.player, Lang.Get($"{ServerUtilities.MOD_ID}:rtp-fail")); // This check needs to be removed from the queue RTPCache.Remove(rtp); return; // We modified the list, so abort the loop. } } } 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); ServerUtilities.SendMessageTo(data.player, Lang.Get($"{ServerUtilities.MOD_ID}:rtp", GetDistance(new Vec2i(data.StartPosition.X, data.StartPosition.Z), new Vec2i(pos.X, pos.Z)))); } // 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); } }