Further Tests & Progress
This commit is contained in:
@@ -59,14 +59,32 @@ namespace ProjectM.Server
|
||||
|
||||
float dt = SystemAPI.Time.DeltaTime;
|
||||
var serverTick = SystemAPI.GetSingleton<NetworkTime>().ServerTick;
|
||||
uint now = serverTick.TickIndexForValidTick;
|
||||
var ecb = new EntityCommandBuffer(Allocator.Temp);
|
||||
|
||||
foreach (var (xform, stats, cooldown) in
|
||||
SystemAPI.Query<RefRW<LocalTransform>, RefRO<EnemyStats>, RefRW<EnemyAttackCooldown>>()
|
||||
foreach (var (xform, stats, cooldown, knockback, windup) in
|
||||
SystemAPI.Query<RefRW<LocalTransform>, RefRO<EnemyStats>, RefRW<EnemyAttackCooldown>,
|
||||
RefRW<KnockbackState>, RefRW<AttackWindup>>()
|
||||
.WithAll<EnemyTag>())
|
||||
{
|
||||
float3 pos = xform.ValueRO.Position;
|
||||
|
||||
// Knockback overrides seek/strike for its window — EnemyAISystem stays the SOLE writer of Position.
|
||||
var kb = knockback.ValueRO;
|
||||
if (kb.UntilTick != 0)
|
||||
{
|
||||
var kbTick = new NetworkTick(kb.UntilTick);
|
||||
if (kbTick.IsValid && kbTick.IsNewerThan(serverTick))
|
||||
{
|
||||
float3 kpos = pos + new float3(kb.Dir.x, 0f, kb.Dir.y) * (kb.Speed * dt);
|
||||
kpos.y = pos.y;
|
||||
xform.ValueRW.Position = kpos;
|
||||
windup.ValueRW.WindUpUntilTick = 0; // a recoiling Husk does not wind up
|
||||
continue; // recoiling: skip seek + strike this tick
|
||||
}
|
||||
knockback.ValueRW.UntilTick = 0; // window elapsed
|
||||
}
|
||||
|
||||
// Nearest living player (planar XZ).
|
||||
int best = -1;
|
||||
float bestSq = float.MaxValue;
|
||||
@@ -96,8 +114,35 @@ namespace ProjectM.Server
|
||||
if (math.lengthsq(toTarget) > 1e-6f)
|
||||
xform.ValueRW.Rotation = quaternion.LookRotationSafe(math.normalize(toTarget), math.up());
|
||||
|
||||
// Strike on contact once the cooldown has elapsed.
|
||||
if (EnemyAIMath.InAttackRange(pos, targetPos, stats.ValueRO.AttackRange))
|
||||
// Two-phase strike with a telegraph wind-up: commit a wind-up when first in-range + cooldown-ready,
|
||||
// then strike when it elapses. WindUpUntilTick is a [GhostField] so the client can cue the ~0.3s
|
||||
// tell; leaving range mid-windup cancels it. Tuning.AttackWindupTicks = 0/1 -> near-instant (legacy).
|
||||
bool inRange = EnemyAIMath.InAttackRange(pos, targetPos, stats.ValueRO.AttackRange);
|
||||
uint windRaw = windup.ValueRO.WindUpUntilTick;
|
||||
|
||||
if (windRaw != 0)
|
||||
{
|
||||
if (!inRange)
|
||||
{
|
||||
windup.ValueRW.WindUpUntilTick = 0; // target left range -> cancel the wind-up
|
||||
}
|
||||
else
|
||||
{
|
||||
var windTick = new NetworkTick(windRaw);
|
||||
if (!(windTick.IsValid && windTick.IsNewerThan(serverTick)))
|
||||
{
|
||||
ecb.AppendToBuffer(playerEntities[best], new DamageEvent
|
||||
{
|
||||
Amount = stats.ValueRO.AttackDamage,
|
||||
SourceNetworkId = -1, // environment / Husk, not a player
|
||||
});
|
||||
uint cooldownTicks = (uint)math.max(1, stats.ValueRO.AttackCooldownTicks);
|
||||
cooldown.ValueRW.NextAttackTick = TickUtil.NonZero(now + cooldownTicks);
|
||||
windup.ValueRW.WindUpUntilTick = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (inRange)
|
||||
{
|
||||
uint nextRaw = cooldown.ValueRO.NextAttackTick;
|
||||
bool ready = true;
|
||||
@@ -107,16 +152,10 @@ namespace ProjectM.Server
|
||||
if (nextTick.IsValid && nextTick.IsNewerThan(serverTick))
|
||||
ready = false;
|
||||
}
|
||||
|
||||
if (ready)
|
||||
{
|
||||
ecb.AppendToBuffer(playerEntities[best], new DamageEvent
|
||||
{
|
||||
Amount = stats.ValueRO.AttackDamage,
|
||||
SourceNetworkId = -1, // environment / Husk, not a player
|
||||
});
|
||||
uint cooldownTicks = (uint)math.max(1, stats.ValueRO.AttackCooldownTicks);
|
||||
cooldown.ValueRW.NextAttackTick = TickUtil.NonZero(serverTick.TickIndexForValidTick + cooldownTicks);
|
||||
uint windupTicks = (uint)math.max(1, Tuning.AttackWindupTicks);
|
||||
windup.ValueRW.WindUpUntilTick = TickUtil.NonZero(now + windupTicks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user