43f355c06b
Equipment slots reusing the AbilityRef/StatModifier machinery: EquipmentSlot [GhostField] buffer (index=slot), server-only event-driven EquipSystem (RPC). Weapon -> AbilityRef.Id swaps the attack (prefab + base stats, prediction-correct); gear -> StatModifiers tagged a reserved per-slot EquipSourceId, stripped target-agnostically via RemoveBySourceId. Item mods are INLINE on ItemDefBlob (a nested BlobArray reads empty under the by-value TryGetItem copy). Atomic equip-over swap (no item loss); DefaultAbility restores the unarmed ability on weapon-unequip. Client keys + build-safe hooks; HUD equipment panel + click-to-equip. 4 catalog weapon/gear items wired + re-baked. Play-validated host+client: weapon equip swaps AbilityRef on both worlds, gear folds into EffectiveCharacterStats, unequip reverses + restores DefaultAbility, all replicated to the owner. See DR-027. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
71 lines
3.0 KiB
C#
71 lines
3.0 KiB
C#
using System.Collections.Generic;
|
|
using ProjectM.Simulation;
|
|
using Unity.Collections;
|
|
using Unity.Entities;
|
|
using UnityEngine;
|
|
|
|
namespace ProjectM.Authoring
|
|
{
|
|
/// <summary>
|
|
/// Bakes the designer-authored item definitions into a single ItemDatabase blob singleton (immutable,
|
|
/// shared, Burst-fast), mirroring AbilityDatabaseAuthoring. Place ONE in the gameplay subscene; it streams
|
|
/// identically into the client and server worlds (config, not replicated). DependsOn each definition so a
|
|
/// value change re-bakes the blob. Runtime lookup is ID-keyed (ItemDatabaseBlob.TryGetItem), so the list
|
|
/// order here does not matter and inserting an item never renumbers existing ids.
|
|
/// </summary>
|
|
public class ItemDatabaseAuthoring : MonoBehaviour
|
|
{
|
|
[Tooltip("All item definitions in the game (resources + tools/gear). Looked up at runtime by ItemId.")]
|
|
public List<ItemDefinition> Items = new List<ItemDefinition>();
|
|
|
|
private class DatabaseBaker : Baker<ItemDatabaseAuthoring>
|
|
{
|
|
public override void Bake(ItemDatabaseAuthoring authoring)
|
|
{
|
|
var entity = GetEntity(TransformUsageFlags.None);
|
|
|
|
int count = authoring.Items != null ? authoring.Items.Count : 0;
|
|
|
|
var builder = new BlobBuilder(Allocator.Temp);
|
|
ref var root = ref builder.ConstructRoot<ItemDatabaseBlob>();
|
|
var arr = builder.Allocate(ref root.Items, count);
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
var def = authoring.Items[i];
|
|
if (def == null) { arr[i] = default; continue; }
|
|
DependsOn(def);
|
|
arr[i] = new ItemDefBlob
|
|
{
|
|
ItemId = (ushort)def.ItemId,
|
|
Category = def.Category,
|
|
Tier = def.Tier,
|
|
StackMax = def.StackMax,
|
|
EquipSlot = def.EquipSlot,
|
|
GrantedAbilityId = def.GrantedAbilityId,
|
|
Mod0 = ModAt(def, 0),
|
|
Mod1 = ModAt(def, 1),
|
|
Mod2 = ModAt(def, 2),
|
|
Mod3 = ModAt(def, 3),
|
|
Name = def.DisplayName,
|
|
};
|
|
}
|
|
|
|
var blob = builder.CreateBlobAssetReference<ItemDatabaseBlob>(Allocator.Persistent);
|
|
builder.Dispose();
|
|
AddBlobAsset(ref blob, out _);
|
|
AddComponent(entity, new ItemDatabase { Value = blob });
|
|
|
|
static ItemModSpec ModAt(ItemDefinition def, int i)
|
|
{
|
|
if (def.Mods != null && i < def.Mods.Count)
|
|
{
|
|
var m = def.Mods[i];
|
|
return new ItemModSpec { Target = m.Target, Op = m.Op, Value = m.Value };
|
|
}
|
|
return new ItemModSpec { Target = 255 };
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|