This commit is contained in:
2026-07-04 16:57:40 -07:00
parent 16e396841e
commit c6b7890212
47 changed files with 1713 additions and 209 deletions
@@ -62,6 +62,8 @@ namespace ProjectM.Client
bool _slashActive;
float _slashRange, _slashHalf; // live cone geometry re-sampled each frame for the per-frame sweep rebuild
int _slashSweepSign = 1; // alternate sweep direction per combo step (reads as alternating strikes)
uint _lastConeFireTick; // own latch — the muzzle block owns _lastLocalFireTick and runs first
bool _coneTickInit;
Material _dangerMat;
readonly Dictionary<Entity, GameObject> _dangerZones = new();
readonly HashSet<Entity> _dangerSeen = new();
@@ -367,6 +369,11 @@ namespace ProjectM.Client
bool finisher = step >= comboLen;
float slashRange = tcfg.MeleeRange > 0f ? tcfg.MeleeRange : 2.6f;
float slashHalf = tcfg.MeleeConeHalfAngleRad > 0f ? tcfg.MeleeConeHalfAngleRad : 0.9f;
// Slice-2 deferred reach fix: the SERVER folds per-player StatModifiers into melee range
// (class seed +0.8, boons, meta tiers) — the arc must sweep the REAL reach, not the base.
if (EntityManager.HasBuffer<StatModifier>(_localPlayer))
slashRange = math.max(0f, StatMath.Apply(slashRange, StatTarget.MeleeRange,
EntityManager.GetBuffer<StatModifier>(_localPlayer, true)));
if (finisher) slashRange *= tcfg.MeleeFinisherMult > 0f ? tcfg.MeleeFinisherMult : 1.8f;
// MC-4 connect-vs-whiff: client-side cone overlap over the cached enemy snapshot gives an IMMEDIATE
// "you bit" read (the authoritative server damage spark/number arrives a few ticks later).
@@ -397,6 +404,40 @@ namespace ProjectM.Client
_swingTickInit = true;
}
// Slice-2 deferred: the Warrior CONE ability had no client VFX (server-only cleave). Edge-detect the
// PREDICTED AbilityCooldown (raw uint, like the dash) and reuse the slash-arc sweep with the ability's
// own effective geometry — an aimed cone IS an arc.
if (_localPlayer != Entity.Null
&& EntityManager.HasComponent<AbilityCooldown>(_localPlayer)
&& EntityManager.HasComponent<AbilityRef>(_localPlayer)
&& EntityManager.HasComponent<EffectiveAbilityStats>(_localPlayer))
{
uint nextFire = EntityManager.GetComponentData<AbilityCooldown>(_localPlayer).NextFireTick;
if (_coneTickInit && nextFire != 0 && nextFire != _lastConeFireTick
&& SystemAPI.TryGetSingleton<AbilityDatabase>(out var coneDb) && coneDb.Value.IsCreated)
{
ref var coneAdb = ref coneDb.Value.Value;
if (coneAdb.TryGetAbility(EntityManager.GetComponentData<AbilityRef>(_localPlayer).Id, out var coneDef)
&& coneDef.Archetype == (byte)AbilityArchetype.Cone)
{
var ceff = EntityManager.GetComponentData<EffectiveAbilityStats>(_localPlayer);
Vector3 cface = Vector3.forward;
if (EntityManager.HasComponent<PlayerFacing>(_localPlayer))
{
var cfd = EntityManager.GetComponentData<PlayerFacing>(_localPlayer).Direction;
if (math.lengthsq(cfd) > 1e-6f) cface = new Vector3(cfd.x, 0f, cfd.y).normalized;
}
float coneRange = Mathf.Max(0.1f, ceff.Range);
float coneHalf = Mathf.Clamp(ceff.AutoTargetConeRadians, 0.01f, 3.14159f);
TriggerSlash((Vector3)localPos, new float2(cface.x, cface.z), coneRange, coneHalf, 1, 1, false);
PlayClip(_swingClip, (Vector3)localPos, 0.5f);
PrototypeCameraRig.AddShake(0.06f);
}
}
_lastConeFireTick = nextFire;
_coneTickInit = true;
}
// Footsteps (combat feel): edge-detect local locomotion from the position delta; a soft step at a cadence.
if (_localPlayer != Entity.Null)
{
@@ -820,7 +861,11 @@ namespace ProjectM.Client
{
int step = math.max(1, (int)mc.ValueRO.Step);
bool finisher = step >= comboLen;
rs.Range = finisher ? baseRange * finisherMult : baseRange;
float rRange = baseRange; // same reach fix for teammates' arcs (their buffer is OwnerSendType.All)
if (EntityManager.HasBuffer<StatModifier>(entity))
rRange = math.max(0f, StatMath.Apply(baseRange, StatTarget.MeleeRange,
EntityManager.GetBuffer<StatModifier>(entity, true)));
rs.Range = finisher ? rRange * finisherMult : rRange;
rs.Half = baseHalf;
rs.SweepSign = (step % 2 == 0) ? -1 : 1;
rs.Tint = FeelConfig.RemoteSlashColor * (finisher ? 1.5f : 1f);