Further Tests & Progress
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
using Unity.Entities;
|
||||
using Unity.NetCode;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Replicated Husk attack-telegraph signal. While <see cref="WindUpUntilTick"/> is non-zero the Husk is
|
||||
/// "winding up" to strike; EnemyAISystem sets it ~<see cref="Tuning.AttackWindupTicks"/> before the strike
|
||||
/// lands, and the strike fires when the tick elapses. This is a [GhostField] (the only replicated Husk field
|
||||
/// beyond the stock LocalTransform) so the CLIENT can play a ~0.3s pre-strike cue — the client has none of the
|
||||
/// server-only timing inputs (EnemyStats / EnemyAttackCooldown), so the wind-up MUST be replicated. A uint tick
|
||||
/// (not a [GhostEnabledBit]) so the cue can ramp/countdown and survive a missed snapshot (absolute, not an edge).
|
||||
/// </summary>
|
||||
public struct AttackWindup : IComponentData
|
||||
{
|
||||
/// <summary>Server tick the wind-up completes + the strike lands (0 = not winding up; scheduled via TickUtil.NonZero).</summary>
|
||||
[GhostField] public uint WindUpUntilTick;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f2c9d899714758b4baefe6c1cbb3be0a
|
||||
@@ -0,0 +1,26 @@
|
||||
using Unity.Entities;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// SERVER-ONLY transient knockback on a Husk. While <see cref="UntilTick"/> has not elapsed, EnemyAISystem
|
||||
/// moves the Husk along <see cref="Dir"/> at <see cref="Speed"/> (REPLACING its seek) and suppresses its strike.
|
||||
/// Stamped by ProjectileDamageSystem on a hit (Dir = the projectile's heading). NOT a [GhostField] — the Husk's
|
||||
/// displaced position already replicates via the stock LocalTransform default variant, so knockback adds NO
|
||||
/// replicated surface (no ghost re-bake). EnemyAISystem must remain the SOLE writer of the Husk's Position, so
|
||||
/// knockback is applied INSIDE it (never a competing system). Force/duration live in <see cref="Tuning"/>
|
||||
/// (KnockbackSpeed = 0 disables knockback globally).
|
||||
/// </summary>
|
||||
public struct KnockbackState : IComponentData
|
||||
{
|
||||
/// <summary>Planar (XZ) knockback heading — the projectile's direction at impact.</summary>
|
||||
public float2 Dir;
|
||||
|
||||
/// <summary>Knockback speed (world units/sec) applied for the window; 0 = not knocked.</summary>
|
||||
public float Speed;
|
||||
|
||||
/// <summary>Server tick until which the knockback is active (0 = none; scheduled via TickUtil.NonZero).</summary>
|
||||
public uint UntilTick;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9fa07e28f83ad6b43a8164b8c673a6b1
|
||||
@@ -0,0 +1,35 @@
|
||||
using Unity.Entities;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// SERVER-ONLY expiry tracker paired with a <see cref="StatModifier"/> by <see cref="SourceId"/>. It is NOT a
|
||||
/// [GhostField] and lives in a SEPARATE buffer so the replicated <see cref="StatModifier"/> layout stays
|
||||
/// byte-identical — adding ANY member (even non-ghost) to a [GhostField] buffer element regenerates its
|
||||
/// serializer/stride/hash = an effective ghost re-bake. To grant a TIMED buff, append both a StatModifier and a
|
||||
/// TimedModifier sharing one unique SourceId; <c>TimedModifierExpirySystem</c> removes the matching StatModifier
|
||||
/// when <see cref="UntilTick"/> elapses, and that removal replicates for free via the StatModifier [GhostField]
|
||||
/// buffer (OwnerSendType.All), so StatRecomputeSystem reverts the effective stat on both worlds with no change.
|
||||
/// </summary>
|
||||
public struct TimedModifier : IBufferElementData
|
||||
{
|
||||
/// <summary>Matches the <see cref="StatModifier.SourceId"/> this row governs.</summary>
|
||||
public uint SourceId;
|
||||
|
||||
/// <summary>Server tick at which the paired StatModifier expires (0 = no expiry / inert; schedule via TickUtil.NonZero).</summary>
|
||||
public uint UntilTick;
|
||||
}
|
||||
|
||||
/// <summary>Pure helpers for removing modifiers by provenance (clear-by-type / timed expiry). Deterministic, no RNG/wall-clock.</summary>
|
||||
public static class TimedModifierUtil
|
||||
{
|
||||
/// <summary>Remove every <see cref="StatModifier"/> row whose SourceId matches (RemoveAtSwapBack). Returns the count removed.</summary>
|
||||
public static int RemoveBySourceId(DynamicBuffer<StatModifier> mods, uint sourceId)
|
||||
{
|
||||
int removed = 0;
|
||||
for (int j = mods.Length - 1; j >= 0; j--)
|
||||
if (mods[j].SourceId == sourceId) { mods.RemoveAtSwapBack(j); removed++; }
|
||||
return removed;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 67465323b013e6a4cb59519111b1b9e5
|
||||
@@ -0,0 +1,54 @@
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Central home for gameplay-balance constants that were previously buried as <c>private const</c>s
|
||||
/// inside individual systems, so designers have one searchable place to tune them. Burst-safe (compile-time
|
||||
/// <c>const</c>s only — they inline into the consuming systems with no runtime cost or managed reference).
|
||||
/// <para>
|
||||
/// Systems reference these via <c>Tuning.*</c> (wired in the 2026-06-04 polish pass, Stage C). When adding a
|
||||
/// new tunable value, prefer adding it here over a local private const UNLESS it already has an obvious,
|
||||
/// well-named public home (see the cross-references below) — duplicating a literal creates two sources of truth.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Values that already live in a clear, public, semantically-named home (NOT duplicated here):</b>
|
||||
/// <list type="bullet">
|
||||
/// <item><see cref="CyclePhase.ExpeditionTicks"/> / <see cref="CyclePhase.BuildTicks"/> — cycle phase durations.</item>
|
||||
/// <item><see cref="RegionMath.ExpeditionOffsetX"/> — base→expedition world-space offset.</item>
|
||||
/// <item>Per-ability/character stats — authored in ScriptableObjects, baked to the AbilityDatabase blob (M3).</item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static class Tuning
|
||||
{
|
||||
// ---- Ability damage upgrade (AbilityUpgradeSystem) ----
|
||||
|
||||
/// <summary>Distinct sentinel SourceId so the upgrade <c>StatModifier</c> is found + grown in place
|
||||
/// (replace-by-SourceId keeps the bounded modifier buffer from growing a row per upgrade).</summary>
|
||||
public const uint AbilityUpgradeSourceId = 0x00A0E711u;
|
||||
|
||||
/// <summary>Damage bonus added per upgrade tier (PercentAdd op): +25% per tier.</summary>
|
||||
public const float AbilityUpgradeTierStep = 0.25f;
|
||||
|
||||
/// <summary>Aether cost charged to the shared ledger per upgrade tier.</summary>
|
||||
public const int AbilityUpgradeCostAmount = 20;
|
||||
|
||||
// ---- Resource harvest (ResourceHarvestSystem) ----
|
||||
|
||||
/// <summary>Effective projectile radius used by the swept-segment node-hit test (added to the node's
|
||||
/// <c>HitRadius</c>). Tunnel-safe because the segment is reconstructed from <c>Projectile.LastStep</c>.</summary>
|
||||
public const float HarvestProjectileRadius = 0.2f;
|
||||
|
||||
// ---- Enemy knockback (ProjectileDamageSystem stamps on hit; EnemyAISystem applies + suppresses seek/strike) ----
|
||||
|
||||
/// <summary>Knockback speed (world units/sec) a Husk recoils at when shot; 0 disables knockback globally.</summary>
|
||||
public const float KnockbackSpeed = 8f;
|
||||
|
||||
/// <summary>Server ticks the knockback lasts (~60 ticks/sec).</summary>
|
||||
public const int KnockbackDurationTicks = 8;
|
||||
|
||||
// ---- Husk attack telegraph (EnemyAISystem 2-phase strike; client cue in CombatFeedbackSystem) ----
|
||||
|
||||
/// <summary>Wind-up ticks before a Husk strike lands (~0.3s @ 60 ticks/sec). 0/1 = near-instant (legacy behaviour).</summary>
|
||||
public const int AttackWindupTicks = 18;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a78ec19017f42bd4a8a16bfa8a03886d
|
||||
@@ -21,6 +21,9 @@ namespace ProjectM.Simulation
|
||||
|
||||
/// <summary>Server tick the current timed phase ends (Expedition/Build only; 0 in Defend).</summary>
|
||||
[GhostField] public uint PhaseEndTick;
|
||||
|
||||
/// <summary>Live Husk wave number during Defend, synced from the server-only WaveState by CyclePhaseSystem so the replicated-state-only HUD can show it (holds the last wave number outside Defend; the HUD gates the display to the Defend phase).</summary>
|
||||
[GhostField] public int WaveNumber;
|
||||
}
|
||||
|
||||
/// <summary>Phase constants for <see cref="CycleState.Phase"/> (a byte, not an enum, for trivial Burst/serialization).</summary>
|
||||
|
||||
Reference in New Issue
Block a user