From 5e33f16cdd55c10889cad67952fdbf8e76abcb38 Mon Sep 17 00:00:00 2001 From: ze-dom Date: Tue, 21 Jul 2026 17:44:16 +0100 Subject: [PATCH] Fixed frustum dynamic projectile index (cherry picked from commit 2b30d22c1f49ac3e8ec42815a48cef6fa646e119) --- .../Skills/AreaSkillAttackAction.cs | 16 ++++++++++++---- .../Skills/FrustumBasedTargetFilter.cs | 14 ++++++++------ .../VersionSeasonSix/SkillsInitializer.cs | 2 +- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/GameLogic/PlayerActions/Skills/AreaSkillAttackAction.cs b/src/GameLogic/PlayerActions/Skills/AreaSkillAttackAction.cs index f5bbe74..877d99f 100644 --- a/src/GameLogic/PlayerActions/Skills/AreaSkillAttackAction.cs +++ b/src/GameLogic/PlayerActions/Skills/AreaSkillAttackAction.cs @@ -134,6 +134,7 @@ public class AreaSkillAttackAction .GetAttackablesInRange(targetAreaCenter, range) .Where(a => a != player) .Where(a => !a.IsAtSafezone()) + .Where(a => a.IsActive()) ?? []; if (skill.AreaSkillSettings is { UseFrustumFilter: true } areaSkillSettings) @@ -239,6 +240,7 @@ public class AreaSkillAttackAction var orderedTargets = targets.ToList(); FrustumBasedTargetFilter? filter = null; + var extraProjectiles = 0; var projectileCount = 1; var attackRounds = areaSkillSettings.MaximumNumberOfHitsPerTarget; @@ -246,11 +248,12 @@ public class AreaSkillAttackAction { // Order targets by distance to process nearest targets first orderedTargets.Sort((a, b) => player.GetDistanceTo(a).CompareTo(player.GetDistanceTo(b))); - projectileCount = areaSkillSettings.ProjectileCount + (int)(player.Attributes?[Stats.ExtraProjectiles] ?? 0); + filter = FrustumFilters.GetOrAdd(areaSkillSettings, static s => new FrustumBasedTargetFilter(s.FrustumStartWidth, s.FrustumEndWidth, s.FrustumDistance, s.ProjectileCount)); + extraProjectiles += (int)player.Attributes![Stats.ExtraProjectiles]; + projectileCount = areaSkillSettings.ProjectileCount + extraProjectiles; attackRounds = 1; // One attack round per projectile - filter = FrustumFilters.GetOrAdd(areaSkillSettings, s => new FrustumBasedTargetFilter(s.FrustumStartWidth, s.FrustumEndWidth, s.FrustumDistance, projectileCount)); - minAttacks = projectileCount; maxAttacks = projectileCount; + minAttacks = projectileCount; extraTarget = orderedTargets.FirstOrDefault(t => t.Id == extraTargetId); if (extraTarget is not null) @@ -296,7 +299,7 @@ public class AreaSkillAttackAction } // For multiple projectiles, check if this specific projectile can hit the target - if (filter != null && !filter.IsTargetWithinBounds(player, target, rotation, projectileIndex)) + if (filter != null && !filter.IsTargetWithinBounds(player, target, rotation, projectileIndex, extraProjectiles)) { continue; // This projectile cannot hit this target } @@ -345,6 +348,11 @@ public class AreaSkillAttackAction } }); } + + if (filter != null) + { + break; // This projectile has hit, so we move on to the next projectile + } } currentDelay += areaSkillSettings.DelayBetweenHits; diff --git a/src/GameLogic/PlayerActions/Skills/FrustumBasedTargetFilter.cs b/src/GameLogic/PlayerActions/Skills/FrustumBasedTargetFilter.cs index 614b5a8..4a76052 100644 --- a/src/GameLogic/PlayerActions/Skills/FrustumBasedTargetFilter.cs +++ b/src/GameLogic/PlayerActions/Skills/FrustumBasedTargetFilter.cs @@ -78,8 +78,9 @@ public record FrustumBasedTargetFilter /// The target. /// The rotation. /// The zero-based index of the projectile (0 to ProjectileCount-1). + /// The number of extra projectiles, if any. /// true if the target is within hit bounds for the specified projectile; otherwise, false. - public bool IsTargetWithinBounds(ILocateable attacker, ILocateable target, byte rotation, int projectileIndex) + public bool IsTargetWithinBounds(ILocateable attacker, ILocateable target, byte rotation, int projectileIndex, int extraProjectiles = 0) { if (this.ProjectileCount <= 1) { @@ -87,7 +88,8 @@ public record FrustumBasedTargetFilter return this.IsTargetWithinBounds(attacker, target, rotation); } - if (projectileIndex < 0 || projectileIndex >= this.ProjectileCount) + var totalProjectiles = this.ProjectileCount + extraProjectiles; + if (projectileIndex < 0 || projectileIndex >= totalProjectiles) { return false; } @@ -103,19 +105,19 @@ public record FrustumBasedTargetFilter // Divide the frustum into sections (-1 to 1 range) // For 3 projectiles: left (-1 to -0.33), center (-0.33 to 0.33), right (0.33 to 1) - var sectionWidth = 2.0 / this.ProjectileCount; + var sectionWidth = 2.0 / totalProjectiles; var sectionStart = -1.0 + (projectileIndex * sectionWidth); var sectionEnd = sectionStart + sectionWidth; // Add overlap so targets near boundaries can be hit by adjacent projectiles - var overlap = this.GetOverlap(attacker.Position, target.Position); + var overlap = this.GetOverlap(attacker.Position, target.Position, extraProjectiles); sectionStart -= overlap; sectionEnd += overlap; return relativePosition >= sectionStart && relativePosition <= sectionEnd; } - private double GetOverlap(Point attackerPos, Point targetPos) + private double GetOverlap(Point attackerPos, Point targetPos, int extraProjectiles = 0) { var distance = attackerPos.EuclideanDistanceTo(targetPos); if (distance == 0) @@ -124,7 +126,7 @@ public record FrustumBasedTargetFilter } // The overlap decreases over higher distance - var overlap = (1.0 / Math.Floor(distance)) / this.ProjectileCount; + var overlap = (1.0 / Math.Floor(distance)) / (this.ProjectileCount + extraProjectiles); overlap += 0.001; // Adding a small epsilon to make it slightly more tolerant in the comparisons return overlap; } diff --git a/src/Persistence/Initialization/VersionSeasonSix/SkillsInitializer.cs b/src/Persistence/Initialization/VersionSeasonSix/SkillsInitializer.cs index e8d1bb4..716e79e 100644 --- a/src/Persistence/Initialization/VersionSeasonSix/SkillsInitializer.cs +++ b/src/Persistence/Initialization/VersionSeasonSix/SkillsInitializer.cs @@ -828,7 +828,7 @@ internal class SkillsInitializer : SkillsInitializerBase this.AddMasterSkillDefinition(SkillNumber.PenetrationStrengthener, SkillNumber.Penetration, SkillNumber.Undefined, 2, 3, SkillNumber.Penetration, 20, Formula502); this.AddMasterSkillDefinition(SkillNumber.DefenseIncreaseStr, SkillNumber.GreaterDefense, SkillNumber.Undefined, 2, 3, SkillNumber.GreaterDefense, 20, $"{Formula502} / 100", Formula502, Stats.GreaterDefenseBonus, AggregateType.Multiplicate); this.AddMasterSkillDefinition(SkillNumber.TripleShotMastery, SkillNumber.TripleShotStrengthener, SkillNumber.Undefined, 2, 3, SkillNumber.TripleShotStrengthener, 10, Formula1WhenComplete, Formula1WhenComplete, Stats.ExtraProjectiles, AggregateType.AddRaw); - this.AddPassiveMasterSkillDefinition(SkillNumber.SummonedMonsterStr2, Stats.SummonedMonsterDefenseIncrease, AggregateType.AddRaw, Formula6020, 2, 3, SkillNumber.SummonGoblin); + this.AddPassiveMasterSkillDefinition(SkillNumber.SummonedMonsterStr2, Stats.SummonedMonsterDefenseIncrease, AggregateType.AddRaw, Formula6020Value, 3, 2, SkillNumber.SummonGoblin); this.AddMasterSkillDefinition(SkillNumber.AttackIncreaseStr, SkillNumber.GreaterDamage, SkillNumber.Undefined, 2, 4, SkillNumber.GreaterDamage, 20, $"{Formula502} / 100", Formula502, Stats.GreaterDamageBonus, AggregateType.Multiplicate); this.AddPassiveMasterSkillDefinition(SkillNumber.WeaponMasteryHighElf, Stats.MasterSkillPhysBonusDmg, AggregateType.AddRaw, Formula502, 4, 2); this.AddMasterSkillDefinition(SkillNumber.AttackIncreaseMastery, SkillNumber.AttackIncreaseStr, SkillNumber.Undefined, 2, 5, SkillNumber.AttackIncreaseStr, 20, $"{Formula502} / 100", Formula502, Stats.GreaterDamageBonus, AggregateType.Multiplicate, true);