using DatReaderWriter.DBObjs; using DatReaderWriter.Enums; using DatReaderWriter.Types; namespace AcDream.Core.Vfx; /// /// Exact retail PhysicsScriptTable lookup: hash by raw script type, then scan /// that type's entries in stored DAT order for the first upper threshold. /// /// /// Port of PhysicsScriptTable::GetScript at 0x00521E40 and /// PhysicsScriptTableData::GetScript at 0x00521A20. /// public sealed class PhysicsScriptTableResolver { private readonly Func _loadTable; public PhysicsScriptTableResolver(Func loadTable) => _loadTable = loadTable ?? throw new ArgumentNullException(nameof(loadTable)); /// /// Returns the selected PhysicsScript DID, or null for a missing table or /// type, an unordered/above-range intensity, or an invalid script DID. /// public uint? Resolve(uint tableDid, uint rawScriptType, float intensity) => Resolve(tableDid, rawScriptType, intensity, out _); /// /// Resolves a typed play while preserving a corrupt-DAT exception for the /// App owner to diagnose with entity context. A load failure remains a /// retail no-play and never corrupts the caller's script FIFO. /// public uint? Resolve( uint tableDid, uint rawScriptType, float intensity, out Exception? loadFailure) { loadFailure = null; if (!IsPhysicsScriptTableDid(tableDid)) return null; PhysicsScriptTable? table; try { table = _loadTable(tableDid); } catch (Exception error) { loadFailure = error; return null; } if (table is null || table.Id != tableDid || !table.ScriptTable.TryGetValue( unchecked((PlayScript)rawScriptType), out PhysicsScriptTableData? data)) { return null; } // Retail compares intensity <= Mod. Do not sort: equal and duplicate // thresholds resolve to the first stored entry. IEEE comparisons make // NaN fall through all entries, +infinity fall through finite entries, // and -infinity select the first finite threshold. foreach (ScriptAndModData entry in data.Scripts) { if (intensity <= entry.Mod) { uint scriptDid = entry.ScriptId.DataId; return IsPhysicsScriptDid(scriptDid) ? scriptDid : null; } } return null; } public static bool IsPhysicsScriptDid(uint did) => // AC data IDs encode the DBObj type in the high byte; the remaining // 24 bits are the file index and must not be truncated to 16 bits. (did & 0xFF000000u) == 0x33000000u; public static bool IsPhysicsScriptTableDid(uint did) => (did & 0xFF000000u) == 0x34000000u; }