LANTERN purge B6: delete the legacy single-ability path (sockets are THE ability model)
AbilityRef, AbilityCooldown, EffectiveAbilityStats, DefaultAbility deleted. GoInGameServerSystem seeds the per-frame 4-socket Spark loadout UNCONDITIONALLY (was gym-only); ClassSelectReceiveSystem + DebugOp.SetClass swap FrameId + re-seed sockets + zero SocketCooldown; ClassSwapUtil.Apply drops newAbilityId; EquipSystem weapons become stat-sticks (GrantedAbilityId removed from the item blob/authoring); StatRecomputeSystem folds CharacterStatsRef + sockets only; HUD cooldown bar reads socket 0 of SocketCooldown/EffectiveSocketStats; class HUD readers are FrameId-only (ClassForAbility/AbilityFor deleted); DebugModifierInjectionSystem drops CycleAbility. 390 tests green; Play-verified: a non-gym spawn gets frame=2 with Sparks [7,8,6,9] and no console errors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -142,13 +142,13 @@ namespace ProjectM.Server
|
||||
}
|
||||
break;
|
||||
case DebugOp.SetClass:
|
||||
// Swap an already-spawned player's class IN PLACE (editor dev tool). Class = two replicated
|
||||
// pieces: the AbilityRef Fire slot + the ClassSourceId-tagged StatModifier seeds; the owner's
|
||||
// StatRecomputeSystem refolds EffectiveCharacterStats. Server-authoritative + prediction-correct
|
||||
// (same buffer-mutation path as GrantUpgrade). Reapply + AbilityRef run unconditionally so the
|
||||
// class is correct even on a corpse; the heal is gated on a LIVING player so we don't resurrect
|
||||
// it out-of-band and race PlayerRespawnSystem (which refills to the new max on respawn itself).
|
||||
if (sender != Entity.Null && SystemAPI.HasComponent<AbilityRef>(sender)
|
||||
// Swap an already-spawned player's frame IN PLACE (editor dev tool). Frame = FrameId + the
|
||||
// ClassSourceId-tagged StatModifier seeds + the 4-socket Spark loadout; the owner's
|
||||
// StatRecomputeSystem refolds EffectiveCharacterStats. Server-authoritative + prediction-
|
||||
// correct (same buffer-mutation path as GrantUpgrade). The swap runs even on a corpse; the
|
||||
// heal is gated on a LIVING player so we don't resurrect out-of-band and race
|
||||
// PlayerRespawnSystem (which refills to the new max on respawn itself).
|
||||
if (sender != Entity.Null && SystemAPI.HasBuffer<AbilitySocket>(sender)
|
||||
&& SystemAPI.HasBuffer<StatModifier>(sender))
|
||||
{
|
||||
var classMods = SystemAPI.GetBuffer<StatModifier>(sender);
|
||||
@@ -156,15 +156,23 @@ namespace ProjectM.Server
|
||||
bool haveMeta2 = SystemAPI.TryGetSingleton<MetaUpgradeCatalog>(out var metaCat2)
|
||||
&& SystemAPI.TryGetSingletonEntity<ResourceLedger>(out dir2) && SystemAPI.HasBuffer<MetaTierState>(dir2);
|
||||
var metaRec2 = haveMeta2 ? SystemAPI.GetBuffer<MetaTierState>(dir2) : default;
|
||||
// DR-046: the FULL swap (class seeds + meta re-sync) now lives in the shared ClassSwapUtil,
|
||||
// DR-046: the FULL swap (class seeds + meta re-sync) lives in the shared ClassSwapUtil,
|
||||
// used by BOTH this dev path and the base ClassSelectReceiveSystem so they cannot drift.
|
||||
ClassSwapUtil.Apply((byte)cmd.ArgA, classMods, haveMeta2, metaCat2, metaRec2,
|
||||
out byte swNewClass, out byte swNewAbility);
|
||||
SystemAPI.SetComponent(sender, new AbilityRef { Id = swNewAbility });
|
||||
out byte swNewClass);
|
||||
if (SystemAPI.HasComponent<FrameId>(sender))
|
||||
SystemAPI.SetComponent(sender, new FrameId { Value = swNewClass });
|
||||
if (SystemAPI.HasComponent<PlayerClass>(sender))
|
||||
SystemAPI.SetComponent(sender, new PlayerClass { ClassId = swNewClass });
|
||||
if (SystemAPI.HasComponent<AbilityCooldown>(sender))
|
||||
SystemAPI.SetComponent(sender, new AbilityCooldown { NextFireTick = 0 });
|
||||
ClassTraits.FrameLoadout(swNewClass, out byte sf0, out byte sf1, out byte sf2, out byte sf3);
|
||||
var swSockets = SystemAPI.GetBuffer<AbilitySocket>(sender);
|
||||
swSockets.Clear();
|
||||
swSockets.Add(new AbilitySocket { SparkId = sf0 });
|
||||
swSockets.Add(new AbilitySocket { SparkId = sf1 });
|
||||
swSockets.Add(new AbilitySocket { SparkId = sf2 });
|
||||
swSockets.Add(new AbilitySocket { SparkId = sf3 });
|
||||
if (SystemAPI.HasComponent<SocketCooldown>(sender))
|
||||
SystemAPI.SetComponent(sender, default(SocketCooldown));
|
||||
if (SystemAPI.HasComponent<Health>(sender) && SystemAPI.HasComponent<CharacterStatsRef>(sender)
|
||||
&& SystemAPI.TryGetSingleton<AbilityDatabase>(out var abilityDb2))
|
||||
{
|
||||
@@ -178,6 +186,7 @@ namespace ProjectM.Server
|
||||
}
|
||||
}
|
||||
break;
|
||||
break;
|
||||
case DebugOp.SpawnEnemy:
|
||||
// GYM: spawn a chosen enemy KIND (Drowner/Grindylow) from the baked roster near the sender.
|
||||
if (sender != Entity.Null && SystemAPI.HasComponent<LocalTransform>(sender)
|
||||
|
||||
@@ -14,7 +14,6 @@ namespace ProjectM.Server
|
||||
/// client. In-editor single-process only (client + server worlds in one process). Poke from execute_code:
|
||||
/// DebugModifierInjectionSystem.AddModifier((byte)StatTarget.Damage, (byte)ModOp.Flat, 50f);
|
||||
/// DebugModifierInjectionSystem.AddModifier((byte)StatTarget.MoveSpeed, (byte)ModOp.PercentAdd, 0.5f);
|
||||
/// DebugModifierInjectionSystem.CycleAbility(); // Primary -> FastLight -> SlowHeavy -> Primary
|
||||
/// DebugModifierInjectionSystem.ClearModifiers();
|
||||
/// All applied to the first player on the next server tick.
|
||||
/// </summary>
|
||||
@@ -25,7 +24,6 @@ namespace ProjectM.Server
|
||||
|
||||
static readonly List<PendingModifier> s_Pending = new List<PendingModifier>();
|
||||
static bool s_Clear;
|
||||
static bool s_Cycle;
|
||||
|
||||
/// <summary>Queue a modifier to append to the first player on the next server tick.</summary>
|
||||
public static void AddModifier(byte target, byte op, float value)
|
||||
@@ -36,17 +34,15 @@ namespace ProjectM.Server
|
||||
/// <summary>Clear the first player's whole modifier stack on the next server tick.</summary>
|
||||
public static void ClearModifiers() => s_Clear = true;
|
||||
|
||||
/// <summary>Cycle the first player's primary ability id on the next server tick.</summary>
|
||||
public static void CycleAbility() => s_Cycle = true;
|
||||
|
||||
protected override void OnUpdate()
|
||||
{
|
||||
if (s_Pending.Count == 0 && !s_Clear && !s_Cycle)
|
||||
if (s_Pending.Count == 0 && !s_Clear)
|
||||
return;
|
||||
|
||||
Entity player = Entity.Null;
|
||||
foreach (var (abilityRef, e) in
|
||||
SystemAPI.Query<RefRO<AbilityRef>>().WithAll<PlayerTag, StatModifier>().WithEntityAccess())
|
||||
foreach (var (tag, e) in
|
||||
SystemAPI.Query<RefRO<PlayerTag>>().WithAll<StatModifier>().WithEntityAccess())
|
||||
{
|
||||
player = e;
|
||||
break;
|
||||
@@ -70,19 +66,6 @@ namespace ProjectM.Server
|
||||
}
|
||||
s_Pending.Clear();
|
||||
}
|
||||
|
||||
if (s_Cycle)
|
||||
{
|
||||
var abilityRef = EntityManager.GetComponentData<AbilityRef>(player);
|
||||
abilityRef.Id = abilityRef.Id switch
|
||||
{
|
||||
(byte)AbilityId.Primary => (byte)AbilityId.FastLight,
|
||||
(byte)AbilityId.FastLight => (byte)AbilityId.SlowHeavy,
|
||||
_ => (byte)AbilityId.Primary,
|
||||
};
|
||||
EntityManager.SetComponentData(player, abilityRef);
|
||||
s_Cycle = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user