using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Numerics; using AriasServerUtils; using Microsoft.Win32.SafeHandles; 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 = data.LastPosition.GetPlayerPosition(); BlockPos check = data.LastPosition.GetBlockPos(); check.Y = 1; int Y = 255; bool lastBlockAir = true; bool lastLastBlockAir = true; bool curBlockAir = true; bool safe = false; for (Y = 255; Y > 1; Y--) { // Manually scan downwards var BA = ServerUtilities.API.World.BlockAccessor; check.Y = Y; var current = BA.GetBlock(check); if (current.BlockMaterial != EnumBlockMaterial.Air) { curBlockAir = false; } lastLastBlockAir = lastBlockAir; lastBlockAir = curBlockAir; if (!curBlockAir && lastBlockAir && lastLastBlockAir) { // We found a safe spot to land check.Y++; safe = true; break; } } if (!safe) return null; PPos.Y = check.Y; 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, 10, 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; 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. } // Get the world handle, then get chunk size var worldManager = ServerUtilities.API.WorldManager; var chunkSize = worldManager.ChunkSize; var worldSize = new Vec3i(worldManager.MapSizeX, worldManager.MapSizeY, worldManager.MapSizeZ); // Generate a new position var position = rtp.MakeNewPosition(worldSize); //ServerUtilities.SendMessageTo(rtp.player, Lang.Get($"{ServerUtilities.MOD_ID}:rtp-progress")); // 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; // Log the request ChunkChecks.Add(chunk); // Load the chunk worldManager.LoadChunkColumnPriority(chunk.ChunkX, chunk.ChunkZ); } } 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(Vec3i mapSize) { NumTriesRemaining--; LastPosition = new RTPPosition((int)player.Entity.Pos.X, (int)player.Entity.Pos.Z, MaxDistance, player.Entity.Pos.Dimension, mapSize); 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, Vec3i mapSize) { int worldx = mapSize.X / 2; int worldz = mapSize.Z / 2; if (maxDist > worldx) maxDist = worldx / 2; int minX = x - maxDist; int maxX = x + maxDist; int minZ = z - maxDist; int maxZ = z + maxDist; this.x = ServerUtilities.rng.Next(worldx - minX, worldx + maxX); this.y = 1; this.z = ServerUtilities.rng.Next(worldz - minZ, worldz + maxZ); this.dimension = dim; } public PlayerPosition GetPlayerPosition() { return new PlayerPosition { X = x, Y = y, Dimension = dimension, Z = z }; } public BlockPos GetBlockPos() { return new BlockPos(new Vec3i(x, y, z), dimension); } }