stuff
This commit is contained in:
parent
a8341e8232
commit
a8c43ba7e2
43 changed files with 2618 additions and 48 deletions
201
src/Roboto.Data/AreaGraph.cs
Normal file
201
src/Roboto.Data/AreaGraph.cs
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
using System.Text.Json;
|
||||
|
||||
namespace Roboto.Data;
|
||||
|
||||
public record AreaNode(
|
||||
string Id,
|
||||
string Name,
|
||||
int Act,
|
||||
int Level,
|
||||
int Order,
|
||||
bool HasWaypoint,
|
||||
bool IsTown,
|
||||
List<string> ConnectsTo);
|
||||
|
||||
/// <summary>
|
||||
/// Graph of game areas loaded from data/poe2/areas.json.
|
||||
/// Supports progression ordering, adjacency queries, and BFS pathfinding.
|
||||
/// </summary>
|
||||
public sealed class AreaGraph
|
||||
{
|
||||
private readonly Dictionary<string, AreaNode> _byId;
|
||||
private readonly Dictionary<string, AreaNode> _byName;
|
||||
private readonly List<AreaNode> _allByOrder;
|
||||
|
||||
private AreaGraph(Dictionary<string, AreaNode> byId, Dictionary<string, AreaNode> byName, List<AreaNode> allByOrder)
|
||||
{
|
||||
_byId = byId;
|
||||
_byName = byName;
|
||||
_allByOrder = allByOrder;
|
||||
}
|
||||
|
||||
public AreaNode? GetById(string id)
|
||||
=> _byId.GetValueOrDefault(id);
|
||||
|
||||
public AreaNode? GetByName(string name)
|
||||
=> _byName.GetValueOrDefault(name);
|
||||
|
||||
public List<AreaNode> GetConnections(string id)
|
||||
{
|
||||
if (!_byId.TryGetValue(id, out var node)) return [];
|
||||
var result = new List<AreaNode>(node.ConnectsTo.Count);
|
||||
foreach (var cid in node.ConnectsTo)
|
||||
{
|
||||
if (_byId.TryGetValue(cid, out var connected))
|
||||
result.Add(connected);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// BFS from currentId to find the lowest-order unvisited reachable area.
|
||||
/// Returns the area ID to target next, or null if progression is complete.
|
||||
/// </summary>
|
||||
public string? FindNextTarget(string currentId, HashSet<string> visited)
|
||||
{
|
||||
if (!_byId.ContainsKey(currentId)) return null;
|
||||
|
||||
// BFS to find all reachable areas
|
||||
var reachable = new HashSet<string>();
|
||||
var queue = new Queue<string>();
|
||||
queue.Enqueue(currentId);
|
||||
reachable.Add(currentId);
|
||||
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
var id = queue.Dequeue();
|
||||
if (!_byId.TryGetValue(id, out var node)) continue;
|
||||
foreach (var cid in node.ConnectsTo)
|
||||
{
|
||||
if (reachable.Add(cid))
|
||||
queue.Enqueue(cid);
|
||||
}
|
||||
}
|
||||
|
||||
// Find the lowest-order unvisited reachable area
|
||||
AreaNode? best = null;
|
||||
foreach (var rid in reachable)
|
||||
{
|
||||
if (visited.Contains(rid)) continue;
|
||||
if (!_byId.TryGetValue(rid, out var node)) continue;
|
||||
if (best is null || CompareOrder(node, best) < 0)
|
||||
best = node;
|
||||
}
|
||||
|
||||
return best?.Id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// BFS shortest path from fromId to toId through the area graph.
|
||||
/// Returns the sequence of area IDs, or null if unreachable.
|
||||
/// </summary>
|
||||
public List<string>? FindAreaPath(string fromId, string toId)
|
||||
{
|
||||
if (fromId == toId) return [fromId];
|
||||
if (!_byId.ContainsKey(fromId) || !_byId.ContainsKey(toId)) return null;
|
||||
|
||||
var prev = new Dictionary<string, string>();
|
||||
var queue = new Queue<string>();
|
||||
queue.Enqueue(fromId);
|
||||
prev[fromId] = fromId;
|
||||
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
var id = queue.Dequeue();
|
||||
if (id == toId)
|
||||
{
|
||||
// Reconstruct path
|
||||
var path = new List<string>();
|
||||
var cur = toId;
|
||||
while (cur != fromId)
|
||||
{
|
||||
path.Add(cur);
|
||||
cur = prev[cur];
|
||||
}
|
||||
path.Add(fromId);
|
||||
path.Reverse();
|
||||
return path;
|
||||
}
|
||||
|
||||
if (!_byId.TryGetValue(id, out var node)) continue;
|
||||
foreach (var cid in node.ConnectsTo)
|
||||
{
|
||||
if (!prev.ContainsKey(cid))
|
||||
{
|
||||
prev[cid] = id;
|
||||
queue.Enqueue(cid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null; // unreachable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two nodes by (act, order) for progression ordering.
|
||||
/// </summary>
|
||||
private static int CompareOrder(AreaNode a, AreaNode b)
|
||||
{
|
||||
var actCmp = a.Act.CompareTo(b.Act);
|
||||
return actCmp != 0 ? actCmp : a.Order.CompareTo(b.Order);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all area IDs with order less than or equal to the given area's order (same act).
|
||||
/// Used to auto-mark earlier areas as visited so progression always moves forward.
|
||||
/// </summary>
|
||||
public List<string> GetEarlierAreas(string areaId)
|
||||
{
|
||||
if (!_byId.TryGetValue(areaId, out var current)) return [];
|
||||
var result = new List<string>();
|
||||
foreach (var node in _allByOrder)
|
||||
{
|
||||
if (node.Act == current.Act && node.Order < current.Order)
|
||||
result.Add(node.Id);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static AreaGraph Load()
|
||||
{
|
||||
var byId = new Dictionary<string, AreaNode>(StringComparer.OrdinalIgnoreCase);
|
||||
var byName = new Dictionary<string, AreaNode>(StringComparer.OrdinalIgnoreCase);
|
||||
var allByOrder = new List<AreaNode>();
|
||||
|
||||
try
|
||||
{
|
||||
var path = Path.Combine("data", "poe2", "areas.json");
|
||||
if (!File.Exists(path)) return new AreaGraph(byId, byName, allByOrder);
|
||||
|
||||
var json = File.ReadAllText(path);
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
|
||||
foreach (var actElement in doc.RootElement.EnumerateArray())
|
||||
{
|
||||
var act = actElement.GetProperty("act").GetInt32();
|
||||
foreach (var area in actElement.GetProperty("areas").EnumerateArray())
|
||||
{
|
||||
var id = area.GetProperty("id").GetString()!;
|
||||
var name = area.GetProperty("name").GetString()!;
|
||||
var level = area.GetProperty("level").GetInt32();
|
||||
var order = area.GetProperty("order").GetInt32();
|
||||
var wp = area.GetProperty("wp").GetBoolean();
|
||||
var town = area.TryGetProperty("town", out var townProp) && townProp.GetBoolean();
|
||||
|
||||
var connects = new List<string>();
|
||||
foreach (var c in area.GetProperty("connects").EnumerateArray())
|
||||
connects.Add(c.GetString()!);
|
||||
|
||||
var node = new AreaNode(id, name, act, level, order, wp, town, connects);
|
||||
byId[id] = node;
|
||||
byName[name] = node;
|
||||
allByOrder.Add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { /* non-critical */ }
|
||||
|
||||
allByOrder.Sort((a, b) => CompareOrder(a, b));
|
||||
return new AreaGraph(byId, byName, allByOrder);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue