VSMod_AriasServerUtils/AriasServerUtils/RTPFactory.cs

272 lines
No EOL
8.4 KiB
C#

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<RTPData> RTPCache = new();
private static List<RTPChunk> 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));
*/
/// <summary>
/// This function searches for a safe position
/// </summary>
/// <param name="isp">The player to be teleported</param>
/// <returns>A safe position if able to be found</returns>
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;
}
/// <summary>
/// This function will schedule a task to perform an RTP.
/// </summary>
/// <param name="isp">Player to be teleported</param>
/// <param name="maxDistance">Max distance +/- the current position.</param>
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));
}
/// <summary>
/// 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.
/// </summary>
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);
}
}