using Unity.Collections; using Unity.Entities; namespace ProjectM.Simulation { /// /// One authored item definition, baked immutable into the blob. This is the /// single source of truth for everything an item IS — resources, tools, weapons, gear, consumables — so /// adding game content is an authoring row + re-bake, with no code change. Id space is the SAME /// ushort space as / , and it /// SUBSUMES the low byte ids (Aether=1/Ore=2/Biomass=3) — a resource is just a /// low-id item of . KEEP ids 1-3 stable for the existing resources and /// reserve new item ids > 3; 0 = none. Entity/prefab refs do NOT live here (blobs don't remap entity /// refs) — a future companion buffer carries those, exactly like AbilityPrefabElement. /// /// The blob is config (baked identically into both worlds, NOT replicated, NOT in SaveData), so growing /// this struct later (a granted-ability id, a StatModifier-spec array, a slot id for Phase 1/2/3) is a /// pure re-bake with zero migration: no SaveData version bump, no ghost-hash change, no desync. /// is baked NOW because the project's progression axis is gear tiers, so Phase 2/3 tier gating is a /// content-only edit. /// /// One stat-modifier grant on an equippable item, stored INLINE (NOT a nested BlobArray: a nested /// BlobArray is a relative-offset pointer that corrupts the moment /// returns the containing BY VALUE — the same copy hazard the class note warns about). /// Target 255 = unused. public struct ItemModSpec { /// as a byte; 255 = unused slot. public byte Target; /// as a byte. public byte Op; /// Magnitude (flat amount or fractional percent). public float Value; } public struct ItemDefBlob { /// Stable item id (ushort; 1-3 reserved for the existing resources, keep stable for saves). public ushort ItemId; /// Broad category (see ), stored as a byte. public byte Category; /// Progression tier (0 = base). Higher-tier tools harvest higher-tier nodes / hit harder (Phase 2/3). public byte Tier; /// Max units that stack in a single inventory slot (1 for non-stacking equipment). public int StackMax; /// Equip slot (see ); 255 = not equippable. public byte EquipSlot; /// AbilityId granted when equipped in the Weapon slot (0 = none); the equip handler writes it into AbilityRef.Id. public byte GrantedAbilityId; /// Up to INLINE stat-mod grants applied while equipped (Target 255 = unused). Inline, not a nested BlobArray. public ItemModSpec Mod0, Mod1, Mod2, Mod3; /// Designer-facing display name (shown in the HUD inventory panel). public FixedString64Bytes Name; /// Number of inline mod slots. public const int MaxMods = 4; /// Indexed access to the inline mod slots (returns a copy — safe, ItemModSpec holds no BlobArray). public ItemModSpec GetMod(int i) { switch (i) { case 0: return Mod0; case 1: return Mod1; case 2: return Mod2; default: return Mod3; } } } /// /// Immutable designer-authored item database, baked from ScriptableObjects to a blob asset and shared by /// every entity (Burst-fast, zero per-instance cost). Looked up by stable /// — ID-KEYED, never by array index, so inserting a new item never renumbers existing ids. /// /// NOTE: the lookup is intentionally NOT a 'readonly' method. A readonly struct method forces a defensive /// copy of a field when calling a non-readonly member on it; copying a BlobArray breaks its relative-offset /// pointer, so the array would read as empty. A plain (non-readonly) method accesses the BlobArray in place. /// Always reach this through 'ref blob.Value' (mirrors ). /// public struct ItemDatabaseBlob { public BlobArray Items; /// Linear lookup by item id (the array is tiny). Returns false if not present. public bool TryGetItem(ushort id, out ItemDefBlob def) { for (int i = 0; i < Items.Length; i++) { if (Items[i].ItemId == id) { def = Items[i]; return true; } } def = default; return false; } } }