Files
kronic 0948d49886 Combat: ability archetype byte (MC-6 dispatch spike)
byte Archetype on AbilityDefBlob (AbilityArchetype enum: Projectile/Hitscan/Cone/Aoe), authored on AbilityDefinition + baked in AbilityDatabaseAuthoring, read at dispatch in AbilityFireSystem -- NOT folded through EffectiveAbilityStats/StatRecomputeSystem (static identity, not a tunable stat; MC-4 review BURST-1). All current abilities are Projectile (0) -> zero behaviour change; the dispatch read-point is the de-risk spike for MC-6 hitscan/cone/aoe.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 17:45:33 -07:00

94 lines
4.3 KiB
C#

using System.Collections.Generic;
using ProjectM.Simulation;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
namespace ProjectM.Authoring
{
/// <summary>
/// Bakes the designer-authored ability + character definitions into a single AbilityDatabase blob
/// singleton (immutable, shared, Burst-fast) plus a companion AbilityPrefabElement buffer holding the
/// per-ability projectile ghost prefab entity refs (entity refs cannot live in a blob). Place ONE of
/// these in the gameplay subscene; it streams identically into the client and server worlds (config,
/// not replicated). DependsOn each definition so a value change in an SO re-bakes the blob.
/// </summary>
public class AbilityDatabaseAuthoring : MonoBehaviour
{
[Tooltip("All ability definitions available in the game. Indexed at runtime by AbilityId.")]
public List<AbilityDefinition> Abilities = new List<AbilityDefinition>();
[Tooltip("All character-stats definitions. Indexed at runtime by CharacterId.")]
public List<CharacterStatsDefinition> Characters = new List<CharacterStatsDefinition>();
private class DatabaseBaker : Baker<AbilityDatabaseAuthoring>
{
public override void Bake(AbilityDatabaseAuthoring authoring)
{
var entity = GetEntity(TransformUsageFlags.None);
int abilityCount = authoring.Abilities != null ? authoring.Abilities.Count : 0;
int charCount = authoring.Characters != null ? authoring.Characters.Count : 0;
var builder = new BlobBuilder(Allocator.Temp);
ref var root = ref builder.ConstructRoot<AbilityDatabaseBlob>();
var abilityArray = builder.Allocate(ref root.Abilities, abilityCount);
for (int i = 0; i < abilityCount; i++)
{
var def = authoring.Abilities[i];
if (def == null) { abilityArray[i] = default; continue; }
DependsOn(def);
abilityArray[i] = new AbilityDefBlob
{
Id = (byte)def.Id,
Damage = def.Damage,
ProjectileSpeed = def.ProjectileSpeed,
Range = def.Range,
AutoTargetRange = def.AutoTargetRange,
Archetype = (byte)def.Archetype,
AutoTargetConeRadians = math.radians(def.AutoTargetConeDegrees),
CooldownTicks = def.CooldownTicks,
Name = def.DisplayName,
};
}
var charArray = builder.Allocate(ref root.Characters, charCount);
for (int i = 0; i < charCount; i++)
{
var def = authoring.Characters[i];
if (def == null) { charArray[i] = default; continue; }
DependsOn(def);
charArray[i] = new CharacterStatsBlob
{
Id = (byte)def.Id,
MoveSpeed = def.MoveSpeed,
TurnRateRadiansPerSec = math.radians(def.TurnRateDegreesPerSec),
MaxHealth = def.MaxHealth,
Name = def.DisplayName,
};
}
var blob = builder.CreateBlobAssetReference<AbilityDatabaseBlob>(Allocator.Persistent);
builder.Dispose();
AddBlobAsset(ref blob, out _);
AddComponent(entity, new AbilityDatabase { Value = blob });
// Companion entity-ref buffer: per-ability projectile ghost prefab (resolved via GetEntity).
var prefabBuffer = AddBuffer<AbilityPrefabElement>(entity);
for (int i = 0; i < abilityCount; i++)
{
var def = authoring.Abilities[i];
if (def == null || def.ProjectilePrefab == null) continue;
prefabBuffer.Add(new AbilityPrefabElement
{
Id = (byte)def.Id,
Prefab = GetEntity(def.ProjectilePrefab, TransformUsageFlags.Dynamic),
});
}
}
}
}
}