diff --git a/Server.Configurations/ZolianDB/ZolianMundanes.sql b/Server.Configurations/ZolianDB/ZolianMundanes.sql index 1fe72388..fce1e064 100644 Binary files a/Server.Configurations/ZolianDB/ZolianMundanes.sql and b/Server.Configurations/ZolianDB/ZolianMundanes.sql differ diff --git a/Zolian.Server.Base/GameScripts/Monsters/AdvancedMonsterAI.cs b/Zolian.Server.Base/GameScripts/Monsters/AdvancedMonsterAI.cs index 2dc48af9..5d9dbc30 100644 --- a/Zolian.Server.Base/GameScripts/Monsters/AdvancedMonsterAI.cs +++ b/Zolian.Server.Base/GameScripts/Monsters/AdvancedMonsterAI.cs @@ -1,4 +1,5 @@ -using Chaos.Geometry; +using System.Diagnostics; +using Chaos.Geometry; using Chaos.Geometry.Abstractions.Definitions; using Darkages.Common; @@ -181,10 +182,10 @@ public override void MonsterState(TimeSpan elapsedTime) return; } - if (Monster.BashEnabled) + if (Monster.BashEnabled || Monster.NextToTargetFirstAttack) { if (!Monster.CantAttack) - if (assail) Bash(); + if (assail || Monster.NextToTargetFirstAttack) Bash(); } if (Monster.AbilityEnabled) @@ -268,6 +269,7 @@ private string LevelColor(WorldClient client) private void Bash() { + Monster.NextToTargetFirstAttack = false; if (Monster.CantAttack) return; // Training Dummy or other enemies who can't attack if (Monster.SkillScripts.Count == 0) return; @@ -295,8 +297,7 @@ private void Ability() // Training Dummy or other enemies who can't attack if (Monster.AbilityScripts.Count == 0) return; - var abilityAttempt = Generator.RandNumGen100(); - if (abilityAttempt <= 60) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var abilityIdx = RandomNumberGenerator.GetInt32(Monster.AbilityScripts.Count); if (Monster.AbilityScripts[abilityIdx] is null) return; @@ -313,7 +314,7 @@ private void CastSpell() // Training Dummy or other enemies who can't attack if (Monster.SpellScripts.Count == 0) return; - if (!(Generator.RandomPercentPrecise() >= 0.70)) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var spellIdx = RandomNumberGenerator.GetInt32(Monster.SpellScripts.Count); if (Monster.SpellScripts[spellIdx] is null) return; @@ -776,10 +777,10 @@ public override void MonsterState(TimeSpan elapsedTime) return; } - if (Monster.BashEnabled) + if (Monster.BashEnabled || Monster.NextToTargetFirstAttack) { if (!Monster.CantAttack) - if (assail) Bash(); + if (assail || Monster.NextToTargetFirstAttack) Bash(); } if (Monster.AbilityEnabled) @@ -834,6 +835,7 @@ private string LevelColor(IWorldClient client) private void Bash() { + Monster.NextToTargetFirstAttack = false; if (Monster.CantAttack) return; // Training Dummy or other enemies who can't attack if (Monster.SkillScripts.Count == 0) return; @@ -861,8 +863,7 @@ private void Ability() // Training Dummy or other enemies who can't attack if (Monster.AbilityScripts.Count == 0) return; - var abilityAttempt = Generator.RandNumGen100(); - if (abilityAttempt <= 60) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var abilityIdx = RandomNumberGenerator.GetInt32(Monster.AbilityScripts.Count); if (Monster.AbilityScripts[abilityIdx] is null) return; @@ -875,7 +876,7 @@ private void SummonMonsterNearby() if (Monster.CantCast) return; if (Monster.Target is null) return; - if (!(Generator.RandomPercentPrecise() >= 0.70)) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var monstersNearby = Monster.MonstersOnMap(); @@ -1120,7 +1121,7 @@ private void CastSpell() if (Monster.Target is null) return; if (!Monster.Target.WithinMonsterSpellRangeOf(Monster)) return; - if (!(Generator.RandomPercentPrecise() >= 0.70)) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var spellIdx = RandomNumberGenerator.GetInt32(Monster.SpellScripts.Count); if (Monster.SpellScripts[spellIdx] is null) return; @@ -1303,10 +1304,10 @@ public override void MonsterState(TimeSpan elapsedTime) return; } - if (Monster.BashEnabled) + if (Monster.BashEnabled || Monster.NextToTargetFirstAttack) { if (!Monster.CantAttack) - if (assail) Bash(); + if (assail || Monster.NextToTargetFirstAttack) Bash(); } if (Monster.AbilityEnabled) @@ -1363,6 +1364,7 @@ private string LevelColor(WorldClient client) private void Bash() { + Monster.NextToTargetFirstAttack = false; if (Monster.CantAttack) return; // Training Dummy or other enemies who can't attack if (Monster.SkillScripts.Count == 0) return; @@ -1390,8 +1392,7 @@ private void Ability() // Training Dummy or other enemies who can't attack if (Monster.AbilityScripts.Count == 0) return; - var abilityAttempt = Generator.RandNumGen100(); - if (abilityAttempt <= 60) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var abilityIdx = RandomNumberGenerator.GetInt32(Monster.AbilityScripts.Count); if (Monster.AbilityScripts[abilityIdx] is null) return; @@ -1410,7 +1411,7 @@ private void CastSpell() // Training Dummy or other enemies who can't attack if (Monster.SpellScripts.Count == 0) return; - if (!(Generator.RandomPercentPrecise() >= 0.70)) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var spellIdx = RandomNumberGenerator.GetInt32(Monster.SpellScripts.Count); if (Monster.SpellScripts[spellIdx] is null) return; @@ -1593,10 +1594,10 @@ public override void MonsterState(TimeSpan elapsedTime) return; } - if (Monster.BashEnabled) + if (Monster.BashEnabled || Monster.NextToTargetFirstAttack) { if (!Monster.CantAttack) - if (assail) Bash(); + if (assail || Monster.NextToTargetFirstAttack) Bash(); } if (Monster.AbilityEnabled) @@ -1653,6 +1654,7 @@ private string LevelColor(WorldClient client) private void Bash() { + Monster.NextToTargetFirstAttack = false; if (Monster.CantAttack) return; // Training Dummy or other enemies who can't attack if (Monster.SkillScripts.Count == 0) return; @@ -1689,8 +1691,7 @@ private void Ability() // Training Dummy or other enemies who can't attack if (Monster.AbilityScripts.Count == 0) return; - var abilityAttempt = Generator.RandNumGen100(); - if (abilityAttempt <= 60) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var abilityIdx = RandomNumberGenerator.GetInt32(Monster.AbilityScripts.Count); if (Monster.AbilityScripts[abilityIdx] is null) return; @@ -1709,7 +1710,7 @@ private void CastSpell() // Training Dummy or other enemies who can't attack if (Monster.SpellScripts.Count == 0) return; - if (!(Generator.RandomPercentPrecise() >= 0.70)) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var spellIdx = RandomNumberGenerator.GetInt32(Monster.SpellScripts.Count); if (Monster.SpellScripts[spellIdx] is null) return; @@ -1902,10 +1903,10 @@ public override void MonsterState(TimeSpan elapsedTime) return; } - if (Monster.BashEnabled) + if (Monster.BashEnabled || Monster.NextToTargetFirstAttack) { if (!Monster.CantAttack) - if (assail) Bash(); + if (assail || Monster.NextToTargetFirstAttack) Bash(); } if (Monster.AbilityEnabled) @@ -1962,6 +1963,7 @@ private string LevelColor(WorldClient client) private void Bash() { + Monster.NextToTargetFirstAttack = false; if (Monster.CantAttack) return; // Training Dummy or other enemies who can't attack if (Monster.SkillScripts.Count == 0) return; @@ -1989,8 +1991,7 @@ private void Ability() // Training Dummy or other enemies who can't attack if (Monster.AbilityScripts.Count == 0) return; - var abilityAttempt = Generator.RandNumGen100(); - if (abilityAttempt <= 60) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var abilityIdx = RandomNumberGenerator.GetInt32(Monster.AbilityScripts.Count); if (Monster.AbilityScripts[abilityIdx] is null) return; @@ -2009,7 +2010,7 @@ private void CastSpell() // Training Dummy or other enemies who can't attack if (Monster.SpellScripts.Count == 0) return; - if (!(Generator.RandomPercentPrecise() >= 0.70)) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var spellIdx = RandomNumberGenerator.GetInt32(Monster.SpellScripts.Count); if (Monster.SpellScripts[spellIdx] is null) return; @@ -2029,6 +2030,7 @@ public class AosdaHero : MonsterScript private int Count => GhostChat.Length; private int RunCount => GhostChase.Length; private bool _deathCry; + private readonly Stopwatch _vulnerabilityWatch = new(); public AosdaHero(Monster monster, Area map) : base(monster, map) { @@ -2056,11 +2058,19 @@ public override void Update(TimeSpan elapsedTime) if (Monster.IsVulnerable || Monster.IsPoisoned) { - var pos = Monster.Pos; - Monster.SendAnimationNearby(75, new Position(pos)); + if (!_vulnerabilityWatch.IsRunning) + _vulnerabilityWatch.Start(); - foreach (var debuff in Monster.Debuffs.Values) - debuff?.OnEnded(Monster, debuff); + if (_vulnerabilityWatch.Elapsed.TotalMilliseconds > 1000) + { + var pos = Monster.Pos; + Monster.SendAnimationNearby(75, new Position(pos)); + + foreach (var debuff in Monster.Debuffs.Values) + debuff?.OnEnded(Monster, debuff); + + _vulnerabilityWatch.Restart(); + } } MonsterState(elapsedTime); @@ -2199,10 +2209,10 @@ public override void MonsterState(TimeSpan elapsedTime) return; } - if (Monster.BashEnabled) + if (Monster.BashEnabled || Monster.NextToTargetFirstAttack) { if (!Monster.CantAttack) - if (assail) Bash(); + if (assail || Monster.NextToTargetFirstAttack) Bash(); } if (Monster.AbilityEnabled) @@ -2259,6 +2269,7 @@ private string LevelColor(WorldClient client) private void Bash() { + Monster.NextToTargetFirstAttack = false; if (Monster.CantAttack) return; // Training Dummy or other enemies who can't attack if (Monster.SkillScripts.Count == 0) return; @@ -2286,8 +2297,7 @@ private void Ability() // Training Dummy or other enemies who can't attack if (Monster.AbilityScripts.Count == 0) return; - var abilityAttempt = Generator.RandNumGen100(); - if (abilityAttempt <= 30) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var abilityIdx = RandomNumberGenerator.GetInt32(Monster.AbilityScripts.Count); if (Monster.AbilityScripts[abilityIdx] is null) return; @@ -2306,7 +2316,7 @@ private void CastSpell() // Training Dummy or other enemies who can't attack if (Monster.SpellScripts.Count == 0) return; - if (!(Generator.RandomPercentPrecise() >= 0.50)) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var spellIdx = RandomNumberGenerator.GetInt32(Monster.SpellScripts.Count); if (Monster.SpellScripts[spellIdx] is null) return; @@ -2496,10 +2506,10 @@ public override void MonsterState(TimeSpan elapsedTime) return; } - if (Monster.BashEnabled) + if (Monster.BashEnabled || Monster.NextToTargetFirstAttack) { if (!Monster.CantAttack) - if (assail) Bash(); + if (assail || Monster.NextToTargetFirstAttack) Bash(); } if (Monster.AbilityEnabled) @@ -2556,6 +2566,7 @@ private string LevelColor(WorldClient client) private void Bash() { + Monster.NextToTargetFirstAttack = false; if (Monster.CantAttack) return; // Training Dummy or other enemies who can't attack if (Monster.SkillScripts.Count == 0) return; @@ -2583,8 +2594,7 @@ private void Ability() // Training Dummy or other enemies who can't attack if (Monster.AbilityScripts.Count == 0) return; - var abilityAttempt = Generator.RandNumGen100(); - if (abilityAttempt <= 60) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var abilityIdx = RandomNumberGenerator.GetInt32(Monster.AbilityScripts.Count); if (Monster.AbilityScripts[abilityIdx] is null) return; @@ -2603,7 +2613,7 @@ private void CastSpell() // Training Dummy or other enemies who can't attack if (Monster.SpellScripts.Count == 0) return; - if (!(Generator.RandomPercentPrecise() >= 0.70)) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var spellIdx = RandomNumberGenerator.GetInt32(Monster.SpellScripts.Count); if (Monster.SpellScripts[spellIdx] is null) return; @@ -2778,10 +2788,10 @@ public override void MonsterState(TimeSpan elapsedTime) return; } - if (Monster.BashEnabled) + if (Monster.BashEnabled || Monster.NextToTargetFirstAttack) { if (!Monster.CantAttack) - if (assail) Bash(); + if (assail || Monster.NextToTargetFirstAttack) Bash(); } if (Monster.AbilityEnabled) @@ -2851,6 +2861,7 @@ public override void OnApproach(WorldClient client) private void Bash() { + Monster.NextToTargetFirstAttack = false; if (Monster.CantAttack) return; // Training Dummy or other enemies who can't attack if (Monster.SkillScripts.Count == 0) return; @@ -2878,8 +2889,7 @@ private void Ability() // Training Dummy or other enemies who can't attack if (Monster.AbilityScripts.Count == 0) return; - var abilityAttempt = Generator.RandNumGen100(); - if (abilityAttempt <= 60) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var abilityIdx = RandomNumberGenerator.GetInt32(Monster.AbilityScripts.Count); if (Monster.AbilityScripts[abilityIdx] is null) return; @@ -2896,7 +2906,7 @@ private void CastSpell() // Training Dummy or other enemies who can't attack if (Monster.SpellScripts.Count == 0) return; - if (!(Generator.RandomPercentPrecise() >= 0.70)) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var spellIdx = RandomNumberGenerator.GetInt32(Monster.SpellScripts.Count); if (Monster.SpellScripts[spellIdx] is null) return; diff --git a/Zolian.Server.Base/GameScripts/Monsters/BossMonsterAI.cs b/Zolian.Server.Base/GameScripts/Monsters/BossMonsterAI.cs index cd10fd12..37c850c3 100644 --- a/Zolian.Server.Base/GameScripts/Monsters/BossMonsterAI.cs +++ b/Zolian.Server.Base/GameScripts/Monsters/BossMonsterAI.cs @@ -190,10 +190,10 @@ public override void MonsterState(TimeSpan elapsedTime) return; } - if (Monster.BashEnabled) + if (Monster.BashEnabled || Monster.NextToTargetFirstAttack) { if (!Monster.CantAttack) - if (assail) Bash(); + if (assail || Monster.NextToTargetFirstAttack) Bash(); } if (Monster.AbilityEnabled) @@ -250,6 +250,7 @@ private string LevelColor(WorldClient client) private void Bash() { + Monster.NextToTargetFirstAttack = false; if (Monster.CantAttack) return; // Training Dummy or other enemies who can't attack if (Monster.SkillScripts.Count == 0) return; @@ -277,8 +278,7 @@ private void Ability() // Training Dummy or other enemies who can't attack if (Monster.AbilityScripts.Count == 0) return; - var abilityAttempt = Generator.RandNumGen100(); - if (abilityAttempt <= 60) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var abilityIdx = RandomNumberGenerator.GetInt32(Monster.AbilityScripts.Count); if (Monster.AbilityScripts[abilityIdx] is null) return; @@ -297,7 +297,7 @@ private void CastSpell() // Training Dummy or other enemies who can't attack if (Monster.SpellScripts.Count == 0) return; - if (!(Generator.RandomPercentPrecise() >= 0.70)) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var spellIdx = RandomNumberGenerator.GetInt32(Monster.SpellScripts.Count); if (Monster.SpellScripts[spellIdx] is null) return; @@ -487,10 +487,10 @@ public override void MonsterState(TimeSpan elapsedTime) return; } - if (Monster.BashEnabled) + if (Monster.BashEnabled || Monster.NextToTargetFirstAttack) { if (!Monster.CantAttack) - if (assail) Bash(); + if (assail || Monster.NextToTargetFirstAttack) Bash(); } if (Monster.AbilityEnabled) @@ -547,6 +547,7 @@ private string LevelColor(WorldClient client) private void Bash() { + Monster.NextToTargetFirstAttack = false; if (Monster.CantAttack) return; // Training Dummy or other enemies who can't attack if (Monster.SkillScripts.Count == 0) return; @@ -574,8 +575,7 @@ private void Ability() // Training Dummy or other enemies who can't attack if (Monster.AbilityScripts.Count == 0) return; - var abilityAttempt = Generator.RandNumGen100(); - if (abilityAttempt <= 60) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var abilityIdx = RandomNumberGenerator.GetInt32(Monster.AbilityScripts.Count); if (Monster.AbilityScripts[abilityIdx] is null) return; @@ -594,7 +594,7 @@ private void CastSpell() // Training Dummy or other enemies who can't attack if (Monster.SpellScripts.Count == 0) return; - if (!(Generator.RandomPercentPrecise() >= 0.70)) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var spellIdx = RandomNumberGenerator.GetInt32(Monster.SpellScripts.Count); if (Monster.SpellScripts[spellIdx] is null) return; @@ -845,10 +845,10 @@ public override void MonsterState(TimeSpan elapsedTime) return; } - if (Monster.BashEnabled) + if (Monster.BashEnabled || Monster.NextToTargetFirstAttack) { if (!Monster.CantAttack) - if (assail) Bash(); + if (assail || Monster.NextToTargetFirstAttack) Bash(); } if (Monster.AbilityEnabled) @@ -905,6 +905,7 @@ private string LevelColor(WorldClient client) private void Bash() { + Monster.NextToTargetFirstAttack = false; if (Monster.CantAttack) return; // Training Dummy or other enemies who can't attack if (Monster.SkillScripts.Count == 0) return; @@ -932,8 +933,7 @@ private void Ability() // Training Dummy or other enemies who can't attack if (Monster.AbilityScripts.Count == 0) return; - var abilityAttempt = Generator.RandNumGen100(); - if (abilityAttempt <= 60) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var abilityIdx = RandomNumberGenerator.GetInt32(Monster.AbilityScripts.Count); if (Monster.AbilityScripts[abilityIdx] is null) return; @@ -952,7 +952,7 @@ private void CastSpell() // Training Dummy or other enemies who can't attack if (Monster.SpellScripts.Count == 0) return; - if (!(Generator.RandomPercentPrecise() >= 0.70)) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var spellIdx = RandomNumberGenerator.GetInt32(Monster.SpellScripts.Count); if (Monster.SpellScripts[spellIdx] is null) return; @@ -1147,10 +1147,11 @@ public override void MonsterState(TimeSpan elapsedTime) { Bash(); } - if (Monster.BashEnabled) + + if (Monster.BashEnabled || Monster.NextToTargetFirstAttack) { if (!Monster.CantAttack) - if (assail) Bash(); + if (assail || Monster.NextToTargetFirstAttack) Bash(); } if (Monster.AbilityEnabled) @@ -1236,6 +1237,7 @@ private string LevelColor(WorldClient client) private void Bash() { + Monster.NextToTargetFirstAttack = false; if (Monster.CantAttack) return; if (Monster.NextTo((int)Monster.Target.Pos.X, (int)Monster.Target.Pos.Y)) { @@ -1272,7 +1274,7 @@ private void Ability() // Training Dummy or other enemies who can't attack if (Monster.AbilityScripts.Count == 0) return; - if (!(Generator.RandomPercentPrecise() >= 0.70)) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var abilityIdx = RandomNumberGenerator.GetInt32(Monster.AbilityScripts.Count); if (Monster.AbilityScripts[abilityIdx] is null) return; @@ -1289,7 +1291,7 @@ private void CastSpell() // Training Dummy or other enemies who can't attack if (Monster.SpellScripts.Count == 0) return; - if (!(Generator.RandomPercentPrecise() >= 0.70)) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var spellIdx = RandomNumberGenerator.GetInt32(Monster.SpellScripts.Count); if (Monster.SpellScripts[spellIdx] is null) return; @@ -1531,10 +1533,10 @@ public override void MonsterState(TimeSpan elapsedTime) return; } - if (Monster.BashEnabled) + if (Monster.BashEnabled || Monster.NextToTargetFirstAttack) { if (!Monster.CantAttack) - if (assail) Bash(); + if (assail || Monster.NextToTargetFirstAttack) Bash(); } if (Monster.AbilityEnabled) @@ -1586,6 +1588,7 @@ private string LevelColor(WorldClient client) private void Bash() { + Monster.NextToTargetFirstAttack = false; if (Monster.CantAttack) return; // Training Dummy or other enemies who can't attack if (Monster.SkillScripts.Count == 0) return; @@ -1613,8 +1616,7 @@ private void Ability() // Training Dummy or other enemies who can't attack if (Monster.AbilityScripts.Count == 0) return; - var abilityAttempt = Generator.RandNumGen100(); - if (abilityAttempt <= 60) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var abilityIdx = RandomNumberGenerator.GetInt32(Monster.AbilityScripts.Count); if (Monster.AbilityScripts[abilityIdx] is null) return; @@ -1633,7 +1635,7 @@ private void CastSpell() // Training Dummy or other enemies who can't attack if (Monster.SpellScripts.Count == 0) return; - if (!(Generator.RandomPercentPrecise() >= 0.70)) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var spellIdx = RandomNumberGenerator.GetInt32(Monster.SpellScripts.Count); if (Monster.SpellScripts[spellIdx] is null) return; diff --git a/Zolian.Server.Base/GameScripts/Monsters/FriendlyMonsterAI.cs b/Zolian.Server.Base/GameScripts/Monsters/FriendlyMonsterAI.cs index ef25d448..210c19b3 100644 --- a/Zolian.Server.Base/GameScripts/Monsters/FriendlyMonsterAI.cs +++ b/Zolian.Server.Base/GameScripts/Monsters/FriendlyMonsterAI.cs @@ -175,10 +175,10 @@ public override void MonsterState(TimeSpan elapsedTime) return; } - if (Monster.BashEnabled) + if (Monster.BashEnabled || Monster.NextToTargetFirstAttack) { if (!Monster.CantAttack) - if (assail) Bash(); + if (assail || Monster.NextToTargetFirstAttack) Bash(); } if (Monster.AbilityEnabled) @@ -257,6 +257,7 @@ private void UpdateTarget() private void Bash() { + Monster.NextToTargetFirstAttack = false; if (Monster.CantAttack) return; // Training Dummy or other enemies who can't attack if (Monster.SkillScripts.Count == 0) return; @@ -282,8 +283,8 @@ private void Ability() if (Monster.CantAttack) return; // Training Dummy or other enemies who can't attack if (Monster.AbilityScripts.Count == 0) return; - var abilityAttempt = Generator.RandNumGen100(); - if (abilityAttempt <= 60) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; + var abilityIdx = RandomNumberGenerator.GetInt32(Monster.AbilityScripts.Count); if (Monster.AbilityScripts[abilityIdx] is null) return; Monster.AbilityScripts[abilityIdx].OnUse(Monster); @@ -297,7 +298,7 @@ private void CastSpell() if (!Monster.Target.WithinMonsterSpellRangeOf(Monster)) return; // Training Dummy or other enemies who can't attack if (Monster.SpellScripts.Count == 0) return; - if (!(Generator.RandomPercentPrecise() >= 0.70)) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var spellIdx = RandomNumberGenerator.GetInt32(Monster.SpellScripts.Count); if (Monster.SpellScripts[spellIdx] is null) return; Monster.SpellScripts[spellIdx].OnUse(Monster, Monster.Target); diff --git a/Zolian.Server.Base/GameScripts/Monsters/MonsterAI.cs b/Zolian.Server.Base/GameScripts/Monsters/MonsterAI.cs index 51026d64..894a995e 100644 --- a/Zolian.Server.Base/GameScripts/Monsters/MonsterAI.cs +++ b/Zolian.Server.Base/GameScripts/Monsters/MonsterAI.cs @@ -451,10 +451,10 @@ public override void MonsterState(TimeSpan elapsedTime) return; } - if (Monster.BashEnabled) + if (Monster.BashEnabled || Monster.NextToTargetFirstAttack) { if (!Monster.CantAttack) - if (assail) Bash(); + if (assail || Monster.NextToTargetFirstAttack) Bash(); } if (Monster.AbilityEnabled) @@ -506,6 +506,7 @@ private string LevelColor(WorldClient client) private void Bash() { + Monster.NextToTargetFirstAttack = false; if (Monster.CantAttack) return; // Training Dummy or other enemies who can't attack if (Monster.SkillScripts.Count == 0) return; @@ -533,8 +534,7 @@ private void Ability() // Training Dummy or other enemies who can't attack if (Monster.AbilityScripts.Count == 0) return; - var abilityAttempt = Generator.RandNumGen100(); - if (abilityAttempt <= 60) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var abilityIdx = RandomNumberGenerator.GetInt32(Monster.AbilityScripts.Count); if (Monster.AbilityScripts[abilityIdx] is null) return; @@ -551,7 +551,7 @@ private void CastSpell() // Training Dummy or other enemies who can't attack if (Monster.SpellScripts.Count == 0) return; - if (!(Generator.RandomPercentPrecise() >= 0.70)) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var spellIdx = RandomNumberGenerator.GetInt32(Monster.SpellScripts.Count); if (Monster.SpellScripts[spellIdx] is null) return; @@ -901,10 +901,10 @@ public override void MonsterState(TimeSpan elapsedTime) return; } - if (Monster.BashEnabled) + if (Monster.BashEnabled || Monster.NextToTargetFirstAttack) { if (!Monster.CantAttack) - if (assail) Bash(); + if (assail || Monster.NextToTargetFirstAttack) Bash(); } if (Monster.AbilityEnabled) @@ -956,6 +956,7 @@ private string LevelColor(WorldClient client) private void Bash() { + Monster.NextToTargetFirstAttack = false; if (Monster.CantAttack) return; // Training Dummy or other enemies who can't attack if (Monster.SkillScripts.Count == 0) return; @@ -983,8 +984,7 @@ private void Ability() // Training Dummy or other enemies who can't attack if (Monster.AbilityScripts.Count == 0) return; - var abilityAttempt = Generator.RandNumGen100(); - if (abilityAttempt <= 60) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var abilityIdx = RandomNumberGenerator.GetInt32(Monster.AbilityScripts.Count); if (Monster.AbilityScripts[abilityIdx] is null) return; @@ -1001,7 +1001,7 @@ private void CastSpell() // Training Dummy or other enemies who can't attack if (Monster.SpellScripts.Count == 0) return; - if (!(Generator.RandomPercentPrecise() >= 0.70)) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var spellIdx = RandomNumberGenerator.GetInt32(Monster.SpellScripts.Count); if (Monster.SpellScripts[spellIdx] is null) return; @@ -1176,10 +1176,10 @@ public override void MonsterState(TimeSpan elapsedTime) return; } - if (Monster.BashEnabled) + if (Monster.BashEnabled || Monster.NextToTargetFirstAttack) { if (!Monster.CantAttack) - if (assail) Bash(); + if (assail || Monster.NextToTargetFirstAttack) Bash(); } if (Monster.AbilityEnabled) @@ -1231,6 +1231,7 @@ private string LevelColor(WorldClient client) private void Bash() { + Monster.NextToTargetFirstAttack = false; if (Monster.CantAttack) return; // Training Dummy or other enemies who can't attack if (Monster.SkillScripts.Count == 0) return; @@ -1258,8 +1259,7 @@ private void Ability() // Training Dummy or other enemies who can't attack if (Monster.AbilityScripts.Count == 0) return; - var abilityAttempt = Generator.RandNumGen100(); - if (abilityAttempt <= 60) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var abilityIdx = RandomNumberGenerator.GetInt32(Monster.AbilityScripts.Count); if (Monster.AbilityScripts[abilityIdx] is null) return; @@ -1276,7 +1276,7 @@ private void CastSpell() // Training Dummy or other enemies who can't attack if (Monster.SpellScripts.Count == 0) return; - if (!(Generator.RandomPercentPrecise() >= 0.70)) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var spellIdx = RandomNumberGenerator.GetInt32(Monster.SpellScripts.Count); if (Monster.SpellScripts[spellIdx] is null) return; @@ -1451,10 +1451,10 @@ public override void MonsterState(TimeSpan elapsedTime) return; } - if (Monster.BashEnabled) + if (Monster.BashEnabled || Monster.NextToTargetFirstAttack) { if (!Monster.CantAttack) - if (assail) Bash(); + if (assail || Monster.NextToTargetFirstAttack) Bash(); } if (Monster.AbilityEnabled) @@ -1506,6 +1506,7 @@ private string LevelColor(WorldClient client) private void Bash() { + Monster.NextToTargetFirstAttack = false; if (Monster.CantAttack) return; // Training Dummy or other enemies who can't attack if (Monster.SkillScripts.Count == 0) return; @@ -1533,8 +1534,7 @@ private void Ability() // Training Dummy or other enemies who can't attack if (Monster.AbilityScripts.Count == 0) return; - var abilityAttempt = Generator.RandNumGen100(); - if (abilityAttempt <= 60) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var abilityIdx = RandomNumberGenerator.GetInt32(Monster.AbilityScripts.Count); if (Monster.AbilityScripts[abilityIdx] is null) return; @@ -1551,7 +1551,7 @@ private void CastSpell() // Training Dummy or other enemies who can't attack if (Monster.SpellScripts.Count == 0) return; - if (!(Generator.RandomPercentPrecise() >= 0.70)) return; + if (Generator.RandomPercentPrecise() <= 0.70) return; var spellIdx = RandomNumberGenerator.GetInt32(Monster.SpellScripts.Count); if (Monster.SpellScripts[spellIdx] is null) return; diff --git a/Zolian.Server.Base/Network/Server/WorldServer.cs b/Zolian.Server.Base/Network/Server/WorldServer.cs index 731c3ed4..10ba48cd 100644 --- a/Zolian.Server.Base/Network/Server/WorldServer.cs +++ b/Zolian.Server.Base/Network/Server/WorldServer.cs @@ -151,7 +151,8 @@ private static async Task UpdateGroundItemsRoutine() { var groundWatch = new Stopwatch(); groundWatch.Start(); - var variableGameSpeed = 60000; + const int defaultGameSpeed = 60000; // Default update interval in milliseconds (1 minute) + var variableGameSpeed = defaultGameSpeed; while (ServerSetup.Instance.Running) { @@ -161,18 +162,23 @@ private static async Task UpdateGroundItemsRoutine() continue; } + // Call the ground items update logic UpdateGroundItems(); - var awaiter = (int)(60000 - groundWatch.Elapsed.TotalMilliseconds); + + // Calculate the remaining time until the next update + var awaiter = (int)(defaultGameSpeed - groundWatch.Elapsed.TotalMilliseconds); if (awaiter < 0) { - variableGameSpeed = 60000 + awaiter; + // Adjust the game speed dynamically to account for delays + variableGameSpeed = defaultGameSpeed + awaiter; groundWatch.Restart(); continue; } + // Delay for the remaining time, if any await Task.Delay(TimeSpan.FromMilliseconds(awaiter)); - variableGameSpeed = 60000; + variableGameSpeed = defaultGameSpeed; // Reset to default interval groundWatch.Restart(); } } @@ -181,7 +187,8 @@ private static async Task UpdateGroundMoneyRoutine() { var groundWatch = new Stopwatch(); groundWatch.Start(); - var variableGameSpeed = 60000; + const int defaultGameSpeed = 60000; // Default update interval in milliseconds (1 minute) + var variableGameSpeed = defaultGameSpeed; while (ServerSetup.Instance.Running) { @@ -191,18 +198,23 @@ private static async Task UpdateGroundMoneyRoutine() continue; } + // Call the ground money update logic UpdateGroundMoney(); - var awaiter = (int)(60000 - groundWatch.Elapsed.TotalMilliseconds); + + // Calculate the remaining time until the next update + var awaiter = (int)(defaultGameSpeed - groundWatch.Elapsed.TotalMilliseconds); if (awaiter < 0) { - variableGameSpeed = 60000 + awaiter; + // Adjust the game speed dynamically to account for delays + variableGameSpeed = defaultGameSpeed + awaiter; groundWatch.Restart(); continue; } + // Delay for the remaining time, if any await Task.Delay(TimeSpan.FromMilliseconds(awaiter)); - variableGameSpeed = 60000; + variableGameSpeed = defaultGameSpeed; // Reset to default interval groundWatch.Restart(); } } @@ -211,7 +223,8 @@ private static async Task UpdateMundanesRoutine() { var mundanesWatch = new Stopwatch(); mundanesWatch.Start(); - var variableGameSpeed = 1500; + const int defaultGameSpeed = 1500; // Default update interval in milliseconds + var variableGameSpeed = defaultGameSpeed; while (ServerSetup.Instance.Running) { @@ -221,46 +234,60 @@ private static async Task UpdateMundanesRoutine() continue; } + // Call the mundane update logic UpdateMundanes(mundanesWatch.Elapsed); - var awaiter = (int)(1500 - mundanesWatch.Elapsed.TotalMilliseconds); + + // Calculate remaining time until the next update + var awaiter = (int)(defaultGameSpeed - mundanesWatch.Elapsed.TotalMilliseconds); if (awaiter < 0) { - variableGameSpeed = 1500 + awaiter; + // Adjust the game speed dynamically to account for processing delays + variableGameSpeed = defaultGameSpeed + awaiter; mundanesWatch.Restart(); continue; } + // Delay for the remaining time, if any await Task.Delay(TimeSpan.FromMilliseconds(awaiter)); - variableGameSpeed = 1500; + variableGameSpeed = defaultGameSpeed; // Reset to default interval mundanesWatch.Restart(); } } private static async Task UpdateMonstersRoutine() { - var updateInterval = TimeSpan.FromMilliseconds(500); - var nextUpdate = DateTime.UtcNow + updateInterval; + var monstersWatch = new Stopwatch(); + monstersWatch.Start(); + const int defaultGameSpeed = 250; // Default update interval in milliseconds + var variableGameSpeed = defaultGameSpeed; while (ServerSetup.Instance.Running) { - var elapsed = DateTime.UtcNow; - if (elapsed >= nextUpdate) + if (monstersWatch.Elapsed.TotalMilliseconds < variableGameSpeed) { - // Calculate actual elapsed time since the last update - var actualElapsed = DateTime.UtcNow - (nextUpdate - updateInterval); - UpdateMonsters(actualElapsed); - - // Schedule the next update - nextUpdate += updateInterval; + await Task.Delay(1); + continue; } - // Calculate remaining time and delay - var remainingTime = nextUpdate - DateTime.UtcNow; - if (remainingTime > TimeSpan.Zero) + // Update monsters with the elapsed time + UpdateMonsters(monstersWatch.Elapsed); + + // Calculate the remaining time for the next update + var awaiter = (int)(defaultGameSpeed - monstersWatch.Elapsed.TotalMilliseconds); + + if (awaiter < 0) { - await Task.Delay(remainingTime); + // Adjust the game speed dynamically to compensate for overrun + variableGameSpeed = defaultGameSpeed + awaiter; + monstersWatch.Restart(); + continue; } + + // Delay for the remainder of the interval + await Task.Delay(TimeSpan.FromMilliseconds(awaiter)); + variableGameSpeed = defaultGameSpeed; + monstersWatch.Restart(); } } @@ -268,7 +295,7 @@ private static async Task UpdateMapsRoutine() { var gameWatch = new Stopwatch(); gameWatch.Start(); - var variableGameSpeed = GameSpeed; + var variableGameSpeed = GameSpeed; // Use the constant directly while (ServerSetup.Instance.Running) { @@ -278,18 +305,23 @@ private static async Task UpdateMapsRoutine() continue; } + // Call the maps update logic UpdateMaps(gameWatch.Elapsed); + + // Calculate the remaining time until the next update var awaiter = (int)(GameSpeed - gameWatch.Elapsed.TotalMilliseconds); if (awaiter < 0) { + // Adjust the game speed dynamically to account for delays variableGameSpeed = GameSpeed + awaiter; gameWatch.Restart(); continue; } + // Delay for the remaining time, if any await Task.Delay(TimeSpan.FromMilliseconds(awaiter)); - variableGameSpeed = GameSpeed; + variableGameSpeed = GameSpeed; // Reset to default interval gameWatch.Restart(); } } @@ -332,52 +364,66 @@ private async Task UpdateTrapsRoutine() private async Task UpdateClients() { const int baseConcurrency = 10; // Minimum number of concurrent tasks + const int batchSize = 100; // Number of players per batch var maxConcurrency = Math.Max(baseConcurrency, Environment.ProcessorCount); // Adjust based on system capabilities var semaphore = new SemaphoreSlim(maxConcurrency); - var updateInterval = TimeSpan.FromMilliseconds(50); // Fixed update interval - var nextUpdate = DateTime.UtcNow + updateInterval; + var clientWatch = new Stopwatch(); + clientWatch.Start(); + var variableGameSpeed = GameSpeed; // Base interval while (ServerSetup.Instance.Running) { + if (clientWatch.ElapsedMilliseconds < variableGameSpeed) + { + await Task.Delay(1); + continue; + } + var players = Aislings.Where(player => player?.Client != null).ToList(); // Divide players into chunks (batches) for processing - var batchSize = 100; // Number of players per batch - var playerBatches = players.Chunk(batchSize); + var playerBatches = players.Chunk(batchSize).ToList(); - foreach (var batch in playerBatches) - { - // Process each batch with limited concurrency - var playerUpdateTasks = batch.Select(async player => + // Process batches concurrently + var batchTasks = playerBatches.Select(batch => + Task.Run(async () => { - await semaphore.WaitAsync(); - try + foreach (var player in batch) { - await ProcessClientTask(player); - } - finally - { - semaphore.Release(); + await semaphore.WaitAsync(); + try + { + // Process the player inline to reduce task overhead + await ProcessClientTask(player); + } + finally + { + semaphore.Release(); + } } - }); + }) + ); - // Wait for all tasks in the current batch to complete - await Task.WhenAll(playerUpdateTasks); - } + // Wait for all batches to complete + await Task.WhenAll(batchTasks); + + // Calculate the remaining time for the next update + var syncCount = (int)(GameSpeed - clientWatch.ElapsedMilliseconds); - // Calculate remaining time and delay until the next update - var remainingTime = nextUpdate - DateTime.UtcNow; - if (remainingTime > TimeSpan.Zero) + if (syncCount < 0) { - await Task.Delay(remainingTime); + // Adjust GameSpeed dynamically to account for delays + variableGameSpeed = GameSpeed + syncCount; + clientWatch.Restart(); + continue; } - // Schedule the next update - nextUpdate += updateInterval; + await Task.Delay(TimeSpan.FromMilliseconds(syncCount)); + variableGameSpeed = GameSpeed; // Reset to default interval + clientWatch.Restart(); } } - private async Task ProcessClientTask(Aisling player) { if (player?.Client == null) return; diff --git a/Zolian.Server.Base/Sprites/Entity/Monster.cs b/Zolian.Server.Base/Sprites/Entity/Monster.cs index 9b7ba812..de8b1a5c 100644 --- a/Zolian.Server.Base/Sprites/Entity/Monster.cs +++ b/Zolian.Server.Base/Sprites/Entity/Monster.cs @@ -59,6 +59,7 @@ public Monster() public string Size { get; set; } public ushort Image { get; set; } public bool WalkEnabled { get; set; } + public bool NextToTargetFirstAttack { get; set; } public WorldServerTimer BashTimer { get; init; } public WorldServerTimer AbilityTimer { get; init; } public WorldServerTimer CastTimer { get; init; } @@ -557,6 +558,7 @@ public void Walk() public void NextToTarget() { if (Target == null) return; + NextToTargetFirstAttack = true; if (Facing((int)Target.Pos.X, (int)Target.Pos.Y, out var direction)) { diff --git a/Zolian.Server.Base/Sprites/Sprite.cs b/Zolian.Server.Base/Sprites/Sprite.cs index a273a662..a398ffec 100644 --- a/Zolian.Server.Base/Sprites/Sprite.cs +++ b/Zolian.Server.Base/Sprites/Sprite.cs @@ -44,8 +44,8 @@ public abstract class Sprite : INotifyPropertyChanged HasBuff("Iron Skin") || HasBuff("Wings of Protection"); public bool Hastened => HasBuff("Hastenga") || HasBuff("Hasten") || HasBuff("Haste"); public bool SpellReflect => HasBuff("Deireas Faileas") || HasBuff("Secured Position"); - public bool SpellNegate => HasBuff("Perfect Defense") || this is Aisling { GameMaster: true }; - public bool SkillReflect => HasBuff("Asgall") || this is Aisling { GameMaster: true }; + public bool SpellNegate => HasBuff("Perfect Defense")/* || this is Aisling { GameMaster: true }*/; + public bool SkillReflect => HasBuff("Asgall")/* || this is Aisling { GameMaster: true }*/; public bool IsBlind => HasDebuff("Blind"); public bool IsConfused => HasDebuff("Confused"); public bool IsSilenced => HasDebuff("Silence");