52818d0dd6
Per Phase1_Combat_Gym_Build_Spec §6 (the Blink Spark = a second, socketed dash): - AbilityArchetype += Movement (falls through AbilityFireSystem; a sibling system drives it). - AbilityId += the 5 Phase-1 Sparks (DecoyWisp/HookPull/Vortex/Blink/LightZone). - BlinkState: predicted, non-replicated blink window (DashState sibling). - BlinkSystem: velocity blink (never a LocalTransform write), triggered by the Movement- archetype socket, dash-wins precedence ([UpdateAfter(DashSystem)]), sole writer of its socket's SocketCooldown row; reads socket loadout/cooldown by entity via lookups (query at 6 args, under the 7-cap). Baked BlinkState on the player. L1 clean; L2 494/494. Dead until a Blink Spark def is socketed (ability-database SO = next). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
87 lines
3.4 KiB
C#
87 lines
3.4 KiB
C#
using Unity.Collections;
|
|
using Unity.Entities;
|
|
|
|
namespace ProjectM.Simulation
|
|
{
|
|
/// <summary>Ability KIND for the AbilityFireSystem dispatch (MC-4 spike for MC-6). Backed by <c>byte</c> so the
|
|
/// baked blob stores a byte (the Burst cross-assembly enum rule); the runtime switch compares the byte value.</summary>
|
|
public enum AbilityArchetype : byte
|
|
{
|
|
Projectile = 0, // today's path: spawn a projectile ghost
|
|
Hitscan = 1, // MC-6
|
|
Cone = 2, // MC-6 (melee-style cone via the ability system)
|
|
Aoe = 3, // MC-6
|
|
Movement = 4, // LANTERN: NOT dispatched by AbilityFireSystem (falls through); a sibling predicted system (BlinkSystem) drives it
|
|
}
|
|
|
|
/// <summary>One authored ability definition, baked immutable into the AbilityDatabase blob.</summary>
|
|
public struct AbilityDefBlob
|
|
{
|
|
public byte Id; // AbilityId
|
|
public byte Archetype; // AbilityArchetype (0 = Projectile); read at dispatch in AbilityFireSystem (MC-6 spike)
|
|
public float Damage;
|
|
public float ProjectileSpeed;
|
|
public float Range;
|
|
public float AutoTargetRange;
|
|
public float AutoTargetConeRadians;
|
|
public int CooldownTicks;
|
|
public FixedString64Bytes Name;
|
|
}
|
|
|
|
/// <summary>One authored character-stats definition, baked immutable into the AbilityDatabase blob.</summary>
|
|
public struct CharacterStatsBlob
|
|
{
|
|
public byte Id; // CharacterId
|
|
public float MoveSpeed;
|
|
public float TurnRateRadiansPerSec;
|
|
public float MaxHealth;
|
|
public FixedString64Bytes Name;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Immutable designer-authored definition database, baked from ScriptableObjects to a blob asset and
|
|
/// shared by every entity (Burst-fast, zero per-instance cost). Looked up by stable id. Entity/prefab
|
|
/// references are NOT stored here (blobs don't remap entity refs) - see AbilityPrefabElement.
|
|
///
|
|
/// NOTE: the lookups are intentionally NOT 'readonly' methods. 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. Plain (non-readonly) methods access the
|
|
/// BlobArray in place. Always reach these through 'ref blob.Value'.
|
|
/// </summary>
|
|
public struct AbilityDatabaseBlob
|
|
{
|
|
public BlobArray<AbilityDefBlob> Abilities;
|
|
public BlobArray<CharacterStatsBlob> Characters;
|
|
|
|
/// <summary>Linear lookup by ability id (the array is tiny). Returns false if not present.</summary>
|
|
public bool TryGetAbility(byte id, out AbilityDefBlob def)
|
|
{
|
|
for (int i = 0; i < Abilities.Length; i++)
|
|
{
|
|
if (Abilities[i].Id == id)
|
|
{
|
|
def = Abilities[i];
|
|
return true;
|
|
}
|
|
}
|
|
def = default;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>Linear lookup by character id (the array is tiny). Returns false if not present.</summary>
|
|
public bool TryGetCharacter(byte id, out CharacterStatsBlob def)
|
|
{
|
|
for (int i = 0; i < Characters.Length; i++)
|
|
{
|
|
if (Characters[i].Id == id)
|
|
{
|
|
def = Characters[i];
|
|
return true;
|
|
}
|
|
}
|
|
def = default;
|
|
return false;
|
|
}
|
|
}
|
|
}
|