Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/Perpetuum.Bootstrapper/Modules/ZonesModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Perpetuum.Services.EventServices.EventProcessors;
using Perpetuum.Services.EventServices.EventProcessors.NpcSpawnEventHandlers;
using Perpetuum.Services.HighScores;
using Perpetuum.Services.PathFind;
using Perpetuum.Services.Relics;
using Perpetuum.Services.RiftSystem;
using Perpetuum.Services.Strongholds;
Expand Down Expand Up @@ -58,6 +59,11 @@ protected override void Load(ContainerBuilder builder)
e.Context.Resolve<IProcessManager>().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromMinutes(5)));
}).As<IWeatherService>();

_ = builder.RegisterType<PathFindService>().OnActivated(e =>
{
e.Context.Resolve<IProcessManager>().AddProcess(e.Instance.ToAsync().AsTimed(TimeSpan.FromMinutes(5)));
}).As<IPathFindService>().SingleInstance();

_ = builder.RegisterType<WeatherMonitor>();
_ = builder.RegisterType<WeatherEventListener>();
_ = builder.Register<Func<IZone, WeatherEventListener>>(x =>
Expand Down Expand Up @@ -160,6 +166,7 @@ void RegisterZone<T>(ZoneType type) where T : Zone
zone.SafeSpawnPoints = ctx.Resolve<ISafeSpawnPointsRepository>(new TypedParameter(typeof(IZone), zone));
zone.ZoneSessionFactory = ctx.Resolve<ZoneSession.Factory>();
zone.RelicManager = ctx.Resolve<Func<IZone, IRelicManager>>().Invoke(zone);
zone.PathFindService = ctx.Resolve<IPathFindService>();

if (configuration.Terraformable)
{
Expand Down
2 changes: 2 additions & 0 deletions src/Perpetuum.Bootstrapper/PerpetuumBootstrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
using System.Transactions;
using IContainer = Autofac.IContainer;
using LogEvent = Perpetuum.Log.LogEvent;
using Perpetuum.Zones.NpcSystem.Presences.PathFinders;

[assembly: SupportedOSPlatform("windows")]
namespace Perpetuum.Bootstrapper
Expand Down Expand Up @@ -357,6 +358,7 @@ private void InitContainer(string gameRoot)
string settingsFile = fileManager.ReadAllText("perpetuum.ini");
GlobalConfiguration configuration = JsonConvert.DeserializeObject<GlobalConfiguration>(settingsFile);
configuration.GameRoot = gameRoot;
RoamingState.Mode = configuration.RoamingMode;

return configuration;
}).SingleInstance();
Expand Down
14 changes: 14 additions & 0 deletions src/Perpetuum/GlobalConfiguration.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Perpetuum.Zones.NpcSystem.Presences.PathFinders;
using System.ComponentModel;

namespace Perpetuum
Expand Down Expand Up @@ -50,5 +52,17 @@ public class GlobalConfiguration
public string DiscordBotToken { get; set; }

public string OpHelpChannelId { get; set; }

// Default roaming mode.
[DefaultValue(RoamingState.RoamingMode.Default)]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
[JsonConverter(typeof(StringEnumConverter))]
public RoamingState.RoamingMode RoamingMode { get; set; }

/// <summary>
/// Gets or sets the maximum number of concurrent pathfinding operations allowed.
/// </summary>
[DefaultValue(0), JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
public int ConcurrentPathFinds { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Perpetuum.Services.Weather;
using Perpetuum.Zones;
using Perpetuum.Zones.Locking.Locks;
using Perpetuum.Zones.NpcSystem.Presences.PathFinders;
using Perpetuum.Zones.Teleporting.Strategies;
using Perpetuum.Zones.Terrains;
using Perpetuum.Zones.Terrains.Materials.Plants;
Expand Down Expand Up @@ -695,6 +696,28 @@ public static void GetWeather(AdminCommandData data)
IZone zone = data.Request.Session.ZoneMgr.GetZone(zoneId);
SendMessageToAll(data, $"Weather set {zone.Weather.GetCurrentWeather()}");
}
[ChatCommand("GetRoamingMode")]
public static void GetRoamingMode(AdminCommandData data)
{
SendMessageToAll(data, $"RoamingMode is {RoamingState.Mode}");
}
[ChatCommand("SetRoamingMode")]
public static void SetRoamingMode(AdminCommandData data)
{
RoamingState.RoamingMode mode = RoamingState.RoamingMode.Default;
bool err = false;
if (!data.Command.Args.IsNullOrEmpty())
{
err = !RoamingState.RoamingMode.TryParse(data.Command.Args[0], out mode);
}
if (err)
{
SendMessageToAll(data, $"Bad RoamingMode!");
throw PerpetuumException.Create(ErrorCodes.RequiredArgumentIsNotSpecified);
}
RoamingState.Mode = mode;
SendMessageToAll(data, $"RoamingMode set {RoamingState.Mode}");
}
#endregion
#region DevCommands
[ChatCommand("ZoneCleanObstacleBlocking")]
Expand Down
174 changes: 174 additions & 0 deletions src/Perpetuum/Services/PathFind/AStarFinderReusable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
using Perpetuum.Services.PathFind;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Threading;

namespace Perpetuum.PathFinders
{
public class AStarFinderReusable
{
public const int W_H = 2048;

private const int WEIGHT = 10;
private const int WEIGHT_DIAG = (int)(1.41421356237 * WEIGHT); // sqrt(2) * WEIGHT
private readonly PriorityQueue<Node, int> _openList = new PriorityQueue<Node, int>();
private readonly uint[] _closedList = new uint[W_H * W_H];

private uint _currentStamp = 0;

/// <summary>
/// Finds a path using the A* algorithm based on the provided pathfinding information.
/// </summary>
public Point[]? FindPathHome(IPathFindInfo pathFindInfo, CancellationToken cancellationToken)
{
var start = pathFindInfo.Start;
var end = pathFindInfo.End;
var heuristic = pathFindInfo.Heuristic;
var passable = pathFindInfo.Passable;

// basic validation
if (start.X < 0 || start.Y < 0 || end.X < 0 || end.Y < 0 || start.X >= W_H || start.Y >= W_H || end.X >= W_H || end.Y >= W_H)
{
return null;
}

if (!passable(end.X, end.Y))
{
return null;
}

if (start == end)
{
return Array.Empty<Point>();
}

_openList.Clear();
if (++_currentStamp >= 1024)
{
_currentStamp = 1;
Array.Clear(_closedList);
}

var startNode = new Node((uint)start.X, (uint)start.Y, 0);
_openList.Enqueue(startNode, heuristic.Calculate(start.X, start.Y, end.X, end.Y) * WEIGHT);
_closedList[(int)startNode.HashCode] = (_currentStamp << 22) | startNode.HashCode;

while (_openList.TryDequeue(out Node node, out _) && !cancellationToken.IsCancellationRequested)
{
if (node.X == end.X && node.Y == end.Y)
{
var result = Backtrace(node, _closedList);
TestPath(result, start, end);
return result;
}

foreach (var neighbor in GetNeighbors(node, passable))
{
int idx = (int)neighbor.HashCode;
var stamp = _closedList[idx] >> 22;

// already processed this stamp -> skip
if (stamp == _currentStamp)
continue;

// set parent and mark with current stamp
_closedList[idx] = (_currentStamp << 22) | node.HashCode;
var priority = neighbor.G + heuristic.Calculate((int)neighbor.X, (int)neighbor.Y, end.X, end.Y) * WEIGHT;
_openList.Enqueue(neighbor, priority);
}
}

return null;
}

[Conditional("DEBUG")]
private static void TestPath(Point[] path, Point start, Point end)
{
if (path.Length < 2)
{
throw new InvalidOperationException($"[AStarFinderReusable] Pathfinding logic error: less than 2 nodes in path");
}
if (path[0] != start || path[^1] != end)
{
throw new InvalidOperationException($"[AStarFinderReusable] Pathfinding logic error: path does not connect start and end");
}
for (int i = 0; i < path.Length - 1; i++)
{
var dx = Math.Abs(path[i].X - path[i + 1].X);
var dy = Math.Abs(path[i].Y - path[i + 1].Y);
if (dx > 1 || dy > 1)
{
throw new InvalidOperationException($"[AStarFinderReusable] Pathfinding logic error: move bigger than 1 cell");
}
}
}

private static Point[] Backtrace(Node node, uint[] list)
{
var stack = new Stack<Point>();

int hash = (int)(node.HashCode & 0x3FFFFF);
while (true)
{
int x = hash & 0x7FF;
int y = (hash >> 11) & 0x7FF;
stack.Push(new Point(x, y));

uint parent = list[hash] & 0x3FFFFF;
if (parent == (uint)hash)
break;

hash = (int)parent;
}

return stack.ToArray();
}

protected static readonly sbyte[,] _n = { { -1, -1 }, { 0, -1 }, { 1, -1 }, { -1, 0 }, { 1, 0 }, { -1, 1 }, { 0, 1 }, { 1, 1 } };

private static IEnumerable<Node> GetNeighbors(Node node, Func<int, int, bool> passable)
{
for (var i = 0; i < 8; i++)
{
var dx = _n[i, 0];
var dy = _n[i, 1];

int nx = (int)node.X + dx;
int ny = (int)node.Y + dy;

if (nx < 0 || ny < 0 || nx >= W_H || ny >= W_H)
continue;

if (!passable(nx, ny))
continue;

int cost = node.G + ((dx == 0 || dy == 0) ? WEIGHT : WEIGHT_DIAG);
yield return new Node((uint)nx, (uint)ny, cost);
}
}

protected class Node
{
private readonly uint _hashCode;
private readonly int _g;

public uint HashCode => _hashCode;
public uint X => _hashCode & 0x7FF;
public uint Y => (_hashCode >> 11) & 0x7FF;
public int G => _g;

public Node(uint x, uint y, int g)
{
_hashCode = (y << 11) | x;
_g = g;
}

public override string ToString()
{
return $"({X}, {Y}), G: {G}";
}
}
}
}
31 changes: 31 additions & 0 deletions src/Perpetuum/Services/PathFind/IPathFindInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using Perpetuum.PathFinders;
using System.Drawing;

namespace Perpetuum.Services.PathFind
{
/// <summary>
/// Interface for providing pathfinding information to the A* algorithm.
/// </summary>
public interface IPathFindInfo
{
/// <summary>
/// Gets the starting point of the path.
/// </summary>
Point Start { get; }
/// <summary>
/// Gets the ending point of the path.
/// </summary>
Point End { get; }
/// <summary>
/// Gets the heuristic function for pathfinding.
/// </summary>
Heuristic Heuristic { get; }

/// <summary>
/// Gets the function that determines if a cell is passable.
/// </summary>
Func<int, int, bool> Passable { get; }

int ZoneId { get; }
}
}
7 changes: 7 additions & 0 deletions src/Perpetuum/Services/PathFind/IPathFindService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Perpetuum.Services.PathFind
{
public interface IPathFindService
{
void EnqueuePathFinding(IPathFindInfo pathFindInfo);
}
}
47 changes: 47 additions & 0 deletions src/Perpetuum/Services/PathFind/PathFindInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using Perpetuum.PathFinders;
using System;
using System.Drawing;

namespace Perpetuum.Services.PathFind
{
public class PathFindInfo : IPathFindInfo
{
private readonly Point _start;
private readonly Point _end;
private readonly Heuristic _heuristic;
private readonly Func<int, int, bool> _passable;
private readonly int _zoneId;

public PathFindInfo(Point start, Point end, Heuristic heuristic, Func<int, int, bool> passable, int zoneId)
{
_start = start;
_end = end;
_heuristic = heuristic;
_passable = passable;
_zoneId = zoneId;
}

public Point Start => _start;

public Point End => _end;

public Heuristic Heuristic => _heuristic;

public Func<int, int, bool> Passable => _passable;

public int ZoneId => _zoneId;
}

public class HomePathFindInfo : PathFindInfo
{
private readonly Action<Point[]> _onPathFound;

public HomePathFindInfo(Point start, Point end, Heuristic heuristic, Func<int, int, bool> passable, int zoneId, Action<Point[]> onPathFound)
: base(start, end, heuristic, passable, zoneId)
{
_onPathFound = onPathFound ?? (_ => { });
}

public Action<Point[]> OnPathFound => _onPathFound;
}
}
Loading
Loading