Slice 2 (WIP): class data layer + melee-augment routing
Foundation for Two Classes (DR-037). New ids (CharacterId.Warrior/Ranger, AbilityId.WarriorCone, StatTarget.MeleeDamage/MeleeRange); CharacterStatsRef.Id -> [GhostField] so the owning client folds the right class stats; MeleeComboSystem folds per-player MeleeDamage/MeleeRange off the replicated StatModifier buffer (HasBuffer-guarded -> identity without class seeds, so behavior-preserving). 345/345 EditMode. Slice 2 design review + locked forks logged in the session note. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,15 +1,16 @@
|
||||
using Unity.Entities;
|
||||
using Unity.NetCode;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Which authored character-stats definition this entity uses - a light key into the CharacterStats
|
||||
/// blob, replacing M2's inlined PlayerMoveStats values. Not replicated (baked identically on both
|
||||
/// worlds); promote to a GhostField if runtime character changes are ever needed. <c>Id</c> stores a
|
||||
/// blob, replacing M2's inlined PlayerMoveStats values. NOW a [GhostField] (Slice 2 classes) so the
|
||||
/// server-written per-player class id replicates -> the owning client folds correct stats. <c>Id</c> stores a
|
||||
/// <see cref="CharacterId"/>.
|
||||
/// </summary>
|
||||
public struct CharacterStatsRef : IComponentData
|
||||
{
|
||||
public byte Id;
|
||||
[GhostField] public byte Id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ namespace ProjectM.Simulation
|
||||
ComponentLookup<KnockbackState> m_KnockbackLookup;
|
||||
ComponentLookup<RegionTag> m_RegionLookup;
|
||||
BufferLookup<InventorySlot> m_InvLookup;
|
||||
BufferLookup<StatModifier> m_StatModLookup;
|
||||
|
||||
[BurstCompile]
|
||||
public void OnCreate(ref SystemState state)
|
||||
@@ -58,6 +59,7 @@ namespace ProjectM.Simulation
|
||||
m_KnockbackLookup = state.GetComponentLookup<KnockbackState>(isReadOnly: false);
|
||||
m_RegionLookup = state.GetComponentLookup<RegionTag>(isReadOnly: true);
|
||||
m_InvLookup = state.GetBufferLookup<InventorySlot>(isReadOnly: false);
|
||||
m_StatModLookup = state.GetBufferLookup<StatModifier>(isReadOnly: true);
|
||||
state.RequireForUpdate<NetworkTime>();
|
||||
}
|
||||
|
||||
@@ -87,11 +89,12 @@ namespace ProjectM.Simulation
|
||||
// Server-only queue of cleaves to resolve after the player loop (so enemies are gathered ONCE, and only
|
||||
// when at least one swing actually started — no per-tick enemy gather on idle/client ticks).
|
||||
var cleaves = isServer ? new NativeList<PendingCleave>(Allocator.Temp) : default;
|
||||
m_StatModLookup.Update(ref state); // Slice 2: per-player melee stat fold (read inside the player loop)
|
||||
|
||||
foreach (var (mc, control, input, facing, xform, owner, ds) in
|
||||
foreach (var (mc, control, input, facing, xform, owner, ds, entity) in
|
||||
SystemAPI.Query<RefRW<MeleeCombo>, RefRW<CharacterControl>, RefRO<PlayerInput>,
|
||||
RefRO<PlayerFacing>, RefRO<LocalTransform>, RefRO<GhostOwner>, RefRO<DashState>>()
|
||||
.WithAll<Simulate>().WithDisabled<Dead>())
|
||||
.WithAll<Simulate>().WithDisabled<Dead>().WithEntityAccess())
|
||||
{
|
||||
// A dash window (i-frame OR recovery) active = dash owns movement + blocks a swing start (dash-cancel).
|
||||
bool dashActive = ds.ValueRO.StartTick != 0u
|
||||
@@ -138,14 +141,19 @@ namespace ProjectM.Simulation
|
||||
if (swingStarted && isServer)
|
||||
{
|
||||
bool isFin = swingStep >= comboLen;
|
||||
// Slice 2: fold the player's class/run StatModifiers onto the live-tunable melee base so the
|
||||
// PRIMARY verb scales with class identity (Warrior +MeleeDamage/+reach) + run augments.
|
||||
bool hasMods = m_StatModLookup.HasBuffer(entity);
|
||||
float pDamage = math.max(0f, hasMods ? StatMath.Apply(baseDamage, StatTarget.MeleeDamage, m_StatModLookup[entity]) : baseDamage);
|
||||
float pRange = math.max(0f, hasMods ? StatMath.Apply(baseRange, StatTarget.MeleeRange, m_StatModLookup[entity]) : baseRange);
|
||||
float2 face = facing.ValueRO.Direction;
|
||||
face = math.lengthsq(face) < 1e-6f ? new float2(0f, 1f) : math.normalize(face);
|
||||
cleaves.Add(new PendingCleave
|
||||
{
|
||||
From = xform.ValueRO.Position,
|
||||
Face = face,
|
||||
Damage = isFin ? baseDamage * finisherMult : baseDamage,
|
||||
Range = isFin ? baseRange * finisherMult : baseRange,
|
||||
Damage = isFin ? pDamage * finisherMult : pDamage,
|
||||
Range = isFin ? pRange * finisherMult : pRange,
|
||||
KnockSpeed = isFin ? knockSpeed * finisherMult : knockSpeed,
|
||||
OwnerId = owner.ValueRO.NetworkId,
|
||||
Stamp = stamp,
|
||||
|
||||
Reference in New Issue
Block a user