using ProjectM.Simulation; using Unity.Entities; using UnityEngine; namespace ProjectM.Authoring { /// /// Authoring for the player ghost prefab. As of M3 the numeric tunables live in data /// ( / ScriptableObjects); /// this authoring only selects which definitions the player uses and bakes the light id refs, the /// (empty) replicated modifier buffer, and the zeroed effective-stat components that /// StatRecomputeSystem fills each predicted tick. Health is seeded from the character definition's /// MaxHealth (single source). Ghost replication, GhostOwner and AutoCommandTarget come from /// the GhostAuthoringComponent on the same prefab GameObject; GetEntity(TransformUsageFlags.Dynamic) /// ensures a runtime-mutable LocalTransform exists. /// public class PlayerAuthoring : MonoBehaviour { [Tooltip("Character-stats definition (move speed, turn rate, max health). Single source of those values.")] public CharacterStatsDefinition Character; [Tooltip("Ability definition occupying the player's primary slot.")] public AbilityDefinition PrimaryAbility; [Header("Fallbacks (used only if a definition above is unassigned)")] [Min(0f)] public float FallbackMaxHealth = 100f; /// Projectile hit-test radius for the player as a damageable target, in world units. [Min(0f)] public float HitRadius = 0.6f; [Min(1)] [Tooltip("Ticks the player stays down before respawning at base (~60 ticks/sec).")] public int RespawnDelayTicks = 180; [Min(0)] [Tooltip("Ticks of post-respawn damage immunity (~60 ticks/sec).")] public int RespawnInvulnTicks = 120; private class PlayerBaker : Baker { public override void Bake(PlayerAuthoring authoring) { var entity = GetEntity(authoring, TransformUsageFlags.Dynamic); // Re-bake when a referenced definition's serialized values change. if (authoring.Character != null) DependsOn(authoring.Character); if (authoring.PrimaryAbility != null) DependsOn(authoring.PrimaryAbility); byte characterId = authoring.Character != null ? (byte)authoring.Character.Id : (byte)CharacterId.Default; byte abilityId = authoring.PrimaryAbility != null ? (byte)authoring.PrimaryAbility.Id : (byte)AbilityId.Primary; float maxHealth = authoring.Character != null ? authoring.Character.MaxHealth : authoring.FallbackMaxHealth; AddComponent(entity); AddComponent(entity); AddComponent(entity); // Data-driven stat refs (replace M2's inlined PlayerMoveStats / AbilityStats values). AddComponent(entity, new CharacterStatsRef { Id = characterId }); AddComponent(entity, new AbilityRef { Id = abilityId }); // Unarmed/base ability restored on weapon-unequip (AbilityRef.Id mutates when a weapon is equipped). AddComponent(entity, new DefaultAbility { Id = abilityId }); // Effective stats: zeroed at bake, recomputed every predicted tick by StatRecomputeSystem. AddComponent(entity, new EffectiveAbilityStats()); AddComponent(entity, new EffectiveCharacterStats()); // Empty replicated modifier stack (grown by upgrades/pickups/debug hook, server-authoritative). AddBuffer(entity); // Empty replicated personal inventory (server-authoritative; harvest yield + deposit RPC land here). AddBuffer(entity); // Equipment loadout: one replicated row per slot in FIXED order (buffer index = EquipSlotId), empty. var equip = AddBuffer(entity); for (int s = 0; s < EquipSlotId.Count; s++) equip.Add(new EquipmentSlot { ItemId = 0 }); // Server-only expiry tracker for timed buffs (paired with a StatModifier by SourceId; not replicated). AddBuffer(entity); // Combat: server-authoritative health (Current replicated for display), the player's // damageable hit radius, predicted cooldown state, and the per-tick damage inbox. AddComponent(entity, new Health { Current = maxHealth, Max = maxHealth }); AddComponent(entity, new HitRadius { Value = authoring.HitRadius }); AddComponent(entity); AddBuffer(entity); // MC-1 dash: predicted dash window (derived from PlayerInput.Dash) + cooldown gate, baked idle/ready. AddComponent(entity); AddComponent(entity, new DashCooldown { NextTick = 0 }); // MC-4 melee combo: predicted, owner-replicated combo anchor (Step/SwingStartTick/LockUntilTick), baked idle/zero. AddComponent(entity); // Death gate (enableable, derived from Health by PlayerDeathStateSystem) baked DISABLED = alive; // plus the server-only respawn timer. AddComponent(entity); SetComponentEnabled(entity, false); // Dev god-mode gate (enableable, server-only) baked DISABLED so toggling it is a bit flip, never structural. AddComponent(entity); SetComponentEnabled(entity, false); AddComponent(entity, new RespawnState { RespawnTick = 0, DelayTicks = authoring.RespawnDelayTicks, InvulnTicks = authoring.RespawnInvulnTicks }); AddComponent(entity, new RespawnInvuln { UntilTick = 0 }); } } } }