Boss becomes a real fight + readable-but-fair enemy threat
BossState (server-only) + BossAISystem: sole mover, seek -> telegraphed radial slam (AttackWindup) -> phase-2 speed + swarmer summon. Excluded from the EnemyAISystem Charger pass; knockback-immune at all 3 stamp sites; arena-anchored spawn; scaled Health/HitRadius/AttackRange. Grunt windup now commits in its last ~30% (whiffs out-of-range); Charger stagger roots; grunt speed 4.2->5.2; swarmer telegraph synced. SweptMove + zone-enemy spawn-stamp extracted to shared helpers. Zero new [GhostField]. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -145,7 +145,7 @@ namespace ProjectM.Server
|
||||
baseCenter = BaseGridMath.PlotCenter(anchor);
|
||||
float3 origin = RegionMath.ExpeditionRoomOrigin(baseCenter, run.ActiveSubSlot);
|
||||
float3 center = bossRoom
|
||||
? origin // the boss anchors the room center
|
||||
? origin + new float3(0f, 0f, 12f) // the boss anchors the room center
|
||||
: EnemyAIMath.RingPosition(origin, slot, math.max(1, dir.RingSlots), dir.RingRadius);
|
||||
center.y = origin.y;
|
||||
|
||||
@@ -160,20 +160,35 @@ namespace ProjectM.Server
|
||||
float3 pos = packSize > 1
|
||||
? EnemyAIMath.ClusterOffset(center, k, packSize, dir.ClusterTightRadius) : center;
|
||||
pos.y = origin.y;
|
||||
var enemy = ecb.Instantiate(prefab);
|
||||
var xform = baked.WithPosition(pos); // preserve the baked [GhostField] Scale
|
||||
var enemy = ZoneEnemySpawnUtil.Spawn(ecb, prefab, in baked, pos, RegionId.Expedition, room);
|
||||
if (bossRoom)
|
||||
xform.Scale = baked.Scale * Tuning.BossScaleMultiplier;
|
||||
ecb.SetComponent(enemy, xform);
|
||||
ecb.AddComponent(enemy, new RegionTag { Region = RegionId.Expedition });
|
||||
ecb.AddComponent<ZoneEnemyTag>(enemy);
|
||||
ecb.AddComponent(enemy, new RoomTag { Room = room });
|
||||
if (bossRoom && SystemAPI.HasComponent<Health>(prefab))
|
||||
{
|
||||
var hp = SystemAPI.GetComponent<Health>(prefab);
|
||||
hp.Current *= Tuning.BossHealthMultiplier;
|
||||
hp.Max *= Tuning.BossHealthMultiplier;
|
||||
ecb.SetComponent(enemy, hp);
|
||||
// Boss = a scaled Charger given a real kit by BossAISystem. Scale the visual AND the
|
||||
// hitbox/reach (so hits register on the big model + its reach matches), multiply Health,
|
||||
// and tag BossState (server-only discriminator) so BossAISystem alone drives it.
|
||||
var bxform = baked.WithPosition(pos);
|
||||
bxform.Scale = baked.Scale * Tuning.BossScaleMultiplier;
|
||||
ecb.SetComponent(enemy, bxform);
|
||||
if (SystemAPI.HasComponent<Health>(prefab))
|
||||
{
|
||||
var hp = SystemAPI.GetComponent<Health>(prefab);
|
||||
hp.Current *= Tuning.BossHealthMultiplier;
|
||||
hp.Max *= Tuning.BossHealthMultiplier;
|
||||
ecb.SetComponent(enemy, hp);
|
||||
}
|
||||
if (SystemAPI.HasComponent<HitRadius>(prefab))
|
||||
{
|
||||
var hr = SystemAPI.GetComponent<HitRadius>(prefab);
|
||||
hr.Value *= Tuning.BossScaleMultiplier;
|
||||
ecb.SetComponent(enemy, hr);
|
||||
}
|
||||
if (SystemAPI.HasComponent<EnemyStats>(prefab))
|
||||
{
|
||||
var es = SystemAPI.GetComponent<EnemyStats>(prefab);
|
||||
es.AttackRange *= Tuning.BossScaleMultiplier;
|
||||
ecb.SetComponent(enemy, es);
|
||||
}
|
||||
ecb.AddComponent(enemy, new BossState { Phase = 1 });
|
||||
}
|
||||
}
|
||||
ecb.Playback(state.EntityManager);
|
||||
|
||||
Reference in New Issue
Block a user