feat(gameplay): integrate DRMK NPC spawner, dynamic scene presets, pure combat combo router, and clean character switching
This commit is contained in:
1 parent
3fd7922ac8
commit
b5fc15e4ec
20 files changed
+2329029
-53
No files matched your search
@@ -0,0 +1,68 @@
|
||||
using System.Text.Json;
|
||||
using Ananta.Server.Configuration;
|
||||
|
||||
namespace Ananta.Server.ClientData.Client4229938;
|
||||
|
||||
internal sealed record NpcCatalogEntry4229938(
|
||||
uint Id,
|
||||
string Name,
|
||||
uint GeneralModelId,
|
||||
uint AgentPersonaId,
|
||||
uint AnimSetTag,
|
||||
uint BehaviorActionConfigId,
|
||||
uint ActionGroupId,
|
||||
uint AiSettingId,
|
||||
uint InteractSettingId);
|
||||
|
||||
internal static class NpcCatalog4229938
|
||||
{
|
||||
private static readonly Lazy<Dictionary<uint, NpcCatalogEntry4229938>> Cache = new(Load);
|
||||
|
||||
internal static bool TryGet(uint id, out NpcCatalogEntry4229938 entry)
|
||||
=> Cache.Value.TryGetValue(id, out entry!);
|
||||
|
||||
private static Dictionary<uint, NpcCatalogEntry4229938> Load()
|
||||
{
|
||||
var root = PrivateServerConfigStore.ResolveProjectPath(PrivateServerConfigStore.Current.Paths.ClientConfigs);
|
||||
var path = Path.Combine(root, "AgentConfig.json");
|
||||
using var doc = JsonDocument.Parse(File.ReadAllText(path));
|
||||
if (!doc.RootElement.TryGetProperty("records", out var records) || records.ValueKind != JsonValueKind.Array)
|
||||
throw new InvalidDataException($"AgentConfig does not contain records: {path}");
|
||||
var result = new Dictionary<uint, NpcCatalogEntry4229938>();
|
||||
foreach (var row in records.EnumerateArray())
|
||||
{
|
||||
if (!row.TryGetProperty("Id", out var idNode) || !idNode.TryGetUInt32(out var id) || id == 0)
|
||||
continue;
|
||||
var name = row.TryGetProperty("Name", out var n) && n.ValueKind == JsonValueKind.String ? n.GetString() ?? "" : "";
|
||||
var model = row.TryGetProperty("GeneralModelId", out var m) && m.TryGetUInt32(out var modelId) ? modelId : 0;
|
||||
static uint UInt32(JsonElement source, string propertyName)
|
||||
=> source.TryGetProperty(propertyName, out var node) && node.TryGetUInt32(out var value) ? value : 0;
|
||||
|
||||
var sex = UInt32(row, "SexType");
|
||||
var age = UInt32(row, "Age");
|
||||
var economicLevel = UInt32(row, "EconomicLevel");
|
||||
var refined = economicLevel >= 3;
|
||||
var persona = (sex, age >= 45, refined) switch
|
||||
{
|
||||
(1, false, false) when age < 18 => 45200003u,
|
||||
(2, false, false) when age < 18 => 45200004u,
|
||||
(1, false, false) => 45200007u,
|
||||
(2, false, false) => 45200008u,
|
||||
(1, false, true) => 45200009u,
|
||||
(2, false, true) => 45200010u,
|
||||
(1, true, false) => 45200011u,
|
||||
(2, true, false) => 45200012u,
|
||||
(1, true, true) => 45200013u,
|
||||
(2, true, true) => 45200014u,
|
||||
_ => 0u,
|
||||
};
|
||||
var animSetTag = UInt32(row, "AnimStereotype");
|
||||
if (animSetTag == 0)
|
||||
animSetTag = UInt32(row, "BattleAnimType");
|
||||
result[id] = new(id, name, model, persona, animSetTag,
|
||||
UInt32(row, "Action"), UInt32(row, "ActionGroupId"),
|
||||
UInt32(row, "AI"), UInt32(row, "InteractSetting"));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using System.Text.Json;
|
||||
using Ananta.Server.Configuration;
|
||||
|
||||
namespace Ananta.Server.ClientData.Client4229938;
|
||||
|
||||
internal sealed record PoiActionCatalogEntry4229938(uint Id, uint MainActionGroup, uint[] MainActions)
|
||||
{
|
||||
internal uint UsageId { get; init; }
|
||||
internal uint[] BelongingSceneItems { get; init; } = [];
|
||||
internal uint SelectMainAction(ulong entityId)
|
||||
=> MainActions.Length == 0 ? 0 : MainActions[(int)(entityId % (ulong)MainActions.Length)];
|
||||
}
|
||||
|
||||
internal static class PoiActionCatalog4229938
|
||||
{
|
||||
private static readonly Lazy<Dictionary<uint, PoiActionCatalogEntry4229938>> Cache = new(Load);
|
||||
|
||||
internal static bool TryGet(uint id, out PoiActionCatalogEntry4229938 entry)
|
||||
=> Cache.Value.TryGetValue(id, out entry!);
|
||||
|
||||
private static Dictionary<uint, PoiActionCatalogEntry4229938> Load()
|
||||
{
|
||||
var root = PrivateServerConfigStore.ResolveProjectPath(PrivateServerConfigStore.Current.Paths.ClientConfigs);
|
||||
var path = Path.Combine(root, "UrbanDiversityPOIActionConfig.json");
|
||||
using var doc = JsonDocument.Parse(File.ReadAllText(path));
|
||||
if (!doc.RootElement.TryGetProperty("records", out var records) || records.ValueKind != JsonValueKind.Array)
|
||||
throw new InvalidDataException($"UrbanDiversityPOIActionConfig does not contain records: {path}");
|
||||
|
||||
var result = new Dictionary<uint, PoiActionCatalogEntry4229938>();
|
||||
using var usagesDoc = JsonDocument.Parse(File.ReadAllText(Path.Combine(root, "BelongingBelongingUsageConfig.json")));
|
||||
var usages = usagesDoc.RootElement.GetProperty("records").EnumerateArray()
|
||||
.ToDictionary(row => row.GetProperty("Id").GetUInt32());
|
||||
foreach (var row in records.EnumerateArray())
|
||||
{
|
||||
if (!row.TryGetProperty("Id", out var idNode) || !idNode.TryGetUInt32(out var id) || id == 0)
|
||||
continue;
|
||||
var actionGroup = row.TryGetProperty("MainActionGroup", out var groupNode) && groupNode.TryGetUInt32(out var group)
|
||||
? group
|
||||
: 0;
|
||||
var actions = row.TryGetProperty("MainActions", out var actionsNode) && actionsNode.ValueKind == JsonValueKind.Array
|
||||
? actionsNode.EnumerateArray()
|
||||
.Select(node => node.TryGetUInt32(out var action) ? action : 0)
|
||||
.Where(action => action != 0)
|
||||
.ToArray()
|
||||
: [];
|
||||
var usageId = row.TryGetProperty("UsageId", out var usageNode) ? usageNode.GetUInt32() : 0;
|
||||
uint[] belongingItems = [];
|
||||
if (usageId != 0)
|
||||
{
|
||||
if (!usages.TryGetValue(usageId, out var usage))
|
||||
throw new InvalidDataException($"POI {id} refers to missing belonging usage {usageId}");
|
||||
belongingItems = usage.GetProperty("BelongingSceneItem").EnumerateArray()
|
||||
.Select(node => node.GetUInt32()).Where(item => item != 0).Distinct().ToArray();
|
||||
// Type-based usages choose from the NPC's inventory. For standalone
|
||||
// admin NPCs, seed the usage's configured representative item.
|
||||
if (belongingItems.Length == 0 && usage.GetProperty("BelongingType").GetArrayLength() != 0)
|
||||
{
|
||||
var slotItem = usage.GetProperty("SlotSceneItem").GetUInt32();
|
||||
if (slotItem != 0) belongingItems = [slotItem];
|
||||
}
|
||||
}
|
||||
result[id] = new(id, actionGroup, actions) { UsageId = usageId, BelongingSceneItems = belongingItems };
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
using System.Text.Json;
|
||||
using Ananta.Server.Configuration;
|
||||
|
||||
namespace Ananta.Server.ClientData.Client4229938;
|
||||
|
||||
internal sealed record ScenePresetEntry4229938(
|
||||
string Key, string Label, uint RaidId, ulong InstanceId, uint UniverseId,
|
||||
float X, float Y, float Z, float Facing);
|
||||
|
||||
/// <summary>
|
||||
/// Scene ("raid") preset catalog built from the game dumps:
|
||||
/// RaidConfig.json (raid → SceneId = instance id, Multiverse = universe id) and
|
||||
/// MapentranceConfig.json (entrance → RaidId + Coordinate/MapTeleportPos + facing).
|
||||
/// Verified: Nova raid 23300888 → scene/instance 20001222, Lingyun 23300999 → 20001223.
|
||||
/// </summary>
|
||||
internal static class SceneCatalog4229938
|
||||
{
|
||||
private static readonly Lazy<IReadOnlyList<ScenePresetEntry4229938>> Cache = new(Load);
|
||||
|
||||
internal static IReadOnlyList<ScenePresetEntry4229938> Presets => Cache.Value;
|
||||
|
||||
internal static bool TryGetByRaid(uint raidId, out ScenePresetEntry4229938 preset)
|
||||
{
|
||||
foreach (var p in Cache.Value)
|
||||
{
|
||||
if (p.RaidId != raidId)
|
||||
continue;
|
||||
preset = p;
|
||||
return true;
|
||||
}
|
||||
preset = null!;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static IReadOnlyList<ScenePresetEntry4229938> Load()
|
||||
{
|
||||
var root = PrivateServerConfigStore.ResolveProjectPath(PrivateServerConfigStore.Current.Paths.ClientConfigs);
|
||||
var raids = LoadRecords(Path.Combine(root, "RaidConfig.json"));
|
||||
var entrances = LoadRecords(Path.Combine(root, "MapentranceConfig.json"));
|
||||
|
||||
var raidById = new Dictionary<uint, JsonElement>();
|
||||
foreach (var row in raids)
|
||||
{
|
||||
if (row.TryGetProperty("Id", out var id) && id.TryGetUInt32(out var raidId) && raidId != 0)
|
||||
raidById[raidId] = row;
|
||||
}
|
||||
|
||||
uint defaultUniverse;
|
||||
try { defaultUniverse = PrivateServerConfigStore.Current.World.UniverseId; }
|
||||
catch { defaultUniverse = 76000888; }
|
||||
|
||||
var result = new List<ScenePresetEntry4229938>();
|
||||
foreach (var row in entrances)
|
||||
{
|
||||
if (!row.TryGetProperty("Id", out var idNode) || !idNode.TryGetUInt32(out var entranceId))
|
||||
continue;
|
||||
if (!row.TryGetProperty("RaidId", out var raidNode) || !raidNode.TryGetUInt32(out var raidId) || raidId == 0)
|
||||
continue;
|
||||
var coords = row.TryGetProperty("MapTeleportPos", out var tp) && tp.ValueKind == JsonValueKind.Array && tp.GetArrayLength() >= 3
|
||||
? tp
|
||||
: row.TryGetProperty("Coordinate", out var c) && c.ValueKind == JsonValueKind.Array && c.GetArrayLength() >= 3 ? c : default;
|
||||
if (coords.ValueKind != JsonValueKind.Array)
|
||||
continue;
|
||||
var xyz = coords.EnumerateArray().Take(3).ToArray();
|
||||
if (xyz.Any(e => e.ValueKind != JsonValueKind.Number))
|
||||
continue;
|
||||
float x = (float)xyz[0].GetDouble(), y = (float)xyz[1].GetDouble(), z = (float)xyz[2].GetDouble();
|
||||
if (!float.IsFinite(x) || !float.IsFinite(y) || !float.IsFinite(z))
|
||||
continue;
|
||||
float facing = row.TryGetProperty("EntranceFacing", out var f) && f.ValueKind == JsonValueKind.Number
|
||||
? (float)f.GetDouble() : 0f;
|
||||
|
||||
ulong instanceId = 0;
|
||||
uint universeId = defaultUniverse;
|
||||
if (raidById.TryGetValue(raidId, out var raid))
|
||||
{
|
||||
if (raid.TryGetProperty("SceneId", out var s) && s.TryGetUInt64(out var sceneId) && sceneId != 0)
|
||||
instanceId = sceneId;
|
||||
if (raid.TryGetProperty("Multiverse", out var m) && m.TryGetUInt32(out var multi) && multi != 0)
|
||||
universeId = multi;
|
||||
}
|
||||
if (instanceId == 0)
|
||||
continue;
|
||||
|
||||
var name = row.TryGetProperty("Name", out var n) && n.ValueKind == JsonValueKind.String
|
||||
? n.GetString() ?? string.Empty : string.Empty;
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
name = $"Entrance {entranceId}";
|
||||
result.Add(new ScenePresetEntry4229938(
|
||||
$"entrance-{entranceId}", $"{name} (raid {raidId})",
|
||||
raidId, instanceId, universeId, x, y, z, facing));
|
||||
}
|
||||
|
||||
result.Sort((a, b)
|
||||
=> a.RaidId != b.RaidId ? a.RaidId.CompareTo(b.RaidId) : string.Compare(a.Label, b.Label, StringComparison.Ordinal));
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<JsonElement> LoadRecords(string path)
|
||||
{
|
||||
using var doc = JsonDocument.Parse(File.ReadAllText(path));
|
||||
if (!doc.RootElement.TryGetProperty("records", out var records) || records.ValueKind != JsonValueKind.Array)
|
||||
throw new InvalidDataException($"Config does not contain records: {path}");
|
||||
// Clone: the document is disposed on return.
|
||||
return records.EnumerateArray().Select(e => e.Clone()).ToList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Text.Json;
|
||||
using Ananta.Server.Configuration;
|
||||
|
||||
namespace Ananta.Server.ClientData.Client4229938;
|
||||
|
||||
internal sealed record WeatherEntry4229938(uint Id, string Name);
|
||||
|
||||
/// <summary>Weather catalog from WeatherConfig.json (5 entries in this build).</summary>
|
||||
internal static class WeatherCatalog4229938
|
||||
{
|
||||
private static readonly Lazy<IReadOnlyList<WeatherEntry4229938>> Cache = new(Load);
|
||||
|
||||
internal static IReadOnlyList<WeatherEntry4229938> All => Cache.Value;
|
||||
|
||||
private static IReadOnlyList<WeatherEntry4229938> Load()
|
||||
{
|
||||
var root = PrivateServerConfigStore.ResolveProjectPath(PrivateServerConfigStore.Current.Paths.ClientConfigs);
|
||||
var path = Path.Combine(root, "WeatherConfig.json");
|
||||
if (!File.Exists(path))
|
||||
return [];
|
||||
using var doc = JsonDocument.Parse(File.ReadAllText(path));
|
||||
var result = new List<WeatherEntry4229938>();
|
||||
if (!doc.RootElement.TryGetProperty("records", out var records) || records.ValueKind != JsonValueKind.Array)
|
||||
return result;
|
||||
foreach (var row in records.EnumerateArray())
|
||||
{
|
||||
if (!row.TryGetProperty("Id", out var id) || !id.TryGetUInt32(out var weatherId) || weatherId == 0)
|
||||
continue;
|
||||
var name = row.TryGetProperty("Name", out var n) && n.ValueKind == JsonValueKind.String
|
||||
? n.GetString() ?? string.Empty : string.Empty;
|
||||
result.Add(new WeatherEntry4229938(weatherId, string.IsNullOrWhiteSpace(name) ? $"Weather {weatherId}" : name));
|
||||
}
|
||||
return result.OrderBy(w => w.Id).ToList();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user