From a7cf086ccafa80f3484d65f7777db05d9fd71dd5 Mon Sep 17 00:00:00 2001 From: Designer225 Date: Thu, 18 Jan 2024 22:20:20 -0800 Subject: [PATCH] Fixed error vomit --- BattleRegen/BattleRegen.csproj | 2 +- BattleRegen/BattleRegenAgentComponent.cs | 319 ++++++++++++++++++++++ BattleRegen/BattleRegenAgentData.cs | 307 --------------------- BattleRegen/BattleRegenMissionBehavior.cs | 110 ++++++++ BattleRegen/BattleRegenerationBehavior.cs | 174 ------------ BattleRegen/SubModule.cs | 2 +- SubModule.xml | 2 +- 7 files changed, 432 insertions(+), 484 deletions(-) create mode 100644 BattleRegen/BattleRegenAgentComponent.cs delete mode 100644 BattleRegen/BattleRegenAgentData.cs create mode 100644 BattleRegen/BattleRegenMissionBehavior.cs delete mode 100644 BattleRegen/BattleRegenerationBehavior.cs diff --git a/BattleRegen/BattleRegen.csproj b/BattleRegen/BattleRegen.csproj index b70c10d..fa31927 100644 --- a/BattleRegen/BattleRegen.csproj +++ b/BattleRegen/BattleRegen.csproj @@ -11,7 +11,7 @@ false false True - 2.2.3.100 + 2.2.4.110 full ..\bin\Win64_Shipping_Client\BattleRegen.xml $(OutputPath) diff --git a/BattleRegen/BattleRegenAgentComponent.cs b/BattleRegen/BattleRegenAgentComponent.cs new file mode 100644 index 0000000..f6b8fe7 --- /dev/null +++ b/BattleRegen/BattleRegenAgentComponent.cs @@ -0,0 +1,319 @@ +using System; +using System.Collections.Generic; +using System.Runtime; +using System.Text; +using TaleWorlds.CampaignSystem; +using TaleWorlds.Core; +using TaleWorlds.Library; +using TaleWorlds.MountAndBlade; + +namespace BattleRegen +{ + internal class BattleRegenAgentComponent : AgentComponent + { + private const int HumanFamilyType = 0; + + private readonly Stack<(Hero, float)> _xpGains; + private readonly Queue _messages; + private readonly IBattleRegenSettings _settings; + + public float HealthLimit { get; } + public float TimeSinceLastAttack { get; private set; } + public AgentType AgentType { get; internal set; } + + public BattleRegenAgentComponent(Agent agent) : base(agent) + { + _settings = BattleRegenSettingsUtil.Instance; + HealthLimit = _settings.HealToFull ? agent.HealthLimit : agent.Health; + TimeSinceLastAttack = 0f; + AgentType = default; // make the compiler happy + _xpGains = new Stack<(Hero, float)>(); + _messages = new Queue(); + UpdateAgentType(); + + if (_settings.Debug) + Debug.Print($"[BattleRegeneration] Initial {Agent.Name} ({Agent.GetHashCode()}) information: " + + $"HealthLimit {HealthLimit}, AgentType {AgentType}, initial team {Agent.Team}"); + } + + public override void OnHit(Agent affectorAgent, int damage, in MissionWeapon affectorWeapon) + => TimeSinceLastAttack = 0f; + + public void TransferInformation(Dictionary heroXpGainPairs, Queue messages) + { + while (_xpGains.Count > 0) + { + var item = _xpGains.Pop(); + var check = heroXpGainPairs.TryGetValue(item.Item1, out var xp); + heroXpGainPairs[item.Item1] = item.Item2 + (check ? xp : 0); + } + while (_messages.Count > 0) + messages.Enqueue(_messages.Dequeue()); + } + + internal void OnTick(float dt) + { + TimeSinceLastAttack += dt; + if (TimeSinceLastAttack < _settings.DelayedRegenTime) return; + + if (Agent.Health < Agent.HealthDyingThreshold) return; + if (Agent.Health > HealthLimit) return; + + try + { + switch (AgentType) + { + case AgentType.Mount: + case AgentType.Animal: + Regenerate(_settings.RegenAmountAnimals, dt); + break; + case AgentType.Player: + Regenerate(_settings.RegenAmount, dt); + break; + case AgentType.Companion: + Regenerate(_settings.RegenAmountCompanions, dt); + break; + case AgentType.Subordinate: + Regenerate(_settings.RegenAmountSubordinates, dt); + break; + case AgentType.PlayerTroop: + Regenerate(_settings.RegenAmountPartyTroops, dt); + break; + case AgentType.AlliedHero: + Regenerate(_settings.RegenAmountAllies, dt); + break; + case AgentType.AlliedTroop: + Regenerate(_settings.RegenAmountAlliedTroops, dt); + break; + case AgentType.IndependentHero: + case AgentType.EnemyHero: + Regenerate(_settings.RegenAmountEnemies, dt); + break; + case AgentType.IndependentTroop: + case AgentType.EnemyTroop: + default: + Regenerate(_settings.RegenAmountEnemyTroops, dt); + break; + } + } + catch (Exception e) + { + _messages.Enqueue($"[BattleRegeneration] An exception has occurred attempting to heal {Agent.Name}. Will try again next tick.\nException: {e}"); + } + } + + private void Regenerate(float ratePercent, float dt) + { + if (Agent.Health > 0f && Agent.Health < HealthLimit) + { + var modifier = GetHealthModifier(); + float baseRegenRate = ratePercent / 100f * Agent.HealthLimit; // regen rate is always based on all-time health limit + float regenRate = ApplyRegenModel(baseRegenRate, modifier); + float regenAmount = regenRate * dt; + + if (Agent.Health + regenAmount >= HealthLimit) + Agent.Health = HealthLimit; + else + Agent.Health += (float)regenAmount; + + if (Game.Current.GameType is Campaign) + GiveXpToHealers(Agent, regenAmount, _settings); + if (_settings.VerboseDebug) + _messages.Enqueue( + $"[BattleRegeneration] {Enum.GetName(typeof(AgentType), AgentType)} agent {Agent.Name} health: {Agent.Health}, health limit: {HealthLimit}, " + + $"health added: {regenAmount} (base: {baseRegenRate * dt}, multiplier: {modifier}), dt: {dt}"); + } + } + + private float ApplyRegenModel(float baseRegenRate, float modifier) + { + float regenRate = baseRegenRate * modifier; + float regenTime = HealthLimit / regenRate; + float origRegenTime = Agent.HealthLimit / regenRate; + + try + { + var data = new RegenDataInfo(Agent, HealthLimit, regenRate, regenTime, origRegenTime); + regenRate = _settings.RegenModel.Calculate(ref data); + } + catch (Exception e) + { + _messages.Enqueue($"[BattleRegeneration] An exception has occurred attempting to calculate regen value for {Agent.Name}. Using linear instead.\nException: {e}"); + } + + return regenRate; + } + + private float GetHealthModifier() + { + float modifier = 1f; + float percentMedBoost = _settings.MedicineBoost / 100f; + + // rewrite + // for mounts, get rider skills + // for humans, get unit skills, then commander skills - for now general only, but maybe sergeant later on + switch (AgentType) + { + case AgentType.Mount: + case AgentType.Animal: + if (Agent.MountAgent != default) modifier += Agent.MountAgent.Character.GetSkillValue(DefaultSkills.Medicine) / 50f * percentMedBoost; + break; + default: + modifier += Agent.Character.GetSkillValue(DefaultSkills.Medicine) / 50f * percentMedBoost; + switch (AgentType) + { + case AgentType.Player: + case AgentType.Companion: + case AgentType.Subordinate: + case AgentType.PlayerTroop: + modifier += Agent.Main.Character.GetSkillValue(DefaultSkills.Medicine) / 50f * _settings.CommanderMedicineBoost / 100f; + break; + case AgentType.AlliedHero: + case AgentType.AlliedTroop: + case AgentType.EnemyHero: + case AgentType.EnemyTroop: + if (Agent.Team?.GeneralAgent == null) break; + modifier += Agent.Team.GeneralAgent.Character.GetSkillValue(DefaultSkills.Medicine) / 50f * _settings.CommanderMedicineBoost / 100f; + break; + } + break; + } + + if (_settings.VerboseDebug) + _messages.Enqueue( + $"[BattleRegeneration] {Enum.GetName(typeof(AgentType), AgentType)} agent {Agent.Name} is receiving a {modifier} multiplier in health regeneration"); + return modifier; + } + + private void GiveXpToHealers(Agent agent, float regenAmount, IBattleRegenSettings settings) + { + float xpGain = regenAmount / agent.HealthLimit; // xp gain is also based on all-time health limit + + switch (AgentType) + { + case AgentType.Mount: + case AgentType.Animal: + float riderXpGain = xpGain * settings.XpGain; + var rider = (agent.MountAgent?.Character as CharacterObject)?.HeroObject; + if (rider != null) + { + _xpGains.Push((rider, riderXpGain)); + if (settings.VerboseDebug && rider != null) + _messages.Enqueue($"[BattleRegeneration] rider agent {rider} has received {riderXpGain} xp"); + } + + break; + default: + float selfXpGain = xpGain * settings.XpGain; + var hero = (agent.Character as CharacterObject)?.HeroObject; + if (hero != null) + { + _xpGains.Push((hero, selfXpGain)); + if (settings.VerboseDebug) + _messages.Enqueue($"[BattleRegeneration] agent {agent.Name} has received {selfXpGain} xp"); + } + + float cdrXpGain = xpGain * settings.CommanderXpGain; + var commander = default(Hero); + switch (AgentType) + { + case AgentType.Player: + case AgentType.Companion: + case AgentType.Subordinate: + case AgentType.PlayerTroop: + commander = (Agent.Main?.Character as CharacterObject)?.HeroObject; + commander ??= (Mission.Current?.MainAgent?.Character as CharacterObject)?.HeroObject; + commander ??= (Mission.Current?.PlayerTeam?.ActiveAgents?.Find(x => x.IsPlayerUnit)?.Character as CharacterObject)?.HeroObject; + commander ??= Hero.MainHero; + break; + case AgentType.AlliedHero: + case AgentType.AlliedTroop: + case AgentType.EnemyHero: + case AgentType.EnemyTroop: + commander = (agent.Team?.GeneralAgent?.Character as CharacterObject)?.HeroObject; + break; + } + if (commander != default) + { + _xpGains.Push((commander, cdrXpGain)); + if (settings.VerboseDebug) + _messages.Enqueue($"[BattleRegeneration] commander agent {commander.Name} has received {cdrXpGain} xp"); + } + break; + } + } + + public void UpdateAgentType() + { + try + { + // rewrite + // if agent is animal or mount, return animal or mount + // if team is null, return independent + // units in player-commanded teams are subordinates or player troops + // units in other teams are handled accordingly + if (Agent.IsMount) AgentType = AgentType.Mount; + else if (Agent.Monster.FamilyType != HumanFamilyType) AgentType = AgentType.Animal; + else if (Agent.IsPlayerUnit) AgentType = AgentType.Player; + else if (Agent.IsHero && Agent.Character is CharacterObject chObj && chObj.HeroObject.IsPlayerCompanion) + AgentType = AgentType.Companion; + else + { + var team = Agent.Team; + if (team == null || !team.IsValid) + { + if (Agent.IsHero) AgentType = AgentType.IndependentHero; + else AgentType = AgentType.IndependentTroop; + } + else if (team.IsPlayerTeam) + { + if (team.IsPlayerGeneral || Agent.Formation.PlayerOwner != default && Agent.Formation.PlayerOwner.IsPlayerUnit) + { + if (Agent.IsHero) AgentType = AgentType.Subordinate; + else AgentType = AgentType.PlayerTroop; + } + else + { + if (Agent.IsHero) AgentType = AgentType.AlliedHero; + else AgentType = AgentType.AlliedTroop; + } + } + else if (team.IsPlayerAlly) + { + if (Agent.IsHero) AgentType = AgentType.AlliedHero; + else AgentType = AgentType.AlliedTroop; + } + else + { + if (Agent.IsHero) AgentType = AgentType.EnemyHero; + else AgentType = AgentType.EnemyTroop; + } + } + } + catch (Exception e) + { + Debug.Print($"[BattleRegen]An error has occurred retriving troop type. None will be returned; note this is not normal behavior.\n{e}"); + AgentType = AgentType.None; + } + if (_settings.Debug) + Debug.Print($"[BattleRegen] Agent {Agent.Name} {Agent.GetHashCode()} is now AgentType {AgentType}"); + } + } + + internal enum AgentType + { + None, + Mount, + Animal, + Player, + Companion, + IndependentHero, + IndependentTroop, + Subordinate, + PlayerTroop, + AlliedHero, + AlliedTroop, + EnemyHero, + EnemyTroop + } +} diff --git a/BattleRegen/BattleRegenAgentData.cs b/BattleRegen/BattleRegenAgentData.cs deleted file mode 100644 index 711143b..0000000 --- a/BattleRegen/BattleRegenAgentData.cs +++ /dev/null @@ -1,307 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime; -using System.Text; -using TaleWorlds.CampaignSystem; -using TaleWorlds.Core; -using TaleWorlds.Library; -using TaleWorlds.MountAndBlade; - -namespace BattleRegen -{ - internal struct BattleRegenAgentData - { - private const int HumanFamilyType = 0; - - public readonly Agent Agent { get; } - public readonly float HealthLimit { get; } - public float TimeSinceLastAttack { get; private set; } - - public BattleRegenAgentData(Agent agent) - { - Agent = agent; - var settings = BattleRegenSettingsUtil.Instance; - HealthLimit = settings.HealToFull ? agent.HealthLimit : agent.Health; - TimeSinceLastAttack = 0f; - } - - internal void TickHeal() => TimeSinceLastAttack = 0f; - - internal (Queue? messages, Stack<(Hero, float)>? xpGains) AttemptRegeneration(float dt, IBattleRegenSettings settings) - { - TimeSinceLastAttack += dt; - if (TimeSinceLastAttack < settings.DelayedRegenTime) return (null, null); - - if (Agent.Health < Agent.HealthDyingThreshold) return (null, null); - if (Agent.Health > HealthLimit) return (null, null); - - var messages = new Queue(); - var xpGains = new Stack<(Hero, float)>(); - try - { - var troopType = GetTroopType(messages); - switch (troopType) - { - case TroopType.Mount: - case TroopType.Animal: - Regenerate(settings.RegenAmountAnimals, dt, troopType, messages, xpGains, settings); - break; - case TroopType.Player: - Regenerate(settings.RegenAmount, dt, troopType, messages, xpGains, settings); - break; - case TroopType.Companion: - Regenerate(settings.RegenAmountCompanions, dt, troopType, messages, xpGains, settings); - break; - case TroopType.Subordinate: - Regenerate(settings.RegenAmountSubordinates, dt, troopType, messages, xpGains, settings); - break; - case TroopType.PlayerTroop: - Regenerate(settings.RegenAmountPartyTroops, dt, troopType, messages, xpGains, settings); - break; - case TroopType.AlliedHero: - Regenerate(settings.RegenAmountAllies, dt, troopType, messages, xpGains, settings); - break; - case TroopType.AlliedTroop: - Regenerate(settings.RegenAmountAlliedTroops, dt, troopType, messages, xpGains, settings); - break; - case TroopType.IndependentHero: - case TroopType.EnemyHero: - Regenerate(settings.RegenAmountEnemies, dt, troopType, messages, xpGains, settings); - break; - case TroopType.IndependentTroop: - case TroopType.EnemyTroop: - default: - Regenerate(settings.RegenAmountEnemyTroops, dt, troopType, messages, xpGains, settings); - break; - } - } - catch (Exception e) - { - messages.Enqueue($"[BattleRegeneration] An exception has occurred attempting to heal {Agent.Name}. Will try again next tick.\nException: {e}"); - } - - return (messages, xpGains); - } - - private readonly void Regenerate( - float ratePercent, - float dt, - TroopType troopType, - Queue messages, - Stack<(Hero, float)> xpGains, - IBattleRegenSettings settings) - { - if (Agent.Health > 0f && Agent.Health < HealthLimit) - { - var modifier = GetHealthModifier(troopType, messages, settings); - float baseRegenRate = ratePercent / 100f * Agent.HealthLimit; // regen rate is always based on all-time health limit - float regenRate = ApplyRegenModel(baseRegenRate, modifier, messages, settings); - float regenAmount = regenRate * dt; - - if (Agent.Health + regenAmount >= HealthLimit) - Agent.Health = HealthLimit; - else - Agent.Health += (float)regenAmount; - - if (Game.Current.GameType is Campaign) - GiveXpToHealers(Agent, troopType, regenAmount, messages, xpGains, settings); - if (settings.VerboseDebug) - messages.Enqueue( - $"[BattleRegeneration] {Enum.GetName(typeof(TroopType), troopType)} agent {Agent.Name} health: {Agent.Health}, health limit: {HealthLimit}, " + - $"health added: {regenAmount} (base: {baseRegenRate * dt}, multiplier: {modifier}), dt: {dt}"); - } - } - - private readonly float ApplyRegenModel(float baseRegenRate, float modifier, Queue messages, IBattleRegenSettings settings) - { - float regenRate = baseRegenRate * modifier; - float regenTime = HealthLimit / regenRate; - float origRegenTime = Agent.HealthLimit / regenRate; - - try - { - var data = new RegenDataInfo(Agent, HealthLimit, regenRate, regenTime, origRegenTime); - regenRate = settings.RegenModel.Calculate(ref data); - } - catch (Exception e) - { - messages.Enqueue($"[BattleRegeneration] An exception has occurred attempting to calculate regen value for {Agent.Name}. Using linear instead.\nException: {e}"); - } - - return regenRate; - } - - private readonly float GetHealthModifier(TroopType troopType, Queue messages, IBattleRegenSettings settings) - { - float modifier = 1f; - float percentMedBoost = settings.MedicineBoost / 100f; - - // rewrite - // for mounts, get rider skills - // for humans, get unit skills, then commander skills - for now general only, but maybe sergeant later on - switch (troopType) - { - case TroopType.Mount: - case TroopType.Animal: - if (Agent.MountAgent != default) modifier += Agent.MountAgent.Character.GetSkillValue(DefaultSkills.Medicine) / 50f * percentMedBoost; - break; - default: - modifier += Agent.Character.GetSkillValue(DefaultSkills.Medicine) / 50f * percentMedBoost; - switch (troopType) - { - case TroopType.Player: - case TroopType.Companion: - case TroopType.Subordinate: - case TroopType.PlayerTroop: - modifier += Agent.Main.Character.GetSkillValue(DefaultSkills.Medicine) / 50f * settings.CommanderMedicineBoost / 100f; - break; - case TroopType.AlliedHero: - case TroopType.AlliedTroop: - case TroopType.EnemyHero: - case TroopType.EnemyTroop: - if (Agent.Team?.GeneralAgent == null) break; - modifier += Agent.Team.GeneralAgent.Character.GetSkillValue(DefaultSkills.Medicine) / 50f * settings.CommanderMedicineBoost / 100f; - break; - } - break; - } - - if (settings.VerboseDebug) - messages.Enqueue( - $"[BattleRegeneration] {Enum.GetName(typeof(TroopType), troopType)} agent {Agent.Name} is receiving a {modifier} multiplier in health regeneration"); - return modifier; - } - - public readonly void GiveXpToHealers( - Agent agent, - TroopType troopType, - float regenAmount, - Queue messages, - Stack<(Hero, float)> xpGains, - IBattleRegenSettings settings) - { - float xpGain = regenAmount / agent.HealthLimit; // xp gain is also based on all-time health limit - - switch (troopType) - { - case TroopType.Mount: - case TroopType.Animal: - float riderXpGain = xpGain * settings.XpGain; - var rider = (agent.MountAgent?.Character as CharacterObject)?.HeroObject; - if (rider != null) - xpGains.Push((rider, riderXpGain)); - - if (settings.VerboseDebug && rider != null) - messages.Enqueue($"[BattleRegeneration] rider agent {rider} has received {riderXpGain} xp"); - break; - default: - float selfXpGain = xpGain * settings.XpGain; - var hero = (agent.Character as CharacterObject)?.HeroObject; - if (hero != null) - xpGains.Push((hero, selfXpGain)); - if (settings.VerboseDebug) - messages.Enqueue($"[BattleRegeneration] agent {agent.Name} has received {selfXpGain} xp"); - - float cdrXpGain = xpGain * settings.CommanderXpGain; - var commander = default(Hero); - switch (troopType) - { - case TroopType.Player: - case TroopType.Companion: - case TroopType.Subordinate: - case TroopType.PlayerTroop: - commander = (Agent.Main?.Character as CharacterObject)?.HeroObject; - commander ??= (Mission.Current?.MainAgent?.Character as CharacterObject)?.HeroObject; - commander ??= (Mission.Current?.PlayerTeam?.ActiveAgents?.Find(x => x.IsPlayerUnit)?.Character as CharacterObject)?.HeroObject; - commander ??= Hero.MainHero; - break; - case TroopType.AlliedHero: - case TroopType.AlliedTroop: - case TroopType.EnemyHero: - case TroopType.EnemyTroop: - commander = (agent.Team?.GeneralAgent?.Character as CharacterObject)?.HeroObject; - break; - } - if (commander != default) - { - xpGains.Push((commander, cdrXpGain)); - if (settings.VerboseDebug) - messages.Enqueue($"[BattleRegeneration] commander agent {commander.Name} has received {cdrXpGain} xp"); - } - break; - } - } - - private readonly TroopType GetTroopType(Queue messages) - { - try - { - - // rewrite - // if agent is animal or mount, return animal or mount - // if team is null, return independent - // units in player-commanded teams are subordinates or player troops - // units in other teams are handled accordingly - if (Agent.IsMount) return TroopType.Mount; - else if (Agent.Monster.FamilyType != HumanFamilyType) return TroopType.Animal; - else if (Agent.IsPlayerUnit) return TroopType.Player; - else if (Agent.IsHero && Agent.Character is CharacterObject chObj && chObj.HeroObject.IsPlayerCompanion) - return TroopType.Companion; - else - { - var team = Agent.Team; - if (team == null || !team.IsValid) - { - if (Agent.IsHero) return TroopType.IndependentHero; - else return TroopType.IndependentTroop; - } - else if (team.IsPlayerTeam) - { - if (team.IsPlayerGeneral || Agent.Formation.PlayerOwner != default && Agent.Formation.PlayerOwner.IsPlayerUnit) - { - if (Agent.IsHero) return TroopType.Subordinate; - else return TroopType.PlayerTroop; - } - else - { - if (Agent.IsHero) return TroopType.AlliedHero; - else return TroopType.AlliedTroop; - } - } - else if (team.IsPlayerAlly) - { - if (Agent.IsHero) return TroopType.AlliedHero; - else return TroopType.AlliedTroop; - } - else - { - if (Agent.IsHero) return TroopType.EnemyHero; - else return TroopType.EnemyTroop; - } - } - } - catch (Exception e) - { - messages.Enqueue($"[BattleRegen]An error has occurred retriving troop type. None will be returned; note this is not normal behavior.\n{e}"); - return TroopType.None; - } - } - } - - internal enum TroopType - { - None, - Mount, - Animal, - Player, - Companion, - IndependentHero, - IndependentTroop, - Subordinate, - PlayerTroop, - AlliedHero, - AlliedTroop, - EnemyHero, - EnemyTroop - } -} diff --git a/BattleRegen/BattleRegenMissionBehavior.cs b/BattleRegen/BattleRegenMissionBehavior.cs new file mode 100644 index 0000000..449a18f --- /dev/null +++ b/BattleRegen/BattleRegenMissionBehavior.cs @@ -0,0 +1,110 @@ +using SandBox; +using SandBox.Missions.MissionLogics.Arena; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.ComponentModel.Design; +using System.Linq; +using System.Runtime; +using System.Threading.Tasks; +using TaleWorlds.CampaignSystem; +using TaleWorlds.Core; +using TaleWorlds.Engine; +using TaleWorlds.Library; +using TaleWorlds.MountAndBlade; + +namespace BattleRegen +{ + sealed class BattleRegenMissionBehavior : MissionBehavior + { + private const int AnticipatedAgentCount = 2048; + + private readonly IBattleRegenSettings _settings; + private readonly Dictionary _heroXpGainPairs; + private readonly Dictionary _agentComponents; + private readonly Queue _messages; + + public override MissionBehaviorType BehaviorType => MissionBehaviorType.Other; + + public BattleRegenMissionBehavior() + { + _settings = BattleRegenSettingsUtil.Instance; + _heroXpGainPairs = new Dictionary(); + _agentComponents = new Dictionary(AnticipatedAgentCount); + _messages = new Queue(); + + Debug.Print("[BattleRegeneration] Mission started, data initialized"); + Debug.Print($"[BattleRegeneration] Debug mode on, dumping settings: regen mode: {_settings.RegenModel}, " + + $"medicine boost: {_settings.RegenAmount}, regen model: {_settings.MedicineBoost}, commander medicine boost: {_settings.CommanderMedicineBoost}, " + + $"xp gain: {_settings.XpGain}, commander xp gain: {_settings.CommanderXpGain}, " + + $"regen in percent HP: player:{_settings.RegenAmount}, subordinate:{_settings.RegenAmountCompanions}, allied heroes:{_settings.RegenAmountAllies}, " + + $"party troops:{_settings.RegenAmountPartyTroops}, allied troops:{_settings.RegenAmountAlliedTroops}, enemy heroes:{_settings.RegenAmountEnemies}, " + + $"enemy troops:{_settings.RegenAmountEnemyTroops}, animals:{_settings.RegenAmountAnimals}"); + } + + public override void OnAgentBuild(Agent agent, Banner banner) + { + var behavior = new BattleRegenAgentComponent(agent); + agent.AddComponent(behavior); + _agentComponents[agent] = behavior; + } + + private void RemoveAgent(Agent agent) + { + if (_agentComponents.TryGetValue(agent, out var component)) + { + agent.RemoveComponent(component); + _agentComponents.Remove(agent); + } + } + + public override void OnAgentRemoved(Agent affectedAgent, Agent affectorAgent, AgentState agentState, KillingBlow blow) + => RemoveAgent(affectedAgent); + + public override void OnAgentDeleted(Agent affectedAgent) + => RemoveAgent(affectedAgent); + + public override void OnMissionTick(float dt) + { + var arenaController = Mission.GetMissionBehavior(); + if (arenaController != default && arenaController.AfterPractice) return; + _agentComponents.Values.AsParallel().ForAll(x => x.OnTick(dt)); + foreach (var component in _agentComponents.Values) + component.TransferInformation(_heroXpGainPairs, _messages); + while (_messages.Count > 0) + Debug.Print(_messages.Dequeue()); + } + + public override void OnAgentTeamChanged(Team prevTeam, Team newTeam, Agent agent) + { + if (_agentComponents.TryGetValue(agent, out var component)) component.UpdateAgentType(); + } + + public override void OnClearScene() + { + _heroXpGainPairs.Clear(); + _agentComponents.Clear(); + } + + protected override void OnEndMission() + { + foreach (var kv in _heroXpGainPairs) + { + var (hero, xp) = (kv.Key, kv.Value); + try + { + if (hero != default) + { + hero.AddSkillXp(DefaultSkills.Medicine, xp); + if (_settings.Debug) + Debug.Print($"[BattleRegeneration] hero {hero.Name} has received {xp} xp from battle"); + } + } + catch (Exception e) + { + Debug.Print($"[BattleRegeneration] An error occurred attempting to add XP to a hero.\n{e}"); + } + } + } + } +} diff --git a/BattleRegen/BattleRegenerationBehavior.cs b/BattleRegen/BattleRegenerationBehavior.cs deleted file mode 100644 index ec19df3..0000000 --- a/BattleRegen/BattleRegenerationBehavior.cs +++ /dev/null @@ -1,174 +0,0 @@ -using SandBox; -using SandBox.Missions.MissionLogics.Arena; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.ComponentModel.Design; -using System.Linq; -using System.Runtime; -using System.Threading.Tasks; -using TaleWorlds.CampaignSystem; -using TaleWorlds.Core; -using TaleWorlds.Engine; -using TaleWorlds.Library; -using TaleWorlds.MountAndBlade; - -namespace BattleRegen -{ - sealed class BattleRegenerationBehavior : MissionBehavior - { - private const int AnticipatedAgentCount = 2048; - - private readonly IBattleRegenSettings _settings; - private readonly Dictionary _heroXpGainPairs; - private readonly Dictionary _agentIndexPairs; - private BattleRegenAgentData[] _agentData; - private Stack<(Agent, bool)> _toAddOrRemove; - - public override MissionBehaviorType BehaviorType => MissionBehaviorType.Other; - - public BattleRegenerationBehavior() - { - _settings = BattleRegenSettingsUtil.Instance; - _heroXpGainPairs = new Dictionary(); - _agentIndexPairs = new Dictionary(AnticipatedAgentCount); - _agentData = new BattleRegenAgentData[AnticipatedAgentCount]; - _toAddOrRemove = new Stack<(Agent, bool)>(); - - Debug.Print("[BattleRegeneration] Mission started, data initialized"); - Debug.Print($"[BattleRegeneration] Debug mode on, dumping settings: regen mode: {_settings.RegenModel}, " + - $"medicine boost: {_settings.RegenAmount}, regen model: {_settings.MedicineBoost}, commander medicine boost: {_settings.CommanderMedicineBoost}, " + - $"xp gain: {_settings.XpGain}, commander xp gain: {_settings.CommanderXpGain}, " + - $"regen in percent HP: player:{_settings.RegenAmount}, subordinate:{_settings.RegenAmountCompanions}, allied heroes:{_settings.RegenAmountAllies}, " + - $"party troops:{_settings.RegenAmountPartyTroops}, allied troops:{_settings.RegenAmountAlliedTroops}, enemy heroes:{_settings.RegenAmountEnemies}, " + - $"enemy troops:{_settings.RegenAmountEnemyTroops}, animals:{_settings.RegenAmountAnimals}"); - } - - private void EnsureCapacity(int add = 1) - { - int capacity = _agentIndexPairs.Count + add; - if (_agentData.Length < capacity) - { - int newCapacity = _agentData.Length * 2; - if (newCapacity < capacity) newCapacity = capacity; - Array.Resize(ref _agentData, newCapacity); - } - } - - public override void OnAgentBuild(Agent agent, Banner banner) => _toAddOrRemove.Push((agent, true)); - - public override void OnAgentRemoved(Agent affectedAgent, Agent affectorAgent, AgentState agentState, KillingBlow blow) - => _toAddOrRemove.Push((affectedAgent, false)); - - public override void OnAgentDeleted(Agent affectedAgent) => _toAddOrRemove.Push((affectedAgent, false)); - - public override void OnRegisterBlow(Agent attacker, Agent victim, GameEntity realHitEntity, Blow b, ref AttackCollisionData collisionData, in MissionWeapon attackerWeapon) - { - if (victim != null && _agentIndexPairs.TryGetValue(victim, out var index)) - _agentData[index].TickHeal(); - if (b.SelfInflictedDamage == 1 && attacker != null) - { - if (_agentIndexPairs.TryGetValue(attacker, out index)) - _agentData[index].TickHeal(); - if (attacker.MountAgent != null && _agentIndexPairs.TryGetValue(attacker.MountAgent, out index)) - _agentData[index].TickHeal(); - } - } - - public override void OnMissionTick(float dt) - { - while (_toAddOrRemove.Count > 0) - { - var (agent, addIfTrue) = _toAddOrRemove.Pop(); - if (addIfTrue) AddAgent(agent); - else RemoveAgent(agent); - } - - var arenaController = Mission.GetMissionBehavior(); - if (arenaController != default && arenaController.AfterPractice) return; - - int messageCount = 0; - const int infoMessageCap = 10, messageCap = 100; - foreach (var (messages, xpGains) in ParallelEnumerable.Range(0, _agentIndexPairs.Count).Select(x => _agentData[x].AttemptRegeneration(dt, _settings))) - { - if (messages != null) - for (; messageCount < messageCap; ++messageCount) - if (messages.Count > 0) - { - var message = messages.Dequeue(); - if (messageCount < infoMessageCap) InformationManager.DisplayMessage(new InformationMessage(message)); - Debug.Print(message); - } - if (xpGains != null) - { - while (xpGains.Count > 0) - { - var (hero, xp) = xpGains.Pop(); - if (_heroXpGainPairs.ContainsKey(hero)) - _heroXpGainPairs[hero] = _heroXpGainPairs[hero] + xp; - else _heroXpGainPairs[hero] = xp; - } - } - } - } - - private void AddAgent(Agent agent) - { - EnsureCapacity(); - int index = _agentIndexPairs.Count; - _agentIndexPairs[agent] = index; - _agentData[index] = new BattleRegenAgentData(agent); - - if (_settings.Debug) - Debug.Print($"[BattleRegen] agent {agent.Name} registered"); - } - - private void RemoveAgent(Agent agent) - { - if (_agentIndexPairs.TryGetValue(agent, out var index)) - { - _agentIndexPairs.Remove(agent); - ref var dataRef = ref _agentData[index]; - // since index < Count always, after removal from dictionary index <= Count always. - // we do not need to swap if index == Count (since the last item removed is the last added) - // otherwise we swap and update accordingly - var lastIndex = _agentIndexPairs.Count; - if (index < lastIndex) - { - // swap with last item - ref var lastDataRef = ref _agentData[lastIndex]; - dataRef = lastDataRef; - lastDataRef = default; - _agentIndexPairs[dataRef.Agent] = index; - } - else dataRef = default; - - if (_settings.Debug) - Debug.Print($"[BattleRegen] agent {agent.Name} unregistered"); - } - } - - protected override void OnEndMission() - { - base.OnEndMission(); - - foreach (var kv in _heroXpGainPairs) - { - var (hero, xp) = (kv.Key, kv.Value); - try - { - if (hero != default) - { - hero.AddSkillXp(DefaultSkills.Medicine, xp); - if (_settings.Debug) - Debug.Print($"[BattleRegeneration] hero {hero.Name} has received {xp} xp from battle"); - } - } - catch (Exception e) - { - Debug.Print($"[BattleRegeneration] An error occurred attempting to add XP to a hero.\n{e}"); - } - } - } - } -} diff --git a/BattleRegen/SubModule.cs b/BattleRegen/SubModule.cs index b7766ad..c510ee4 100644 --- a/BattleRegen/SubModule.cs +++ b/BattleRegen/SubModule.cs @@ -27,7 +27,7 @@ protected override void OnBeforeInitialModuleScreenSetAsRoot() public override void OnMissionBehaviorInitialize(Mission mission) { base.OnMissionBehaviorInitialize(mission); - mission.AddMissionBehavior(new BattleRegenerationBehavior()); + mission.AddMissionBehavior(new BattleRegenMissionBehavior()); } } diff --git a/SubModule.xml b/SubModule.xml index 7520440..dc36e15 100644 --- a/SubModule.xml +++ b/SubModule.xml @@ -1,7 +1,7 @@ - +