From 862bd523e3c485f2bf9025cb2be3a7aca526588c Mon Sep 17 00:00:00 2001 From: ajkroeg <50463892+ajkroeg@users.noreply.github.com> Date: Tue, 11 Jul 2023 19:35:18 -0400 Subject: [PATCH] resharper reorganize, dump evasion on airlift pickup/drop off, make evasion match carrier immediately, and hopefully stop players from being able to indirect tab-target mounted BA --- .../StrategicOperations/Framework/AI_Utils.cs | 717 +-- .../Framework/AirliftUtils.cs | 877 ++-- .../Framework/BattleArmorUtils.cs | 1973 ++++---- .../StrategicOperations/Framework/Classes.cs | 1597 +++---- .../Framework/DropPodSpawn.cs | 68 +- .../StrategicOperations/Framework/ModState.cs | 133 +- .../Framework/ResupplyUtils.cs | 101 +- .../Framework/SpawnUtils.cs | 24 +- .../Framework/TB_FlyAwaySequence.cs | 51 +- .../Framework/TB_StrafeSequence.cs | 63 +- .../StrategicOperations/Framework/Utils.cs | 1116 ++--- .../StrategicOperations/ModInit.cs | 199 +- .../Patches/AIM_ButNotReally.cs | 96 +- .../Patches/AI_DEBUG_Patches.cs | 50 +- .../Patches/AI_InfluenceMapFactorPatches.cs | 42 +- .../StrategicOperations/Patches/AI_Patches.cs | 499 +- .../Patches/BattleArmorPatches.cs | 3275 ++++++------- .../Patches/DirtyDebugPatches.cs | 5 +- .../Patches/SelectionPatches.cs | 650 +-- .../Patches/SimGamePatches.cs | 2 +- .../Patches/StrategicOperationsPatches.cs | 4144 +++++++++-------- .../StrategicOperations.csproj | 4 +- 22 files changed, 7943 insertions(+), 7743 deletions(-) diff --git a/StrategicOperations/StrategicOperations/Framework/AI_Utils.cs b/StrategicOperations/StrategicOperations/Framework/AI_Utils.cs index 9498831..af440c4 100644 --- a/StrategicOperations/StrategicOperations/Framework/AI_Utils.cs +++ b/StrategicOperations/StrategicOperations/Framework/AI_Utils.cs @@ -11,66 +11,349 @@ namespace StrategicOperations.Framework { public static class AI_Utils { - public static bool IsPositionWithinAnAirstrike(AbstractActor unit, Vector3 position) + public static string AssignRandomSpawnAsset(Ability ability, string factionName, out int waves) { - foreach (var pendingStrike in ModState.PendingStrafeWaves.Values) + var dm = UnityGameInstance.BattleTechGame.DataManager; + + if (!string.IsNullOrEmpty(ability.Def.ActorResource)) { - foreach (var rectangle in pendingStrike.FootPrintRects) + if (ModState.CachedFactionCommandBeacons.ContainsKey(ability.Def.Id)) { - ModInit.modLog?.Trace?.Write($"[IsPositionWithinAnAirstrike] position {position} is inside an incoming airstrike. ohnos."); - if (rectangle.Contains(position)) return true; + if (ModState.CachedFactionCommandBeacons[ability.Def.Id].ContainsKey(factionName)) + { + var beaconsToCheck = + ModState.CachedFactionCommandBeacons[ability.Def.Id] + [factionName]; + var chosen = beaconsToCheck.GetRandomElement(); + waves = chosen.StrafeWaves; + ModInit.modLog?.Trace?.Write($"Chose {chosen} for this activation."); + + LoadRequest loadRequest = dm.CreateLoadRequest(); + if (chosen.UnitDefID.StartsWith("mechdef_")) + { + ModInit.modLog?.Trace?.Write($"Added loadrequest for MechDef: {chosen.UnitDefID}"); + loadRequest.AddBlindLoadRequest(BattleTechResourceType.MechDef, chosen.UnitDefID); + } + else if (chosen.UnitDefID.StartsWith("vehicledef_")) + { + ModInit.modLog?.Trace?.Write($"Added loadrequest for VehicleDef: {chosen.UnitDefID}"); + loadRequest.AddBlindLoadRequest(BattleTechResourceType.VehicleDef, chosen.UnitDefID); + } + else if (chosen.UnitDefID.StartsWith("turretdef_")) + { + ModInit.modLog?.Trace?.Write($"Added loadrequest for TurretDef: {chosen.UnitDefID}"); + loadRequest.AddBlindLoadRequest(BattleTechResourceType.TurretDef, chosen.UnitDefID); + } + loadRequest.ProcessRequests(1000U); + + return chosen.UnitDefID; + } + + ModInit.modLog?.Trace?.Write($"No setting in AI_FactionBeacons for {ability.Def.Id} and {factionName}, using only default {ability.Def.ActorResource}"); + waves = ModInit.modSettings.strafeWaves; + return ability.Def.ActorResource; + } + + ModInit.modLog?.Trace?.Write($"No setting in AI_FactionBeacons for {ability.Def.Id} and {factionName}, using only default {ability.Def.ActorResource}"); + waves = ModInit.modSettings.strafeWaves; + return ability.Def.ActorResource; + } + waves = 0; + return ""; + } + + public static float CalcExpectedDamage(AbstractActor actor, string attackerResource) + { + if (attackerResource.StartsWith("mechdef_")) + { + actor.Combat.DataManager.MechDefs.TryGet(attackerResource, out MechDef attacker); + attacker.Refresh(); + var potentialRegDamage = 0f; + var potentialHeatDamage = 0f; + var potentialStabDamage = 0f; + foreach (var weapon in attacker.Inventory.Where(x=>x.ComponentDefType == ComponentType.Weapon)) + { + if (!(weapon.Def is WeaponDef weaponDef)) continue; + potentialRegDamage += weaponDef.Damage; + potentialHeatDamage += weaponDef.HeatDamage; + potentialStabDamage += weaponDef.Instability; + } + + var finalDamage = potentialRegDamage + potentialHeatDamage + potentialStabDamage; + return finalDamage; + } + else if (attackerResource.StartsWith("vehicledef_")) + { + actor.Combat.DataManager.VehicleDefs.TryGet(attackerResource, out VehicleDef attacker); + attacker.Refresh(); + var potentialRegDamage = 0f; + var potentialHeatDamage = 0f; + var potentialStabDamage = 0f; + foreach (var weapon in attacker.Inventory.Where(x=>x.ComponentDefType == ComponentType.Weapon)) + { + if (!(weapon.Def is WeaponDef weaponDef)) continue; + potentialRegDamage += weaponDef.Damage; + potentialHeatDamage += weaponDef.HeatDamage; + potentialStabDamage += weaponDef.Instability; + } + + var finalDamage = potentialRegDamage + potentialHeatDamage + potentialStabDamage; + return finalDamage; + } + else if (attackerResource.StartsWith("turretdef_")) + { + actor.Combat.DataManager.TurretDefs.TryGet(attackerResource, out TurretDef attacker); + attacker.Refresh(); + var potentialRegDamage = 0f; + var potentialHeatDamage = 0f; + var potentialStabDamage = 0f; + foreach (var weapon in attacker.Inventory.Where(x=>x.ComponentDefType == ComponentType.Weapon)) + { + if (!(weapon.Def is WeaponDef weaponDef)) continue; + potentialRegDamage += weaponDef.Damage; + potentialHeatDamage += weaponDef.HeatDamage; + potentialStabDamage += weaponDef.Instability; } + + var finalDamage = potentialRegDamage + potentialHeatDamage + potentialStabDamage; + return finalDamage; } + return 0f; + } + + + public static bool CanSpawn(AbstractActor actor, out Ability ability) + { + var _ability = + actor.ComponentAbilities.FirstOrDefault(x => x.Def.specialRules == AbilityDef.SpecialRules.SpawnTurret); + if (_ability != null) + { + if (_ability.IsAvailable) + { + ability = _ability; + return true; + } + } + ability = default(Ability); return false; } - public static void ProcessAIBeaconWeights(this Classes.ConfigOptions.AI_FactionCommandAbilitySetting BeaconWeights, DataManager dm, - string factionID, string abilityName) + + public static bool CanStrafe(AbstractActor actor, out Ability ability) { - foreach (var beaconType in BeaconWeights.AvailableBeacons) + var _ability = + actor.ComponentAbilities.FirstOrDefault(x => x.Def.specialRules == AbilityDef.SpecialRules.Strafe); + if (_ability != null) { - if (beaconType.UnitDefID.StartsWith("mechdef_")) + if (_ability.IsAvailable) { - if (dm.Exists(BattleTechResourceType.MechDef, beaconType.UnitDefID) || beaconType.UnitDefID == "BEACON_EMPTY") + ability = _ability; + return true; + } + } + ability = default(Ability); + return false; + } + + + //this is still spawning way out of ranhe for some reason + public static int EvaluateSpawn(AbstractActor actor, out Ability ability, out Vector3 spawnpoint, out Vector3 rotationVector) + { + spawnpoint = new Vector3(); + rotationVector = new Vector3(); + if (!CanSpawn(actor, out ability)) return 0; + + if (ability != null) + { + var assetID = AssignRandomSpawnAsset(ability, actor.team.FactionValue.Name, out var waves); + + var asset = actor.Combat.DataManager.FetchUnitFromDataManager(assetID); + + Classes.AI_SpawnBehavior spawnBehavior = new Classes.AI_SpawnBehavior(); + if (asset is MechDef mech) + { + foreach (var behavior in ModInit.modSettings.AI_SpawnBehavior) { - ModInit.modLog?.Trace?.Write( - $"[ProcessAIBeaconWeights - MechDef] Processing spawn weights for {beaconType.UnitDefID} and weight {beaconType.Weight}"); - for (int i = 0; i < beaconType.Weight; i++) + if (mech.MechTags.Contains(behavior.Tag)) { - ModState.CachedFactionCommandBeacons[BeaconWeights.AbilityDefID][factionID].Add(beaconType); - ModInit.modLog?.Trace?.Write( - $"[ProcessAIBeaconWeights - MechDef] spawn list has {ModState.CachedFactionCommandBeacons[BeaconWeights.AbilityDefID][factionID].Count} entries"); + spawnBehavior = behavior; + goto behaviorEvalFinished; } } } - if (beaconType.UnitDefID.StartsWith("vehicledef_")) + else if (asset is VehicleDef vehicle) { - if (dm.Exists(BattleTechResourceType.VehicleDef, beaconType.UnitDefID) || beaconType.UnitDefID == "BEACON_EMPTY") + foreach (var behavior in ModInit.modSettings.AI_SpawnBehavior) { - ModInit.modLog?.Trace?.Write( - $"[ProcessAIBeaconWeights - VehicleDef] Processing spawn weights for {beaconType.UnitDefID} and weight {beaconType.Weight}"); - for (int i = 0; i < beaconType.Weight; i++) + if (vehicle.VehicleTags.Contains(behavior.Tag)) { - ModState.CachedFactionCommandBeacons[BeaconWeights.AbilityDefID][factionID].Add(beaconType); - ModInit.modLog?.Trace?.Write( - $"[ProcessAIBeaconWeights - VehicleDef] spawn list has {ModState.CachedFactionCommandBeacons[BeaconWeights.AbilityDefID][factionID].Count} entries"); + spawnBehavior = behavior; + goto behaviorEvalFinished; } } } - if (beaconType.UnitDefID.StartsWith("turretdef_")) + else if (asset is TurretDef turret) { - if (dm.Exists(BattleTechResourceType.TurretDef, beaconType.UnitDefID) || beaconType.UnitDefID == "BEACON_EMPTY") + foreach (var behavior in ModInit.modSettings.AI_SpawnBehavior) { - ModInit.modLog?.Trace?.Write( - $"[ProcessAIBeaconWeights - TurretDef] Processing spawn weights for {beaconType.UnitDefID} and weight {beaconType.Weight}"); - for (int i = 0; i < beaconType.Weight; i++) + if (turret.TurretTags.Contains(behavior.Tag)) { - ModState.CachedFactionCommandBeacons[BeaconWeights.AbilityDefID][factionID].Add(beaconType); - ModInit.modLog?.Trace?.Write( - $"[ProcessAIBeaconWeights - TurretDef] spawn list has {ModState.CachedFactionCommandBeacons[BeaconWeights.AbilityDefID][factionID].Count} entries"); + spawnBehavior = behavior; + goto behaviorEvalFinished; } } } + + behaviorEvalFinished: + var maxRange = ability.Def.IntParam2; + //var enemyActors = new List(actor.Combat.AllEnemies); + var enemyActors = actor.team.VisibilityCache.GetAllDetectedEnemies(actor); + ModInit.modLog?.Trace?.Write( + $"found {enemyActors.Count} to eval"); + enemyActors.RemoveAll(x => x.WasDespawned || x.IsDead || x.IsFlaggedForDeath || x.WasEjected); + ModInit.modLog?.Trace?.Write( + $"found {enemyActors.Count} after eval"); + var avgCenter = new Vector3(); + var theCenter = new Vector3(); + var orientation = new Vector3(); + var finalOrientation = new Vector3(); + + if (spawnBehavior.Behavior == "AMBUSH") + { + var count = 0; + var targetEnemy = actor.GetClosestDetectedEnemy(actor.CurrentPosition); + + if (targetEnemy != null) + { + ModInit.modLog?.Trace?.Write( + $"Target enemy {targetEnemy.DisplayName}"); + theCenter = targetEnemy.CurrentPosition; + count = 1; + } + + if (Vector3.Distance(actor.CurrentPosition, theCenter) > maxRange) + { + theCenter = Utils.LerpByDistance(actor.CurrentPosition, theCenter, maxRange); + ModInit.modLog?.Trace?.Write( + $"Chosen point is {Vector3.Distance(actor.CurrentPosition, theCenter)} from source after LerpByDist"); + } + else + { + ModInit.modLog?.Trace?.Write( + $"Chosen point is {Vector3.Distance(actor.CurrentPosition, theCenter)} from source, should be < {maxRange}"); + } + + theCenter = theCenter.FetchRandomAdjacentHexFromVector(actor, targetEnemy, spawnBehavior.MinRange, maxRange);//SpawnUtils.FindValidSpawn(targetEnemy, actor, spawnBehavior.MinRange, maxRange); + + finalOrientation = targetEnemy.CurrentPosition - theCenter; + + theCenter.y = actor.Combat.MapMetaData.GetLerpedHeightAt(theCenter); + spawnpoint = theCenter; + rotationVector = finalOrientation; + return count; + } + + if (spawnBehavior.Behavior == "REINFORCE") + { + var friendlyActors = actor.team.VisibilityCache.GetAllFriendlies(actor); + var center = new Vector3(0, 0, 0); + var count = 0; + foreach (var friendly in friendlyActors) + { + center += friendly.CurrentPosition; + count++; + ModInit.modLog?.Trace?.Write( + $"friendlyActors count = {count}"); + } + + if (count == 0) + { + ModInit.modLog?.Trace?.Write( + $"FINAL friendlyActors count = {count}"); + theCenter = actor.CurrentPosition; + finalOrientation = orientation; + goto skip; + } + + avgCenter = center / count; + + if (Vector3.Distance(actor.CurrentPosition, avgCenter) > maxRange) + { + theCenter = Utils.LerpByDistance(actor.CurrentPosition, avgCenter, maxRange); + ModInit.modLog?.Trace?.Write( + $"Chosen point is {Vector3.Distance(actor.CurrentPosition, theCenter)} from source after LerpByDist"); + } + else + { + theCenter = avgCenter; + ModInit.modLog?.Trace?.Write( + $"Chosen point is {Vector3.Distance(actor.CurrentPosition, theCenter)} from source, should be < {maxRange}"); + } + + var targetFriendly = Utils.GetClosestDetectedFriendly(theCenter, actor); + var closestEnemy = actor.GetClosestDetectedEnemy(theCenter); + + //theCenter = SpawnUtils.FindValidSpawn(targetFriendly, actor, spawnBehavior.MinRange, maxRange); + theCenter = theCenter.FetchRandomAdjacentHexFromVector(actor, targetFriendly, spawnBehavior.MinRange, maxRange);//SpawnUtils.FindValidSpawn(targetEnemy, actor, spawnBehavior.MinRange, maxRange); + + finalOrientation = closestEnemy.CurrentPosition - theCenter; + + skip: + theCenter.y = actor.Combat.MapMetaData.GetLerpedHeightAt(theCenter); + spawnpoint = theCenter; + rotationVector = finalOrientation; + return count; + } + + if (spawnBehavior.Behavior == "DEFAULT" || spawnBehavior.Behavior == "BRAWLER") + { + var center = new Vector3(); + var count = 0; + foreach (var enemy in enemyActors) + { + center += enemy.CurrentPosition; + count++; + ModInit.modLog?.Trace?.Write( + $"enemyActors count = {count}"); + } + + if (count == 0) + { + ModInit.modLog?.Trace?.Write( + $"FINAL enemyActors count = {count}"); + theCenter = actor.CurrentPosition; + finalOrientation = orientation; + goto skip; + } + + avgCenter = center / count; + + if (Vector3.Distance(actor.CurrentPosition, avgCenter) > maxRange) + { + theCenter = Utils.LerpByDistance(actor.CurrentPosition, avgCenter, maxRange); + ModInit.modLog?.Trace?.Write( + $"Chosen point is {Vector3.Distance(actor.CurrentPosition, theCenter)} from source after LerpByDist"); + } + else + { + theCenter = avgCenter; + ModInit.modLog?.Trace?.Write( + $"Chosen point is {Vector3.Distance(actor.CurrentPosition, theCenter)} from source, should be < {maxRange}"); + } + + var closestEnemy = actor.GetClosestDetectedEnemy(theCenter); + + //theCenter = SpawnUtils.FindValidSpawn(closestEnemy, actor, spawnBehavior.MinRange, maxRange); + theCenter = theCenter.FetchRandomAdjacentHexFromVector(actor, closestEnemy, spawnBehavior.MinRange, maxRange);//SpawnUtils.FindValidSpawn(targetEnemy, actor, spawnBehavior.MinRange, maxRange); + finalOrientation = closestEnemy.CurrentPosition = theCenter; + + skip: + theCenter.y = actor.Combat.MapMetaData.GetLerpedHeightAt(theCenter); + spawnpoint = theCenter; + rotationVector = finalOrientation; + return count; + } } + return 0; } public static int EvaluateStrafing(AbstractActor actor, out Ability ability, out Vector3 startpoint, out Vector3 endpoint, out AbstractActor targetUnit) @@ -445,130 +728,67 @@ public static void GenerateAIStrategicAbilities(AbstractActor unit) } } - public static string AssignRandomSpawnAsset(Ability ability, string factionName, out int waves) - { - var dm = UnityGameInstance.BattleTechGame.DataManager; - - if (!string.IsNullOrEmpty(ability.Def.ActorResource)) - { - if (ModState.CachedFactionCommandBeacons.ContainsKey(ability.Def.Id)) - { - if (ModState.CachedFactionCommandBeacons[ability.Def.Id].ContainsKey(factionName)) - { - var beaconsToCheck = - ModState.CachedFactionCommandBeacons[ability.Def.Id] - [factionName]; - var chosen = beaconsToCheck.GetRandomElement(); - waves = chosen.StrafeWaves; - ModInit.modLog?.Trace?.Write($"Chose {chosen} for this activation."); - - LoadRequest loadRequest = dm.CreateLoadRequest(); - if (chosen.UnitDefID.StartsWith("mechdef_")) - { - ModInit.modLog?.Trace?.Write($"Added loadrequest for MechDef: {chosen.UnitDefID}"); - loadRequest.AddBlindLoadRequest(BattleTechResourceType.MechDef, chosen.UnitDefID); - } - else if (chosen.UnitDefID.StartsWith("vehicledef_")) - { - ModInit.modLog?.Trace?.Write($"Added loadrequest for VehicleDef: {chosen.UnitDefID}"); - loadRequest.AddBlindLoadRequest(BattleTechResourceType.VehicleDef, chosen.UnitDefID); - } - else if (chosen.UnitDefID.StartsWith("turretdef_")) - { - ModInit.modLog?.Trace?.Write($"Added loadrequest for TurretDef: {chosen.UnitDefID}"); - loadRequest.AddBlindLoadRequest(BattleTechResourceType.TurretDef, chosen.UnitDefID); - } - loadRequest.ProcessRequests(1000U); - - return chosen.UnitDefID; - } - - ModInit.modLog?.Trace?.Write($"No setting in AI_FactionBeacons for {ability.Def.Id} and {factionName}, using only default {ability.Def.ActorResource}"); - waves = ModInit.modSettings.strafeWaves; - return ability.Def.ActorResource; - } - - ModInit.modLog?.Trace?.Write($"No setting in AI_FactionBeacons for {ability.Def.Id} and {factionName}, using only default {ability.Def.ActorResource}"); - waves = ModInit.modSettings.strafeWaves; - return ability.Def.ActorResource; - } - waves = 0; - return ""; - } - - public static bool CanStrafe(AbstractActor actor, out Ability ability) + public static bool IsPositionWithinAnAirstrike(AbstractActor unit, Vector3 position) { - var _ability = - actor.ComponentAbilities.FirstOrDefault(x => x.Def.specialRules == AbilityDef.SpecialRules.Strafe); - if (_ability != null) + foreach (var pendingStrike in ModState.PendingStrafeWaves.Values) { - if (_ability.IsAvailable) + foreach (var rectangle in pendingStrike.FootPrintRects) { - ability = _ability; - return true; + ModInit.modLog?.Trace?.Write($"[IsPositionWithinAnAirstrike] position {position} is inside an incoming airstrike. ohnos."); + if (rectangle.Contains(position)) return true; } } - ability = default(Ability); return false; } - public static float CalcExpectedDamage(AbstractActor actor, string attackerResource) + public static void ProcessAIBeaconWeights(this Classes.ConfigOptions.AI_FactionCommandAbilitySetting BeaconWeights, DataManager dm, + string factionID, string abilityName) { - if (attackerResource.StartsWith("mechdef_")) + foreach (var beaconType in BeaconWeights.AvailableBeacons) { - actor.Combat.DataManager.MechDefs.TryGet(attackerResource, out MechDef attacker); - attacker.Refresh(); - var potentialRegDamage = 0f; - var potentialHeatDamage = 0f; - var potentialStabDamage = 0f; - foreach (var weapon in attacker.Inventory.Where(x=>x.ComponentDefType == ComponentType.Weapon)) + if (beaconType.UnitDefID.StartsWith("mechdef_")) { - if (!(weapon.Def is WeaponDef weaponDef)) continue; - potentialRegDamage += weaponDef.Damage; - potentialHeatDamage += weaponDef.HeatDamage; - potentialStabDamage += weaponDef.Instability; + if (dm.Exists(BattleTechResourceType.MechDef, beaconType.UnitDefID) || beaconType.UnitDefID == "BEACON_EMPTY") + { + ModInit.modLog?.Trace?.Write( + $"[ProcessAIBeaconWeights - MechDef] Processing spawn weights for {beaconType.UnitDefID} and weight {beaconType.Weight}"); + for (int i = 0; i < beaconType.Weight; i++) + { + ModState.CachedFactionCommandBeacons[BeaconWeights.AbilityDefID][factionID].Add(beaconType); + ModInit.modLog?.Trace?.Write( + $"[ProcessAIBeaconWeights - MechDef] spawn list has {ModState.CachedFactionCommandBeacons[BeaconWeights.AbilityDefID][factionID].Count} entries"); + } + } } - - var finalDamage = potentialRegDamage + potentialHeatDamage + potentialStabDamage; - return finalDamage; - } - else if (attackerResource.StartsWith("vehicledef_")) - { - actor.Combat.DataManager.VehicleDefs.TryGet(attackerResource, out VehicleDef attacker); - attacker.Refresh(); - var potentialRegDamage = 0f; - var potentialHeatDamage = 0f; - var potentialStabDamage = 0f; - foreach (var weapon in attacker.Inventory.Where(x=>x.ComponentDefType == ComponentType.Weapon)) + if (beaconType.UnitDefID.StartsWith("vehicledef_")) { - if (!(weapon.Def is WeaponDef weaponDef)) continue; - potentialRegDamage += weaponDef.Damage; - potentialHeatDamage += weaponDef.HeatDamage; - potentialStabDamage += weaponDef.Instability; + if (dm.Exists(BattleTechResourceType.VehicleDef, beaconType.UnitDefID) || beaconType.UnitDefID == "BEACON_EMPTY") + { + ModInit.modLog?.Trace?.Write( + $"[ProcessAIBeaconWeights - VehicleDef] Processing spawn weights for {beaconType.UnitDefID} and weight {beaconType.Weight}"); + for (int i = 0; i < beaconType.Weight; i++) + { + ModState.CachedFactionCommandBeacons[BeaconWeights.AbilityDefID][factionID].Add(beaconType); + ModInit.modLog?.Trace?.Write( + $"[ProcessAIBeaconWeights - VehicleDef] spawn list has {ModState.CachedFactionCommandBeacons[BeaconWeights.AbilityDefID][factionID].Count} entries"); + } + } } - - var finalDamage = potentialRegDamage + potentialHeatDamage + potentialStabDamage; - return finalDamage; - } - else if (attackerResource.StartsWith("turretdef_")) - { - actor.Combat.DataManager.TurretDefs.TryGet(attackerResource, out TurretDef attacker); - attacker.Refresh(); - var potentialRegDamage = 0f; - var potentialHeatDamage = 0f; - var potentialStabDamage = 0f; - foreach (var weapon in attacker.Inventory.Where(x=>x.ComponentDefType == ComponentType.Weapon)) + if (beaconType.UnitDefID.StartsWith("turretdef_")) { - if (!(weapon.Def is WeaponDef weaponDef)) continue; - potentialRegDamage += weaponDef.Damage; - potentialHeatDamage += weaponDef.HeatDamage; - potentialStabDamage += weaponDef.Instability; + if (dm.Exists(BattleTechResourceType.TurretDef, beaconType.UnitDefID) || beaconType.UnitDefID == "BEACON_EMPTY") + { + ModInit.modLog?.Trace?.Write( + $"[ProcessAIBeaconWeights - TurretDef] Processing spawn weights for {beaconType.UnitDefID} and weight {beaconType.Weight}"); + for (int i = 0; i < beaconType.Weight; i++) + { + ModState.CachedFactionCommandBeacons[BeaconWeights.AbilityDefID][factionID].Add(beaconType); + ModInit.modLog?.Trace?.Write( + $"[ProcessAIBeaconWeights - TurretDef] spawn list has {ModState.CachedFactionCommandBeacons[BeaconWeights.AbilityDefID][factionID].Count} entries"); + } + } } - - var finalDamage = potentialRegDamage + potentialHeatDamage + potentialStabDamage; - return finalDamage; } - return 0f; } public static int TargetsForStrafe(AbstractActor actor, Ability ability, out Vector3 startPos, out Vector3 endPos, out AbstractActor targetUnit) //switch to Icombatant @@ -677,224 +897,5 @@ public static int TargetsForStrafe(AbstractActor actor, Ability ability, out Vec targetUnit = null; return 0; } - - - public static bool CanSpawn(AbstractActor actor, out Ability ability) - { - var _ability = - actor.ComponentAbilities.FirstOrDefault(x => x.Def.specialRules == AbilityDef.SpecialRules.SpawnTurret); - if (_ability != null) - { - if (_ability.IsAvailable) - { - ability = _ability; - return true; - } - } - ability = default(Ability); - return false; - } - - - //this is still spawning way out of ranhe for some reason - public static int EvaluateSpawn(AbstractActor actor, out Ability ability, out Vector3 spawnpoint, out Vector3 rotationVector) - { - spawnpoint = new Vector3(); - rotationVector = new Vector3(); - if (!CanSpawn(actor, out ability)) return 0; - - if (ability != null) - { - var assetID = AssignRandomSpawnAsset(ability, actor.team.FactionValue.Name, out var waves); - - var asset = actor.Combat.DataManager.FetchUnitFromDataManager(assetID); - - Classes.AI_SpawnBehavior spawnBehavior = new Classes.AI_SpawnBehavior(); - if (asset is MechDef mech) - { - foreach (var behavior in ModInit.modSettings.AI_SpawnBehavior) - { - if (mech.MechTags.Contains(behavior.Tag)) - { - spawnBehavior = behavior; - goto behaviorEvalFinished; - } - } - } - else if (asset is VehicleDef vehicle) - { - foreach (var behavior in ModInit.modSettings.AI_SpawnBehavior) - { - if (vehicle.VehicleTags.Contains(behavior.Tag)) - { - spawnBehavior = behavior; - goto behaviorEvalFinished; - } - } - } - else if (asset is TurretDef turret) - { - foreach (var behavior in ModInit.modSettings.AI_SpawnBehavior) - { - if (turret.TurretTags.Contains(behavior.Tag)) - { - spawnBehavior = behavior; - goto behaviorEvalFinished; - } - } - } - - behaviorEvalFinished: - var maxRange = ability.Def.IntParam2; - //var enemyActors = new List(actor.Combat.AllEnemies); - var enemyActors = actor.team.VisibilityCache.GetAllDetectedEnemies(actor); - ModInit.modLog?.Trace?.Write( - $"found {enemyActors.Count} to eval"); - enemyActors.RemoveAll(x => x.WasDespawned || x.IsDead || x.IsFlaggedForDeath || x.WasEjected); - ModInit.modLog?.Trace?.Write( - $"found {enemyActors.Count} after eval"); - var avgCenter = new Vector3(); - var theCenter = new Vector3(); - var orientation = new Vector3(); - var finalOrientation = new Vector3(); - - if (spawnBehavior.Behavior == "AMBUSH") - { - var count = 0; - var targetEnemy = actor.GetClosestDetectedEnemy(actor.CurrentPosition); - - if (targetEnemy != null) - { - ModInit.modLog?.Trace?.Write( - $"Target enemy {targetEnemy.DisplayName}"); - theCenter = targetEnemy.CurrentPosition; - count = 1; - } - - if (Vector3.Distance(actor.CurrentPosition, theCenter) > maxRange) - { - theCenter = Utils.LerpByDistance(actor.CurrentPosition, theCenter, maxRange); - ModInit.modLog?.Trace?.Write( - $"Chosen point is {Vector3.Distance(actor.CurrentPosition, theCenter)} from source after LerpByDist"); - } - else - { - ModInit.modLog?.Trace?.Write( - $"Chosen point is {Vector3.Distance(actor.CurrentPosition, theCenter)} from source, should be < {maxRange}"); - } - - theCenter = theCenter.FetchRandomAdjacentHexFromVector(actor, targetEnemy, spawnBehavior.MinRange, maxRange);//SpawnUtils.FindValidSpawn(targetEnemy, actor, spawnBehavior.MinRange, maxRange); - - finalOrientation = targetEnemy.CurrentPosition - theCenter; - - theCenter.y = actor.Combat.MapMetaData.GetLerpedHeightAt(theCenter); - spawnpoint = theCenter; - rotationVector = finalOrientation; - return count; - } - - if (spawnBehavior.Behavior == "REINFORCE") - { - var friendlyActors = actor.team.VisibilityCache.GetAllFriendlies(actor); - var center = new Vector3(0, 0, 0); - var count = 0; - foreach (var friendly in friendlyActors) - { - center += friendly.CurrentPosition; - count++; - ModInit.modLog?.Trace?.Write( - $"friendlyActors count = {count}"); - } - - if (count == 0) - { - ModInit.modLog?.Trace?.Write( - $"FINAL friendlyActors count = {count}"); - theCenter = actor.CurrentPosition; - finalOrientation = orientation; - goto skip; - } - - avgCenter = center / count; - - if (Vector3.Distance(actor.CurrentPosition, avgCenter) > maxRange) - { - theCenter = Utils.LerpByDistance(actor.CurrentPosition, avgCenter, maxRange); - ModInit.modLog?.Trace?.Write( - $"Chosen point is {Vector3.Distance(actor.CurrentPosition, theCenter)} from source after LerpByDist"); - } - else - { - theCenter = avgCenter; - ModInit.modLog?.Trace?.Write( - $"Chosen point is {Vector3.Distance(actor.CurrentPosition, theCenter)} from source, should be < {maxRange}"); - } - - var targetFriendly = Utils.GetClosestDetectedFriendly(theCenter, actor); - var closestEnemy = actor.GetClosestDetectedEnemy(theCenter); - - //theCenter = SpawnUtils.FindValidSpawn(targetFriendly, actor, spawnBehavior.MinRange, maxRange); - theCenter = theCenter.FetchRandomAdjacentHexFromVector(actor, targetFriendly, spawnBehavior.MinRange, maxRange);//SpawnUtils.FindValidSpawn(targetEnemy, actor, spawnBehavior.MinRange, maxRange); - - finalOrientation = closestEnemy.CurrentPosition - theCenter; - - skip: - theCenter.y = actor.Combat.MapMetaData.GetLerpedHeightAt(theCenter); - spawnpoint = theCenter; - rotationVector = finalOrientation; - return count; - } - - if (spawnBehavior.Behavior == "DEFAULT" || spawnBehavior.Behavior == "BRAWLER") - { - var center = new Vector3(); - var count = 0; - foreach (var enemy in enemyActors) - { - center += enemy.CurrentPosition; - count++; - ModInit.modLog?.Trace?.Write( - $"enemyActors count = {count}"); - } - - if (count == 0) - { - ModInit.modLog?.Trace?.Write( - $"FINAL enemyActors count = {count}"); - theCenter = actor.CurrentPosition; - finalOrientation = orientation; - goto skip; - } - - avgCenter = center / count; - - if (Vector3.Distance(actor.CurrentPosition, avgCenter) > maxRange) - { - theCenter = Utils.LerpByDistance(actor.CurrentPosition, avgCenter, maxRange); - ModInit.modLog?.Trace?.Write( - $"Chosen point is {Vector3.Distance(actor.CurrentPosition, theCenter)} from source after LerpByDist"); - } - else - { - theCenter = avgCenter; - ModInit.modLog?.Trace?.Write( - $"Chosen point is {Vector3.Distance(actor.CurrentPosition, theCenter)} from source, should be < {maxRange}"); - } - - var closestEnemy = actor.GetClosestDetectedEnemy(theCenter); - - //theCenter = SpawnUtils.FindValidSpawn(closestEnemy, actor, spawnBehavior.MinRange, maxRange); - theCenter = theCenter.FetchRandomAdjacentHexFromVector(actor, closestEnemy, spawnBehavior.MinRange, maxRange);//SpawnUtils.FindValidSpawn(targetEnemy, actor, spawnBehavior.MinRange, maxRange); - finalOrientation = closestEnemy.CurrentPosition = theCenter; - - skip: - theCenter.y = actor.Combat.MapMetaData.GetLerpedHeightAt(theCenter); - spawnpoint = theCenter; - rotationVector = finalOrientation; - return count; - } - } - return 0; - } } } \ No newline at end of file diff --git a/StrategicOperations/StrategicOperations/Framework/AirliftUtils.cs b/StrategicOperations/StrategicOperations/Framework/AirliftUtils.cs index 3ac1bb5..bd12646 100644 --- a/StrategicOperations/StrategicOperations/Framework/AirliftUtils.cs +++ b/StrategicOperations/StrategicOperations/Framework/AirliftUtils.cs @@ -10,378 +10,168 @@ namespace StrategicOperations.Framework { public static class AirliftUtils { - public static bool HasAirliftedUnits(this AbstractActor actor) - { - return ModState.AirliftTrackers.Any(x => x.Value.CarrierGUID == actor.GUID); - } - public static bool IsAirlifted(this AbstractActor actor) - { - return ModState.AirliftTrackers.ContainsKey(actor.GUID); - } - public static bool IsAirliftedFriendly(this AbstractActor actor) - { - return ModState.AirliftTrackers.ContainsKey(actor.GUID) && ModState.AirliftTrackers[actor.GUID].IsFriendly; - } - - public static bool IsAirliftingTargetUnit(this AbstractActor actor, AbstractActor targetActor) - { - return ModState.AirliftTrackers.ContainsKey(targetActor.GUID) && ModState.AirliftTrackers[targetActor.GUID].CarrierGUID == actor.GUID; - } - - public static bool IsAirliftedByTarget(this AbstractActor actor, AbstractActor targetActor) - { - return ModState.AirliftTrackers.ContainsKey(actor.GUID) && - ModState.AirliftTrackers[actor.GUID].CarrierGUID == targetActor.GUID; - } - - public static bool HasAirliftedFriendly(this AbstractActor actor) + public static void AttachToAirliftCarrier(this AbstractActor actor, AbstractActor carrier, bool isFriendly) { - return ModState.AirliftTrackers.Any(x => x.Value.IsFriendly && x.Value.CarrierGUID == actor.GUID); - } + if (isFriendly) + { + ModInit.modLog?.Trace?.Write($"AttachToAirliftCarrier processing on friendly."); + if (carrier is CustomMech custMech && custMech.FlyingHeight() > 1.5f) + { + //Check if actually flying unit + //CALL ATTACH CODE BUT WITHOUT SQUAD REPRESENTATION HIDING + //custMech.MountBattleArmorToChassis(squad, false); - public static bool IsAirliftedEnemy(this AbstractActor actor) - { - return ModState.AirliftTrackers.ContainsKey(actor.GUID) && !ModState.AirliftTrackers[actor.GUID].IsFriendly; - } + custMech.custGameRep.HeightController.UpSpeed = 50f; + custMech.custGameRep.HeightController.DownSpeed = -50f; - public static bool HasAirliftedEnemy(this AbstractActor actor) - { - return ModState.AirliftTrackers.Any(x => !x.Value.IsFriendly && x.Value.CarrierGUID == actor.GUID); + var attachDel = new AttachToAirliftCarrierDelegate(actor, custMech); + custMech.DropOffAnimation(attachDel.OnLandAttach, attachDel.OnRestoreHeightControl); + } + else + { + ModInit.modLog?.Trace?.Write($"AttachToAirliftCarrier call mount."); + //CALL DEFAULT ATTACH CODE + carrier.MountUnitToAirliftCarrier(actor, true); + } + } + else + { + ModInit.modLog?.Trace?.Write($"AttachToCarrier call mount."); + //CALL DEFAULT ATTACH CODE + carrier.MountUnitToAirliftCarrier(actor, false); + } } - public static bool GetOverrideCapacityMethod(this AbstractActor actor) + internal class AttachToAirliftCarrierDelegate { - return actor.StatCollection.GetValue("OverrideGlobalCapacity"); - } + public float CarrierDownSpeed = -20f; + public float CarrierUpSpeed = 5f; + public AbstractActor Actor { get; set; } + public CustomMech Carrier { get; set; } - public static bool GetCanAirliftHostiles(this AbstractActor actor) - { - return actor.StatCollection.GetValue("CanAirliftHostiles"); - } + public AttachToAirliftCarrierDelegate(AbstractActor actor, CustomMech carrier) + { + this.Actor = actor; + this.Carrier = carrier; + } - public static int GetInternalLiftCapacity(this AbstractActor actor) - { - return actor.StatCollection.GetValue("InternalLiftCapacity"); - } - public static int GetInternalLiftCapacityUsed(this AbstractActor actor) - { - return actor.StatCollection.GetValue("InternalLiftCapacityUsed"); - } - public static void ModifyUsedInternalLiftCapacity(this AbstractActor actor, int value) - { - actor.StatCollection.ModifyStat("modifyUsedInternalLiftCapacity", -1, "InternalLiftCapacityUsed", StatCollection.StatOperation.Int_Add, value); - } - public static int GetAvailableInternalLiftCapacity(this AbstractActor actor) - { - return actor.StatCollection.GetValue("InternalLiftCapacity") - actor.StatCollection.GetValue("InternalLiftCapacityUsed"); - } + public void OnLandAttach() + { + if (ModInit.modSettings.ReworkedCarrierEvasion) Carrier.DumpAllEvasivePips(); + //HIDE SQUAD REPRESENTATION + //attachTarget.MountBattleArmorToChassis(squad, true); + Carrier.MountUnitToAirliftCarrier(Actor, true); + //attachTarget.HideBattleArmorOnChassis(squad); + } - public static bool GetHasAvailableInternalLiftCapacityForTarget(this AbstractActor actor, AbstractActor targetActor) - { - if (ModInit.modSettings.AirliftCapacityByTonnage && !actor.GetOverrideCapacityMethod() || !ModInit.modSettings.AirliftCapacityByTonnage && actor.GetOverrideCapacityMethod()) + public void OnRestoreHeightControl() { - if (targetActor is Mech targetMech) + var offset = Vector3.zero; + if (Actor.IsAirlifted()) { - return actor.StatCollection.GetValue("InternalLiftCapacity") - - actor.StatCollection.GetValue("InternalLiftCapacityUsed") >= targetMech.tonnage; + offset = Vector3.down * ModState.AirliftTrackers[Actor.GUID].Offset; } - else if (targetActor is Vehicle vehicle) + Carrier.custGameRep.HeightController.UpSpeed = CarrierUpSpeed; + Carrier.custGameRep.HeightController.DownSpeed = CarrierDownSpeed; + var pos = Carrier.CurrentPosition + offset + + Vector3.up * Carrier.custGameRep.HeightController.CurrentHeight; + Actor.TeleportActor(pos); + + //Actor.GameRep.thisTransform.rotation = Quaternion.identity; + //Actor.CurrentRotation = Quaternion.identity; + if (Actor is CustomMech customMech) { - - return actor.StatCollection.GetValue("InternalLiftCapacity") - - actor.StatCollection.GetValue("InternalLiftCapacityUsed") >= vehicle.tonnage; + customMech.custGameRep.j_Root.localRotation = Quaternion.identity; } + Actor.GameRep.thisTransform.rotation = Carrier.GameRep.thisTransform.rotation; + Actor.CurrentRotation = Carrier.CurrentRotation; + //var rotate = Quaternion.LookRotation(Carrier.CurrentRotation.eulerAngles); + //Actor.GameRep.thisTransform.LookAt(rotate.eulerAngles, Vector3.up); } - return actor.StatCollection.GetValue("InternalLiftCapacity") - - actor.StatCollection.GetValue("InternalLiftCapacityUsed") >= 1; } - public static bool GetHasAvailableExternalLiftCapacityForTarget(this AbstractActor actor, AbstractActor targetActor) + public static void DetachFromAirliftCarrier(this AbstractActor actor, AbstractActor carrier, bool isFriendly) { - if (ModInit.modSettings.AirliftCapacityByTonnage && !actor.GetOverrideCapacityMethod() || !ModInit.modSettings.AirliftCapacityByTonnage && actor.GetOverrideCapacityMethod()) + if (isFriendly) { - if (targetActor is Mech targetMech) + if (carrier is CustomMech custMech && custMech.FlyingHeight() > 1.5f) { - return actor.StatCollection.GetValue("ExternalLiftCapacity") - - actor.StatCollection.GetValue("ExternalLiftCapacityUsed") >= Mathf.RoundToInt(targetMech.tonnage); + //Check if actually flying unit + //CALL ATTACH CODE BUT WITHOUT SQUAD REPRESENTATION HIDING + //custMech.DismountBA(squad, false, false, false); + custMech.custGameRep.HeightController.UpSpeed = 50f; + custMech.custGameRep.HeightController.DownSpeed = -50f; + + var detachDel = new DetachFromAirliftCarrierDelegate(actor, custMech); + custMech.DropOffAnimation(detachDel.OnLandDetach, detachDel.OnRestoreHeightControl); } - else if (targetActor is Vehicle vehicle) + else { - - return actor.StatCollection.GetValue("ExternalLiftCapacity") - - actor.StatCollection.GetValue("ExternalLiftCapacityUsed") >= Mathf.RoundToInt(vehicle.tonnage); + ModInit.modLog?.Trace?.Write($"DetachFromAirliftCarrier call DropAirliftedUnit."); + //CALL DEFAULT ATTACH CODE + carrier.DropAirliftedUnit(actor, Vector3.zero, false, false, false, true); } } - return actor.StatCollection.GetValue("ExternalLiftCapacity") - - actor.StatCollection.GetValue("ExternalLiftCapacityUsed") >= 1; - } - - public static int GetExternalLiftCapacity(this AbstractActor actor) - { - return actor.StatCollection.GetValue("ExternalLiftCapacity"); - } - public static int GetExternalLiftCapacityUsed(this AbstractActor actor) - { - return actor.StatCollection.GetValue("ExternalLiftCapacityUsed"); - } - public static void ModifyUsedExternalLiftCapacity(this AbstractActor actor, int value) - { - actor.StatCollection.ModifyStat("modifyUsedExternalLiftCapacity", -1, "ExternalLiftCapacityUsed", StatCollection.StatOperation.Int_Add, value); - } - public static int GetAvailableExternalLiftCapacity(this AbstractActor actor) - { - return actor.StatCollection.GetValue("ExternalLiftCapacity") - actor.StatCollection.GetValue("ExternalLiftCapacityUsed"); + else + { + ModInit.modLog?.Trace?.Write($"DetachFromAirliftCarrier call DropAirliftedUnit."); + //CALL DEFAULT ATTACH CODE + carrier.DropAirliftedUnit(actor, Vector3.zero, true, false, false, true); + //squad.DismountBA(carrier, Vector3.zero, false, false, true); + } } - public static float GetVerticalOffsetForExternalMount(this AbstractActor targetUnit) + public class DetachFromAirliftCarrierDelegate { - return targetUnit.HighestLOSPosition.y * .7f; - } + public float CarrierDownSpeed = -20f; + public float CarrierUpSpeed = 5f; + public AbstractActor Actor{ get; set; } + public CustomMech Carrier{ get; set; } - public static List GetAirliftedUnits(this AbstractActor carrier) - { - var results = new List(); - var trackers = ModState.AirliftTrackers.Where(x => x.Value.CarrierGUID == carrier.GUID); - foreach (var tracker in trackers) + public DetachFromAirliftCarrierDelegate(AbstractActor actor, CustomMech carrier) { - results.Add(carrier.Combat.FindActorByGUID(tracker.Key)); + this.Actor = actor; + this.Carrier = carrier; } - return results; - } - public static void HandleTurretFallingDamage(this Turret turret) - { - var dmg = turret.TurretDef.Chassis.Tonnage; - - var fallingWeapon = new Weapon(turret, turret.Combat, turret.TurretDef.Inventory.First(), "TurretFallingFakeWeapon"); - // Initialize a game representation to prevent warnings in CAC logs - fallingWeapon.Init(); - fallingWeapon.InitStats(); - fallingWeapon.InitGameRep(fallingWeapon.baseComponentRef.prefabName, turret.GameRep.TurretAttach, turret.LogDisplayName); - var hitinfo = new WeaponHitInfo(-1, -1, 0, 0, turret.GUID, - turret.GUID, 1, new float[1], new float[1], new float[1], - new bool[1], new int[1], new int[1], new AttackImpactQuality[1], - new AttackDirection[1], new Vector3[1], new string[1], new int[1]); + public void OnLandDetach() + { + if (ModInit.modSettings.ReworkedCarrierEvasion) Carrier.DumpAllEvasivePips(); + Actor.GameRep.transform.localScale = new Vector3(1f, 1f, 1f); + Carrier.DropAirliftedUnit(Actor, Vector3.zero, false, false, false, true); + //Actor.GameRep.ToggleHeadlights(true); // maybe toggle headlights if internal? + } - turret.TakeWeaponDamage(hitinfo, 1, - fallingWeapon, dmg, - 0, 0, DamageType.DFASelf); + public void OnRestoreHeightControl() + { + Carrier.custGameRep.HeightController.UpSpeed = CarrierUpSpeed; + Carrier.custGameRep.HeightController.DownSpeed = CarrierDownSpeed; + } } - public static void MountUnitToAirliftCarrier(this AbstractActor carrier, AbstractActor targetUnit, bool isFriendly) - { -// if (targetUnit.GUID == ModState.UnitPendingAirliftInvocation) +// public static void ProcessWiggleWiggle(this AbstractActor creator, AbstractActor carrier) +// { +// var settings = ModInit.modSettings.AirliftWiggleConfiguration; +// var roll = ModInit.Random.NextDouble(); +// var successChance = Mathf.Min(settings.BaseSuccessChance + +// (settings.PilotingSuccessFactor * creator.GetPilot().Piloting), settings.MaxSuccessChance); +// ModInit.modLog?.Info?.Write($"[ProcessWiggleWiggle] {creator.DisplayName} is wiggling with success chance {successChance} vs roll {roll}."); +// if (roll <= successChance) // { -// targetUnit.StatCollection.Set("irbtmu_immobile_unit", false); +// creator.DetachFromAirliftCarrier(carrier, false); // } -// ModState.UnitPendingAirliftInvocation = ""; - if (targetUnit is Mech targetMech) //handle turret also below this +// } + + public static void DropAirliftedUnit(this AbstractActor carrier, AbstractActor actor, Vector3 locationOverride, bool isHostileDrop, bool calledFromDeswarm = false, + bool calledFromHandleDeath = false, bool unShrinkRep = true) + { + var em = actor.Combat.EffectManager; + foreach (var airliftEffect in ModState.AirliftEffects) { - foreach (var airliftEffect in ModState.AirliftEffects) + foreach (var effectProper in airliftEffect.effects) { - if (airliftEffect.FriendlyAirlift && isFriendly) - { - foreach (var effectData in airliftEffect.effects) - { - targetUnit.CreateEffect(effectData, null, effectData.Description.Id, -1, targetUnit); - } - } - else if (!airliftEffect.FriendlyAirlift && !isFriendly) - { - foreach (var effectData in airliftEffect.effects) - { - targetUnit.CreateEffect(effectData, null, effectData.Description.Id, -1, targetUnit); - } - } - } - var availableInternalCapacity = carrier.GetAvailableInternalLiftCapacity(); - var availableExternalCapacity = carrier.GetAvailableExternalLiftCapacity(); - var unitTonnage = Mathf.RoundToInt(targetMech.tonnage); - var offset = targetUnit.GetVerticalOffsetForExternalMount(); - if (isFriendly) - { - if (ModInit.modSettings.AirliftCapacityByTonnage && !carrier.GetOverrideCapacityMethod() || !ModInit.modSettings.AirliftCapacityByTonnage && carrier.GetOverrideCapacityMethod()) - { - if (availableInternalCapacity >= unitTonnage) - { - ModInit.modLog?.Info?.Write( - $"[MountUnitToAirliftCarrier] - target unit {carrier.DisplayName} has available internal lift capacity of {availableInternalCapacity}; mounting {targetMech.DisplayName} internally."); - carrier.ModifyUsedInternalLiftCapacity(unitTonnage); - ModState.AirliftTrackers.Add(targetMech.GUID, new Classes.AirliftTracker(carrier.GUID, true, true, 0f)); - //shrinkadink - targetMech.GameRep.transform.localScale = new Vector3(.01f, .01f, .01f); - targetMech.GameRep.ToggleHeadlights(false); - return; - } - if (availableExternalCapacity >= unitTonnage) - { - ModInit.modLog?.Info?.Write( - $"[MountUnitToAirliftCarrier] - target unit {carrier.DisplayName} has available external lift capacity of {availableExternalCapacity}; mounting {targetMech.DisplayName} externally. Offset calculated at {offset}"); - carrier.ModifyUsedExternalLiftCapacity(unitTonnage); - ModState.AirliftTrackers.Add(targetMech.GUID, new Classes.AirliftTracker(carrier.GUID, false, true, offset)); - } - } - else - { - if (availableInternalCapacity > 0) - { - ModInit.modLog?.Info?.Write( - $"[MountUnitToAirliftCarrier] - target unit {carrier.DisplayName} has available internal lift capacity of {availableInternalCapacity}; mounting {targetMech.DisplayName} internally."); - carrier.ModifyUsedInternalLiftCapacity(1); - ModState.AirliftTrackers.Add(targetMech.GUID, new Classes.AirliftTracker(carrier.GUID, false, true, 0f)); - //shrinkadink - targetMech.GameRep.transform.localScale = new Vector3(.01f, .01f, .01f); - targetMech.GameRep.ToggleHeadlights(false); - return; - } - if (availableExternalCapacity > 0) - { - ModInit.modLog?.Info?.Write( - $"[MountUnitToAirliftCarrier] - target unit {carrier.DisplayName} has available external lift capacity of {availableExternalCapacity}; mounting {targetMech.DisplayName} externally. Offset calculated at {offset}"); - carrier.ModifyUsedExternalLiftCapacity(1); - ModState.AirliftTrackers.Add(targetMech.GUID, new Classes.AirliftTracker(carrier.GUID, false, true, offset)); - } - } - } - else - { - if (ModInit.modSettings.AirliftCapacityByTonnage && !carrier.GetOverrideCapacityMethod() || !ModInit.modSettings.AirliftCapacityByTonnage && carrier.GetOverrideCapacityMethod()) - { - if (availableExternalCapacity >= unitTonnage) - { - ModInit.modLog?.Info?.Write( - $"[MountUnitToAirliftCarrier] - target unit {carrier.DisplayName} has available external lift capacity of {availableExternalCapacity}; mounting {targetMech.DisplayName} externally. Offset calculated at {offset}"); - carrier.ModifyUsedExternalLiftCapacity(unitTonnage); - ModState.AirliftTrackers.Add(targetMech.GUID, new Classes.AirliftTracker(carrier.GUID, false, true, offset)); - } - } - else - { - if (availableExternalCapacity > 0) - { - ModInit.modLog?.Info?.Write( - $"[MountUnitToAirliftCarrier] - target unit {carrier.DisplayName} has available external lift capacity of {availableExternalCapacity}; mounting {targetMech.DisplayName} externally. Offset calculated at {offset}"); - carrier.ModifyUsedExternalLiftCapacity(1); - ModState.AirliftTrackers.Add(targetMech.GUID, new Classes.AirliftTracker(carrier.GUID, false, true, offset)); - } - } - } - } - else if (targetUnit is Turret targetTurret) //handle turret also below this - { - foreach (var airliftEffect in ModState.AirliftEffects) - { - if (airliftEffect.FriendlyAirlift && isFriendly) - { - foreach (var effectData in airliftEffect.effects) - { - targetUnit.CreateEffect(effectData, null, effectData.Description.Id, -1, targetUnit); - } - } - else if (!airliftEffect.FriendlyAirlift && !isFriendly) - { - foreach (var effectData in airliftEffect.effects) - { - targetUnit.CreateEffect(effectData, null, effectData.Description.Id, -1, targetUnit); - } - } - } - var availableInternalCapacity = carrier.GetAvailableInternalLiftCapacity(); - var availableExternalCapacity = carrier.GetAvailableExternalLiftCapacity(); - var unitTonnage = Mathf.RoundToInt(targetTurret.TurretDef.Chassis.Tonnage); - var offset = targetUnit.GetVerticalOffsetForExternalMount(); - if (isFriendly) - { - if (ModInit.modSettings.AirliftCapacityByTonnage && !carrier.GetOverrideCapacityMethod() || !ModInit.modSettings.AirliftCapacityByTonnage && carrier.GetOverrideCapacityMethod()) - { - if (availableInternalCapacity >= unitTonnage) - { - ModInit.modLog?.Info?.Write( - $"[MountUnitToAirliftCarrier] - target unit {carrier.DisplayName} has available internal lift capacity of {availableInternalCapacity}; mounting {targetTurret.DisplayName} internally."); - carrier.ModifyUsedInternalLiftCapacity(unitTonnage); - ModState.AirliftTrackers.Add(targetTurret.GUID, new Classes.AirliftTracker(carrier.GUID, true, true, 0f)); - //shrinkadink - targetTurret.GameRep.transform.localScale = new Vector3(.01f, .01f, .01f); - return; - } - if (availableExternalCapacity >= unitTonnage) - { - ModInit.modLog?.Info?.Write( - $"[MountUnitToAirliftCarrier] - target unit {carrier.DisplayName} has available external lift capacity of {availableExternalCapacity}; mounting {targetTurret.DisplayName} externally. Offset calculated at {offset}"); - carrier.ModifyUsedExternalLiftCapacity(unitTonnage); - ModState.AirliftTrackers.Add(targetTurret.GUID, new Classes.AirliftTracker(carrier.GUID, false, true, offset)); - } - } - else - { - if (availableInternalCapacity > 0) - { - ModInit.modLog?.Info?.Write( - $"[MountUnitToAirliftCarrier] - target unit {carrier.DisplayName} has available internal lift capacity of {availableInternalCapacity}; mounting {targetTurret.DisplayName} internally."); - carrier.ModifyUsedInternalLiftCapacity(1); - ModState.AirliftTrackers.Add(targetTurret.GUID, new Classes.AirliftTracker(carrier.GUID, false, true, 0f)); - //shrinkadink - targetTurret.GameRep.transform.localScale = new Vector3(.01f, .01f, .01f); - return; - } - if (availableExternalCapacity > 0) - { - ModInit.modLog?.Info?.Write( - $"[MountUnitToAirliftCarrier] - target unit {carrier.DisplayName} has available external lift capacity of {availableExternalCapacity}; mounting {targetTurret.DisplayName} externally. Offset calculated at {offset}"); - carrier.ModifyUsedExternalLiftCapacity(1); - ModState.AirliftTrackers.Add(targetTurret.GUID, new Classes.AirliftTracker(carrier.GUID, false, true, offset)); - } - } - } - else - { - if (ModInit.modSettings.AirliftCapacityByTonnage && !carrier.GetOverrideCapacityMethod() || !ModInit.modSettings.AirliftCapacityByTonnage && carrier.GetOverrideCapacityMethod()) - { - if (availableExternalCapacity >= unitTonnage) - { - ModInit.modLog?.Info?.Write( - $"[MountUnitToAirliftCarrier] - target unit {carrier.DisplayName} has available external lift capacity of {availableExternalCapacity}; mounting {targetTurret.DisplayName} externally. Offset calculated at {offset}"); - carrier.ModifyUsedExternalLiftCapacity(unitTonnage); - ModState.AirliftTrackers.Add(targetTurret.GUID, new Classes.AirliftTracker(carrier.GUID, false, true, offset)); - } - } - else - { - if (availableExternalCapacity > 0) - { - ModInit.modLog?.Info?.Write( - $"[MountUnitToAirliftCarrier] - target unit {carrier.DisplayName} has available external lift capacity of {availableExternalCapacity}; mounting {targetTurret.DisplayName} externally. Offset calculated at {offset}"); - carrier.ModifyUsedExternalLiftCapacity(1); - ModState.AirliftTrackers.Add(targetTurret.GUID, new Classes.AirliftTracker(carrier.GUID, false, true, offset)); - } - } - } - } - } - -// public static void ProcessWiggleWiggle(this AbstractActor creator, AbstractActor carrier) -// { -// var settings = ModInit.modSettings.AirliftWiggleConfiguration; -// var roll = ModInit.Random.NextDouble(); -// var successChance = Mathf.Min(settings.BaseSuccessChance + -// (settings.PilotingSuccessFactor * creator.GetPilot().Piloting), settings.MaxSuccessChance); -// ModInit.modLog?.Info?.Write($"[ProcessWiggleWiggle] {creator.DisplayName} is wiggling with success chance {successChance} vs roll {roll}."); -// if (roll <= successChance) -// { -// creator.DetachFromAirliftCarrier(carrier, false); -// } -// } - - public static void DropAirliftedUnit(this AbstractActor carrier, AbstractActor actor, Vector3 locationOverride, bool isHostileDrop, bool calledFromDeswarm = false, - bool calledFromHandleDeath = false, bool unShrinkRep = true) - { - var em = actor.Combat.EffectManager; - foreach (var airliftEffect in ModState.AirliftEffects) - { - foreach (var effectProper in airliftEffect.effects) - { - var effects = em.GetAllEffectsTargetingWithBaseID(actor, effectProper.Description.Id); - for (int i = effects.Count - 1; i >= 0; i--) + var effects = em.GetAllEffectsTargetingWithBaseID(actor, effectProper.Description.Id); + for (int i = effects.Count - 1; i >= 0; i--) { ModInit.modLog?.Info?.Write( $"[DropAirliftedUnit] Cancelling effect on {actor.DisplayName}: {effects[i].EffectData.Description.Name}."); @@ -505,140 +295,363 @@ public static void DropAirliftedUnit(this AbstractActor carrier, AbstractActor a $"[DropAirliftedUnit] Removing PositionLock with rider {actor.DisplayName} {actor.GUID} and carrier {carrier.DisplayName} {carrier.GUID} and rebuilding visibility cache."); } - public class DetachFromAirliftCarrierDelegate + public static List GetAirliftedUnits(this AbstractActor carrier) { - public AbstractActor Actor{ get; set; } - public CustomMech Carrier{ get; set; } - public float CarrierDownSpeed = -20f; - public float CarrierUpSpeed = 5f; - - public DetachFromAirliftCarrierDelegate(AbstractActor actor, CustomMech carrier) - { - this.Actor = actor; - this.Carrier = carrier; - } - - public void OnRestoreHeightControl() - { - Carrier.custGameRep.HeightController.UpSpeed = CarrierUpSpeed; - Carrier.custGameRep.HeightController.DownSpeed = CarrierDownSpeed; - } - public void OnLandDetach() + var results = new List(); + var trackers = ModState.AirliftTrackers.Where(x => x.Value.CarrierGUID == carrier.GUID); + foreach (var tracker in trackers) { - Actor.GameRep.transform.localScale = new Vector3(1f, 1f, 1f); - Carrier.DropAirliftedUnit(Actor, Vector3.zero, false, false, false, true); - //Actor.GameRep.ToggleHeadlights(true); // maybe toggle headlights if internal? + results.Add(carrier.Combat.FindActorByGUID(tracker.Key)); } + return results; } - internal class AttachToAirliftCarrierDelegate + public static int GetAvailableExternalLiftCapacity(this AbstractActor actor) { - public AbstractActor Actor { get; set; } - public CustomMech Carrier { get; set; } - public float CarrierDownSpeed = -20f; - public float CarrierUpSpeed = 5f; + return actor.StatCollection.GetValue("ExternalLiftCapacity") - actor.StatCollection.GetValue("ExternalLiftCapacityUsed"); + } - public AttachToAirliftCarrierDelegate(AbstractActor actor, CustomMech carrier) - { - this.Actor = actor; - this.Carrier = carrier; - } + public static int GetAvailableInternalLiftCapacity(this AbstractActor actor) + { + return actor.StatCollection.GetValue("InternalLiftCapacity") - actor.StatCollection.GetValue("InternalLiftCapacityUsed"); + } - public void OnLandAttach() - { - //HIDE SQUAD REPRESENTATION - //attachTarget.MountBattleArmorToChassis(squad, true); - Carrier.MountUnitToAirliftCarrier(Actor, true); - //attachTarget.HideBattleArmorOnChassis(squad); - } + public static bool GetCanAirliftHostiles(this AbstractActor actor) + { + return actor.StatCollection.GetValue("CanAirliftHostiles"); + } - public void OnRestoreHeightControl() + public static int GetExternalLiftCapacity(this AbstractActor actor) + { + return actor.StatCollection.GetValue("ExternalLiftCapacity"); + } + + public static int GetExternalLiftCapacityUsed(this AbstractActor actor) + { + return actor.StatCollection.GetValue("ExternalLiftCapacityUsed"); + } + + public static bool GetHasAvailableExternalLiftCapacityForTarget(this AbstractActor actor, AbstractActor targetActor) + { + if (ModInit.modSettings.AirliftCapacityByTonnage && !actor.GetOverrideCapacityMethod() || !ModInit.modSettings.AirliftCapacityByTonnage && actor.GetOverrideCapacityMethod()) { - var offset = Vector3.zero; - if (Actor.IsAirlifted()) + if (targetActor is Mech targetMech) { - offset = Vector3.down * ModState.AirliftTrackers[Actor.GUID].Offset; + return actor.StatCollection.GetValue("ExternalLiftCapacity") - + actor.StatCollection.GetValue("ExternalLiftCapacityUsed") >= Mathf.RoundToInt(targetMech.tonnage); } - Carrier.custGameRep.HeightController.UpSpeed = CarrierUpSpeed; - Carrier.custGameRep.HeightController.DownSpeed = CarrierDownSpeed; - var pos = Carrier.CurrentPosition + offset + - Vector3.up * Carrier.custGameRep.HeightController.CurrentHeight; - Actor.TeleportActor(pos); - - //Actor.GameRep.thisTransform.rotation = Quaternion.identity; - //Actor.CurrentRotation = Quaternion.identity; - if (Actor is CustomMech customMech) + else if (targetActor is Vehicle vehicle) { - customMech.custGameRep.j_Root.localRotation = Quaternion.identity; + + return actor.StatCollection.GetValue("ExternalLiftCapacity") - + actor.StatCollection.GetValue("ExternalLiftCapacityUsed") >= Mathf.RoundToInt(vehicle.tonnage); } - Actor.GameRep.thisTransform.rotation = Carrier.GameRep.thisTransform.rotation; - Actor.CurrentRotation = Carrier.CurrentRotation; - //var rotate = Quaternion.LookRotation(Carrier.CurrentRotation.eulerAngles); - //Actor.GameRep.thisTransform.LookAt(rotate.eulerAngles, Vector3.up); } + return actor.StatCollection.GetValue("ExternalLiftCapacity") - + actor.StatCollection.GetValue("ExternalLiftCapacityUsed") >= 1; } - public static void AttachToAirliftCarrier(this AbstractActor actor, AbstractActor carrier, bool isFriendly) + public static bool GetHasAvailableInternalLiftCapacityForTarget(this AbstractActor actor, AbstractActor targetActor) { - if (isFriendly) + if (ModInit.modSettings.AirliftCapacityByTonnage && !actor.GetOverrideCapacityMethod() || !ModInit.modSettings.AirliftCapacityByTonnage && actor.GetOverrideCapacityMethod()) { - ModInit.modLog?.Trace?.Write($"AttachToAirliftCarrier processing on friendly."); - if (carrier is CustomMech custMech && custMech.FlyingHeight() > 1.5f) + if (targetActor is Mech targetMech) { - //Check if actually flying unit - //CALL ATTACH CODE BUT WITHOUT SQUAD REPRESENTATION HIDING - //custMech.MountBattleArmorToChassis(squad, false); - - custMech.custGameRep.HeightController.UpSpeed = 50f; - custMech.custGameRep.HeightController.DownSpeed = -50f; - - var attachDel = new AttachToAirliftCarrierDelegate(actor, custMech); - custMech.DropOffAnimation(attachDel.OnLandAttach, attachDel.OnRestoreHeightControl); + return actor.StatCollection.GetValue("InternalLiftCapacity") - + actor.StatCollection.GetValue("InternalLiftCapacityUsed") >= targetMech.tonnage; } - else + else if (targetActor is Vehicle vehicle) { - ModInit.modLog?.Trace?.Write($"AttachToAirliftCarrier call mount."); - //CALL DEFAULT ATTACH CODE - carrier.MountUnitToAirliftCarrier(actor, true); + + return actor.StatCollection.GetValue("InternalLiftCapacity") - + actor.StatCollection.GetValue("InternalLiftCapacityUsed") >= vehicle.tonnage; } } - else - { - ModInit.modLog?.Trace?.Write($"AttachToCarrier call mount."); - //CALL DEFAULT ATTACH CODE - carrier.MountUnitToAirliftCarrier(actor, false); - } + return actor.StatCollection.GetValue("InternalLiftCapacity") - + actor.StatCollection.GetValue("InternalLiftCapacityUsed") >= 1; } - public static void DetachFromAirliftCarrier(this AbstractActor actor, AbstractActor carrier, bool isFriendly) + public static int GetInternalLiftCapacity(this AbstractActor actor) { - if (isFriendly) + return actor.StatCollection.GetValue("InternalLiftCapacity"); + } + + public static int GetInternalLiftCapacityUsed(this AbstractActor actor) + { + return actor.StatCollection.GetValue("InternalLiftCapacityUsed"); + } + + public static bool GetOverrideCapacityMethod(this AbstractActor actor) + { + return actor.StatCollection.GetValue("OverrideGlobalCapacity"); + } + + public static float GetVerticalOffsetForExternalMount(this AbstractActor targetUnit) + { + return targetUnit.HighestLOSPosition.y * .7f; + } + + public static void HandleTurretFallingDamage(this Turret turret) + { + var dmg = turret.TurretDef.Chassis.Tonnage; + + var fallingWeapon = new Weapon(turret, turret.Combat, turret.TurretDef.Inventory.First(), "TurretFallingFakeWeapon"); + // Initialize a game representation to prevent warnings in CAC logs + fallingWeapon.Init(); + fallingWeapon.InitStats(); + fallingWeapon.InitGameRep(fallingWeapon.baseComponentRef.prefabName, turret.GameRep.TurretAttach, turret.LogDisplayName); + + var hitinfo = new WeaponHitInfo(-1, -1, 0, 0, turret.GUID, + turret.GUID, 1, new float[1], new float[1], new float[1], + new bool[1], new int[1], new int[1], new AttackImpactQuality[1], + new AttackDirection[1], new Vector3[1], new string[1], new int[1]); + + turret.TakeWeaponDamage(hitinfo, 1, + fallingWeapon, dmg, + 0, 0, DamageType.DFASelf); + } + + public static bool HasAirliftedEnemy(this AbstractActor actor) + { + return ModState.AirliftTrackers.Any(x => !x.Value.IsFriendly && x.Value.CarrierGUID == actor.GUID); + } + + public static bool HasAirliftedFriendly(this AbstractActor actor) + { + return ModState.AirliftTrackers.Any(x => x.Value.IsFriendly && x.Value.CarrierGUID == actor.GUID); + } + + public static bool HasAirliftedUnits(this AbstractActor actor) + { + return ModState.AirliftTrackers.Any(x => x.Value.CarrierGUID == actor.GUID); + } + + public static bool IsAirlifted(this AbstractActor actor) + { + return ModState.AirliftTrackers.ContainsKey(actor.GUID); + } + + public static bool IsAirliftedByTarget(this AbstractActor actor, AbstractActor targetActor) + { + return ModState.AirliftTrackers.ContainsKey(actor.GUID) && + ModState.AirliftTrackers[actor.GUID].CarrierGUID == targetActor.GUID; + } + + public static bool IsAirliftedEnemy(this AbstractActor actor) + { + return ModState.AirliftTrackers.ContainsKey(actor.GUID) && !ModState.AirliftTrackers[actor.GUID].IsFriendly; + } + + public static bool IsAirliftedFriendly(this AbstractActor actor) + { + return ModState.AirliftTrackers.ContainsKey(actor.GUID) && ModState.AirliftTrackers[actor.GUID].IsFriendly; + } + + public static bool IsAirliftingTargetUnit(this AbstractActor actor, AbstractActor targetActor) + { + return ModState.AirliftTrackers.ContainsKey(targetActor.GUID) && ModState.AirliftTrackers[targetActor.GUID].CarrierGUID == actor.GUID; + } + + public static void ModifyUsedExternalLiftCapacity(this AbstractActor actor, int value) + { + actor.StatCollection.ModifyStat("modifyUsedExternalLiftCapacity", -1, "ExternalLiftCapacityUsed", StatCollection.StatOperation.Int_Add, value); + } + + public static void ModifyUsedInternalLiftCapacity(this AbstractActor actor, int value) + { + actor.StatCollection.ModifyStat("modifyUsedInternalLiftCapacity", -1, "InternalLiftCapacityUsed", StatCollection.StatOperation.Int_Add, value); + } + + public static void MountUnitToAirliftCarrier(this AbstractActor carrier, AbstractActor targetUnit, bool isFriendly) + { +// if (targetUnit.GUID == ModState.UnitPendingAirliftInvocation) +// { +// targetUnit.StatCollection.Set("irbtmu_immobile_unit", false); +// } +// ModState.UnitPendingAirliftInvocation = ""; + if (targetUnit is Mech targetMech) //handle turret also below this { - if (carrier is CustomMech custMech && custMech.FlyingHeight() > 1.5f) + foreach (var airliftEffect in ModState.AirliftEffects) { - //Check if actually flying unit - //CALL ATTACH CODE BUT WITHOUT SQUAD REPRESENTATION HIDING - //custMech.DismountBA(squad, false, false, false); - custMech.custGameRep.HeightController.UpSpeed = 50f; - custMech.custGameRep.HeightController.DownSpeed = -50f; - - var detachDel = new DetachFromAirliftCarrierDelegate(actor, custMech); - custMech.DropOffAnimation(detachDel.OnLandDetach, detachDel.OnRestoreHeightControl); + if (airliftEffect.FriendlyAirlift && isFriendly) + { + foreach (var effectData in airliftEffect.effects) + { + targetUnit.CreateEffect(effectData, null, effectData.Description.Id, -1, targetUnit); + } + } + else if (!airliftEffect.FriendlyAirlift && !isFriendly) + { + foreach (var effectData in airliftEffect.effects) + { + targetUnit.CreateEffect(effectData, null, effectData.Description.Id, -1, targetUnit); + } + } + } + var availableInternalCapacity = carrier.GetAvailableInternalLiftCapacity(); + var availableExternalCapacity = carrier.GetAvailableExternalLiftCapacity(); + var unitTonnage = Mathf.RoundToInt(targetMech.tonnage); + var offset = targetUnit.GetVerticalOffsetForExternalMount(); + if (isFriendly) + { + if (ModInit.modSettings.AirliftCapacityByTonnage && !carrier.GetOverrideCapacityMethod() || !ModInit.modSettings.AirliftCapacityByTonnage && carrier.GetOverrideCapacityMethod()) + { + if (availableInternalCapacity >= unitTonnage) + { + ModInit.modLog?.Info?.Write( + $"[MountUnitToAirliftCarrier] - target unit {carrier.DisplayName} has available internal lift capacity of {availableInternalCapacity}; mounting {targetMech.DisplayName} internally."); + carrier.ModifyUsedInternalLiftCapacity(unitTonnage); + ModState.AirliftTrackers.Add(targetMech.GUID, new Classes.AirliftTracker(carrier.GUID, true, true, 0f)); + //shrinkadink + targetMech.GameRep.transform.localScale = new Vector3(.01f, .01f, .01f); + targetMech.GameRep.ToggleHeadlights(false); + return; + } + if (availableExternalCapacity >= unitTonnage) + { + ModInit.modLog?.Info?.Write( + $"[MountUnitToAirliftCarrier] - target unit {carrier.DisplayName} has available external lift capacity of {availableExternalCapacity}; mounting {targetMech.DisplayName} externally. Offset calculated at {offset}"); + carrier.ModifyUsedExternalLiftCapacity(unitTonnage); + ModState.AirliftTrackers.Add(targetMech.GUID, new Classes.AirliftTracker(carrier.GUID, false, true, offset)); + } + } + else + { + if (availableInternalCapacity > 0) + { + ModInit.modLog?.Info?.Write( + $"[MountUnitToAirliftCarrier] - target unit {carrier.DisplayName} has available internal lift capacity of {availableInternalCapacity}; mounting {targetMech.DisplayName} internally."); + carrier.ModifyUsedInternalLiftCapacity(1); + ModState.AirliftTrackers.Add(targetMech.GUID, new Classes.AirliftTracker(carrier.GUID, false, true, 0f)); + //shrinkadink + targetMech.GameRep.transform.localScale = new Vector3(.01f, .01f, .01f); + targetMech.GameRep.ToggleHeadlights(false); + return; + } + if (availableExternalCapacity > 0) + { + ModInit.modLog?.Info?.Write( + $"[MountUnitToAirliftCarrier] - target unit {carrier.DisplayName} has available external lift capacity of {availableExternalCapacity}; mounting {targetMech.DisplayName} externally. Offset calculated at {offset}"); + carrier.ModifyUsedExternalLiftCapacity(1); + ModState.AirliftTrackers.Add(targetMech.GUID, new Classes.AirliftTracker(carrier.GUID, false, true, offset)); + } + } } else { - ModInit.modLog?.Trace?.Write($"DetachFromAirliftCarrier call DropAirliftedUnit."); - //CALL DEFAULT ATTACH CODE - carrier.DropAirliftedUnit(actor, Vector3.zero, false, false, false, true); + if (ModInit.modSettings.AirliftCapacityByTonnage && !carrier.GetOverrideCapacityMethod() || !ModInit.modSettings.AirliftCapacityByTonnage && carrier.GetOverrideCapacityMethod()) + { + if (availableExternalCapacity >= unitTonnage) + { + ModInit.modLog?.Info?.Write( + $"[MountUnitToAirliftCarrier] - target unit {carrier.DisplayName} has available external lift capacity of {availableExternalCapacity}; mounting {targetMech.DisplayName} externally. Offset calculated at {offset}"); + carrier.ModifyUsedExternalLiftCapacity(unitTonnage); + ModState.AirliftTrackers.Add(targetMech.GUID, new Classes.AirliftTracker(carrier.GUID, false, true, offset)); + } + } + else + { + if (availableExternalCapacity > 0) + { + ModInit.modLog?.Info?.Write( + $"[MountUnitToAirliftCarrier] - target unit {carrier.DisplayName} has available external lift capacity of {availableExternalCapacity}; mounting {targetMech.DisplayName} externally. Offset calculated at {offset}"); + carrier.ModifyUsedExternalLiftCapacity(1); + ModState.AirliftTrackers.Add(targetMech.GUID, new Classes.AirliftTracker(carrier.GUID, false, true, offset)); + } + } } } - else + else if (targetUnit is Turret targetTurret) //handle turret also below this { - ModInit.modLog?.Trace?.Write($"DetachFromAirliftCarrier call DropAirliftedUnit."); - //CALL DEFAULT ATTACH CODE - carrier.DropAirliftedUnit(actor, Vector3.zero, true, false, false, true); - //squad.DismountBA(carrier, Vector3.zero, false, false, true); + foreach (var airliftEffect in ModState.AirliftEffects) + { + if (airliftEffect.FriendlyAirlift && isFriendly) + { + foreach (var effectData in airliftEffect.effects) + { + targetUnit.CreateEffect(effectData, null, effectData.Description.Id, -1, targetUnit); + } + } + else if (!airliftEffect.FriendlyAirlift && !isFriendly) + { + foreach (var effectData in airliftEffect.effects) + { + targetUnit.CreateEffect(effectData, null, effectData.Description.Id, -1, targetUnit); + } + } + } + var availableInternalCapacity = carrier.GetAvailableInternalLiftCapacity(); + var availableExternalCapacity = carrier.GetAvailableExternalLiftCapacity(); + var unitTonnage = Mathf.RoundToInt(targetTurret.TurretDef.Chassis.Tonnage); + var offset = targetUnit.GetVerticalOffsetForExternalMount(); + if (isFriendly) + { + if (ModInit.modSettings.AirliftCapacityByTonnage && !carrier.GetOverrideCapacityMethod() || !ModInit.modSettings.AirliftCapacityByTonnage && carrier.GetOverrideCapacityMethod()) + { + if (availableInternalCapacity >= unitTonnage) + { + ModInit.modLog?.Info?.Write( + $"[MountUnitToAirliftCarrier] - target unit {carrier.DisplayName} has available internal lift capacity of {availableInternalCapacity}; mounting {targetTurret.DisplayName} internally."); + carrier.ModifyUsedInternalLiftCapacity(unitTonnage); + ModState.AirliftTrackers.Add(targetTurret.GUID, new Classes.AirliftTracker(carrier.GUID, true, true, 0f)); + //shrinkadink + targetTurret.GameRep.transform.localScale = new Vector3(.01f, .01f, .01f); + return; + } + if (availableExternalCapacity >= unitTonnage) + { + ModInit.modLog?.Info?.Write( + $"[MountUnitToAirliftCarrier] - target unit {carrier.DisplayName} has available external lift capacity of {availableExternalCapacity}; mounting {targetTurret.DisplayName} externally. Offset calculated at {offset}"); + carrier.ModifyUsedExternalLiftCapacity(unitTonnage); + ModState.AirliftTrackers.Add(targetTurret.GUID, new Classes.AirliftTracker(carrier.GUID, false, true, offset)); + } + } + else + { + if (availableInternalCapacity > 0) + { + ModInit.modLog?.Info?.Write( + $"[MountUnitToAirliftCarrier] - target unit {carrier.DisplayName} has available internal lift capacity of {availableInternalCapacity}; mounting {targetTurret.DisplayName} internally."); + carrier.ModifyUsedInternalLiftCapacity(1); + ModState.AirliftTrackers.Add(targetTurret.GUID, new Classes.AirliftTracker(carrier.GUID, false, true, 0f)); + //shrinkadink + targetTurret.GameRep.transform.localScale = new Vector3(.01f, .01f, .01f); + return; + } + if (availableExternalCapacity > 0) + { + ModInit.modLog?.Info?.Write( + $"[MountUnitToAirliftCarrier] - target unit {carrier.DisplayName} has available external lift capacity of {availableExternalCapacity}; mounting {targetTurret.DisplayName} externally. Offset calculated at {offset}"); + carrier.ModifyUsedExternalLiftCapacity(1); + ModState.AirliftTrackers.Add(targetTurret.GUID, new Classes.AirliftTracker(carrier.GUID, false, true, offset)); + } + } + } + else + { + if (ModInit.modSettings.AirliftCapacityByTonnage && !carrier.GetOverrideCapacityMethod() || !ModInit.modSettings.AirliftCapacityByTonnage && carrier.GetOverrideCapacityMethod()) + { + if (availableExternalCapacity >= unitTonnage) + { + ModInit.modLog?.Info?.Write( + $"[MountUnitToAirliftCarrier] - target unit {carrier.DisplayName} has available external lift capacity of {availableExternalCapacity}; mounting {targetTurret.DisplayName} externally. Offset calculated at {offset}"); + carrier.ModifyUsedExternalLiftCapacity(unitTonnage); + ModState.AirliftTrackers.Add(targetTurret.GUID, new Classes.AirliftTracker(carrier.GUID, false, true, offset)); + } + } + else + { + if (availableExternalCapacity > 0) + { + ModInit.modLog?.Info?.Write( + $"[MountUnitToAirliftCarrier] - target unit {carrier.DisplayName} has available external lift capacity of {availableExternalCapacity}; mounting {targetTurret.DisplayName} externally. Offset calculated at {offset}"); + carrier.ModifyUsedExternalLiftCapacity(1); + ModState.AirliftTrackers.Add(targetTurret.GUID, new Classes.AirliftTracker(carrier.GUID, false, true, offset)); + } + } + } } + if (ModInit.modSettings.ReworkedCarrierEvasion) targetUnit.MountedEvasion(carrier); } } } diff --git a/StrategicOperations/StrategicOperations/Framework/BattleArmorUtils.cs b/StrategicOperations/StrategicOperations/Framework/BattleArmorUtils.cs index 56bf467..b94f79a 100644 --- a/StrategicOperations/StrategicOperations/Framework/BattleArmorUtils.cs +++ b/StrategicOperations/StrategicOperations/Framework/BattleArmorUtils.cs @@ -18,20 +18,67 @@ namespace StrategicOperations.Framework { public static class BattleArmorUtils { - public static void HandleBattleArmorFallingDamage(this TrooperSquad squad) + public const string BA_Simlink = "BA_Simlink_"; + + public static void AttachToCarrier(this TrooperSquad squad, AbstractActor attachTarget, bool isFriendly) { - var dmg = squad.StatCollection.GetValue("DFASelfDamage"); - var trooperLocs = squad.GetPossibleHitLocations(squad); - for (int i = 0; i < trooperLocs.Count; i++) + if (isFriendly) { - var hitinfo = new WeaponHitInfo(-1, -1, 0, 0, squad.GUID, - squad.GUID, 1, new float[1], new float[1], new float[1], - new bool[1], new int[trooperLocs[i]], new int[1], new AttackImpactQuality[1], - new AttackDirection[1], new Vector3[1], new string[1], new int[trooperLocs[i]]); + ModInit.modLog?.Trace?.Write($"AttachToCarrier processing on friendly."); + if (attachTarget is CustomMech custMech && custMech.FlyingHeight() > 1.5f) + { + //Check if actually flying unit + //CALL ATTACH CODE BUT WITHOUT SQUAD REPRESENTATION HIDING + //custMech.MountBattleArmorToChassis(squad, false); - squad.TakeWeaponDamage(hitinfo, trooperLocs[i], - squad.MeleeWeapon, dmg, - 0, 0, DamageType.DFASelf); + custMech.custGameRep.HeightController.UpSpeed = 50f; + custMech.custGameRep.HeightController.DownSpeed = -50f; + + var attachDel = new AttachToCarrierDelegate(squad, custMech); + custMech.DropOffAnimation(attachDel.OnLandAttach, attachDel.OnRestoreHeightControl); + } + else + { + ModInit.modLog?.Trace?.Write($"AttachToCarrier call mount."); + //CALL DEFAULT ATTACH CODE + attachTarget.MountBattleArmorToChassis(squad, true, true); + } + } + else + { + ModInit.modLog?.Trace?.Write($"AttachToCarrier call mount."); + //CALL DEFAULT ATTACH CODE + attachTarget.MountBattleArmorToChassis(squad, true, false); + } + } + + internal class AttachToCarrierDelegate + { + public float attachTargetDownSpeed = -20f; + public float attachTargetUpSpeed = 5f; + public CustomMech attachTarget { get; set; } + public TrooperSquad squad { get; set; } + + public AttachToCarrierDelegate(TrooperSquad squad, CustomMech target) + { + this.squad = squad; + this.attachTarget = target; + } + + public void OnLandAttach() + { + //HIDE SQUAD REPRESENTATION + attachTarget.MountBattleArmorToChassis(squad, true, true); + //attachTarget.HideBattleArmorOnChassis(squad); + } + + public void OnRestoreHeightControl() + { + attachTarget.custGameRep.HeightController.UpSpeed = attachTargetUpSpeed; + attachTarget.custGameRep.HeightController.DownSpeed = attachTargetDownSpeed; + var pos = attachTarget.CurrentPosition + + Vector3.up * attachTarget.custGameRep.HeightController.CurrentHeight; + squad.TeleportActor(pos); } } @@ -49,24 +96,40 @@ public static float[] CalculateClusterDamages(float totalDamage, int clusters, L } return dmgClusters; } - public static void ShowBATargetsMeleeIndicator(this CombatHUDInWorldElementMgr inWorld, List targets, AbstractActor unit) + + public static bool CanRideInternalOnly(this AbstractActor actor) { - var tickMarks = inWorld.WeaponTickMarks;//Traverse.Create(inWorld).Field("WeaponTickMarks").GetValue>(); + return actor.StatCollection.GetValue("BattleArmorInternalMountsOnly"); + } - for (int i = 0; i < tickMarks.Count; i++) + public static bool CanSwarm(this AbstractActor actor) + { + if (actor.GetStaticUnitTags().Contains(ModInit.modSettings.DisableAISwarmTag) && actor.team is AITeam) return false; + return actor.StatCollection.GetValue("CanSwarm"); + } + + public static void CheckForBPodAndActivate(this AbstractActor actor) + { + if (!actor.team.IsLocalPlayer || actor.team.IsLocalPlayer && ModInit.modSettings.BPodsAutoActivate) { - if (targets.Contains(tickMarks[i].Owner)) + if (actor is Mech mech) { - if (tickMarks[i].Owner.team.IsEnemy(unit.team)) + foreach (var component in mech.allComponents) { - if (tickMarks[i].MeleeTweenAnimations != null) + if (ModInit.modSettings.BPodComponentIDs.Contains(component.defId)) { - tickMarks[i].MeleeTweenAnimations.SetState(ButtonState.Enabled, false); + if (ActivatableComponent.getChargesCount(component) > 0) + { + ModInit.modLog?.Info?.Write($"[CheckForBPodAndActivate] Auto-activating BPod {component.Name} due incoming swarm attempt"); + ActivatableComponent.activateComponent(component, true, false); + break; + } } } } } } + public static List CreateBPodDmgClusters(List locs, float totalDmg) { var clusters = new List(); @@ -95,690 +158,619 @@ public static List CreateBPodDmgClusters(List locs, float totalDmg) } return clusters; } - public static void CheckForBPodAndActivate(this AbstractActor actor) + + public static void DetachFromCarrier(this TrooperSquad squad, AbstractActor attachTarget, bool isFriendly) { - if (!actor.team.IsLocalPlayer || actor.team.IsLocalPlayer && ModInit.modSettings.BPodsAutoActivate) + ModState.PositionLockMount.Remove(squad.GUID); + if (isFriendly) { - if (actor is Mech mech) + ModInit.modLog?.Trace?.Write($"DetachFromCarrier processing on friendly."); + if (attachTarget is CustomMech custMech && custMech.FlyingHeight() > 1.5f) { - foreach (var component in mech.allComponents) - { - if (ModInit.modSettings.BPodComponentIDs.Contains(component.defId)) - { - if (ActivatableComponent.getChargesCount(component) > 0) - { - ModInit.modLog?.Info?.Write($"[CheckForBPodAndActivate] Auto-activating BPod {component.Name} due incoming swarm attempt"); - ActivatableComponent.activateComponent(component, true, false); - break; - } - } - } + //Check if actually flying unit + //CALL ATTACH CODE BUT WITHOUT SQUAD REPRESENTATION HIDING + //custMech.DismountBA(squad, false, false, false); + custMech.custGameRep.HeightController.UpSpeed = 50f; + custMech.custGameRep.HeightController.DownSpeed = -50f; + + var detachDel = new DetachFromCarrierDelegate(squad, custMech); + custMech.DropOffAnimation(detachDel.OnLandDetach, detachDel.OnRestoreHeightControl); } + else + { + ModInit.modLog?.Trace?.Write($"DetachFromCarrier call dismount."); + //CALL DEFAULT ATTACH CODE + squad.DismountBA(attachTarget, Vector3.zero, true, false, false); + } + } + else + { + ModInit.modLog?.Trace?.Write($"DetachFromCarrier call dismount."); + //CALL DEFAULT ATTACH CODE + squad.DismountBA(attachTarget, Vector3.zero, false, false, false); } } - public static string ProcessBattleArmorSpawnWeights(this Classes.BA_FactionAssoc BaWgts, DataManager dm, string factionID, string type) + internal class DetachFromCarrierDelegate { - if (ModState.CachedFactionAssociations.ContainsKey(factionID)) + public float detachTargetDownSpeed = -20f; + public float detachTargetUpSpeed = 5f; + public CustomMech detachTarget { get; set; } + public TrooperSquad squad { get; set; } + + public DetachFromCarrierDelegate(TrooperSquad squad, CustomMech target) { - if (ModState.CachedFactionAssociations[factionID][type].Count > 0) - { - return ModState.CachedFactionAssociations[factionID][type].GetRandomElement(); - } + this.squad = squad; + this.detachTarget = target; } - foreach (var faction in BaWgts.FactionIDs) + public void OnLandDetach() { - if (!ModState.CachedFactionAssociations.ContainsKey(faction)) + squad.GameRep.transform.localScale = new Vector3(1f, 1f, 1f); + squad.GameRep.ToggleHeadlights(true); + squad.DismountBA(detachTarget, Vector3.zero, true, false, false); + //ModState.SavedBAScale[squad.GUID]; + //if (ModState.SavedBAScale.ContainsKey(squad.GUID)) + //{ + // squad.GameRep.transform.localScale = ModState.SavedBAScale[squad.GUID]; + // ModState.SavedBAScale.Remove(squad.GUID); + //} + } + + public void OnRestoreHeightControl() + { + detachTarget.custGameRep.HeightController.UpSpeed = detachTargetUpSpeed; + detachTarget.custGameRep.HeightController.DownSpeed = detachTargetDownSpeed; + } + } + + public static void DismountBA(this AbstractActor actor, AbstractActor carrier, Vector3 locationOverride, bool isFriendly, bool calledFromDeswarm = false, + bool calledFromHandleDeath = false, bool unShrinkRep = true) + { + if (actor is TrooperSquad squad) + { + //if (squad.StatCollection.ContainsStatistic("irbtmu_immobile_unit")) squad.StatCollection.Set("irbtmu_immobile_unit", false); + if (ModState.BADamageTrackers.ContainsKey(actor.GUID)) { - ModState.CachedFactionAssociations.Add(faction, new Dictionary>()); - ModState.CachedFactionAssociations[faction].Add("InternalBattleArmorWeight", new List()); - ModState.CachedFactionAssociations[faction].Add("MountedBattleArmorWeight", new List()); - ModState.CachedFactionAssociations[faction].Add("HandsyBattleArmorWeight", new List()); - foreach (var BaTypeInternal in BaWgts.InternalBattleArmorWeight) - { - if (dm.Exists(BattleTechResourceType.MechDef, BaTypeInternal.Key) || BaTypeInternal.Key == "BA_EMPTY") - { - ModInit.modLog?.Trace?.Write( - $"[ProcessBattleArmorSpawnWeights - InternalBattleArmorWeight] Processing spawn weights for {BaTypeInternal.Key} and weight {BaTypeInternal.Value}"); - for (int i = 0; i < BaTypeInternal.Value; i++) - { - ModState.CachedFactionAssociations[faction]["InternalBattleArmorWeight"] - .Add(BaTypeInternal.Key); - ModInit.modLog?.Trace?.Write( - $"[ProcessBattleArmorSpawnWeights - InternalBattleArmorWeight] spawn list has {ModState.CachedFactionAssociations[faction]["InternalBattleArmorWeight"].Count} entries"); - } - } - } - foreach (var BaTypeMount in BaWgts.MountedBattleArmorWeight) + if (isFriendly) { - if (dm.Exists(BattleTechResourceType.MechDef, BaTypeMount.Key) || BaTypeMount.Key == "BA_EMPTY") + if (ModState.BADamageTrackers[actor.GUID].IsSquadInternal) { - ModInit.modLog?.Trace?.Write( - $"[ProcessBattleArmorSpawnWeights - MountedBattleArmorWeight] Processing spawn weights for {BaTypeMount.Key} and weight {BaTypeMount.Value}"); - for (int i = 0; i < BaTypeMount.Value; i++) - { - ModState.CachedFactionAssociations[faction]["MountedBattleArmorWeight"] - .Add(BaTypeMount.Key); - ModInit.modLog?.Trace?.Write( - $"[ProcessBattleArmorSpawnWeights - MountedBattleArmorWeight] spawn list has {ModState.CachedFactionAssociations[faction]["MountedBattleArmorWeight"].Count} entries"); - } + carrier.ModifyInternalBASquads(-1); + ModInit.modLog?.Info?.Write( + $"[DismountBA] Dismounted {actor.DisplayName} from internal capacity. Capacity is now {carrier.GetAvailableInternalBASpace()}."); + squad.FiringArc(90f); //reset to 90? } + else carrier.SetHasExternalMountedBattleArmor(false); } - foreach (var BaTypeHandsy in BaWgts.HandsyBattleArmorWeight) + ModState.BADamageTrackers.Remove(actor.GUID); + } + + var em = actor.Combat.EffectManager; + foreach (var BA_effect in ModState.BA_MountSwarmEffects) + { + foreach (var effectProper in BA_effect.effects) { - if (dm.Exists(BattleTechResourceType.MechDef, BaTypeHandsy.Key) || BaTypeHandsy.Key == "BA_EMPTY") + ModInit.modLog?.Trace?.Write( + $"[DismountBA] DEBUGGINS Checking for {effectProper.Description.Name} with id {effectProper.Description.Id} on {actor.DisplayName}."); + var effects = em.GetAllEffectsTargetingWithBaseID(actor, effectProper.Description.Id); + + for (int i = effects.Count - 1; i >= 0; i--) { - ModInit.modLog?.Trace?.Write( - $"[ProcessBattleArmorSpawnWeights - HandsyBattleArmorWeight] Processing spawn weights for {BaTypeHandsy.Key} and weight {BaTypeHandsy.Value}"); - for (int i = 0; i < BaTypeHandsy.Value; i++) - { - ModState.CachedFactionAssociations[faction]["HandsyBattleArmorWeight"] - .Add(BaTypeHandsy.Key); - ModInit.modLog?.Trace?.Write( - $"[ProcessBattleArmorSpawnWeights - HandsyBattleArmorWeight] spawn list has {ModState.CachedFactionAssociations[faction]["HandsyBattleArmorWeight"].Count} entries"); - } + ModInit.modLog?.Info?.Write( + $"[DismountBA] Cancelling effect on {actor.DisplayName}: {effects[i].EffectData.Description.Name}."); + em.CancelEffect(effects[i]); } } } - if (ModState.CachedFactionAssociations[faction][type].Count > 0) - { - return ModState.CachedFactionAssociations[faction][type].GetRandomElement(); - } - - } - ModInit.modLog?.Error?.Write($"[ProcessBattleArmorSpawnWeights] no applicable config for this unit, returning empty list."); - return ""; - } - public static void ReInitIndicator(this CombatHUD hud, AbstractActor actor) - { - var indicators = hud.InWorldMgr.AttackDirectionIndicators;//Traverse.Create(hud.InWorldMgr).Field("AttackDirectionIndicators").GetValue>(); - foreach (var indicator in indicators) - { - if (indicator.Owner.GUID == actor.GUID) + + actor.FiringArc(actor.GetCustomInfo().FiringArc); + var hud = CameraControl.Instance.HUD;//SharedState.CombatHUD; + //var hud = Traverse.Create(CameraControl.Instance).Property("HUD").GetValue(); + //actor.GameRep.IsTargetable = true; + + ModState.PositionLockMount.Remove(actor.GUID); + ModState.PositionLockSwarm.Remove(actor.GUID); + ModState.CachedUnitCoordinates.Remove(carrier.GUID); + + if (unShrinkRep) { - indicator.Init(actor, hud); + actor.GameRep.transform.localScale = new Vector3(1f, 1f, 1f); + //actor.GameRep.transform.localScale = ModState.SavedBAScale[actor.GUID]; + //ModState.SavedBAScale.Remove(actor.GUID); + squad.GameRep.ToggleHeadlights(true); } - } - } - - public static bool IsAvailableBAAbility(this Ability ability) - { - var flag = true; - if (ability.parentComponent != null) - { - flag = ability.parentComponent.IsFunctional; - ModInit.modLog?.Trace?.Write($"[IsAvailableBAAbility] - {ability.parentComponent.parent.DisplayName} has parentComponent for ability {ability.Def.Description.Name}. Component functional? {flag}."); - if (!flag) + var point = carrier.CurrentPosition; + if (locationOverride != Vector3.zero) { - if (ability.parentComponent.parent.ComponentAbilities.Any(x => - x.parentComponent.IsFunctional && x.Def.Id == ability.Def.Id)) + point = locationOverride; + ModInit.modLog?.Info?.Write($"[DismountBA] Using location override {locationOverride}."); + } + + else if (calledFromDeswarm || calledFromHandleDeath) + { + point = carrier.FetchRandomAdjacentHex(); +; ModInit.modLog?.Info?.Write($"[DismountBA] Using adjacent hex {point} or fallback carrier loc {carrier.CurrentPosition}."); + } + point.y = actor.Combat.MapMetaData.GetLerpedHeightAt(point, false); + actor.TeleportActor(point); + if (actor is CustomMech customMech) + { + customMech.custGameRep.j_Root.localRotation = Quaternion.identity; + } + actor.GameRep.thisTransform.rotation = carrier.GameRep.thisTransform.rotation; + actor.CurrentRotation = carrier.CurrentRotation; + if (!calledFromHandleDeath && !calledFromDeswarm) + { + ModInit.modLog?.Info?.Write($"[DismountBA] Not called from HandleDeath or Deswarm, resetting pathing."); + if (actor.team.IsLocalPlayer) { - flag = true; - ModInit.modLog?.Trace?.Write($"[IsAvailableBAAbility] - {ability.parentComponent.parent.DisplayName} has other component with same ability {ability.Def.Description.Name}. Component functional? {flag}."); + ModInit.modLog?.Info?.Write($"[DismountBA] Local player unit, resetting buttons."); + hud.MechWarriorTray.JumpButton.ResetButtonIfNotActive(actor); + hud.MechWarriorTray.SprintButton.ResetButtonIfNotActive(actor); + hud.MechWarriorTray.MoveButton.ResetButtonIfNotActive(actor); + hud.SelectionHandler.AddJumpState(actor); + hud.SelectionHandler.AddSprintState(actor); + hud.SelectionHandler.AddMoveState(actor); } + ModInit.modLog?.Info?.Write( + $"[DismountBA] Local player unit, Not called from HandleDeath or Deswarm, resetting buttons and pathing."); + actor.ResetPathing(false); + actor.Pathing.UpdateCurrentPath(false); + } + + if (false) //(actor.HasBegunActivation) + { + ModInit.modLog?.Info?.Write( + $"[DismountBA] Called from handledeath? {calledFromHandleDeath} or Deswarm? {calledFromDeswarm}, forcing end of activation."); // was i trying to end carrier activation maybe? + + var sequence = actor.DoneWithActor(); + actor.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(sequence)); + //actor.OnActivationEnd(actor.GUID, -1); } + + //actor.VisibilityCache.UpdateCacheReciprocal(actor.Combat.GetAllLivingCombatants()); + + ModInit.modLog?.Info?.Write( + $"[DismountBA] Removing PositionLock with rider {actor.DisplayName} {actor.GUID} and carrier {carrier.DisplayName} {carrier.GUID} and rebuilding visibility cache."); } - return ability.CurrentCooldown < 1 && (ability.Def.NumberOfUses < 1 || ability.NumUsesLeft > 0) && flag; - }// need to redo Ability.Activate from start, completely override for BA? Or just put ability on hidden componenet and ignore this shit. + } - public static void SetPairingOverlay(this LanceConfiguratorPanel lanceConfiguratorPanel, LanceLoadoutSlot baLanceLoadoutSlot, bool showOverlay, - LanceLoadoutSlot carrierLanceLoadoutSlot = null) + public static void DismountGarrison(this TrooperSquad squad, BattleTech.Building building, Vector3 locationOverride, + bool calledFromHandleDeath = false) { - if (ModState.DefaultOverlay == new Color()) + var em = squad.Combat.EffectManager; + foreach (var BA_effect in ModState.BA_MountSwarmEffects) { - var overlayChildren = baLanceLoadoutSlot.SelectedMech.UnavailableOverlay.gameObject.GetComponentsInChildren(); - foreach (var overlayChild in overlayChildren) + if (BA_effect.TargetEffectType != Classes.ConfigOptions.BA_TargetEffectType.GARRISON && + BA_effect.TargetEffectType != Classes.ConfigOptions.BA_TargetEffectType.BOTH) continue; + + foreach (var effectProper in BA_effect.effects) { - if (overlayChild.name == "stripes") + var effects = em.GetAllEffectsTargetingWithBaseID(squad, effectProper.Description.Id); + for (int i = effects.Count - 1; i >= 0; i--) { - var overlayChildImage = overlayChild.GetComponentInChildren(); - ModState.DefaultOverlay = new Color(overlayChildImage.color.r, overlayChildImage.color.g, overlayChildImage.color.b, overlayChildImage.color.a); - ModInit.modLog?.Trace?.Write( - $"[SetPairingOverlay] - set default overlay color to {ModState.DefaultOverlay} {ModState.DefaultOverlay.r} {ModState.DefaultOverlay.b} {ModState.DefaultOverlay.g} {ModState.DefaultOverlay.a}"); + ModInit.modLog?.Info?.Write( + $"[DismountGarrison] Cancelling effect on {squad.DisplayName}: {effects[i].EffectData.Description.Name}."); + em.CancelEffect(effects[i]); } } } - if (!showOverlay) - { - baLanceLoadoutSlot.SelectedMech.UnavailableOverlay.SetActive(false); - return; - } - - var baOverlayChildren = baLanceLoadoutSlot.SelectedMech.UnavailableOverlay.gameObject.GetComponentsInChildren(); + squad.FiringArc(squad.GetCustomInfo().FiringArc); + var hud = CameraControl.Instance.HUD;//SharedState.CombatHUD; + //var hud = Traverse.Create(CameraControl.Instance).Property("HUD").GetValue(); + //actor.GameRep.IsTargetable = true; - Image BAOverlayChildImage = null; - foreach (var baOverlayChild in baOverlayChildren) + if (ModState.GarrisonFriendlyTeam.ContainsKey(building.GUID)) { - if (baOverlayChild.name == "stripes") + if (!ModState.GarrisonFriendlyTeam[building.GUID]) { - BAOverlayChildImage = baOverlayChild.GetComponent(); + building.AddToTeam(squad?.Combat?.Teams?.FirstOrDefault(x => x?.GUID == "421027ec-8480-4cc6-bf01-369f84a22012")); // add back to world team. } } - if (carrierLanceLoadoutSlot == null) + if (!calledFromHandleDeath) { - baLanceLoadoutSlot.SelectedMech.UnavailableOverlay.SetActive(true); - if (BAOverlayChildImage != null) - BAOverlayChildImage.color = ModState.PendingSelectionColor;//new Color(0, 0, 0, ModState.DefaultOverlay.a); - lanceConfiguratorPanel.ToggleOverlayPotentialCarriers(baLanceLoadoutSlot, true); - return; + locationOverride = ModState.PositionLockGarrison[squad.GUID].OriginalSquadPos; } - baLanceLoadoutSlot.SelectedMech.UnavailableOverlay.SetActive(true); - carrierLanceLoadoutSlot.SelectedMech.UnavailableOverlay.SetActive(true); - carrierLanceLoadoutSlot.SelectedMech.UnavailableOverlay.SetActive(false); - var carrierOverlayChildren = carrierLanceLoadoutSlot.SelectedMech.UnavailableOverlay.gameObject.GetComponentsInChildren(); - var carrierPilotID = carrierLanceLoadoutSlot.SelectedPilot.Pilot.pilotDef.Description.Id; - foreach (var carrierOverlayChild in carrierOverlayChildren) + squad.GameRep.transform.localScale = new Vector3(1f, 1f, 1f); + squad.GameRep.ToggleHeadlights(true); + ModState.PositionLockGarrison.Remove(squad.GUID); + + var point = building.CurrentPosition; + + var hk = Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl); + + if (!hk) { - if (carrierOverlayChild.name == "stripes") + if (locationOverride != Vector3.zero) { - var carrierOverlayChildImage = carrierOverlayChild.GetComponent(); - if (!ModState.UsedOverlayColorsByCarrier.ContainsKey(carrierPilotID)) - { - //initialize new overlay color - var foundUnused = false; + point = locationOverride; + ModInit.modLog?.Info?.Write($"[DismountGarrison] Using location override {locationOverride}."); + } + } - foreach (var potentialColor in ModState.ProcessedOverlayColors) - { - if (!ModState.UsedOverlayColors.Contains(potentialColor)) - { - ModState.UsedOverlayColors.Add(potentialColor); - ModState.UsedOverlayColorsByCarrier.Add(carrierPilotID, potentialColor); - lanceConfiguratorPanel.ToggleOverlayPotentialCarriers(baLanceLoadoutSlot, false, carrierLanceLoadoutSlot); - carrierLanceLoadoutSlot.SelectedMech.UnavailableOverlay.SetActive(true); - carrierOverlayChildImage.color = potentialColor; - ModInit.modLog?.Trace?.Write($"[SetPairingOverlay] - carrier overlay color set to {carrierOverlayChildImage.color.r} {carrierOverlayChildImage.color.g} {carrierOverlayChildImage.color.b}"); - if (BAOverlayChildImage != null) - { - BAOverlayChildImage.color = potentialColor; - ModInit.modLog?.Trace?.Write($"[SetPairingOverlay] - BA overlay color set to {BAOverlayChildImage.color.r} {BAOverlayChildImage.color.g} {BAOverlayChildImage.color.b}"); - } - foundUnused = true; - break; - } - } + point.y = squad.Combat.MapMetaData.GetLerpedHeightAt(point, false); + squad.TeleportActor(point); - if (!foundUnused) - { - var chosenColor = ModState.UsedOverlayColors.GetRandomElement(); - ModState.UsedOverlayColorsByCarrier.Add(carrierPilotID, chosenColor); - lanceConfiguratorPanel.ToggleOverlayPotentialCarriers(baLanceLoadoutSlot, false, carrierLanceLoadoutSlot); - carrierLanceLoadoutSlot.SelectedMech.UnavailableOverlay.SetActive(true); - carrierOverlayChildImage.color = chosenColor; - ModInit.modLog?.Trace?.Write($"[SetPairingOverlay] - no unused colors, chose one at random to duplicate. carrier overlay color set to {carrierOverlayChildImage.color.r} {carrierOverlayChildImage.color.g} {carrierOverlayChildImage.color.b}"); - if (BAOverlayChildImage != null) - { - BAOverlayChildImage.color = chosenColor; - ModInit.modLog?.Trace?.Write($"[SetPairingOverlay] - no unused colors, chose one at random to duplicate. BA overlay color set to {BAOverlayChildImage.color.r} {BAOverlayChildImage.color.g} {BAOverlayChildImage.color.b}"); - } - } - } - else - { - if (BAOverlayChildImage != null) - { - lanceConfiguratorPanel.ToggleOverlayPotentialCarriers(baLanceLoadoutSlot, false, carrierLanceLoadoutSlot); - carrierOverlayChildImage.color = ModState.UsedOverlayColorsByCarrier[carrierPilotID]; - carrierLanceLoadoutSlot.SelectedMech.UnavailableOverlay.SetActive(true); - BAOverlayChildImage.color = ModState.UsedOverlayColorsByCarrier[carrierPilotID]; - ModInit.modLog?.Trace?.Write( - $"[SetPairingOverlay] - Carrier already has non-default color. Setting BA color to match: {BAOverlayChildImage.color.r}, {BAOverlayChildImage.color.g}, {BAOverlayChildImage.color.b}"); - } - } + if (!calledFromHandleDeath) + { + ModInit.modLog?.Info?.Write($"[DismountGarrison] Not called from HandleDeath or Deswarm, resetting pathing."); + if (squad.team.IsLocalPlayer) + { + ModInit.modLog?.Info?.Write($"[DismountGarrison] Local player unit, resetting buttons."); + hud.MechWarriorTray.JumpButton.ResetButtonIfNotActive(squad); + hud.MechWarriorTray.SprintButton.ResetButtonIfNotActive(squad); + hud.MechWarriorTray.MoveButton.ResetButtonIfNotActive(squad); + hud.SelectionHandler.AddJumpState(squad); + hud.SelectionHandler.AddSprintState(squad); + hud.SelectionHandler.AddMoveState(squad); } + squad.ResetPathing(false); + squad.Pathing.UpdateCurrentPath(false); } + + //squad.VisibilityCache.UpdateCacheReciprocal(squad.Combat.GetAllLivingCombatants()); + + ModInit.modLog?.Info?.Write( + $"[DismountGarrison] Removing PositionLock with rider {squad.DisplayName} {squad.GUID} and carrier {building.DisplayName} {building.GUID} and rebuilding visibility cache."); + } - public static void ToggleOverlayPotentialCarriers(this LanceConfiguratorPanel lanceConfiguratorPanel, LanceLoadoutSlot baLanceLoadoutSlot, bool toggleOn = true, LanceLoadoutSlot carrierLanceLoadoutSlot = null) + public static Vector3 FetchRandomAdjacentHex(this AbstractActor actor) { - if (baLanceLoadoutSlot?.SelectedMech == null) return; - foreach (var loadOutSlot in lanceConfiguratorPanel.loadoutSlots) + var points = actor.Combat.HexGrid.GetAdjacentPointsOnGrid(actor.CurrentPosition); + var validPoints = new List(); + for (int i = 0; i < points.Count; i++) { - if (loadOutSlot.SelectedMech != null && loadOutSlot.SelectedPilot != null) + Vector3 resultPos = Vector3.zero; + var walkGrid = actor.Pathing.WalkingGrid;//Traverse.Create(actor.Pathing).Property("WalkingGrid").GetValue(); + var pathNode = walkGrid.GetClosestPathNode(points[i], 0f, 1000f, points[i], ref resultPos, + out var resultAngle, false, false); + if (pathNode != null) { - if (loadOutSlot.SelectedMech != baLanceLoadoutSlot.SelectedMech) + var list = walkGrid.BuildPathFromEnd(pathNode, 1000f, resultPos, points[i], null, out var costLeft, out resultPos, out resultAngle); + if (list != null && list.Count > 0) { - var hasSpace = false; - var hasPx = false; - var pilotID = loadOutSlot.SelectedPilot.Pilot.Description.Id; - - if (!ModState.PairingInfos.ContainsKey(pilotID)) - { - if (loadOutSlot.SelectedMech.mechDef.GetTotalBASpaceMechDef() > 0) hasSpace = true; - } - else - { - var pairInfo = ModState.PairingInfos[pilotID]; - if (pairInfo.PairedBattleArmor.Count < pairInfo.CapacityInitial) - { - hasSpace = true; - if (pairInfo.PairedBattleArmor.Count > 0) hasPx = true; - } - } - - if (hasSpace)// && !hasPx) - { - var carrierOverlayChildren = loadOutSlot.SelectedMech.UnavailableOverlay.gameObject - .GetComponentsInChildren(); - foreach (var carrierOverlayChild in carrierOverlayChildren) - { - if (carrierOverlayChild.name == "stripes") - { - var carrierOverlayChildImage = carrierOverlayChild.GetComponent(); - - if (toggleOn) - { - carrierOverlayChildImage.color = ModState.PendingSelectionColor;//new Color(0, 0, 0, ModState.DefaultOverlay.a); - loadOutSlot.SelectedMech.UnavailableOverlay.SetActive(true); - } - else if (carrierLanceLoadoutSlot?.SelectedMech != null && - loadOutSlot?.SelectedMech != carrierLanceLoadoutSlot.SelectedMech) - { - if (ModState.UsedOverlayColorsByCarrier.TryGetValue(pilotID, out var color)) - { - carrierOverlayChildImage.color = color;//new Color(0, 0, 0, ModState.DefaultOverlay.a); - } - loadOutSlot.SelectedMech.UnavailableOverlay.SetActive(false); - } - } - } - } + validPoints.Add(resultPos); } } } - } - internal class DetachFromCarrierDelegate - { - public TrooperSquad squad { get; set; } - public CustomMech detachTarget { get; set; } - public float detachTargetDownSpeed = -20f; - public float detachTargetUpSpeed = 5f; - - public DetachFromCarrierDelegate(TrooperSquad squad, CustomMech target) + var point = actor.CurrentPosition; + point.y = actor.Combat.MapMetaData.GetLerpedHeightAt(point, false); + if (validPoints.Count > 0) { - this.squad = squad; - this.detachTarget = target; + point = validPoints.GetRandomElement(); + point.y = actor.Combat.MapMetaData.GetLerpedHeightAt(point, false); } + return point; + } - public void OnRestoreHeightControl() - { - detachTarget.custGameRep.HeightController.UpSpeed = detachTargetUpSpeed; - detachTarget.custGameRep.HeightController.DownSpeed = detachTargetDownSpeed; - } - public void OnLandDetach() + public static int GetAvailableInternalBASpace(this AbstractActor actor) + { + return actor.StatCollection.GetValue("InternalBattleArmorSquadCap") - actor.StatCollection.GetValue("InternalBattleArmorSquads"); + } + + public static string GetBASimLink(this MechDef mechDef) + { + foreach (var tag in mechDef.MechTags) { - squad.GameRep.transform.localScale = new Vector3(1f, 1f, 1f); - squad.GameRep.ToggleHeadlights(true); - squad.DismountBA(detachTarget, Vector3.zero, true, false, false); - //ModState.SavedBAScale[squad.GUID]; - //if (ModState.SavedBAScale.ContainsKey(squad.GUID)) - //{ - // squad.GameRep.transform.localScale = ModState.SavedBAScale[squad.GUID]; - // ModState.SavedBAScale.Remove(squad.GUID); - //} + if (tag.StartsWith(BA_Simlink)) return tag; } + return null; } - internal class AttachToCarrierDelegate + public static Ability GetDeswarmerAbilityForAI(this AbstractActor actor, bool UseMovement = false) { - public TrooperSquad squad { get; set; } - public CustomMech attachTarget { get; set; } - public float attachTargetDownSpeed = -20f; - public float attachTargetUpSpeed = 5f; + var list = new List(); - public AttachToCarrierDelegate(TrooperSquad squad, CustomMech target) + if (UseMovement) { - this.squad = squad; - this.attachTarget = target; + if (!string.IsNullOrEmpty(ModInit.modSettings.DeswarmMovementConfig.AbilityDefID)) + { + var move = actor.GetPilot().Abilities + .FirstOrDefault(x => x.Def.Id == ModInit.modSettings.DeswarmMovementConfig.AbilityDefID) ?? actor.ComponentAbilities + .FirstOrDefault(x => x.Def.Id == ModInit.modSettings.DeswarmMovementConfig.AbilityDefID); + if (move != null) return move; + } } - public void OnLandAttach() + if (!string.IsNullOrEmpty(ModInit.modSettings.BattleArmorDeSwarmSwat)) { - //HIDE SQUAD REPRESENTATION - attachTarget.MountBattleArmorToChassis(squad, true, true); - //attachTarget.HideBattleArmorOnChassis(squad); + var swat = actor.GetPilot().Abilities + .FirstOrDefault(x => x.Def.Id == ModInit.modSettings.BattleArmorDeSwarmSwat) ?? actor.ComponentAbilities + .FirstOrDefault(x => x.Def.Id == ModInit.modSettings.BattleArmorDeSwarmSwat); + if (swat != null) list.Add(swat); } - public void OnRestoreHeightControl() + if (!string.IsNullOrEmpty(ModInit.modSettings.BattleArmorDeSwarmRoll)) { - attachTarget.custGameRep.HeightController.UpSpeed = attachTargetUpSpeed; - attachTarget.custGameRep.HeightController.DownSpeed = attachTargetDownSpeed; - var pos = attachTarget.CurrentPosition + - Vector3.up * attachTarget.custGameRep.HeightController.CurrentHeight; - squad.TeleportActor(pos); + var roll = actor.GetPilot().Abilities + .FirstOrDefault(x => x.Def.Id == ModInit.modSettings.BattleArmorDeSwarmRoll) ?? actor.ComponentAbilities + .FirstOrDefault(x => x.Def.Id == ModInit.modSettings.BattleArmorDeSwarmRoll); + if (roll != null) list.Add(roll); } - } - - public static void AttachToCarrier(this TrooperSquad squad, AbstractActor attachTarget, bool isFriendly) - { - if (isFriendly) - { - ModInit.modLog?.Trace?.Write($"AttachToCarrier processing on friendly."); - if (attachTarget is CustomMech custMech && custMech.FlyingHeight() > 1.5f) - { - //Check if actually flying unit - //CALL ATTACH CODE BUT WITHOUT SQUAD REPRESENTATION HIDING - //custMech.MountBattleArmorToChassis(squad, false); - - custMech.custGameRep.HeightController.UpSpeed = 50f; - custMech.custGameRep.HeightController.DownSpeed = -50f; - var attachDel = new AttachToCarrierDelegate(squad, custMech); - custMech.DropOffAnimation(attachDel.OnLandAttach, attachDel.OnRestoreHeightControl); - } - else - { - ModInit.modLog?.Trace?.Write($"AttachToCarrier call mount."); - //CALL DEFAULT ATTACH CODE - attachTarget.MountBattleArmorToChassis(squad, true, true); - } - } - else + if (list.Count > 0) { - ModInit.modLog?.Trace?.Write($"AttachToCarrier call mount."); - //CALL DEFAULT ATTACH CODE - attachTarget.MountBattleArmorToChassis(squad, true, false); + return list.GetRandomElement(); } + return new Ability(new AbilityDef()); } - public static void DetachFromCarrier(this TrooperSquad squad, AbstractActor attachTarget, bool isFriendly) + public static Vector3 GetEvacBuildingLocation(this AbstractActor squad, BattleTech.Building building) { - ModState.PositionLockMount.Remove(squad.GUID); - if (isFriendly) + //var ogl = ObstructionGameLogic.GetObstructionFromBuilding(building, squad.Combat.ItemRegistry); + + var posToCheck = + squad.Combat.HexGrid.GetGridPointsAroundPointWithinRadius(building.CurrentPosition, + 4); + var evacLoc = squad.team.OffScreenPosition; + foreach (var pos in posToCheck) { - ModInit.modLog?.Trace?.Write($"DetachFromCarrier processing on friendly."); - if (attachTarget is CustomMech custMech && custMech.FlyingHeight() > 1.5f) + ModInit.modLog?.Trace?.Write($"[GetEvacBuildingLocation] Checking position {pos}, is {Vector3.Distance(pos, building.CurrentPosition)} from building source {building.CurrentPosition}."); + var encounterLayerDataCell = + squad.Combat.EncounterLayerData.GetCellAt(pos); + if (encounterLayerDataCell == null) continue; + + var eldcBldgs = encounterLayerDataCell.buildingList; + var foundBuilding = false; + foreach (var bldg in eldcBldgs) { - //Check if actually flying unit - //CALL ATTACH CODE BUT WITHOUT SQUAD REPRESENTATION HIDING - //custMech.DismountBA(squad, false, false, false); - custMech.custGameRep.HeightController.UpSpeed = 50f; - custMech.custGameRep.HeightController.DownSpeed = -50f; + if (bldg.obstructionGameLogic.IsRealBuilding) + { + foundBuilding = true; + ModInit.modLog?.Trace?.Write($"[GetEvacBuildingLocation] Found building {bldg.obstructionGameLogic.buildingDefId}."); + break; + } + } - var detachDel = new DetachFromCarrierDelegate(squad, custMech); - custMech.DropOffAnimation(detachDel.OnLandDetach, detachDel.OnRestoreHeightControl); + if (foundBuilding) + { + ModInit.modLog?.Trace?.Write($"[GetEvacBuildingLocation] Found building, continuing."); + continue; } - else + + if (Vector3.Distance(pos, building.CurrentPosition) < + Vector3.Distance(evacLoc, building.CurrentPosition)) { - ModInit.modLog?.Trace?.Write($"DetachFromCarrier call dismount."); - //CALL DEFAULT ATTACH CODE - squad.DismountBA(attachTarget, Vector3.zero, true, false, false); + evacLoc = pos; } } - else + + if (evacLoc == squad.team.OffScreenPosition) { - ModInit.modLog?.Trace?.Write($"DetachFromCarrier call dismount."); - //CALL DEFAULT ATTACH CODE - squad.DismountBA(attachTarget, Vector3.zero, false, false, false); + ModInit.modLog?.Trace?.Write($"[GetEvacBuildingLocation] No location found, lerping to roof."); + evacLoc = building.CurrentPosition; + evacLoc.y = squad.Combat.MapMetaData.GetLerpedHeightAt(evacLoc); } + + return evacLoc; } - public static void MountBattleArmorToChassis(this AbstractActor carrier, AbstractActor battleArmor, bool shrinkRep, bool isFriendly) + public static Vector3[] GetGarrisionLOSSourcePositions(this BattleTech.Building sourceBuilding) { - battleArmor.TeleportActor(carrier.CurrentPosition); - var isPlayer = battleArmor.team.IsLocalPlayer; - if (battleArmor is Mech battleArmorAsMech) - { - //add irbtu immobile tag? - //Statistic irbtmu_immobile_unit = battleArmor.StatCollection.GetStatistic("irbtmu_immobile_unit"); - //if (!battleArmor.StatCollection.ContainsStatistic("irbtmu_immobile_unit")) - //{ - // battleArmor.StatCollection.AddStatistic("irbtmu_immobile_unit", false); - //} + Point buildingPoint = new Point( + sourceBuilding.Combat.MapMetaData.GetXIndex(sourceBuilding.CurrentPosition.x), + sourceBuilding.Combat.MapMetaData.GetZIndex(sourceBuilding.CurrentPosition.z)); + MapEncounterLayerDataCell encounterLayerDataCell = + sourceBuilding.Combat.EncounterLayerData.mapEncounterLayerDataCells[buildingPoint.Z, buildingPoint.X]; + float buildingHeight = encounterLayerDataCell.GetBuildingHeight() * 2f; - //battleArmor.StatCollection.Set("irbtmu_immobile_unit", true); - if (shrinkRep) - { - //var baseScale = battleArmor.GameRep.transform.localScale; - //ModState.SavedBAScale.Add(battleArmor.GUID, baseScale); - battleArmor.GameRep.transform.localScale = new Vector3(.01f, .01f, .01f); - battleArmorAsMech.GameRep.ToggleHeadlights(false); - } + Vector3[] positions = new Vector3[5]; + var pos1 = new Vector3(-10f, buildingHeight, -10f); + var pos2 = new Vector3(10f, buildingHeight, -10f); + var pos3 = new Vector3(0f, buildingHeight, 0f); + var pos4 = new Vector3(-10f, buildingHeight, 10f); + var pos5 = new Vector3(10f, buildingHeight, 10f); - if (!ModState.BADamageTrackers.ContainsKey(battleArmorAsMech.GUID)) - { - ModState.BADamageTrackers.Add(battleArmorAsMech.GUID, new Classes.BA_DamageTracker(carrier.GUID, false, new Dictionary())); - } + positions[0] = pos1; + positions[1] = pos2; + positions[2] = pos3; + positions[3] = pos4; + positions[4] = pos5; - var tracker = ModState.BADamageTrackers[battleArmorAsMech.GUID]; - if (tracker.TargetGUID != carrier.GUID) - { - tracker.TargetGUID = carrier.GUID; - tracker.BA_MountedLocations = new Dictionary(); - } + return positions; + } - if (isFriendly) - { - ModState.PositionLockMount.Add(battleArmor.GUID, carrier.GUID); + public static bool GetHasBattleArmorMounts(this AbstractActor actor) + { + return actor.StatCollection.GetValue("HasBattleArmorMounts"); + } - var internalCap = carrier.GetInternalBACap(); - var currentInternalSquads = carrier.GetInternalBASquads(); - if (currentInternalSquads < internalCap) - { - ModInit.modLog?.Info?.Write($"[MountBattleArmorToChassis] - target unit {carrier.DisplayName} has internal BA capacity of {internalCap}. Currently used: {currentInternalSquads}, mounting squad internally."); - carrier.ModifyInternalBASquads(1); - tracker.IsSquadInternal = true; - // try and set firing arc to 360? - battleArmor.FiringArc(360f); - //refresh firing button state to make sure firing ports are respected - var hud = CameraControl.Instance.HUD; - if (isPlayer && battleArmor == hud.selectedUnit && !carrier.HasFiringPorts()) - { - CameraControl.Instance.HUD.MechWarriorTray.FireButton.DisableButton(); - } - } + public static bool GetHasExternalMountedBattleArmor(this AbstractActor actor) + { + return actor.StatCollection.GetValue("HasExternalMountedBattleArmor"); + } - foreach (var BA_Effect in ModState.BA_MountSwarmEffects) - { - if (BA_Effect.TargetEffectType == Classes.ConfigOptions.BA_TargetEffectType.BOTH) - { - foreach (var effectData in BA_Effect.effects) - { - battleArmor.CreateEffect(effectData, null, effectData.Description.Id, -1, battleArmor); - } - } - if (BA_Effect.TargetEffectType == Classes.ConfigOptions.BA_TargetEffectType.MOUNT_EXT && !tracker.IsSquadInternal) - { - foreach (var effectData in BA_Effect.effects) - { - battleArmor.CreateEffect(effectData, null, effectData.Description.Id, -1, battleArmor); - } - } - if (BA_Effect.TargetEffectType == Classes.ConfigOptions.BA_TargetEffectType.MOUNT_INT && tracker.IsSquadInternal) - { - foreach (var effectData in BA_Effect.effects) - { - battleArmor.CreateEffect(effectData, null, effectData.Description.Id, -1, battleArmor); - } - } - if (BA_Effect.TargetEffectType == Classes.ConfigOptions.BA_TargetEffectType.MOUNTTARGET || - BA_Effect.TargetEffectType == Classes.ConfigOptions.BA_TargetEffectType.BOTHTARGET) - { - foreach (var effectData in BA_Effect.effects) - { - carrier.CreateEffect(effectData, null, effectData.Description.Id, -1, battleArmor); - } - } - } + public static int GetInternalBACap(this AbstractActor actor) + { + return actor.StatCollection.GetValue("InternalBattleArmorSquadCap"); + } - if (tracker.IsSquadInternal) return; - carrier.SetHasExternalMountedBattleArmor(true); + public static int GetInternalBASquads(this AbstractActor actor) + { + return actor.StatCollection.GetValue("InternalBattleArmorSquads"); + } + + public static bool GetIsBattleArmorHandsy(this AbstractActor actor) + { + return actor.StatCollection.GetValue("IsBattleArmorHandsy"); + } + + public static bool GetIsUnMountable(this AbstractActor actor) + { + return actor.StatCollection.GetValue("IsUnmountableBattleArmor"); + } + + public static bool GetIsUnSwarmable(this AbstractActor actor) + { + return (actor is TrooperSquad || actor.StatCollection.GetValue("IsUnswarmableBattleArmor")); + } + + public static LineOfFireLevel GetLineOfFireForGarrison(this ICombatant source, AbstractActor garrisonSquad, + Vector3 sourcePosition, + ICombatant target, Vector3 targetPosition, Quaternion targetRotation, out Vector3 collisionWorldPos) + { + collisionWorldPos = targetPosition; + if (source is BattleTech.Building building) + { + Vector3 forward = targetPosition - sourcePosition; + forward.y = 0f; + Quaternion rotation = Quaternion.LookRotation(forward); + //Vector3[] lossourcePositions = source.GetLOSTargetPositions(sourcePosition, rotation); + + Vector3[] lossourcePositions = building.GetGarrisionLOSSourcePositions(); + + Vector3[] lostargetPositions = target.GetLOSTargetPositions(targetPosition, targetRotation); + List list = new List(source.Combat.GetAllLivingActors()); + list.Remove(garrisonSquad); + AbstractActor abstractActor = target as AbstractActor; + string text = null; + if (abstractActor != null) + { + list.Remove(abstractActor); } else { - ModState.PositionLockSwarm.Add(battleArmor.GUID, carrier.GUID); + text = target.GUID; } - foreach (ChassisLocations battleArmorChassisLoc in Enum.GetValues(typeof(ChassisLocations))) + LineSegment lineSegment = new LineSegment(sourcePosition, targetPosition); + list.Sort((AbstractActor x, AbstractActor y) => Vector3.SqrMagnitude(x.CurrentPosition - sourcePosition) + .CompareTo(Vector3.SqrMagnitude(y.CurrentPosition - sourcePosition))); + float num = Vector3.SqrMagnitude(sourcePosition - targetPosition); + for (int i = list.Count - 1; i >= 0; i--) { - var locationDef = battleArmorAsMech.MechDef.Chassis.GetLocationDef(battleArmorChassisLoc); - - var statname = battleArmorAsMech.GetStringForStructureDamageLevel(battleArmorChassisLoc); - if (string.IsNullOrEmpty(statname) || !battleArmorAsMech.StatCollection.ContainsStatistic(statname)) continue; - if (battleArmorAsMech.GetLocationDamageLevel(battleArmorChassisLoc) == LocationDamageLevel.Destroyed) continue; // why did i do this? + if (list[i].IsDead || Vector3.SqrMagnitude(list[i].CurrentPosition - sourcePosition) > num || + lineSegment.DistToPoint(list[i].CurrentPosition) > list[i].Radius * 5f) + { + list.RemoveAt(i); + } + } - if (locationDef.MaxArmor > 0f || locationDef.InternalStructure > 1f) + float num2 = 0f; + float num3 = 0f; + float num4 = 0f; + float num5 = 999999.9f; + Weapon longestRangeWeapon = garrisonSquad.GetLongestRangeWeapon(false, false); + float num6 = (longestRangeWeapon == null) ? 0f : longestRangeWeapon.MaxRange; + float adjustedSpotterRange = + garrisonSquad.Combat.LOS.GetAdjustedSpotterRange(garrisonSquad, abstractActor); + num6 = Mathf.Max(num6, adjustedSpotterRange); + float num7 = Mathf.Pow(num6, 2f); + for (int j = 0; j < lossourcePositions.Length; j++) + { + for (int k = 0; k < lostargetPositions.Length; k++) { - if (carrier is Mech mech) + num3 += 1f; + if (Vector3.SqrMagnitude(lossourcePositions[j] - lostargetPositions[k]) <= num7) { - if (carrier.team.IsEnemy(battleArmorAsMech.team)) + lineSegment = new LineSegment(lossourcePositions[j], lostargetPositions[k]); + bool flag = false; + Vector3 vector; + if (text == null) { - var hasLoopedOnce = false; - loopagain: - foreach (ArmorLocation tgtMechLoc in ModState.MechArmorSwarmOrder) + for (int l = 0; l < list.Count; l++) { - if (tracker.BA_MountedLocations.ContainsValue((int)tgtMechLoc) && !hasLoopedOnce) - { - continue; - } - var chassisLocSwarm = - MechStructureRules.GetChassisLocationFromArmorLocation(tgtMechLoc); - if (mech.GetLocationDamageLevel(chassisLocSwarm) > LocationDamageLevel.Penalized) continue; - if (!tracker.BA_MountedLocations.ContainsKey((int)battleArmorChassisLoc)) + if (lineSegment.DistToPoint(list[l].CurrentPosition) < list[l].Radius) { - ModInit.modLog?.Info?.Write($"[MountBattleArmorToChassis] - mounting BA {battleArmorChassisLoc} to enemy mech location {tgtMechLoc}."); - tracker.BA_MountedLocations.Add((int)battleArmorChassisLoc, (int)tgtMechLoc); - break; + vector = NvMath.NearestPointStrict(lossourcePositions[j], lostargetPositions[k], + list[l].CurrentPosition); + float num8 = Vector3.Distance(vector, list[l].CurrentPosition); + if (num8 < list[l].HighestLOSPosition.y) + { + flag = true; + num4 += 1f; + if (num8 < num5) + { + num5 = num8; + collisionWorldPos = vector; + break; + } + + break; + } } - hasLoopedOnce = true; - goto loopagain; } } - else if (carrier.team.IsFriendly(battleArmorAsMech.team)) + + if (source.Combat.LOS.HasLineOfFire(lossourcePositions[j], lostargetPositions[k], text, + num6, out vector)) { - var hasLoopedOnce = false; - loopagain: - foreach (ArmorLocation tgtMechLoc in ModState.MechArmorMountOrder) + num2 += 1f; + if (text != null) { - if (tracker.BA_MountedLocations.ContainsValue((int)tgtMechLoc) && !hasLoopedOnce) - { - continue; - } - var chassisLocSwarm = - MechStructureRules.GetChassisLocationFromArmorLocation(tgtMechLoc); - if (mech.GetLocationDamageLevel(chassisLocSwarm) > LocationDamageLevel.Penalized) continue; - if (!tracker.BA_MountedLocations.ContainsKey((int)battleArmorChassisLoc)) - { - ModInit.modLog?.Info?.Write($"[MountBattleArmorToChassis] - mounting BA {battleArmorChassisLoc} to friendly mech location {tgtMechLoc}."); - tracker.BA_MountedLocations.Add((int)battleArmorChassisLoc, (int)tgtMechLoc); - break; - } - hasLoopedOnce = true; - goto loopagain; + break; } } - } - else if (carrier is Vehicle vehicle) - { - var hasLoopedOnce = false; - loopagain: - foreach (VehicleChassisLocations tgtVicLoc in ModState.VehicleMountOrder) + else { - if (tracker.BA_MountedLocations.ContainsValue((int)tgtVicLoc) && !hasLoopedOnce) + if (flag) { - continue; + num4 -= 1f; } - if (vehicle.GetLocationDamageLevel(tgtVicLoc) > LocationDamageLevel.Penalized) continue; - if (!tracker.BA_MountedLocations.ContainsKey((int)battleArmorChassisLoc)) + float num8 = Vector3.Distance(vector, sourcePosition); + if (num8 < num5) { - ModInit.modLog?.Info?.Write($"[MountBattleArmorToChassis] - mounting BA {battleArmorChassisLoc} to vehicle location {tgtVicLoc}."); - tracker.BA_MountedLocations.Add((int)battleArmorChassisLoc, (int)tgtVicLoc); - break; + num5 = num8; + collisionWorldPos = vector; } - hasLoopedOnce = true; - goto loopagain; } } - else if (carrier is Turret turret) - { - ModInit.modLog?.Info?.Write($"[MountBattleArmorToChassis] - mounting BA {battleArmorChassisLoc} to turret location {BuildingLocation.Structure}."); - tracker.BA_MountedLocations.Add((int)battleArmorChassisLoc, (int)BuildingLocation.Structure); - } - else - { - //borken - } + } + + if (text != null && num2 > 0.5f) + { + break; } } - } - } - public static bool HasGarrisonedUnits(this BattleTech.Building building) - { - return ModState.PositionLockGarrison.Any(x=>x.Value.BuildingGUID == building.GUID); - } - public static bool IsGarrisonedInTargetBuilding(this AbstractActor actor, BattleTech.Building building) - { - return ModState.PositionLockGarrison.ContainsKey(actor.GUID) && ModState.PositionLockGarrison[actor.GUID].BuildingGUID == building.GUID; - } - public static bool IsGarrisoned(this AbstractActor actor) - { - return ModState.PositionLockGarrison.ContainsKey(actor.GUID); - } + float num9 = (text == null) ? (num2 / num3) : num2; + float b = num9 - source.Combat.Constants.Visibility.MinRatioFromActors; + float num10 = Mathf.Min(num4 / num3, b); + if (num10 > 0.001f) + { + num9 -= num10; + } - public static float GetMovementDeSwarmMinChance(this AbstractActor actor) - { - return actor.StatCollection.GetValue("MovementDeSwarmMinChance"); - } - public static float GetMovementDeSwarmMaxChance(this AbstractActor actor) - { - return actor.StatCollection.GetValue("MovementDeSwarmMaxChance"); - } - public static float GetMovementDeSwarmEvasivePipsFactor(this AbstractActor actor) - { - return actor.StatCollection.GetValue("MovementDeSwarmEvasivePipsFactor"); - } - public static float GetMovementDeSwarmEvasiveJumpMovementMultiplier(this AbstractActor actor) - { - return actor.StatCollection.GetValue("MovementDeSwarmEvasiveJumpMovementMultiplier"); - } + if (num9 >= source.Combat.Constants.Visibility.RatioFullVis) + { + return LineOfFireLevel.LOFClear; + } - public static bool HasFiringPorts(this AbstractActor actor) - { - return actor.StatCollection.GetValue("HasFiringPorts"); - } + if (num9 >= source.Combat.Constants.Visibility.RatioObstructedVis) + { + return LineOfFireLevel.LOFObstructed; + } - public static bool CanSwarm(this AbstractActor actor) - { - if (actor.GetStaticUnitTags().Contains(ModInit.modSettings.DisableAISwarmTag) && actor.team is AITeam) return false; - return actor.StatCollection.GetValue("CanSwarm"); + return LineOfFireLevel.LOFBlocked; + } + return LineOfFireLevel.LOFBlocked; } - public static bool CanRideInternalOnly(this AbstractActor actor) + public static float GetMovementDeSwarmEvasiveJumpMovementMultiplier(this AbstractActor actor) { - return actor.StatCollection.GetValue("BattleArmorInternalMountsOnly"); + return actor.StatCollection.GetValue("MovementDeSwarmEvasiveJumpMovementMultiplier"); } - public static int GetInternalBACap(this AbstractActor actor) - { - return actor.StatCollection.GetValue("InternalBattleArmorSquadCap"); - } - public static int GetInternalBASquads(this AbstractActor actor) + public static float GetMovementDeSwarmEvasivePipsFactor(this AbstractActor actor) { - return actor.StatCollection.GetValue("InternalBattleArmorSquads"); + return actor.StatCollection.GetValue("MovementDeSwarmEvasivePipsFactor"); } - public static void ModifyInternalBASquads(this AbstractActor actor, int value) + public static float GetMovementDeSwarmMaxChance(this AbstractActor actor) { - actor.StatCollection.ModifyStat("BAMountDismount", -1, "InternalBattleArmorSquads", StatCollection.StatOperation.Int_Add, value); + return actor.StatCollection.GetValue("MovementDeSwarmMaxChance"); } - public static int GetAvailableInternalBASpace(this AbstractActor actor) - { - return actor.StatCollection.GetValue("InternalBattleArmorSquadCap") - actor.StatCollection.GetValue("InternalBattleArmorSquads"); - } - public const string BA_Simlink = "BA_Simlink_"; - public static string GetBASimLink(this MechDef mechDef) + public static float GetMovementDeSwarmMinChance(this AbstractActor actor) { - foreach (var tag in mechDef.MechTags) - { - if (tag.StartsWith(BA_Simlink)) return tag; - } - return null; + return actor.StatCollection.GetValue("MovementDeSwarmMinChance"); } + public static int GetTotalBASpaceMechDef(this MechDef mechDef) { var capacity = 0; @@ -803,42 +795,71 @@ public static int GetTotalBASpaceMechDef(this MechDef mechDef) return capacity; } - public static bool GetHasBattleArmorMounts(this AbstractActor actor) + public static void HandleBattleArmorFallingDamage(this TrooperSquad squad) { - return actor.StatCollection.GetValue("HasBattleArmorMounts"); + var dmg = squad.StatCollection.GetValue("DFASelfDamage"); + var trooperLocs = squad.GetPossibleHitLocations(squad); + for (int i = 0; i < trooperLocs.Count; i++) + { + var hitinfo = new WeaponHitInfo(-1, -1, 0, 0, squad.GUID, + squad.GUID, 1, new float[1], new float[1], new float[1], + new bool[1], new int[trooperLocs[i]], new int[1], new AttackImpactQuality[1], + new AttackDirection[1], new Vector3[1], new string[1], new int[trooperLocs[i]]); + + squad.TakeWeaponDamage(hitinfo, trooperLocs[i], + squad.MeleeWeapon, dmg, + 0, 0, DamageType.DFASelf); + } } - public static bool GetHasExternalMountedBattleArmor(this AbstractActor actor) + public static bool HasFiringPorts(this AbstractActor actor) { - return actor.StatCollection.GetValue("HasExternalMountedBattleArmor"); + return actor.StatCollection.GetValue("HasFiringPorts"); } - public static void SetHasExternalMountedBattleArmor(this AbstractActor actor, bool value) + public static bool HasGarrisonedUnits(this BattleTech.Building building) { - actor.StatCollection.ModifyStat("BAMountDismount", -1, "HasExternalMountedBattleArmor", StatCollection.StatOperation.Set, value); + return ModState.PositionLockGarrison.Any(x=>x.Value.BuildingGUID == building.GUID); } - public static bool GetIsBattleArmorHandsy(this AbstractActor actor) + public static bool HasMountedUnits(this AbstractActor actor) { - return actor.StatCollection.GetValue("IsBattleArmorHandsy"); + return ModState.PositionLockMount.ContainsValue(actor.GUID); } - public static bool GetIsUnMountable(this AbstractActor actor) + public static bool HasSwarmingUnits(this AbstractActor actor) { - return actor.StatCollection.GetValue("IsUnmountableBattleArmor"); + return ModState.PositionLockSwarm.ContainsValue(actor.GUID); } - public static bool GetIsUnSwarmable(this AbstractActor actor) + + public static bool IsAvailableBAAbility(this Ability ability) { - return (actor is TrooperSquad || actor.StatCollection.GetValue("IsUnswarmableBattleArmor")); - } + var flag = true; + if (ability.parentComponent != null) + { + flag = ability.parentComponent.IsFunctional; + ModInit.modLog?.Trace?.Write($"[IsAvailableBAAbility] - {ability.parentComponent.parent.DisplayName} has parentComponent for ability {ability.Def.Description.Name}. Component functional? {flag}."); + if (!flag) + { + if (ability.parentComponent.parent.ComponentAbilities.Any(x => + x.parentComponent.IsFunctional && x.Def.Id == ability.Def.Id)) + { + flag = true; + ModInit.modLog?.Trace?.Write($"[IsAvailableBAAbility] - {ability.parentComponent.parent.DisplayName} has other component with same ability {ability.Def.Description.Name}. Component functional? {flag}."); + } + } + } + return ability.CurrentCooldown < 1 && (ability.Def.NumberOfUses < 1 || ability.NumUsesLeft > 0) && flag; + } // need to redo Ability.Activate from start, completely override for BA? Or just put ability on hidden componenet and ignore this shit. - public static bool HasMountedUnits(this AbstractActor actor) + public static bool IsGarrisoned(this AbstractActor actor) { - return ModState.PositionLockMount.ContainsValue(actor.GUID); + return ModState.PositionLockGarrison.ContainsKey(actor.GUID); } - public static bool IsMountedUnit(this AbstractActor actor) + + public static bool IsGarrisonedInTargetBuilding(this AbstractActor actor, BattleTech.Building building) { - return ModState.PositionLockMount.ContainsKey(actor.GUID); + return ModState.PositionLockGarrison.ContainsKey(actor.GUID) && ModState.PositionLockGarrison[actor.GUID].BuildingGUID == building.GUID; } public static bool IsMountedInternal(this AbstractActor actor) @@ -851,279 +872,305 @@ public static bool IsMountedToUnit(this AbstractActor actor, AbstractActor targe return ModState.PositionLockMount.ContainsKey(actor.GUID) && ModState.PositionLockMount[actor.GUID] == target.GUID; } - public static bool HasSwarmingUnits(this AbstractActor actor) + public static bool IsMountedUnit(this AbstractActor actor) { - return ModState.PositionLockSwarm.ContainsValue(actor.GUID); + return ModState.PositionLockMount.ContainsKey(actor.GUID); + } + + public static bool IsSwarmingTargetUnit(this AbstractActor actor, AbstractActor target) + { + return ModState.PositionLockSwarm.ContainsKey(actor.GUID) && ModState.PositionLockSwarm[actor.GUID] == target.GUID; } public static bool IsSwarmingUnit(this AbstractActor actor) { return ModState.PositionLockSwarm.ContainsKey(actor.GUID); } - public static bool IsSwarmingTargetUnit(this AbstractActor actor, AbstractActor target) + + public static void ModifyInternalBASquads(this AbstractActor actor, int value) { - return ModState.PositionLockSwarm.ContainsKey(actor.GUID) && ModState.PositionLockSwarm[actor.GUID] == target.GUID; + actor.StatCollection.ModifyStat("BAMountDismount", -1, "InternalBattleArmorSquads", StatCollection.StatOperation.Int_Add, value); } - public static Vector3 FetchRandomAdjacentHex(this AbstractActor actor) + public static void MountBattleArmorToChassis(this AbstractActor carrier, AbstractActor battleArmor, bool shrinkRep, bool isFriendly) { - var points = actor.Combat.HexGrid.GetAdjacentPointsOnGrid(actor.CurrentPosition); - var validPoints = new List(); - for (int i = 0; i < points.Count; i++) + battleArmor.TeleportActor(carrier.CurrentPosition); + var isPlayer = battleArmor.team.IsLocalPlayer; + if (battleArmor is Mech battleArmorAsMech) { - Vector3 resultPos = Vector3.zero; - var walkGrid = actor.Pathing.WalkingGrid;//Traverse.Create(actor.Pathing).Property("WalkingGrid").GetValue(); - var pathNode = walkGrid.GetClosestPathNode(points[i], 0f, 1000f, points[i], ref resultPos, - out var resultAngle, false, false); - if (pathNode != null) + //add irbtu immobile tag? + //Statistic irbtmu_immobile_unit = battleArmor.StatCollection.GetStatistic("irbtmu_immobile_unit"); + //if (!battleArmor.StatCollection.ContainsStatistic("irbtmu_immobile_unit")) + //{ + // battleArmor.StatCollection.AddStatistic("irbtmu_immobile_unit", false); + //} + + //battleArmor.StatCollection.Set("irbtmu_immobile_unit", true); + if (shrinkRep) { - var list = walkGrid.BuildPathFromEnd(pathNode, 1000f, resultPos, points[i], null, out var costLeft, out resultPos, out resultAngle); - if (list != null && list.Count > 0) - { - validPoints.Add(resultPos); - } + //var baseScale = battleArmor.GameRep.transform.localScale; + //ModState.SavedBAScale.Add(battleArmor.GUID, baseScale); + battleArmor.GameRep.transform.localScale = new Vector3(.01f, .01f, .01f); + battleArmorAsMech.GameRep.ToggleHeadlights(false); } - } - var point = actor.CurrentPosition; - point.y = actor.Combat.MapMetaData.GetLerpedHeightAt(point, false); - if (validPoints.Count > 0) - { - point = validPoints.GetRandomElement(); - point.y = actor.Combat.MapMetaData.GetLerpedHeightAt(point, false); - } - return point; - } - public static void DismountBA(this AbstractActor actor, AbstractActor carrier, Vector3 locationOverride, bool isFriendly, bool calledFromDeswarm = false, - bool calledFromHandleDeath = false, bool unShrinkRep = true) - { - if (actor is TrooperSquad squad) - { - //if (squad.StatCollection.ContainsStatistic("irbtmu_immobile_unit")) squad.StatCollection.Set("irbtmu_immobile_unit", false); - if (ModState.BADamageTrackers.ContainsKey(actor.GUID)) + if (!ModState.BADamageTrackers.ContainsKey(battleArmorAsMech.GUID)) { - if (isFriendly) - { - if (ModState.BADamageTrackers[actor.GUID].IsSquadInternal) - { - carrier.ModifyInternalBASquads(-1); - ModInit.modLog?.Info?.Write( - $"[DismountBA] Dismounted {actor.DisplayName} from internal capacity. Capacity is now {carrier.GetAvailableInternalBASpace()}."); - squad.FiringArc(90f); //reset to 90? - } - else carrier.SetHasExternalMountedBattleArmor(false); - } + ModState.BADamageTrackers.Add(battleArmorAsMech.GUID, new Classes.BA_DamageTracker(carrier.GUID, false, new Dictionary())); + } - ModState.BADamageTrackers.Remove(actor.GUID); + var tracker = ModState.BADamageTrackers[battleArmorAsMech.GUID]; + if (tracker.TargetGUID != carrier.GUID) + { + tracker.TargetGUID = carrier.GUID; + tracker.BA_MountedLocations = new Dictionary(); } - var em = actor.Combat.EffectManager; - foreach (var BA_effect in ModState.BA_MountSwarmEffects) + if (isFriendly) { - foreach (var effectProper in BA_effect.effects) - { - ModInit.modLog?.Trace?.Write( - $"[DismountBA] DEBUGGINS Checking for {effectProper.Description.Name} with id {effectProper.Description.Id} on {actor.DisplayName}."); - var effects = em.GetAllEffectsTargetingWithBaseID(actor, effectProper.Description.Id); + ModState.PositionLockMount.Add(battleArmor.GUID, carrier.GUID); - for (int i = effects.Count - 1; i >= 0; i--) + var internalCap = carrier.GetInternalBACap(); + var currentInternalSquads = carrier.GetInternalBASquads(); + if (currentInternalSquads < internalCap) + { + ModInit.modLog?.Info?.Write($"[MountBattleArmorToChassis] - target unit {carrier.DisplayName} has internal BA capacity of {internalCap}. Currently used: {currentInternalSquads}, mounting squad internally."); + carrier.ModifyInternalBASquads(1); + tracker.IsSquadInternal = true; + // try and set firing arc to 360? + battleArmor.FiringArc(360f); + //refresh firing button state to make sure firing ports are respected + var hud = CameraControl.Instance.HUD; + if (isPlayer && battleArmor == hud.selectedUnit && !carrier.HasFiringPorts()) { - ModInit.modLog?.Info?.Write( - $"[DismountBA] Cancelling effect on {actor.DisplayName}: {effects[i].EffectData.Description.Name}."); - em.CancelEffect(effects[i]); + CameraControl.Instance.HUD.MechWarriorTray.FireButton.DisableButton(); } } - } - actor.FiringArc(actor.GetCustomInfo().FiringArc); - var hud = CameraControl.Instance.HUD;//SharedState.CombatHUD; - //var hud = Traverse.Create(CameraControl.Instance).Property("HUD").GetValue(); - //actor.GameRep.IsTargetable = true; - - ModState.PositionLockMount.Remove(actor.GUID); - ModState.PositionLockSwarm.Remove(actor.GUID); - ModState.CachedUnitCoordinates.Remove(carrier.GUID); - - if (unShrinkRep) - { - actor.GameRep.transform.localScale = new Vector3(1f, 1f, 1f); - //actor.GameRep.transform.localScale = ModState.SavedBAScale[actor.GUID]; - //ModState.SavedBAScale.Remove(actor.GUID); - squad.GameRep.ToggleHeadlights(true); - } - var point = carrier.CurrentPosition; - if (locationOverride != Vector3.zero) - { - point = locationOverride; - ModInit.modLog?.Info?.Write($"[DismountBA] Using location override {locationOverride}."); - } - - else if (calledFromDeswarm || calledFromHandleDeath) - { - point = carrier.FetchRandomAdjacentHex(); -; ModInit.modLog?.Info?.Write($"[DismountBA] Using adjacent hex {point} or fallback carrier loc {carrier.CurrentPosition}."); - } - point.y = actor.Combat.MapMetaData.GetLerpedHeightAt(point, false); - actor.TeleportActor(point); - if (actor is CustomMech customMech) - { - customMech.custGameRep.j_Root.localRotation = Quaternion.identity; - } - actor.GameRep.thisTransform.rotation = carrier.GameRep.thisTransform.rotation; - actor.CurrentRotation = carrier.CurrentRotation; - if (!calledFromHandleDeath && !calledFromDeswarm) - { - ModInit.modLog?.Info?.Write($"[DismountBA] Not called from HandleDeath or Deswarm, resetting pathing."); - if (actor.team.IsLocalPlayer) + foreach (var BA_Effect in ModState.BA_MountSwarmEffects) { - ModInit.modLog?.Info?.Write($"[DismountBA] Local player unit, resetting buttons."); - hud.MechWarriorTray.JumpButton.ResetButtonIfNotActive(actor); - hud.MechWarriorTray.SprintButton.ResetButtonIfNotActive(actor); - hud.MechWarriorTray.MoveButton.ResetButtonIfNotActive(actor); - hud.SelectionHandler.AddJumpState(actor); - hud.SelectionHandler.AddSprintState(actor); - hud.SelectionHandler.AddMoveState(actor); + if (BA_Effect.TargetEffectType == Classes.ConfigOptions.BA_TargetEffectType.BOTH) + { + foreach (var effectData in BA_Effect.effects) + { + battleArmor.CreateEffect(effectData, null, effectData.Description.Id, -1, battleArmor); + } + } + if (BA_Effect.TargetEffectType == Classes.ConfigOptions.BA_TargetEffectType.MOUNT_EXT && !tracker.IsSquadInternal) + { + foreach (var effectData in BA_Effect.effects) + { + battleArmor.CreateEffect(effectData, null, effectData.Description.Id, -1, battleArmor); + } + } + if (BA_Effect.TargetEffectType == Classes.ConfigOptions.BA_TargetEffectType.MOUNT_INT && tracker.IsSquadInternal) + { + foreach (var effectData in BA_Effect.effects) + { + battleArmor.CreateEffect(effectData, null, effectData.Description.Id, -1, battleArmor); + } + } + if (BA_Effect.TargetEffectType == Classes.ConfigOptions.BA_TargetEffectType.MOUNTTARGET || + BA_Effect.TargetEffectType == Classes.ConfigOptions.BA_TargetEffectType.BOTHTARGET) + { + foreach (var effectData in BA_Effect.effects) + { + carrier.CreateEffect(effectData, null, effectData.Description.Id, -1, battleArmor); + } + } } - ModInit.modLog?.Info?.Write( - $"[DismountBA] Local player unit, Not called from HandleDeath or Deswarm, resetting buttons and pathing."); - actor.ResetPathing(false); - actor.Pathing.UpdateCurrentPath(false); + + if (tracker.IsSquadInternal) return; + carrier.SetHasExternalMountedBattleArmor(true); } - - if (false) //(actor.HasBegunActivation) + else { - ModInit.modLog?.Info?.Write( - $"[DismountBA] Called from handledeath? {calledFromHandleDeath} or Deswarm? {calledFromDeswarm}, forcing end of activation."); // was i trying to end carrier activation maybe? - - var sequence = actor.DoneWithActor(); - actor.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(sequence)); - //actor.OnActivationEnd(actor.GUID, -1); + ModState.PositionLockSwarm.Add(battleArmor.GUID, carrier.GUID); } - //actor.VisibilityCache.UpdateCacheReciprocal(actor.Combat.GetAllLivingCombatants()); - - ModInit.modLog?.Info?.Write( - $"[DismountBA] Removing PositionLock with rider {actor.DisplayName} {actor.GUID} and carrier {carrier.DisplayName} {carrier.GUID} and rebuilding visibility cache."); - } - } + foreach (ChassisLocations battleArmorChassisLoc in Enum.GetValues(typeof(ChassisLocations))) + { + var locationDef = battleArmorAsMech.MechDef.Chassis.GetLocationDef(battleArmorChassisLoc); - public static void DismountGarrison(this TrooperSquad squad, BattleTech.Building building, Vector3 locationOverride, - bool calledFromHandleDeath = false) - { - var em = squad.Combat.EffectManager; - foreach (var BA_effect in ModState.BA_MountSwarmEffects) - { - if (BA_effect.TargetEffectType != Classes.ConfigOptions.BA_TargetEffectType.GARRISON && - BA_effect.TargetEffectType != Classes.ConfigOptions.BA_TargetEffectType.BOTH) continue; + var statname = battleArmorAsMech.GetStringForStructureDamageLevel(battleArmorChassisLoc); + if (string.IsNullOrEmpty(statname) || !battleArmorAsMech.StatCollection.ContainsStatistic(statname)) continue; + if (battleArmorAsMech.GetLocationDamageLevel(battleArmorChassisLoc) == LocationDamageLevel.Destroyed) continue; // why did i do this? - foreach (var effectProper in BA_effect.effects) - { - var effects = em.GetAllEffectsTargetingWithBaseID(squad, effectProper.Description.Id); - for (int i = effects.Count - 1; i >= 0; i--) + if (locationDef.MaxArmor > 0f || locationDef.InternalStructure > 1f) { - ModInit.modLog?.Info?.Write( - $"[DismountGarrison] Cancelling effect on {squad.DisplayName}: {effects[i].EffectData.Description.Name}."); - em.CancelEffect(effects[i]); + if (carrier is Mech mech) + { + if (carrier.team.IsEnemy(battleArmorAsMech.team)) + { + var hasLoopedOnce = false; + loopagain: + foreach (ArmorLocation tgtMechLoc in ModState.MechArmorSwarmOrder) + { + if (tracker.BA_MountedLocations.ContainsValue((int)tgtMechLoc) && !hasLoopedOnce) + { + continue; + } + var chassisLocSwarm = + MechStructureRules.GetChassisLocationFromArmorLocation(tgtMechLoc); + if (mech.GetLocationDamageLevel(chassisLocSwarm) > LocationDamageLevel.Penalized) continue; + if (!tracker.BA_MountedLocations.ContainsKey((int)battleArmorChassisLoc)) + { + ModInit.modLog?.Info?.Write($"[MountBattleArmorToChassis] - mounting BA {battleArmorChassisLoc} to enemy mech location {tgtMechLoc}."); + tracker.BA_MountedLocations.Add((int)battleArmorChassisLoc, (int)tgtMechLoc); + break; + } + hasLoopedOnce = true; + goto loopagain; + } + } + else if (carrier.team.IsFriendly(battleArmorAsMech.team)) + { + var hasLoopedOnce = false; + loopagain: + foreach (ArmorLocation tgtMechLoc in ModState.MechArmorMountOrder) + { + if (tracker.BA_MountedLocations.ContainsValue((int)tgtMechLoc) && !hasLoopedOnce) + { + continue; + } + var chassisLocSwarm = + MechStructureRules.GetChassisLocationFromArmorLocation(tgtMechLoc); + if (mech.GetLocationDamageLevel(chassisLocSwarm) > LocationDamageLevel.Penalized) continue; + if (!tracker.BA_MountedLocations.ContainsKey((int)battleArmorChassisLoc)) + { + ModInit.modLog?.Info?.Write($"[MountBattleArmorToChassis] - mounting BA {battleArmorChassisLoc} to friendly mech location {tgtMechLoc}."); + tracker.BA_MountedLocations.Add((int)battleArmorChassisLoc, (int)tgtMechLoc); + break; + } + hasLoopedOnce = true; + goto loopagain; + } + } + } + else if (carrier is Vehicle vehicle) + { + var hasLoopedOnce = false; + loopagain: + foreach (VehicleChassisLocations tgtVicLoc in ModState.VehicleMountOrder) + { + if (tracker.BA_MountedLocations.ContainsValue((int)tgtVicLoc) && !hasLoopedOnce) + { + continue; + } + + if (vehicle.GetLocationDamageLevel(tgtVicLoc) > LocationDamageLevel.Penalized) continue; + if (!tracker.BA_MountedLocations.ContainsKey((int)battleArmorChassisLoc)) + { + ModInit.modLog?.Info?.Write($"[MountBattleArmorToChassis] - mounting BA {battleArmorChassisLoc} to vehicle location {tgtVicLoc}."); + tracker.BA_MountedLocations.Add((int)battleArmorChassisLoc, (int)tgtVicLoc); + break; + } + hasLoopedOnce = true; + goto loopagain; + } + } + else if (carrier is Turret turret) + { + ModInit.modLog?.Info?.Write($"[MountBattleArmorToChassis] - mounting BA {battleArmorChassisLoc} to turret location {BuildingLocation.Structure}."); + tracker.BA_MountedLocations.Add((int)battleArmorChassisLoc, (int)BuildingLocation.Structure); + } + else + { + //borken + } } } + if (ModInit.modSettings.ReworkedCarrierEvasion) battleArmor.MountedEvasion(carrier); } - squad.FiringArc(squad.GetCustomInfo().FiringArc); - var hud = CameraControl.Instance.HUD;//SharedState.CombatHUD; - //var hud = Traverse.Create(CameraControl.Instance).Property("HUD").GetValue(); - //actor.GameRep.IsTargetable = true; + } - if (ModState.GarrisonFriendlyTeam.ContainsKey(building.GUID)) + public static string ProcessBattleArmorSpawnWeights(this Classes.BA_FactionAssoc BaWgts, DataManager dm, string factionID, string type) + { + if (ModState.CachedFactionAssociations.ContainsKey(factionID)) { - if (!ModState.GarrisonFriendlyTeam[building.GUID]) + if (ModState.CachedFactionAssociations[factionID][type].Count > 0) { - building.AddToTeam(squad?.Combat?.Teams?.FirstOrDefault(x => x?.GUID == "421027ec-8480-4cc6-bf01-369f84a22012")); // add back to world team. + return ModState.CachedFactionAssociations[factionID][type].GetRandomElement(); } } - - if (!calledFromHandleDeath) - { - locationOverride = ModState.PositionLockGarrison[squad.GUID].OriginalSquadPos; - } - squad.GameRep.transform.localScale = new Vector3(1f, 1f, 1f); - squad.GameRep.ToggleHeadlights(true); - ModState.PositionLockGarrison.Remove(squad.GUID); - - var point = building.CurrentPosition; - var hk = Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl); - - if (!hk) + foreach (var faction in BaWgts.FactionIDs) { - if (locationOverride != Vector3.zero) + if (!ModState.CachedFactionAssociations.ContainsKey(faction)) { - point = locationOverride; - ModInit.modLog?.Info?.Write($"[DismountGarrison] Using location override {locationOverride}."); - } - } - - point.y = squad.Combat.MapMetaData.GetLerpedHeightAt(point, false); - squad.TeleportActor(point); + ModState.CachedFactionAssociations.Add(faction, new Dictionary>()); + ModState.CachedFactionAssociations[faction].Add("InternalBattleArmorWeight", new List()); + ModState.CachedFactionAssociations[faction].Add("MountedBattleArmorWeight", new List()); + ModState.CachedFactionAssociations[faction].Add("HandsyBattleArmorWeight", new List()); + foreach (var BaTypeInternal in BaWgts.InternalBattleArmorWeight) + { + if (dm.Exists(BattleTechResourceType.MechDef, BaTypeInternal.Key) || BaTypeInternal.Key == "BA_EMPTY") + { + ModInit.modLog?.Trace?.Write( + $"[ProcessBattleArmorSpawnWeights - InternalBattleArmorWeight] Processing spawn weights for {BaTypeInternal.Key} and weight {BaTypeInternal.Value}"); + for (int i = 0; i < BaTypeInternal.Value; i++) + { + ModState.CachedFactionAssociations[faction]["InternalBattleArmorWeight"] + .Add(BaTypeInternal.Key); + ModInit.modLog?.Trace?.Write( + $"[ProcessBattleArmorSpawnWeights - InternalBattleArmorWeight] spawn list has {ModState.CachedFactionAssociations[faction]["InternalBattleArmorWeight"].Count} entries"); + } + } + } + foreach (var BaTypeMount in BaWgts.MountedBattleArmorWeight) + { + if (dm.Exists(BattleTechResourceType.MechDef, BaTypeMount.Key) || BaTypeMount.Key == "BA_EMPTY") + { + ModInit.modLog?.Trace?.Write( + $"[ProcessBattleArmorSpawnWeights - MountedBattleArmorWeight] Processing spawn weights for {BaTypeMount.Key} and weight {BaTypeMount.Value}"); + for (int i = 0; i < BaTypeMount.Value; i++) + { + ModState.CachedFactionAssociations[faction]["MountedBattleArmorWeight"] + .Add(BaTypeMount.Key); + ModInit.modLog?.Trace?.Write( + $"[ProcessBattleArmorSpawnWeights - MountedBattleArmorWeight] spawn list has {ModState.CachedFactionAssociations[faction]["MountedBattleArmorWeight"].Count} entries"); + } + } + } - if (!calledFromHandleDeath) - { - ModInit.modLog?.Info?.Write($"[DismountGarrison] Not called from HandleDeath or Deswarm, resetting pathing."); - if (squad.team.IsLocalPlayer) + foreach (var BaTypeHandsy in BaWgts.HandsyBattleArmorWeight) + { + if (dm.Exists(BattleTechResourceType.MechDef, BaTypeHandsy.Key) || BaTypeHandsy.Key == "BA_EMPTY") + { + ModInit.modLog?.Trace?.Write( + $"[ProcessBattleArmorSpawnWeights - HandsyBattleArmorWeight] Processing spawn weights for {BaTypeHandsy.Key} and weight {BaTypeHandsy.Value}"); + for (int i = 0; i < BaTypeHandsy.Value; i++) + { + ModState.CachedFactionAssociations[faction]["HandsyBattleArmorWeight"] + .Add(BaTypeHandsy.Key); + ModInit.modLog?.Trace?.Write( + $"[ProcessBattleArmorSpawnWeights - HandsyBattleArmorWeight] spawn list has {ModState.CachedFactionAssociations[faction]["HandsyBattleArmorWeight"].Count} entries"); + } + } + } + } + if (ModState.CachedFactionAssociations[faction][type].Count > 0) { - ModInit.modLog?.Info?.Write($"[DismountGarrison] Local player unit, resetting buttons."); - hud.MechWarriorTray.JumpButton.ResetButtonIfNotActive(squad); - hud.MechWarriorTray.SprintButton.ResetButtonIfNotActive(squad); - hud.MechWarriorTray.MoveButton.ResetButtonIfNotActive(squad); - hud.SelectionHandler.AddJumpState(squad); - hud.SelectionHandler.AddSprintState(squad); - hud.SelectionHandler.AddMoveState(squad); + return ModState.CachedFactionAssociations[faction][type].GetRandomElement(); } - squad.ResetPathing(false); - squad.Pathing.UpdateCurrentPath(false); + } - - //squad.VisibilityCache.UpdateCacheReciprocal(squad.Combat.GetAllLivingCombatants()); - - ModInit.modLog?.Info?.Write( - $"[DismountGarrison] Removing PositionLock with rider {squad.DisplayName} {squad.GUID} and carrier {building.DisplayName} {building.GUID} and rebuilding visibility cache."); - + ModInit.modLog?.Error?.Write($"[ProcessBattleArmorSpawnWeights] no applicable config for this unit, returning empty list."); + return ""; } - public static Ability GetDeswarmerAbilityForAI(this AbstractActor actor, bool UseMovement = false) + public static void ProcessDeswarmMovement(this AbstractActor creator, List> swarmingUnits) { - var list = new List(); - - if (UseMovement) - { - if (!string.IsNullOrEmpty(ModInit.modSettings.DeswarmMovementConfig.AbilityDefID)) - { - var move = actor.GetPilot().Abilities - .FirstOrDefault(x => x.Def.Id == ModInit.modSettings.DeswarmMovementConfig.AbilityDefID) ?? actor.ComponentAbilities - .FirstOrDefault(x => x.Def.Id == ModInit.modSettings.DeswarmMovementConfig.AbilityDefID); - if (move != null) return move; - } - } - - if (!string.IsNullOrEmpty(ModInit.modSettings.BattleArmorDeSwarmSwat)) - { - var swat = actor.GetPilot().Abilities - .FirstOrDefault(x => x.Def.Id == ModInit.modSettings.BattleArmorDeSwarmSwat) ?? actor.ComponentAbilities - .FirstOrDefault(x => x.Def.Id == ModInit.modSettings.BattleArmorDeSwarmSwat); - if (swat != null) list.Add(swat); - } - - if (!string.IsNullOrEmpty(ModInit.modSettings.BattleArmorDeSwarmRoll)) - { - var roll = actor.GetPilot().Abilities - .FirstOrDefault(x => x.Def.Id == ModInit.modSettings.BattleArmorDeSwarmRoll) ?? actor.ComponentAbilities - .FirstOrDefault(x => x.Def.Id == ModInit.modSettings.BattleArmorDeSwarmRoll); - if (roll != null) list.Add(roll); - } - - if (list.Count > 0) + var DeswarmMovementInfo = new Classes.BA_DeswarmMovementInfo(creator); + ModInit.modLog?.Trace?.Write($"[ProcessDeswarmMovement] - {DeswarmMovementInfo.Carrier.DisplayName} set to {creator.DisplayName}."); + foreach (var swarmingUnit in swarmingUnits) { - return list.GetRandomElement(); + var swarmingUnitActor = creator.Combat.FindActorByGUID(swarmingUnit.Key); + DeswarmMovementInfo.SwarmingUnits.Add(swarmingUnitActor); + ModInit.modLog?.Trace?.Write($"[ProcessDeswarmMovement] - Added {swarmingUnitActor.DisplayName} to list of swarming."); } - return new Ability(new AbilityDef()); + ModState.DeSwarmMovementInfo = DeswarmMovementInfo; + ModInit.modLog?.Trace?.Write($"[ProcessDeswarmMovement] - Set modstate."); } public static void ProcessDeswarmRoll(this AbstractActor creator, List> swarmingUnits) @@ -1296,20 +1343,6 @@ public static void ProcessDeswarmSwat(this AbstractActor creator, } } - public static void ProcessDeswarmMovement(this AbstractActor creator, List> swarmingUnits) - { - var DeswarmMovementInfo = new Classes.BA_DeswarmMovementInfo(creator); - ModInit.modLog?.Trace?.Write($"[ProcessDeswarmMovement] - {DeswarmMovementInfo.Carrier.DisplayName} set to {creator.DisplayName}."); - foreach (var swarmingUnit in swarmingUnits) - { - var swarmingUnitActor = creator.Combat.FindActorByGUID(swarmingUnit.Key); - DeswarmMovementInfo.SwarmingUnits.Add(swarmingUnitActor); - ModInit.modLog?.Trace?.Write($"[ProcessDeswarmMovement] - Added {swarmingUnitActor.DisplayName} to list of swarming."); - } - ModState.DeSwarmMovementInfo = DeswarmMovementInfo; - ModInit.modLog?.Trace?.Write($"[ProcessDeswarmMovement] - Set modstate."); - } - public static void ProcessGarrisonBuilding(this TrooperSquad creator, BattleTech.Building targetBuilding) { var creatorActor = creator as AbstractActor; @@ -1370,7 +1403,7 @@ public static void ProcessGarrisonBuilding(this TrooperSquad creator, BattleTech creator.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(sequence)); } } - + public static void ProcessMountFriendly(this AbstractActor creator, AbstractActor targetActor) { if (creator is TrooperSquad squad) @@ -1505,221 +1538,207 @@ public static void ProcessSwarmEnemy(this Mech creatorMech, AbstractActor target } } - public static Vector3[] GetGarrisionLOSSourcePositions(this BattleTech.Building sourceBuilding) + public static void ReInitIndicator(this CombatHUD hud, AbstractActor actor) { - Point buildingPoint = new Point( - sourceBuilding.Combat.MapMetaData.GetXIndex(sourceBuilding.CurrentPosition.x), - sourceBuilding.Combat.MapMetaData.GetZIndex(sourceBuilding.CurrentPosition.z)); - MapEncounterLayerDataCell encounterLayerDataCell = - sourceBuilding.Combat.EncounterLayerData.mapEncounterLayerDataCells[buildingPoint.Z, buildingPoint.X]; - float buildingHeight = encounterLayerDataCell.GetBuildingHeight() * 2f; - - Vector3[] positions = new Vector3[5]; - var pos1 = new Vector3(-10f, buildingHeight, -10f); - var pos2 = new Vector3(10f, buildingHeight, -10f); - var pos3 = new Vector3(0f, buildingHeight, 0f); - var pos4 = new Vector3(-10f, buildingHeight, 10f); - var pos5 = new Vector3(10f, buildingHeight, 10f); - - positions[0] = pos1; - positions[1] = pos2; - positions[2] = pos3; - positions[3] = pos4; - positions[4] = pos5; - - return positions; + var indicators = hud.InWorldMgr.AttackDirectionIndicators;//Traverse.Create(hud.InWorldMgr).Field("AttackDirectionIndicators").GetValue>(); + foreach (var indicator in indicators) + { + if (indicator.Owner.GUID == actor.GUID) + { + indicator.Init(actor, hud); + } + } } - public static Vector3 GetEvacBuildingLocation(this AbstractActor squad, BattleTech.Building building) + public static void SetHasExternalMountedBattleArmor(this AbstractActor actor, bool value) { - //var ogl = ObstructionGameLogic.GetObstructionFromBuilding(building, squad.Combat.ItemRegistry); + actor.StatCollection.ModifyStat("BAMountDismount", -1, "HasExternalMountedBattleArmor", StatCollection.StatOperation.Set, value); + } - var posToCheck = - squad.Combat.HexGrid.GetGridPointsAroundPointWithinRadius(building.CurrentPosition, - 4); - var evacLoc = squad.team.OffScreenPosition; - foreach (var pos in posToCheck) + public static void SetPairingOverlay(this LanceConfiguratorPanel lanceConfiguratorPanel, LanceLoadoutSlot baLanceLoadoutSlot, bool showOverlay, + LanceLoadoutSlot carrierLanceLoadoutSlot = null) + { + if (ModState.DefaultOverlay == new Color()) { - ModInit.modLog?.Trace?.Write($"[GetEvacBuildingLocation] Checking position {pos}, is {Vector3.Distance(pos, building.CurrentPosition)} from building source {building.CurrentPosition}."); - var encounterLayerDataCell = - squad.Combat.EncounterLayerData.GetCellAt(pos); - if (encounterLayerDataCell == null) continue; - - var eldcBldgs = encounterLayerDataCell.buildingList; - var foundBuilding = false; - foreach (var bldg in eldcBldgs) + var overlayChildren = baLanceLoadoutSlot.SelectedMech.UnavailableOverlay.gameObject.GetComponentsInChildren(); + foreach (var overlayChild in overlayChildren) { - if (bldg.obstructionGameLogic.IsRealBuilding) + if (overlayChild.name == "stripes") { - foundBuilding = true; - ModInit.modLog?.Trace?.Write($"[GetEvacBuildingLocation] Found building {bldg.obstructionGameLogic.buildingDefId}."); - break; + var overlayChildImage = overlayChild.GetComponentInChildren(); + ModState.DefaultOverlay = new Color(overlayChildImage.color.r, overlayChildImage.color.g, overlayChildImage.color.b, overlayChildImage.color.a); + ModInit.modLog?.Trace?.Write( + $"[SetPairingOverlay] - set default overlay color to {ModState.DefaultOverlay} {ModState.DefaultOverlay.r} {ModState.DefaultOverlay.b} {ModState.DefaultOverlay.g} {ModState.DefaultOverlay.a}"); } } - - if (foundBuilding) - { - ModInit.modLog?.Trace?.Write($"[GetEvacBuildingLocation] Found building, continuing."); - continue; - } - - if (Vector3.Distance(pos, building.CurrentPosition) < - Vector3.Distance(evacLoc, building.CurrentPosition)) - { - evacLoc = pos; - } } - - if (evacLoc == squad.team.OffScreenPosition) + if (!showOverlay) { - ModInit.modLog?.Trace?.Write($"[GetEvacBuildingLocation] No location found, lerping to roof."); - evacLoc = building.CurrentPosition; - evacLoc.y = squad.Combat.MapMetaData.GetLerpedHeightAt(evacLoc); + baLanceLoadoutSlot.SelectedMech.UnavailableOverlay.SetActive(false); + return; } - return evacLoc; - } + var baOverlayChildren = baLanceLoadoutSlot.SelectedMech.UnavailableOverlay.gameObject.GetComponentsInChildren(); - public static LineOfFireLevel GetLineOfFireForGarrison(this ICombatant source, AbstractActor garrisonSquad, - Vector3 sourcePosition, - ICombatant target, Vector3 targetPosition, Quaternion targetRotation, out Vector3 collisionWorldPos) - { - collisionWorldPos = targetPosition; - if (source is BattleTech.Building building) + Image BAOverlayChildImage = null; + foreach (var baOverlayChild in baOverlayChildren) { - Vector3 forward = targetPosition - sourcePosition; - forward.y = 0f; - Quaternion rotation = Quaternion.LookRotation(forward); - //Vector3[] lossourcePositions = source.GetLOSTargetPositions(sourcePosition, rotation); - - Vector3[] lossourcePositions = building.GetGarrisionLOSSourcePositions(); - - Vector3[] lostargetPositions = target.GetLOSTargetPositions(targetPosition, targetRotation); - List list = new List(source.Combat.GetAllLivingActors()); - list.Remove(garrisonSquad); - AbstractActor abstractActor = target as AbstractActor; - string text = null; - if (abstractActor != null) - { - list.Remove(abstractActor); - } - else + if (baOverlayChild.name == "stripes") { - text = target.GUID; + BAOverlayChildImage = baOverlayChild.GetComponent(); } - - LineSegment lineSegment = new LineSegment(sourcePosition, targetPosition); - list.Sort((AbstractActor x, AbstractActor y) => Vector3.SqrMagnitude(x.CurrentPosition - sourcePosition) - .CompareTo(Vector3.SqrMagnitude(y.CurrentPosition - sourcePosition))); - float num = Vector3.SqrMagnitude(sourcePosition - targetPosition); - for (int i = list.Count - 1; i >= 0; i--) + } + + if (carrierLanceLoadoutSlot == null) + { + baLanceLoadoutSlot.SelectedMech.UnavailableOverlay.SetActive(true); + if (BAOverlayChildImage != null) + BAOverlayChildImage.color = ModState.PendingSelectionColor;//new Color(0, 0, 0, ModState.DefaultOverlay.a); + lanceConfiguratorPanel.ToggleOverlayPotentialCarriers(baLanceLoadoutSlot, true); + return; + } + baLanceLoadoutSlot.SelectedMech.UnavailableOverlay.SetActive(true); + carrierLanceLoadoutSlot.SelectedMech.UnavailableOverlay.SetActive(true); + carrierLanceLoadoutSlot.SelectedMech.UnavailableOverlay.SetActive(false); + var carrierOverlayChildren = carrierLanceLoadoutSlot.SelectedMech.UnavailableOverlay.gameObject.GetComponentsInChildren(); + var carrierPilotID = carrierLanceLoadoutSlot.SelectedPilot.Pilot.pilotDef.Description.Id; + foreach (var carrierOverlayChild in carrierOverlayChildren) + { + if (carrierOverlayChild.name == "stripes") { - if (list[i].IsDead || Vector3.SqrMagnitude(list[i].CurrentPosition - sourcePosition) > num || - lineSegment.DistToPoint(list[i].CurrentPosition) > list[i].Radius * 5f) + var carrierOverlayChildImage = carrierOverlayChild.GetComponent(); + if (!ModState.UsedOverlayColorsByCarrier.ContainsKey(carrierPilotID)) { - list.RemoveAt(i); - } - } + //initialize new overlay color + var foundUnused = false; - float num2 = 0f; - float num3 = 0f; - float num4 = 0f; - float num5 = 999999.9f; - Weapon longestRangeWeapon = garrisonSquad.GetLongestRangeWeapon(false, false); - float num6 = (longestRangeWeapon == null) ? 0f : longestRangeWeapon.MaxRange; - float adjustedSpotterRange = - garrisonSquad.Combat.LOS.GetAdjustedSpotterRange(garrisonSquad, abstractActor); - num6 = Mathf.Max(num6, adjustedSpotterRange); - float num7 = Mathf.Pow(num6, 2f); - for (int j = 0; j < lossourcePositions.Length; j++) - { - for (int k = 0; k < lostargetPositions.Length; k++) - { - num3 += 1f; - if (Vector3.SqrMagnitude(lossourcePositions[j] - lostargetPositions[k]) <= num7) + foreach (var potentialColor in ModState.ProcessedOverlayColors) { - lineSegment = new LineSegment(lossourcePositions[j], lostargetPositions[k]); - bool flag = false; - Vector3 vector; - if (text == null) + if (!ModState.UsedOverlayColors.Contains(potentialColor)) { - for (int l = 0; l < list.Count; l++) + ModState.UsedOverlayColors.Add(potentialColor); + ModState.UsedOverlayColorsByCarrier.Add(carrierPilotID, potentialColor); + lanceConfiguratorPanel.ToggleOverlayPotentialCarriers(baLanceLoadoutSlot, false, carrierLanceLoadoutSlot); + carrierLanceLoadoutSlot.SelectedMech.UnavailableOverlay.SetActive(true); + carrierOverlayChildImage.color = potentialColor; + ModInit.modLog?.Trace?.Write($"[SetPairingOverlay] - carrier overlay color set to {carrierOverlayChildImage.color.r} {carrierOverlayChildImage.color.g} {carrierOverlayChildImage.color.b}"); + if (BAOverlayChildImage != null) { - if (lineSegment.DistToPoint(list[l].CurrentPosition) < list[l].Radius) - { - vector = NvMath.NearestPointStrict(lossourcePositions[j], lostargetPositions[k], - list[l].CurrentPosition); - float num8 = Vector3.Distance(vector, list[l].CurrentPosition); - if (num8 < list[l].HighestLOSPosition.y) - { - flag = true; - num4 += 1f; - if (num8 < num5) - { - num5 = num8; - collisionWorldPos = vector; - break; - } - - break; - } - } + BAOverlayChildImage.color = potentialColor; + ModInit.modLog?.Trace?.Write($"[SetPairingOverlay] - BA overlay color set to {BAOverlayChildImage.color.r} {BAOverlayChildImage.color.g} {BAOverlayChildImage.color.b}"); } + foundUnused = true; + break; } + } - if (source.Combat.LOS.HasLineOfFire(lossourcePositions[j], lostargetPositions[k], text, - num6, out vector)) - { - num2 += 1f; - if (text != null) - { - break; - } - } - else + if (!foundUnused) + { + var chosenColor = ModState.UsedOverlayColors.GetRandomElement(); + ModState.UsedOverlayColorsByCarrier.Add(carrierPilotID, chosenColor); + lanceConfiguratorPanel.ToggleOverlayPotentialCarriers(baLanceLoadoutSlot, false, carrierLanceLoadoutSlot); + carrierLanceLoadoutSlot.SelectedMech.UnavailableOverlay.SetActive(true); + carrierOverlayChildImage.color = chosenColor; + ModInit.modLog?.Trace?.Write($"[SetPairingOverlay] - no unused colors, chose one at random to duplicate. carrier overlay color set to {carrierOverlayChildImage.color.r} {carrierOverlayChildImage.color.g} {carrierOverlayChildImage.color.b}"); + if (BAOverlayChildImage != null) { - if (flag) - { - num4 -= 1f; - } - - float num8 = Vector3.Distance(vector, sourcePosition); - if (num8 < num5) - { - num5 = num8; - collisionWorldPos = vector; - } + BAOverlayChildImage.color = chosenColor; + ModInit.modLog?.Trace?.Write($"[SetPairingOverlay] - no unused colors, chose one at random to duplicate. BA overlay color set to {BAOverlayChildImage.color.r} {BAOverlayChildImage.color.g} {BAOverlayChildImage.color.b}"); } } } - - if (text != null && num2 > 0.5f) + else { - break; + if (BAOverlayChildImage != null) + { + lanceConfiguratorPanel.ToggleOverlayPotentialCarriers(baLanceLoadoutSlot, false, carrierLanceLoadoutSlot); + carrierOverlayChildImage.color = ModState.UsedOverlayColorsByCarrier[carrierPilotID]; + carrierLanceLoadoutSlot.SelectedMech.UnavailableOverlay.SetActive(true); + BAOverlayChildImage.color = ModState.UsedOverlayColorsByCarrier[carrierPilotID]; + ModInit.modLog?.Trace?.Write( + $"[SetPairingOverlay] - Carrier already has non-default color. Setting BA color to match: {BAOverlayChildImage.color.r}, {BAOverlayChildImage.color.g}, {BAOverlayChildImage.color.b}"); + } } } + } + } - float num9 = (text == null) ? (num2 / num3) : num2; - float b = num9 - source.Combat.Constants.Visibility.MinRatioFromActors; - float num10 = Mathf.Min(num4 / num3, b); - if (num10 > 0.001f) - { - num9 -= num10; - } + public static void ShowBATargetsMeleeIndicator(this CombatHUDInWorldElementMgr inWorld, List targets, AbstractActor unit) + { + var tickMarks = inWorld.WeaponTickMarks;//Traverse.Create(inWorld).Field("WeaponTickMarks").GetValue>(); - if (num9 >= source.Combat.Constants.Visibility.RatioFullVis) + for (int i = 0; i < tickMarks.Count; i++) + { + if (targets.Contains(tickMarks[i].Owner)) { - return LineOfFireLevel.LOFClear; + if (tickMarks[i].Owner.team.IsEnemy(unit.team)) + { + if (tickMarks[i].MeleeTweenAnimations != null) + { + tickMarks[i].MeleeTweenAnimations.SetState(ButtonState.Enabled, false); + } + } } + } + } - if (num9 >= source.Combat.Constants.Visibility.RatioObstructedVis) + public static void ToggleOverlayPotentialCarriers(this LanceConfiguratorPanel lanceConfiguratorPanel, LanceLoadoutSlot baLanceLoadoutSlot, bool toggleOn = true, LanceLoadoutSlot carrierLanceLoadoutSlot = null) + { + if (baLanceLoadoutSlot?.SelectedMech == null) return; + foreach (var loadOutSlot in lanceConfiguratorPanel.loadoutSlots) + { + if (loadOutSlot.SelectedMech != null && loadOutSlot.SelectedPilot != null) { - return LineOfFireLevel.LOFObstructed; - } + if (loadOutSlot.SelectedMech != baLanceLoadoutSlot.SelectedMech) + { + var hasSpace = false; + var hasPx = false; + var pilotID = loadOutSlot.SelectedPilot.Pilot.Description.Id; + + if (!ModState.PairingInfos.ContainsKey(pilotID)) + { + if (loadOutSlot.SelectedMech.mechDef.GetTotalBASpaceMechDef() > 0) hasSpace = true; + } + else + { + var pairInfo = ModState.PairingInfos[pilotID]; + if (pairInfo.PairedBattleArmor.Count < pairInfo.CapacityInitial) + { + hasSpace = true; + if (pairInfo.PairedBattleArmor.Count > 0) hasPx = true; + } + } - return LineOfFireLevel.LOFBlocked; + if (hasSpace)// && !hasPx) + { + var carrierOverlayChildren = loadOutSlot.SelectedMech.UnavailableOverlay.gameObject + .GetComponentsInChildren(); + foreach (var carrierOverlayChild in carrierOverlayChildren) + { + if (carrierOverlayChild.name == "stripes") + { + var carrierOverlayChildImage = carrierOverlayChild.GetComponent(); + + if (toggleOn) + { + carrierOverlayChildImage.color = ModState.PendingSelectionColor;//new Color(0, 0, 0, ModState.DefaultOverlay.a); + loadOutSlot.SelectedMech.UnavailableOverlay.SetActive(true); + } + else if (carrierLanceLoadoutSlot?.SelectedMech != null && + loadOutSlot?.SelectedMech != carrierLanceLoadoutSlot.SelectedMech) + { + if (ModState.UsedOverlayColorsByCarrier.TryGetValue(pilotID, out var color)) + { + carrierOverlayChildImage.color = color;//new Color(0, 0, 0, ModState.DefaultOverlay.a); + } + loadOutSlot.SelectedMech.UnavailableOverlay.SetActive(false); + } + } + } + } + } + } } - return LineOfFireLevel.LOFBlocked; } } } diff --git a/StrategicOperations/StrategicOperations/Framework/Classes.cs b/StrategicOperations/StrategicOperations/Framework/Classes.cs index b5a8dc1..7461dca 100644 --- a/StrategicOperations/StrategicOperations/Framework/Classes.cs +++ b/StrategicOperations/StrategicOperations/Framework/Classes.cs @@ -18,19 +18,19 @@ namespace StrategicOperations.Framework { public class StrategicInfluenceMapFactors { - public class CustomPositionFactors + public class CustomHostileFactors { - public class PreferAvoidStandingInAirstrikeAreaPosition : CustomInfluenceMapPositionFactor + public class PreferAvoidStandingInAirstrikeAreaWithHostile : CustomInfluenceMapHostileFactor { - public PreferAvoidStandingInAirstrikeAreaPosition() - { - } + public override bool IgnoreFactorNormalization => true; public override string Name => "Prefer not standing in the area of an incoming airstrike"; - public override bool IgnoreFactorNormalization => true; - public override float EvaluateInfluenceMapFactorAtPosition(AbstractActor unit, Vector3 position, - float angle, MoveType moveType, PathNode pathNode) + public PreferAvoidStandingInAirstrikeAreaWithHostile() + { + } + + public override float EvaluateInfluenceMapFactorAtPositionWithHostile(AbstractActor unit, Vector3 position, float angle, MoveType moveType, ICombatant hostileUnit) { ModInit.modLog?.Trace?.Write(Name); if (!AI_Utils.IsPositionWithinAnAirstrike(unit, position)) @@ -53,52 +53,18 @@ public override float GetSprintMoveWeight(AbstractActor actor) } } - public class PreferNearerToSwarmTargets : CustomInfluenceMapPositionFactor + public class PreferCloserToResupplyWithHostile : CustomInfluenceMapHostileFactor { - public PreferNearerToSwarmTargets() - { - } - - public override string Name => "Battle armor and their carriers prefer getting close to enemy units"; public override bool IgnoreFactorNormalization => true; - public override float EvaluateInfluenceMapFactorAtPosition(AbstractActor unit, Vector3 position, - float angle, MoveType moveType, PathNode pathNode) - { - ModInit.modLog?.Trace?.Write(Name); - if (!unit.HasMountedUnits() && !unit.CanSwarm()) - { - IgnoreFactorNormalization = false; - return 0f; - } - - return 9001 * (1 / unit.DistanceToClosestDetectedEnemy(position)); - } - - public override float GetRegularMoveWeight(AbstractActor actor) - { - return 1f; - } - - public override float GetSprintMoveWeight(AbstractActor actor) - { - return 1f; - } - } - - public class PreferCloserToResupply : CustomInfluenceMapPositionFactor - { - public PreferCloserToResupply() - { - } - public override string Name => "Units with missing ammo or <60% armor prefer getting within range of resupply"; - public override bool IgnoreFactorNormalization => true; + public PreferCloserToResupplyWithHostile() + { + } - public override float EvaluateInfluenceMapFactorAtPosition(AbstractActor unit, Vector3 position, - float angle, MoveType moveType, PathNode pathNode) + public override float EvaluateInfluenceMapFactorAtPositionWithHostile(AbstractActor unit, Vector3 position, float angle, MoveType moveType, ICombatant hostileUnit) { ModInit.modLog?.Trace?.Write(Name); @@ -127,29 +93,27 @@ public override float GetSprintMoveWeight(AbstractActor actor) return 1f; } } - } - public class CustomHostileFactors - { - public class PreferAvoidStandingInAirstrikeAreaWithHostile : CustomInfluenceMapHostileFactor + public class PreferNearerToSwarmTargetsWithHostile : CustomInfluenceMapHostileFactor { - public PreferAvoidStandingInAirstrikeAreaWithHostile() + public override bool IgnoreFactorNormalization => true; + + public override string Name => "Battle armor and their carriers prefer getting close to enemy units"; + + public PreferNearerToSwarmTargetsWithHostile() { } - public override string Name => "Prefer not standing in the area of an incoming airstrike"; - public override bool IgnoreFactorNormalization => true; - public override float EvaluateInfluenceMapFactorAtPositionWithHostile(AbstractActor unit, Vector3 position, float angle, MoveType moveType, ICombatant hostileUnit) { ModInit.modLog?.Trace?.Write(Name); - if (!AI_Utils.IsPositionWithinAnAirstrike(unit, position)) + if (!unit.HasMountedUnits() && !unit.CanSwarm()) { IgnoreFactorNormalization = false; - return 1f; + return 0f; } - return -9001f; + return 9001 * (1 / unit.DistanceToClosestDetectedEnemy(position)); } public override float GetRegularMoveWeight(AbstractActor actor) @@ -162,26 +126,31 @@ public override float GetSprintMoveWeight(AbstractActor actor) return 1f; } } + } - public class PreferNearerToSwarmTargetsWithHostile : CustomInfluenceMapHostileFactor + public class CustomPositionFactors + { + public class PreferAvoidStandingInAirstrikeAreaPosition : CustomInfluenceMapPositionFactor { - public PreferNearerToSwarmTargetsWithHostile() + public override bool IgnoreFactorNormalization => true; + + public override string Name => "Prefer not standing in the area of an incoming airstrike"; + + public PreferAvoidStandingInAirstrikeAreaPosition() { } - public override string Name => "Battle armor and their carriers prefer getting close to enemy units"; - public override bool IgnoreFactorNormalization => true; - - public override float EvaluateInfluenceMapFactorAtPositionWithHostile(AbstractActor unit, Vector3 position, float angle, MoveType moveType, ICombatant hostileUnit) + public override float EvaluateInfluenceMapFactorAtPosition(AbstractActor unit, Vector3 position, + float angle, MoveType moveType, PathNode pathNode) { ModInit.modLog?.Trace?.Write(Name); - if (!unit.HasMountedUnits() && !unit.CanSwarm()) + if (!AI_Utils.IsPositionWithinAnAirstrike(unit, position)) { IgnoreFactorNormalization = false; - return 0f; + return 1f; } - return 9001 * (1 / unit.DistanceToClosestDetectedEnemy(position)); + return -9001f; } public override float GetRegularMoveWeight(AbstractActor actor) @@ -195,18 +164,19 @@ public override float GetSprintMoveWeight(AbstractActor actor) } } - public class PreferCloserToResupplyWithHostile : CustomInfluenceMapHostileFactor + public class PreferCloserToResupply : CustomInfluenceMapPositionFactor { - public PreferCloserToResupplyWithHostile() - { - } + public override bool IgnoreFactorNormalization => true; public override string Name => "Units with missing ammo or <60% armor prefer getting within range of resupply"; - public override bool IgnoreFactorNormalization => true; + public PreferCloserToResupply() + { + } - public override float EvaluateInfluenceMapFactorAtPositionWithHostile(AbstractActor unit, Vector3 position, float angle, MoveType moveType, ICombatant hostileUnit) + public override float EvaluateInfluenceMapFactorAtPosition(AbstractActor unit, Vector3 position, + float angle, MoveType moveType, PathNode pathNode) { ModInit.modLog?.Trace?.Write(Name); @@ -235,9 +205,41 @@ public override float GetSprintMoveWeight(AbstractActor actor) return 1f; } } - } + public class PreferNearerToSwarmTargets : CustomInfluenceMapPositionFactor + { + public override bool IgnoreFactorNormalization => true; + + public override string Name => "Battle armor and their carriers prefer getting close to enemy units"; + + public PreferNearerToSwarmTargets() + { + } + + public override float EvaluateInfluenceMapFactorAtPosition(AbstractActor unit, Vector3 position, + float angle, MoveType moveType, PathNode pathNode) + { + ModInit.modLog?.Trace?.Write(Name); + if (!unit.HasMountedUnits() && !unit.CanSwarm()) + { + IgnoreFactorNormalization = false; + return 0f; + } + + return 9001 * (1 / unit.DistanceToClosestDetectedEnemy(position)); + } + + public override float GetRegularMoveWeight(AbstractActor actor) + { + return 1f; + } + public override float GetSprintMoveWeight(AbstractActor actor) + { + return 1f; + } + } + } } [CustomComponent("InternalAmmoTonnage")] @@ -247,281 +249,423 @@ public class InternalAmmoTonnage : SimpleCustomComponent } public class Classes { - public class ConfigOptions +// public class AirliftWiggleConfig +// { +// public string AbilityDefID = ""; +// public float BaseSuccessChance = 0f; +// public float MaxSuccessChance = 0f; +// public float PilotingSuccessFactor = 0f; +// public float PilotingDamageReductionFactor = 0f; +// } + + + public class AI_BeaconProxyInfo { - public class BeaconExclusionConfig - { - public bool ExcludedPlayerSpawn = true; - public bool ExcludedPlayerStrafe = true; - public bool ExcludedAISpawn = true; - public bool ExcludedAIStrafe = true; + public int StrafeWaves = 0; + public string UnitDefID = ""; + public int Weight = 0; + } - } - public enum BA_TargetEffectType + public class AI_CmdInvocation + { + public Ability ability; + public bool active; + public float dist; + public Vector3 vectorOne; + public Vector3 vectorTwo; + + public AI_CmdInvocation() { - MOUNT_INT, - MOUNT_EXT, - SWARM, - GARRISON, - BOTH, - MOUNTTARGET, - SWARMTARGET, - BOTHTARGET + this.ability = default(Ability); + this.vectorOne = new Vector3(); + this.vectorTwo = new Vector3(); + this.active = false; + this.dist = Vector3.Distance(vectorOne, vectorTwo); } - public class BA_DeswarmAbilityConfig // key will be AbilityDefID + public AI_CmdInvocation(Ability cmdAbility, Vector3 firstVector, Vector3 secondVector, bool active) { - //public string AbilityDefID = ""; - public float BaseSuccessChance = 0f; - public float MaxSuccessChance = 0f; - public float PilotingSuccessFactor = 0f; - public float TotalDamage = 0f; - public int Clusters = 1; - public int InitPenalty = 0; - - public BA_DeswarmAbilityConfig(){} + this.ability = cmdAbility; + this.vectorOne = firstVector; + this.vectorTwo = secondVector; + this.active = active; + this.dist = Vector3.Distance(vectorOne, vectorTwo); } + } - public class BA_DeswarmMovementConfig + public class AI_DealWithBAInvocation + { + public Ability ability; + public bool active; + public AbstractActor targetActor; + + public AI_DealWithBAInvocation() { - public string AbilityDefID = ""; - public float BaseSuccessChance = 0f; - public float MaxSuccessChance = 0f; - public float EvasivePipsFactor = 0f; - public float JumpMovementModifier = 0f; - public bool UseDFADamage = false; - public float LocationDamageOverride = 0f; - public float PilotingDamageReductionFactor = 0f; - public BA_DeswarmMovementConfig(){} + this.ability = default(Ability); + this.targetActor = default(AbstractActor); + this.active = false; } - public class AI_FactionCommandAbilitySetting + public AI_DealWithBAInvocation(Ability cmdAbility, AbstractActor targetActor, bool active) { - public List ContractBlacklist = new List(); - public string AbilityDefID = ""; - public List FactionIDs = new List(); - public float AddChance = 0f; - public float DiffMod = 0f; - public int MaxUsersAddedPerContract = 0; - public List AvailableBeacons = new List(); + this.ability = cmdAbility; + this.targetActor = targetActor; + this.active = active; } + } - public class ResupplyConfigOptions + public class AI_SpawnBehavior + { + public string Behavior; + public int MinRange; + public string Tag; + + public AI_SpawnBehavior() { - public string ResupplyIndicatorAsset = ""; - public ColorSetting ResupplyIndicatorColor = new ColorSetting(); - public string ResupplyIndicatorInRangeAsset = ""; - public ColorSetting ResupplyIndicatorInRangeColor = new ColorSetting(); - public string ResupplyAbilityID = ""; - public string ResupplyUnitTag = ""; - public string SPAMMYAmmoDefId = ""; - public List SPAMMYBlackList = new List(); - public string InternalSPAMMYDefId = ""; - public List InternalSPAMMYBlackList = new List(); - public string ArmorSupplyAmmoDefId = ""; - public float ArmorRepairMax = 0.75f; - public float BasePhasesToResupply = 1; - public float ResupplyPhasesPerAmmoTonnage = 1f; - public float ResupplyPhasesPerArmorPoint = 0.25f; - public Dictionary UnitTagFactor = new Dictionary(); + Tag = "DEFAULT"; + Behavior = "DEFAULT"; + MinRange = 50; } } - public class StrategicMovementInvocation : AbstractActorMovementInvocation + + public class AirliftTargetEffect { - public new string ActorGUID = ""; - public new bool AbilityConsumesFiring; - public new List Waypoints = new List(); - public new MoveType MoveType; - public new Vector3 FinalOrientation; - public new string MeleeTargetGUID = ""; - public ICombatant MoveTarget; - public bool IsFriendly; - public bool IsMountOrSwarm; - public StrategicMovementInvocation(){} - public StrategicMovementInvocation(AbstractActor actor, bool abilityConsumesFiring, ICombatant moveTarget, bool isFriendly, bool isMountOrSwarm) : base(actor, abilityConsumesFiring) + public string Description = ""; + public List effectDataJO = new List(); + + [JsonIgnore] + public List effects = new List(); + + public bool FriendlyAirlift; + public string ID = ""; + public string Name = ""; + } + + public class AirliftTracker + { + public string CarrierGUID; // guid of carrier unit. + public bool IsCarriedInternal; + public bool IsFriendly; // key is BA unit chassis location (HD, CT, LT, RT, LA, RA), value is BA mounted ARMOR location on carrier. + public float Offset; + + public AirliftTracker() { - Pathing pathing = actor.Pathing; + this.CarrierGUID = ""; + this.IsCarriedInternal = false; + this.IsFriendly = false; + this.Offset = 0f; + } - this.MoveTarget = moveTarget; - pathing.SetSprinting(); - pathing.UpdateFreePath(moveTarget.CurrentPosition, moveTarget.CurrentPosition, false, false); - pathing.UpdateLockedPath(moveTarget.CurrentPosition, moveTarget.CurrentPosition, false); - pathing.LockPosition(); - this.ActorGUID = actor.GUID; - this.AbilityConsumesFiring = abilityConsumesFiring; - List collection = ActorMovementSequence.ExtractWaypointsFromPath(actor, pathing.CurrentPath, pathing.ResultDestination, pathing.CurrentMeleeTarget, this.MoveType); - this.Waypoints = new List(collection); - this.MoveType = pathing.MoveType; - this.FinalOrientation = moveTarget.GameRep.transform.forward; - this.MeleeTargetGUID = ""; + public AirliftTracker(string targetGUID, bool internalCarry, bool isFriendly, float offset) + { + this.CarrierGUID = targetGUID; + this.IsCarriedInternal = internalCarry; this.IsFriendly = isFriendly; - this.IsMountOrSwarm = isMountOrSwarm; + this.Offset = offset; } + } - public override bool Invoke(CombatGameState combat) + public class BA_DamageTracker + { + public Dictionary BA_MountedLocations = new Dictionary(); // key is BA unit chassis location (HD, CT, LT, RT, LA, RA), value is BA mounted ARMOR location on carrier. + public bool IsSquadInternal; + public string TargetGUID = ""; // guid of carrier unit. + + public BA_DamageTracker() { - InvocationMessage.logger.Log("Invoking a STRATEGIC MOVE!"); - AbstractActor abstractActor = combat.FindActorByGUID(this.ActorGUID); - if (abstractActor == null) - { - InvocationMessage.logger.LogError(string.Format("MechMovement.Invoke Actor with GUID {0} not found!", this.ActorGUID)); - return false; - } - ICombatant combatant = null; - if (!string.IsNullOrEmpty(this.MeleeTargetGUID)) - { - combatant = combat.FindCombatantByGUID(this.MeleeTargetGUID, false); - if (combatant == null) - { - InvocationMessage.logger.LogError(string.Format("MechMovement.Invoke ICombatant with GUID {0} not found!", this.MeleeTargetGUID)); - return false; - } - } + this.TargetGUID = ""; + this.IsSquadInternal = false; + this.BA_MountedLocations = new Dictionary(); + } - StrategicMovementSequence stackSequence = new StrategicMovementSequence(abstractActor, this.Waypoints, this.FinalOrientation, this.MoveType, combatant, this.AbilityConsumesFiring, this.MoveTarget, this.IsFriendly, this.IsMountOrSwarm); - base.PublishStackSequence(combat.MessageCenter, stackSequence, this); - return true; + public BA_DamageTracker(string targetGUID, bool internalSquad, Dictionary mountedLocations) + { + this.TargetGUID = targetGUID; + this.IsSquadInternal = internalSquad; + this.BA_MountedLocations = mountedLocations; } } - public class StrategicMovementSequence : ActorMovementSequence + public class BA_DeswarmMovementInfo { - public ICombatant Target; - public bool IsFriendly; - public bool MountSwarmBA; //handle if airlifting unit dies? - public override bool ConsumesActivation => !MountSwarmBA;//!this.MountSwarmBA; //this might fuck up attack on swarm. grr. + public AbstractActor Carrier; + public List SwarmingUnits; - //public new virtual bool ForceActivationEnd => false; + public BA_DeswarmMovementInfo() + { + this.Carrier = null; + this.SwarmingUnits = new List(); + } - public StrategicMovementSequence(AbstractActor actor, List waypoints, Vector3 finalOrientation, MoveType moveType, ICombatant meleeTarget, bool consumesFiring, ICombatant target, bool friendly, bool mountORswarm) : base(actor, waypoints, finalOrientation, moveType, meleeTarget, consumesFiring) + public BA_DeswarmMovementInfo(AbstractActor carrier) { - this.Target = target; - this.IsFriendly = friendly; - this.MountSwarmBA = mountORswarm; + this.Carrier = carrier; + this.SwarmingUnits = new List(); } - public override void CompleteOrders() + public BA_DeswarmMovementInfo(AbstractActor carrier, List swarmingUnits) { - base.owningActor.AutoBrace = false; - base.CompleteOrders(); - base.owningActor.ResetPathing(false); + this.Carrier = carrier; + this.SwarmingUnits = swarmingUnits; + } + } - if (base.owningActor.team.IsLocalPlayer) - {} + public class BA_FactionAssoc + { + public List FactionIDs = new List(); + public Dictionary HandsyBattleArmorWeight = new Dictionary(); + public Dictionary InternalBattleArmorWeight = new Dictionary(); + public int MaxSquadsPerContract = 0; + public Dictionary MountedBattleArmorWeight = new Dictionary(); + public float SpawnChanceBase = 0f; + public float SpawnChanceDiffMod = 0f; + } - if (MountSwarmBA && owningActor is TrooperSquad squad) - { - if (this.Target is BattleTech.Building building) - { - ModInit.modLog?.Info?.Write( - $"[StrategicMovementSequence] Called for BA movement to garrison building {building.DisplayName}."); - squad.ProcessGarrisonBuilding(building); - return; - } - } + public class BA_GarrisonInfo + { + public string BuildingGUID = ""; //guid of building + public Vector3 OriginalSquadPos = new Vector3(); // original position of squad. when exiting, squad will move here because pathing sucks and i hate it. - if (this.Target is AbstractActor targetActor) - { - if (MountSwarmBA) - { - ModInit.modLog?.Info?.Write( - $"[StrategicMovementSequence] Called for BA movement to mount or swarm."); - if (base.owningActor is TrooperSquad squad2) - { - if (this.IsFriendly) - { - if (!squad2.IsMountedUnit()) - { - squad2.ProcessMountFriendly(targetActor); - return; - } - } + public BA_GarrisonInfo(BattleTech.Building building, TrooperSquad squad) + { + BuildingGUID = building.GUID; + OriginalSquadPos = squad.CurrentPosition; + } + } - if (!squad2.IsSwarmingUnit() && squad2.CanSwarm()) - { - squad2.ProcessSwarmEnemy(targetActor); - } - if (ModInit.modSettings.AttackOnSwarmSuccess && squad2.IsSwarmingUnit()) - { - if (squad2.GetAbilityUsedFiring()) - { - ModInit.modLog?.Info?.Write($"[StrategicMovementSequence] Actor {squad2.DisplayName} has used an ability that consumed firing, not generating swarm."); - return; - } - if (!squad2.team.IsLocalPlayer) - { - foreach (var weapon in squad2.Weapons) - { - weapon.EnableWeapon(); - } - } - - var weps = squad2.Weapons.Where(x => x.IsEnabled && x.HasAmmo).ToList(); - var loc = ModState.BADamageTrackers[squad2.GUID].BA_MountedLocations.Values - .GetRandomElement(); - - var attackStackSequence = new AttackStackSequence(squad2, targetActor, - squad2.CurrentPosition, - squad2.CurrentRotation, weps, MeleeAttackType.NotSet, loc, -1); - squad2.Combat.MessageCenter.PublishMessage( - new AddSequenceToStackMessage(attackStackSequence)); - ModInit.modLog?.Info?.Write( - $"[StrategicMovementSequence - CompleteOrders] Creating attack sequence on successful swarm attack targeting location {loc}."); - - } + public class BA_TargetEffect + { + public string Description = ""; + public List effectDataJO = new List(); - //doattacksequencehere + [JsonIgnore] + public List effects = new List(); - } - else - { - ModInit.modLog?.Info?.Write( - $"[StrategicMovementSequence] ERROR: called sequence for BA, but actor is not TrooperSquad."); - return; - } - } - else if (!MountSwarmBA) - { - ModInit.modLog?.Info?.Write( - $"[StrategicMovementSequence] Called for airlift/dropoff for Target {this.Target.DisplayName}."); + public string ID = ""; + public string Name = ""; + public ConfigOptions.BA_TargetEffectType TargetEffectType = ConfigOptions.BA_TargetEffectType.BOTH; + } - if (targetActor.IsAirlifted()) - { - targetActor.DetachFromAirliftCarrier(base.OwningActor, IsFriendly); - return; - } + public class BAPairingInfo + { + public int CapacityInitial; + public List PairedBattleArmor = new List(); - if (!targetActor.IsAirlifted()) - { - targetActor.AttachToAirliftCarrier(base.OwningActor, IsFriendly); - } - } - } + public BAPairingInfo(int capacityInitial, string pairedBattleArmor = null) + { + this.CapacityInitial = capacityInitial; + if (pairedBattleArmor != null) this.PairedBattleArmor.Add(pairedBattleArmor); + } + } + + public class CmdInvocationParams + { + public string ActorResource; + public bool IsStrafeAOE; + public string PilotOverride; + public bool PlayerControl; + public bool PlayerControlOverridden; + public string QUID = ""; + public AbilityDef.SpecialRules Rules; + public int StrafeWaves; + + public CmdInvocationParams(int strafeWaves, string actorResource, string pilotOverride, + AbilityDef.SpecialRules rules, string quid = "", bool isStrafeAOE = false, bool playerControl = false, bool playerControlOverridden = false) + { + QUID = quid; + StrafeWaves = strafeWaves; + ActorResource = actorResource; + PilotOverride = pilotOverride; + Rules = rules; + IsStrafeAOE = isStrafeAOE; + PlayerControl = playerControl; + PlayerControlOverridden = playerControlOverridden; + } + + public CmdInvocationParams() + { + QUID = ""; + StrafeWaves = 0; + ActorResource = ""; + PilotOverride = ""; + Rules = AbilityDef.SpecialRules.NotSet; + IsStrafeAOE = false; + PlayerControl = false; + PlayerControlOverridden = false; + } + + public CmdInvocationParams(CmdInvocationParams cmdParams) + { + QUID = cmdParams.QUID; + StrafeWaves = cmdParams.StrafeWaves; + ActorResource = cmdParams.ActorResource; + PilotOverride = cmdParams.PilotOverride; + Rules = cmdParams.Rules; + IsStrafeAOE = cmdParams.IsStrafeAOE; + PlayerControl = cmdParams.PlayerControl; + PlayerControlOverridden = cmdParams.PlayerControlOverridden; + } + } + + public class CmdUseInfo + { + public int AbilityUseCost; + public string CommandName; + public string UnitID; + public string UnitName; + public int UseCost; + public int UseCount; + public int TotalCost => UseCount * UseCostAdjusted; + public int UseCostAdjusted => Mathf.RoundToInt((UseCost * ModInit.modSettings.commandUseCostsMulti) + AbilityUseCost); + + public CmdUseInfo(string unitId, string commandName, string unitName, int useCost, int abilityUseCost) + { + this.UnitID = unitId; + this.CommandName = commandName; + this.UnitName = unitName; + this.UseCost = useCost; + this.AbilityUseCost = abilityUseCost; + this.UseCount = 1; + } + } + + public class CmdUseStat + { + public bool consumeOnUse; + public int contractUses; + public string ID; + public string pilotID; + public int simStatCount; + public string stat; + + public CmdUseStat(string id, string stat, bool consumeOnUse, int contractUses, int simStatCount, string pilotId = null) + { + this.ID = id; + this.stat = stat; + this.pilotID = pilotId; + this.consumeOnUse = consumeOnUse; + this.contractUses = contractUses; + this.simStatCount = simStatCount; + } + } + + public class ColorSetting + { + public int b; + public int g; + public int r; + public float Bf => b / 255f; + public float Gf => g / 255f; + + public Color ProcessedColor => new Color(Rf, Gf, Bf); + //public int a; + + public float Rf => r / 255f; + //public float Af => a / 255f; + } + + public class ConfigOptions + { + public enum BA_TargetEffectType + { + MOUNT_INT, + MOUNT_EXT, + SWARM, + GARRISON, + BOTH, + MOUNTTARGET, + SWARMTARGET, + BOTHTARGET + } + + public class AI_FactionCommandAbilitySetting + { + public string AbilityDefID = ""; + public float AddChance = 0f; + public List AvailableBeacons = new List(); + public List ContractBlacklist = new List(); + public float DiffMod = 0f; + public List FactionIDs = new List(); + public int MaxUsersAddedPerContract = 0; + } + + public class BA_DeswarmAbilityConfig // key will be AbilityDefID + { + //public string AbilityDefID = ""; + public float BaseSuccessChance = 0f; + public int Clusters = 1; + public int InitPenalty = 0; + public float MaxSuccessChance = 0f; + public float PilotingSuccessFactor = 0f; + public float TotalDamage = 0f; + + public BA_DeswarmAbilityConfig(){} + } + + public class BA_DeswarmMovementConfig + { + public string AbilityDefID = ""; + public float BaseSuccessChance = 0f; + public float EvasivePipsFactor = 0f; + public float JumpMovementModifier = 0f; + public float LocationDamageOverride = 0f; + public float MaxSuccessChance = 0f; + public float PilotingDamageReductionFactor = 0f; + public bool UseDFADamage = false; + public BA_DeswarmMovementConfig(){} + } + + public class BeaconExclusionConfig + { + public bool ExcludedAISpawn = true; + public bool ExcludedAIStrafe = true; + public bool ExcludedPlayerSpawn = true; + public bool ExcludedPlayerStrafe = true; + } + + public class ResupplyConfigOptions + { + public float ArmorRepairMax = 0.75f; + public string ArmorSupplyAmmoDefId = ""; + public float BasePhasesToResupply = 1; + public List InternalSPAMMYBlackList = new List(); + public string InternalSPAMMYDefId = ""; + public string ResupplyAbilityID = ""; + public string ResupplyIndicatorAsset = ""; + public ColorSetting ResupplyIndicatorColor = new ColorSetting(); + public string ResupplyIndicatorInRangeAsset = ""; + public ColorSetting ResupplyIndicatorInRangeColor = new ColorSetting(); + public float ResupplyPhasesPerAmmoTonnage = 1f; + public float ResupplyPhasesPerArmorPoint = 0.25f; + public string ResupplyUnitTag = ""; + public string SPAMMYAmmoDefId = ""; + public List SPAMMYBlackList = new List(); + public Dictionary UnitTagFactor = new Dictionary(); } } public class CustomSpawner { - public CombatGameState Combat; public AbstractActor Actor; - public string ChosenUnit; public string ChosenPilot; + public string ChosenUnit; + public CombatGameState Combat; + public TagSet CustomEncounterTags; public Lance CustomLance; public DataManager DM; - public MechDef NewUnitDef; + public Vector3 Loc2 = Vector3.zero; public PilotDef NewPilotDef; - public Team TeamSelection; - public Team SourceTeam; + public MechDef NewUnitDef; + public string ParentSequenceIDForStrafe = ""; + public bool PlayerControl = false; public Ability SourceAbility; - public TagSet CustomEncounterTags; + public Team SourceTeam; public Vector3 SpawnLoc = Vector3.zero; - public Vector3 Loc2 = Vector3.zero; public Quaternion SpawnRotation = new Quaternion(0f, 0f, 0f, 0f); - public HeraldryDef SupportHeraldryDef; public PendingStrafeWave StrafeWave; - public string ParentSequenceIDForStrafe = ""; - public bool PlayerControl = false; + public HeraldryDef SupportHeraldryDef; + public Team TeamSelection; public CustomSpawner(CombatGameState combat, AbstractActor actor, string chosen, Lance custLance) { @@ -562,10 +706,6 @@ public CustomSpawner(string parentSequenceID, PendingStrafeWave wave) this.ParentSequenceIDForStrafe = parentSequenceID; } - public void OnStratOpsDepsFailed() - { - ModInit.modLog?.Trace?.Write($"Failed to load Dependencies for {ChosenUnit}. This shouldnt happen!"); - } public void OnBADepsLoaded() { var newBattleArmor = ActorFactory.CreateMech(NewUnitDef, NewPilotDef, @@ -597,84 +737,18 @@ public void OnBADepsLoaded() $"[SpawnBattleArmorAtActor] Added PositionLockMount with rider {newBattleArmor.DisplayName} {newBattleArmor.GUID} and carrier {Actor.DisplayName} {Actor.GUID}."); } - public void SpawnBattleArmorAtActor() + public void OnBeaconDepsLoaded() { - LoadRequest loadRequest = DM.CreateLoadRequest(); - loadRequest.AddBlindLoadRequest(BattleTechResourceType.MechDef, ChosenUnit); - ModInit.modLog?.Info?.Write($"Added loadrequest for MechDef: {ChosenUnit}"); - loadRequest.ProcessRequests(1000U); - - var instanceGUID = - $"{Actor.Description.Id}_{Actor.team.Name}_{ChosenUnit}"; - - if (Actor.Combat.TurnDirector.CurrentRound <= 1) - { - if (ModState.DeferredInvokeBattleArmor.All(x => x.Key != instanceGUID) && !ModState.DeferredBattleArmorSpawnerFromDelegate) - { - ModInit.modLog?.Trace?.Write( - $"Deferred BA Spawner missing, creating delegate and returning. Delegate should spawn {ChosenUnit}"); - - void DeferredInvokeBASpawn() => - SpawnBattleArmorAtActor(); - - var kvp = new KeyValuePair(instanceGUID, DeferredInvokeBASpawn); - ModState.DeferredInvokeBattleArmor.Add(kvp); - foreach (var value in ModState.DeferredInvokeBattleArmor) - { - ModInit.modLog?.Trace?.Write( - $"there is a delegate {value.Key} here, with value {value.Value}"); - } - return; - } - } - - TeamSelection = Actor.team; - var alliedActors = Actor.Combat.AllMechs.Where(x => x.team == Actor.team); - var chosenpilotSourceMech = alliedActors.GetRandomElement(); - var newPilotDefID = chosenpilotSourceMech.pilot.pilotDef.Description.Id; - DM.PilotDefs.TryGet(newPilotDefID, out this.NewPilotDef); - ModInit.modLog?.Info?.Write($"Attempting to spawn {ChosenUnit} with pilot {NewPilotDef?.Description?.Callsign}."); - DM.MechDefs.TryGet(ChosenUnit, out NewUnitDef); - NewUnitDef.Refresh(); - //var injectedDependencyLoadRequest = new DataManager.InjectedDependencyLoadRequest(dm); - //newBattleArmorDef.GatherDependencies(dm, injectedDependencyLoadRequest, 1000U); - //newBattleArmorDef.Refresh(); - CustomEncounterTags = new TagSet(TeamSelection.EncounterTags) { "SpawnedFromAbility" }; - - if (!NewUnitDef.DependenciesLoaded(1000U) || !NewPilotDef.DependenciesLoaded(1000U)) - { - DataManager.InjectedDependencyLoadRequest dependencyLoad = new DataManager.InjectedDependencyLoadRequest(DM); - dependencyLoad.RegisterLoadCompleteCallback(new Action(this.OnBADepsLoaded)); - dependencyLoad.RegisterLoadFailedCallback(new Action(this.OnStratOpsDepsFailed)); // do failure handling here - if (!NewUnitDef.DependenciesLoaded(1000U)) - { - NewUnitDef.GatherDependencies(DM, dependencyLoad, 1000U); - } - if (!NewPilotDef.DependenciesLoaded(1000U)) - { - NewPilotDef.GatherDependencies(DM, dependencyLoad, 1000U); - } - DM.InjectDependencyLoader(dependencyLoad, 1000U); - } - else - { - this.OnBADepsLoaded(); - } - - } - - public void OnBeaconDepsLoaded() - { - var newUnit= ActorFactory.CreateMech(NewUnitDef, NewPilotDef, - CustomEncounterTags, TeamSelection.Combat, - TeamSelection.GetNextSupportUnitGuid(), "", SupportHeraldryDef); - newUnit.Init(SpawnLoc, SpawnRotation.eulerAngles.y, PlayerControl); - newUnit.InitGameRep(null); - TeamSelection.AddUnit(newUnit); - newUnit.AddToTeam(TeamSelection); - newUnit.AddToLance(CustomLance); - CustomLance.AddUnitGUID(newUnit.GUID); - if (PlayerControl) + var newUnit= ActorFactory.CreateMech(NewUnitDef, NewPilotDef, + CustomEncounterTags, TeamSelection.Combat, + TeamSelection.GetNextSupportUnitGuid(), "", SupportHeraldryDef); + newUnit.Init(SpawnLoc, SpawnRotation.eulerAngles.y, PlayerControl); + newUnit.InitGameRep(null); + TeamSelection.AddUnit(newUnit); + newUnit.AddToTeam(TeamSelection); + newUnit.AddToLance(CustomLance); + CustomLance.AddUnitGUID(newUnit.GUID); + if (PlayerControl) { newUnit.BehaviorTree = BehaviorTreeFactory.MakeBehaviorTree( Combat.BattleTechGame, newUnit, BehaviorTreeIDEnum.DoNothingTree); @@ -758,6 +832,140 @@ public void OnBeaconDepsLoaded() } } + public void OnStrafeDepsLoaded() + { + var newUnit = ActorFactory.CreateMech(NewUnitDef, NewPilotDef, + CustomEncounterTags, TeamSelection.Combat, + TeamSelection.GetNextSupportUnitGuid(), "", SupportHeraldryDef); + newUnit.Init(StrafeWave.NeutralTeam.OffScreenPosition, 0f, false); + newUnit.InitGameRep(null); + TeamSelection.AddUnit(newUnit); + newUnit.AddToTeam(TeamSelection); + newUnit.AddToLance(CustomLance); + CustomLance.AddUnitGUID(newUnit.GUID); + newUnit.GameRep.gameObject.SetActive(true); + newUnit.BehaviorTree = BehaviorTreeFactory.MakeBehaviorTree( + this.StrafeWave.Ability.Combat.BattleTechGame, newUnit, + BehaviorTreeIDEnum.DoNothingTree); + var eventID = Guid.NewGuid().ToString(); + ModInit.modLog?.Info?.Write($"Initializing Strafing Run (wave) with id {eventID}!"); + TB_StrafeSequence eventSequence = + new TB_StrafeSequence(this.ParentSequenceIDForStrafe, eventID, newUnit, SpawnLoc, + this.StrafeWave.PositionB, this.StrafeWave.Radius, this.StrafeWave.Team, ModState.IsStrafeAOE, this.StrafeWave.Ability.Def.IntParam1); + TurnEvent tEvent = new TurnEvent(eventID, Combat, + this.StrafeWave.Ability.Def.ActivationETA, null, eventSequence, this.StrafeWave.Ability.Def, false); + Combat.TurnDirector.AddTurnEvent(tEvent); + + + if (this.StrafeWave.Team.IsLocalPlayer && (ModInit.modSettings.commandUseCostsMulti > 0 || + this.StrafeWave.Ability.Def.getAbilityDefExtension().CBillCost > 0)) + { + var unitName = ""; + var unitCost = 0; + var unitID = ""; + unitName = NewUnitDef.Description.UIName; + unitID = NewUnitDef.Description.Id; + unitCost = NewUnitDef.Chassis.Description.Cost; + ModInit.modLog?.Info?.Write($"Usage cost will be {unitCost}"); + + if (ModState.CommandUses.All(x => x.UnitID != this.StrafeWave.ActorResource)) + { + + var commandUse = new CmdUseInfo(unitID, this.StrafeWave.Ability.Def.Description.Name, unitName, + unitCost, this.StrafeWave.Ability.Def.getAbilityDefExtension().CBillCost); + + ModState.CommandUses.Add(commandUse); + ModInit.modLog?.Info?.Write( + $"Added usage cost for {commandUse.CommandName} - {commandUse.UnitName}"); + } + else + { + var cmdUse = ModState.CommandUses.FirstOrDefault(x => x.UnitID == this.StrafeWave.ActorResource); + if (cmdUse == null) + { + ModInit.modLog?.Info?.Write($"ERROR: cmdUseInfo was null"); + } + else + { + cmdUse.UseCount += 1; + ModInit.modLog?.Info?.Write( + $"Added usage cost for {cmdUse.CommandName} - {cmdUse.UnitName}, used {cmdUse.UseCount} times"); + } + } + } + } + + public void OnStratOpsDepsFailed() + { + ModInit.modLog?.Trace?.Write($"Failed to load Dependencies for {ChosenUnit}. This shouldnt happen!"); + } + + public void SpawnBattleArmorAtActor() + { + LoadRequest loadRequest = DM.CreateLoadRequest(); + loadRequest.AddBlindLoadRequest(BattleTechResourceType.MechDef, ChosenUnit); + ModInit.modLog?.Info?.Write($"Added loadrequest for MechDef: {ChosenUnit}"); + loadRequest.ProcessRequests(1000U); + + var instanceGUID = + $"{Actor.Description.Id}_{Actor.team.Name}_{ChosenUnit}"; + + if (Actor.Combat.TurnDirector.CurrentRound <= 1) + { + if (ModState.DeferredInvokeBattleArmor.All(x => x.Key != instanceGUID) && !ModState.DeferredBattleArmorSpawnerFromDelegate) + { + ModInit.modLog?.Trace?.Write( + $"Deferred BA Spawner missing, creating delegate and returning. Delegate should spawn {ChosenUnit}"); + + void DeferredInvokeBASpawn() => + SpawnBattleArmorAtActor(); + + var kvp = new KeyValuePair(instanceGUID, DeferredInvokeBASpawn); + ModState.DeferredInvokeBattleArmor.Add(kvp); + foreach (var value in ModState.DeferredInvokeBattleArmor) + { + ModInit.modLog?.Trace?.Write( + $"there is a delegate {value.Key} here, with value {value.Value}"); + } + return; + } + } + + TeamSelection = Actor.team; + var alliedActors = Actor.Combat.AllMechs.Where(x => x.team == Actor.team); + var chosenpilotSourceMech = alliedActors.GetRandomElement(); + var newPilotDefID = chosenpilotSourceMech.pilot.pilotDef.Description.Id; + DM.PilotDefs.TryGet(newPilotDefID, out this.NewPilotDef); + ModInit.modLog?.Info?.Write($"Attempting to spawn {ChosenUnit} with pilot {NewPilotDef?.Description?.Callsign}."); + DM.MechDefs.TryGet(ChosenUnit, out NewUnitDef); + NewUnitDef.Refresh(); + //var injectedDependencyLoadRequest = new DataManager.InjectedDependencyLoadRequest(dm); + //newBattleArmorDef.GatherDependencies(dm, injectedDependencyLoadRequest, 1000U); + //newBattleArmorDef.Refresh(); + CustomEncounterTags = new TagSet(TeamSelection.EncounterTags) { "SpawnedFromAbility" }; + + if (!NewUnitDef.DependenciesLoaded(1000U) || !NewPilotDef.DependenciesLoaded(1000U)) + { + DataManager.InjectedDependencyLoadRequest dependencyLoad = new DataManager.InjectedDependencyLoadRequest(DM); + dependencyLoad.RegisterLoadCompleteCallback(new Action(this.OnBADepsLoaded)); + dependencyLoad.RegisterLoadFailedCallback(new Action(this.OnStratOpsDepsFailed)); // do failure handling here + if (!NewUnitDef.DependenciesLoaded(1000U)) + { + NewUnitDef.GatherDependencies(DM, dependencyLoad, 1000U); + } + if (!NewPilotDef.DependenciesLoaded(1000U)) + { + NewPilotDef.GatherDependencies(DM, dependencyLoad, 1000U); + } + DM.InjectDependencyLoader(dependencyLoad, 1000U); + } + else + { + this.OnBADepsLoaded(); + } + + } + public void SpawnBeaconUnitAtLocation() { LoadRequest loadRequest = DM.CreateLoadRequest(); @@ -840,69 +1048,6 @@ void DeferredInvokeBASpawn() => } } - public void OnStrafeDepsLoaded() - { - var newUnit = ActorFactory.CreateMech(NewUnitDef, NewPilotDef, - CustomEncounterTags, TeamSelection.Combat, - TeamSelection.GetNextSupportUnitGuid(), "", SupportHeraldryDef); - newUnit.Init(StrafeWave.NeutralTeam.OffScreenPosition, 0f, false); - newUnit.InitGameRep(null); - TeamSelection.AddUnit(newUnit); - newUnit.AddToTeam(TeamSelection); - newUnit.AddToLance(CustomLance); - CustomLance.AddUnitGUID(newUnit.GUID); - newUnit.GameRep.gameObject.SetActive(true); - newUnit.BehaviorTree = BehaviorTreeFactory.MakeBehaviorTree( - this.StrafeWave.Ability.Combat.BattleTechGame, newUnit, - BehaviorTreeIDEnum.DoNothingTree); - var eventID = Guid.NewGuid().ToString(); - ModInit.modLog?.Info?.Write($"Initializing Strafing Run (wave) with id {eventID}!"); - TB_StrafeSequence eventSequence = - new TB_StrafeSequence(this.ParentSequenceIDForStrafe, eventID, newUnit, SpawnLoc, - this.StrafeWave.PositionB, this.StrafeWave.Radius, this.StrafeWave.Team, ModState.IsStrafeAOE, this.StrafeWave.Ability.Def.IntParam1); - TurnEvent tEvent = new TurnEvent(eventID, Combat, - this.StrafeWave.Ability.Def.ActivationETA, null, eventSequence, this.StrafeWave.Ability.Def, false); - Combat.TurnDirector.AddTurnEvent(tEvent); - - - if (this.StrafeWave.Team.IsLocalPlayer && (ModInit.modSettings.commandUseCostsMulti > 0 || - this.StrafeWave.Ability.Def.getAbilityDefExtension().CBillCost > 0)) - { - var unitName = ""; - var unitCost = 0; - var unitID = ""; - unitName = NewUnitDef.Description.UIName; - unitID = NewUnitDef.Description.Id; - unitCost = NewUnitDef.Chassis.Description.Cost; - ModInit.modLog?.Info?.Write($"Usage cost will be {unitCost}"); - - if (ModState.CommandUses.All(x => x.UnitID != this.StrafeWave.ActorResource)) - { - - var commandUse = new CmdUseInfo(unitID, this.StrafeWave.Ability.Def.Description.Name, unitName, - unitCost, this.StrafeWave.Ability.Def.getAbilityDefExtension().CBillCost); - - ModState.CommandUses.Add(commandUse); - ModInit.modLog?.Info?.Write( - $"Added usage cost for {commandUse.CommandName} - {commandUse.UnitName}"); - } - else - { - var cmdUse = ModState.CommandUses.FirstOrDefault(x => x.UnitID == this.StrafeWave.ActorResource); - if (cmdUse == null) - { - ModInit.modLog?.Info?.Write($"ERROR: cmdUseInfo was null"); - } - else - { - cmdUse.UseCount += 1; - ModInit.modLog?.Info?.Write( - $"Added usage cost for {cmdUse.CommandName} - {cmdUse.UnitName}, used {cmdUse.UseCount} times"); - } - } - } - } - public void SpawnStrafingUnit() { LoadRequest loadRequest = DM.CreateLoadRequest(); @@ -964,361 +1109,42 @@ void DeferredInvokeBASpawn() => { NewPilotDef.GatherDependencies(DM, dependencyLoad, 1000U); } - DM.InjectDependencyLoader(dependencyLoad, 1000U); - } - else - { - this.OnStrafeDepsLoaded(); - } - } - } - -// public class AirliftWiggleConfig -// { -// public string AbilityDefID = ""; -// public float BaseSuccessChance = 0f; -// public float MaxSuccessChance = 0f; -// public float PilotingSuccessFactor = 0f; -// public float PilotingDamageReductionFactor = 0f; -// } - - - public class AI_BeaconProxyInfo - { - public string UnitDefID = ""; - public int Weight = 0; - public int StrafeWaves = 0; - } - public class BA_FactionAssoc - { - public List FactionIDs = new List(); - public float SpawnChanceBase = 0f; - public float SpawnChanceDiffMod = 0f; - public int MaxSquadsPerContract = 0; - public Dictionary InternalBattleArmorWeight = new Dictionary(); - public Dictionary MountedBattleArmorWeight = new Dictionary(); - public Dictionary HandsyBattleArmorWeight = new Dictionary(); - } - - public class BA_GarrisonInfo - { - public string BuildingGUID = ""; //guid of building - public Vector3 OriginalSquadPos = new Vector3(); // original position of squad. when exiting, squad will move here because pathing sucks and i hate it. - public BA_GarrisonInfo(BattleTech.Building building, TrooperSquad squad) - { - BuildingGUID = building.GUID; - OriginalSquadPos = squad.CurrentPosition; - } - } - - public class BA_DamageTracker - { - public string TargetGUID = ""; // guid of carrier unit. - public bool IsSquadInternal; - public Dictionary BA_MountedLocations = new Dictionary(); // key is BA unit chassis location (HD, CT, LT, RT, LA, RA), value is BA mounted ARMOR location on carrier. - - public BA_DamageTracker() - { - this.TargetGUID = ""; - this.IsSquadInternal = false; - this.BA_MountedLocations = new Dictionary(); - } - - public BA_DamageTracker(string targetGUID, bool internalSquad, Dictionary mountedLocations) - { - this.TargetGUID = targetGUID; - this.IsSquadInternal = internalSquad; - this.BA_MountedLocations = mountedLocations; - } - } - - public class AirliftTracker - { - public string CarrierGUID; // guid of carrier unit. - public bool IsCarriedInternal; - public bool IsFriendly; // key is BA unit chassis location (HD, CT, LT, RT, LA, RA), value is BA mounted ARMOR location on carrier. - public float Offset; - - public AirliftTracker() - { - this.CarrierGUID = ""; - this.IsCarriedInternal = false; - this.IsFriendly = false; - this.Offset = 0f; - } - - public AirliftTracker(string targetGUID, bool internalCarry, bool isFriendly, float offset) - { - this.CarrierGUID = targetGUID; - this.IsCarriedInternal = internalCarry; - this.IsFriendly = isFriendly; - this.Offset = offset; - } - } - - - public class BA_TargetEffect - { - public string ID = ""; - public ConfigOptions.BA_TargetEffectType TargetEffectType = ConfigOptions.BA_TargetEffectType.BOTH; - public string Name = ""; - public string Description = ""; - - [JsonIgnore] - public List effects = new List(); - public List effectDataJO = new List(); - - } - - public class AirliftTargetEffect - { - public string ID = ""; - public bool FriendlyAirlift; - public string Name = ""; - public string Description = ""; - - [JsonIgnore] - public List effects = new List(); - public List effectDataJO = new List(); - - } - - public class SpawnCoords - { - public string ID; - public Vector3 Loc; - public float DistFromTarget; - - public SpawnCoords(string id, Vector3 loc, float distFromTarget) - { - this.ID = id; - this.Loc = loc; - this.DistFromTarget = distFromTarget; - } - } - public class ColorSetting - { - public int r; - public int g; - public int b; - //public int a; - - public float Rf => r / 255f; - public float Gf => g / 255f; - public float Bf => b / 255f; - - public Color ProcessedColor => new Color(Rf, Gf, Bf); - //public float Af => a / 255f; - } - public class CmdUseStat - { - public string ID; - public string stat; - public string pilotID; - public bool consumeOnUse; - public int contractUses; - public int simStatCount; - - public CmdUseStat(string id, string stat, bool consumeOnUse, int contractUses, int simStatCount, string pilotId = null) - { - this.ID = id; - this.stat = stat; - this.pilotID = pilotId; - this.consumeOnUse = consumeOnUse; - this.contractUses = contractUses; - this.simStatCount = simStatCount; - } - } - public class CmdUseInfo - { - public string UnitID; - public string CommandName; - public string UnitName; - public int UseCost; - public int AbilityUseCost; - public int UseCostAdjusted => Mathf.RoundToInt((UseCost * ModInit.modSettings.commandUseCostsMulti) + AbilityUseCost); - public int UseCount; - public int TotalCost => UseCount * UseCostAdjusted; - - public CmdUseInfo(string unitId, string commandName, string unitName, int useCost, int abilityUseCost) - { - this.UnitID = unitId; - this.CommandName = commandName; - this.UnitName = unitName; - this.UseCost = useCost; - this.AbilityUseCost = abilityUseCost; - this.UseCount = 1; - } - } - - public class BA_DeswarmMovementInfo - { - public AbstractActor Carrier; - public List SwarmingUnits; - - public BA_DeswarmMovementInfo() - { - this.Carrier = null; - this.SwarmingUnits = new List(); - } - - public BA_DeswarmMovementInfo(AbstractActor carrier) - { - this.Carrier = carrier; - this.SwarmingUnits = new List(); - } - public BA_DeswarmMovementInfo(AbstractActor carrier, List swarmingUnits) - { - this.Carrier = carrier; - this.SwarmingUnits = swarmingUnits; - } - } - - public class AI_DealWithBAInvocation - { - public Ability ability; - public AbstractActor targetActor; - public bool active; - - public AI_DealWithBAInvocation() - { - this.ability = default(Ability); - this.targetActor = default(AbstractActor); - this.active = false; - } - public AI_DealWithBAInvocation(Ability cmdAbility, AbstractActor targetActor, bool active) - { - this.ability = cmdAbility; - this.targetActor = targetActor; - this.active = active; - } - } - - public class StrategicActorTargetInvocation - { - public Ability ability; - public AbstractActor targetActor; - public bool isFriendlyDismount; - public bool active; - - public StrategicActorTargetInvocation() - { - this.ability = default(Ability); - this.targetActor = default(AbstractActor); - this.isFriendlyDismount = false; - this.active = false; - } - public StrategicActorTargetInvocation(Ability cmdAbility, AbstractActor targetActor, bool active, bool isFriendlyDismount = false) - { - this.ability = cmdAbility; - this.targetActor = targetActor; - this.isFriendlyDismount = isFriendlyDismount; - this.active = active; - } - } - - public class AI_CmdInvocation - { - public Ability ability; - public Vector3 vectorOne; - public Vector3 vectorTwo; - public bool active; - public float dist; - - public AI_CmdInvocation() - { - this.ability = default(Ability); - this.vectorOne = new Vector3(); - this.vectorTwo = new Vector3(); - this.active = false; - this.dist = Vector3.Distance(vectorOne, vectorTwo); - } - public AI_CmdInvocation(Ability cmdAbility, Vector3 firstVector, Vector3 secondVector, bool active) - { - this.ability = cmdAbility; - this.vectorOne = firstVector; - this.vectorTwo = secondVector; - this.active = active; - this.dist = Vector3.Distance(vectorOne, vectorTwo); - } - } - public class AI_SpawnBehavior - { - public string Tag; - public string Behavior; - public int MinRange; - - public AI_SpawnBehavior() - { - Tag = "DEFAULT"; - Behavior = "DEFAULT"; - MinRange = 50; - } - } - - public class CmdInvocationParams - { - public string QUID = ""; - public int StrafeWaves; - public string ActorResource; - public string PilotOverride; - public AbilityDef.SpecialRules Rules; - public bool IsStrafeAOE; - public bool PlayerControl; - public bool PlayerControlOverridden; - - public CmdInvocationParams(int strafeWaves, string actorResource, string pilotOverride, - AbilityDef.SpecialRules rules, string quid = "", bool isStrafeAOE = false, bool playerControl = false, bool playerControlOverridden = false) - { - QUID = quid; - StrafeWaves = strafeWaves; - ActorResource = actorResource; - PilotOverride = pilotOverride; - Rules = rules; - IsStrafeAOE = isStrafeAOE; - PlayerControl = playerControl; - PlayerControlOverridden = playerControlOverridden; - } - - public CmdInvocationParams() - { - QUID = ""; - StrafeWaves = 0; - ActorResource = ""; - PilotOverride = ""; - Rules = AbilityDef.SpecialRules.NotSet; - IsStrafeAOE = false; - PlayerControl = false; - PlayerControlOverridden = false; + DM.InjectDependencyLoader(dependencyLoad, 1000U); + } + else + { + this.OnStrafeDepsLoaded(); + } } + } - public CmdInvocationParams(CmdInvocationParams cmdParams) + public class PendingBAPairingInfo + { + public string BAPilotID; + public LanceLoadoutMechItem MechItem; + + public PendingBAPairingInfo(string baPilotId, LanceLoadoutMechItem lanceLoadoutMechItem) { - QUID = cmdParams.QUID; - StrafeWaves = cmdParams.StrafeWaves; - ActorResource = cmdParams.ActorResource; - PilotOverride = cmdParams.PilotOverride; - Rules = cmdParams.Rules; - IsStrafeAOE = cmdParams.IsStrafeAOE; - PlayerControl = cmdParams.PlayerControl; - PlayerControlOverridden = cmdParams.PlayerControlOverridden; + this.BAPilotID = baPilotId; + this.MechItem = lanceLoadoutMechItem; } } + public class PendingStrafeWave { - public int RemainingWaves; public Ability Ability; - public Team Team; - public Vector3 PositionA; - public Vector3 PositionB; - public float Radius; public string ActorResource; - public Team NeutralTeam; public Lance CmdLance; - public PilotDef SupportPilotDef; - public HeraldryDef SupportHeraldryDef; public DataManager DM; public List FootPrintRects; + public Team NeutralTeam; + public Vector3 PositionA; + public Vector3 PositionB; + public float Radius; + public int RemainingWaves; + public HeraldryDef SupportHeraldryDef; + public PilotDef SupportPilotDef; + public Team Team; public PendingStrafeWave(int remainingWaves, Ability ability, Team team, Vector3 positionA, Vector3 positionB, float radius, string actorResource, Team neutralTeam, Lance cmdLance, PilotDef supportPilotDef, HeraldryDef supportHeraldryDef, DataManager dm) { @@ -1338,26 +1164,217 @@ public PendingStrafeWave(int remainingWaves, Ability ability, Team team, Vector3 } } - public class BAPairingInfo + public class SpawnCoords { - public int CapacityInitial; - public List PairedBattleArmor = new List(); - public BAPairingInfo(int capacityInitial, string pairedBattleArmor = null) + public float DistFromTarget; + public string ID; + public Vector3 Loc; + + public SpawnCoords(string id, Vector3 loc, float distFromTarget) { - this.CapacityInitial = capacityInitial; - if (pairedBattleArmor != null) this.PairedBattleArmor.Add(pairedBattleArmor); + this.ID = id; + this.Loc = loc; + this.DistFromTarget = distFromTarget; } } - public class PendingBAPairingInfo + public class StrategicActorTargetInvocation { - public string BAPilotID; - public LanceLoadoutMechItem MechItem; + public Ability ability; + public bool active; + public bool isFriendlyDismount; + public AbstractActor targetActor; - public PendingBAPairingInfo(string baPilotId, LanceLoadoutMechItem lanceLoadoutMechItem) + public StrategicActorTargetInvocation() { - this.BAPilotID = baPilotId; - this.MechItem = lanceLoadoutMechItem; + this.ability = default(Ability); + this.targetActor = default(AbstractActor); + this.isFriendlyDismount = false; + this.active = false; + } + + public StrategicActorTargetInvocation(Ability cmdAbility, AbstractActor targetActor, bool active, bool isFriendlyDismount = false) + { + this.ability = cmdAbility; + this.targetActor = targetActor; + this.isFriendlyDismount = isFriendlyDismount; + this.active = active; + } + } + + public class StrategicMovementInvocation : AbstractActorMovementInvocation + { + public new bool AbilityConsumesFiring; + public new string ActorGUID = ""; + public new Vector3 FinalOrientation; + public bool IsFriendly; + public bool IsMountOrSwarm; + public new string MeleeTargetGUID = ""; + public ICombatant MoveTarget; + public new MoveType MoveType; + public new List Waypoints = new List(); + public StrategicMovementInvocation(){} + + public StrategicMovementInvocation(AbstractActor actor, bool abilityConsumesFiring, ICombatant moveTarget, bool isFriendly, bool isMountOrSwarm) : base(actor, abilityConsumesFiring) + { + Pathing pathing = actor.Pathing; + + this.MoveTarget = moveTarget; + pathing.SetSprinting(); + pathing.UpdateFreePath(moveTarget.CurrentPosition, moveTarget.CurrentPosition, false, false); + pathing.UpdateLockedPath(moveTarget.CurrentPosition, moveTarget.CurrentPosition, false); + pathing.LockPosition(); + this.ActorGUID = actor.GUID; + this.AbilityConsumesFiring = abilityConsumesFiring; + List collection = ActorMovementSequence.ExtractWaypointsFromPath(actor, pathing.CurrentPath, pathing.ResultDestination, pathing.CurrentMeleeTarget, this.MoveType); + this.Waypoints = new List(collection); + this.MoveType = pathing.MoveType; + this.FinalOrientation = moveTarget.GameRep.transform.forward; + this.MeleeTargetGUID = ""; + this.IsFriendly = isFriendly; + this.IsMountOrSwarm = isMountOrSwarm; + } + + public override bool Invoke(CombatGameState combat) + { + InvocationMessage.logger.Log("Invoking a STRATEGIC MOVE!"); + AbstractActor abstractActor = combat.FindActorByGUID(this.ActorGUID); + if (abstractActor == null) + { + InvocationMessage.logger.LogError(string.Format("MechMovement.Invoke Actor with GUID {0} not found!", this.ActorGUID)); + return false; + } + ICombatant combatant = null; + if (!string.IsNullOrEmpty(this.MeleeTargetGUID)) + { + combatant = combat.FindCombatantByGUID(this.MeleeTargetGUID, false); + if (combatant == null) + { + InvocationMessage.logger.LogError(string.Format("MechMovement.Invoke ICombatant with GUID {0} not found!", this.MeleeTargetGUID)); + return false; + } + } + + StrategicMovementSequence stackSequence = new StrategicMovementSequence(abstractActor, this.Waypoints, this.FinalOrientation, this.MoveType, combatant, this.AbilityConsumesFiring, this.MoveTarget, this.IsFriendly, this.IsMountOrSwarm); + base.PublishStackSequence(combat.MessageCenter, stackSequence, this); + return true; + } + } + + public class StrategicMovementSequence : ActorMovementSequence + { + public bool IsFriendly; + public bool MountSwarmBA; //handle if airlifting unit dies? + public ICombatant Target; + public override bool ConsumesActivation => !MountSwarmBA;//!this.MountSwarmBA; //this might fuck up attack on swarm. grr. + + //public new virtual bool ForceActivationEnd => false; + + public StrategicMovementSequence(AbstractActor actor, List waypoints, Vector3 finalOrientation, MoveType moveType, ICombatant meleeTarget, bool consumesFiring, ICombatant target, bool friendly, bool mountORswarm) : base(actor, waypoints, finalOrientation, moveType, meleeTarget, consumesFiring) + { + this.Target = target; + this.IsFriendly = friendly; + this.MountSwarmBA = mountORswarm; + } + + public override void CompleteOrders() + { + base.owningActor.AutoBrace = false; + base.CompleteOrders(); + base.owningActor.ResetPathing(false); + + if (base.owningActor.team.IsLocalPlayer) + {} + + if (MountSwarmBA && owningActor is TrooperSquad squad) + { + if (this.Target is BattleTech.Building building) + { + ModInit.modLog?.Info?.Write( + $"[StrategicMovementSequence] Called for BA movement to garrison building {building.DisplayName}."); + squad.ProcessGarrisonBuilding(building); + return; + } + } + + if (this.Target is AbstractActor targetActor) + { + if (MountSwarmBA) + { + ModInit.modLog?.Info?.Write( + $"[StrategicMovementSequence] Called for BA movement to mount or swarm."); + if (base.owningActor is TrooperSquad squad2) + { + if (this.IsFriendly) + { + if (!squad2.IsMountedUnit()) + { + squad2.ProcessMountFriendly(targetActor); + return; + } + } + + if (!squad2.IsSwarmingUnit() && squad2.CanSwarm()) + { + squad2.ProcessSwarmEnemy(targetActor); + } + + if (ModInit.modSettings.AttackOnSwarmSuccess && squad2.IsSwarmingUnit()) + { + if (squad2.GetAbilityUsedFiring()) + { + ModInit.modLog?.Info?.Write($"[StrategicMovementSequence] Actor {squad2.DisplayName} has used an ability that consumed firing, not generating swarm."); + return; + } + if (!squad2.team.IsLocalPlayer) + { + foreach (var weapon in squad2.Weapons) + { + weapon.EnableWeapon(); + } + } + + var weps = squad2.Weapons.Where(x => x.IsEnabled && x.HasAmmo).ToList(); + var loc = ModState.BADamageTrackers[squad2.GUID].BA_MountedLocations.Values + .GetRandomElement(); + + var attackStackSequence = new AttackStackSequence(squad2, targetActor, + squad2.CurrentPosition, + squad2.CurrentRotation, weps, MeleeAttackType.NotSet, loc, -1); + squad2.Combat.MessageCenter.PublishMessage( + new AddSequenceToStackMessage(attackStackSequence)); + ModInit.modLog?.Info?.Write( + $"[StrategicMovementSequence - CompleteOrders] Creating attack sequence on successful swarm attack targeting location {loc}."); + + } + + //doattacksequencehere + + } + else + { + ModInit.modLog?.Info?.Write( + $"[StrategicMovementSequence] ERROR: called sequence for BA, but actor is not TrooperSquad."); + return; + } + } + else if (!MountSwarmBA) + { + ModInit.modLog?.Info?.Write( + $"[StrategicMovementSequence] Called for airlift/dropoff for Target {this.Target.DisplayName}."); + + if (targetActor.IsAirlifted()) + { + targetActor.DetachFromAirliftCarrier(base.OwningActor, IsFriendly); + return; + } + + if (!targetActor.IsAirlifted()) + { + targetActor.AttachToAirliftCarrier(base.OwningActor, IsFriendly); + } + } + } } } } diff --git a/StrategicOperations/StrategicOperations/Framework/DropPodSpawn.cs b/StrategicOperations/StrategicOperations/Framework/DropPodSpawn.cs index 33011fa..943c2fd 100644 --- a/StrategicOperations/StrategicOperations/Framework/DropPodSpawn.cs +++ b/StrategicOperations/StrategicOperations/Framework/DropPodSpawn.cs @@ -26,27 +26,52 @@ public static void Postfix(CombatHUD __instance, CombatGameState Combat) public class DropPodSpawner: MonoBehaviour { - public AbstractActor Unit { get; set; } = null; - public bool DropProcessing { get; set; } = false; - public EncounterLayerParent Parent { get; set; } = null; - public ParticleSystem DropPodVfxPrefab + //public string SpawnGUID { get; set; } + //public Action OnDropComplete { get; set; } + public CombatGameState Combat; + + public Vector3 DropPodPosition; + public Quaternion DropPodRotation; + + public GameObject DropPodLandedPrefab { get; set; } - public GameObject DropPodLandedPrefab + public ParticleSystem DropPodVfxPrefab { get; set; } - public Vector3 DropPodPosition; - public Quaternion DropPodRotation; + public bool DropProcessing { get; set; } = false; public Vector3 OffscreenDropPodPosition { get; set; } = Vector3.zero; - //public string SpawnGUID { get; set; } - //public Action OnDropComplete { get; set; } - public CombatGameState Combat; + public EncounterLayerParent Parent { get; set; } = null; + public AbstractActor Unit { get; set; } = null; + + public IEnumerator DestroyFlimsyObjects(Vector3 position) + { + Collider[] hits = Physics.OverlapSphere(position, 36f, -5, QueryTriggerInteraction.Ignore); + float impactMagnitude = 3f * Combat.Constants.ResolutionConstants.FlimsyDestructionForceMultiplier; + for (int i = 0; i < hits.Length; ++i) + { + Collider collider = hits[i]; + Vector3 normalized = (collider.transform.position - position).normalized; + DestructibleObject component1 = collider.gameObject.GetComponent(); + DestructibleUrbanFlimsy component2 = collider.gameObject.GetComponent(); + if (component1 != null && component1.isFlimsy) + { + component1.TakeDamage(position, normalized, impactMagnitude); + component1.Collapse(normalized, impactMagnitude); + } + if ((UnityEngine.Object)component2 != (UnityEngine.Object)null) + component2.PlayDestruction(normalized, impactMagnitude); + if (i % 10 == 0) + yield return (object)null; + } + yield return (object)null; + } public void LoadDropPodPrefabs(ParticleSystem dropPodVfxPrefab, GameObject dropPodLandedPrefab) { @@ -113,29 +138,6 @@ public void TeleportUnitToSpawnPoint() this.Unit.GameRep.FadeIn(1f); this.Unit.OnPlayerVisibilityChanged(VisibilityLevel.LOSFull); } - - public IEnumerator DestroyFlimsyObjects(Vector3 position) - { - Collider[] hits = Physics.OverlapSphere(position, 36f, -5, QueryTriggerInteraction.Ignore); - float impactMagnitude = 3f * Combat.Constants.ResolutionConstants.FlimsyDestructionForceMultiplier; - for (int i = 0; i < hits.Length; ++i) - { - Collider collider = hits[i]; - Vector3 normalized = (collider.transform.position - position).normalized; - DestructibleObject component1 = collider.gameObject.GetComponent(); - DestructibleUrbanFlimsy component2 = collider.gameObject.GetComponent(); - if (component1 != null && component1.isFlimsy) - { - component1.TakeDamage(position, normalized, impactMagnitude); - component1.Collapse(normalized, impactMagnitude); - } - if ((UnityEngine.Object)component2 != (UnityEngine.Object)null) - component2.PlayDestruction(normalized, impactMagnitude); - if (i % 10 == 0) - yield return (object)null; - } - yield return (object)null; - } } } } diff --git a/StrategicOperations/StrategicOperations/Framework/ModState.cs b/StrategicOperations/StrategicOperations/Framework/ModState.cs index 88a18ba..4fb05ee 100644 --- a/StrategicOperations/StrategicOperations/Framework/ModState.cs +++ b/StrategicOperations/StrategicOperations/Framework/ModState.cs @@ -9,99 +9,101 @@ namespace StrategicOperations.Framework { public static class ModState { - public static List ProcessedOverlayColors = new List(); - public static Dictionary UsedOverlayColorsByCarrier = new Dictionary(); - public static List UsedOverlayColors = new List(); - public static Color DefaultOverlay = new Color(); - public static Color PendingSelectionColor = new Color(0.15f, 0.15f, 0.17f, .5f); - //public static string UnitPendingAirliftInvocation = ""; - //public static AbstractActor startUnitFromInvocation = null; - public static LanceLoadoutSlot PendingPairBAUnit = null; - - public static Dictionary PairingInfos = new Dictionary(); - public static bool ReinitPhaseIcons = false; - public static float CancelChanceForPlayerStrafe = 0f; - public static List TeamsWithResupply = new List(); - public static AbstractActor CurrentGarrisonSquadForLOS = null; - public static AbstractActor CurrentGarrisonSquadForLOF = null; - public static List CurrentContractBASpawners = new List(); - public static Dictionary GarrisonFriendlyTeam = new Dictionary(); - public static float SwarmSuccessChance = 0f; - public static float DeSwarmSuccessChance = 0f; - - public static Dictionary>> CachedFactionAssociations = new Dictionary>>(); - public static Dictionary>> CachedFactionCommandBeacons = new Dictionary>>(); // key1 is abilityID, key2 is faction name + public static Dictionary AiCmds = new Dictionary(); - public static Dictionary CurrentBattleArmorSquads = new Dictionary(); - public static Dictionary> CurrentCommandUnits = new Dictionary>(); + public static Dictionary AiDealWithBattleArmorCmds = new Dictionary(); - public static List CurrentFactionSettingsList = new List(); + public static List AirliftEffects = new List(); + //public static Dictionary PositionLockAirlift = new Dictionary(); // key is mounted unit, value is carrier - public static bool IsStrafeAOE = false; - public static Dictionary PendingStrafeWaves = - new Dictionary(); - public static List MechArmorMountOrder = new List(); - public static List MechArmorSwarmOrder = new List(); + public static Dictionary AirliftTrackers = new Dictionary(); //Key is mounted unit, value has carrier - public static List VehicleMountOrder = new List(); + public static List BA_MountSwarmEffects = new List(); public static Dictionary BADamageTrackers = new Dictionary(); // key is GUID of BA squad + public static Dictionary>> CachedFactionAssociations = new Dictionary>>(); + public static Dictionary>> CachedFactionCommandBeacons = new Dictionary>>(); // key1 is abilityID, key2 is faction name + //public static Dictionary SavedBAScale = new Dictionary(); // should always be 1,1,1 public static Dictionary CachedUnitCoordinates = new Dictionary(); - public static Dictionary PositionLockGarrison = new Dictionary(); // key is mounted unit, value is building - public static Dictionary PositionLockMount = new Dictionary(); // key is mounted unit, value is carrier - public static Dictionary PositionLockSwarm = new Dictionary(); // key is mounted unit, value is carrier - //public static Dictionary PositionLockAirlift = new Dictionary(); // key is mounted unit, value is carrier + public static float CancelChanceForPlayerStrafe = 0f; - public static Dictionary AirliftTrackers = new Dictionary(); //Key is mounted unit, value has carrier + public static List CommandAbilities = new List(); - public static Dictionary ResupplyShutdownPhases = new Dictionary(); + public static List CommandUses = new List(); - public static List CommandAbilities = new List(); + public static Dictionary CurrentBattleArmorSquads = new Dictionary(); + public static Dictionary> CurrentCommandUnits = new Dictionary>(); + public static List CurrentContractBASpawners = new List(); - public static List> - DeferredInvokeSpawns = new List>(); + public static List CurrentFactionSettingsList = new List(); + public static AbstractActor CurrentGarrisonSquadForLOF = null; + public static AbstractActor CurrentGarrisonSquadForLOS = null; + public static Color DefaultOverlay = new Color(); - public static List> - DeferredInvokeBattleArmor = new List>(); + public static string DeferredActorResource = ""; + public static bool DeferredBattleArmorSpawnerFromDelegate; public static Dictionary DeferredDespawnersFromStrafe = new Dictionary(); - public static string DeferredActorResource = ""; - - public static Dictionary StoredCmdParams = - new Dictionary(); + public static List> + DeferredInvokeBattleArmor = new List>(); - public static CmdInvocationParams PendingPlayerCmdParams = new CmdInvocationParams(); + public static List> + DeferredInvokeSpawns = new List>(); //public static string PopupActorResource = ""; //public static int StrafeWaves; //public static string PilotOverride = null; public static bool DeferredSpawnerFromDelegate; - public static bool DeferredBattleArmorSpawnerFromDelegate; - public static bool OutOfRange; - public static Dictionary AiDealWithBattleArmorCmds = new Dictionary(); + public static List DeploymentAssetsStats = new List(); - public static Dictionary AiCmds = new Dictionary(); + public static BA_DeswarmMovementInfo DeSwarmMovementInfo = new BA_DeswarmMovementInfo(); + public static float DeSwarmSuccessChance = 0f; + public static Dictionary GarrisonFriendlyTeam = new Dictionary(); - public static Dictionary StrategicActorTargetInvocationCmds = new Dictionary(); + public static bool IsStrafeAOE = false; + public static List MechArmorMountOrder = new List(); + public static List MechArmorSwarmOrder = new List(); + public static List OnGarrisonCollapseEffects = new List(); + public static bool OutOfRange; - public static List CommandUses = new List(); + public static Dictionary PairingInfos = new Dictionary(); - public static List DeploymentAssetsStats = new List(); + //public static string UnitPendingAirliftInvocation = ""; + //public static AbstractActor startUnitFromInvocation = null; + public static LanceLoadoutSlot PendingPairBAUnit = null; - public static List BA_MountSwarmEffects = new List(); - public static List OnGarrisonCollapseEffects = new List(); - public static List AirliftEffects = new List(); + public static CmdInvocationParams PendingPlayerCmdParams = new CmdInvocationParams(); + public static Color PendingSelectionColor = new Color(0.15f, 0.15f, 0.17f, .5f); - public static BA_DeswarmMovementInfo DeSwarmMovementInfo = new BA_DeswarmMovementInfo(); + public static Dictionary PendingStrafeWaves = + new Dictionary(); public static List PlayerSpawnGUIDs = new List(); + public static Dictionary PositionLockGarrison = new Dictionary(); // key is mounted unit, value is building + public static Dictionary PositionLockMount = new Dictionary(); // key is mounted unit, value is carrier + public static Dictionary PositionLockSwarm = new Dictionary(); // key is mounted unit, value is carrier + public static List ProcessedOverlayColors = new List(); + public static bool ReinitPhaseIcons = false; + + public static Dictionary ResupplyShutdownPhases = new Dictionary(); + + public static Dictionary StoredCmdParams = + new Dictionary(); + + public static Dictionary StrategicActorTargetInvocationCmds = new Dictionary(); + public static float SwarmSuccessChance = 0f; + public static List TeamsWithResupply = new List(); + public static List UsedOverlayColors = new List(); + public static Dictionary UsedOverlayColorsByCarrier = new Dictionary(); + + public static List VehicleMountOrder = new List(); public static void Initialize() { @@ -229,21 +231,22 @@ public static void ResetAll() ReinitPhaseIcons = false; } - public static void ResetDelegateInfos() + public static void ResetDeferredBASpawners() { - DeferredSpawnerFromDelegate = false; - DeferredActorResource = ""; - //PopupActorResource = ""; - //PilotOverride = null; + DeferredInvokeBattleArmor = new List>(); } public static void ResetDeferredSpawners() { DeferredInvokeSpawns = new List>(); } - public static void ResetDeferredBASpawners() + + public static void ResetDelegateInfos() { - DeferredInvokeBattleArmor = new List>(); + DeferredSpawnerFromDelegate = false; + DeferredActorResource = ""; + //PopupActorResource = ""; + //PilotOverride = null; } } } diff --git a/StrategicOperations/StrategicOperations/Framework/ResupplyUtils.cs b/StrategicOperations/StrategicOperations/Framework/ResupplyUtils.cs index c38ef7e..3b82573 100644 --- a/StrategicOperations/StrategicOperations/Framework/ResupplyUtils.cs +++ b/StrategicOperations/StrategicOperations/Framework/ResupplyUtils.cs @@ -12,24 +12,6 @@ namespace StrategicOperations.Framework { public static class ResupplyUtils { - public static void ModifyAmmoCount(this Weapon weapon, int value) - { - weapon.StatCollection.ModifyStat("resupplyAMMO", -1, "InternalAmmo", StatCollection.StatOperation.Int_Add, value); - } - public static void ModifyAmmoCount(this AmmunitionBox box, int value) - { - box.StatCollection.ModifyStat("resupplyAMMO", -1, "CurrentAmmo", StatCollection.StatOperation.Int_Add, value); - box.tCurrentAmmo(box.CurrentAmmo); - } - public static void ZeroAmmoCount(this AmmunitionBox box) - { - box.StatCollection.ModifyStat("resupplyAMMO", -1, "CurrentAmmo", StatCollection.StatOperation.Set, 0); - } - public static void ModifyMechArmorValue(this Mech mech, ArmorLocation loc, float value) - { - mech.StatCollection.ModifyStat("resupplyARMOR", -1, mech.GetStringForArmorLocation(loc), StatCollection.StatOperation.Float_Add, value); - } - public static bool AreAnyWeaponsOutOfAmmo(this AbstractActor actor) { foreach (var weapon in actor.Weapons) @@ -38,41 +20,41 @@ public static bool AreAnyWeaponsOutOfAmmo(this AbstractActor actor) } return true; } - - public static float GetDistanceToClosestDetectedResupply(this AbstractActor actor, Vector3 position) + + public static AbstractActor GetClosestDetectedResupply(this AbstractActor actor) { var friendlyUnits = actor.team.VisibilityCache.GetAllFriendlies(actor).Where(x => !x.IsDead && !x.IsFlaggedForDeath); var num = -1f; - var magnitude = -9999f; + var distance = -9999f; + AbstractActor resupplyActor = null; foreach (var friendly in friendlyUnits) { if (!friendly.GetStaticUnitTags().Contains(ModInit.modSettings.ResupplyConfig.ResupplyUnitTag)) continue; - magnitude = (position - friendly.CurrentPosition).magnitude; - if (num < 0f || magnitude < num) + distance = Vector3.Distance(actor.CurrentPosition, friendly.CurrentPosition); + if (num < 0f || distance < num) { - num = magnitude; + num = distance; + resupplyActor = friendly; } } - return magnitude; + return resupplyActor; } - public static AbstractActor GetClosestDetectedResupply(this AbstractActor actor) + public static float GetDistanceToClosestDetectedResupply(this AbstractActor actor, Vector3 position) { var friendlyUnits = actor.team.VisibilityCache.GetAllFriendlies(actor).Where(x => !x.IsDead && !x.IsFlaggedForDeath); var num = -1f; - var distance = -9999f; - AbstractActor resupplyActor = null; + var magnitude = -9999f; foreach (var friendly in friendlyUnits) { if (!friendly.GetStaticUnitTags().Contains(ModInit.modSettings.ResupplyConfig.ResupplyUnitTag)) continue; - distance = Vector3.Distance(actor.CurrentPosition, friendly.CurrentPosition); - if (num < 0f || distance < num) + magnitude = (position - friendly.CurrentPosition).magnitude; + if (num < 0f || magnitude < num) { - num = distance; - resupplyActor = friendly; + num = magnitude; } } - return resupplyActor; + return magnitude; } public static void InitiateShutdownForPhases(this AbstractActor actor, int phases) @@ -94,6 +76,22 @@ public static void InitiateShutdownForPhases(this AbstractActor actor, int phase } } + public static void ModifyAmmoCount(this Weapon weapon, int value) + { + weapon.StatCollection.ModifyStat("resupplyAMMO", -1, "InternalAmmo", StatCollection.StatOperation.Int_Add, value); + } + + public static void ModifyAmmoCount(this AmmunitionBox box, int value) + { + box.StatCollection.ModifyStat("resupplyAMMO", -1, "CurrentAmmo", StatCollection.StatOperation.Int_Add, value); + box.tCurrentAmmo(box.CurrentAmmo); + } + + public static void ModifyMechArmorValue(this Mech mech, ArmorLocation loc, float value) + { + mech.StatCollection.ModifyStat("resupplyARMOR", -1, mech.GetStringForArmorLocation(loc), StatCollection.StatOperation.Float_Add, value); + } + public static int ProcessResupplyUnit(this AbstractActor actor, AbstractActor resupplyActor) { var searchForSpammy = !string.IsNullOrEmpty(ModInit.modSettings.ResupplyConfig.SPAMMYAmmoDefId); @@ -316,22 +314,6 @@ public static int ProcessResupplyUnit(this AbstractActor actor, AbstractActor re ModInit.modLog?.Trace?.Write($"[ProcessResupplyUnit] - Calculated resupply should take {finalPhases} phases: {ModInit.modSettings.ResupplyConfig.BasePhasesToResupply} from baseline, {phasesFromAmmo} from ammo, {phasesFromArmor} from armor, x {multiFromTags} total from tags."); return finalPhases; } - - public static void UpdateResupplyTeams(this CombatGameState combat) - { - foreach (var actor in combat.GetAllLivingActors()) - { - if (actor.GetStaticUnitTags().Contains(ModInit.modSettings.ResupplyConfig.ResupplyUnitTag)) - { - foreach (var team in combat.Teams) - { - if (ModState.TeamsWithResupply.Contains(team.GUID)) continue; - if (team.IsFriendly(actor.team)) ModState.TeamsWithResupply.Add(team.GUID); - } - if (!ModState.TeamsWithResupply.Contains(actor.team.GUID)) ModState.TeamsWithResupply.Add(actor.team.GUID); - } - } - } public static void UpdateResupplyAbilitiesGetAllLivingActors(this CombatGameState combat) { @@ -356,5 +338,26 @@ public static void UpdateResupplyAbilitiesGetAllLivingActors(this CombatGameStat } } } + + public static void UpdateResupplyTeams(this CombatGameState combat) + { + foreach (var actor in combat.GetAllLivingActors()) + { + if (actor.GetStaticUnitTags().Contains(ModInit.modSettings.ResupplyConfig.ResupplyUnitTag)) + { + foreach (var team in combat.Teams) + { + if (ModState.TeamsWithResupply.Contains(team.GUID)) continue; + if (team.IsFriendly(actor.team)) ModState.TeamsWithResupply.Add(team.GUID); + } + if (!ModState.TeamsWithResupply.Contains(actor.team.GUID)) ModState.TeamsWithResupply.Add(actor.team.GUID); + } + } + } + + public static void ZeroAmmoCount(this AmmunitionBox box) + { + box.StatCollection.ModifyStat("resupplyAMMO", -1, "CurrentAmmo", StatCollection.StatOperation.Set, 0); + } } } diff --git a/StrategicOperations/StrategicOperations/Framework/SpawnUtils.cs b/StrategicOperations/StrategicOperations/Framework/SpawnUtils.cs index 63ac31d..aded9df 100644 --- a/StrategicOperations/StrategicOperations/Framework/SpawnUtils.cs +++ b/StrategicOperations/StrategicOperations/Framework/SpawnUtils.cs @@ -60,18 +60,6 @@ public static Vector3 FetchRandomAdjacentHexFromVector(this Vector3 startVector, return point; } - public static void ResetPathGridSpawn(this Pathing pathing, Vector3 origin, float beginAngle, AbstractActor actor, bool justStoodUp) - { - pathing.OwningActor = actor; - pathing.PathingCaps = actor.PathingCaps; - pathing.MovementCaps = actor.MovementCaps; - float num = 1f - (justStoodUp ? (0.75f - (float)pathing.OwningActor.SkillPiloting / pathing.Combat.Constants.PilotingConstants.PilotingDivisor) : 0f); - pathing.WalkingGrid.ResetPathGrid(origin, beginAngle, pathing.PathingCaps, actor.MaxWalkDistanceInital() * num, MoveType.Walking); - pathing.SprintingGrid.ResetPathGrid(origin, beginAngle, pathing.PathingCaps, actor.MaxSprintDistanceInital() * num, MoveType.Sprinting); - pathing.BackwardGrid.ResetPathGrid(origin, beginAngle, pathing.PathingCaps, actor.MaxBackwardDistance * num, MoveType.Backward); - pathing.IsLockedToDest = false; - } - public static Vector3 FindValidSpawn(AbstractActor target, AbstractActor source, int minRange, int maxRange) { var pathing = new Pathing(target); @@ -124,5 +112,17 @@ public static Vector3 FindValidSpawn(AbstractActor target, AbstractActor source, } return usableNodes[0].Loc; } + + public static void ResetPathGridSpawn(this Pathing pathing, Vector3 origin, float beginAngle, AbstractActor actor, bool justStoodUp) + { + pathing.OwningActor = actor; + pathing.PathingCaps = actor.PathingCaps; + pathing.MovementCaps = actor.MovementCaps; + float num = 1f - (justStoodUp ? (0.75f - (float)pathing.OwningActor.SkillPiloting / pathing.Combat.Constants.PilotingConstants.PilotingDivisor) : 0f); + pathing.WalkingGrid.ResetPathGrid(origin, beginAngle, pathing.PathingCaps, actor.MaxWalkDistanceInital() * num, MoveType.Walking); + pathing.SprintingGrid.ResetPathGrid(origin, beginAngle, pathing.PathingCaps, actor.MaxSprintDistanceInital() * num, MoveType.Sprinting); + pathing.BackwardGrid.ResetPathGrid(origin, beginAngle, pathing.PathingCaps, actor.MaxBackwardDistance * num, MoveType.Backward); + pathing.IsLockedToDest = false; + } } } \ No newline at end of file diff --git a/StrategicOperations/StrategicOperations/Framework/TB_FlyAwaySequence.cs b/StrategicOperations/StrategicOperations/Framework/TB_FlyAwaySequence.cs index 809203e..8df7053 100644 --- a/StrategicOperations/StrategicOperations/Framework/TB_FlyAwaySequence.cs +++ b/StrategicOperations/StrategicOperations/Framework/TB_FlyAwaySequence.cs @@ -6,33 +6,26 @@ namespace StrategicOperations.Framework { public class TB_FlyAwaySequence : MultiSequence { - public AbstractActor actor { get; set; } - public Vector3 startDirection { get; set; } - public float speed { get; set; } - private float heightOffset { get; set; } - private float minWeaponRange { get; set; } + private float lift = 5f; + private float maxMapCoord = 1200f; + private float minMapCoord = -1200f; - private bool IsOffMap => (this.actor.CurrentPosition.x < this.minMapCoord || this.actor.CurrentPosition.x > this.maxMapCoord) && (this.actor.CurrentPosition.z < this.minMapCoord || this.actor.CurrentPosition.z > this.maxMapCoord); + private TB_FlyAwaySequence.SequenceState state; + private float thrust = 50f; + private float timeInCurrentState; + private Vector3 velocity; + public AbstractActor actor { get; set; } + private float heightOffset { get; set; } public override bool IsCancelable => false; public override bool IsComplete => this.state == SequenceState.Finished; + + private bool IsOffMap => (this.actor.CurrentPosition.x < this.minMapCoord || this.actor.CurrentPosition.x > this.maxMapCoord) && (this.actor.CurrentPosition.z < this.minMapCoord || this.actor.CurrentPosition.z > this.maxMapCoord); public override bool IsParallelInterruptable => false; public override bool IsValidMultiSequenceChild => false; - - private enum SequenceState - { - None, - FlyingAway, - Finished - } - - private TB_FlyAwaySequence.SequenceState state; - private float timeInCurrentState; - private float minMapCoord = -1200f; - private float maxMapCoord = 1200f; - private float lift = 5f; - private float thrust = 50f; - private Vector3 velocity; + private float minWeaponRange { get; set; } + public float speed { get; set; } + public Vector3 startDirection { get; set; } public TB_FlyAwaySequence(AbstractActor actor, Vector3 directionStart, float speed) : base(actor.Combat) { @@ -45,28 +38,41 @@ public TB_FlyAwaySequence(AbstractActor actor, Vector3 directionStart, float spe public override void Load(SerializationStream stream) { } + public override void LoadComplete() { } + public override void OnAdded() { base.OnAdded(); this.SetState(TB_FlyAwaySequence.SequenceState.FlyingAway); } + public override void OnUpdate() { base.OnUpdate(); this.Update(); } + public override void Save(SerializationStream stream) { } + + private enum SequenceState + { + None, + FlyingAway, + Finished + } + private void SetPosition(Vector3 position, Quaternion rotation) { this.actor.GameRep.thisTransform.position = position; this.actor.GameRep.thisTransform.rotation = rotation; this.actor.OnPositionUpdate(position, rotation, base.SequenceGUID, false, null, true); } + private void SetState(TB_FlyAwaySequence.SequenceState newState) { if (this.state == newState) @@ -88,14 +94,17 @@ private void SetState(TB_FlyAwaySequence.SequenceState newState) } this.actor.PlaceFarAwayFromMap(); } + public override bool ShouldSave() { return false; } + public override int Size() { return 0; } + private void Update() { diff --git a/StrategicOperations/StrategicOperations/Framework/TB_StrafeSequence.cs b/StrategicOperations/StrategicOperations/Framework/TB_StrafeSequence.cs index 9d7a961..5221ec6 100644 --- a/StrategicOperations/StrategicOperations/Framework/TB_StrafeSequence.cs +++ b/StrategicOperations/StrategicOperations/Framework/TB_StrafeSequence.cs @@ -13,12 +13,36 @@ namespace StrategicOperations.Framework { public class TB_StrafeSequence : MultiSequence { + public enum SequenceState + { + None, + Incoming, + Strafing, + Finished + } + + private const float HorizMultiplier = 4f; + private const float TimeBetweenAttacks = 0.35f; + + private const float TimeIncoming = 6f; + +// private float speed = 150f; + private TB_StrafeSequence.SequenceState _state; + private float _timeInCurrentState; + private float _timeSinceLastAttack; + private Vector3 _zeroEndPos; + private Vector3 _zeroStartPos; + public int AOECount; + public List AOEPositions = new List(); + public List attackSequences = new List(); + public CombatHUD HUD; + public bool IsStrafeAOE; public string ParentSequenceID; - public string TurnEventID; private Team StrafingTeam; - private List CurrentTargets { get; set; } + public string TurnEventID; private List AllTargetGUIDs { get; set; } private AbstractActor Attacker { get; set; } + private List CurrentTargets { get; set; } private Vector3 EndPos { get; set; } private float HeightOffset { get; set; } public override bool IsCancelable => false; @@ -28,31 +52,11 @@ public class TB_StrafeSequence : MultiSequence private float MaxWeaponRange { get; set; } private float Radius { get; set; } private Vector3 StartPos { get; set; } + private float StrafeLength { get; set; } + // private List StrafeWeapons { get; set; } private Vector3 Velocity { get; set; } - private const float HorizMultiplier = 4f; -// private float speed = 150f; - private TB_StrafeSequence.SequenceState _state; - private const float TimeBetweenAttacks = 0.35f; - private const float TimeIncoming = 6f; - private float _timeInCurrentState; - private float _timeSinceLastAttack; - private Vector3 _zeroEndPos; - private Vector3 _zeroStartPos; - public List attackSequences = new List(); - public bool IsStrafeAOE; - public int AOECount; - public List AOEPositions = new List(); - public CombatHUD HUD; - - public enum SequenceState - { - None, - Incoming, - Strafing, - Finished - } public TB_StrafeSequence(string parentSequenceID, string turnEventID, AbstractActor attacker, Vector3 positionA, Vector3 positionB, float radius, Team team, bool isStrafeAOE, int strafeAOECount = 0) : base(attacker.Combat) @@ -203,6 +207,7 @@ private void AttackNextTargets() } // ModInit.modLog?.Info?.Write($"timeSinceAttack was {this.timeSinceLastAttack} (needs to be > {timeBetweenAttacks}) and IsAnyAttackSequenceActive?: {base.Combat.AttackDirector.IsAnyAttackSequenceActive} should be false"); } + private Vector3 CalcStartPos() { this.MaxWeaponRange = 400; @@ -323,6 +328,7 @@ private void CalcTargets() Vector3 preStartPos = this.EndPos - this.StartPos * 2f; this.CurrentTargets.Sort((ICombatant x, ICombatant y) => Vector3.Distance(x.CurrentPosition, preStartPos).CompareTo(Vector3.Distance(y.CurrentPosition, preStartPos))); } + private void GetWeaponsForStrafe() { //this.StrafeWeapons = this.Attacker.Weapons; //new List(this.Attacker.Weapons); @@ -350,12 +356,15 @@ private bool IsTarget(ICombatant combatant) ModInit.modLog?.Debug?.Write($"Target {combatant.DisplayName} not within strafe radius range. Distance: {dist}, Range: {this.Radius}"); return false; } + public override void Load(SerializationStream stream) { } + public override void LoadComplete() { } + public override void OnAdded() { base.OnAdded(); @@ -399,15 +408,18 @@ public override void OnUpdate() base.OnUpdate(); this.Update(); } + public override void Save(SerializationStream stream) { } + private void SetPosition(Vector3 position, Quaternion rotation) { this.Attacker.GameRep.thisTransform.position = position; this.Attacker.GameRep.thisTransform.rotation = rotation; this.Attacker.OnPositionUpdate(position, rotation, base.SequenceGUID, false, null, true); } + private void SetState(TB_StrafeSequence.SequenceState newState) { if (this._state == newState) @@ -463,14 +475,17 @@ private void SetState(TB_StrafeSequence.SequenceState newState) return; } } + public override bool ShouldSave() { return false; } + public override int Size() { return 0; } + private void Update() { this._timeInCurrentState += Time.deltaTime; diff --git a/StrategicOperations/StrategicOperations/Framework/Utils.cs b/StrategicOperations/StrategicOperations/Framework/Utils.cs index 55caa48..9ec539d 100644 --- a/StrategicOperations/StrategicOperations/Framework/Utils.cs +++ b/StrategicOperations/StrategicOperations/Framework/Utils.cs @@ -17,16 +17,6 @@ namespace StrategicOperations.Framework { public static class Utils { - public static void TeleportActorVisual(this AbstractActor actor, Vector3 newPosition) - { - actor.CurrentPosition = newPosition; - actor.GameRep.transform.position = newPosition; - actor.OnPositionUpdate(newPosition, actor.CurrentRotation, -1, true, null, false); - actor.previousPosition = newPosition; - //actor.ResetPathing(false); - //actor.RebuildPathingForNoMovement(); - actor.IsTeleportedOffScreen = false; - } public static void ActivateSpawnTurretFromActor(this Ability __instance, AbstractActor creator, Team team, Vector3 positionA, Vector3 positionB) { if (__instance.Combat.ActiveContract.ContractTypeValue.IsSkirmish) return; @@ -460,7 +450,6 @@ public static void ActivateSpawnTurretFromActor(this Ability __instance, Abstrac } - public static void ActivateStrafeFromActor(this Ability __instance, AbstractActor creator, Team team, Vector3 positionA, Vector3 positionB, float radius) { if (__instance.Combat.ActiveContract.ContractTypeValue.IsSkirmish) return; @@ -592,45 +581,58 @@ public static void ActivateStrafeFromActor(this Ability __instance, AbstractActo return; } - - - - - public static bool IsComponentPlayerControllable(this TagSet tagset, out bool forced) + public static void ApplyCreatorEffects(this Ability ability, AbstractActor creator) { - if (tagset.Any(x => x == "StratOps_player_control_enable")) - { - forced = true; - return true; - } - if (tagset.Any(x => x == "StratOps_player_control_disable")) + for (int i = 0; i < ability.Def.EffectData.Count; i++) { - forced = true; - return false; - } + if (ability.Def.EffectData[i].targetingData.effectTriggerType == EffectTriggerType.OnActivation && ability.Def.EffectData[i].targetingData.effectTargetType == EffectTargetType.Creator) + { + if (ability.Def.EffectData[i].effectType == EffectType.VFXEffect) + { + if (false) + { + List list = new List(); + var pos = creator.CurrentPosition + Vector3.up; + ObjectSpawnData item = new ObjectSpawnData(ability.Def.EffectData[i].vfxData.vfxName, pos, + Quaternion.identity, true, false); + list.Add(item); - forced = false; - return true; - } - public static bool ShouldPlayerControlSpawn(Team team, Ability ability, string quid) - { - var sim = UnityGameInstance.BattleTechGame.Simulation; - if (sim == null) return false; - if (!team.IsLocalPlayer) return false; - if (ModInit.modSettings.PlayerControlSpawns) return true; + var duration = ability.Def.EffectData[i].durationData.duration; - if (ModState.StoredCmdParams.ContainsKey(quid)) - { - if (ModState.StoredCmdParams[quid].PlayerControl && - ModState.StoredCmdParams[quid].PlayerControlOverridden) return true; - if (!ModState.StoredCmdParams[quid].PlayerControl && - ModState.StoredCmdParams[quid].PlayerControlOverridden) return false; - } + SpawnObjectSequence spawnObjectSequence = new SpawnObjectSequence(creator.Combat, list); + creator.Combat.MessageCenter.PublishMessage( + new AddSequenceToStackMessage(spawnObjectSequence)); + List spawnedObjects = spawnObjectSequence.spawnedObjects; + CleanupObjectSequence cleanupSequence = + new CleanupObjectSequence(creator.Combat, spawnedObjects); + TurnEvent tEvent = new TurnEvent(Guid.NewGuid().ToString(), creator.Combat, duration, null, + cleanupSequence, ability.Def, false); + creator.Combat.TurnDirector.AddTurnEvent(tEvent); + } - if (ModInit.modSettings.PlayerControlSpawnAbilities.Contains(ability.Def.Id)) return true; - if (ModInit.modSettings.PlayerControlSpawnAbilitiesBlacklist.Contains(ability.Def.Id)) return false; + var hitInfo = default(WeaponHitInfo); + hitInfo.numberOfShots = 1; + hitInfo.hitLocations = new int[1]; + hitInfo.hitLocations[0] = 8; + hitInfo.hitPositions = new Vector3[1]; - return sim.CompanyStats.GetValue("StratOps_ControlSpawns"); + var attackPos = creator.GameRep.transform.position + Vector3.up * 100f; + var impactPos = creator.getImpactPositionSimple(attackPos, 8) + Vector3.up * 10f; + + hitInfo.hitPositions[0] = impactPos; + hitInfo.attackerId = creator.GUID; + hitInfo.targetId = creator.GUID; + creator.Combat.EffectManager.CreateEffect(ability.Def.EffectData[i], ability.Def.EffectData[i].Description.Id, 0, creator, creator, hitInfo, 0, false); + } + else + { + //creator.Combat.EffectManager.CreateEffect(ability.Def.EffectData[i], ability.Def.EffectData[i].Description.Id, 0, creator, creator, default(WeaponHitInfo), 0, false); + creator.CreateEffect(ability.Def.EffectData[i], ability, + ability.Def.EffectData[i].Description.Id, + -1, creator); + } + } + } } public static bool CheckOrInitSpawnControl(this SimGameState sim) @@ -642,122 +644,128 @@ public static bool CheckOrInitSpawnControl(this SimGameState sim) } return sim.CompanyStats.GetValue("StratOps_ControlSpawns"); } - public static string Generate2PtCMDQuasiGUID(this Ability ability, string actorGUID, Vector3 positionA, Vector3 positionB) - { - return $"AI_CMD_ID_{actorGUID}_{ability.Def.Id}_{positionA}_{positionB}"; - } - public static void ForceUnitToLastActualPhase(this AbstractActor actor) + public static void CooldownAllCMDAbilities() { - if (actor.Combat.TurnDirector.IsInterleaved && actor.Initiative != actor.Combat.TurnDirector.LastPhase) + for (int i = 0; i < ModState.CommandAbilities.Count; i++) { - actor.Initiative = actor.Combat.TurnDirector.LastPhase; - actor.Combat.MessageCenter.PublishMessage(new ActorPhaseInfoChanged(actor.GUID)); + ModState.CommandAbilities[i].ActivateMiniCooldown(); } - actor.ProcessHesitationSBI(); } - public static void ProcessHesitationSBI(this AbstractActor actor) + public static Lance CreateOrFetchCMDLance(Team team) { - if (actor.StatCollection.ContainsStatistic("SBI_STATE_HESITATION")) + if (!team.lances.Any(x => x.GUID.EndsWith($"{team.GUID}_StratOps"))) { - var currentHesitation = actor.StatCollection.GetValue("SBI_STATE_HESITATION"); - var actorHesitationPhaseMod = actor.StatCollection.GetValue("SBI_MOD_HESITATION") * -1; // invert from SBI - var phasesMoved = Math.Abs(actor.Combat.TurnDirector.CurrentPhase - actor.Combat.TurnDirector.LastPhase); - var hesitationPenalty = (ModInit.modSettings.SBI_HesitationMultiplier * phasesMoved) + actorHesitationPhaseMod; - var roundedPenalty = Mathf.RoundToInt(hesitationPenalty); - var finalHesitation = currentHesitation + roundedPenalty; - actor.StatCollection.ModifyStat(actor.GUID, -1, "SBI_STATE_HESITATION", StatCollection.StatOperation.Set, finalHesitation); - ModInit.modLog?.Info?.Write( - $"[ProcessHesitationSBI] Used quick-reserve with SBI. Final hesitation set to {finalHesitation} from: Multiplier setting {ModInit.modSettings.SBI_HesitationMultiplier} x PhasesMoved {phasesMoved} + Actor Hesitation Mod {actorHesitationPhaseMod} + Current hesitation {currentHesitation} (rounded)"); + Lance lance = new Lance(team, Array.Empty()); + var lanceGuid = $"{LanceSpawnerGameLogic.GetLanceGuid(Guid.NewGuid().ToString())}_{team.GUID}_StratOps"; + lance.lanceGuid = lanceGuid; + var combat = UnityGameInstance.BattleTechGame.Combat; + combat.ItemRegistry.AddItem(lance); + team.lances.Add(lance); + ModInit.modLog?.Info?.Write($"Created lance {lance.DisplayName} for Team {team.DisplayName}."); + return lance; } + return team.lances.FirstOrDefault(x => x.GUID.EndsWith($"{team.GUID}_StratOps")); } - public static AbstractActor FindMeAnOpforUnit(this AbstractActor actor) - { - var opforTeam = actor.Combat.Teams.FirstOrDefault(x => x.GUID == "be77cadd-e245-4240-a93e-b99cc98902a5"); - if (opforTeam != null) - { - var opforUnit = opforTeam.units.FirstOrDefault(x => !x.IsDead); - if (opforUnit != null) - { - return opforUnit; - } - } - return null; - } - public static float GetAvoidStrafeChanceForTeam(this ICombatant combatant) + public static Team CreateOrUpdateAISupportTeam(Team team) { - var actors = combatant.Combat.GetAllLivingActors(); - var cumAA = 0f; - var unitDivisor = 0; - foreach (var unit in actors) + AITeam aiteam = null; + var combat = UnityGameInstance.BattleTechGame.Combat; + aiteam = new AITeam("Opfor Support", Color.black, Guid.NewGuid().ToString(), true, combat); + aiteam.FactionValue = team.FactionValue; + combat.TurnDirector.AddTurnActor(aiteam); + combat.ItemRegistry.AddItem(aiteam); + team.SetSupportTeam(aiteam); + if (!ModState.ReinitPhaseIcons) { - if (unit.team.IsFriendly(combatant.team)) + var phaseIcons = CameraControl.Instance?.HUD?.PhaseTrack?.PhaseIcons; + if (phaseIcons == null) return aiteam; + foreach (var icon in phaseIcons) { - cumAA += unit.GetAAAFactor(); - unitDivisor++; - ModInit.modLog?.Debug?.Write($"unit {unit.DisplayName} is friendly of {combatant.DisplayName}. Added AA factor {unit.GetAAAFactor()}; total is now {cumAA} from {unitDivisor} units"); + icon.Init(combat); } + ModState.ReinitPhaseIcons = true; } - - if (unitDivisor == 0) return 0f; - var finalAA = cumAA / unitDivisor; - ModInit.modLog?.Debug?.Write($"final AA value for {combatant.DisplayName} and team {combatant.team.DisplayName}: {finalAA}"); - return finalAA; + return aiteam; } - public static float GetAAAFactor(this AbstractActor actor) - { - return actor.StatCollection.GetValue("UseAAAFactor") ? actor.StatCollection.GetValue("AAAFactor") : 0f; - } - public static void PublishInvocationExternal(this MessageCenter messageCenter, MessageCenterMessage invocation) + public static void CreateOrUpdateCustomTeam() { - messageCenter.AddSubscriber(MessageCenterMessageType.InvocationStackSequenceCreated, new ReceiveMessageCenterMessage(HandleInvocationStackSequenceCreatedExternal)); - messageCenter.PublishMessage(invocation); - messageCenter.RemoveSubscriber(MessageCenterMessageType.InvocationStackSequenceCreated, new ReceiveMessageCenterMessage(HandleInvocationStackSequenceCreatedExternal)); - - //if (this.Orders == null && this.SelectionType != SelectionType.DoneWithMech) - //{ - // Debug.LogError("No Orders assigned from invocation!"); - //} + AITeam aiteam = null; + var combat = UnityGameInstance.BattleTechGame.Combat; + aiteam = new AITeam("CustomTeamTest", Color.yellow, Guid.NewGuid().ToString(), true, combat); + combat.TurnDirector.AddTurnActor(aiteam); + combat.ItemRegistry.AddItem(aiteam); } - public static void HandleInvocationStackSequenceCreatedExternal(this MessageCenterMessage message) + public static void CreateOrUpdateNeutralTeam() { - // InvocationStackSequenceCreated invocationStackSequenceCreated = message as InvocationStackSequenceCreated; - //message.Orders = invocationStackSequenceCreated.StackSequence; //we dont need orders for the movement sequence. maybe. + AITeam aiteam = null; + var combat = UnityGameInstance.BattleTechGame.Combat; + if (combat.IsLoadingFromSave) + { + aiteam = (combat.GetLoadedTeamByGUID("61612bb3-abf9-4586-952a-0559fa9dcd75") as AITeam); + } + if (!combat.IsLoadingFromSave || aiteam == null) + { + aiteam = new AITeam("Player 1 Support", Color.yellow, "61612bb3-abf9-4586-952a-0559fa9dcd75", true, combat); + } + combat.TurnDirector.AddTurnActor(aiteam); + combat.ItemRegistry.AddItem(aiteam); } - public static void UpdateRangeIndicator(this CombatTargetingReticle reticle, Vector3 newPosition, bool minRangeShow, bool maxRangeShow) + public static void DeployEvasion(AbstractActor actor) { - if (minRangeShow) + ModInit.modLog?.Info?.Write($"Adding deploy protection to {actor.DisplayName}."); + + if (actor is Turret turret) { - reticle.MinRangeHolder.SetActive(false); - reticle.MinRangeHolder.transform.position = newPosition; - reticle.MinRangeHolder.SetActive(true); + ModInit.modLog?.Info?.Write($"{actor.DisplayName} is a turret, skipping."); + return; } - if (maxRangeShow) + if (ModInit.modSettings.deployProtection > 0) { - reticle.MaxRangeHolder.SetActive(false); - reticle.MaxRangeHolder.transform.position = newPosition; - reticle.MaxRangeHolder.SetActive(true); + ModInit.modLog?.Info?.Write($"Adding {ModInit.modSettings.deployProtection} evasion pips"); + actor.EvasivePipsCurrent = ModInit.modSettings.deployProtection; + //Traverse.Create(actor).Property("EvasivePipsTotal").SetValue(actor.EvasivePipsCurrent); + actor.EvasivePipsTotal = actor.EvasivePipsCurrent; + actor.Combat.MessageCenter.PublishMessage(new EvasiveChangedMessage(actor.GUID, actor.EvasivePipsCurrent)); } } - public static bool IsCustomUnitVehicle(this ICombatant combatant) + public static float DistanceToClosestDetectedEnemy(this AbstractActor actor, Vector3 loc) { - if (!combatant.StatCollection.ContainsStatistic("CUFakeVehicle")) return false; - return combatant.StatCollection.GetValue("CUFakeVehicle"); + var enemy = actor.GetClosestDetectedEnemy(loc); + if (enemy == null) return 9999f; + float magnitude = (enemy.CurrentPosition - loc).magnitude; + return magnitude; } - public static Vector3 GetHexFromVector(this Vector3 point) + public static void DP_AnimationComplete(string encounterObjectGUID) { - var combat = UnityGameInstance.BattleTechGame.Combat; - var hex = combat.HexGrid.GetClosestPointOnGrid(point); - hex.y = combat.MapMetaData.GetLerpedHeightAt(hex, false); - return hex; + EncounterLayerParent.EnqueueLoadAwareMessage(new DropshipAnimationCompleteMessage(LanceSpawnerGameLogic.GetDropshipGuid(encounterObjectGUID))); + } + + public static Team FetchAISupportTeam(Team team) + { + if (team.SupportTeam != null) + { + if (!ModState.ReinitPhaseIcons) + { + var phaseIcons = CameraControl.Instance?.HUD?.PhaseTrack?.PhaseIcons; + if (phaseIcons == null) return team.SupportTeam; + foreach (var icon in phaseIcons) + { + icon.Init(team.combat); + } + ModState.ReinitPhaseIcons = true; + } + return team.SupportTeam; + } + return CreateOrUpdateAISupportTeam(team); } public static object FetchUnitFromDataManager(this DataManager dm, string id) @@ -787,67 +795,38 @@ public static object FetchUnitFromDataManager(this DataManager dm, string id) return null; } - public static float DistanceToClosestDetectedEnemy(this AbstractActor actor, Vector3 loc) + public static AbstractActor FindMeAnOpforUnit(this AbstractActor actor) { - var enemy = actor.GetClosestDetectedEnemy(loc); - if (enemy == null) return 9999f; - float magnitude = (enemy.CurrentPosition - loc).magnitude; - return magnitude; - } - public static AbstractActor GetClosestDetectedEnemy(this AbstractActor actor, Vector3 loc) - { - var enemyUnits = new List(); - enemyUnits.AddRange(actor.team.VisibilityCache.GetAllDetectedEnemies(actor).Where(x=>!x.IsDead && !x.IsFlaggedForDeath)); - var num = -1f; - AbstractActor closestActor = null; - foreach (var enemy in enemyUnits) + var opforTeam = actor.Combat.Teams.FirstOrDefault(x => x.GUID == "be77cadd-e245-4240-a93e-b99cc98902a5"); + if (opforTeam != null) { - var magnitude = (loc - enemy.CurrentPosition).magnitude; - if (num < 0f || magnitude < num) + var opforUnit = opforTeam.units.FirstOrDefault(x => !x.IsDead); + if (opforUnit != null) { - num = magnitude; - closestActor = enemy; + return opforUnit; } } - return closestActor; + return null; } - public static AbstractActor GetClosestDetectedSwarmTarget(this AbstractActor actor, Vector3 loc) + public static void ForceUnitToLastActualPhase(this AbstractActor actor) { - var enemyUnits = new List(); - enemyUnits.AddRange(actor.team.VisibilityCache.GetAllDetectedEnemies(actor).Where(x => !x.IsDead && !x.IsFlaggedForDeath)); - var num = -1f; - AbstractActor closestActor = null; - foreach (var enemy in enemyUnits) + if (actor.Combat.TurnDirector.IsInterleaved && actor.Initiative != actor.Combat.TurnDirector.LastPhase) { - if (!enemy.GetIsUnSwarmable()) - { - var magnitude = (loc - enemy.CurrentPosition).magnitude; - if (num < 0f || magnitude < num) - { - num = magnitude; - closestActor = enemy; - } - } + actor.Initiative = actor.Combat.TurnDirector.LastPhase; + actor.Combat.MessageCenter.PublishMessage(new ActorPhaseInfoChanged(actor.GUID)); } - return closestActor; + actor.ProcessHesitationSBI(); } - public static AbstractActor GetClosestDetectedFriendly(Vector3 loc, AbstractActor actor) + public static string Generate2PtCMDQuasiGUID(this Ability ability, string actorGUID, Vector3 positionA, Vector3 positionB) { - var friendlyUnits = actor.team.VisibilityCache.GetAllFriendlies(actor).Where(x=> !x.IsDead && !x.IsFlaggedForDeath); - var num = -1f; - AbstractActor closestActor = null; - foreach (var friendly in friendlyUnits) - { - var magnitude = (loc - friendly.CurrentPosition).magnitude; - if (num < 0f || magnitude < num) - { - num = magnitude; - closestActor = friendly; - } - } - return closestActor; + return $"AI_CMD_ID_{actorGUID}_{ability.Def.Id}_{positionA}_{positionB}"; + } + + public static float GetAAAFactor(this AbstractActor actor) + { + return actor.StatCollection.GetValue("UseAAAFactor") ? actor.StatCollection.GetValue("AAAFactor") : 0f; } public static List GetAllDetectedEnemies(this SharedVisibilityCache cache, AbstractActor actor) @@ -864,18 +843,18 @@ public static List GetAllDetectedEnemies(this SharedVisibilityCac return detectedEnemies; } - public static List GetVisibleEnemyUnitsEnemiesOnly(this AbstractActor actor) + public static List GetAllEnemies(this Team team) { - var detectedEnemies = actor.VisibilityCache.GetVisibleEnemyUnits(); - for (var index = detectedEnemies.Count - 1; index >= 0; index--) + var enemyActors = new List(); + foreach (var enemy in team.Combat.GetAllLivingActors()) { - var enemy = detectedEnemies[index]; - if (!actor.team.IsEnemy(enemy.team) || enemy.IsDead || enemy.IsFlaggedForDeath) + if (team.IsEnemy(enemy.team) && !enemy.IsDead && !enemy.IsFlaggedForDeath) { - detectedEnemies.Remove(enemy); + ModInit.modLog?.Debug?.Write($"unit {enemy.DisplayName} is enemy of {team.DisplayName}."); + enemyActors.Add(enemy); } } - return detectedEnemies; + return enemyActors; } public static List GetAllEnemiesWithinRange(this AbstractActor actor, float range) @@ -895,6 +874,20 @@ public static List GetAllEnemiesWithinRange(this AbstractActor ac return detectedEnemies; } + public static List GetAllFriendlies (this SharedVisibilityCache cache, AbstractActor actor) + { + var friendlyActors = new List(); + foreach (var friendly in actor.Combat.GetAllLivingActors()) + { + if (actor.team.IsFriendly(friendly.team) && !friendly.IsDead && !friendly.IsFlaggedForDeath) + { + ModInit.modLog?.Debug?.Write($"unit {friendly.DisplayName} is friendly of {actor.DisplayName}."); + friendlyActors.Add(friendly); + } + } + return friendlyActors; + } + public static List GetAllFriendliesWithinRange(this AbstractActor actor, float range) { var detectedFriendlies = new List(); @@ -912,188 +905,89 @@ public static List GetAllFriendliesWithinRange(this AbstractActor return detectedFriendlies; } - public static List GetAllFriendlies (this SharedVisibilityCache cache, AbstractActor actor) + public static float GetAvoidStrafeChanceForTeam(this ICombatant combatant) { - var friendlyActors = new List(); - foreach (var friendly in actor.Combat.GetAllLivingActors()) + var actors = combatant.Combat.GetAllLivingActors(); + var cumAA = 0f; + var unitDivisor = 0; + foreach (var unit in actors) { - if (actor.team.IsFriendly(friendly.team) && !friendly.IsDead && !friendly.IsFlaggedForDeath) + if (unit.team.IsFriendly(combatant.team)) { - ModInit.modLog?.Debug?.Write($"unit {friendly.DisplayName} is friendly of {actor.DisplayName}."); - friendlyActors.Add(friendly); + cumAA += unit.GetAAAFactor(); + unitDivisor++; + ModInit.modLog?.Debug?.Write($"unit {unit.DisplayName} is friendly of {combatant.DisplayName}. Added AA factor {unit.GetAAAFactor()}; total is now {cumAA} from {unitDivisor} units"); } } - return friendlyActors; + + if (unitDivisor == 0) return 0f; + var finalAA = cumAA / unitDivisor; + ModInit.modLog?.Debug?.Write($"final AA value for {combatant.DisplayName} and team {combatant.team.DisplayName}: {finalAA}"); + return finalAA; } - public static List GetAllEnemies(this Team team) + public static AbstractActor GetClosestDetectedEnemy(this AbstractActor actor, Vector3 loc) { - var enemyActors = new List(); - foreach (var enemy in team.Combat.GetAllLivingActors()) + var enemyUnits = new List(); + enemyUnits.AddRange(actor.team.VisibilityCache.GetAllDetectedEnemies(actor).Where(x=>!x.IsDead && !x.IsFlaggedForDeath)); + var num = -1f; + AbstractActor closestActor = null; + foreach (var enemy in enemyUnits) { - if (team.IsEnemy(enemy.team) && !enemy.IsDead && !enemy.IsFlaggedForDeath) + var magnitude = (loc - enemy.CurrentPosition).magnitude; + if (num < 0f || magnitude < num) { - ModInit.modLog?.Debug?.Write($"unit {enemy.DisplayName} is enemy of {team.DisplayName}."); - enemyActors.Add(enemy); + num = magnitude; + closestActor = enemy; } } - return enemyActors; + return closestActor; } - public static Vector3[] MakeCircle(Vector3 start, int numOfPoints, float radius) + public static AbstractActor GetClosestDetectedFriendly(Vector3 loc, AbstractActor actor) { - if (ModInit.modSettings.debugFlares) Utils.SpawnDebugFlare(start, "vfxPrfPrtl_artillerySmokeSignal_loop",3); - var vectors = new List(); - for (int i = 0; i < numOfPoints; i++) + var friendlyUnits = actor.team.VisibilityCache.GetAllFriendlies(actor).Where(x=> !x.IsDead && !x.IsFlaggedForDeath); + var num = -1f; + AbstractActor closestActor = null; + foreach (var friendly in friendlyUnits) { - var radians = 2 * Mathf.PI / numOfPoints * i; - var vertical = Mathf.Sin(radians); - var horizontal = Mathf.Cos(radians); - var spawnDir = new Vector3(horizontal, 0, vertical); - - var newPos = start + spawnDir * radius; - vectors.Add(newPos); - if (ModInit.modSettings.debugFlares) Utils.SpawnDebugFlare(newPos, "vfxPrfPrtl_artillerySmokeSignal_loop", 3); - ModInit.modLog?.Debug?.Write($"Distance from possibleStart to ray endpoint is {Vector3.Distance(start, newPos)}."); + var magnitude = (loc - friendly.CurrentPosition).magnitude; + if (num < 0f || magnitude < num) + { + num = magnitude; + closestActor = friendly; + } } - - return vectors.ToArray(); - } - - public static List MakeRectangle(Vector3 start, Vector3 end, float width) - { - - var rectangles = new List(); - Vector3 line = end - start; - float length = Vector3.Distance(start, end); - ModInit.modLog?.Debug?.Write($"Rectangle length should be {length}."); - Vector3 left = Vector3.Cross(line, Vector3.up).normalized; - Vector3 right = -left; - var startLeft = start + (left * width); - var startRight = start + (right * width); - var rectLeft = new Rect(startLeft.x, startLeft.y, width, length); - var rectRight = new Rect(startRight.x, startRight.y, width, length); - rectangles.Add(rectLeft); - rectangles.Add(rectRight); - return rectangles;//.ToArray(); - } - - public static Vector3 LerpByDistance(Vector3 start, Vector3 end, float x) - { - return x * Vector3.Normalize(end - start) + start; + return closestActor; } - public static HeraldryDef SwapHeraldryColors(HeraldryDef def, DataManager dataManager, Action loadCompleteCallback = null) - { - var secondaryID = def.primaryMechColorID; - var tertiaryID = def.secondaryMechColorID; - var primaryID = def.tertiaryMechColorID; - - ModInit.modLog?.Trace?.Write($"Creating new heraldry for support. {primaryID} was tertiary, now primary. {secondaryID} was primary, now secondary. {tertiaryID} was secondary, now tertiary."); - var newHeraldry = new HeraldryDef(def.Description, def.textureLogoID, primaryID, secondaryID, tertiaryID); - - newHeraldry.DataManager = dataManager; - LoadRequest loadRequest = dataManager.CreateLoadRequest(delegate (LoadRequest request) - { - newHeraldry.Refresh(); - loadCompleteCallback?.Invoke(); - }, false); - loadRequest.AddBlindLoadRequest(BattleTechResourceType.Texture2D, newHeraldry.textureLogoID, new bool?(false)); - loadRequest.AddBlindLoadRequest(BattleTechResourceType.Sprite, newHeraldry.textureLogoID, new bool?(false)); - loadRequest.AddBlindLoadRequest(BattleTechResourceType.ColorSwatch, newHeraldry.primaryMechColorID, new bool?(false)); - loadRequest.AddBlindLoadRequest(BattleTechResourceType.ColorSwatch, newHeraldry.secondaryMechColorID, new bool?(false)); - loadRequest.AddBlindLoadRequest(BattleTechResourceType.ColorSwatch, newHeraldry.tertiaryMechColorID, new bool?(false)); - loadRequest.ProcessRequests(10U); - newHeraldry.Refresh(); - return newHeraldry; - } - public static Lance CreateOrFetchCMDLance(Team team) - { - if (!team.lances.Any(x => x.GUID.EndsWith($"{team.GUID}_StratOps"))) - { - Lance lance = new Lance(team, Array.Empty()); - var lanceGuid = $"{LanceSpawnerGameLogic.GetLanceGuid(Guid.NewGuid().ToString())}_{team.GUID}_StratOps"; - lance.lanceGuid = lanceGuid; - var combat = UnityGameInstance.BattleTechGame.Combat; - combat.ItemRegistry.AddItem(lance); - team.lances.Add(lance); - ModInit.modLog?.Info?.Write($"Created lance {lance.DisplayName} for Team {team.DisplayName}."); - return lance; - } - return team.lances.FirstOrDefault(x => x.GUID.EndsWith($"{team.GUID}_StratOps")); - } - public static void CooldownAllCMDAbilities() + public static AbstractActor GetClosestDetectedSwarmTarget(this AbstractActor actor, Vector3 loc) { - for (int i = 0; i < ModState.CommandAbilities.Count; i++) + var enemyUnits = new List(); + enemyUnits.AddRange(actor.team.VisibilityCache.GetAllDetectedEnemies(actor).Where(x => !x.IsDead && !x.IsFlaggedForDeath)); + var num = -1f; + AbstractActor closestActor = null; + foreach (var enemy in enemyUnits) { - ModState.CommandAbilities[i].ActivateMiniCooldown(); + if (!enemy.GetIsUnSwarmable()) + { + var magnitude = (loc - enemy.CurrentPosition).magnitude; + if (num < 0f || magnitude < num) + { + num = magnitude; + closestActor = enemy; + } + } } + return closestActor; } - public static void CreateOrUpdateNeutralTeam() + public static Vector3 GetHexFromVector(this Vector3 point) { - AITeam aiteam = null; var combat = UnityGameInstance.BattleTechGame.Combat; - if (combat.IsLoadingFromSave) - { - aiteam = (combat.GetLoadedTeamByGUID("61612bb3-abf9-4586-952a-0559fa9dcd75") as AITeam); - } - if (!combat.IsLoadingFromSave || aiteam == null) - { - aiteam = new AITeam("Player 1 Support", Color.yellow, "61612bb3-abf9-4586-952a-0559fa9dcd75", true, combat); - } - combat.TurnDirector.AddTurnActor(aiteam); - combat.ItemRegistry.AddItem(aiteam); - } - - public static void CreateOrUpdateCustomTeam() - { - AITeam aiteam = null; - var combat = UnityGameInstance.BattleTechGame.Combat; - aiteam = new AITeam("CustomTeamTest", Color.yellow, Guid.NewGuid().ToString(), true, combat); - combat.TurnDirector.AddTurnActor(aiteam); - combat.ItemRegistry.AddItem(aiteam); - } - - public static Team CreateOrUpdateAISupportTeam(Team team) - { - AITeam aiteam = null; - var combat = UnityGameInstance.BattleTechGame.Combat; - aiteam = new AITeam("Opfor Support", Color.black, Guid.NewGuid().ToString(), true, combat); - aiteam.FactionValue = team.FactionValue; - combat.TurnDirector.AddTurnActor(aiteam); - combat.ItemRegistry.AddItem(aiteam); - team.SetSupportTeam(aiteam); - if (!ModState.ReinitPhaseIcons) - { - var phaseIcons = CameraControl.Instance?.HUD?.PhaseTrack?.PhaseIcons; - if (phaseIcons == null) return aiteam; - foreach (var icon in phaseIcons) - { - icon.Init(combat); - } - ModState.ReinitPhaseIcons = true; - } - return aiteam; - } - - public static Team FetchAISupportTeam(Team team) - { - if (team.SupportTeam != null) - { - if (!ModState.ReinitPhaseIcons) - { - var phaseIcons = CameraControl.Instance?.HUD?.PhaseTrack?.PhaseIcons; - if (phaseIcons == null) return team.SupportTeam; - foreach (var icon in phaseIcons) - { - icon.Init(team.combat); - } - ModState.ReinitPhaseIcons = true; - } - return team.SupportTeam; - } - return CreateOrUpdateAISupportTeam(team); + var hex = combat.HexGrid.GetClosestPointOnGrid(point); + hex.y = combat.MapMetaData.GetLerpedHeightAt(hex, false); + return hex; } public static List GetOwnedDeploymentBeacons() @@ -1220,167 +1114,26 @@ public static List GetOwnedDeploymentBeaconsOfByTypeAndTag(str return beacons; } - public static void DeployEvasion(AbstractActor actor) - { - ModInit.modLog?.Info?.Write($"Adding deploy protection to {actor.DisplayName}."); - - if (actor is Turret turret) - { - ModInit.modLog?.Info?.Write($"{actor.DisplayName} is a turret, skipping."); - return; - } - - if (ModInit.modSettings.deployProtection > 0) - { - ModInit.modLog?.Info?.Write($"Adding {ModInit.modSettings.deployProtection} evasion pips"); - actor.EvasivePipsCurrent = ModInit.modSettings.deployProtection; - //Traverse.Create(actor).Property("EvasivePipsTotal").SetValue(actor.EvasivePipsCurrent); - actor.EvasivePipsTotal = actor.EvasivePipsCurrent; - actor.Combat.MessageCenter.PublishMessage(new EvasiveChangedMessage(actor.GUID, actor.EvasivePipsCurrent)); - } - } - - public static void MountedEvasion(this AbstractActor actor, AbstractActor carrier) - { - ModInit.modLog?.Info?.Write($"Adding BA mounted protection to {actor.DisplayName}."); - - if (actor is Turret turret) - { - ModInit.modLog?.Info?.Write($"{actor.DisplayName} is a turret, skipping."); - return; - } - - var carrierEvasion = carrier.EvasivePipsCurrent; - ModInit.modLog?.Info?.Write($"Setting evasion to {carrierEvasion} from carrier"); - actor.EvasivePipsCurrent = carrierEvasion; - actor.EvasivePipsTotal = actor.EvasivePipsCurrent; - //Traverse.Create(actor).Property("EvasivePipsTotal").SetValue(actor.EvasivePipsCurrent); - } - - - public static void SpawnDebugFlare(Vector3 position, string prefabName, int numPhases) - { - var combat = UnityGameInstance.BattleTechGame.Combat; - position.y = combat.MapMetaData.GetLerpedHeightAt(position, false); - List list = new List(); - ObjectSpawnData item = new ObjectSpawnData(prefabName, position, Quaternion.identity, true, false); - list.Add(item); - SpawnObjectSequence spawnObjectSequence = new SpawnObjectSequence(combat, list); - combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(spawnObjectSequence)); - List spawnedObjects = spawnObjectSequence.spawnedObjects; - CleanupObjectSequence eventSequence = new CleanupObjectSequence(combat, spawnedObjects); - TurnEvent tEvent = new TurnEvent(Guid.NewGuid().ToString(), combat, numPhases, null, eventSequence, default(AbilityDef), false); - combat.TurnDirector.AddTurnEvent(tEvent); - } - - public static void SpawnFlares(Ability ability, Vector3 positionA, Vector3 positionB, string prefabName, - int numFlares, int numPhases, bool IsLocalPlayer) - { - if (ability.Combat.ActiveContract.ContractTypeValue.IsSkirmish) return; - - if (ability.Def.specialRules == AbilityDef.SpecialRules.SpawnTurret) - { - positionA.y = ability.Combat.MapMetaData.GetLerpedHeightAt(positionA, false); - List listSpawn = new List(); - ObjectSpawnData item = new ObjectSpawnData(prefabName, positionA, Quaternion.identity, true, false); - listSpawn.Add(item); - SpawnObjectSequence spawnObjectSequenceSpawn = new SpawnObjectSequence(ability.Combat, listSpawn); - ability.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(spawnObjectSequenceSpawn)); - List spawnedObjectsSpawn = spawnObjectSequenceSpawn.spawnedObjects; - CleanupObjectSequence eventSequenceSpawn = new CleanupObjectSequence(ability.Combat, spawnedObjectsSpawn); - TurnEvent tEventSpawn = new TurnEvent(Guid.NewGuid().ToString(), ability.Combat, numPhases + 1, null, eventSequenceSpawn, default(AbilityDef), false); - ability.Combat.TurnDirector.AddTurnEvent(tEventSpawn); - return; - } - - Vector3 b = (positionB - positionA) / Math.Max(numFlares - 1, 1); - - Vector3 line = positionB - positionA; - Vector3 left = Vector3.Cross(line, Vector3.up).normalized; - Vector3 right = -left; - - var startLeft = positionA + (left * ability.Def.FloatParam1); - var startRight = positionA + (right * ability.Def.FloatParam1); - - Vector3 vector = positionA; - - vector.y = ability.Combat.MapMetaData.GetLerpedHeightAt(vector, false); - startLeft.y = ability.Combat.MapMetaData.GetLerpedHeightAt(startLeft, false); - startRight.y = ability.Combat.MapMetaData.GetLerpedHeightAt(startRight, false); - List list = new List(); - - //add endcap radii, also for babbies - var start = vector - b; - start.y = ability.Combat.MapMetaData.GetLerpedHeightAt(vector, false); - ObjectSpawnData outsideStartFlare = new ObjectSpawnData(prefabName, start, Quaternion.identity, true, false); - list.Add(outsideStartFlare); - - for (int i = 0; i < numFlares + 1; i++) - { - ObjectSpawnData item = new ObjectSpawnData(prefabName, vector, Quaternion.identity, true, false); - list.Add(item); - vector += b; - vector.y = ability.Combat.MapMetaData.GetLerpedHeightAt(vector, false); - } - - for (int i = 0; i < numFlares; i++) - { - ObjectSpawnData item = new ObjectSpawnData(prefabName, startLeft, Quaternion.identity, true, false); - list.Add(item); - startLeft += b; - startLeft.y = ability.Combat.MapMetaData.GetLerpedHeightAt(startLeft, false); - } - - for (int i = 0; i < numFlares; i++) - { - ObjectSpawnData item = - new ObjectSpawnData(prefabName, startRight, Quaternion.identity, true, false); - list.Add(item); - startRight += b; - startRight.y = ability.Combat.MapMetaData.GetLerpedHeightAt(startRight, false); - } - - SpawnObjectSequence spawnObjectSequence = new SpawnObjectSequence(ability.Combat, list); - ability.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(spawnObjectSequence)); - List spawnedObjects = spawnObjectSequence.spawnedObjects; - CleanupObjectSequence eventSequence = new CleanupObjectSequence(ability.Combat, spawnedObjects); - // if (!IsLocalPlayer) numPhases += 1; - TurnEvent tEvent = new TurnEvent(Guid.NewGuid().ToString(), ability.Combat, numPhases + 1, null, - eventSequence, ability.Def, false); - ability.Combat.TurnDirector.AddTurnEvent(tEvent); - return; - } - - public static void NotifyStrafeSequenceComplete(string parentID, string currentID) + public static List GetVisibleEnemyUnitsEnemiesOnly(this AbstractActor actor) { - if (ModState.PendingStrafeWaves.ContainsKey(parentID)) + var detectedEnemies = actor.VisibilityCache.GetVisibleEnemyUnits(); + for (var index = detectedEnemies.Count - 1; index >= 0; index--) { - ModInit.modLog?.Info?.Write($"Strafe Sequence with parent {parentID} and ID {currentID} complete. Remaining waves: {ModState.PendingStrafeWaves[parentID].RemainingWaves}"); - if (ModState.PendingStrafeWaves[parentID].RemainingWaves > 0) - { - ModState.PendingStrafeWaves[parentID].RemainingWaves--; - InitiateStrafe(parentID, ModState.PendingStrafeWaves[parentID]); - } - else + var enemy = detectedEnemies[index]; + if (!actor.team.IsEnemy(enemy.team) || enemy.IsDead || enemy.IsFlaggedForDeath) { - ModInit.modLog?.Info?.Write($"Strafe Sequence with parent {parentID} and ID {currentID} complete. No remaining waves, removing from state."); - ModState.PendingStrafeWaves.Remove(parentID); + detectedEnemies.Remove(enemy); } } + return detectedEnemies; } - public static void DP_AnimationComplete(string encounterObjectGUID) + public static void HandleInvocationStackSequenceCreatedExternal(this MessageCenterMessage message) { - EncounterLayerParent.EnqueueLoadAwareMessage(new DropshipAnimationCompleteMessage(LanceSpawnerGameLogic.GetDropshipGuid(encounterObjectGUID))); + // InvocationStackSequenceCreated invocationStackSequenceCreated = message as InvocationStackSequenceCreated; + //message.Orders = invocationStackSequenceCreated.StackSequence; //we dont need orders for the movement sequence. maybe. } - public static bool IsAOEStrafe(this MechComponentRef component, bool IsStrafe) - { - if (!IsStrafe) return false; - if (component.Def.ComponentTags.Any(x => x == "IsAOEStrafe")) return true; - return false; - } - public static void InitiateStrafe(string parentSequenceID, PendingStrafeWave wave) { @@ -1597,92 +1350,345 @@ public static void InitiateStrafe(string parentSequenceID, PendingStrafeWave wav } } } - public static void PerformAttackStrafe(this TerrainAttackDeligate del, TB_StrafeSequence strafeSequence) + + public static bool IsAOEStrafe(this MechComponentRef component, bool IsStrafe) { - MechRepresentation gameRep = del.actor.GameRep as MechRepresentation; - bool flag = gameRep != null; - if (flag) - { - Log.LogWrite("ToggleRandomIdles false\n", false); - gameRep.ToggleRandomIdles(false); - } - - //del.HUD.SelectionHandler.DeselectActor(del.HUD.SelectionHandler.SelectedActor); - //del.HUD.MechWarriorTray.HideAllChevrons(); - CombatSelectionHandler_TrySelectActor.SelectionForbidden = true; - int seqId = del.actor.Combat.StackManager.NextStackUID; - bool flag2 = del.actor.GUID == del.target.GUID; - if (flag2) + if (!IsStrafe) return false; + if (component.Def.ComponentTags.Any(x => x == "IsAOEStrafe")) return true; + return false; + } + + + public static bool IsComponentPlayerControllable(this TagSet tagset, out bool forced) + { + if (tagset.Any(x => x == "StratOps_player_control_enable")) { - Log.LogWrite("Registering terrain attack to " + seqId + "\n", false); - del.actor.addTerrainHitPosition(del.targetPosition, del.LOFLevel < LineOfFireLevel.LOFObstructed); + forced = true; + return true; } - else + if (tagset.Any(x => x == "StratOps_player_control_disable")) { - Log.LogWrite("Registering friendly attack to " + seqId + "\n", false); + forced = true; + return false; } - AttackDirector.AttackSequence attackSequence = del.actor.Combat.AttackDirector.CreateAttackSequence(seqId, del.actor, del.target, del.actor.CurrentPosition, del.actor.CurrentRotation, 0, del.weaponsList, MeleeAttackType.NotSet, 0, false); - strafeSequence.attackSequences.Add(attackSequence.id); - attackSequence.indirectFire = (del.LOFLevel < LineOfFireLevel.LOFObstructed); - del.actor.Combat.AttackDirector.PerformAttack(attackSequence); - attackSequence.ResetWeapons(); + + forced = false; + return true; } - public static void ApplyCreatorEffects(this Ability ability, AbstractActor creator) + public static bool IsCustomUnitVehicle(this ICombatant combatant) { - for (int i = 0; i < ability.Def.EffectData.Count; i++) - { - if (ability.Def.EffectData[i].targetingData.effectTriggerType == EffectTriggerType.OnActivation && ability.Def.EffectData[i].targetingData.effectTargetType == EffectTargetType.Creator) - { - if (ability.Def.EffectData[i].effectType == EffectType.VFXEffect) - { - if (false) - { - List list = new List(); - var pos = creator.CurrentPosition + Vector3.up; - ObjectSpawnData item = new ObjectSpawnData(ability.Def.EffectData[i].vfxData.vfxName, pos, - Quaternion.identity, true, false); - list.Add(item); + if (!combatant.StatCollection.ContainsStatistic("CUFakeVehicle")) return false; + return combatant.StatCollection.GetValue("CUFakeVehicle"); + } - var duration = ability.Def.EffectData[i].durationData.duration; + public static Vector3 LerpByDistance(Vector3 start, Vector3 end, float x) + { + return x * Vector3.Normalize(end - start) + start; + } - SpawnObjectSequence spawnObjectSequence = new SpawnObjectSequence(creator.Combat, list); - creator.Combat.MessageCenter.PublishMessage( - new AddSequenceToStackMessage(spawnObjectSequence)); - List spawnedObjects = spawnObjectSequence.spawnedObjects; - CleanupObjectSequence cleanupSequence = - new CleanupObjectSequence(creator.Combat, spawnedObjects); - TurnEvent tEvent = new TurnEvent(Guid.NewGuid().ToString(), creator.Combat, duration, null, - cleanupSequence, ability.Def, false); - creator.Combat.TurnDirector.AddTurnEvent(tEvent); - } + public static Vector3[] MakeCircle(Vector3 start, int numOfPoints, float radius) + { + if (ModInit.modSettings.debugFlares) Utils.SpawnDebugFlare(start, "vfxPrfPrtl_artillerySmokeSignal_loop",3); + var vectors = new List(); + for (int i = 0; i < numOfPoints; i++) + { + var radians = 2 * Mathf.PI / numOfPoints * i; + var vertical = Mathf.Sin(radians); + var horizontal = Mathf.Cos(radians); + var spawnDir = new Vector3(horizontal, 0, vertical); - var hitInfo = default(WeaponHitInfo); - hitInfo.numberOfShots = 1; - hitInfo.hitLocations = new int[1]; - hitInfo.hitLocations[0] = 8; - hitInfo.hitPositions = new Vector3[1]; + var newPos = start + spawnDir * radius; + vectors.Add(newPos); + if (ModInit.modSettings.debugFlares) Utils.SpawnDebugFlare(newPos, "vfxPrfPrtl_artillerySmokeSignal_loop", 3); + ModInit.modLog?.Debug?.Write($"Distance from possibleStart to ray endpoint is {Vector3.Distance(start, newPos)}."); + } - var attackPos = creator.GameRep.transform.position + Vector3.up * 100f; - var impactPos = creator.getImpactPositionSimple(attackPos, 8) + Vector3.up * 10f; + return vectors.ToArray(); + } - hitInfo.hitPositions[0] = impactPos; - hitInfo.attackerId = creator.GUID; - hitInfo.targetId = creator.GUID; - creator.Combat.EffectManager.CreateEffect(ability.Def.EffectData[i], ability.Def.EffectData[i].Description.Id, 0, creator, creator, hitInfo, 0, false); - } - else - { - //creator.Combat.EffectManager.CreateEffect(ability.Def.EffectData[i], ability.Def.EffectData[i].Description.Id, 0, creator, creator, default(WeaponHitInfo), 0, false); - creator.CreateEffect(ability.Def.EffectData[i], ability, - ability.Def.EffectData[i].Description.Id, - -1, creator); - } + public static List MakeRectangle(Vector3 start, Vector3 end, float width) + { + + var rectangles = new List(); + Vector3 line = end - start; + float length = Vector3.Distance(start, end); + ModInit.modLog?.Debug?.Write($"Rectangle length should be {length}."); + Vector3 left = Vector3.Cross(line, Vector3.up).normalized; + Vector3 right = -left; + var startLeft = start + (left * width); + var startRight = start + (right * width); + var rectLeft = new Rect(startLeft.x, startLeft.y, width, length); + var rectRight = new Rect(startRight.x, startRight.y, width, length); + rectangles.Add(rectLeft); + rectangles.Add(rectRight); + return rectangles;//.ToArray(); + } + + public static void MountedEvasion(this AbstractActor actor, AbstractActor carrier) + { + ModInit.modLog?.Info?.Write($"Adding carrier evasion protection to {actor.DisplayName}."); + + if (actor is Turret turret) + { + ModInit.modLog?.Info?.Write($"{actor.DisplayName} is a turret, skipping."); + return; + } + + var carrierEvasion = carrier.EvasivePipsCurrent; + actor.EvasivePipsCurrent = carrierEvasion; + actor.EvasivePipsTotal = actor.EvasivePipsCurrent; + actor.Combat.MessageCenter.PublishMessage(new EvasiveChangedMessage(actor.GUID, actor.EvasivePipsCurrent)); + ModInit.modLog?.Info?.Write($"Setting {actor.DisplayName} evasion to {actor.EvasivePipsCurrent} from carrier {carrierEvasion}"); + //Traverse.Create(actor).Property("EvasivePipsTotal").SetValue(actor.EvasivePipsCurrent); + + } + + public static void NotifyStrafeSequenceComplete(string parentID, string currentID) + { + if (ModState.PendingStrafeWaves.ContainsKey(parentID)) + { + ModInit.modLog?.Info?.Write($"Strafe Sequence with parent {parentID} and ID {currentID} complete. Remaining waves: {ModState.PendingStrafeWaves[parentID].RemainingWaves}"); + if (ModState.PendingStrafeWaves[parentID].RemainingWaves > 0) + { + ModState.PendingStrafeWaves[parentID].RemainingWaves--; + InitiateStrafe(parentID, ModState.PendingStrafeWaves[parentID]); + } + else + { + ModInit.modLog?.Info?.Write($"Strafe Sequence with parent {parentID} and ID {currentID} complete. No remaining waves, removing from state."); + ModState.PendingStrafeWaves.Remove(parentID); } } } - + public static void PerformAttackStrafe(this TerrainAttackDeligate del, TB_StrafeSequence strafeSequence) + { + MechRepresentation gameRep = del.actor.GameRep as MechRepresentation; + bool flag = gameRep != null; + if (flag) + { + Log.LogWrite("ToggleRandomIdles false\n", false); + gameRep.ToggleRandomIdles(false); + } + + //del.HUD.SelectionHandler.DeselectActor(del.HUD.SelectionHandler.SelectedActor); + //del.HUD.MechWarriorTray.HideAllChevrons(); + CombatSelectionHandler_TrySelectActor.SelectionForbidden = true; + int seqId = del.actor.Combat.StackManager.NextStackUID; + bool flag2 = del.actor.GUID == del.target.GUID; + if (flag2) + { + Log.LogWrite("Registering terrain attack to " + seqId + "\n", false); + del.actor.addTerrainHitPosition(del.targetPosition, del.LOFLevel < LineOfFireLevel.LOFObstructed); + } + else + { + Log.LogWrite("Registering friendly attack to " + seqId + "\n", false); + } + AttackDirector.AttackSequence attackSequence = del.actor.Combat.AttackDirector.CreateAttackSequence(seqId, del.actor, del.target, del.actor.CurrentPosition, del.actor.CurrentRotation, 0, del.weaponsList, MeleeAttackType.NotSet, 0, false); + strafeSequence.attackSequences.Add(attackSequence.id); + attackSequence.indirectFire = (del.LOFLevel < LineOfFireLevel.LOFObstructed); + del.actor.Combat.AttackDirector.PerformAttack(attackSequence); + attackSequence.ResetWeapons(); + } + + public static void ProcessHesitationSBI(this AbstractActor actor) + { + if (actor.StatCollection.ContainsStatistic("SBI_STATE_HESITATION")) + { + var currentHesitation = actor.StatCollection.GetValue("SBI_STATE_HESITATION"); + var actorHesitationPhaseMod = actor.StatCollection.GetValue("SBI_MOD_HESITATION") * -1; // invert from SBI + var phasesMoved = Math.Abs(actor.Combat.TurnDirector.CurrentPhase - actor.Combat.TurnDirector.LastPhase); + var hesitationPenalty = (ModInit.modSettings.SBI_HesitationMultiplier * phasesMoved) + actorHesitationPhaseMod; + var roundedPenalty = Mathf.RoundToInt(hesitationPenalty); + var finalHesitation = currentHesitation + roundedPenalty; + actor.StatCollection.ModifyStat(actor.GUID, -1, "SBI_STATE_HESITATION", StatCollection.StatOperation.Set, finalHesitation); + ModInit.modLog?.Info?.Write( + $"[ProcessHesitationSBI] Used quick-reserve with SBI. Final hesitation set to {finalHesitation} from: Multiplier setting {ModInit.modSettings.SBI_HesitationMultiplier} x PhasesMoved {phasesMoved} + Actor Hesitation Mod {actorHesitationPhaseMod} + Current hesitation {currentHesitation} (rounded)"); + } + } + + public static void PublishInvocationExternal(this MessageCenter messageCenter, MessageCenterMessage invocation) + { + messageCenter.AddSubscriber(MessageCenterMessageType.InvocationStackSequenceCreated, new ReceiveMessageCenterMessage(HandleInvocationStackSequenceCreatedExternal)); + messageCenter.PublishMessage(invocation); + messageCenter.RemoveSubscriber(MessageCenterMessageType.InvocationStackSequenceCreated, new ReceiveMessageCenterMessage(HandleInvocationStackSequenceCreatedExternal)); + + //if (this.Orders == null && this.SelectionType != SelectionType.DoneWithMech) + //{ + // Debug.LogError("No Orders assigned from invocation!"); + //} + } + + public static bool ShouldPlayerControlSpawn(Team team, Ability ability, string quid) + { + var sim = UnityGameInstance.BattleTechGame.Simulation; + if (sim == null) return false; + if (!team.IsLocalPlayer) return false; + if (ModInit.modSettings.PlayerControlSpawns) return true; + + if (ModState.StoredCmdParams.ContainsKey(quid)) + { + if (ModState.StoredCmdParams[quid].PlayerControl && + ModState.StoredCmdParams[quid].PlayerControlOverridden) return true; + if (!ModState.StoredCmdParams[quid].PlayerControl && + ModState.StoredCmdParams[quid].PlayerControlOverridden) return false; + } + + if (ModInit.modSettings.PlayerControlSpawnAbilities.Contains(ability.Def.Id)) return true; + if (ModInit.modSettings.PlayerControlSpawnAbilitiesBlacklist.Contains(ability.Def.Id)) return false; + + return sim.CompanyStats.GetValue("StratOps_ControlSpawns"); + } + + + public static void SpawnDebugFlare(Vector3 position, string prefabName, int numPhases) + { + var combat = UnityGameInstance.BattleTechGame.Combat; + position.y = combat.MapMetaData.GetLerpedHeightAt(position, false); + List list = new List(); + ObjectSpawnData item = new ObjectSpawnData(prefabName, position, Quaternion.identity, true, false); + list.Add(item); + SpawnObjectSequence spawnObjectSequence = new SpawnObjectSequence(combat, list); + combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(spawnObjectSequence)); + List spawnedObjects = spawnObjectSequence.spawnedObjects; + CleanupObjectSequence eventSequence = new CleanupObjectSequence(combat, spawnedObjects); + TurnEvent tEvent = new TurnEvent(Guid.NewGuid().ToString(), combat, numPhases, null, eventSequence, default(AbilityDef), false); + combat.TurnDirector.AddTurnEvent(tEvent); + } + + public static void SpawnFlares(Ability ability, Vector3 positionA, Vector3 positionB, string prefabName, + int numFlares, int numPhases, bool IsLocalPlayer) + { + if (ability.Combat.ActiveContract.ContractTypeValue.IsSkirmish) return; + + if (ability.Def.specialRules == AbilityDef.SpecialRules.SpawnTurret) + { + positionA.y = ability.Combat.MapMetaData.GetLerpedHeightAt(positionA, false); + List listSpawn = new List(); + ObjectSpawnData item = new ObjectSpawnData(prefabName, positionA, Quaternion.identity, true, false); + listSpawn.Add(item); + SpawnObjectSequence spawnObjectSequenceSpawn = new SpawnObjectSequence(ability.Combat, listSpawn); + ability.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(spawnObjectSequenceSpawn)); + List spawnedObjectsSpawn = spawnObjectSequenceSpawn.spawnedObjects; + CleanupObjectSequence eventSequenceSpawn = new CleanupObjectSequence(ability.Combat, spawnedObjectsSpawn); + TurnEvent tEventSpawn = new TurnEvent(Guid.NewGuid().ToString(), ability.Combat, numPhases + 1, null, eventSequenceSpawn, default(AbilityDef), false); + ability.Combat.TurnDirector.AddTurnEvent(tEventSpawn); + return; + } + + Vector3 b = (positionB - positionA) / Math.Max(numFlares - 1, 1); + + Vector3 line = positionB - positionA; + Vector3 left = Vector3.Cross(line, Vector3.up).normalized; + Vector3 right = -left; + + var startLeft = positionA + (left * ability.Def.FloatParam1); + var startRight = positionA + (right * ability.Def.FloatParam1); + + Vector3 vector = positionA; + + vector.y = ability.Combat.MapMetaData.GetLerpedHeightAt(vector, false); + startLeft.y = ability.Combat.MapMetaData.GetLerpedHeightAt(startLeft, false); + startRight.y = ability.Combat.MapMetaData.GetLerpedHeightAt(startRight, false); + List list = new List(); + + //add endcap radii, also for babbies + var start = vector - b; + start.y = ability.Combat.MapMetaData.GetLerpedHeightAt(vector, false); + ObjectSpawnData outsideStartFlare = new ObjectSpawnData(prefabName, start, Quaternion.identity, true, false); + list.Add(outsideStartFlare); + + for (int i = 0; i < numFlares + 1; i++) + { + ObjectSpawnData item = new ObjectSpawnData(prefabName, vector, Quaternion.identity, true, false); + list.Add(item); + vector += b; + vector.y = ability.Combat.MapMetaData.GetLerpedHeightAt(vector, false); + } + + for (int i = 0; i < numFlares; i++) + { + ObjectSpawnData item = new ObjectSpawnData(prefabName, startLeft, Quaternion.identity, true, false); + list.Add(item); + startLeft += b; + startLeft.y = ability.Combat.MapMetaData.GetLerpedHeightAt(startLeft, false); + } + + for (int i = 0; i < numFlares; i++) + { + ObjectSpawnData item = + new ObjectSpawnData(prefabName, startRight, Quaternion.identity, true, false); + list.Add(item); + startRight += b; + startRight.y = ability.Combat.MapMetaData.GetLerpedHeightAt(startRight, false); + } + + SpawnObjectSequence spawnObjectSequence = new SpawnObjectSequence(ability.Combat, list); + ability.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(spawnObjectSequence)); + List spawnedObjects = spawnObjectSequence.spawnedObjects; + CleanupObjectSequence eventSequence = new CleanupObjectSequence(ability.Combat, spawnedObjects); + // if (!IsLocalPlayer) numPhases += 1; + TurnEvent tEvent = new TurnEvent(Guid.NewGuid().ToString(), ability.Combat, numPhases + 1, null, + eventSequence, ability.Def, false); + ability.Combat.TurnDirector.AddTurnEvent(tEvent); + return; + } + + public static HeraldryDef SwapHeraldryColors(HeraldryDef def, DataManager dataManager, Action loadCompleteCallback = null) + { + var secondaryID = def.primaryMechColorID; + var tertiaryID = def.secondaryMechColorID; + var primaryID = def.tertiaryMechColorID; + + ModInit.modLog?.Trace?.Write($"Creating new heraldry for support. {primaryID} was tertiary, now primary. {secondaryID} was primary, now secondary. {tertiaryID} was secondary, now tertiary."); + var newHeraldry = new HeraldryDef(def.Description, def.textureLogoID, primaryID, secondaryID, tertiaryID); + + newHeraldry.DataManager = dataManager; + LoadRequest loadRequest = dataManager.CreateLoadRequest(delegate (LoadRequest request) + { + newHeraldry.Refresh(); + loadCompleteCallback?.Invoke(); + }, false); + loadRequest.AddBlindLoadRequest(BattleTechResourceType.Texture2D, newHeraldry.textureLogoID, new bool?(false)); + loadRequest.AddBlindLoadRequest(BattleTechResourceType.Sprite, newHeraldry.textureLogoID, new bool?(false)); + loadRequest.AddBlindLoadRequest(BattleTechResourceType.ColorSwatch, newHeraldry.primaryMechColorID, new bool?(false)); + loadRequest.AddBlindLoadRequest(BattleTechResourceType.ColorSwatch, newHeraldry.secondaryMechColorID, new bool?(false)); + loadRequest.AddBlindLoadRequest(BattleTechResourceType.ColorSwatch, newHeraldry.tertiaryMechColorID, new bool?(false)); + loadRequest.ProcessRequests(10U); + newHeraldry.Refresh(); + return newHeraldry; + } + + public static void TeleportActorVisual(this AbstractActor actor, Vector3 newPosition) + { + actor.CurrentPosition = newPosition; + actor.GameRep.transform.position = newPosition; + actor.OnPositionUpdate(newPosition, actor.CurrentRotation, -1, true, null, false); + actor.previousPosition = newPosition; + //actor.ResetPathing(false); + //actor.RebuildPathingForNoMovement(); + actor.IsTeleportedOffScreen = false; + } + + public static void UpdateRangeIndicator(this CombatTargetingReticle reticle, Vector3 newPosition, bool minRangeShow, bool maxRangeShow) + { + if (minRangeShow) + { + reticle.MinRangeHolder.SetActive(false); + reticle.MinRangeHolder.transform.position = newPosition; + reticle.MinRangeHolder.SetActive(true); + } + + if (maxRangeShow) + { + reticle.MaxRangeHolder.SetActive(false); + reticle.MaxRangeHolder.transform.position = newPosition; + reticle.MaxRangeHolder.SetActive(true); + } + } + //public static MethodInfo _activateSpawnTurretMethod = AccessTools.Method(typeof(Ability), "ActivateSpawnTurret"); //public static MethodInfo _activateStrafeMethod = AccessTools.Method(typeof(Ability), "ActivateStrafe"); diff --git a/StrategicOperations/StrategicOperations/ModInit.cs b/StrategicOperations/StrategicOperations/ModInit.cs index a483187..969b36c 100644 --- a/StrategicOperations/StrategicOperations/ModInit.cs +++ b/StrategicOperations/StrategicOperations/ModInit.cs @@ -14,12 +14,32 @@ namespace StrategicOperations { public static class ModInit { - internal static DeferringLogger modLog; + public const string HarmonyPackage = "us.tbone.StrategicOperations"; private static string modDir; - public static readonly Random Random = new Random(); + internal static DeferringLogger modLog; internal static Settings modSettings; - public const string HarmonyPackage = "us.tbone.StrategicOperations"; + public static readonly Random Random = new Random(); + + public static void FinishedLoading(List loadOrder) + { + ModInit.modLog?.Info?.Write($"Invoking FinishedLoading"); + var customPositionFactors = new List() + { + new StrategicInfluenceMapFactors.CustomPositionFactors.PreferAvoidStandingInAirstrikeAreaPosition(), + new StrategicInfluenceMapFactors.CustomPositionFactors.PreferCloserToResupply(), + new StrategicInfluenceMapFactors.CustomPositionFactors.PreferNearerToSwarmTargets() + }; + CustomFactors.Register("StrategicOperations_PositionFactors", customPositionFactors); + var customHostileFactors = new List() + { + new StrategicInfluenceMapFactors.CustomHostileFactors.PreferAvoidStandingInAirstrikeAreaWithHostile(), + new StrategicInfluenceMapFactors.CustomHostileFactors.PreferCloserToResupplyWithHostile(), + new StrategicInfluenceMapFactors.CustomHostileFactors.PreferNearerToSwarmTargetsWithHostile() + }; + CustomFactors.Register("StrategicOperations_HostileFactors", customHostileFactors); + } + public static void Init(string directory, string settings) { @@ -51,130 +71,117 @@ public static void Init(string directory, string settings) //dump settings ModInit.modLog?.Info?.Write($"Settings dump: {settings}"); } - - public static void FinishedLoading(List loadOrder) - { - ModInit.modLog?.Info?.Write($"Invoking FinishedLoading"); - var customPositionFactors = new List() - { - new StrategicInfluenceMapFactors.CustomPositionFactors.PreferAvoidStandingInAirstrikeAreaPosition(), - new StrategicInfluenceMapFactors.CustomPositionFactors.PreferCloserToResupply(), - new StrategicInfluenceMapFactors.CustomPositionFactors.PreferNearerToSwarmTargets() - }; - CustomFactors.Register("StrategicOperations_PositionFactors", customPositionFactors); - var customHostileFactors = new List() - { - new StrategicInfluenceMapFactors.CustomHostileFactors.PreferAvoidStandingInAirstrikeAreaWithHostile(), - new StrategicInfluenceMapFactors.CustomHostileFactors.PreferCloserToResupplyWithHostile(), - new StrategicInfluenceMapFactors.CustomHostileFactors.PreferNearerToSwarmTargetsWithHostile() - }; - CustomFactors.Register("StrategicOperations_HostileFactors", customHostileFactors); - } } class Settings { - public bool AllowIRBTUHandleVisibility = false; - public bool DEVTEST_AIPOS = false; - public bool Debug = false; - public bool debugFlares = false; - public bool enableTrace = true; - public string flareResourceID = "vfxPrfPrtl_fireTerrain_smLoop"; - public string CUVehicleStat = "CUFakeVehicle"; - public bool showStrafeCamera = true; - public bool strafeEndsActivation = true; - public bool spawnTurretEndsActivation = true; - public float strafeTargetsFriendliesChance = 1f; - public float strafeNeutralBuildingsChance = 1f; - public float strafeObjectiveBuildingsChance = 1f; - public int deployProtection = 8; - public float strafeSensorFactor = 4f; - public float strafeVelocityDefault = 150f; - public float strafeAltitudeMin = 75f; - public float strafeAltitudeMax = 250f; - public float strafePreDistanceMult = 6f; - public int strafeWaves = 1; // strafes will spawn this many units and do - // ive strafing runs. - - public float strafeAAFailThreshold = 1f; //for AI strafes, if fail % is higher than this, they wont try - public float timeBetweenAttacks = 0.35f; - public float strafeMinDistanceToEnd = 10f; - public float commandUseCostsMulti = 1f; // deprecate? - public float lostBeaconUnitCostMult = 0f; - - public List deploymentBeaconEquipment = new List(); //e.g. Item.HeatSinkDef.Gear_HeatSink_Generic_Standard - - public List commandAbilities_AI = new List(); - public ColorSetting customSpawnReticleColor = new ColorSetting(); - public string customSpawnReticleAsset = ""; - public string MountIndicatorAsset = ""; - public ColorSetting MountIndicatorColor = new ColorSetting(); - public int AI_InvokeStrafeThreshold = 1; + public List AI_BattleArmorExcludedContractIDs = new List(); + public List AI_BattleArmorExcludedContractTypes = new List(); public int AI_InvokeSpawnThreshold = 1; + public int AI_InvokeStrafeThreshold = 1; public List AI_SpawnBehavior = new List(); // values can be "AMBUSH", "BRAWLER" (is default if none selected), "REINFORCE" - public string BattleArmorMountAndSwarmID = ""; - public List BATargetEffects = new List(); + + public string AirliftAbilityID = ""; + public bool AirliftCapacityByTonnage = false; + public List AirliftImmuneTags = new List(); public List AirliftTargetEffects = new List(); - public List OnGarrisonCollapseEffects = new List(); - public List BattleArmorFactionAssociations = new List(); + public bool AllowIRBTUHandleVisibility = false; + public List ArmActuatorCategoryIDs = new List(); + + public bool AttackOnSwarmSuccess = false; + //BD wants controllable gated on item: companystat and use ROI? + //RT wants specific ability or reinforcement thingy controllable: component tag on the beacon item. per abilitry will suck bc abilities suck. + + public List BAMountPairColors = new List(); + public string BAMountReminderText = "Shift-click unit in drop slot to set carrier"; + public List BATargetEffects = new List(); public string BattleArmorDeSwarmRoll = ""; public string BattleArmorDeSwarmSwat = ""; + public List BattleArmorFactionAssociations = new List(); + public string BattleArmorMountAndSwarmID = ""; + + public Dictionary BeaconExclusionConfig = + new Dictionary(); + //public string BattleArmorDeSwarmMovement = ""; public List BPodComponentIDs = new List(); //statistic for dmg will be BPod_DamageDealt public bool BPodsAutoActivate = true; //BPods always auto activate when swarmed for AI, this only controls for player - public List ArmActuatorCategoryIDs = new List(); + public bool CanDropOffAfterMoving = false; + + public List commandAbilities_AI = new List(); + public float commandUseCostsMulti = 1f; // deprecate? + + public List crewOrCockpitCustomID = new List + { + "CrewCompartment", + "Cockpit" + }; + + public string customSpawnReticleAsset = ""; + public ColorSetting customSpawnReticleColor = new ColorSetting(); + public string CUVehicleStat = "CUFakeVehicle"; + public bool Debug = false; + public bool debugFlares = false; + + public List deploymentBeaconEquipment = new List(); //e.g. Item.HeatSinkDef.Gear_HeatSink_Generic_Standard + public int deployProtection = 8; //public bool UseActorStatsForDeswarmAbilities = false; public Dictionary DeswarmConfigs = new Dictionary(); public ConfigOptions.BA_DeswarmMovementConfig DeswarmMovementConfig = new ConfigOptions.BA_DeswarmMovementConfig(); + public bool DEVTEST_AIPOS = false; - public bool AttackOnSwarmSuccess = false; - public List AI_BattleArmorExcludedContractTypes = new List(); - public List AI_BattleArmorExcludedContractIDs = new List(); + //StratOps_player_control_enable -> component tag for player control always + //StratOps_player_control_disable-> component tag for player control NEVER - public Dictionary BeaconExclusionConfig = - new Dictionary(); - //public List BeaconExcludedContractTypes = new List(); - //public List BeaconExcludedContractIDs = new List(); - public bool UsingMechAffinityForSwarmBreach = false; + public string DisableAISwarmTag = "AI_DISABLE_SWARM"; public bool DisableGarrisons = false; - public float GarrisonBuildingArmorFactor = 1f; - - public string AirliftAbilityID = ""; - public bool CanDropOffAfterMoving = false; - public bool AirliftCapacityByTonnage = false; - public List AirliftImmuneTags = new List(); - - public ConfigOptions.ResupplyConfigOptions ResupplyConfig = new ConfigOptions.ResupplyConfigOptions(); + public bool EnableQuickReserve = false; + public bool enableTrace = true; public bool EnforceIFFForAmmoTooltips = false; - public bool ShowAmmoInVehicleTooltips = false; - public bool EnableQuickReserve = false; - public float SBI_HesitationMultiplier = 0f; public KeyCode EquipmentButtonsHotkey = KeyCode.M; // with shift to activate/cycle through any existing buttons - - public bool PlayerControlSpawns = false; //complete override. players always control all spawns. + public string flareResourceID = "vfxPrfPrtl_fireTerrain_smLoop"; + public float GarrisonBuildingArmorFactor = 1f; + public float lostBeaconUnitCostMult = 0f; + public string MountIndicatorAsset = ""; + public ColorSetting MountIndicatorColor = new ColorSetting(); + public List OnGarrisonCollapseEffects = new List(); public List PlayerControlSpawnAbilities = new List(); //abilities here will always allow player control public List PlayerControlSpawnAbilitiesBlacklist = new List(); // abilities here will never allow player control - //StratOps_player_control_enable -> component tag for player control always - //StratOps_player_control_disable-> component tag for player control NEVER + public bool PlayerControlSpawns = false; //complete override. players always control all spawns. - public string DisableAISwarmTag = "AI_DISABLE_SWARM"; + public ConfigOptions.ResupplyConfigOptions ResupplyConfig = new ConfigOptions.ResupplyConfigOptions(); + public float SBI_HesitationMultiplier = 0f; + public bool ShowAmmoInVehicleTooltips = false; + public bool showStrafeCamera = true; - public List crewOrCockpitCustomID = new List - { - "CrewCompartment", - "Cockpit" - }; - //BD wants controllable gated on item: companystat and use ROI? - //RT wants specific ability or reinforcement thingy controllable: component tag on the beacon item. per abilitry will suck bc abilities suck. + public bool spawnTurretEndsActivation = true; + // ive strafing runs. - public List BAMountPairColors = new List(); - public string BAMountReminderText = "Shift-click unit in drop slot to set carrier"; + public float strafeAAFailThreshold = 1f; //for AI strafes, if fail % is higher than this, they wont try + public float strafeAltitudeMax = 250f; + public float strafeAltitudeMin = 75f; + public bool strafeEndsActivation = true; + public float strafeMinDistanceToEnd = 10f; + public float strafeNeutralBuildingsChance = 1f; + public float strafeObjectiveBuildingsChance = 1f; + public float strafePreDistanceMult = 6f; + public float strafeSensorFactor = 4f; + public float strafeTargetsFriendliesChance = 1f; + public float strafeVelocityDefault = 150f; + public int strafeWaves = 1; // strafes will spawn this many units and do + + public float timeBetweenAttacks = 0.35f; + + //public List BeaconExcludedContractTypes = new List(); + //public List BeaconExcludedContractIDs = new List(); + public bool UsingMechAffinityForSwarmBreach = false; + public bool ReworkedCarrierEvasion = true; } } \ No newline at end of file diff --git a/StrategicOperations/StrategicOperations/Patches/AIM_ButNotReally.cs b/StrategicOperations/StrategicOperations/Patches/AIM_ButNotReally.cs index 14b348c..b4af2a7 100644 --- a/StrategicOperations/StrategicOperations/Patches/AIM_ButNotReally.cs +++ b/StrategicOperations/StrategicOperations/Patches/AIM_ButNotReally.cs @@ -8,58 +8,11 @@ namespace StrategicOperations.Patches { public class AIM_ButNotReally { - [HarmonyPatch(typeof(Weapon), "InitStats")] - public static class Weapon_InitStats - { - public static void Prefix(Weapon __instance) - { - __instance.StatCollection.AddStatistic("APArmorShardsModWeaponMultiplier", 1f); - __instance.StatCollection.AddStatistic("APMaxArmorThicknessWeaponMultiplier", 1f); - __instance.StatCollection.AddStatistic("APCriticalChanceMultiplierWeaponMultiplier", 1f); - } - } - - [HarmonyPatch(typeof(CustomAmmoCategories), "APArmorShardsMod")] - public static class CustomAmmoCategories_APArmorShardsMod_WeaponExt - { - public static void Postfix(Weapon weapon, ref float __result) - { - if (__result > 0f) - { - __result *= weapon.StatCollection.GetValue("APArmorShardsModWeaponMultiplier"); - } - } - } - - [HarmonyPatch(typeof(CustomAmmoCategories), "APMaxArmorThickness")] - public static class CustomAmmoCategories_APMaxArmorThickness_WeaponExt - { - public static void Postfix(Weapon weapon, ref float __result) - { - if (__result > 0f) - { - __result *= weapon.StatCollection.GetValue("APMaxArmorThicknessWeaponMultiplier"); - } - } - } - - [HarmonyPatch(typeof(CustomAmmoCategories), "APCriticalChanceMultiplier")] - public static class CustomAmmoCategories_APCriticalChanceMultiplier_WeaponExt - { - public static void Postfix(Weapon weapon, ref float __result) - { - if (__result > 0f) - { - __result *= weapon.StatCollection.GetValue("APCriticalChanceMultiplierWeaponMultiplier"); - } - } - } - [HarmonyPatch(typeof(CombatHUDFakeVehicleArmorHover), "setToolTipInfo", new Type[] {typeof(Mech), typeof(ChassisLocations)})] public static class CombatHUDFakeVehicleArmorHover_setToolTipInfo { static bool Prepare() => ModInit.modSettings.ShowAmmoInVehicleTooltips; - + public static void Prefix(ref bool __runOriginal, CombatHUDFakeVehicleArmorHover __instance, Mech vehicle, ChassisLocations location) { if (!__runOriginal) return; @@ -130,5 +83,52 @@ public static void Prefix(ref bool __runOriginal, CombatHUDFakeVehicleArmorHover return; } } + + [HarmonyPatch(typeof(CustomAmmoCategories), "APArmorShardsMod")] + public static class CustomAmmoCategories_APArmorShardsMod_WeaponExt + { + public static void Postfix(Weapon weapon, ref float __result) + { + if (__result > 0f) + { + __result *= weapon.StatCollection.GetValue("APArmorShardsModWeaponMultiplier"); + } + } + } + + [HarmonyPatch(typeof(CustomAmmoCategories), "APCriticalChanceMultiplier")] + public static class CustomAmmoCategories_APCriticalChanceMultiplier_WeaponExt + { + public static void Postfix(Weapon weapon, ref float __result) + { + if (__result > 0f) + { + __result *= weapon.StatCollection.GetValue("APCriticalChanceMultiplierWeaponMultiplier"); + } + } + } + + [HarmonyPatch(typeof(CustomAmmoCategories), "APMaxArmorThickness")] + public static class CustomAmmoCategories_APMaxArmorThickness_WeaponExt + { + public static void Postfix(Weapon weapon, ref float __result) + { + if (__result > 0f) + { + __result *= weapon.StatCollection.GetValue("APMaxArmorThicknessWeaponMultiplier"); + } + } + } + + [HarmonyPatch(typeof(Weapon), "InitStats")] + public static class Weapon_InitStats + { + public static void Prefix(Weapon __instance) + { + __instance.StatCollection.AddStatistic("APArmorShardsModWeaponMultiplier", 1f); + __instance.StatCollection.AddStatistic("APMaxArmorThicknessWeaponMultiplier", 1f); + __instance.StatCollection.AddStatistic("APCriticalChanceMultiplierWeaponMultiplier", 1f); + } + } } } diff --git a/StrategicOperations/StrategicOperations/Patches/AI_DEBUG_Patches.cs b/StrategicOperations/StrategicOperations/Patches/AI_DEBUG_Patches.cs index 6d29ebb..877f94b 100644 --- a/StrategicOperations/StrategicOperations/Patches/AI_DEBUG_Patches.cs +++ b/StrategicOperations/StrategicOperations/Patches/AI_DEBUG_Patches.cs @@ -6,35 +6,11 @@ namespace StrategicOperations.Patches { class AI_DEBUG_Patches { - - [HarmonyPatch(typeof(SortMoveCandidatesByInfMapNode), "Tick")] - public static class SortMoveCandidatesByInfMapNode_Tick - { - static bool Prepare() => ModInit.modSettings.Debug; - - public static void Postfix(SortMoveCandidatesByInfMapNode __instance, ref BehaviorTreeResults __result) - { - ModInit.modLog?.Debug?.Write( - $"[SortMoveCandidatesByInfMapNode Tick] Sorting finished. Actor {__instance.unit.DisplayName} eval'd highest weighted position as {__instance.unit.BehaviorTree.influenceMapEvaluator.WorkspaceEvaluationEntries[0].Position} with weight {__instance.unit.BehaviorTree.influenceMapEvaluator.WorkspaceEvaluationEntries[0].GetHighestAccumulator()}"); - } - } - - [HarmonyPatch(typeof(MoveTowardsHighestPriorityMoveCandidateNode), "Tick")] - public static class MoveTowardsHighestPriorityMoveCandidateNode_Tick - { - static bool Prepare() => ModInit.modSettings.Debug; - - public static void Postfix(MoveTowardsHighestPriorityMoveCandidateNode __instance, ref BehaviorTreeResults __result) - { - ModInit.modLog?.Debug?.Write( - $"[MoveTowardsHighestPriorityMoveCandidateNode Tick] Moving towards highest eval'd position: Actor {__instance.unit.DisplayName} eval'd highest weighted position as {__instance.unit.BehaviorTree.influenceMapEvaluator.WorkspaceEvaluationEntries[0].Position} with weight {__instance.unit.BehaviorTree.influenceMapEvaluator.WorkspaceEvaluationEntries[0].GetHighestAccumulator()}"); - } - } - [HarmonyPatch(typeof(Mech), "JumpDistance", MethodType.Getter)] public static class Mech_JumpDistance { static bool Prepare() => false; //turned back off because fuck it, not my problem. + public static void Prefix(Mech __instance, ref float __result) { try @@ -73,5 +49,29 @@ public static void Postfix(Mech __instance, ref float __result) } } } + + [HarmonyPatch(typeof(MoveTowardsHighestPriorityMoveCandidateNode), "Tick")] + public static class MoveTowardsHighestPriorityMoveCandidateNode_Tick + { + static bool Prepare() => ModInit.modSettings.Debug; + + public static void Postfix(MoveTowardsHighestPriorityMoveCandidateNode __instance, ref BehaviorTreeResults __result) + { + ModInit.modLog?.Debug?.Write( + $"[MoveTowardsHighestPriorityMoveCandidateNode Tick] Moving towards highest eval'd position: Actor {__instance.unit.DisplayName} eval'd highest weighted position as {__instance.unit.BehaviorTree.influenceMapEvaluator.WorkspaceEvaluationEntries[0].Position} with weight {__instance.unit.BehaviorTree.influenceMapEvaluator.WorkspaceEvaluationEntries[0].GetHighestAccumulator()}"); + } + } + + [HarmonyPatch(typeof(SortMoveCandidatesByInfMapNode), "Tick")] + public static class SortMoveCandidatesByInfMapNode_Tick + { + static bool Prepare() => ModInit.modSettings.Debug; + + public static void Postfix(SortMoveCandidatesByInfMapNode __instance, ref BehaviorTreeResults __result) + { + ModInit.modLog?.Debug?.Write( + $"[SortMoveCandidatesByInfMapNode Tick] Sorting finished. Actor {__instance.unit.DisplayName} eval'd highest weighted position as {__instance.unit.BehaviorTree.influenceMapEvaluator.WorkspaceEvaluationEntries[0].Position} with weight {__instance.unit.BehaviorTree.influenceMapEvaluator.WorkspaceEvaluationEntries[0].GetHighestAccumulator()}"); + } + } } } diff --git a/StrategicOperations/StrategicOperations/Patches/AI_InfluenceMapFactorPatches.cs b/StrategicOperations/StrategicOperations/Patches/AI_InfluenceMapFactorPatches.cs index 0f63981..0df32de 100644 --- a/StrategicOperations/StrategicOperations/Patches/AI_InfluenceMapFactorPatches.cs +++ b/StrategicOperations/StrategicOperations/Patches/AI_InfluenceMapFactorPatches.cs @@ -46,19 +46,20 @@ public static void Prefix(ref bool __runOriginal, PreferFarthestAwayFromClosestH } } - [HarmonyPatch(typeof(PreferLowerMovementFactor), "EvaluateInfluenceMapFactorAtPosition", - new Type[] { typeof(AbstractActor), typeof(Vector3), typeof(float), typeof(MoveType), typeof(PathNode) })] - public static class PreferLowerMovementFactor_EvaluateInfluenceMapFactorAtPosition_BattleArmor + [HarmonyPatch(typeof(PreferHigherExpectedDamageToHostileFactor), + "EvaluateInfluenceMapFactorAtPositionWithHostile", + new Type[] { typeof(AbstractActor), typeof(Vector3), typeof(float), typeof(MoveType), typeof(ICombatant) })] + public static class PreferHigherExpectedDamageToHostileFactor_EvaluateInfluenceMapFactorAtPosition_BattleArmor { public static void Prefix(ref bool __runOriginal, PreferFarthestAwayFromClosestHostilePositionFactor __instance, AbstractActor unit, - Vector3 position, float angle, MoveType moveType_unused, PathNode pathNode_unused, ref float __result) + Vector3 position, float angle, MoveType moveType, ICombatant hostileUnit, ref float __result) { if (!__runOriginal) return; if (unit.HasMountedUnits() || (unit.CanSwarm() && unit is TrooperSquad)) { var result = 9001 * (1 / unit.DistanceToClosestDetectedEnemy(position)); ModInit.modLog?.Debug?.Write( - $"[PreferLowerMovementFactor] Actor {unit.DisplayName} evaluating position {position}, should return {result}"); + $"[PreferFarthestAwayFromClosestHostilePositionFactor] Actor {unit.DisplayName} evaluating position {position}, should return {result}"); __result = result; __runOriginal = false; return; @@ -81,19 +82,19 @@ public static void Prefix(ref bool __runOriginal, PreferFarthestAwayFromClosestH } } - [HarmonyPatch(typeof(PreferOptimalDistanceToAllyFactor), "EvaluateInfluenceMapFactorAtPositionWithAlly", - new Type[] { typeof(AbstractActor), typeof(Vector3), typeof(float), typeof(ICombatant) })] - public static class PreferOptimalDistanceToAllyFactor_EvaluateInfluenceMapFactorAtPosition_BattleArmor + [HarmonyPatch(typeof(PreferLowerMovementFactor), "EvaluateInfluenceMapFactorAtPosition", + new Type[] { typeof(AbstractActor), typeof(Vector3), typeof(float), typeof(MoveType), typeof(PathNode) })] + public static class PreferLowerMovementFactor_EvaluateInfluenceMapFactorAtPosition_BattleArmor { public static void Prefix(ref bool __runOriginal, PreferFarthestAwayFromClosestHostilePositionFactor __instance, AbstractActor unit, - Vector3 position, float angle, ICombatant allyUnit, ref float __result) + Vector3 position, float angle, MoveType moveType_unused, PathNode pathNode_unused, ref float __result) { if (!__runOriginal) return; if (unit.HasMountedUnits() || (unit.CanSwarm() && unit is TrooperSquad)) { var result = 9001 * (1 / unit.DistanceToClosestDetectedEnemy(position)); ModInit.modLog?.Debug?.Write( - $"[PreferOptimalDistanceToAllyFactor] Actor {unit.DisplayName} evaluating position {position}, should return {result}"); + $"[PreferLowerMovementFactor] Actor {unit.DisplayName} evaluating position {position}, should return {result}"); __result = result; __runOriginal = false; return; @@ -116,12 +117,12 @@ public static void Prefix(ref bool __runOriginal, PreferFarthestAwayFromClosestH } } - [HarmonyPatch(typeof(PreferHigherExpectedDamageToHostileFactor), + [HarmonyPatch(typeof(PreferNoCloserThanMinDistToHostileFactor), "EvaluateInfluenceMapFactorAtPositionWithHostile", new Type[] { typeof(AbstractActor), typeof(Vector3), typeof(float), typeof(MoveType), typeof(ICombatant) })] - public static class PreferHigherExpectedDamageToHostileFactor_EvaluateInfluenceMapFactorAtPosition_BattleArmor + public static class PreferNoCloserThanMinDistToHostileFactor_EvaluateInfluenceMapFactorAtPosition_BattleArmor { - public static void Prefix(ref bool __runOriginal, PreferFarthestAwayFromClosestHostilePositionFactor __instance, AbstractActor unit, + public static void Prefix(ref bool __runOriginal, PreferNoCloserThanMinDistToHostileFactor __instance, AbstractActor unit, Vector3 position, float angle, MoveType moveType, ICombatant hostileUnit, ref float __result) { if (!__runOriginal) return; @@ -129,7 +130,7 @@ public static void Prefix(ref bool __runOriginal, PreferFarthestAwayFromClosestH { var result = 9001 * (1 / unit.DistanceToClosestDetectedEnemy(position)); ModInit.modLog?.Debug?.Write( - $"[PreferFarthestAwayFromClosestHostilePositionFactor] Actor {unit.DisplayName} evaluating position {position}, should return {result}"); + $"[PreferNoCloserThanMinDistToHostileFactor] Actor {unit.DisplayName} evaluating position {position}, should return {result}"); __result = result; __runOriginal = false; return; @@ -152,20 +153,19 @@ public static void Prefix(ref bool __runOriginal, PreferFarthestAwayFromClosestH } } - [HarmonyPatch(typeof(PreferNoCloserThanMinDistToHostileFactor), - "EvaluateInfluenceMapFactorAtPositionWithHostile", - new Type[] { typeof(AbstractActor), typeof(Vector3), typeof(float), typeof(MoveType), typeof(ICombatant) })] - public static class PreferNoCloserThanMinDistToHostileFactor_EvaluateInfluenceMapFactorAtPosition_BattleArmor + [HarmonyPatch(typeof(PreferOptimalDistanceToAllyFactor), "EvaluateInfluenceMapFactorAtPositionWithAlly", + new Type[] { typeof(AbstractActor), typeof(Vector3), typeof(float), typeof(ICombatant) })] + public static class PreferOptimalDistanceToAllyFactor_EvaluateInfluenceMapFactorAtPosition_BattleArmor { - public static void Prefix(ref bool __runOriginal, PreferNoCloserThanMinDistToHostileFactor __instance, AbstractActor unit, - Vector3 position, float angle, MoveType moveType, ICombatant hostileUnit, ref float __result) + public static void Prefix(ref bool __runOriginal, PreferFarthestAwayFromClosestHostilePositionFactor __instance, AbstractActor unit, + Vector3 position, float angle, ICombatant allyUnit, ref float __result) { if (!__runOriginal) return; if (unit.HasMountedUnits() || (unit.CanSwarm() && unit is TrooperSquad)) { var result = 9001 * (1 / unit.DistanceToClosestDetectedEnemy(position)); ModInit.modLog?.Debug?.Write( - $"[PreferNoCloserThanMinDistToHostileFactor] Actor {unit.DisplayName} evaluating position {position}, should return {result}"); + $"[PreferOptimalDistanceToAllyFactor] Actor {unit.DisplayName} evaluating position {position}, should return {result}"); __result = result; __runOriginal = false; return; diff --git a/StrategicOperations/StrategicOperations/Patches/AI_Patches.cs b/StrategicOperations/StrategicOperations/Patches/AI_Patches.cs index 2049a69..f849c45 100644 --- a/StrategicOperations/StrategicOperations/Patches/AI_Patches.cs +++ b/StrategicOperations/StrategicOperations/Patches/AI_Patches.cs @@ -13,12 +13,260 @@ namespace StrategicOperations.Patches { public class AI_Patches { + [HarmonyPatch(typeof(AITeam), "makeInvocationFromOrders")] + public static class AITeam_makeInvocationFromOrders_patch + { + public static void Prefix(ref bool __runOriginal, AITeam __instance, AbstractActor unit, OrderInfo order, + ref InvocationMessage __result) + { + if (!__runOriginal) return; + if (unit.HasSwarmingUnits()){ + + if (unit is FakeVehicleMech && !unit.HasMovedThisRound && order.OrderType == OrderType.Move || order.OrderType == OrderType.JumpMove || order.OrderType == OrderType.SprintMove) + { + var ability = unit.GetDeswarmerAbilityForAI(true); + if (ability.IsAvailable && !ability.IsActive) + { + ability.Activate(unit, unit); + ModInit.modLog?.Info?.Write($"{unit.DisplayName} {unit.GUID} is vehicle being swarmed. Found movement order, activating erratic maneuvers ability."); + __runOriginal = true; + return; + } + } + + if (ModState.AiDealWithBattleArmorCmds.ContainsKey(unit.GUID)) + { + ModState.AiDealWithBattleArmorCmds[unit.GUID].ability.Activate(unit, unit); + // ModState.AiDealWithBattleArmorCmds[unit.GUID].targetActor); + + ModInit.modLog?.Info?.Write( + $"activated {ModState.AiDealWithBattleArmorCmds[unit.GUID].ability.Def.Description.Id} on actor {unit.DisplayName} {unit.GUID}"); + + if (!unit.HasMovedThisRound) + { + unit.BehaviorTree.IncreaseSprintHysteresisLevel(); + } + + __result = new ReserveActorInvocation(unit, ReserveActorAction.DONE, + unit.Combat.TurnDirector.CurrentRound); + ModState.AiDealWithBattleArmorCmds.Remove(unit.GUID); + __runOriginal = false; + return; + } + } + + if (unit.IsMountedUnit() && !ModState.StrategicActorTargetInvocationCmds.ContainsKey(unit.GUID)) + { + if (unit.CanDeferUnit) + { + __result = new ReserveActorInvocation(unit, ReserveActorAction.DEFER, unit.Combat.TurnDirector.CurrentRound); + __runOriginal = false; + return; // changed to defer to get BA to reserve down? + } + __result = new ReserveActorInvocation(unit, ReserveActorAction.DONE, unit.Combat.TurnDirector.CurrentRound); + __runOriginal = false; + return; // changed to defer to get BA to reserve down? + } + + if (unit.IsSwarmingUnit() && !unit.HasFiredThisRound) + { + var target = unit.Combat.FindActorByGUID(ModState.PositionLockSwarm[unit.GUID]); + if (unit.IsFlaggedForDeath || unit.IsDead) + { + unit.HandleDeath(target.GUID); + __runOriginal = false; + return; + } + + ModInit.modLog?.Info?.Write( + $"[AITeam.makeInvocationFromOrders] Actor {unit.DisplayName} has active swarm attack on {target.DisplayName}"); + if (unit.GetAbilityUsedFiring()) + { + foreach (var weapon in unit.Weapons) + { + weapon.DisableWeapon(); + } + } + else + { + foreach (var weapon in unit.Weapons) + { + weapon.EnableWeapon(); + } + } + + var weps = unit.Weapons.Where(x => x.IsEnabled && x.HasAmmo).ToList(); + + var loc = ModState.BADamageTrackers[unit.GUID].BA_MountedLocations.Values.GetRandomElement(); + //var attackStackSequence = new AttackStackSequence(unit, target, unit.CurrentPosition, unit.CurrentRotation, weps, MeleeAttackType.NotSet, loc, -1); + //unit.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(attackStackSequence)); + var vent = unit.HasVentCoolantAbility && unit.CanVentCoolant; + __result = new AttackInvocation(unit, target, weps, MeleeAttackType.NotSet, loc) + { + ventHeatBeforeAttack = vent + }; // making a regular attack invocation here, instead of stacksequence + reserve + + //if (!unit.HasMovedThisRound) + //{ + // unit.BehaviorTree.IncreaseSprintHysteresisLevel(); + //} + //__result = new ReserveActorInvocation(unit, ReserveActorAction.DONE, unit.Combat.TurnDirector.CurrentRound); + __runOriginal = false; + return; + } + + if (ModState.StrategicActorTargetInvocationCmds.ContainsKey(unit.GUID)) + { + if (ModState.StrategicActorTargetInvocationCmds[unit.GUID].active) + { + ModInit.modLog?.Debug?.Write( + $"BA AI Swarm/Mount Ability DUMP: {ModState.StrategicActorTargetInvocationCmds[unit.GUID].active}, {ModState.StrategicActorTargetInvocationCmds[unit.GUID].targetActor.DisplayName}."); + ModInit.modLog?.Debug?.Write( + $"BA AI Swarm/Mount Ability DUMP: {ModState.StrategicActorTargetInvocationCmds[unit.GUID].ability} {ModState.StrategicActorTargetInvocationCmds[unit.GUID].ability.Def.Id}, Combat is not null? {ModState.StrategicActorTargetInvocationCmds[unit.GUID].ability.Combat != null}"); + + if (unit.IsMountedUnit()) + { + var carrier = unit.Combat.FindActorByGUID(ModState.PositionLockMount[unit.GUID]); + ModState.StrategicActorTargetInvocationCmds[unit.GUID].ability.Activate(unit, + carrier); + } + + ModInit.modLog?.Info?.Write( + $"[makeInvocationFromOrders] activated {ModState.StrategicActorTargetInvocationCmds[unit.GUID].ability.Def.Description.Id} on actor {ModState.StrategicActorTargetInvocationCmds[unit.GUID].targetActor.DisplayName} {ModState.StrategicActorTargetInvocationCmds[unit.GUID].targetActor.GUID}"); + ModInit.modLog?.Trace?.Write( + $"[makeInvocationFromOrders] checking if swarm success {unit.IsSwarmingUnit()}, and has fired this round {unit.HasFiredThisRound}"); + + + __result = new StrategicMovementInvocation(unit, true, ModState.StrategicActorTargetInvocationCmds[unit.GUID].targetActor, false, true); + + if (false) + { + if (unit.IsSwarmingUnit() && ModInit.modSettings.AttackOnSwarmSuccess && + !unit.HasFiredThisRound) // maybe move this whole shit do the Strategic Move invocation, check for success, and make the first attack invocation there, at OnComplete? + { + ModInit.modLog?.Trace?.Write( + $"[makeInvocationFromOrders] - found freshly swarmed unit; trying to make attack invocation for same round. Fingies crossed!"); + var target = unit.Combat.FindActorByGUID(ModState.PositionLockSwarm[unit.GUID]); + foreach (var weapon in unit.Weapons) + { + weapon.EnableWeapon(); + } + + var weps = unit.Weapons.Where(x => x.IsEnabled && x.HasAmmo).ToList(); + + var loc = ModState.BADamageTrackers[unit.GUID].BA_MountedLocations.Values + .GetRandomElement(); + //var attackStackSequence = new AttackStackSequence(unit, target, unit.CurrentPosition, unit.CurrentRotation, weps, MeleeAttackType.NotSet, loc, -1); + //unit.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(attackStackSequence)); + var vent = unit.HasVentCoolantAbility && unit.CanVentCoolant; + __result = new AttackInvocation(unit, target, weps, MeleeAttackType.NotSet, loc) + { + ventHeatBeforeAttack = vent + }; // making a regular attack invocation here, instead of stacksequence + reserve + } + + else + { + if (!unit.HasMovedThisRound) + { + unit.BehaviorTree.IncreaseSprintHysteresisLevel(); + } + + __result = new ReserveActorInvocation(unit, ReserveActorAction.DONE, + unit.Combat.TurnDirector.CurrentRound); + } + } + + ModState.StrategicActorTargetInvocationCmds.Remove(unit.GUID); + __runOriginal = false; + return; + } + } + + if (!ModState.AiCmds.ContainsKey(unit.GUID)) + { + __runOriginal = true; + return; + } + + if (!ModState.AiCmds[unit.GUID].active) + { + __runOriginal = true; + return; + } + var quid = ModState.AiCmds[unit.GUID].ability + .Generate2PtCMDQuasiGUID(unit.GUID, ModState.AiCmds[unit.GUID].vectorOne, + ModState.AiCmds[unit.GUID].vectorTwo); + if (ModState.StoredCmdParams.ContainsKey(quid)) + { + __runOriginal = true; + return; + } + //ModState.PopupActorResource = AI_Utils.AssignRandomSpawnAsset(ModState.AiCmds[unit.GUID].ability, unit.team.FactionValue.Name, out var waves); + //ModState.StrafeWaves = waves; + var newParams = new CmdInvocationParams(); + newParams.ActorResource = AI_Utils.AssignRandomSpawnAsset(ModState.AiCmds[unit.GUID].ability, unit.team.FactionValue.Name, + out var waves); + newParams.StrafeWaves = waves; + + if (!string.IsNullOrEmpty(ModState.AiCmds[unit.GUID].ability.Def.getAbilityDefExtension() + .CMDPilotOverride)) + { + newParams.PilotOverride = ModState.AiCmds[unit.GUID].ability.Def.getAbilityDefExtension() + .CMDPilotOverride; + } + + ModState.StoredCmdParams.Add(quid, newParams); + //assign waves here if needed + ModInit.modLog?.Debug?.Write( + $"AICMD DUMP: {ModState.AiCmds[unit.GUID].active}, {ModState.AiCmds[unit.GUID].vectorOne}, {ModState.AiCmds[unit.GUID].vectorTwo}."); + ModInit.modLog?.Debug?.Write( + $"CMD Ability DUMP: {ModState.AiCmds[unit.GUID].ability} {ModState.AiCmds[unit.GUID].ability.Def.Id}, Combat is not null? {ModState.AiCmds[unit.GUID].ability.Combat != null}"); + + ModState.AiCmds[unit.GUID].ability.Activate(unit, ModState.AiCmds[unit.GUID].vectorOne, + ModState.AiCmds[unit.GUID].vectorTwo); + ModInit.modLog?.Info?.Write( + $"activated {ModState.AiCmds[unit.GUID].ability.Def.Description.Id} at pos {ModState.AiCmds[unit.GUID].vectorOne.x}, {ModState.AiCmds[unit.GUID].vectorOne.y}, {ModState.AiCmds[unit.GUID].vectorOne.z} and {ModState.AiCmds[unit.GUID].vectorTwo.x}, {ModState.AiCmds[unit.GUID].vectorTwo.y}, {ModState.AiCmds[unit.GUID].vectorTwo.z}, dist = {ModState.AiCmds[unit.GUID].dist}"); + + if (!unit.HasMovedThisRound) + { + unit.BehaviorTree.IncreaseSprintHysteresisLevel(); + } + + __result = new ReserveActorInvocation(unit, ReserveActorAction.DONE, + unit.Combat.TurnDirector.CurrentRound); + ModState.AiCmds.Remove(unit.GUID); //somehow spawned BA doesn't always reserve on correct round? + __runOriginal = false; + return; + // invoke ability from modstate and then create/use a Brace/Reserve order. + + } + } + + [HarmonyPatch(typeof(AIUtil), + "UnitHasVisibilityToTargetFromCurrentPosition")] //need to add optionally depends on lowvis to make this work for RT + public static class AIUtil_UnitHasVisibilityToTargetFromCurrentPosition + { + public static void Postfix(AbstractActor attacker, ICombatant target, ref bool __result) + { + if (target is AbstractActor actor) + { + if (actor.IsSwarmingUnit() || actor.IsMountedUnit()) // i could force visibility to zero for BA? unsure what i want to do here since i dont think the AI is smart enough to directly target the building. have to see how it plays/maybe give garrisoned BA some DR or something. + { + ModInit.modLog?.Debug?.Write( + $"[AIUtil.UnitHasVisibilityToTargetFromCurrentPosition] DUMP: Target {target.DisplayName} is either mounted or swarming, forcing AI visibility to zero for this node."); + __result = false; + } + } + } + } + [HarmonyPatch(typeof(CanMoveAndShootWithoutOverheatingNode), "Tick")] public static class CanMoveAndShootWithoutOverheatingNode_Tick_Patch // may need to be CanMoveAndShootWithoutOverheatingNode. was IsMovementAvailableForUnitNode { - public static void Prefix(ref bool __runOriginal, CanMoveAndShootWithoutOverheatingNode __instance, ref BehaviorTreeResults __result) + public static void Prefix(ref bool __runOriginal, CanMoveAndShootWithoutOverheatingNode __instance, ref BehaviorTreeResults __result) { if (!__runOriginal) return; if (!__instance.unit.Combat.TurnDirector.IsInterleaved) @@ -335,254 +583,6 @@ public static void Prefix(ref bool __runOriginal, CanMoveAndShootWithoutOverheat } } - [HarmonyPatch(typeof(AITeam), "makeInvocationFromOrders")] - public static class AITeam_makeInvocationFromOrders_patch - { - public static void Prefix(ref bool __runOriginal, AITeam __instance, AbstractActor unit, OrderInfo order, - ref InvocationMessage __result) - { - if (!__runOriginal) return; - if (unit.HasSwarmingUnits()){ - - if (unit is FakeVehicleMech && !unit.HasMovedThisRound && order.OrderType == OrderType.Move || order.OrderType == OrderType.JumpMove || order.OrderType == OrderType.SprintMove) - { - var ability = unit.GetDeswarmerAbilityForAI(true); - if (ability.IsAvailable && !ability.IsActive) - { - ability.Activate(unit, unit); - ModInit.modLog?.Info?.Write($"{unit.DisplayName} {unit.GUID} is vehicle being swarmed. Found movement order, activating erratic maneuvers ability."); - __runOriginal = true; - return; - } - } - - if (ModState.AiDealWithBattleArmorCmds.ContainsKey(unit.GUID)) - { - ModState.AiDealWithBattleArmorCmds[unit.GUID].ability.Activate(unit, unit); - // ModState.AiDealWithBattleArmorCmds[unit.GUID].targetActor); - - ModInit.modLog?.Info?.Write( - $"activated {ModState.AiDealWithBattleArmorCmds[unit.GUID].ability.Def.Description.Id} on actor {unit.DisplayName} {unit.GUID}"); - - if (!unit.HasMovedThisRound) - { - unit.BehaviorTree.IncreaseSprintHysteresisLevel(); - } - - __result = new ReserveActorInvocation(unit, ReserveActorAction.DONE, - unit.Combat.TurnDirector.CurrentRound); - ModState.AiDealWithBattleArmorCmds.Remove(unit.GUID); - __runOriginal = false; - return; - } - } - - if (unit.IsMountedUnit() && !ModState.StrategicActorTargetInvocationCmds.ContainsKey(unit.GUID)) - { - if (unit.CanDeferUnit) - { - __result = new ReserveActorInvocation(unit, ReserveActorAction.DEFER, unit.Combat.TurnDirector.CurrentRound); - __runOriginal = false; - return; // changed to defer to get BA to reserve down? - } - __result = new ReserveActorInvocation(unit, ReserveActorAction.DONE, unit.Combat.TurnDirector.CurrentRound); - __runOriginal = false; - return; // changed to defer to get BA to reserve down? - } - - if (unit.IsSwarmingUnit() && !unit.HasFiredThisRound) - { - var target = unit.Combat.FindActorByGUID(ModState.PositionLockSwarm[unit.GUID]); - if (unit.IsFlaggedForDeath || unit.IsDead) - { - unit.HandleDeath(target.GUID); - __runOriginal = false; - return; - } - - ModInit.modLog?.Info?.Write( - $"[AITeam.makeInvocationFromOrders] Actor {unit.DisplayName} has active swarm attack on {target.DisplayName}"); - if (unit.GetAbilityUsedFiring()) - { - foreach (var weapon in unit.Weapons) - { - weapon.DisableWeapon(); - } - } - else - { - foreach (var weapon in unit.Weapons) - { - weapon.EnableWeapon(); - } - } - - var weps = unit.Weapons.Where(x => x.IsEnabled && x.HasAmmo).ToList(); - - var loc = ModState.BADamageTrackers[unit.GUID].BA_MountedLocations.Values.GetRandomElement(); - //var attackStackSequence = new AttackStackSequence(unit, target, unit.CurrentPosition, unit.CurrentRotation, weps, MeleeAttackType.NotSet, loc, -1); - //unit.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(attackStackSequence)); - var vent = unit.HasVentCoolantAbility && unit.CanVentCoolant; - __result = new AttackInvocation(unit, target, weps, MeleeAttackType.NotSet, loc) - { - ventHeatBeforeAttack = vent - }; // making a regular attack invocation here, instead of stacksequence + reserve - - //if (!unit.HasMovedThisRound) - //{ - // unit.BehaviorTree.IncreaseSprintHysteresisLevel(); - //} - //__result = new ReserveActorInvocation(unit, ReserveActorAction.DONE, unit.Combat.TurnDirector.CurrentRound); - __runOriginal = false; - return; - } - - if (ModState.StrategicActorTargetInvocationCmds.ContainsKey(unit.GUID)) - { - if (ModState.StrategicActorTargetInvocationCmds[unit.GUID].active) - { - ModInit.modLog?.Debug?.Write( - $"BA AI Swarm/Mount Ability DUMP: {ModState.StrategicActorTargetInvocationCmds[unit.GUID].active}, {ModState.StrategicActorTargetInvocationCmds[unit.GUID].targetActor.DisplayName}."); - ModInit.modLog?.Debug?.Write( - $"BA AI Swarm/Mount Ability DUMP: {ModState.StrategicActorTargetInvocationCmds[unit.GUID].ability} {ModState.StrategicActorTargetInvocationCmds[unit.GUID].ability.Def.Id}, Combat is not null? {ModState.StrategicActorTargetInvocationCmds[unit.GUID].ability.Combat != null}"); - - if (unit.IsMountedUnit()) - { - var carrier = unit.Combat.FindActorByGUID(ModState.PositionLockMount[unit.GUID]); - ModState.StrategicActorTargetInvocationCmds[unit.GUID].ability.Activate(unit, - carrier); - } - - ModInit.modLog?.Info?.Write( - $"[makeInvocationFromOrders] activated {ModState.StrategicActorTargetInvocationCmds[unit.GUID].ability.Def.Description.Id} on actor {ModState.StrategicActorTargetInvocationCmds[unit.GUID].targetActor.DisplayName} {ModState.StrategicActorTargetInvocationCmds[unit.GUID].targetActor.GUID}"); - ModInit.modLog?.Trace?.Write( - $"[makeInvocationFromOrders] checking if swarm success {unit.IsSwarmingUnit()}, and has fired this round {unit.HasFiredThisRound}"); - - - __result = new StrategicMovementInvocation(unit, true, ModState.StrategicActorTargetInvocationCmds[unit.GUID].targetActor, false, true); - - if (false) - { - if (unit.IsSwarmingUnit() && ModInit.modSettings.AttackOnSwarmSuccess && - !unit.HasFiredThisRound) // maybe move this whole shit do the Strategic Move invocation, check for success, and make the first attack invocation there, at OnComplete? - { - ModInit.modLog?.Trace?.Write( - $"[makeInvocationFromOrders] - found freshly swarmed unit; trying to make attack invocation for same round. Fingies crossed!"); - var target = unit.Combat.FindActorByGUID(ModState.PositionLockSwarm[unit.GUID]); - foreach (var weapon in unit.Weapons) - { - weapon.EnableWeapon(); - } - - var weps = unit.Weapons.Where(x => x.IsEnabled && x.HasAmmo).ToList(); - - var loc = ModState.BADamageTrackers[unit.GUID].BA_MountedLocations.Values - .GetRandomElement(); - //var attackStackSequence = new AttackStackSequence(unit, target, unit.CurrentPosition, unit.CurrentRotation, weps, MeleeAttackType.NotSet, loc, -1); - //unit.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(attackStackSequence)); - var vent = unit.HasVentCoolantAbility && unit.CanVentCoolant; - __result = new AttackInvocation(unit, target, weps, MeleeAttackType.NotSet, loc) - { - ventHeatBeforeAttack = vent - }; // making a regular attack invocation here, instead of stacksequence + reserve - } - - else - { - if (!unit.HasMovedThisRound) - { - unit.BehaviorTree.IncreaseSprintHysteresisLevel(); - } - - __result = new ReserveActorInvocation(unit, ReserveActorAction.DONE, - unit.Combat.TurnDirector.CurrentRound); - } - } - - ModState.StrategicActorTargetInvocationCmds.Remove(unit.GUID); - __runOriginal = false; - return; - } - } - - if (!ModState.AiCmds.ContainsKey(unit.GUID)) - { - __runOriginal = true; - return; - } - - if (!ModState.AiCmds[unit.GUID].active) - { - __runOriginal = true; - return; - } - var quid = ModState.AiCmds[unit.GUID].ability - .Generate2PtCMDQuasiGUID(unit.GUID, ModState.AiCmds[unit.GUID].vectorOne, - ModState.AiCmds[unit.GUID].vectorTwo); - if (ModState.StoredCmdParams.ContainsKey(quid)) - { - __runOriginal = true; - return; - } - //ModState.PopupActorResource = AI_Utils.AssignRandomSpawnAsset(ModState.AiCmds[unit.GUID].ability, unit.team.FactionValue.Name, out var waves); - //ModState.StrafeWaves = waves; - var newParams = new CmdInvocationParams(); - newParams.ActorResource = AI_Utils.AssignRandomSpawnAsset(ModState.AiCmds[unit.GUID].ability, unit.team.FactionValue.Name, - out var waves); - newParams.StrafeWaves = waves; - - if (!string.IsNullOrEmpty(ModState.AiCmds[unit.GUID].ability.Def.getAbilityDefExtension() - .CMDPilotOverride)) - { - newParams.PilotOverride = ModState.AiCmds[unit.GUID].ability.Def.getAbilityDefExtension() - .CMDPilotOverride; - } - - ModState.StoredCmdParams.Add(quid, newParams); - //assign waves here if needed - ModInit.modLog?.Debug?.Write( - $"AICMD DUMP: {ModState.AiCmds[unit.GUID].active}, {ModState.AiCmds[unit.GUID].vectorOne}, {ModState.AiCmds[unit.GUID].vectorTwo}."); - ModInit.modLog?.Debug?.Write( - $"CMD Ability DUMP: {ModState.AiCmds[unit.GUID].ability} {ModState.AiCmds[unit.GUID].ability.Def.Id}, Combat is not null? {ModState.AiCmds[unit.GUID].ability.Combat != null}"); - - ModState.AiCmds[unit.GUID].ability.Activate(unit, ModState.AiCmds[unit.GUID].vectorOne, - ModState.AiCmds[unit.GUID].vectorTwo); - ModInit.modLog?.Info?.Write( - $"activated {ModState.AiCmds[unit.GUID].ability.Def.Description.Id} at pos {ModState.AiCmds[unit.GUID].vectorOne.x}, {ModState.AiCmds[unit.GUID].vectorOne.y}, {ModState.AiCmds[unit.GUID].vectorOne.z} and {ModState.AiCmds[unit.GUID].vectorTwo.x}, {ModState.AiCmds[unit.GUID].vectorTwo.y}, {ModState.AiCmds[unit.GUID].vectorTwo.z}, dist = {ModState.AiCmds[unit.GUID].dist}"); - - if (!unit.HasMovedThisRound) - { - unit.BehaviorTree.IncreaseSprintHysteresisLevel(); - } - - __result = new ReserveActorInvocation(unit, ReserveActorAction.DONE, - unit.Combat.TurnDirector.CurrentRound); - ModState.AiCmds.Remove(unit.GUID); //somehow spawned BA doesn't always reserve on correct round? - __runOriginal = false; - return; - // invoke ability from modstate and then create/use a Brace/Reserve order. - - } - } - - [HarmonyPatch(typeof(AIUtil), - "UnitHasVisibilityToTargetFromCurrentPosition")] //need to add optionally depends on lowvis to make this work for RT - public static class AIUtil_UnitHasVisibilityToTargetFromCurrentPosition - { - public static void Postfix(AbstractActor attacker, ICombatant target, ref bool __result) - { - if (target is AbstractActor actor) - { - if (actor.IsSwarmingUnit() || actor.IsMountedUnit()) // i could force visibility to zero for BA? unsure what i want to do here since i dont think the AI is smart enough to directly target the building. have to see how it plays/maybe give garrisoned BA some DR or something. - { - ModInit.modLog?.Debug?.Write( - $"[AIUtil.UnitHasVisibilityToTargetFromCurrentPosition] DUMP: Target {target.DisplayName} is either mounted or swarming, forcing AI visibility to zero for this node."); - __result = false; - } - } - } - } - [HarmonyPatch()] public static class Mech_CanEngageTarget { @@ -603,6 +603,7 @@ public static MethodBase TargetMethod() } return null; } + public static void Postfix(Mech __instance, ICombatant target, ref string debugMsg, ref bool __result) { //debugMsg = default(string); diff --git a/StrategicOperations/StrategicOperations/Patches/BattleArmorPatches.cs b/StrategicOperations/StrategicOperations/Patches/BattleArmorPatches.cs index 70a8cfc..ee873dc 100644 --- a/StrategicOperations/StrategicOperations/Patches/BattleArmorPatches.cs +++ b/StrategicOperations/StrategicOperations/Patches/BattleArmorPatches.cs @@ -27,61 +27,91 @@ namespace StrategicOperations.Patches { public class GarrisonLOSPatches { - // Copied wholesale from FrostRaptor's ConcreteJungle, because why reinvent the wheel. Plus if I made it the wheel would be square. - // When a trap turret's line of sight is calculated, give it 'x-ray' vision to see through the shell building. - [HarmonyPatch(typeof(LineOfSight), "GetVisibilityToTargetWithPositionsAndRotations")] - [HarmonyPatch(new Type[] - {typeof(AbstractActor), typeof(Vector3), typeof(ICombatant), typeof(Vector3), typeof(Quaternion)})] - static class LineOfSight_GetVisibilityToTargetWithPositionsAndRotations + // Modify the vision test to allow 'x-ray' vision through the shell building for trap turrets + [HarmonyPatch(typeof(LineOfSight), "bresenhamHeightTest")] + static class LineOfSight_bresenhamHeightTest { - static void Prefix(AbstractActor source, Vector3 sourcePosition, ICombatant target, Vector3 targetPosition, - Quaternion targetRotation) + static void Postfix(LineOfSight __instance, Point p0, float height0, Point p1, float height1, + string targetedBuildingGuid, ref Point collisionWorldPos, + ref bool __result) { - if (source.IsGarrisoned()) - { - ModState.CurrentGarrisonSquadForLOS = source; - } + if (ModState.CurrentGarrisonSquadForLOF == null) return; - } + ModInit.modLog?.Debug?.Write( + $"Recalculating LOF from {ModState.CurrentGarrisonSquadForLOF.DisplayName} due to collision on building shell. " + + $"CollisonWorldPos=> x={collisionWorldPos.X} z={collisionWorldPos.Z}"); - static void Postfix(AbstractActor source, Vector3 sourcePosition, ICombatant target, Vector3 targetPosition, - Quaternion targetRotation, VisibilityLevel __result) - { - ModState.CurrentGarrisonSquadForLOS = null; - } - } + collisionWorldPos = p1; - [HarmonyPatch(typeof(LineOfSight), "FindSecondaryImpactTarget")] - static class LineOfSight_FindSecondaryImpactTarget - { - [HarmonyPriority(Priority.Last)] - static void Postfix(LineOfSight __instance, RaycastHit[] rayInfos, AbstractActor attacker, ICombatant initialTarget, Vector3 attackPosition, - ref string impactTargetId, ref int impactHitLocation, ref AttackDirection attackDirection, ref Vector3 impactPoint, ref bool __result) - { - if (!__result) return; - if (string.IsNullOrEmpty(impactTargetId)) return; - var impactUnit = __instance.Combat.FindCombatantByGUID(impactTargetId); - if (impactUnit is BattleTech.Building impactBuilding) + // If the origin and target points are the same, there is a collision + if (p0.X == p1.X && p0.Z == p1.Z) { - if (!attacker.IsGarrisonedInTargetBuilding(impactBuilding)) return; - impactTargetId = null; - impactHitLocation = 0; - attackDirection = AttackDirection.FromFront; + __result = true; + return; } - else if (impactUnit is AbstractActor impactActor) + // If the origin or target points are outsie the bounds of the map, there is no collision (because how could there be) + if (!__instance.Combat.MapMetaData.IsWithinBounds(p0) || + !__instance.Combat.MapMetaData.IsWithinBounds(p1)) { - if (!attacker.IsMountedToUnit(impactActor) && !attacker.IsAirliftedByTarget(impactActor)) + __result = false; + return; + } + + MapMetaData mapMetaData = __instance.Combat.MapMetaData; + EncounterLayerData encounterLayerData = __instance.Combat.EncounterLayerData; + + bool targetIsABuilding = !string.IsNullOrEmpty(targetedBuildingGuid); + string shellBuildingGUID = ModState.PositionLockGarrison[ModState.CurrentGarrisonSquadForLOF.GUID] + .BuildingGUID; + + List bresenhamLinePoints = BresenhamLineUtil.BresenhamLine(p0, p1); + float heightDeltaPerPoint = (height1 - height0) / (float) bresenhamLinePoints.Count; + float collisionPointHeight = height0; + // Walk the bresenham Line, evaluation collision at a speciifc height as we go. + for (int i = 0; i < bresenhamLinePoints.Count; i++) + { + collisionPointHeight += heightDeltaPerPoint; + Point point = bresenhamLinePoints[i]; + + if (encounterLayerData.mapEncounterLayerDataCells[point.Z, point.X] + .HasSpecifiedBuilding(shellBuildingGUID)) + { + ModInit.modLog?.Debug?.Write( + $" Point x={point.X} z={point.Z} is inside the shell building, continuing."); + continue; + } + + if (targetIsABuilding && encounterLayerData.mapEncounterLayerDataCells[point.Z, point.X] + .HasSpecifiedBuilding(targetedBuildingGuid)) + { + ModInit.modLog?.Debug?.Write( + $" Building {targetedBuildingGuid} conflicts with the LoS, collision at x={collisionWorldPos.X} z={collisionWorldPos.Z}"); + collisionWorldPos = bresenhamLinePoints[i]; + __result = true; return; - impactTargetId = null; - impactHitLocation = 0; - attackDirection = AttackDirection.FromFront; + } + + if (mapMetaData.mapTerrainDataCells[point.Z, point.X].cachedHeight > collisionPointHeight) + { + ModInit.modLog?.Debug?.Write( + $" Collision on terrain at position x={collisionWorldPos.X} z={collisionWorldPos.Z}"); + collisionWorldPos = bresenhamLinePoints[i]; + __result = false; + return; + } } + + ModInit.modLog?.Trace?.Write( + $"No collision detected, changing LoF to true. CollisonWorldPos => x ={collisionWorldPos.X} z ={collisionWorldPos.Z}"); + + __result = true; + return; } } // Modify the vision test to allow 'x-ray' vision through the shell building for trap turrets - [HarmonyPatch(typeof(LineOfSight), "bresenhamVisionTest")] + [HarmonyPatch(typeof(LineOfSight), "bresenhamVisionTest")] static class LineOfSight_bresenhamVisionTest { static void Postfix(LineOfSight __instance, Point p0, float height0, Point p1, float height1, @@ -176,11 +206,63 @@ static void Postfix(LineOfSight __instance, Point p0, float height0, Point p1, f } } + [HarmonyPatch(typeof(LineOfSight), "FindSecondaryImpactTarget")] + static class LineOfSight_FindSecondaryImpactTarget + { + [HarmonyPriority(Priority.Last)] + static void Postfix(LineOfSight __instance, RaycastHit[] rayInfos, AbstractActor attacker, ICombatant initialTarget, Vector3 attackPosition, + ref string impactTargetId, ref int impactHitLocation, ref AttackDirection attackDirection, ref Vector3 impactPoint, ref bool __result) + { + if (!__result) return; + if (string.IsNullOrEmpty(impactTargetId)) return; + var impactUnit = __instance.Combat.FindCombatantByGUID(impactTargetId); + if (impactUnit is BattleTech.Building impactBuilding) + { + if (!attacker.IsGarrisonedInTargetBuilding(impactBuilding)) return; + impactTargetId = null; + impactHitLocation = 0; + attackDirection = AttackDirection.FromFront; + } + + else if (impactUnit is AbstractActor impactActor) + { + if (!attacker.IsMountedToUnit(impactActor) && !attacker.IsAirliftedByTarget(impactActor)) + return; + impactTargetId = null; + impactHitLocation = 0; + attackDirection = AttackDirection.FromFront; + } + } + } + + // Copied wholesale from FrostRaptor's ConcreteJungle, because why reinvent the wheel. Plus if I made it the wheel would be square. + // When a trap turret's line of sight is calculated, give it 'x-ray' vision to see through the shell building. + [HarmonyPatch(typeof(LineOfSight), "GetVisibilityToTargetWithPositionsAndRotations")] + [HarmonyPatch(new Type[] + {typeof(AbstractActor), typeof(Vector3), typeof(ICombatant), typeof(Vector3), typeof(Quaternion)})] + static class LineOfSight_GetVisibilityToTargetWithPositionsAndRotations + { + static void Prefix(AbstractActor source, Vector3 sourcePosition, ICombatant target, Vector3 targetPosition, + Quaternion targetRotation) + { + if (source.IsGarrisoned()) + { + ModState.CurrentGarrisonSquadForLOS = source; + } + + } + + static void Postfix(AbstractActor source, Vector3 sourcePosition, ICombatant target, Vector3 targetPosition, + Quaternion targetRotation, VisibilityLevel __result) + { + ModState.CurrentGarrisonSquadForLOS = null; + } + } + // When a trap turret's line of fire is calculated, give it 'x-ray' vision to see through the shell building. [HarmonyPatch(typeof(LOFCache), "GetLineOfFire")] static class LOFCache_GetLineOfFire { - static void Prefix(AbstractActor source, ICombatant target, LineOfFireLevel __result) { if (source.IsGarrisoned()) @@ -199,341 +281,221 @@ static void Postfix(AbstractActor source, ICombatant target, LineOfFireLevel __r } } } + } - // Modify the vision test to allow 'x-ray' vision through the shell building for trap turrets - [HarmonyPatch(typeof(LineOfSight), "bresenhamHeightTest")] - static class LineOfSight_bresenhamHeightTest + public class BattleArmorPatches + { + [HarmonyPatch(typeof(AbstractActor), "CanMoveAfterShooting", MethodType.Getter)] + public static class AbstractActor_CanMoveAfterShooting { - static void Postfix(LineOfSight __instance, Point p0, float height0, Point p1, float height1, - string targetedBuildingGuid, ref Point collisionWorldPos, - ref bool __result) + public static void Postfix(AbstractActor __instance, ref bool __result) { - if (ModState.CurrentGarrisonSquadForLOF == null) return; - - ModInit.modLog?.Debug?.Write( - $"Recalculating LOF from {ModState.CurrentGarrisonSquadForLOF.DisplayName} due to collision on building shell. " + - $"CollisonWorldPos=> x={collisionWorldPos.X} z={collisionWorldPos.Z}"); - - collisionWorldPos = p1; - - // If the origin and target points are the same, there is a collision - if (p0.X == p1.X && p0.Z == p1.Z) + if (__instance.CanSwarm() || __instance.IsAirlifted()) { - __result = true; - return; + __result = false; } + } + } - // If the origin or target points are outsie the bounds of the map, there is no collision (because how could there be) - if (!__instance.Combat.MapMetaData.IsWithinBounds(p0) || - !__instance.Combat.MapMetaData.IsWithinBounds(p1)) + [HarmonyPatch(typeof(AbstractActor), "HandleDeath", + new Type[] {typeof(string)})] + public static class AbstractActor_HandleDeath + { + public static void Prefix(AbstractActor __instance, string attackerGUID) + { + if (__instance.IsSwarmingUnit()) { - __result = false; - return; + var carrier = __instance.Combat.FindActorByGUID(ModState.PositionLockSwarm[__instance.GUID]); + __instance.DismountBA(carrier, Vector3.zero, false, true); } - MapMetaData mapMetaData = __instance.Combat.MapMetaData; - EncounterLayerData encounterLayerData = __instance.Combat.EncounterLayerData; + if (__instance.IsMountedUnit()) + { + var carrier = __instance.Combat.FindActorByGUID(ModState.PositionLockMount[__instance.GUID]); + __instance.DismountBA(carrier, Vector3.zero, true, true); + } - bool targetIsABuilding = !string.IsNullOrEmpty(targetedBuildingGuid); - string shellBuildingGUID = ModState.PositionLockGarrison[ModState.CurrentGarrisonSquadForLOF.GUID] - .BuildingGUID; + var dismount = false || (__instance.DeathMethod == DeathMethod.PilotEjection || + __instance.DeathMethod == DeathMethod.PilotEjectionActorDisabled || + __instance.DeathMethod == DeathMethod.PilotEjectionNoMessage || + __instance.DeathMethod == DeathMethod.DespawnedNoMessage || + __instance.DeathMethod == DeathMethod.DespawnedEscaped); - List bresenhamLinePoints = BresenhamLineUtil.BresenhamLine(p0, p1); - float heightDeltaPerPoint = (height1 - height0) / (float) bresenhamLinePoints.Count; - float collisionPointHeight = height0; - // Walk the bresenham Line, evaluation collision at a speciifc height as we go. - for (int i = 0; i < bresenhamLinePoints.Count; i++) + if (__instance.HasSwarmingUnits()) { - collisionPointHeight += heightDeltaPerPoint; - Point point = bresenhamLinePoints[i]; - - if (encounterLayerData.mapEncounterLayerDataCells[point.Z, point.X] - .HasSpecifiedBuilding(shellBuildingGUID)) + var swarmingUnits = new List>(ModState.PositionLockSwarm + .Where(x => x.Value == __instance.GUID).ToList()); + var wereSwarmingUnitsResponsible = swarmingUnits.Any(x => x.Key == attackerGUID); + foreach (var swarmingUnit in swarmingUnits) { - ModInit.modLog?.Debug?.Write( - $" Point x={point.X} z={point.Z} is inside the shell building, continuing."); - continue; - } + var actor = __instance.Combat.FindActorByGUID(swarmingUnit.Key); + var squad = actor as TrooperSquad; + if (ModInit.Random.NextDouble() <= (double) 1 / 3 && !wereSwarmingUnitsResponsible && !dismount) + { + var trooperLocs = squad.GetPossibleHitLocations(__instance); + for (int i = 0; i < trooperLocs.Count; i++) + { + var cLoc = (ChassisLocations) trooperLocs[i]; + var hitinfo = new WeaponHitInfo(-1, -1, 0, 0, __instance.GUID, squad.GUID, 1, + new float[1], new float[1], new float[1], new bool[1], new int[trooperLocs[i]], + new int[1], new AttackImpactQuality[1], new AttackDirection[1], new Vector3[1], + new string[1], new int[trooperLocs[i]]); + squad.NukeStructureLocation(hitinfo, trooperLocs[i], cLoc, Vector3.up, + DamageType.ComponentExplosion); + } - if (targetIsABuilding && encounterLayerData.mapEncounterLayerDataCells[point.Z, point.X] - .HasSpecifiedBuilding(targetedBuildingGuid)) - { - ModInit.modLog?.Debug?.Write( - $" Building {targetedBuildingGuid} conflicts with the LoS, collision at x={collisionWorldPos.X} z={collisionWorldPos.Z}"); - collisionWorldPos = bresenhamLinePoints[i]; - __result = true; - return; + actor.DismountBA(__instance, Vector3.zero, false, true); + actor.FlagForDeath("Killed When Mount Died", DeathMethod.VitalComponentDestroyed, + DamageType.Melee, 0, -1, __instance.GUID, false); + actor.HandleDeath(__instance.GUID); + continue; + } + + ModInit.modLog?.Trace?.Write( + $"[AbstractActor.HandleDeath] Swarmed unit {__instance.DisplayName} destroyed, calling dismount."); + actor.DismountBA(__instance, Vector3.zero, false, true); } + } - if (mapMetaData.mapTerrainDataCells[point.Z, point.X].cachedHeight > collisionPointHeight) + if (__instance.HasMountedUnits()) + { + var mountedUnits = new List>(ModState.PositionLockMount + .Where(x => x.Value == __instance.GUID).ToList()); + foreach (var mountedUnit in mountedUnits) { - ModInit.modLog?.Debug?.Write( - $" Collision on terrain at position x={collisionWorldPos.X} z={collisionWorldPos.Z}"); - collisionWorldPos = bresenhamLinePoints[i]; - __result = false; - return; + var actor = __instance.Combat.FindActorByGUID(mountedUnit.Key); + var squad = actor as TrooperSquad; + if (ModInit.Random.NextDouble() <= (double) 1 / 3 && !dismount) + { + var trooperLocs = squad.GetPossibleHitLocations(__instance); + for (int i = 0; i < trooperLocs.Count; i++) + { + var cLoc = (ChassisLocations) trooperLocs[i]; + var hitinfo = new WeaponHitInfo(-1, -1, 0, 0, __instance.GUID, squad.GUID, 1, + new float[1], new float[1], new float[1], new bool[1], new int[trooperLocs[i]], + new int[1], new AttackImpactQuality[1], new AttackDirection[1], new Vector3[1], + new string[1], new int[trooperLocs[i]]); + squad.NukeStructureLocation(hitinfo, trooperLocs[i], cLoc, Vector3.up, + DamageType.ComponentExplosion); + } + + actor.DismountBA(__instance, Vector3.zero, true, true); + actor.FlagForDeath("Killed When Mount Died", DeathMethod.VitalComponentDestroyed, + DamageType.Melee, 0, -1, __instance.GUID, false); + actor.HandleDeath(__instance.GUID); + continue; + } + + ModInit.modLog?.Trace?.Write( + $"[AbstractActor.HandleDeath] Mount {__instance.DisplayName} destroyed, calling dismount."); + actor.DismountBA(__instance, Vector3.zero, true, true); } } - ModInit.modLog?.Trace?.Write( - $"No collision detected, changing LoF to true. CollisonWorldPos => x ={collisionWorldPos.X} z ={collisionWorldPos.Z}"); + if (__instance.HasAirliftedUnits()) + { + var airliftedUnits = + new List>( + ModState.AirliftTrackers.Where(x => x.Value.CarrierGUID == __instance.GUID)).ToList(); - __result = true; - return; + foreach (var unitTracker in airliftedUnits) + { + var actor = __instance.Combat.FindActorByGUID(unitTracker.Key); + ModInit.modLog?.Trace?.Write( + $"[AbstractActor.HandleDeath] Airlift carrier {__instance.DisplayName} destroyed, calling dismount for {actor.DisplayName}."); + __instance.DropAirliftedUnit(actor, Vector3.zero, false, false, true); + } + } } } - } - public class BattleArmorPatches - { - [HarmonyPatch(typeof(LanceConfiguratorPanel), "OnButtonClicked", - new Type[] {typeof(IMechLabDraggableItem)})] - public static class LanceConfiguratorPanel_OnButtonClicked + [HarmonyPatch(typeof(AbstractActor), "HasIndirectFireImmunity", MethodType.Getter)] + public static class AbstractActor_HasIndirectFireImmunity { - private static bool Prepare() => true; - public static void Postfix(LanceConfiguratorPanel __instance, IMechLabDraggableItem item) + public static void Postfix(AbstractActor __instance, ref bool __result) { - var proc = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); + if (__instance.IsMountedUnit() || __instance.IsSwarmingUnit()) + { + __result = true; + } + } + } - if (proc) + [HarmonyPatch(typeof(AbstractActor), "HasIndirectLOFToTargetUnit", + new Type[] {typeof(Vector3), typeof(Quaternion), typeof(ICombatant), typeof(bool)})] + public static class AbstractActor_HasIndirectLOFToTargetUnit_Patch + { + public static void Postfix(AbstractActor __instance, Vector3 attackPosition, Quaternion attackRotation, + ICombatant targetUnit, bool enabledWeaponsOnly, ref bool __result) + { + if (targetUnit is AbstractActor targetActor) { - if (item.ItemType == MechLabDraggableItemType.Mech && - item.DropParent.dropTargetType == MechLabDropTargetType.LanceSlot) + if (__instance.IsSwarmingUnit()) { - if (item is not LanceLoadoutMechItem lanceLoadoutMechItem) return; - var mechDef = lanceLoadoutMechItem.MechDef; - if (mechDef == null) return; - - foreach (var slot in __instance.loadoutSlots) + if (ModState.PositionLockSwarm[__instance.GUID] == targetActor.GUID) { - if (slot.SelectedMech == lanceLoadoutMechItem) - { - if (slot.SelectedPilot == null) - return; // need to have pilot assigned before we can assign BA-Mech Pairings. - var pilotID = slot.SelectedPilot.Pilot.pilotDef.Description.Id; - ModInit.modLog?.Trace?.Write($"[LanceConfiguratorPanel_OnButtonClicked] current pilotID {pilotID}"); - var unitCustomInfo = lanceLoadoutMechItem.MechDef.GetCustomInfo(); - if (unitCustomInfo is {SquadInfo.Troopers: > 1}) - { - if (ModState.PendingPairBAUnit == slot) - { - ModState.PendingPairBAUnit = null; - __instance.SetPairingOverlay(slot, false); - //lanceLoadoutMechItem.UnavailableOverlay.SetActive(false); - return; - } - - if (ModState.PendingPairBAUnit == null) - { - ModState.PendingPairBAUnit = slot; - __instance.SetPairingOverlay(slot, true); // maybe tweak unavailable overlay GO ccolors? - return; - } - } - - if (ModState.PendingPairBAUnit != null && ModState.PendingPairBAUnit != slot) - { - ModInit.modLog?.Trace?.Write( - $"[LanceConfiguratorPanel_OnButtonClicked] found pending BA {ModState.PendingPairBAUnit.SelectedMech.MechDef.Description.Id} and pilot {pilotID}with no pairing"); - var pendingBAPilot = ModState.PendingPairBAUnit.SelectedPilot.Pilot.pilotDef - .Description.Id; - if (!ModState.PairingInfos.ContainsKey(pilotID)) - { - var totalBASpace = mechDef.GetTotalBASpaceMechDef(); - if (totalBASpace > 0) - { - ModState.PairingInfos.Add(pilotID, - new Classes.BAPairingInfo(totalBASpace, pendingBAPilot)); - __instance.SetPairingOverlay(ModState.PendingPairBAUnit, true, slot); - //ModState.PendingPairBAUnit.SelectedMech.UnavailableOverlay.SetActive(false); // maybe tweak unavailable overlay GO ccolors? - ModState.PendingPairBAUnit = null; - ModInit.modLog?.Trace?.Write( - $"[LanceConfiguratorPanel_OnButtonClicked] Added new BA Pairing info. {mechDef.Description.Id} and {pilotID} paired with {string.Join(", ", ModState.PairingInfos[pilotID].PairedBattleArmor)}"); - //make a notification that pairing happened? bloop? - } - } - else - { - var pairInfo = ModState.PairingInfos[pilotID]; - if (pairInfo.PairedBattleArmor.Count < pairInfo.CapacityInitial) - { - pairInfo.PairedBattleArmor.Add(pendingBAPilot); - __instance.SetPairingOverlay(ModState.PendingPairBAUnit, true, slot); - //ModState.PendingPairBAUnit.SelectedMech.UnavailableOverlay.SetActive(false); // maybe tweak unavailable overlay GO ccolors? - ModState.PendingPairBAUnit = null; - ModInit.modLog?.Trace?.Write( - $"[LanceConfiguratorPanel_OnButtonClicked] Added BA Pairing to existing info for {pilotID}. {mechDef.Description.Id} and {pilotID} paired with {string.Join(", ", ModState.PairingInfos[pilotID].PairedBattleArmor)}"); - //make a notification that pairing happened? bloop? - } - } - return; - } - return; - } +// ModInit.modLog?.Trace?.Write($"[AbstractActor.HasIndirectLOFToTargetUnit] {__instance.DisplayName} is swarming {targetActor.DisplayName}, forcing direct LOS for weapons"); + __result = false; } } + + if (targetActor.IsSwarmingUnit() || targetActor.IsMountedUnit()) //|| targetActor.isGarrisoned()) + { + __result = false; + } } } } - [HarmonyPatch(typeof(LanceConfiguratorPanel), "OnCancelClicked", - new Type[] {})] - public static class LanceConfiguratorPanel_OnCancelClicked + //patching LOFCache.GetLineOfFire with BA to make sure its not obstructed AND that the carrier isnt obstructed. gonna be messy AF. will also probaly break LowVis. + + + [HarmonyPatch(typeof(AbstractActor), "HasLOFToTargetUnitAtTargetPosition", + new Type[] + { + typeof(ICombatant), typeof(float), typeof(Vector3), typeof(Quaternion), typeof(Vector3), + typeof(Quaternion), typeof(bool) + })] + public static class AbstractActor_HasLOFToTargetUnitAtTargetPosition_Patch { - private static bool Prepare() => true; + static bool Prepare() => true; //disabled for now. why? - public static void Postfix(LanceConfiguratorPanel __instance) + // make sure units doing swarming or riding cannot be targeted. + public static void Postfix(AbstractActor __instance, ICombatant targetUnit, float maxRange, + Vector3 attackPosition, Quaternion attackRotation, Vector3 targetPosition, Quaternion targetRotation, + bool isIndirectFireCapable, ref bool __result) { - if (__instance.IsSimGame) + if (targetUnit is AbstractActor targetActor) { - ModState.PairingInfos = new Dictionary(); - ModState.UsedOverlayColors = new List(); - ModState.UsedOverlayColorsByCarrier = new Dictionary(); + if (targetActor.IsSwarmingUnit() || targetActor.IsMountedUnit()) //|| targetActor.isGarrisoned()) + { +// ModInit.modLog?.Trace?.Write($"[AbstractActor.HasLOFToTargetUnitAtTargetPosition] {targetActor.DisplayName} is swarming or mounted, preventing LOS."); + __result = false; + } } } } - [HarmonyPatch(typeof(LanceLoadoutSlot), "OnRemoveItem", - new Type[] {typeof(IMechLabDraggableItem), typeof(bool) })] - public static class LanceLoadoutSlot_OnRemoveItem + [HarmonyPatch(typeof(ActivatableComponent), "activateComponent", + new Type[] {typeof(MechComponent), typeof(bool), typeof(bool)})] + public static class ActivatableComponent_activateComponent { - private static bool Prepare() => true; - public static void Prefix(ref bool __runOriginal, LanceLoadoutSlot __instance, IMechLabDraggableItem item, bool validate) + public static void Postfix(ActivatableComponent __instance, MechComponent component, bool autoActivate, + bool isInital) { - if (!__runOriginal) return; - ModState.PendingPairBAUnit = null; - if (__instance.SelectedPilot != null) + if (ModInit.modSettings.BPodComponentIDs.Contains(component.defId)) { - var pilotID = __instance.SelectedPilot.Pilot.pilotDef.Description.Id; - var toRemoveKeys = new List(); - if (ModState.PairingInfos.ContainsKey(pilotID)) + ActivatableComponent activatableComponent = + component.componentDef.GetComponent(); + var enemyActors = component.parent.GetAllEnemiesWithinRange(activatableComponent.Explosion.Range); + foreach (var enemyActor in enemyActors) { - var toRemove = new List(); - foreach (var pairedBA in ModState.PairingInfos[pilotID].PairedBattleArmor) - { - toRemove.Add(pairedBA); - } - ModState.PairingInfos.Remove(pilotID); - foreach (var slot in __instance.LC.loadoutSlots) + if (enemyActor is TrooperSquad trooperSquad) { - if (toRemove.Contains(slot?.SelectedPilot?.Pilot.pilotDef.Description.Id)) + if (trooperSquad.IsSwarmingUnit() && + ModState.PositionLockSwarm[trooperSquad.GUID] == component.parent.GUID) { - var overlayChildren = slot?.SelectedMech?.UnavailableOverlay.gameObject.GetComponentsInChildren(); - if (overlayChildren == null) continue; - foreach (var overlayChild in overlayChildren) - { - if (overlayChild.name != "stripes") continue; - var overlayChildImage = overlayChild.GetComponent(); - if (overlayChildImage.color != ModState.DefaultOverlay && - slot.SelectedMech.UnavailableOverlay.activeSelf) - { - overlayChildImage.color = ModState.PendingSelectionColor;//new Color(0, 0, 0, ModState.DefaultOverlay.a); - slot.SelectedMech.UnavailableOverlay.SetActive(false); - } - } - } - } - } - - else - { - foreach (var pairedKvP in ModState.PairingInfos) - { - if (pairedKvP.Value.PairedBattleArmor.Contains(pilotID)) - { - var carrierID = pairedKvP.Key; - pairedKvP.Value.PairedBattleArmor.Remove(pilotID); - if (pairedKvP.Value.PairedBattleArmor.Count == 0) - { - toRemoveKeys.Add(carrierID); - } - foreach (var slot in __instance.LC.loadoutSlots) - { - if (pilotID == slot?.SelectedPilot?.Pilot.pilotDef.Description.Id || toRemoveKeys.Contains(slot?.SelectedPilot?.Pilot.pilotDef.Description.Id)) - { - var overlayChildren = slot?.SelectedMech?.UnavailableOverlay.gameObject.GetComponentsInChildren(); - if (overlayChildren == null) continue; - foreach (var overlayChild in overlayChildren) - { - if (overlayChild.name != "stripes") continue; - var overlayChildImage = overlayChild.GetComponentInChildren(); - if (overlayChildImage.color != ModState.DefaultOverlay && - slot.SelectedMech.UnavailableOverlay.activeSelf) - { - overlayChildImage.color = ModState.PendingSelectionColor;//new Color(0, 0, 0, ModState.DefaultOverlay.a); - slot.SelectedMech.UnavailableOverlay.SetActive(false); - } - } - } - } - } - } - - foreach (var toRemoveKey in toRemoveKeys) - { - ModState.PairingInfos.Remove(toRemoveKey); - } - } - ModInit.modLog?.Trace?.Write($"[LanceLoadoutSlot_OnRemoveItem] Removed {pilotID}. Should have removed {string.Join(", ",toRemoveKeys)} from PairingInfo keys. Pairing info keys: {string.Join(", ", ModState.PairingInfos.Keys)}"); - } - __instance.SelectedMech?.UnavailableOverlay.SetActive(false); - } - } - - [HarmonyPatch(typeof(MechBayMechInfoWidget), "SetData", - new Type[] {typeof(SimGameState), typeof(MechBayPanel), typeof(DataManager), typeof(MechBayMechUnitElement), typeof(bool), typeof(bool)})] - public static class MechBayMechInfoWidget_SetData - { - public static void Postfix(MechBayMechInfoWidget __instance, SimGameState sim, MechBayPanel mechBay, - DataManager dataManager, MechBayMechUnitElement mechElement, bool useNoMechOverlay, - bool useRepairButton) - { - var decoGO = __instance.gameObject.FindFirstChildNamed("Deco"); - var svgComponentDeco = decoGO.GetComponent(); - var decoParent = decoGO?.transform?.parent; - UnityEngine.Object.DestroyImmediate(svgComponentDeco); - var newlocTxtGO = decoParent?.gameObject.FindFirstChildNamed("StratOps_SquadCarrierTip"); - if (newlocTxtGO == null) - { - ModInit.modLog?.Trace?.Write($"[MechBayMechInfoWidget_SetData] couldnt find game object `StratOps_SquadCarrierTip`, instantiating a new one"); - newlocTxtGO = UnityEngine.Object.Instantiate(decoGO, decoParent); - newlocTxtGO.name = "StratOps_SquadCarrierTip"; - } - else - { - ModInit.modLog?.Trace?.Write($"[MechBayMechInfoWidget_SetData] Found game object `StratOps_SquadCarrierTip` and using it."); - } - var localizableTextComponent = newlocTxtGO.GetOrAddComponent(); - localizableTextComponent.SetText(ModInit.modSettings.BAMountReminderText); - localizableTextComponent.alignment = TextAlignmentOptions.BottomLeft; - localizableTextComponent.enableAutoSizing = true; - localizableTextComponent.enableWordWrapping = false; - if (mechBay == null) newlocTxtGO.SetActive(__instance?.selectedMech?.GetCustomInfo()?.SquadInfo?.Troopers > 0); - //decoGO.SetActive(true); - - } - } - - [HarmonyPatch(typeof(ActivatableComponent), "activateComponent", - new Type[] {typeof(MechComponent), typeof(bool), typeof(bool)})] - public static class ActivatableComponent_activateComponent - { - public static void Postfix(ActivatableComponent __instance, MechComponent component, bool autoActivate, - bool isInital) - { - if (ModInit.modSettings.BPodComponentIDs.Contains(component.defId)) - { - ActivatableComponent activatableComponent = - component.componentDef.GetComponent(); - var enemyActors = component.parent.GetAllEnemiesWithinRange(activatableComponent.Explosion.Range); - foreach (var enemyActor in enemyActors) - { - if (enemyActor is TrooperSquad trooperSquad) - { - if (trooperSquad.IsSwarmingUnit() && - ModState.PositionLockSwarm[trooperSquad.GUID] == component.parent.GUID) - { - trooperSquad.DismountBA(component.parent, Vector3.zero, false, true); + trooperSquad.DismountBA(component.parent, Vector3.zero, false, true); } var baLoc = trooperSquad.GetPossibleHitLocations(component.parent); @@ -567,144 +529,6 @@ public static void Postfix(ActivatableComponent __instance, MechComponent compon } } - [HarmonyPatch(typeof(SelectionStateTargetSingleCombatantBase), "ProcessClickedCombatant", - new Type[] {typeof(ICombatant)})] - public static class SelectionStateTargetSingleCombatantBase_ProcessClickedCombatant - { - //private static bool Prepare() => false; - public static void Postfix(SelectionStateTargetSingleCombatantBase __instance, ICombatant combatant) - { - - if (__instance.FromButton.Ability.Def.Id == ModInit.modSettings.BattleArmorMountAndSwarmID) - { - var cHUD = __instance - .HUD; //IRBTModUtils.SharedState.CombatHUD;//Traverse.Create(__instance).Property("HUD").GetValue(); - var creator = cHUD.SelectedActor; - if (!creator.Pathing.ArePathGridsComplete) - { - cHUD.AttackModeSelector.FireButton.CurrentFireMode = CombatHUDFireButton.FireMode.None; - cHUD.AttackModeSelector.FireButton.FireText.SetText($"Pathing Incomplete - DISABLED", - Array.Empty()); - return; - } - - if (creator is Mech creatorMech && combatant != null && combatant.team.IsEnemy(creator.team)) - { - var chance = creator.Combat.ToHit.GetToHitChance(creator, creatorMech.MeleeWeapon, combatant, - creator.CurrentPosition, combatant.CurrentPosition, 1, MeleeAttackType.Charge, false); - ModInit.modLog?.Trace?.Write( - $"[SelectionState.ShowFireButton - Swarm Success calculated as {chance}, storing in state."); - ModState.SwarmSuccessChance = chance; - var chanceDisplay = (float) Math.Round(chance, 2) * 100; - cHUD.AttackModeSelector.FireButton.FireText.SetText($"{chanceDisplay}% - Confirm", - Array.Empty()); - } - } - else if (__instance.FromButton.Ability.Def.Id == ModInit.modSettings.AirliftAbilityID) - { - var cHUD = __instance - .HUD; //IRBTModUtils.SharedState.CombatHUD;//Traverse.Create(__instance).Property("HUD").GetValue(); - var creator = cHUD.SelectedActor; - if (!creator.Pathing.ArePathGridsComplete) - { - cHUD.AttackModeSelector.FireButton.CurrentFireMode = CombatHUDFireButton.FireMode.None; - cHUD.AttackModeSelector.FireButton.FireText.SetText($"Wait For Pathing Incomplete - DISABLED", - Array.Empty()); - } - } - } - } - - [HarmonyPatch(typeof(SelectionStateAbilityInstant), "OnAddToStack", new Type[] { })] - public static class SelectionStateAbilityInstant_OnAddToStack - { - public static void Postfix(SelectionStateAbilityInstant __instance) - { - var cHUD = __instance - .HUD; //IRBTModUtils.SharedState.CombatHUD;//Traverse.Create(__instance).Property("HUD").GetValue(); - var creator = cHUD.SelectedActor; - if (__instance.FromButton.Ability.Def.Id == ModInit.modSettings.BattleArmorDeSwarmRoll) - { - var settings = - ModInit.modSettings.DeswarmConfigs.ContainsKey(ModInit.modSettings.BattleArmorDeSwarmRoll) - ? ModInit.modSettings.DeswarmConfigs[ModInit.modSettings.BattleArmorDeSwarmRoll] - : new Classes.ConfigOptions.BA_DeswarmAbilityConfig(); - //var parsed = float.TryParse(__instance.FromButton.Ability.Def.EffectData - // .FirstOrDefault(x => x.statisticData.statName == "BattleArmorDeSwarmerRoll") - // ?.statisticData - // .modValue, out var baseChance); - - var pilotSkill = creator.GetPilot().Piloting; - var finalChance = Mathf.Min(settings.BaseSuccessChance + (0.05f * pilotSkill), - settings.MaxSuccessChance); - ModInit.modLog?.Info?.Write( - $"[SelectionStateAbilityInstant.OnAddToStack - BattleArmorDeSwarm] Deswarm chance: {finalChance} from baseChance {settings.BaseSuccessChance} + pilotSkill x 0.05 {0.05f * pilotSkill}, max {settings.MaxSuccessChance}., stored in state."); - ModState.DeSwarmSuccessChance = finalChance; - var chanceDisplay = (float) Math.Round(finalChance, 3) * 100; - cHUD.AttackModeSelector.FireButton.FireText.SetText($"{chanceDisplay}% - Confirm", - Array.Empty()); - } - else if (__instance.FromButton.Ability.Def.Id == ModInit.modSettings.BattleArmorDeSwarmSwat) - { - var settings = - ModInit.modSettings.DeswarmConfigs.ContainsKey(ModInit.modSettings.BattleArmorDeSwarmSwat) - ? ModInit.modSettings.DeswarmConfigs[ModInit.modSettings.BattleArmorDeSwarmSwat] - : new Classes.ConfigOptions.BA_DeswarmAbilityConfig(); - //var parsed = float.TryParse(__instance.FromButton.Ability.Def.EffectData - // .FirstOrDefault(x => x.statisticData.statName == "BattleArmorDeSwarmerSwat") - // ?.statisticData - // .modValue, out var baseChance); - //if (!parsed) baseChance = 0.55f; - - var pilotSkill = creator.GetPilot().Piloting; - var missingActuatorCount = -8; - foreach (var armComponent in creator.allComponents.Where(x => - x.IsFunctional && (x.Location == 2 || x.Location == 32))) - { - foreach (var CategoryID in ModInit.modSettings.ArmActuatorCategoryIDs) - { - if (armComponent.mechComponentRef.IsCategory(CategoryID)) - { - missingActuatorCount += 1; - break; - } - } - } - - var finalChance = - Mathf.Min(settings.BaseSuccessChance + (0.05f * pilotSkill) - (0.05f * missingActuatorCount), - settings.MaxSuccessChance); - - ModInit.modLog?.Info?.Write( - $"[SelectionStateAbilityInstant.OnAddToStack - BattleArmorDeSwarm] Deswarm chance: {finalChance} from baseChance {settings.BaseSuccessChance} + pilotSkill x 0.05 {0.05f * pilotSkill} - missingActuators x 0.05 {0.05f * missingActuatorCount}, max {settings.MaxSuccessChance} stored in state."); - ModState.DeSwarmSuccessChance = finalChance; - var chanceDisplay = (float) Math.Round(finalChance, 3) * 100; - cHUD.AttackModeSelector.FireButton.FireText.SetText($"{chanceDisplay}% - Confirm", - Array.Empty()); - } - } - } - - [HarmonyPatch(typeof(AttackDirector.AttackSequence), "IsBreachingShot", MethodType.Getter)] - public static class AttackDirector_AttackSequence_IsBreachingShot - { - static bool Prepare() => !ModInit.modSettings.UsingMechAffinityForSwarmBreach; - - public static void Postfix(AttackDirector.AttackSequence __instance, ref bool __result) - { - if (!__result) - { - if (__instance.chosenTarget is AbstractActor targetActor) - { - if (__instance.attacker.IsSwarmingTargetUnit(targetActor)) - { - __result = true; - } - } - } - } - } - [HarmonyPatch(typeof(ActorMovementSequence), "CompleteOrders")] public static class ActorMovementSequence_CompleteOrders @@ -754,324 +578,140 @@ public static void Postfix(ActorMovementSequence __instance) } } - [HarmonyPatch(typeof(MechJumpSequence), "CompleteOrders")] - public static class MechJumpSequence_CompleteOrders + [HarmonyPatch(typeof(AttackDirector.AttackSequence), "IsBreachingShot", MethodType.Getter)] + public static class AttackDirector_AttackSequence_IsBreachingShot { - public static void Postfix(MechJumpSequence __instance) - { - if (__instance.OwningMech == null) return; - var settings = ModInit.modSettings.DeswarmMovementConfig; + static bool Prepare() => !ModInit.modSettings.UsingMechAffinityForSwarmBreach; - if (ModState.DeSwarmMovementInfo?.Carrier?.GUID == __instance.OwningMech.GUID) + public static void Postfix(AttackDirector.AttackSequence __instance, ref bool __result) + { + if (!__result) { - var baseChance = settings.BaseSuccessChance; //__instance.owningActor.getMovementDeSwarmMinChance(); - var chanceFromPips = __instance.owningActor.EvasivePipsCurrent * settings.EvasivePipsFactor; - //__instance.owningActor.getMovementDeSwarmEvasivePipsFactor(); - var finalChance = - Mathf.Min((baseChance + chanceFromPips) * settings.JumpMovementModifier, - settings - .MaxSuccessChance); //__instance.owningActor.getMovementDeSwarmEvasiveJumpMovementMultiplier(), __instance.owningActor.getMovementDeSwarmMaxChance()); - var roll = ModInit.Random.NextDouble(); - ModInit.modLog?.Info?.Write( - $"[ActorMovementSequence.CompleteOrders] Found DeSwarmMovementInfo for unit {__instance.owningActor.DisplayName} {__instance.owningActor.GUID}. Rolled {roll} vs finalChance {finalChance} from (baseChance {baseChance} + evasive chance {chanceFromPips}) x JumpMovementMulti {settings.JumpMovementModifier}"); - if (roll <= finalChance) + if (__instance.chosenTarget is AbstractActor targetActor) { - var baseDistance = Vector3.Distance(__instance.StartPos, __instance.FinalPos); - - foreach (var swarmingUnit in ModState.DeSwarmMovementInfo.SwarmingUnits) + if (__instance.attacker.IsSwarmingTargetUnit(targetActor)) { - var finalDist = (float) (baseDistance * ModInit.Random.NextDouble()); - var finalDestination = - Utils.LerpByDistance(__instance.StartPos, __instance.FinalPos, finalDist); - finalDestination.y = - swarmingUnit.Combat.MapMetaData.GetLerpedHeightAt(finalDestination, - false); //set proper height on ground. - ModInit.modLog?.Info?.Write( - $"[ActorMovementSequence.CompleteOrders] Roll succeeded, plonking {swarmingUnit.DisplayName} at {finalDestination}"); - swarmingUnit.DismountBA(__instance.owningActor, finalDestination, false, true); - if (swarmingUnit is TrooperSquad swarmingUnitSquad) - { - var dmg = settings.UseDFADamage - ? swarmingUnitSquad.StatCollection.GetValue("DFASelfDamage") - : settings.LocationDamageOverride; - var reduction = settings.PilotingDamageReductionFactor * - swarmingUnitSquad.GetPilot().Piloting; - var dmgReduction = dmg * reduction; - dmg -= dmgReduction; - if (dmg > 0f) - { - var trooperLocs = swarmingUnitSquad.GetPossibleHitLocations(__instance.owningActor); - for (int i = 0; i < trooperLocs.Count; i++) - { - var hitinfo = new WeaponHitInfo(-1, -1, 0, 0, __instance.owningActor.GUID, - swarmingUnitSquad.GUID, 1, new float[1], new float[1], new float[1], - new bool[1], new int[trooperLocs[i]], new int[1], - new AttackImpactQuality[1], - new AttackDirection[1], new Vector3[1], new string[1], - new int[trooperLocs[i]]); - - swarmingUnitSquad.TakeWeaponDamage(hitinfo, trooperLocs[i], - swarmingUnitSquad.MeleeWeapon, dmg, - 0, 0, DamageType.DFASelf); - } - } - } + __result = true; } } - - ModState.DeSwarmMovementInfo = new Classes.BA_DeswarmMovementInfo(); } } } - [HarmonyPatch(typeof(CombatHUDMechwarriorTray), "ResetMechwarriorButtons", - new Type[] {typeof(AbstractActor)})] - public static class CombatHUDMechwarriorTray_ResetMechwarriorButtons + [HarmonyPatch(typeof(BattleTech.Building), "FlagForDeath", + new Type[] + { + typeof(string), typeof(DeathMethod), typeof(DamageType), typeof(int), typeof(int), typeof(string), + typeof(bool) + })] + public static class Building_FlagForDeath { - [HarmonyPriority(Priority.Last)] - public static void Postfix(CombatHUDMechwarriorTray __instance, AbstractActor actor) + public static void Prefix(BattleTech.Building __instance, string reason, DeathMethod deathMethod, + DamageType damageType, int location, int stackItemID, string attackerID, bool isSilent) { - if (UnityGameInstance.BattleTechGame.Combat.ActiveContract.ContractTypeValue.IsSkirmish) return; - if (actor == null) return; - - var moraleButtons = - __instance - .MoraleButtons; //Traverse.Create(__instance).Property("MoraleButtons").GetValue(); - var abilityButtons = - __instance - .AbilityButtons; //Traverse.Create(__instance).Property("AbilityButtons").GetValue(); - - if (actor.IsAirlifted()) + if (__instance.IsFlaggedForDeath) return; + ModInit.modLog?.Trace?.Write( + $"[Building.FlagForDeath] Building {__instance.DisplayName} {__instance.GUID} at position {__instance.CurrentPosition} dieded."); + if (!__instance.HasGarrisonedUnits()) return; + var garrisons = new List>(ModState.PositionLockGarrison + .Where(x => x.Value.BuildingGUID == __instance.GUID).ToList()); + foreach (var garrison in garrisons) { ModInit.modLog?.Trace?.Write( - $"[CombatHUDMechwarriorTray.ResetMechwarriorButtons] Actor {actor.DisplayName} {actor.GUID} is Airlifted. Disabling movement buttons."); - __instance.MoveButton.DisableButton(); - __instance.SprintButton.DisableButton(); - __instance.JumpButton.DisableButton(); + $"[Building.FlagForDeath] Building {__instance.DisplayName} {__instance.GUID} dieded, has units mounted."); - if (ModState.AirliftTrackers[actor.GUID].IsCarriedInternal) + var actor = __instance.Combat.FindActorByGUID(garrison.Key); + if (actor is TrooperSquad squad) { - foreach (var moraleButton in moraleButtons) + foreach (var garrisonEffect in ModState.OnGarrisonCollapseEffects) { - moraleButton.DisableButton(); + if (garrisonEffect.TargetEffectType == Classes.ConfigOptions.BA_TargetEffectType.GARRISON) + { + foreach (var effectData in garrisonEffect.effects) + { + //squad.Combat.EffectManager.CreateEffect(effectData,effectData.Description.Id,-1, actor, actor, default(WeaponHitInfo), 1); + squad.CreateEffect(effectData, null, + effectData.Description.Id, + -1, squad); + } + } } - __instance.FireButton.DisableButton(); - } - } - - if (actor.IsGarrisoned()) - { - ModInit.modLog?.Trace?.Write( - $"[CombatHUDMechwarriorTray.ResetMechwarriorButtons] Actor {actor.DisplayName} {actor.GUID} found in garrison. Disabling buttons."); - - __instance.MoveButton.DisableButton(); - __instance.SprintButton.DisableButton(); - __instance.JumpButton.DisableButton(); - } - - else if (actor.IsMountedUnit()) - { - ModInit.modLog?.Trace?.Write( - $"[CombatHUDMechwarriorTray.ResetMechwarriorButtons] Actor {actor.DisplayName} {actor.GUID} found in PositionLockMount. Disabling buttons."); - var carrier = actor.Combat.FindActorByGUID(ModState.PositionLockMount[actor.GUID]); + squad.DismountGarrison(__instance, Vector3.zero, true); - __instance.MoveButton.DisableButton(); - __instance.SprintButton.DisableButton(); - __instance.JumpButton.DisableButton(); - if (!actor.IsMountedInternal() || !carrier.HasFiringPorts()) - { - __instance.FireButton.DisableButton(); - foreach (var moraleButton in moraleButtons) + var dmg = squad.StatCollection.GetValue("DFASelfDamage"); + var trooperLocs = squad.GetPossibleHitLocations(squad); + for (int i = 0; i < trooperLocs.Count; i++) { - moraleButton.DisableButton(); - } + var hitinfo = new WeaponHitInfo(-1, -1, 0, 0, squad.GUID, + squad.GUID, 1, new float[1], new float[1], new float[1], + new bool[1], new int[trooperLocs[i]], new int[1], new AttackImpactQuality[1], + new AttackDirection[1], new Vector3[1], new string[1], new int[trooperLocs[i]]); - foreach (var abilityButton in abilityButtons) - { - if (abilityButton?.Ability?.Def?.Id == ModInit.modSettings.BattleArmorMountAndSwarmID) - abilityButton?.DisableButton(); + squad.TakeWeaponDamage(hitinfo, trooperLocs[i], + squad.MeleeWeapon, dmg, + 0, 0, DamageType.DFASelf); } } } - else if (actor.IsSwarmingUnit()) - { - ModInit.modLog?.Trace?.Write( - $"[CombatHUDMechwarriorTray.ResetMechwarriorButtons] Actor {actor.DisplayName} {actor.GUID} found in PositionLockSwarm. Disabling buttons."); - __instance.FireButton.DisableButton(); - __instance.MoveButton.DisableButton(); - __instance.SprintButton.DisableButton(); - __instance.JumpButton.DisableButton(); - - foreach (var moraleButton in moraleButtons) - { - moraleButton.DisableButton(); - } - - foreach (var abilityButton in abilityButtons) - { - if (abilityButton?.Ability?.Def?.Id == ModInit.modSettings.BattleArmorMountAndSwarmID) - abilityButton?.DisableButton(); - } - } } } - //patching LOFCache.GetLineOfFire with BA to make sure its not obstructed AND that the carrier isnt obstructed. gonna be messy AF. will also probaly break LowVis. - - - - [HarmonyPatch(typeof(AbstractActor), "HasLOFToTargetUnitAtTargetPosition", - new Type[] - { - typeof(ICombatant), typeof(float), typeof(Vector3), typeof(Quaternion), typeof(Vector3), - typeof(Quaternion), typeof(bool) - })] - public static class AbstractActor_HasLOFToTargetUnitAtTargetPosition_Patch + [HarmonyPatch(typeof(CombatGameState), "GetAllTabTargets", + new Type[] {typeof(AbstractActor)})] + public static class CombatGameState_GetAllTabTargets { - static bool Prepare() => true; //disabled for now. why? - - // make sure units doing swarming or riding cannot be targeted. - public static void Postfix(AbstractActor __instance, ICombatant targetUnit, float maxRange, - Vector3 attackPosition, Quaternion attackRotation, Vector3 targetPosition, Quaternion targetRotation, - bool isIndirectFireCapable, ref bool __result) + static bool Prepare() => false; + public static void Postfix(CombatGameState __instance, AbstractActor actor, ref List __result) { - if (targetUnit is AbstractActor targetActor) + for (var index = __result.Count - 1; index >= 0; index--) { - if (targetActor.IsSwarmingUnit() || targetActor.IsMountedUnit()) //|| targetActor.isGarrisoned()) + var combatant = __result[index]; + if (combatant is AbstractActor targetActor && (targetActor.IsSwarmingUnit() || + targetActor.IsMountedUnit())) { -// ModInit.modLog?.Trace?.Write($"[AbstractActor.HasLOFToTargetUnitAtTargetPosition] {targetActor.DisplayName} is swarming or mounted, preventing LOS."); - __result = false; + __result.Remove(combatant); } } } } - - [HarmonyPatch(typeof(AbstractActor), "HasIndirectLOFToTargetUnit", - new Type[] {typeof(Vector3), typeof(Quaternion), typeof(ICombatant), typeof(bool)})] - public static class AbstractActor_HasIndirectLOFToTargetUnit_Patch + + [HarmonyPatch(typeof(CombatHUDButtonBase), "OnClick", + new Type[] { })] + public static class CombatHUDButtonBase_OnClick { - public static void Postfix(AbstractActor __instance, Vector3 attackPosition, Quaternion attackRotation, - ICombatant targetUnit, bool enabledWeaponsOnly, ref bool __result) + //static bool Prepare() => ModInit.modSettings.EnableQuickReserve; + public static void Prefix(ref bool __runOriginal, CombatHUDButtonBase __instance) { - if (targetUnit is AbstractActor targetActor) + if (!__runOriginal) return; + if (__instance.GUID != "BTN_DoneWithMech") { - if (__instance.IsSwarmingUnit()) - { - if (ModState.PositionLockSwarm[__instance.GUID] == targetActor.GUID) - { -// ModInit.modLog?.Trace?.Write($"[AbstractActor.HasIndirectLOFToTargetUnit] {__instance.DisplayName} is swarming {targetActor.DisplayName}, forcing direct LOS for weapons"); - __result = false; - } - } - - if (targetActor.IsSwarmingUnit() || targetActor.IsMountedUnit()) //|| targetActor.isGarrisoned()) - { - __result = false; - } + __runOriginal = true; + return; } - } - } - [HarmonyPatch(typeof(Weapon), "WillFireAtTargetFromPosition", - new Type[] {typeof(ICombatant), typeof(Vector3), typeof(Quaternion)})] - public static class Weapon_WillFireAtTargetFromPosition - { - public static void Postfix(Weapon __instance, ICombatant target, Vector3 position, Quaternion rotation, - ref bool __result) - { - if (__instance.parent == null) return; - if (target is AbstractActor targetActor) + var hud = __instance + .HUD; //IRBTModUtils.SharedState.CombatHUD;//Traverse.Create(__instance).Property("HUD").GetValue(); + var actor = hud.SelectedActor; + if (ModInit.modSettings.EnableQuickReserve) { - if (__instance.parent.IsSwarmingUnit()) + var hk = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); + if (hk && actor.CanDeferUnit) { - if (ModState.PositionLockSwarm[__instance.parent.GUID] == targetActor.GUID) - { - // ModInit.modLog?.Trace?.Write($"[Weapon.WillFireAtTargetFromPosition] {__instance.parent.DisplayName} is swarming {targetActor.DisplayName}, forcing LOS for weapon {__instance.Name}"); - __result = true; - return; - } + actor.DeferUnit(); + actor.ForceUnitToLastActualPhase(); + __runOriginal = false; + return; } + } - if (targetActor.IsSwarmingUnit() || targetActor.IsMountedUnit()) - { - __result = false; - } - } - } - } - - [HarmonyPatch(typeof(Team), "DeferAllAvailableActors", - new Type[] { })] - public static class Team_DeferAllAvailableActors - { - static bool Prepare() => ModInit.modSettings.EnableQuickReserve; - - public static void Prefix(ref bool __runOriginal, Team __instance) - { - if (!__runOriginal) return; - var hk = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); - if (hk) - { - for (int i = 0; i < __instance.units.Count; i++) - { - if (__instance.units[i].Combat.TurnDirector.IsInterleaved && !__instance.units[i].IsDead && - !__instance.units[i].IsFlaggedForDeath && !__instance.units[i].HasActivatedThisRound && - !__instance.units[i].HasBegunActivation) - { - if (__instance.units[i].Initiative != __instance.units[i].Combat.TurnDirector.LastPhase) - { - __instance.units[i].DeferUnit(); - __instance.units[i].ForceUnitToLastActualPhase(); - } - } - } - - __runOriginal = false; - return; - } - - __runOriginal = true; - return; - } - } - - [HarmonyPatch(typeof(CombatHUDButtonBase), "OnClick", - new Type[] { })] - public static class CombatHUDButtonBase_OnClick - { - //static bool Prepare() => ModInit.modSettings.EnableQuickReserve; - public static void Prefix(ref bool __runOriginal, CombatHUDButtonBase __instance) - { - if (!__runOriginal) return; - if (__instance.GUID != "BTN_DoneWithMech") - { - __runOriginal = true; - return; - } - - var hud = __instance - .HUD; //IRBTModUtils.SharedState.CombatHUD;//Traverse.Create(__instance).Property("HUD").GetValue(); - var actor = hud.SelectedActor; - if (ModInit.modSettings.EnableQuickReserve) - { - var hk = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); - if (hk && actor.CanDeferUnit) - { - actor.DeferUnit(); - actor.ForceUnitToLastActualPhase(); - __runOriginal = false; - return; - } - } - - if (!actor.IsSwarmingUnit()) - { - ModInit.modLog?.Debug?.Write( - $"[CombatHUDButtonBase.OnClick] Actor {actor.DisplayName} is not swarming, ending turn like normal."); - __runOriginal = true; - return; + if (!actor.IsSwarmingUnit()) + { + ModInit.modLog?.Debug?.Write( + $"[CombatHUDButtonBase.OnClick] Actor {actor.DisplayName} is not swarming, ending turn like normal."); + __runOriginal = true; + return; } if (actor.GetAbilityUsedFiring()) @@ -1104,305 +744,376 @@ public static void Prefix(ref bool __runOriginal, CombatHUDButtonBase __instance } } - - [HarmonyPatch(typeof(SelectionStateFire), "ProcessClickedCombatant", - new Type[] {typeof(ICombatant)})] - public static class SelectionStateFire_ProcessClickedCombatant + [HarmonyPatch(typeof(CombatHUDEquipmentSlot), "ActivateAbility", + new Type[] {typeof(string), typeof(string)})] + public static class CombatHUDEquipmentSlot_ConfirmAbility { - static bool Prepare() => false; //disable for now, try with force-end turn. - - public static void Postfix(SelectionStateFire __instance, ref ICombatant combatant) + public static void Postfix(CombatHUDEquipmentSlot __instance, string creatorGUID, string targetGUID) { - if (__instance.SelectedActor.IsSwarmingUnit()) + var HUD = __instance.HUD; //Traverse.Create(__instance).Property("HUD").GetValue(); + var theActor = HUD.SelectedActor; + if (theActor == null) return; + if (__instance.Ability == null || + __instance.Ability?.Def?.Id != ModInit.modSettings.BattleArmorMountAndSwarmID) return; + if (theActor.IsGarrisoned()) { - var newTarget = - __instance.SelectedActor.Combat.FindActorByGUID( - ModState.PositionLockSwarm[__instance.SelectedActor.GUID]); - combatant = newTarget; + __instance.Text.SetText("DISMOUNT GARRISON", Array.Empty()); + } + + if (theActor.IsMountedUnit()) + { + __instance.Text.SetText("DISMOUNT BATTLEARMOR", Array.Empty()); + } + else if (theActor.IsSwarmingUnit()) + { + __instance.Text.SetText("HALT SWARM ATTACK", Array.Empty()); + } + else + { + __instance.Text.SetText(__instance.Ability.Def?.Description.Name); } } } - [HarmonyPatch(typeof(Mech), "OnLocationDestroyed", - new Type[] {typeof(ChassisLocations), typeof(Vector3), typeof(WeaponHitInfo), typeof(DamageType)})] - public static class Mech_OnLocationDestroyed + [HarmonyPatch(typeof(CombatHUDEquipmentSlot), "InitButton", + new Type[] + { + typeof(SelectionType), typeof(Ability), typeof(SVGAsset), typeof(string), typeof(string), + typeof(AbstractActor) + })] + public static class CombatHUDEquipmentSlot_InitButton { - public static void Prefix(Mech __instance, ChassisLocations location, Vector3 attackDirection, - WeaponHitInfo hitInfo, DamageType damageType) + public static void Postfix(CombatHUDEquipmentSlot __instance, SelectionType SelectionType, Ability Ability, + SVGAsset Icon, string GUID, string Tooltip, AbstractActor actor) { - if (!__instance.HasMountedUnits() && !__instance.HasSwarmingUnits()) return; + if (actor == null) return; + if (Ability == null || Ability.Def?.Id != ModInit.modSettings.BattleArmorMountAndSwarmID) return; + if (actor.IsGarrisoned()) + { + __instance.Text.SetText("DISMOUNT GARRISON", Array.Empty()); + } + else if (actor.IsMountedUnit()) + { + __instance.Text.SetText("DISMOUNT BATTLEARMOR", Array.Empty()); + } + else if (actor.IsSwarmingUnit()) + { + __instance.Text.SetText("HALT SWARM ATTACK", Array.Empty()); + } + } + } + [HarmonyPatch(typeof(CombatHUDMechTrayArmorHover), "setToolTipInfo", + new Type[] {typeof(Mech), typeof(ArmorLocation)})] + public static class CombatHUDMechTrayArmorHover_setToolTipInfo + { + public static void Postfix(CombatHUDMechTrayArmorHover __instance, Mech mech, ArmorLocation location) + { + if (!mech.HasSwarmingUnits() && !mech.HasMountedUnits()) return; + var tooltip = + __instance + .ToolTip; //Traverse.Create(__instance).Property("ToolTip").GetValue(); foreach (var squadInfo in ModState.BADamageTrackers.Where(x => - x.Value.TargetGUID == __instance.GUID && !x.Value.IsSquadInternal && + x.Value.TargetGUID == mech.GUID && !x.Value.IsSquadInternal && x.Value.BA_MountedLocations.ContainsValue((int) location))) { - var wereSwarmingUnitsResponsible = squadInfo.Key == hitInfo.attackerId; - ModInit.modLog?.Trace?.Write( - $"[Mech.OnLocationDestroyed] Evaluating {squadInfo.Key} for {squadInfo.Value.TargetGUID}"); - if (ModInit.Random.NextDouble() >= (double) 1 / 3 || wereSwarmingUnitsResponsible) continue; - if (__instance.Combat.FindActorByGUID(squadInfo.Key) is Mech battleArmorAsMech) + $"[CombatHUDMechTrayArmorHover.setToolTipInfo] Evaluating {squadInfo.Key} for {squadInfo.Value.TargetGUID} for tooltip infos"); + + if (mech.Combat.FindActorByGUID(squadInfo.Key) is Mech BattleArmorAsMech) { - var battleArmorMounts = + var BattleArmorMounts = squadInfo.Value.BA_MountedLocations.Where(x => x.Value == (int) location); - var battleArmorMountsCopy = new List>(battleArmorMounts); - foreach (var mount in battleArmorMountsCopy) + foreach (var mount in BattleArmorMounts) { + var BALocArmor = (ArmorLocation) mount.Key; + //var BALocArmorString = BattleArmorAsMech.GetStringForArmorLocation(BALocArmor); var BALocStruct = MechStructureRules.GetChassisLocationFromArmorLocation(BALocArmor); - battleArmorAsMech.NukeStructureLocation(hitInfo, 1, BALocStruct, attackDirection, - damageType); + //var BALocStructString = BattleArmorAsMech.GetStringForStructureLocation(BALocStruct); + + var BattleArmorLocArmor = BattleArmorAsMech.ArmorForLocation((int) BALocArmor); + var BattleArmorLocStruct = BattleArmorAsMech.StructureForLocation((int) BALocStruct); + var newText = + new Localize.Text( + $"Battle Armor: Arm. {Mathf.RoundToInt(BattleArmorLocArmor)} / Str. {Mathf.RoundToInt(BattleArmorLocStruct)}", + Array.Empty()); + if (mech.team.IsFriendly(BattleArmorAsMech.team)) + { + tooltip.BuffStrings.Add(newText); + } + else + { + tooltip.DebuffStrings.Add(newText); + } } - //battleArmorAsMech.DismountBA(__instance, Vector3.zero, false, true, true); - //battleArmorAsMech.FlagForDeath("Killed When Mount Died", DeathMethod.VitalComponentDestroyed, DamageType.Melee, 0, -1, __instance.GUID, false); - //battleArmorAsMech.HandleDeath(__instance.GUID); //this is probably wrong but i dont care } } } } - [HarmonyPatch(typeof(BattleTech.Building), "FlagForDeath", - new Type[] - { - typeof(string), typeof(DeathMethod), typeof(DamageType), typeof(int), typeof(int), typeof(string), - typeof(bool) - })] - public static class Building_FlagForDeath + [HarmonyPatch(typeof(CombatHUDMechwarriorTray), "ResetMechwarriorButtons", + new Type[] {typeof(AbstractActor)})] + public static class CombatHUDMechwarriorTray_ResetMechwarriorButtons { - public static void Prefix(BattleTech.Building __instance, string reason, DeathMethod deathMethod, - DamageType damageType, int location, int stackItemID, string attackerID, bool isSilent) + [HarmonyPriority(Priority.Last)] + public static void Postfix(CombatHUDMechwarriorTray __instance, AbstractActor actor) { - if (__instance.IsFlaggedForDeath) return; - ModInit.modLog?.Trace?.Write( - $"[Building.FlagForDeath] Building {__instance.DisplayName} {__instance.GUID} at position {__instance.CurrentPosition} dieded."); - if (!__instance.HasGarrisonedUnits()) return; - var garrisons = new List>(ModState.PositionLockGarrison - .Where(x => x.Value.BuildingGUID == __instance.GUID).ToList()); - foreach (var garrison in garrisons) + if (UnityGameInstance.BattleTechGame.Combat.ActiveContract.ContractTypeValue.IsSkirmish) return; + if (actor == null) return; + + var moraleButtons = + __instance + .MoraleButtons; //Traverse.Create(__instance).Property("MoraleButtons").GetValue(); + var abilityButtons = + __instance + .AbilityButtons; //Traverse.Create(__instance).Property("AbilityButtons").GetValue(); + + if (actor.IsAirlifted()) { ModInit.modLog?.Trace?.Write( - $"[Building.FlagForDeath] Building {__instance.DisplayName} {__instance.GUID} dieded, has units mounted."); + $"[CombatHUDMechwarriorTray.ResetMechwarriorButtons] Actor {actor.DisplayName} {actor.GUID} is Airlifted. Disabling movement buttons."); + __instance.MoveButton.DisableButton(); + __instance.SprintButton.DisableButton(); + __instance.JumpButton.DisableButton(); - var actor = __instance.Combat.FindActorByGUID(garrison.Key); - if (actor is TrooperSquad squad) + if (ModState.AirliftTrackers[actor.GUID].IsCarriedInternal) { - foreach (var garrisonEffect in ModState.OnGarrisonCollapseEffects) + foreach (var moraleButton in moraleButtons) { - if (garrisonEffect.TargetEffectType == Classes.ConfigOptions.BA_TargetEffectType.GARRISON) - { - foreach (var effectData in garrisonEffect.effects) - { - //squad.Combat.EffectManager.CreateEffect(effectData,effectData.Description.Id,-1, actor, actor, default(WeaponHitInfo), 1); - squad.CreateEffect(effectData, null, - effectData.Description.Id, - -1, squad); - } - } + moraleButton.DisableButton(); } - squad.DismountGarrison(__instance, Vector3.zero, true); - - - var dmg = squad.StatCollection.GetValue("DFASelfDamage"); - var trooperLocs = squad.GetPossibleHitLocations(squad); - for (int i = 0; i < trooperLocs.Count; i++) - { - var hitinfo = new WeaponHitInfo(-1, -1, 0, 0, squad.GUID, - squad.GUID, 1, new float[1], new float[1], new float[1], - new bool[1], new int[trooperLocs[i]], new int[1], new AttackImpactQuality[1], - new AttackDirection[1], new Vector3[1], new string[1], new int[trooperLocs[i]]); - - squad.TakeWeaponDamage(hitinfo, trooperLocs[i], - squad.MeleeWeapon, dmg, - 0, 0, DamageType.DFASelf); - } + __instance.FireButton.DisableButton(); } } - } - } - [HarmonyPatch(typeof(AbstractActor), "HandleDeath", - new Type[] {typeof(string)})] - public static class AbstractActor_HandleDeath - { - public static void Prefix(AbstractActor __instance, string attackerGUID) - { - if (__instance.IsSwarmingUnit()) + if (actor.IsGarrisoned()) { - var carrier = __instance.Combat.FindActorByGUID(ModState.PositionLockSwarm[__instance.GUID]); - __instance.DismountBA(carrier, Vector3.zero, false, true); + ModInit.modLog?.Trace?.Write( + $"[CombatHUDMechwarriorTray.ResetMechwarriorButtons] Actor {actor.DisplayName} {actor.GUID} found in garrison. Disabling buttons."); + + __instance.MoveButton.DisableButton(); + __instance.SprintButton.DisableButton(); + __instance.JumpButton.DisableButton(); } - if (__instance.IsMountedUnit()) + else if (actor.IsMountedUnit()) { - var carrier = __instance.Combat.FindActorByGUID(ModState.PositionLockMount[__instance.GUID]); - __instance.DismountBA(carrier, Vector3.zero, true, true); - } + ModInit.modLog?.Trace?.Write( + $"[CombatHUDMechwarriorTray.ResetMechwarriorButtons] Actor {actor.DisplayName} {actor.GUID} found in PositionLockMount. Disabling buttons."); + var carrier = actor.Combat.FindActorByGUID(ModState.PositionLockMount[actor.GUID]); - var dismount = false || (__instance.DeathMethod == DeathMethod.PilotEjection || - __instance.DeathMethod == DeathMethod.PilotEjectionActorDisabled || - __instance.DeathMethod == DeathMethod.PilotEjectionNoMessage || - __instance.DeathMethod == DeathMethod.DespawnedNoMessage || - __instance.DeathMethod == DeathMethod.DespawnedEscaped); + __instance.MoveButton.DisableButton(); + __instance.SprintButton.DisableButton(); + __instance.JumpButton.DisableButton(); - if (__instance.HasSwarmingUnits()) - { - var swarmingUnits = new List>(ModState.PositionLockSwarm - .Where(x => x.Value == __instance.GUID).ToList()); - var wereSwarmingUnitsResponsible = swarmingUnits.Any(x => x.Key == attackerGUID); - foreach (var swarmingUnit in swarmingUnits) + if (!actor.IsMountedInternal() || !carrier.HasFiringPorts()) { - var actor = __instance.Combat.FindActorByGUID(swarmingUnit.Key); - var squad = actor as TrooperSquad; - if (ModInit.Random.NextDouble() <= (double) 1 / 3 && !wereSwarmingUnitsResponsible && !dismount) + __instance.FireButton.DisableButton(); + foreach (var moraleButton in moraleButtons) { - var trooperLocs = squad.GetPossibleHitLocations(__instance); - for (int i = 0; i < trooperLocs.Count; i++) - { - var cLoc = (ChassisLocations) trooperLocs[i]; - var hitinfo = new WeaponHitInfo(-1, -1, 0, 0, __instance.GUID, squad.GUID, 1, - new float[1], new float[1], new float[1], new bool[1], new int[trooperLocs[i]], - new int[1], new AttackImpactQuality[1], new AttackDirection[1], new Vector3[1], - new string[1], new int[trooperLocs[i]]); - squad.NukeStructureLocation(hitinfo, trooperLocs[i], cLoc, Vector3.up, - DamageType.ComponentExplosion); - } - - actor.DismountBA(__instance, Vector3.zero, false, true); - actor.FlagForDeath("Killed When Mount Died", DeathMethod.VitalComponentDestroyed, - DamageType.Melee, 0, -1, __instance.GUID, false); - actor.HandleDeath(__instance.GUID); - continue; + moraleButton.DisableButton(); } - ModInit.modLog?.Trace?.Write( - $"[AbstractActor.HandleDeath] Swarmed unit {__instance.DisplayName} destroyed, calling dismount."); - actor.DismountBA(__instance, Vector3.zero, false, true); - } - } - - if (__instance.HasMountedUnits()) - { - var mountedUnits = new List>(ModState.PositionLockMount - .Where(x => x.Value == __instance.GUID).ToList()); - foreach (var mountedUnit in mountedUnits) - { - var actor = __instance.Combat.FindActorByGUID(mountedUnit.Key); - var squad = actor as TrooperSquad; - if (ModInit.Random.NextDouble() <= (double) 1 / 3 && !dismount) + foreach (var abilityButton in abilityButtons) { - var trooperLocs = squad.GetPossibleHitLocations(__instance); - for (int i = 0; i < trooperLocs.Count; i++) - { - var cLoc = (ChassisLocations) trooperLocs[i]; - var hitinfo = new WeaponHitInfo(-1, -1, 0, 0, __instance.GUID, squad.GUID, 1, - new float[1], new float[1], new float[1], new bool[1], new int[trooperLocs[i]], - new int[1], new AttackImpactQuality[1], new AttackDirection[1], new Vector3[1], - new string[1], new int[trooperLocs[i]]); - squad.NukeStructureLocation(hitinfo, trooperLocs[i], cLoc, Vector3.up, - DamageType.ComponentExplosion); - } - - actor.DismountBA(__instance, Vector3.zero, true, true); - actor.FlagForDeath("Killed When Mount Died", DeathMethod.VitalComponentDestroyed, - DamageType.Melee, 0, -1, __instance.GUID, false); - actor.HandleDeath(__instance.GUID); - continue; + if (abilityButton?.Ability?.Def?.Id == ModInit.modSettings.BattleArmorMountAndSwarmID) + abilityButton?.DisableButton(); } - - ModInit.modLog?.Trace?.Write( - $"[AbstractActor.HandleDeath] Mount {__instance.DisplayName} destroyed, calling dismount."); - actor.DismountBA(__instance, Vector3.zero, true, true); } } - - if (__instance.HasAirliftedUnits()) + else if (actor.IsSwarmingUnit()) { - var airliftedUnits = - new List>( - ModState.AirliftTrackers.Where(x => x.Value.CarrierGUID == __instance.GUID)).ToList(); + ModInit.modLog?.Trace?.Write( + $"[CombatHUDMechwarriorTray.ResetMechwarriorButtons] Actor {actor.DisplayName} {actor.GUID} found in PositionLockSwarm. Disabling buttons."); + __instance.FireButton.DisableButton(); + __instance.MoveButton.DisableButton(); + __instance.SprintButton.DisableButton(); + __instance.JumpButton.DisableButton(); - foreach (var unitTracker in airliftedUnits) + foreach (var moraleButton in moraleButtons) { - var actor = __instance.Combat.FindActorByGUID(unitTracker.Key); - ModInit.modLog?.Trace?.Write( - $"[AbstractActor.HandleDeath] Airlift carrier {__instance.DisplayName} destroyed, calling dismount for {actor.DisplayName}."); - __instance.DropAirliftedUnit(actor, Vector3.zero, false, false, true); + moraleButton.DisableButton(); + } + + foreach (var abilityButton in abilityButtons) + { + if (abilityButton?.Ability?.Def?.Id == ModInit.modSettings.BattleArmorMountAndSwarmID) + abilityButton?.DisableButton(); } } } } - [HarmonyPatch(typeof(CombatHUDEquipmentSlot), "InitButton", - new Type[] - { - typeof(SelectionType), typeof(Ability), typeof(SVGAsset), typeof(string), typeof(string), - typeof(AbstractActor) - })] - public static class CombatHUDEquipmentSlot_InitButton + [HarmonyPatch(typeof(CombatHUDVehicleArmorHover), "setToolTipInfo", + new Type[] {typeof(Vehicle), typeof(VehicleChassisLocations)})] + public static class CombatHUDVehicleArmorHover_setToolTipInfo { - public static void Postfix(CombatHUDEquipmentSlot __instance, SelectionType SelectionType, Ability Ability, - SVGAsset Icon, string GUID, string Tooltip, AbstractActor actor) + public static void Postfix(CombatHUDVehicleArmorHover __instance, Vehicle vehicle, + VehicleChassisLocations location) { - if (actor == null) return; - if (Ability == null || Ability.Def?.Id != ModInit.modSettings.BattleArmorMountAndSwarmID) return; - if (actor.IsGarrisoned()) - { - __instance.Text.SetText("DISMOUNT GARRISON", Array.Empty()); - } - else if (actor.IsMountedUnit()) - { - __instance.Text.SetText("DISMOUNT BATTLEARMOR", Array.Empty()); - } - else if (actor.IsSwarmingUnit()) + if (!vehicle.HasSwarmingUnits() && !vehicle.HasMountedUnits()) return; + var tooltip = + __instance + .ToolTip; //Traverse.Create(__instance).Property("ToolTip").GetValue(); + foreach (var squadInfo in ModState.BADamageTrackers.Where(x => + x.Value.TargetGUID == vehicle.GUID && !x.Value.IsSquadInternal && + x.Value.BA_MountedLocations.ContainsValue((int) location))) { - __instance.Text.SetText("HALT SWARM ATTACK", Array.Empty()); + ModInit.modLog?.Trace?.Write( + $"[CombatHUDMechTrayArmorHover.setToolTipInfo] Evaluating {squadInfo.Key} for {squadInfo.Value.TargetGUID} for tooltip infos"); + + if (vehicle.Combat.FindActorByGUID(squadInfo.Key) is Mech BattleArmorAsMech) + { + var BattleArmorMounts = + squadInfo.Value.BA_MountedLocations.Where(x => x.Value == (int) location); + foreach (var mount in BattleArmorMounts) + { + + var BALocArmor = (VehicleChassisLocations) mount.Key; + //var BALocArmorString = BattleArmorAsMech.GetStringForArmorLocation(BALocArmor); + //var BALocStructString = BattleArmorAsMech.GetStringForStructureLocation(BALocStruct); + + var BattleArmorLocArmor = BattleArmorAsMech.ArmorForLocation((int) BALocArmor); + var BattleArmorLocStruct = BattleArmorAsMech.StructureForLocation((int) BALocArmor); + var newText = + new Localize.Text( + $"Battle Armor: Arm. {Mathf.RoundToInt(BattleArmorLocArmor)} / Str. {Mathf.RoundToInt(BattleArmorLocStruct)}", + Array.Empty()); + if (vehicle.team.IsFriendly(BattleArmorAsMech.team)) + { + tooltip.BuffStrings.Add(newText); + } + else + { + tooltip.DebuffStrings.Add(newText); + } + } + } } } } - [HarmonyPatch(typeof(CombatHUDEquipmentSlot), "ActivateAbility", - new Type[] {typeof(string), typeof(string)})] - public static class CombatHUDEquipmentSlot_ConfirmAbility + [HarmonyPatch(typeof(CombatSelectionHandler), "AddFireState", + new Type[] { typeof(AbstractActor) })] + public static class CombatSelectionHandler_AddFireState { - public static void Postfix(CombatHUDEquipmentSlot __instance, string creatorGUID, string targetGUID) + public static void Prefix(ref bool __runOriginal, CombatSelectionHandler __instance, AbstractActor actor) { - var HUD = __instance.HUD; //Traverse.Create(__instance).Property("HUD").GetValue(); - var theActor = HUD.SelectedActor; - if (theActor == null) return; - if (__instance.Ability == null || - __instance.Ability?.Def?.Id != ModInit.modSettings.BattleArmorMountAndSwarmID) return; - if (theActor.IsGarrisoned()) - { - __instance.Text.SetText("DISMOUNT GARRISON", Array.Empty()); - } - - if (theActor.IsMountedUnit()) - { - __instance.Text.SetText("DISMOUNT BATTLEARMOR", Array.Empty()); - } - else if (theActor.IsSwarmingUnit()) + if (!__runOriginal) return; + var mountedNoPorts = false; + if (actor.IsMountedUnit()) { - __instance.Text.SetText("HALT SWARM ATTACK", Array.Empty()); + var carrier = actor.Combat.FindActorByGUID(ModState.PositionLockMount[actor.GUID]); + if (!carrier.HasFiringPorts()) mountedNoPorts = true; } - else + if (actor.IsSwarmingUnit() || mountedNoPorts) { - __instance.Text.SetText(__instance.Ability.Def?.Description.Name); + ModInit.modLog?.Trace?.Write( + $"[CombatSelectionHandler.AddFireState] Actor {actor.DisplayName}: Disabling AddFireState"); + var selectionStack = + __instance + .SelectionStack; //Traverse.Create(__instance).Property("SelectionStack").GetValue>(); + if (!selectionStack.Any(x => x is SelectionStateDoneWithMech)) + { + var HUD = __instance + .HUD; //IRBTModUtils.SharedState.CombatHUD;//Traverse.Create(__instance).Property("HUD").GetValue(); + var doneState = new SelectionStateDoneWithMech(actor.Combat, HUD, + HUD.MechWarriorTray.DoneWithMechButton, actor); + //var addState = Traverse.Create(__instance).Method("addNewState", new Type[] { typeof(SelectionState) }); + //addState.GetValue(doneState); + __instance.addNewState(doneState); + } + + __runOriginal = false; + return; } + + __runOriginal = true; + return; } } - [HarmonyPatch(typeof(CombatSelectionHandler), "TrySelectActor", - new Type[] {typeof(AbstractActor), typeof(bool)})] - public static class CombatSelectionHandler_TrySelectActor + [HarmonyPatch(typeof(CombatSelectionHandler), "AddMoveState", + new Type[] {typeof(AbstractActor)})] + public static class CombatSelectionHandler_AddMoveState { - public static void Prefix(ref bool __runOriginal, CombatSelectionHandler __instance, AbstractActor actor, bool manualSelection, ref bool __result) + public static void Prefix(ref bool __runOriginal, CombatSelectionHandler __instance, AbstractActor actor) { if (!__runOriginal) return; - if (actor == null) + if (actor.IsSwarmingUnit() || actor.IsMountedUnit() || actor.IsAirlifted() || actor.IsGarrisoned()) { - __runOriginal = true; - return; + ModInit.modLog?.Trace?.Write( + $"[CombatSelectionHandler.AddMoveState] Actor {actor.DisplayName}: Disabling AddMoveState"); + var SelectionStack = + __instance + .SelectionStack; //Traverse.Create(__instance).Property("SelectionStack").GetValue>(); + if (!SelectionStack.Any(x => x is SelectionStateDoneWithMech)) + { + var HUD = __instance + .HUD; //IRBTModUtils.SharedState.CombatHUD;//Traverse.Create(__instance).Property("HUD").GetValue(); + var doneState = new SelectionStateDoneWithMech(actor.Combat, HUD, + HUD.MechWarriorTray.DoneWithMechButton, actor); + //var addState = Traverse.Create(__instance).Method("addNewState", new Type[] { typeof(SelectionState) }); + //addState.GetValue(doneState); + __instance.addNewState(doneState); + } + + __runOriginal = false; + return; + } + + __runOriginal = true; + return; + } + } + + [HarmonyPatch(typeof(CombatSelectionHandler), "AddSprintState", + new Type[] {typeof(AbstractActor)})] + public static class CombatSelectionHandler_AddSprintState + { + public static void Prefix(ref bool __runOriginal, CombatSelectionHandler __instance, AbstractActor actor) + { + if (!__runOriginal) return; + if (actor.IsMountedUnit() || actor.IsSwarmingUnit() || actor.IsAirlifted() || actor.IsGarrisoned()) + { + ModInit.modLog?.Trace?.Write( + $"[CombatSelectionHandler.AddSprintState] Actor {actor.DisplayName}: Disabling SprintState"); + var SelectionStack = + __instance + .SelectionStack; //Traverse.Create(__instance).Property("SelectionStack").GetValue>(); + if (!SelectionStack.Any(x => x is SelectionStateDoneWithMech)) + { + var HUD = __instance + .HUD; //IRBTModUtils.SharedState.CombatHUD;//Traverse.Create(__instance).Property("HUD").GetValue(); + var doneState = new SelectionStateDoneWithMech(actor.Combat, HUD, + HUD.MechWarriorTray.DoneWithMechButton, actor); + //var addState = Traverse.Create(__instance).Method("addNewState", new Type[] {typeof(SelectionState)}); + //addState.GetValue(doneState); + __instance.addNewState(doneState); + } + + __runOriginal = false; + return; + } + + __runOriginal = true; + return; + } + } + + [HarmonyPatch(typeof(CombatSelectionHandler), "TrySelectActor", + new Type[] {typeof(AbstractActor), typeof(bool)})] + public static class CombatSelectionHandler_TrySelectActor + { + public static void Prefix(ref bool __runOriginal, CombatSelectionHandler __instance, AbstractActor actor, bool manualSelection, ref bool __result) + { + if (!__runOriginal) return; + if (actor == null) + { + __runOriginal = true; + return; } if (!actor.Combat.LocalPlayerTeam.IsActive) @@ -1504,369 +1215,951 @@ public static void Prefix(ref bool __runOriginal, CombatSelectionHandler __insta } } - [HarmonyPatch(typeof(AbstractActor), "CanMoveAfterShooting", MethodType.Getter)] - public static class AbstractActor_CanMoveAfterShooting + [HarmonyPatch(typeof(LanceConfiguratorPanel), "OnButtonClicked", + new Type[] {typeof(IMechLabDraggableItem)})] + public static class LanceConfiguratorPanel_OnButtonClicked { - public static void Postfix(AbstractActor __instance, ref bool __result) - { - if (__instance.CanSwarm() || __instance.IsAirlifted()) - { - __result = false; - } - } - } + private static bool Prepare() => true; - [HarmonyPatch(typeof(CombatSelectionHandler), "AddSprintState", - new Type[] {typeof(AbstractActor)})] - public static class CombatSelectionHandler_AddSprintState - { - public static void Prefix(ref bool __runOriginal, CombatSelectionHandler __instance, AbstractActor actor) + public static void Postfix(LanceConfiguratorPanel __instance, IMechLabDraggableItem item) { - if (!__runOriginal) return; - if (actor.IsMountedUnit() || actor.IsSwarmingUnit() || actor.IsAirlifted() || actor.IsGarrisoned()) + var proc = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); + + if (proc) { - ModInit.modLog?.Trace?.Write( - $"[CombatSelectionHandler.AddSprintState] Actor {actor.DisplayName}: Disabling SprintState"); - var SelectionStack = - __instance - .SelectionStack; //Traverse.Create(__instance).Property("SelectionStack").GetValue>(); - if (!SelectionStack.Any(x => x is SelectionStateDoneWithMech)) + if (item.ItemType == MechLabDraggableItemType.Mech && + item.DropParent.dropTargetType == MechLabDropTargetType.LanceSlot) { - var HUD = __instance - .HUD; //IRBTModUtils.SharedState.CombatHUD;//Traverse.Create(__instance).Property("HUD").GetValue(); - var doneState = new SelectionStateDoneWithMech(actor.Combat, HUD, - HUD.MechWarriorTray.DoneWithMechButton, actor); - //var addState = Traverse.Create(__instance).Method("addNewState", new Type[] {typeof(SelectionState)}); - //addState.GetValue(doneState); - __instance.addNewState(doneState); - } + if (item is not LanceLoadoutMechItem lanceLoadoutMechItem) return; + var mechDef = lanceLoadoutMechItem.MechDef; + if (mechDef == null) return; - __runOriginal = false; - return; - } + foreach (var slot in __instance.loadoutSlots) + { + if (slot.SelectedMech == lanceLoadoutMechItem) + { + if (slot.SelectedPilot == null) + return; // need to have pilot assigned before we can assign BA-Mech Pairings. + var pilotID = slot.SelectedPilot.Pilot.pilotDef.Description.Id; + ModInit.modLog?.Trace?.Write($"[LanceConfiguratorPanel_OnButtonClicked] current pilotID {pilotID}"); + var unitCustomInfo = lanceLoadoutMechItem.MechDef.GetCustomInfo(); + if (unitCustomInfo is {SquadInfo.Troopers: > 1}) + { + if (ModState.PendingPairBAUnit == slot) + { + ModState.PendingPairBAUnit = null; + __instance.SetPairingOverlay(slot, false); + //lanceLoadoutMechItem.UnavailableOverlay.SetActive(false); + return; + } - __runOriginal = true; - return; + if (ModState.PendingPairBAUnit == null) + { + ModState.PendingPairBAUnit = slot; + __instance.SetPairingOverlay(slot, true); // maybe tweak unavailable overlay GO ccolors? + return; + } + } + + if (ModState.PendingPairBAUnit != null && ModState.PendingPairBAUnit != slot) + { + ModInit.modLog?.Trace?.Write( + $"[LanceConfiguratorPanel_OnButtonClicked] found pending BA {ModState.PendingPairBAUnit.SelectedMech.MechDef.Description.Id} and pilot {pilotID}with no pairing"); + var pendingBAPilot = ModState.PendingPairBAUnit.SelectedPilot.Pilot.pilotDef + .Description.Id; + if (!ModState.PairingInfos.ContainsKey(pilotID)) + { + var totalBASpace = mechDef.GetTotalBASpaceMechDef(); + if (totalBASpace > 0) + { + ModState.PairingInfos.Add(pilotID, + new Classes.BAPairingInfo(totalBASpace, pendingBAPilot)); + __instance.SetPairingOverlay(ModState.PendingPairBAUnit, true, slot); + //ModState.PendingPairBAUnit.SelectedMech.UnavailableOverlay.SetActive(false); // maybe tweak unavailable overlay GO ccolors? + ModState.PendingPairBAUnit = null; + ModInit.modLog?.Trace?.Write( + $"[LanceConfiguratorPanel_OnButtonClicked] Added new BA Pairing info. {mechDef.Description.Id} and {pilotID} paired with {string.Join(", ", ModState.PairingInfos[pilotID].PairedBattleArmor)}"); + //make a notification that pairing happened? bloop? + } + } + else + { + var pairInfo = ModState.PairingInfos[pilotID]; + if (pairInfo.PairedBattleArmor.Count < pairInfo.CapacityInitial) + { + pairInfo.PairedBattleArmor.Add(pendingBAPilot); + __instance.SetPairingOverlay(ModState.PendingPairBAUnit, true, slot); + //ModState.PendingPairBAUnit.SelectedMech.UnavailableOverlay.SetActive(false); // maybe tweak unavailable overlay GO ccolors? + ModState.PendingPairBAUnit = null; + ModInit.modLog?.Trace?.Write( + $"[LanceConfiguratorPanel_OnButtonClicked] Added BA Pairing to existing info for {pilotID}. {mechDef.Description.Id} and {pilotID} paired with {string.Join(", ", ModState.PairingInfos[pilotID].PairedBattleArmor)}"); + //make a notification that pairing happened? bloop? + } + } + return; + } + return; + } + } + } + } } } - [HarmonyPatch(typeof(CombatSelectionHandler), "AddMoveState", - new Type[] {typeof(AbstractActor)})] - public static class CombatSelectionHandler_AddMoveState + [HarmonyPatch(typeof(LanceConfiguratorPanel), "OnCancelClicked", + new Type[] {})] + public static class LanceConfiguratorPanel_OnCancelClicked { - public static void Prefix(ref bool __runOriginal, CombatSelectionHandler __instance, AbstractActor actor) + private static bool Prepare() => true; + + public static void Postfix(LanceConfiguratorPanel __instance) { - if (!__runOriginal) return; - if (actor.IsSwarmingUnit() || actor.IsMountedUnit() || actor.IsAirlifted() || actor.IsGarrisoned()) + if (__instance.IsSimGame) { - ModInit.modLog?.Trace?.Write( - $"[CombatSelectionHandler.AddMoveState] Actor {actor.DisplayName}: Disabling AddMoveState"); - var SelectionStack = - __instance - .SelectionStack; //Traverse.Create(__instance).Property("SelectionStack").GetValue>(); - if (!SelectionStack.Any(x => x is SelectionStateDoneWithMech)) - { - var HUD = __instance - .HUD; //IRBTModUtils.SharedState.CombatHUD;//Traverse.Create(__instance).Property("HUD").GetValue(); - var doneState = new SelectionStateDoneWithMech(actor.Combat, HUD, - HUD.MechWarriorTray.DoneWithMechButton, actor); - //var addState = Traverse.Create(__instance).Method("addNewState", new Type[] { typeof(SelectionState) }); - //addState.GetValue(doneState); - __instance.addNewState(doneState); - } - - __runOriginal = false; - return; + ModState.PairingInfos = new Dictionary(); + ModState.UsedOverlayColors = new List(); + ModState.UsedOverlayColorsByCarrier = new Dictionary(); } - - __runOriginal = true; - return; } } - [HarmonyPatch(typeof(CombatSelectionHandler), "AddFireState", - new Type[] { typeof(AbstractActor) })] - public static class CombatSelectionHandler_AddFireState + [HarmonyPatch(typeof(LanceLoadoutSlot), "OnRemoveItem", + new Type[] {typeof(IMechLabDraggableItem), typeof(bool) })] + public static class LanceLoadoutSlot_OnRemoveItem { - public static void Prefix(ref bool __runOriginal, CombatSelectionHandler __instance, AbstractActor actor) + private static bool Prepare() => true; + + public static void Prefix(ref bool __runOriginal, LanceLoadoutSlot __instance, IMechLabDraggableItem item, bool validate) { if (!__runOriginal) return; - var mountedNoPorts = false; - if (actor.IsMountedUnit()) - { - var carrier = actor.Combat.FindActorByGUID(ModState.PositionLockMount[actor.GUID]); - if (!carrier.HasFiringPorts()) mountedNoPorts = true; - } - if (actor.IsSwarmingUnit() || mountedNoPorts) + ModState.PendingPairBAUnit = null; + if (__instance.SelectedPilot != null) { - ModInit.modLog?.Trace?.Write( - $"[CombatSelectionHandler.AddFireState] Actor {actor.DisplayName}: Disabling AddFireState"); - var selectionStack = - __instance - .SelectionStack; //Traverse.Create(__instance).Property("SelectionStack").GetValue>(); - if (!selectionStack.Any(x => x is SelectionStateDoneWithMech)) + var pilotID = __instance.SelectedPilot.Pilot.pilotDef.Description.Id; + var toRemoveKeys = new List(); + if (ModState.PairingInfos.ContainsKey(pilotID)) { - var HUD = __instance - .HUD; //IRBTModUtils.SharedState.CombatHUD;//Traverse.Create(__instance).Property("HUD").GetValue(); - var doneState = new SelectionStateDoneWithMech(actor.Combat, HUD, - HUD.MechWarriorTray.DoneWithMechButton, actor); - //var addState = Traverse.Create(__instance).Method("addNewState", new Type[] { typeof(SelectionState) }); - //addState.GetValue(doneState); - __instance.addNewState(doneState); - } - - __runOriginal = false; - return; - } - - __runOriginal = true; - return; - } - } - - [HarmonyPatch(typeof(Mech), "DamageLocation", - new Type[] - { - typeof(int), typeof(WeaponHitInfo), typeof(ArmorLocation), typeof(Weapon), typeof(float), typeof(float), - typeof(int), typeof(AttackImpactQuality), typeof(DamageType) - })] - public static class Mech_DamageLocation_Patch - { - public static void Prefix(Mech __instance, int originalHitLoc, WeaponHitInfo hitInfo, ArmorLocation aLoc, - Weapon weapon, ref float totalArmorDamage, ref float directStructureDamage, int hitIndex, - AttackImpactQuality impactQuality, DamageType damageType) - { - if (__instance is TrooperSquad squad) - { - if (squad.IsGarrisoned()) - { - var bldgCombatant = - squad.Combat.FindCombatantByGUID(ModState.PositionLockGarrison[squad.GUID].BuildingGUID); - if (bldgCombatant is BattleTech.Building building) + var toRemove = new List(); + foreach (var pairedBA in ModState.PairingInfos[pilotID].PairedBattleArmor) { - ModInit.modLog?.Trace?.Write( - $"[Mech.DamageLocation] Redirecting {totalArmorDamage} armor and {directStructureDamage} structure damaghe from squad {squad.DisplayName} {squad.GUID} to garrisoned building {bldgCombatant.DisplayName}"); - building.TakeWeaponDamage(hitInfo, 1, weapon, totalArmorDamage, directStructureDamage, - hitIndex, damageType); - totalArmorDamage = 0f; - directStructureDamage = 0f; + toRemove.Add(pairedBA); } - } - } - - if (!__instance.HasMountedUnits() && !__instance.HasSwarmingUnits()) return; - - foreach (var squadInfo in ModState.BADamageTrackers.Where(x => - x.Value.TargetGUID == __instance.GUID && !x.Value.IsSquadInternal && - x.Value.BA_MountedLocations.ContainsValue((int) aLoc))) - { - ModInit.modLog?.Trace?.Write( - $"[Mech.DamageLocation] Evaluating {squadInfo.Key} for {squadInfo.Value.TargetGUID}"); - if (ModInit.Random.NextDouble() > (double) 1 / 3) continue; - if (__instance.Combat.FindActorByGUID(squadInfo.Key) is Mech BattleArmorAsMech) - { - if (BattleArmorAsMech.GUID == hitInfo.attackerId) continue; - var BattleArmorMounts = squadInfo.Value.BA_MountedLocations.Where(x => x.Value == (int) aLoc); - foreach (var mount in BattleArmorMounts) + ModState.PairingInfos.Remove(pilotID); + foreach (var slot in __instance.LC.loadoutSlots) { - var BALocArmor = (ArmorLocation) mount.Key; - //var BALocArmorString = BattleArmorAsMech.GetStringForArmorLocation(BALocArmor); - var BALocStruct = MechStructureRules.GetChassisLocationFromArmorLocation(BALocArmor); - //var BALocStructString = BattleArmorAsMech.GetStringForStructureLocation(BALocStruct); - - var BattleArmorLocArmor = BattleArmorAsMech.ArmorForLocation((int) BALocArmor); - var BattleArmorLocStruct = BattleArmorAsMech.StructureForLocation((int) BALocStruct); - - if (directStructureDamage > 0) + if (toRemove.Contains(slot?.SelectedPilot?.Pilot.pilotDef.Description.Id)) { - ModInit.modLog?.Info?.Write( - $"[Mech.DamageLocation] directStructureDamage: {directStructureDamage}"); - var directStructureDiff = directStructureDamage - BattleArmorLocStruct; - if (directStructureDiff >= 0) - { - directStructureDamage -= BattleArmorLocStruct; - ModInit.modLog?.Info?.Write( - $"[Mech.DamageLocation] directStructureDamage Diff: {directStructureDiff}. Mech directStructureDamage decremented to {directStructureDamage}"); - BattleArmorAsMech.TakeWeaponDamage(hitInfo, (int) BALocArmor, weapon, 0, - BattleArmorLocStruct, hitIndex, damageType); - ModInit.modLog?.Info?.Write( - $"[Mech.DamageLocation] Battle Armor at location {BALocArmor} takes {BattleArmorLocStruct} direct structure damage"); - __instance.Combat.MessageCenter.PublishMessage( - new AddSequenceToStackMessage(new ShowActorInfoSequence(BattleArmorAsMech, - Strings.T("Battle Armor Damaged!"), - FloatieMessage.MessageNature.CriticalHit, false))); - continue; - } - - else if (directStructureDiff < 0) + var overlayChildren = slot?.SelectedMech?.UnavailableOverlay.gameObject.GetComponentsInChildren(); + if (overlayChildren == null) continue; + foreach (var overlayChild in overlayChildren) { - ModInit.modLog?.Info?.Write( - $"[Mech.DamageLocation] directStructureDamage Diff: {directStructureDiff}. Mech directStructureDamage decremented to 0"); - BattleArmorAsMech.TakeWeaponDamage(hitInfo, (int) BALocArmor, weapon, 0, - Mathf.Abs(directStructureDamage), hitIndex, damageType); - ModInit.modLog?.Info?.Write( - $"[Mech.DamageLocation] Battle Armor at location {BALocArmor} takes {directStructureDamage} direct structure damage"); - __instance.Combat.MessageCenter.PublishMessage( - new AddSequenceToStackMessage(new ShowActorInfoSequence(BattleArmorAsMech, - Strings.T("Battle Armor Damaged!"), - FloatieMessage.MessageNature.CriticalHit, false))); - directStructureDamage = 0; + if (overlayChild.name != "stripes") continue; + var overlayChildImage = overlayChild.GetComponent(); + if (overlayChildImage.color != ModState.DefaultOverlay && + slot.SelectedMech.UnavailableOverlay.activeSelf) + { + overlayChildImage.color = ModState.PendingSelectionColor;//new Color(0, 0, 0, ModState.DefaultOverlay.a); + slot.SelectedMech.UnavailableOverlay.SetActive(false); + } } } + } + } - if (totalArmorDamage > 0) + else + { + foreach (var pairedKvP in ModState.PairingInfos) + { + if (pairedKvP.Value.PairedBattleArmor.Contains(pilotID)) { - ModInit.modLog?.Info?.Write( - $"[Mech.DamageLocation] totalArmorDamage: {totalArmorDamage}"); - var totalArmorDamageDiff = - totalArmorDamage - (BattleArmorLocArmor + BattleArmorLocStruct); - if (totalArmorDamageDiff > 0) + var carrierID = pairedKvP.Key; + pairedKvP.Value.PairedBattleArmor.Remove(pilotID); + if (pairedKvP.Value.PairedBattleArmor.Count == 0) { - totalArmorDamage -= totalArmorDamageDiff; - ModInit.modLog?.Info?.Write( - $"[Mech.DamageLocation] totalArmorDamageDiff Diff: {totalArmorDamageDiff}. Mech totalArmorDamage decremented to {totalArmorDamage}"); - BattleArmorAsMech.TakeWeaponDamage(hitInfo, (int) BALocArmor, weapon, - Mathf.Abs(totalArmorDamageDiff), 0, hitIndex, damageType); - ModInit.modLog?.Info?.Write( - $"[Mech.DamageLocation] Battle Armor at location {BALocArmor} takes {BattleArmorLocArmor} damage"); - __instance.Combat.MessageCenter.PublishMessage( - new AddSequenceToStackMessage(new ShowActorInfoSequence(BattleArmorAsMech, - Strings.T("Battle Armor Damaged!"), - FloatieMessage.MessageNature.CriticalHit, false))); + toRemoveKeys.Add(carrierID); } - - else if (totalArmorDamageDiff <= 0) + foreach (var slot in __instance.LC.loadoutSlots) { - ModInit.modLog?.Info?.Write( - $"[Mech.DamageLocation] totalArmorDamageDiff Diff: {totalArmorDamageDiff}. Mech totalArmorDamage decremented to 0"); - BattleArmorAsMech.TakeWeaponDamage(hitInfo, (int) BALocArmor, weapon, - Mathf.Abs(totalArmorDamage), 0, hitIndex, damageType); - ModInit.modLog?.Info?.Write( - $"[Mech.DamageLocation] Battle Armor at location {BALocArmor} takes {totalArmorDamage} damage"); - totalArmorDamage = 0; - __instance.Combat.MessageCenter.PublishMessage( - new AddSequenceToStackMessage(new ShowActorInfoSequence(BattleArmorAsMech, - Strings.T("Battle Armor Damaged!"), - FloatieMessage.MessageNature.CriticalHit, false))); + if (pilotID == slot?.SelectedPilot?.Pilot.pilotDef.Description.Id || toRemoveKeys.Contains(slot?.SelectedPilot?.Pilot.pilotDef.Description.Id)) + { + var overlayChildren = slot?.SelectedMech?.UnavailableOverlay.gameObject.GetComponentsInChildren(); + if (overlayChildren == null) continue; + foreach (var overlayChild in overlayChildren) + { + if (overlayChild.name != "stripes") continue; + var overlayChildImage = overlayChild.GetComponentInChildren(); + if (overlayChildImage.color != ModState.DefaultOverlay && + slot.SelectedMech.UnavailableOverlay.activeSelf) + { + overlayChildImage.color = ModState.PendingSelectionColor;//new Color(0, 0, 0, ModState.DefaultOverlay.a); + slot.SelectedMech.UnavailableOverlay.SetActive(false); + } + } + } } } } + + foreach (var toRemoveKey in toRemoveKeys) + { + ModState.PairingInfos.Remove(toRemoveKey); + } } + ModInit.modLog?.Trace?.Write($"[LanceLoadoutSlot_OnRemoveItem] Removed {pilotID}. Should have removed {string.Join(", ",toRemoveKeys)} from PairingInfo keys. Pairing info keys: {string.Join(", ", ModState.PairingInfos.Keys)}"); } + __instance.SelectedMech?.UnavailableOverlay.SetActive(false); } } - [HarmonyPatch(typeof(Vehicle), "DamageLocation", - new Type[] - { - typeof(WeaponHitInfo), typeof(int), typeof(VehicleChassisLocations), typeof(Weapon), typeof(float), - typeof(float), typeof(AttackImpactQuality) - })] - public static class Vehicle_DamageLocation_Patch + [HarmonyPatch(typeof(LineOfSight), "GetLineOfFireUncached")] + public static class LineOfSight_GetLineOfFireUncached { - public static void Prefix(Vehicle __instance, WeaponHitInfo hitInfo, int originalHitLoc, - VehicleChassisLocations vLoc, Weapon weapon, ref float totalArmorDamage, - ref float directStructureDamage, AttackImpactQuality impactQuality) + static bool Prepare() => !ModInit.modSettings.AllowIRBTUHandleVisibility; + + public static void Prefix(ref bool __runOriginal, LineOfSight __instance, AbstractActor source, + Vector3 sourcePosition, ICombatant target, Vector3 targetPosition, Quaternion targetRotation, + ref Vector3 collisionWorldPos, ref LineOfFireLevel __result) { - if (!__instance.HasMountedUnits() && !__instance.HasSwarmingUnits()) return; + if (!__runOriginal) return; + //collisionWorldPos = new Vector3(); + // if (target is BattleTech.Building building && !building.hasGarrisonedUnits()) return true; - foreach (var squadInfo in ModState.BADamageTrackers.Where(x => - x.Value.TargetGUID == __instance.GUID && !x.Value.IsSquadInternal && - x.Value.BA_MountedLocations.ContainsValue((int) vLoc))) + if (target is AbstractActor actorTarget) { - ModInit.modLog?.Trace?.Write( - $"[Vehicle.DamageLocation] Evaluating {squadInfo.Key} for {squadInfo.Value.TargetGUID}"); - if (ModInit.Random.NextDouble() > (double) 1 / 3) continue; - if (__instance.Combat.FindActorByGUID(squadInfo.Key) is Mech BattleArmorAsMech) + if (actorTarget.IsSwarmingUnit() || actorTarget.IsMountedUnit()) //|| actorTarget.isGarrisoned()) { - if (BattleArmorAsMech.GUID == hitInfo.attackerId) continue; - var BattleArmorMounts = squadInfo.Value.BA_MountedLocations.Where(x => x.Value == (int) vLoc); - foreach (var mount in BattleArmorMounts) - { - var BALocArmor = (ArmorLocation) mount.Key; - //var BALocArmorString = BattleArmorAsMech.GetStringForArmorLocation(BALocArmor); - var BALocStruct = MechStructureRules.GetChassisLocationFromArmorLocation(BALocArmor); - //var BALocStructString = BattleArmorAsMech.GetStringForStructureLocation(BALocStruct); - - var BattleArmorLocArmor = BattleArmorAsMech.ArmorForLocation((int) BALocArmor); - var BattleArmorLocStruct = BattleArmorAsMech.StructureForLocation((int) BALocStruct); + __result = LineOfFireLevel + .NotSet; // added 1/11 to block all LOF to swarming/mounted units. NotSet, or should it be LOS.Blocked? + ModInit.modLog?.Debug?.Write( + $"[LineOfSight_GetLineOfFireUncached] LOF for {source.DisplayName} and target {actorTarget.DisplayName} is {__result} due to target IsSwarm or IsMount"); + __runOriginal = false; + return; + } - if (directStructureDamage > 0) - { - ModInit.modLog?.Info?.Write( - $"[Vehicle.DamageLocation] directStructureDamage: {directStructureDamage}"); - var directStructureDiff = directStructureDamage - BattleArmorLocStruct; - if (directStructureDiff >= 0) - { - directStructureDamage -= BattleArmorLocStruct; - ModInit.modLog?.Info?.Write( - $"[Vehicle.DamageLocation] directStructureDamage Diff: {directStructureDiff}. Vehicle directStructureDamage decremented to {directStructureDamage}"); - BattleArmorAsMech.TakeWeaponDamage(hitInfo, (int) BALocArmor, weapon, 0, - BattleArmorLocStruct, 1, DamageType.Combat); - ModInit.modLog?.Info?.Write( - $"[Vehicle.DamageLocation] Battle Armor at location {BALocArmor} takes {BattleArmorLocStruct} direct structure damage"); - __instance.Combat.MessageCenter.PublishMessage( - new AddSequenceToStackMessage(new ShowActorInfoSequence(BattleArmorAsMech, - Strings.T("Battle Armor Damaged!"), - FloatieMessage.MessageNature.CriticalHit, false))); - continue; - } +// if (!actorTarget.HasSwarmingUnits() && !actorTarget.HasMountedUnits()) +// { +// return true; +// } + } - else if (directStructureDiff < 0) - { - ModInit.modLog?.Info?.Write( - $"[Vehicle.DamageLocation] directStructureDamage Diff: {directStructureDiff}. Vehicle directStructureDamage decremented to 0"); - BattleArmorAsMech.TakeWeaponDamage(hitInfo, (int) BALocArmor, weapon, 0, - Mathf.Abs(directStructureDamage), 1, DamageType.Combat); - ModInit.modLog?.Info?.Write( - $"[Vehicle.DamageLocation] Battle Armor at location {BALocArmor} takes {directStructureDamage} direct structure damage"); - directStructureDamage = 0; - __instance.Combat.MessageCenter.PublishMessage( - new AddSequenceToStackMessage(new ShowActorInfoSequence(BattleArmorAsMech, - Strings.T("Battle Armor Damaged!"), - FloatieMessage.MessageNature.CriticalHit, false))); + ModInit.modLog?.Debug?.Write( + $"[LineOfSight_GetLineOfFireUncached] Getting LOF for {source.DisplayName}"); + Vector3 forward = targetPosition - sourcePosition; + forward.y = 0f; + Quaternion rotation = Quaternion.LookRotation(forward); + Vector3[] lossourcePositions = source.GetLOSSourcePositions(sourcePosition, rotation); + Vector3[] lostargetPositions = target.GetLOSTargetPositions(targetPosition, targetRotation); + + + List list = new List(source.Combat.GetAllLivingActors()); + list.Remove(source); + + var unitGUIDs = new List(ModState.PositionLockSwarm.Keys); + unitGUIDs.AddRange(ModState.PositionLockMount.Keys); + unitGUIDs.AddRange(ModState.PositionLockGarrison.Keys); + foreach (var actorGUID in unitGUIDs) + { + list.Remove(source.Combat.FindActorByGUID(actorGUID)); + } + + AbstractActor actorTarget2 = target as AbstractActor; + string text = null; + if (actorTarget2 != null) + { + list.Remove(actorTarget2); + } + else + { + text = target.GUID; + } + + if (source.IsMountedUnit()) + { + var carrier = source.Combat.FindActorByGUID(ModState.PositionLockMount[source.GUID]); + if (carrier.HasFiringPorts()) + { + list.Remove( + carrier); // remove mound from LOS blocking (i have no idea if this will work or is even needed) + ModInit.modLog?.Debug?.Write( + $"[LineOfSight_GetLineOfFireUncached] remove {carrier.DisplayName}"); + } + } + + if (source.IsAirlifted()) + { + var airliftCarrier = + source.Combat.FindActorByGUID(ModState.AirliftTrackers[source.GUID].CarrierGUID); + list.Remove(airliftCarrier); + ModInit.modLog?.Debug?.Write( + $"[LineOfSight_GetLineOfFireUncached] remove {airliftCarrier.DisplayName}"); + } + + if (source.HasAirliftedUnits()) + { + var unitsBeingCarried = ModState.AirliftTrackers.Where(x => x.Value.CarrierGUID == source.GUID); + foreach (var unitBeingCarried in unitsBeingCarried) + { + var unit = source.Combat.FindActorByGUID(unitBeingCarried.Key); + list.Remove(unit); //most recent change? + ModInit.modLog?.Debug?.Write($"[LineOfSight_GetLineOfFireUncached] remove {unit.DisplayName}"); + } + } + + LineSegment lineSegment = new LineSegment(sourcePosition, targetPosition); + list.Sort((AbstractActor x, AbstractActor y) => Vector3.SqrMagnitude(x.CurrentPosition - sourcePosition) + .CompareTo(Vector3.SqrMagnitude(y.CurrentPosition - sourcePosition))); + float num = Vector3.SqrMagnitude(sourcePosition - targetPosition); + for (int i = list.Count - 1; i >= 0; i--) + { + if (list[i].IsDead || Vector3.SqrMagnitude(list[i].CurrentPosition - sourcePosition) > num || + lineSegment.DistToPoint(list[i].CurrentPosition) > list[i].Radius * 5f) + { + list.RemoveAt(i); + } + } + + float num2 = 0f; + float num3 = 0f; + float num4 = 0f; + if (collisionWorldPos == null) collisionWorldPos = new Vector3(); + collisionWorldPos = targetPosition; + float num5 = 999999.9f; + Weapon longestRangeWeapon = source.GetLongestRangeWeapon(false, false); + float num6 = (longestRangeWeapon == null) ? 0f : longestRangeWeapon.MaxRange; + float adjustedSpotterRange = source.Combat.LOS.GetAdjustedSpotterRange(source, actorTarget2); + num6 = Mathf.Max(num6, adjustedSpotterRange); + float num7 = Mathf.Pow(num6, 2f); + for (int j = 0; j < lossourcePositions.Length; j++) + { + for (int k = 0; k < lostargetPositions.Length; k++) + { + num3 += 1f; + if (Vector3.SqrMagnitude(lossourcePositions[j] - lostargetPositions[k]) <= num7) + { + lineSegment = new LineSegment(lossourcePositions[j], lostargetPositions[k]); + bool flag = false; + Vector3 vector; + if (text == null) + { + for (int l = 0; l < list.Count; l++) + { + if (lineSegment.DistToPoint(list[l].CurrentPosition) < list[l].Radius) + { + vector = NvMath.NearestPointStrict(lossourcePositions[j], lostargetPositions[k], + list[l].CurrentPosition); + float num8 = Vector3.Distance(vector, list[l].CurrentPosition); + if (num8 < list[l].HighestLOSPosition.y) + { + flag = true; + num4 += 1f; + if (num8 < num5) + { + num5 = num8; + collisionWorldPos = vector; + break; + } + + break; + } + } } } - if (totalArmorDamage > 0) + if (__instance.HasLineOfFire(lossourcePositions[j], lostargetPositions[k], text, num6, + out vector)) { - ModInit.modLog?.Info?.Write( - $"[Vehicle.DamageLocation] totalArmorDamage: {totalArmorDamage}"); - var totalArmorDamageDiff = - totalArmorDamage - (BattleArmorLocArmor + BattleArmorLocStruct); - if (totalArmorDamageDiff > 0) + num2 += 1f; + if (text != null) { - totalArmorDamage -= totalArmorDamageDiff; - ModInit.modLog?.Info?.Write( - $"[Vehicle.DamageLocation] totalArmorDamageDiff Diff: {totalArmorDamageDiff}. Vehicle totalArmorDamage decremented to {totalArmorDamage}"); - BattleArmorAsMech.TakeWeaponDamage(hitInfo, (int) BALocArmor, weapon, - Mathf.Abs(totalArmorDamageDiff), 0, 1, DamageType.Combat); - ModInit.modLog?.Info?.Write( - $"[Vehicle.DamageLocation] Battle Armor at location {BALocArmor} takes {BattleArmorLocArmor} damage"); - __instance.Combat.MessageCenter.PublishMessage( - new AddSequenceToStackMessage(new ShowActorInfoSequence(BattleArmorAsMech, - Strings.T("Battle Armor Damaged!"), - FloatieMessage.MessageNature.CriticalHit, false))); + break; + } + } + else + { + if (flag) + { + num4 -= 1f; + } + + float num8 = Vector3.Distance(vector, sourcePosition); + if (num8 < num5) + { + num5 = num8; + collisionWorldPos = vector; } + } + } + } + + if (text != null && num2 > 0.5f) + { + break; + } + } + + float num9 = (text == null) ? (num2 / num3) : num2; + float b = num9 - source.Combat.Constants.Visibility.MinRatioFromActors; + float num10 = Mathf.Min(num4 / num3, b); + if (num10 > 0.001f) + { + num9 -= num10; + } + + if (num9 >= source.Combat.Constants.Visibility.RatioFullVis) + { + __result = LineOfFireLevel.LOFClear; + ModInit.modLog?.Debug?.Write( + $"[LineOfSight_GetLineOfFireUncached] LOF for {source.DisplayName} and target {target.DisplayName} is {__result} due to num9 >= source.Combat.Constants.Visibility.RatioFullVis"); + __runOriginal = false; + return; + } + + if (num9 >= source.Combat.Constants.Visibility.RatioObstructedVis) + { + __result = LineOfFireLevel.LOFObstructed; + ModInit.modLog?.Debug?.Write( + $"[LineOfSight_GetLineOfFireUncached] LOF for {source.DisplayName} and target {target.DisplayName} is {__result} due to num9 >= source.Combat.Constants.Visibility.RatioObstructedVis"); + __runOriginal = false; + return; + } + + __result = LineOfFireLevel.LOFBlocked; + ModInit.modLog?.Debug?.Write( + $"[LineOfSight_GetLineOfFireUncached] LOF for {source.DisplayName} and target {target.DisplayName} is {__result} due to raisins"); + __runOriginal = false; + return; + } + } + + [HarmonyPatch(typeof(LOFCache), "GetLineOfFire")] + public static class LOFCache_GetLineOfFire + { + //static bool Prepare() => false; + public static void Postfix(LOFCache __instance, AbstractActor source, Vector3 sourcePosition, + ICombatant target, Vector3 targetPosition, Quaternion targetRotation, ref Vector3 collisionWorldPos, + ref LineOfFireLevel __result) + { + //collisionWorldPos = targetPosition; + + if (target is AbstractActor actorTarget) + { + if (actorTarget.IsSwarmingUnit() || + actorTarget.IsMountedUnit()) //|| actorTarget.isGarrisoned()) + { + collisionWorldPos = targetPosition; + __result = LineOfFireLevel + .NotSet; // added 3/27 to block all LOF to swarming/mounted units. NotSet, or should it be LOS.Blocked? + return; + } + } + + if (source.IsAirlifted()) + { + if (!ModState.AirliftTrackers[source.GUID].IsCarriedInternal) + { + var carrier = + source.Combat.FindActorByGUID(ModState.AirliftTrackers[source.GUID].CarrierGUID); + var resultFromCarrier = source.Combat.LOFCache.GetLineOfFire(carrier, + carrier.CurrentPosition, target, + targetPosition, targetRotation, out collisionWorldPos); + if (resultFromCarrier > __result) + { + __result = resultFromCarrier; + } + } + } + + else if (source.IsMountedUnit()) + { + var carrier = source.Combat.FindActorByGUID(ModState.PositionLockMount[source.GUID]); + if (carrier.HasFiringPorts()) + { + __result = source.Combat.LOFCache.GetLineOfFire(carrier, carrier.CurrentPosition, target, + targetPosition, targetRotation, out collisionWorldPos); + //ModInit.modLog?.Debug?.Write($"[LOFCache.GetLineOfFire] returning LOF {__result} from carrier {carrier.DisplayName} for squad {source.DisplayName}"); + } + } + + // else if (source.isGarrisoned()) + // { + +// var carrier = source.Combat.FindCombatantByGUID(ModState.PositionLockGarrison[source.GUID].BuildingGUID); +// __result = carrier.GetLineOfFireForGarrison(source, carrier.CurrentPosition, target, +// targetPosition, targetRotation, out collisionWorldPos); +//ModInit.modLog?.Debug?.Write($"[LOFCache.GetLineOfFire] returning LOF {__result} from carrier {carrier.DisplayName} for squad {source.DisplayName}"); + + // } + //__result = LineOfFireLevel.LOFClear; + } + } + + [HarmonyPatch(typeof(Mech), "DamageLocation", + new Type[] + { + typeof(int), typeof(WeaponHitInfo), typeof(ArmorLocation), typeof(Weapon), typeof(float), typeof(float), + typeof(int), typeof(AttackImpactQuality), typeof(DamageType) + })] + public static class Mech_DamageLocation_Patch + { + public static void Prefix(Mech __instance, int originalHitLoc, WeaponHitInfo hitInfo, ArmorLocation aLoc, + Weapon weapon, ref float totalArmorDamage, ref float directStructureDamage, int hitIndex, + AttackImpactQuality impactQuality, DamageType damageType) + { + if (__instance is TrooperSquad squad) + { + if (squad.IsGarrisoned()) + { + var bldgCombatant = + squad.Combat.FindCombatantByGUID(ModState.PositionLockGarrison[squad.GUID].BuildingGUID); + if (bldgCombatant is BattleTech.Building building) + { + ModInit.modLog?.Trace?.Write( + $"[Mech.DamageLocation] Redirecting {totalArmorDamage} armor and {directStructureDamage} structure damaghe from squad {squad.DisplayName} {squad.GUID} to garrisoned building {bldgCombatant.DisplayName}"); + building.TakeWeaponDamage(hitInfo, 1, weapon, totalArmorDamage, directStructureDamage, + hitIndex, damageType); + totalArmorDamage = 0f; + directStructureDamage = 0f; + } + } + } + + if (!__instance.HasMountedUnits() && !__instance.HasSwarmingUnits()) return; + + foreach (var squadInfo in ModState.BADamageTrackers.Where(x => + x.Value.TargetGUID == __instance.GUID && !x.Value.IsSquadInternal && + x.Value.BA_MountedLocations.ContainsValue((int) aLoc))) + { + ModInit.modLog?.Trace?.Write( + $"[Mech.DamageLocation] Evaluating {squadInfo.Key} for {squadInfo.Value.TargetGUID}"); + if (ModInit.Random.NextDouble() > (double) 1 / 3) continue; + if (__instance.Combat.FindActorByGUID(squadInfo.Key) is Mech BattleArmorAsMech) + { + if (BattleArmorAsMech.GUID == hitInfo.attackerId) continue; + var BattleArmorMounts = squadInfo.Value.BA_MountedLocations.Where(x => x.Value == (int) aLoc); + foreach (var mount in BattleArmorMounts) + { + var BALocArmor = (ArmorLocation) mount.Key; + //var BALocArmorString = BattleArmorAsMech.GetStringForArmorLocation(BALocArmor); + var BALocStruct = MechStructureRules.GetChassisLocationFromArmorLocation(BALocArmor); + //var BALocStructString = BattleArmorAsMech.GetStringForStructureLocation(BALocStruct); + + var BattleArmorLocArmor = BattleArmorAsMech.ArmorForLocation((int) BALocArmor); + var BattleArmorLocStruct = BattleArmorAsMech.StructureForLocation((int) BALocStruct); + + if (directStructureDamage > 0) + { + ModInit.modLog?.Info?.Write( + $"[Mech.DamageLocation] directStructureDamage: {directStructureDamage}"); + var directStructureDiff = directStructureDamage - BattleArmorLocStruct; + if (directStructureDiff >= 0) + { + directStructureDamage -= BattleArmorLocStruct; + ModInit.modLog?.Info?.Write( + $"[Mech.DamageLocation] directStructureDamage Diff: {directStructureDiff}. Mech directStructureDamage decremented to {directStructureDamage}"); + BattleArmorAsMech.TakeWeaponDamage(hitInfo, (int) BALocArmor, weapon, 0, + BattleArmorLocStruct, hitIndex, damageType); + ModInit.modLog?.Info?.Write( + $"[Mech.DamageLocation] Battle Armor at location {BALocArmor} takes {BattleArmorLocStruct} direct structure damage"); + __instance.Combat.MessageCenter.PublishMessage( + new AddSequenceToStackMessage(new ShowActorInfoSequence(BattleArmorAsMech, + Strings.T("Battle Armor Damaged!"), + FloatieMessage.MessageNature.CriticalHit, false))); + continue; + } + + else if (directStructureDiff < 0) + { + ModInit.modLog?.Info?.Write( + $"[Mech.DamageLocation] directStructureDamage Diff: {directStructureDiff}. Mech directStructureDamage decremented to 0"); + BattleArmorAsMech.TakeWeaponDamage(hitInfo, (int) BALocArmor, weapon, 0, + Mathf.Abs(directStructureDamage), hitIndex, damageType); + ModInit.modLog?.Info?.Write( + $"[Mech.DamageLocation] Battle Armor at location {BALocArmor} takes {directStructureDamage} direct structure damage"); + __instance.Combat.MessageCenter.PublishMessage( + new AddSequenceToStackMessage(new ShowActorInfoSequence(BattleArmorAsMech, + Strings.T("Battle Armor Damaged!"), + FloatieMessage.MessageNature.CriticalHit, false))); + directStructureDamage = 0; + } + } + + if (totalArmorDamage > 0) + { + ModInit.modLog?.Info?.Write( + $"[Mech.DamageLocation] totalArmorDamage: {totalArmorDamage}"); + var totalArmorDamageDiff = + totalArmorDamage - (BattleArmorLocArmor + BattleArmorLocStruct); + if (totalArmorDamageDiff > 0) + { + totalArmorDamage -= totalArmorDamageDiff; + ModInit.modLog?.Info?.Write( + $"[Mech.DamageLocation] totalArmorDamageDiff Diff: {totalArmorDamageDiff}. Mech totalArmorDamage decremented to {totalArmorDamage}"); + BattleArmorAsMech.TakeWeaponDamage(hitInfo, (int) BALocArmor, weapon, + Mathf.Abs(totalArmorDamageDiff), 0, hitIndex, damageType); + ModInit.modLog?.Info?.Write( + $"[Mech.DamageLocation] Battle Armor at location {BALocArmor} takes {BattleArmorLocArmor} damage"); + __instance.Combat.MessageCenter.PublishMessage( + new AddSequenceToStackMessage(new ShowActorInfoSequence(BattleArmorAsMech, + Strings.T("Battle Armor Damaged!"), + FloatieMessage.MessageNature.CriticalHit, false))); + } + + else if (totalArmorDamageDiff <= 0) + { + ModInit.modLog?.Info?.Write( + $"[Mech.DamageLocation] totalArmorDamageDiff Diff: {totalArmorDamageDiff}. Mech totalArmorDamage decremented to 0"); + BattleArmorAsMech.TakeWeaponDamage(hitInfo, (int) BALocArmor, weapon, + Mathf.Abs(totalArmorDamage), 0, hitIndex, damageType); + ModInit.modLog?.Info?.Write( + $"[Mech.DamageLocation] Battle Armor at location {BALocArmor} takes {totalArmorDamage} damage"); + totalArmorDamage = 0; + __instance.Combat.MessageCenter.PublishMessage( + new AddSequenceToStackMessage(new ShowActorInfoSequence(BattleArmorAsMech, + Strings.T("Battle Armor Damaged!"), + FloatieMessage.MessageNature.CriticalHit, false))); + } + } + } + } + } + } + } + + [HarmonyPatch(typeof(Mech), "OnLocationDestroyed", + new Type[] {typeof(ChassisLocations), typeof(Vector3), typeof(WeaponHitInfo), typeof(DamageType)})] + public static class Mech_OnLocationDestroyed + { + public static void Prefix(Mech __instance, ChassisLocations location, Vector3 attackDirection, + WeaponHitInfo hitInfo, DamageType damageType) + { + if (!__instance.HasMountedUnits() && !__instance.HasSwarmingUnits()) return; + + foreach (var squadInfo in ModState.BADamageTrackers.Where(x => + x.Value.TargetGUID == __instance.GUID && !x.Value.IsSquadInternal && + x.Value.BA_MountedLocations.ContainsValue((int) location))) + { + var wereSwarmingUnitsResponsible = squadInfo.Key == hitInfo.attackerId; + + ModInit.modLog?.Trace?.Write( + $"[Mech.OnLocationDestroyed] Evaluating {squadInfo.Key} for {squadInfo.Value.TargetGUID}"); + if (ModInit.Random.NextDouble() >= (double) 1 / 3 || wereSwarmingUnitsResponsible) continue; + if (__instance.Combat.FindActorByGUID(squadInfo.Key) is Mech battleArmorAsMech) + { + var battleArmorMounts = + squadInfo.Value.BA_MountedLocations.Where(x => x.Value == (int) location); + var battleArmorMountsCopy = new List>(battleArmorMounts); + foreach (var mount in battleArmorMountsCopy) + { + var BALocArmor = (ArmorLocation) mount.Key; + var BALocStruct = MechStructureRules.GetChassisLocationFromArmorLocation(BALocArmor); + battleArmorAsMech.NukeStructureLocation(hitInfo, 1, BALocStruct, attackDirection, + damageType); + } + //battleArmorAsMech.DismountBA(__instance, Vector3.zero, false, true, true); + //battleArmorAsMech.FlagForDeath("Killed When Mount Died", DeathMethod.VitalComponentDestroyed, DamageType.Melee, 0, -1, __instance.GUID, false); + //battleArmorAsMech.HandleDeath(__instance.GUID); //this is probably wrong but i dont care + } + } + } + } + + [HarmonyPatch(typeof(MechBayMechInfoWidget), "SetData", + new Type[] {typeof(SimGameState), typeof(MechBayPanel), typeof(DataManager), typeof(MechBayMechUnitElement), typeof(bool), typeof(bool)})] + public static class MechBayMechInfoWidget_SetData + { + public static void Postfix(MechBayMechInfoWidget __instance, SimGameState sim, MechBayPanel mechBay, + DataManager dataManager, MechBayMechUnitElement mechElement, bool useNoMechOverlay, + bool useRepairButton) + { + var decoGO = __instance.gameObject.FindFirstChildNamed("Deco"); + var svgComponentDeco = decoGO.GetComponent(); + var decoParent = decoGO?.transform?.parent; + UnityEngine.Object.DestroyImmediate(svgComponentDeco); + var newlocTxtGO = decoParent?.gameObject.FindFirstChildNamed("StratOps_SquadCarrierTip"); + if (newlocTxtGO == null) + { + ModInit.modLog?.Trace?.Write($"[MechBayMechInfoWidget_SetData] couldnt find game object `StratOps_SquadCarrierTip`, instantiating a new one"); + newlocTxtGO = UnityEngine.Object.Instantiate(decoGO, decoParent); + newlocTxtGO.name = "StratOps_SquadCarrierTip"; + } + else + { + ModInit.modLog?.Trace?.Write($"[MechBayMechInfoWidget_SetData] Found game object `StratOps_SquadCarrierTip` and using it."); + } + var localizableTextComponent = newlocTxtGO.GetOrAddComponent(); + localizableTextComponent.SetText(ModInit.modSettings.BAMountReminderText); + localizableTextComponent.alignment = TextAlignmentOptions.BottomLeft; + localizableTextComponent.enableAutoSizing = true; + localizableTextComponent.enableWordWrapping = false; + if (mechBay == null) newlocTxtGO.SetActive(__instance?.selectedMech?.GetCustomInfo()?.SquadInfo?.Troopers > 0); + //decoGO.SetActive(true); + + } + } + + [HarmonyPatch(typeof(MechJumpSequence), "CompleteOrders")] + public static class MechJumpSequence_CompleteOrders + { + public static void Postfix(MechJumpSequence __instance) + { + if (__instance.OwningMech == null) return; + var settings = ModInit.modSettings.DeswarmMovementConfig; + + if (ModState.DeSwarmMovementInfo?.Carrier?.GUID == __instance.OwningMech.GUID) + { + var baseChance = settings.BaseSuccessChance; //__instance.owningActor.getMovementDeSwarmMinChance(); + var chanceFromPips = __instance.owningActor.EvasivePipsCurrent * settings.EvasivePipsFactor; + //__instance.owningActor.getMovementDeSwarmEvasivePipsFactor(); + var finalChance = + Mathf.Min((baseChance + chanceFromPips) * settings.JumpMovementModifier, + settings + .MaxSuccessChance); //__instance.owningActor.getMovementDeSwarmEvasiveJumpMovementMultiplier(), __instance.owningActor.getMovementDeSwarmMaxChance()); + var roll = ModInit.Random.NextDouble(); + ModInit.modLog?.Info?.Write( + $"[ActorMovementSequence.CompleteOrders] Found DeSwarmMovementInfo for unit {__instance.owningActor.DisplayName} {__instance.owningActor.GUID}. Rolled {roll} vs finalChance {finalChance} from (baseChance {baseChance} + evasive chance {chanceFromPips}) x JumpMovementMulti {settings.JumpMovementModifier}"); + if (roll <= finalChance) + { + var baseDistance = Vector3.Distance(__instance.StartPos, __instance.FinalPos); + + foreach (var swarmingUnit in ModState.DeSwarmMovementInfo.SwarmingUnits) + { + var finalDist = (float) (baseDistance * ModInit.Random.NextDouble()); + var finalDestination = + Utils.LerpByDistance(__instance.StartPos, __instance.FinalPos, finalDist); + finalDestination.y = + swarmingUnit.Combat.MapMetaData.GetLerpedHeightAt(finalDestination, + false); //set proper height on ground. + ModInit.modLog?.Info?.Write( + $"[ActorMovementSequence.CompleteOrders] Roll succeeded, plonking {swarmingUnit.DisplayName} at {finalDestination}"); + swarmingUnit.DismountBA(__instance.owningActor, finalDestination, false, true); + if (swarmingUnit is TrooperSquad swarmingUnitSquad) + { + var dmg = settings.UseDFADamage + ? swarmingUnitSquad.StatCollection.GetValue("DFASelfDamage") + : settings.LocationDamageOverride; + var reduction = settings.PilotingDamageReductionFactor * + swarmingUnitSquad.GetPilot().Piloting; + var dmgReduction = dmg * reduction; + dmg -= dmgReduction; + if (dmg > 0f) + { + var trooperLocs = swarmingUnitSquad.GetPossibleHitLocations(__instance.owningActor); + for (int i = 0; i < trooperLocs.Count; i++) + { + var hitinfo = new WeaponHitInfo(-1, -1, 0, 0, __instance.owningActor.GUID, + swarmingUnitSquad.GUID, 1, new float[1], new float[1], new float[1], + new bool[1], new int[trooperLocs[i]], new int[1], + new AttackImpactQuality[1], + new AttackDirection[1], new Vector3[1], new string[1], + new int[trooperLocs[i]]); + + swarmingUnitSquad.TakeWeaponDamage(hitinfo, trooperLocs[i], + swarmingUnitSquad.MeleeWeapon, dmg, + 0, 0, DamageType.DFASelf); + } + } + } + } + } + + ModState.DeSwarmMovementInfo = new Classes.BA_DeswarmMovementInfo(); + } + } + } + + [HarmonyPatch(typeof(MechRepresentation), "ToggleHeadlights")] + public static class MechRepresentation_ToggleHeadlights + { + public static void Postfix(MechRepresentation __instance, bool headlightsActive) + { + if (__instance.parentActor.IsSwarmingUnit() || __instance.parentActor.IsMountedUnit() || + __instance.parentActor.IsAirlifted() || __instance.parentActor.IsGarrisoned()) + { + var customRep = __instance as CustomMechRepresentation; + if (customRep != null) + { + customRep._ToggleHeadlights(false); + } + else + { + for (int i = 0; i < __instance.headlightReps.Count; i++) + { + __instance.headlightReps[i].SetActive(false); + } + } + } + } + } + + [HarmonyPatch(typeof(SelectionStateAbilityInstant), "OnAddToStack", new Type[] { })] + public static class SelectionStateAbilityInstant_OnAddToStack + { + public static void Postfix(SelectionStateAbilityInstant __instance) + { + var cHUD = __instance + .HUD; //IRBTModUtils.SharedState.CombatHUD;//Traverse.Create(__instance).Property("HUD").GetValue(); + var creator = cHUD.SelectedActor; + if (__instance.FromButton.Ability.Def.Id == ModInit.modSettings.BattleArmorDeSwarmRoll) + { + var settings = + ModInit.modSettings.DeswarmConfigs.ContainsKey(ModInit.modSettings.BattleArmorDeSwarmRoll) + ? ModInit.modSettings.DeswarmConfigs[ModInit.modSettings.BattleArmorDeSwarmRoll] + : new Classes.ConfigOptions.BA_DeswarmAbilityConfig(); + //var parsed = float.TryParse(__instance.FromButton.Ability.Def.EffectData + // .FirstOrDefault(x => x.statisticData.statName == "BattleArmorDeSwarmerRoll") + // ?.statisticData + // .modValue, out var baseChance); + + var pilotSkill = creator.GetPilot().Piloting; + var finalChance = Mathf.Min(settings.BaseSuccessChance + (0.05f * pilotSkill), + settings.MaxSuccessChance); + ModInit.modLog?.Info?.Write( + $"[SelectionStateAbilityInstant.OnAddToStack - BattleArmorDeSwarm] Deswarm chance: {finalChance} from baseChance {settings.BaseSuccessChance} + pilotSkill x 0.05 {0.05f * pilotSkill}, max {settings.MaxSuccessChance}., stored in state."); + ModState.DeSwarmSuccessChance = finalChance; + var chanceDisplay = (float) Math.Round(finalChance, 3) * 100; + cHUD.AttackModeSelector.FireButton.FireText.SetText($"{chanceDisplay}% - Confirm", + Array.Empty()); + } + else if (__instance.FromButton.Ability.Def.Id == ModInit.modSettings.BattleArmorDeSwarmSwat) + { + var settings = + ModInit.modSettings.DeswarmConfigs.ContainsKey(ModInit.modSettings.BattleArmorDeSwarmSwat) + ? ModInit.modSettings.DeswarmConfigs[ModInit.modSettings.BattleArmorDeSwarmSwat] + : new Classes.ConfigOptions.BA_DeswarmAbilityConfig(); + //var parsed = float.TryParse(__instance.FromButton.Ability.Def.EffectData + // .FirstOrDefault(x => x.statisticData.statName == "BattleArmorDeSwarmerSwat") + // ?.statisticData + // .modValue, out var baseChance); + //if (!parsed) baseChance = 0.55f; + + var pilotSkill = creator.GetPilot().Piloting; + var missingActuatorCount = -8; + foreach (var armComponent in creator.allComponents.Where(x => + x.IsFunctional && (x.Location == 2 || x.Location == 32))) + { + foreach (var CategoryID in ModInit.modSettings.ArmActuatorCategoryIDs) + { + if (armComponent.mechComponentRef.IsCategory(CategoryID)) + { + missingActuatorCount += 1; + break; + } + } + } + + var finalChance = + Mathf.Min(settings.BaseSuccessChance + (0.05f * pilotSkill) - (0.05f * missingActuatorCount), + settings.MaxSuccessChance); + + ModInit.modLog?.Info?.Write( + $"[SelectionStateAbilityInstant.OnAddToStack - BattleArmorDeSwarm] Deswarm chance: {finalChance} from baseChance {settings.BaseSuccessChance} + pilotSkill x 0.05 {0.05f * pilotSkill} - missingActuators x 0.05 {0.05f * missingActuatorCount}, max {settings.MaxSuccessChance} stored in state."); + ModState.DeSwarmSuccessChance = finalChance; + var chanceDisplay = (float) Math.Round(finalChance, 3) * 100; + cHUD.AttackModeSelector.FireButton.FireText.SetText($"{chanceDisplay}% - Confirm", + Array.Empty()); + } + } + } + + + [HarmonyPatch(typeof(SelectionStateFire), "ProcessClickedCombatant", + new Type[] {typeof(ICombatant)})] + public static class SelectionStateFire_ProcessClickedCombatant + { + static bool Prepare() => false; //disable for now, try with force-end turn. + + public static void Postfix(SelectionStateFire __instance, ref ICombatant combatant) + { + if (__instance.SelectedActor.IsSwarmingUnit()) + { + var newTarget = + __instance.SelectedActor.Combat.FindActorByGUID( + ModState.PositionLockSwarm[__instance.SelectedActor.GUID]); + combatant = newTarget; + } + } + } + + [HarmonyPatch(typeof(SelectionStateTargetSingleCombatantBase), "ProcessClickedCombatant", + new Type[] {typeof(ICombatant)})] + public static class SelectionStateTargetSingleCombatantBase_ProcessClickedCombatant + { + //private static bool Prepare() => false; + public static void Postfix(SelectionStateTargetSingleCombatantBase __instance, ICombatant combatant) + { + + if (__instance.FromButton.Ability.Def.Id == ModInit.modSettings.BattleArmorMountAndSwarmID) + { + var cHUD = __instance + .HUD; //IRBTModUtils.SharedState.CombatHUD;//Traverse.Create(__instance).Property("HUD").GetValue(); + var creator = cHUD.SelectedActor; + if (!creator.Pathing.ArePathGridsComplete) + { + cHUD.AttackModeSelector.FireButton.CurrentFireMode = CombatHUDFireButton.FireMode.None; + cHUD.AttackModeSelector.FireButton.FireText.SetText($"Pathing Incomplete - DISABLED", + Array.Empty()); + return; + } + + if (creator is Mech creatorMech && combatant != null && combatant.team.IsEnemy(creator.team)) + { + var chance = creator.Combat.ToHit.GetToHitChance(creator, creatorMech.MeleeWeapon, combatant, + creator.CurrentPosition, combatant.CurrentPosition, 1, MeleeAttackType.Charge, false); + ModInit.modLog?.Trace?.Write( + $"[SelectionState.ShowFireButton - Swarm Success calculated as {chance}, storing in state."); + ModState.SwarmSuccessChance = chance; + var chanceDisplay = (float) Math.Round(chance, 2) * 100; + cHUD.AttackModeSelector.FireButton.FireText.SetText($"{chanceDisplay}% - Confirm", + Array.Empty()); + } + } + else if (__instance.FromButton.Ability.Def.Id == ModInit.modSettings.AirliftAbilityID) + { + var cHUD = __instance + .HUD; //IRBTModUtils.SharedState.CombatHUD;//Traverse.Create(__instance).Property("HUD").GetValue(); + var creator = cHUD.SelectedActor; + if (!creator.Pathing.ArePathGridsComplete) + { + cHUD.AttackModeSelector.FireButton.CurrentFireMode = CombatHUDFireButton.FireMode.None; + cHUD.AttackModeSelector.FireButton.FireText.SetText($"Wait For Pathing Incomplete - DISABLED", + Array.Empty()); + } + } + } + } - else if (totalArmorDamageDiff <= 0) - { - ModInit.modLog?.Info?.Write( - $"[Vehicle.DamageLocation] totalArmorDamageDiff Diff: {totalArmorDamageDiff}. Vehicle totalArmorDamage decremented to 0"); - BattleArmorAsMech.TakeWeaponDamage(hitInfo, (int) BALocArmor, weapon, - Mathf.Abs(totalArmorDamage), 0, 1, DamageType.Combat); - ModInit.modLog?.Info?.Write( - $"[Vehicle.DamageLocation] Battle Armor at location {BALocArmor} takes {totalArmorDamage} damage"); - totalArmorDamage = 0; - __instance.Combat.MessageCenter.PublishMessage( - new AddSequenceToStackMessage(new ShowActorInfoSequence(BattleArmorAsMech, - Strings.T("Battle Armor Damaged!"), - FloatieMessage.MessageNature.CriticalHit, false))); - } + [HarmonyPatch(typeof(Team), "DeferAllAvailableActors", + new Type[] { })] + public static class Team_DeferAllAvailableActors + { + static bool Prepare() => ModInit.modSettings.EnableQuickReserve; + + public static void Prefix(ref bool __runOriginal, Team __instance) + { + if (!__runOriginal) return; + var hk = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); + if (hk) + { + for (int i = 0; i < __instance.units.Count; i++) + { + if (__instance.units[i].Combat.TurnDirector.IsInterleaved && !__instance.units[i].IsDead && + !__instance.units[i].IsFlaggedForDeath && !__instance.units[i].HasActivatedThisRound && + !__instance.units[i].HasBegunActivation) + { + if (__instance.units[i].Initiative != __instance.units[i].Combat.TurnDirector.LastPhase) + { + __instance.units[i].DeferUnit(); + __instance.units[i].ForceUnitToLastActualPhase(); } } } + + __runOriginal = false; + return; } + + __runOriginal = true; + return; } } @@ -1984,383 +2277,145 @@ public static void Prefix(Turret __instance, WeaponHitInfo hitInfo, BuildingLoca } } - [HarmonyPatch(typeof(CombatHUDMechTrayArmorHover), "setToolTipInfo", - new Type[] {typeof(Mech), typeof(ArmorLocation)})] - public static class CombatHUDMechTrayArmorHover_setToolTipInfo - { - public static void Postfix(CombatHUDMechTrayArmorHover __instance, Mech mech, ArmorLocation location) - { - if (!mech.HasSwarmingUnits() && !mech.HasMountedUnits()) return; - var tooltip = - __instance - .ToolTip; //Traverse.Create(__instance).Property("ToolTip").GetValue(); - foreach (var squadInfo in ModState.BADamageTrackers.Where(x => - x.Value.TargetGUID == mech.GUID && !x.Value.IsSquadInternal && - x.Value.BA_MountedLocations.ContainsValue((int) location))) - { - ModInit.modLog?.Trace?.Write( - $"[CombatHUDMechTrayArmorHover.setToolTipInfo] Evaluating {squadInfo.Key} for {squadInfo.Value.TargetGUID} for tooltip infos"); - - if (mech.Combat.FindActorByGUID(squadInfo.Key) is Mech BattleArmorAsMech) - { - var BattleArmorMounts = - squadInfo.Value.BA_MountedLocations.Where(x => x.Value == (int) location); - foreach (var mount in BattleArmorMounts) - { - - var BALocArmor = (ArmorLocation) mount.Key; - //var BALocArmorString = BattleArmorAsMech.GetStringForArmorLocation(BALocArmor); - var BALocStruct = MechStructureRules.GetChassisLocationFromArmorLocation(BALocArmor); - //var BALocStructString = BattleArmorAsMech.GetStringForStructureLocation(BALocStruct); - - var BattleArmorLocArmor = BattleArmorAsMech.ArmorForLocation((int) BALocArmor); - var BattleArmorLocStruct = BattleArmorAsMech.StructureForLocation((int) BALocStruct); - var newText = - new Localize.Text( - $"Battle Armor: Arm. {Mathf.RoundToInt(BattleArmorLocArmor)} / Str. {Mathf.RoundToInt(BattleArmorLocStruct)}", - Array.Empty()); - if (mech.team.IsFriendly(BattleArmorAsMech.team)) - { - tooltip.BuffStrings.Add(newText); - } - else - { - tooltip.DebuffStrings.Add(newText); - } - } - } - } - } - } - - [HarmonyPatch(typeof(CombatHUDVehicleArmorHover), "setToolTipInfo", - new Type[] {typeof(Vehicle), typeof(VehicleChassisLocations)})] - public static class CombatHUDVehicleArmorHover_setToolTipInfo - { - public static void Postfix(CombatHUDVehicleArmorHover __instance, Vehicle vehicle, - VehicleChassisLocations location) + [HarmonyPatch(typeof(Vehicle), "DamageLocation", + new Type[] { - if (!vehicle.HasSwarmingUnits() && !vehicle.HasMountedUnits()) return; - var tooltip = - __instance - .ToolTip; //Traverse.Create(__instance).Property("ToolTip").GetValue(); - foreach (var squadInfo in ModState.BADamageTrackers.Where(x => - x.Value.TargetGUID == vehicle.GUID && !x.Value.IsSquadInternal && - x.Value.BA_MountedLocations.ContainsValue((int) location))) - { - ModInit.modLog?.Trace?.Write( - $"[CombatHUDMechTrayArmorHover.setToolTipInfo] Evaluating {squadInfo.Key} for {squadInfo.Value.TargetGUID} for tooltip infos"); - - if (vehicle.Combat.FindActorByGUID(squadInfo.Key) is Mech BattleArmorAsMech) - { - var BattleArmorMounts = - squadInfo.Value.BA_MountedLocations.Where(x => x.Value == (int) location); - foreach (var mount in BattleArmorMounts) - { - - var BALocArmor = (VehicleChassisLocations) mount.Key; - //var BALocArmorString = BattleArmorAsMech.GetStringForArmorLocation(BALocArmor); - //var BALocStructString = BattleArmorAsMech.GetStringForStructureLocation(BALocStruct); - - var BattleArmorLocArmor = BattleArmorAsMech.ArmorForLocation((int) BALocArmor); - var BattleArmorLocStruct = BattleArmorAsMech.StructureForLocation((int) BALocArmor); - var newText = - new Localize.Text( - $"Battle Armor: Arm. {Mathf.RoundToInt(BattleArmorLocArmor)} / Str. {Mathf.RoundToInt(BattleArmorLocStruct)}", - Array.Empty()); - if (vehicle.team.IsFriendly(BattleArmorAsMech.team)) - { - tooltip.BuffStrings.Add(newText); - } - else - { - tooltip.DebuffStrings.Add(newText); - } - } - } - } - } - } - - [HarmonyPatch(typeof(LineOfSight), "GetLineOfFireUncached")] - public static class LineOfSight_GetLineOfFireUncached + typeof(WeaponHitInfo), typeof(int), typeof(VehicleChassisLocations), typeof(Weapon), typeof(float), + typeof(float), typeof(AttackImpactQuality) + })] + public static class Vehicle_DamageLocation_Patch { - static bool Prepare() => !ModInit.modSettings.AllowIRBTUHandleVisibility; - - public static void Prefix(ref bool __runOriginal, LineOfSight __instance, AbstractActor source, - Vector3 sourcePosition, ICombatant target, Vector3 targetPosition, Quaternion targetRotation, - ref Vector3 collisionWorldPos, ref LineOfFireLevel __result) - { - if (!__runOriginal) return; - //collisionWorldPos = new Vector3(); - // if (target is BattleTech.Building building && !building.hasGarrisonedUnits()) return true; - - if (target is AbstractActor actorTarget) - { - if (actorTarget.IsSwarmingUnit() || actorTarget.IsMountedUnit()) //|| actorTarget.isGarrisoned()) - { - __result = LineOfFireLevel - .NotSet; // added 1/11 to block all LOF to swarming/mounted units. NotSet, or should it be LOS.Blocked? - ModInit.modLog?.Debug?.Write( - $"[LineOfSight_GetLineOfFireUncached] LOF for {source.DisplayName} and target {actorTarget.DisplayName} is {__result} due to target IsSwarm or IsMount"); - __runOriginal = false; - return; - } - -// if (!actorTarget.HasSwarmingUnits() && !actorTarget.HasMountedUnits()) -// { -// return true; -// } - } - - ModInit.modLog?.Debug?.Write( - $"[LineOfSight_GetLineOfFireUncached] Getting LOF for {source.DisplayName}"); - Vector3 forward = targetPosition - sourcePosition; - forward.y = 0f; - Quaternion rotation = Quaternion.LookRotation(forward); - Vector3[] lossourcePositions = source.GetLOSSourcePositions(sourcePosition, rotation); - Vector3[] lostargetPositions = target.GetLOSTargetPositions(targetPosition, targetRotation); - - - List list = new List(source.Combat.GetAllLivingActors()); - list.Remove(source); - - var unitGUIDs = new List(ModState.PositionLockSwarm.Keys); - unitGUIDs.AddRange(ModState.PositionLockMount.Keys); - unitGUIDs.AddRange(ModState.PositionLockGarrison.Keys); - foreach (var actorGUID in unitGUIDs) - { - list.Remove(source.Combat.FindActorByGUID(actorGUID)); - } - - AbstractActor actorTarget2 = target as AbstractActor; - string text = null; - if (actorTarget2 != null) - { - list.Remove(actorTarget2); - } - else - { - text = target.GUID; - } - - if (source.IsMountedUnit()) - { - var carrier = source.Combat.FindActorByGUID(ModState.PositionLockMount[source.GUID]); - if (carrier.HasFiringPorts()) - { - list.Remove( - carrier); // remove mound from LOS blocking (i have no idea if this will work or is even needed) - ModInit.modLog?.Debug?.Write( - $"[LineOfSight_GetLineOfFireUncached] remove {carrier.DisplayName}"); - } - } - - if (source.IsAirlifted()) - { - var airliftCarrier = - source.Combat.FindActorByGUID(ModState.AirliftTrackers[source.GUID].CarrierGUID); - list.Remove(airliftCarrier); - ModInit.modLog?.Debug?.Write( - $"[LineOfSight_GetLineOfFireUncached] remove {airliftCarrier.DisplayName}"); - } - - if (source.HasAirliftedUnits()) - { - var unitsBeingCarried = ModState.AirliftTrackers.Where(x => x.Value.CarrierGUID == source.GUID); - foreach (var unitBeingCarried in unitsBeingCarried) - { - var unit = source.Combat.FindActorByGUID(unitBeingCarried.Key); - list.Remove(unit); //most recent change? - ModInit.modLog?.Debug?.Write($"[LineOfSight_GetLineOfFireUncached] remove {unit.DisplayName}"); - } - } - - LineSegment lineSegment = new LineSegment(sourcePosition, targetPosition); - list.Sort((AbstractActor x, AbstractActor y) => Vector3.SqrMagnitude(x.CurrentPosition - sourcePosition) - .CompareTo(Vector3.SqrMagnitude(y.CurrentPosition - sourcePosition))); - float num = Vector3.SqrMagnitude(sourcePosition - targetPosition); - for (int i = list.Count - 1; i >= 0; i--) - { - if (list[i].IsDead || Vector3.SqrMagnitude(list[i].CurrentPosition - sourcePosition) > num || - lineSegment.DistToPoint(list[i].CurrentPosition) > list[i].Radius * 5f) - { - list.RemoveAt(i); - } - } + public static void Prefix(Vehicle __instance, WeaponHitInfo hitInfo, int originalHitLoc, + VehicleChassisLocations vLoc, Weapon weapon, ref float totalArmorDamage, + ref float directStructureDamage, AttackImpactQuality impactQuality) + { + if (!__instance.HasMountedUnits() && !__instance.HasSwarmingUnits()) return; - float num2 = 0f; - float num3 = 0f; - float num4 = 0f; - if (collisionWorldPos == null) collisionWorldPos = new Vector3(); - collisionWorldPos = targetPosition; - float num5 = 999999.9f; - Weapon longestRangeWeapon = source.GetLongestRangeWeapon(false, false); - float num6 = (longestRangeWeapon == null) ? 0f : longestRangeWeapon.MaxRange; - float adjustedSpotterRange = source.Combat.LOS.GetAdjustedSpotterRange(source, actorTarget2); - num6 = Mathf.Max(num6, adjustedSpotterRange); - float num7 = Mathf.Pow(num6, 2f); - for (int j = 0; j < lossourcePositions.Length; j++) + foreach (var squadInfo in ModState.BADamageTrackers.Where(x => + x.Value.TargetGUID == __instance.GUID && !x.Value.IsSquadInternal && + x.Value.BA_MountedLocations.ContainsValue((int) vLoc))) { - for (int k = 0; k < lostargetPositions.Length; k++) + ModInit.modLog?.Trace?.Write( + $"[Vehicle.DamageLocation] Evaluating {squadInfo.Key} for {squadInfo.Value.TargetGUID}"); + if (ModInit.Random.NextDouble() > (double) 1 / 3) continue; + if (__instance.Combat.FindActorByGUID(squadInfo.Key) is Mech BattleArmorAsMech) { - num3 += 1f; - if (Vector3.SqrMagnitude(lossourcePositions[j] - lostargetPositions[k]) <= num7) + if (BattleArmorAsMech.GUID == hitInfo.attackerId) continue; + var BattleArmorMounts = squadInfo.Value.BA_MountedLocations.Where(x => x.Value == (int) vLoc); + foreach (var mount in BattleArmorMounts) { - lineSegment = new LineSegment(lossourcePositions[j], lostargetPositions[k]); - bool flag = false; - Vector3 vector; - if (text == null) + var BALocArmor = (ArmorLocation) mount.Key; + //var BALocArmorString = BattleArmorAsMech.GetStringForArmorLocation(BALocArmor); + var BALocStruct = MechStructureRules.GetChassisLocationFromArmorLocation(BALocArmor); + //var BALocStructString = BattleArmorAsMech.GetStringForStructureLocation(BALocStruct); + + var BattleArmorLocArmor = BattleArmorAsMech.ArmorForLocation((int) BALocArmor); + var BattleArmorLocStruct = BattleArmorAsMech.StructureForLocation((int) BALocStruct); + + if (directStructureDamage > 0) { - for (int l = 0; l < list.Count; l++) + ModInit.modLog?.Info?.Write( + $"[Vehicle.DamageLocation] directStructureDamage: {directStructureDamage}"); + var directStructureDiff = directStructureDamage - BattleArmorLocStruct; + if (directStructureDiff >= 0) { - if (lineSegment.DistToPoint(list[l].CurrentPosition) < list[l].Radius) - { - vector = NvMath.NearestPointStrict(lossourcePositions[j], lostargetPositions[k], - list[l].CurrentPosition); - float num8 = Vector3.Distance(vector, list[l].CurrentPosition); - if (num8 < list[l].HighestLOSPosition.y) - { - flag = true; - num4 += 1f; - if (num8 < num5) - { - num5 = num8; - collisionWorldPos = vector; - break; - } - - break; - } - } + directStructureDamage -= BattleArmorLocStruct; + ModInit.modLog?.Info?.Write( + $"[Vehicle.DamageLocation] directStructureDamage Diff: {directStructureDiff}. Vehicle directStructureDamage decremented to {directStructureDamage}"); + BattleArmorAsMech.TakeWeaponDamage(hitInfo, (int) BALocArmor, weapon, 0, + BattleArmorLocStruct, 1, DamageType.Combat); + ModInit.modLog?.Info?.Write( + $"[Vehicle.DamageLocation] Battle Armor at location {BALocArmor} takes {BattleArmorLocStruct} direct structure damage"); + __instance.Combat.MessageCenter.PublishMessage( + new AddSequenceToStackMessage(new ShowActorInfoSequence(BattleArmorAsMech, + Strings.T("Battle Armor Damaged!"), + FloatieMessage.MessageNature.CriticalHit, false))); + continue; } - } - if (__instance.HasLineOfFire(lossourcePositions[j], lostargetPositions[k], text, num6, - out vector)) - { - num2 += 1f; - if (text != null) + else if (directStructureDiff < 0) { - break; + ModInit.modLog?.Info?.Write( + $"[Vehicle.DamageLocation] directStructureDamage Diff: {directStructureDiff}. Vehicle directStructureDamage decremented to 0"); + BattleArmorAsMech.TakeWeaponDamage(hitInfo, (int) BALocArmor, weapon, 0, + Mathf.Abs(directStructureDamage), 1, DamageType.Combat); + ModInit.modLog?.Info?.Write( + $"[Vehicle.DamageLocation] Battle Armor at location {BALocArmor} takes {directStructureDamage} direct structure damage"); + directStructureDamage = 0; + __instance.Combat.MessageCenter.PublishMessage( + new AddSequenceToStackMessage(new ShowActorInfoSequence(BattleArmorAsMech, + Strings.T("Battle Armor Damaged!"), + FloatieMessage.MessageNature.CriticalHit, false))); } } - else + + if (totalArmorDamage > 0) { - if (flag) + ModInit.modLog?.Info?.Write( + $"[Vehicle.DamageLocation] totalArmorDamage: {totalArmorDamage}"); + var totalArmorDamageDiff = + totalArmorDamage - (BattleArmorLocArmor + BattleArmorLocStruct); + if (totalArmorDamageDiff > 0) { - num4 -= 1f; + totalArmorDamage -= totalArmorDamageDiff; + ModInit.modLog?.Info?.Write( + $"[Vehicle.DamageLocation] totalArmorDamageDiff Diff: {totalArmorDamageDiff}. Vehicle totalArmorDamage decremented to {totalArmorDamage}"); + BattleArmorAsMech.TakeWeaponDamage(hitInfo, (int) BALocArmor, weapon, + Mathf.Abs(totalArmorDamageDiff), 0, 1, DamageType.Combat); + ModInit.modLog?.Info?.Write( + $"[Vehicle.DamageLocation] Battle Armor at location {BALocArmor} takes {BattleArmorLocArmor} damage"); + __instance.Combat.MessageCenter.PublishMessage( + new AddSequenceToStackMessage(new ShowActorInfoSequence(BattleArmorAsMech, + Strings.T("Battle Armor Damaged!"), + FloatieMessage.MessageNature.CriticalHit, false))); } - float num8 = Vector3.Distance(vector, sourcePosition); - if (num8 < num5) + else if (totalArmorDamageDiff <= 0) { - num5 = num8; - collisionWorldPos = vector; + ModInit.modLog?.Info?.Write( + $"[Vehicle.DamageLocation] totalArmorDamageDiff Diff: {totalArmorDamageDiff}. Vehicle totalArmorDamage decremented to 0"); + BattleArmorAsMech.TakeWeaponDamage(hitInfo, (int) BALocArmor, weapon, + Mathf.Abs(totalArmorDamage), 0, 1, DamageType.Combat); + ModInit.modLog?.Info?.Write( + $"[Vehicle.DamageLocation] Battle Armor at location {BALocArmor} takes {totalArmorDamage} damage"); + totalArmorDamage = 0; + __instance.Combat.MessageCenter.PublishMessage( + new AddSequenceToStackMessage(new ShowActorInfoSequence(BattleArmorAsMech, + Strings.T("Battle Armor Damaged!"), + FloatieMessage.MessageNature.CriticalHit, false))); } } } } - - if (text != null && num2 > 0.5f) - { - break; - } - } - - float num9 = (text == null) ? (num2 / num3) : num2; - float b = num9 - source.Combat.Constants.Visibility.MinRatioFromActors; - float num10 = Mathf.Min(num4 / num3, b); - if (num10 > 0.001f) - { - num9 -= num10; - } - - if (num9 >= source.Combat.Constants.Visibility.RatioFullVis) - { - __result = LineOfFireLevel.LOFClear; - ModInit.modLog?.Debug?.Write( - $"[LineOfSight_GetLineOfFireUncached] LOF for {source.DisplayName} and target {target.DisplayName} is {__result} due to num9 >= source.Combat.Constants.Visibility.RatioFullVis"); - __runOriginal = false; - return; - } - - if (num9 >= source.Combat.Constants.Visibility.RatioObstructedVis) - { - __result = LineOfFireLevel.LOFObstructed; - ModInit.modLog?.Debug?.Write( - $"[LineOfSight_GetLineOfFireUncached] LOF for {source.DisplayName} and target {target.DisplayName} is {__result} due to num9 >= source.Combat.Constants.Visibility.RatioObstructedVis"); - __runOriginal = false; - return; } - - __result = LineOfFireLevel.LOFBlocked; - ModInit.modLog?.Debug?.Write( - $"[LineOfSight_GetLineOfFireUncached] LOF for {source.DisplayName} and target {target.DisplayName} is {__result} due to raisins"); - __runOriginal = false; - return; } } - [HarmonyPatch(typeof(LOFCache), "GetLineOfFire")] - public static class LOFCache_GetLineOfFire + [HarmonyPatch(typeof(Weapon), "WillFireAtTargetFromPosition", + new Type[] {typeof(ICombatant), typeof(Vector3), typeof(Quaternion)})] + public static class Weapon_WillFireAtTargetFromPosition { - //static bool Prepare() => false; - public static void Postfix(LOFCache __instance, AbstractActor source, Vector3 sourcePosition, - ICombatant target, Vector3 targetPosition, Quaternion targetRotation, ref Vector3 collisionWorldPos, - ref LineOfFireLevel __result) + public static void Postfix(Weapon __instance, ICombatant target, Vector3 position, Quaternion rotation, + ref bool __result) { - //collisionWorldPos = targetPosition; - - if (target is AbstractActor actorTarget) - { - if (actorTarget.IsSwarmingUnit() || - actorTarget.IsMountedUnit()) //|| actorTarget.isGarrisoned()) - { - collisionWorldPos = targetPosition; - __result = LineOfFireLevel - .NotSet; // added 3/27 to block all LOF to swarming/mounted units. NotSet, or should it be LOS.Blocked? - return; - } - } - - if (source.IsAirlifted()) + if (__instance.parent == null) return; + if (target is AbstractActor targetActor) { - if (!ModState.AirliftTrackers[source.GUID].IsCarriedInternal) + if (__instance.parent.IsSwarmingUnit()) { - var carrier = - source.Combat.FindActorByGUID(ModState.AirliftTrackers[source.GUID].CarrierGUID); - var resultFromCarrier = source.Combat.LOFCache.GetLineOfFire(carrier, - carrier.CurrentPosition, target, - targetPosition, targetRotation, out collisionWorldPos); - if (resultFromCarrier > __result) + if (ModState.PositionLockSwarm[__instance.parent.GUID] == targetActor.GUID) { - __result = resultFromCarrier; + // ModInit.modLog?.Trace?.Write($"[Weapon.WillFireAtTargetFromPosition] {__instance.parent.DisplayName} is swarming {targetActor.DisplayName}, forcing LOS for weapon {__instance.Name}"); + __result = true; + return; } } - } - else if (source.IsMountedUnit()) - { - var carrier = source.Combat.FindActorByGUID(ModState.PositionLockMount[source.GUID]); - if (carrier.HasFiringPorts()) + if (targetActor.IsSwarmingUnit() || targetActor.IsMountedUnit()) { - __result = source.Combat.LOFCache.GetLineOfFire(carrier, carrier.CurrentPosition, target, - targetPosition, targetRotation, out collisionWorldPos); - //ModInit.modLog?.Debug?.Write($"[LOFCache.GetLineOfFire] returning LOF {__result} from carrier {carrier.DisplayName} for squad {source.DisplayName}"); + __result = false; } } - - // else if (source.isGarrisoned()) - // { - -// var carrier = source.Combat.FindCombatantByGUID(ModState.PositionLockGarrison[source.GUID].BuildingGUID); -// __result = carrier.GetLineOfFireForGarrison(source, carrier.CurrentPosition, target, -// targetPosition, targetRotation, out collisionWorldPos); -//ModInit.modLog?.Debug?.Write($"[LOFCache.GetLineOfFire] returning LOF {__result} from carrier {carrier.DisplayName} for squad {source.DisplayName}"); - - // } - //__result = LineOfFireLevel.LOFClear; } } @@ -2386,29 +2441,5 @@ public static void Prefix(WeaponRangeIndicators __instance, Vector3 position, Qu } } } - - [HarmonyPatch(typeof(MechRepresentation), "ToggleHeadlights")] - public static class MechRepresentation_ToggleHeadlights - { - public static void Postfix(MechRepresentation __instance, bool headlightsActive) - { - if (__instance.parentActor.IsSwarmingUnit() || __instance.parentActor.IsMountedUnit() || - __instance.parentActor.IsAirlifted() || __instance.parentActor.IsGarrisoned()) - { - var customRep = __instance as CustomMechRepresentation; - if (customRep != null) - { - customRep._ToggleHeadlights(false); - } - else - { - for (int i = 0; i < __instance.headlightReps.Count; i++) - { - __instance.headlightReps[i].SetActive(false); - } - } - } - } - } } } diff --git a/StrategicOperations/StrategicOperations/Patches/DirtyDebugPatches.cs b/StrategicOperations/StrategicOperations/Patches/DirtyDebugPatches.cs index f796c28..664596b 100644 --- a/StrategicOperations/StrategicOperations/Patches/DirtyDebugPatches.cs +++ b/StrategicOperations/StrategicOperations/Patches/DirtyDebugPatches.cs @@ -12,7 +12,6 @@ namespace StrategicOperations.Patches { class DirtyDebugPatches { - [HarmonyPatch] //[HarmonyPatch(typeof(HitLocation), "GetHitLocation", new Type[] {typeof(Dictionary), typeof(float), typeof(ArmorLocation), typeof(float)})] public static class HitLocation_GetHitLocation_MechArmorLoc @@ -23,7 +22,7 @@ public static MethodBase TargetMethod() .GetDeclaredMethods(typeof(HitLocation)).FirstOrDefault(x => x.Name == "GetHitLocation" && x.GetParameters().Length == 4)?.MakeGenericMethod(typeof(ArmorLocation)); return method; } - + public static void Postfix(Dictionary hitTable, float randomRoll, ArmorLocation bonusLocation, float bonusLocationMultiplier, ref ArmorLocation __result) { if (__result == ArmorLocation.None) @@ -137,6 +136,7 @@ public static void Prefix(ref bool __runOriginal, MultiSequence __instance, Vect public static class ReserveActorInvocation_Invoke_ShittyBypass { static bool Prepare() => true; //enabled + public static void Prefix(ReserveActorInvocation __instance, CombatGameState combatGameState) { if (__instance.targetRound != combatGameState.TurnDirector.CurrentRound) @@ -155,6 +155,7 @@ public static void Prefix(ReserveActorInvocation __instance, CombatGameState com public static class Vehicle_ArmorForLocation_Dirty // dirty skip playimpact on despawned/dead actor? { static bool Prepare() => false; + public static void Prefix(Vehicle __instance, ref int loc) { switch (loc) diff --git a/StrategicOperations/StrategicOperations/Patches/SelectionPatches.cs b/StrategicOperations/StrategicOperations/Patches/SelectionPatches.cs index 5ad470a..66f66cc 100644 --- a/StrategicOperations/StrategicOperations/Patches/SelectionPatches.cs +++ b/StrategicOperations/StrategicOperations/Patches/SelectionPatches.cs @@ -14,182 +14,6 @@ namespace StrategicOperations.Patches { public class StrategicSelection { - public static class StrategicTargetIndicatorsManager - { - public static List ReticleGOs = new List(); - public static CombatHUD HUD = null; - public static GameObject BaseReticleObject = null; - - public static void Clear() - { - StrategicTargetIndicatorsManager.ReticleGOs.Clear(); - UnityEngine.Object.Destroy(StrategicTargetIndicatorsManager.BaseReticleObject); - StrategicTargetIndicatorsManager.BaseReticleObject = null; - StrategicTargetIndicatorsManager.HUD = null; - } - public static void HideReticles() - { - foreach (var reticle in StrategicTargetIndicatorsManager.ReticleGOs) - { - reticle.gameObject.SetActive(false); - } - } - public static void InitReticles(CombatHUD hud, int count) - { - if (StrategicTargetIndicatorsManager.BaseReticleObject == null) - { - StrategicTargetIndicatorsManager.BaseReticleObject = new GameObject("StrategicTargetingReticles"); //was TargetingCircles - } - StrategicTargetIndicatorsManager.HUD = hud; - - while (ReticleGOs.Count < count) - { - ModInit.modLog?.Trace?.Write($"[InitReticle] Need to init new reticles; have {ReticleGOs.Count}"); - GameObject reticleGO = new GameObject("StrategicReticle"); //was Circle - StrategicTargetReticle reticle = reticleGO.AddComponent(); - //reticle.transform.SetParent(reticleGO.transform); - reticle.Init(StrategicTargetIndicatorsManager.HUD); - StrategicTargetIndicatorsManager.ReticleGOs.Add(reticleGO); - } - } - public static void ShowRoot() - { - StrategicTargetIndicatorsManager.BaseReticleObject.SetActive(true); - } - } - - public class StrategicTargetReticle : MonoBehaviour - { - public CombatHUD HUD; - public GameObject ReticleObject; - - - public void Init(CombatHUD hud) - { - this.HUD = hud; - ReticleObject = UnityEngine.Object.Instantiate(CombatTargetingReticle.Instance.Circles[0]); - ReticleObject.transform.SetParent(base.transform); - Vector3 localScale = ReticleObject.transform.localScale; - localScale.x = 2f; - localScale.z = 2f; - ReticleObject.transform.localScale = localScale; - ReticleObject.transform.localPosition = Vector3.zero; - } - - public void SetScaleAndLocation(Vector3 loc, float radius) - { - Vector3 localScale = ReticleObject.transform.localScale; - localScale.x = radius * 2f; - localScale.z = radius * 2f; - ReticleObject.transform.localScale = localScale; - base.transform.position = loc; - ReticleObject.SetActive(true); - ReticleObject.gameObject.SetActive(true); - ModInit.modLog?.Trace?.Write($"[SetScaleAndLocation] Set location to {loc}"); - } - - public void UpdateColorAndStyle(bool IsFriendly, bool IsResupply, bool IsResupplyInRange) - { - var dm = UnityGameInstance.BattleTechGame.DataManager; - - Transform[] childComponents; - - childComponents = ReticleObject.GetComponentsInChildren(true); - - for (int i = 0; i < childComponents.Length; i++) - { - if (childComponents[i].name == "Thumper1") - { - childComponents[i].gameObject.SetActive(false); - continue; - } - - if (childComponents[i].name == "Mortar1") - { - childComponents[i].gameObject.SetActive(true); - var decalsFromCircle = childComponents[i].GetComponentsInChildren(); - for (int j = 0; j < decalsFromCircle.Length; j++) - { - if (decalsFromCircle[j].name == "ReticleDecalCircle") - { - if (IsFriendly && !IsResupply) - { - if (!string.IsNullOrEmpty(ModInit.modSettings.MountIndicatorAsset)) - { - var newTexture = dm.GetObjectOfType(ModInit.modSettings.MountIndicatorAsset, - BattleTechResourceType.Texture2D); - if (newTexture != null) decalsFromCircle[j].DecalPropertyBlock.SetTexture("_MainTex", newTexture); - } - - if (ModInit.modSettings.MountIndicatorColor != null) - { - var customColor = new Color(ModInit.modSettings.MountIndicatorColor.Rf, - ModInit.modSettings.MountIndicatorColor.Gf, - ModInit.modSettings.MountIndicatorColor.Bf); - decalsFromCircle[j].DecalPropertyBlock.SetColor("_Color", customColor); - } - else - { - decalsFromCircle[j].DecalPropertyBlock.SetColor("_Color", Color.blue); - } - } - - if (IsResupply && !IsResupplyInRange) - { - if (!string.IsNullOrEmpty(ModInit.modSettings.ResupplyConfig.ResupplyIndicatorAsset)) - { - var newTexture = dm.GetObjectOfType(ModInit.modSettings.ResupplyConfig.ResupplyIndicatorAsset, - BattleTechResourceType.Texture2D); - if (newTexture != null) decalsFromCircle[j].DecalPropertyBlock.SetTexture("_MainTex", newTexture); - } - - if (ModInit.modSettings.MountIndicatorColor != null) - { - var customColor = new Color(ModInit.modSettings.ResupplyConfig.ResupplyIndicatorColor.Rf, - ModInit.modSettings.ResupplyConfig.ResupplyIndicatorColor.Gf, - ModInit.modSettings.ResupplyConfig.ResupplyIndicatorColor.Bf); - decalsFromCircle[j].DecalPropertyBlock.SetColor("_Color", customColor); - - } - else - { - decalsFromCircle[j].DecalPropertyBlock.SetColor("_Color", Color.blue); - } - } - else if (IsResupply) - { - if (!string.IsNullOrEmpty(ModInit.modSettings.ResupplyConfig.ResupplyIndicatorInRangeAsset)) - { - var newTexture = dm.GetObjectOfType(ModInit.modSettings.ResupplyConfig.ResupplyIndicatorInRangeAsset, - BattleTechResourceType.Texture2D); - if (newTexture != null) decalsFromCircle[j].DecalPropertyBlock.SetTexture("_MainTex", newTexture); - } - - if (ModInit.modSettings.MountIndicatorColor != null) - { - var customColor = new Color(ModInit.modSettings.ResupplyConfig.ResupplyIndicatorInRangeColor.Rf, - ModInit.modSettings.ResupplyConfig.ResupplyIndicatorInRangeColor.Gf, - ModInit.modSettings.ResupplyConfig.ResupplyIndicatorInRangeColor.Bf); - decalsFromCircle[j].DecalPropertyBlock.SetColor("_Color", customColor); - - } - else - { - decalsFromCircle[j].DecalPropertyBlock.SetColor("_Color", Color.blue); - } - } - decalsFromCircle[j].gameObject.SetActive(true); - } - else - { - decalsFromCircle[j].gameObject.SetActive(false); - } - } - } - } - } - } - public class SelectionStateMWTargetSingle_Stratops : AbilityExtensions.SelectionStateMWTargetSingle { public SelectionStateMWTargetSingle_Stratops(CombatGameState Combat, CombatHUD HUD, @@ -197,141 +21,6 @@ public SelectionStateMWTargetSingle_Stratops(CombatGameState Combat, CombatHUD H { } - public void HighlightPotentialTargets() - { - var jumpdist = 0f; - if (SelectedActor is Mech mech) - { - jumpdist = mech.JumpDistance; - if (float.IsNaN(jumpdist)) jumpdist = 0f; - } - - var ranges = new List() - { - SelectedActor.MaxWalkDistance, - SelectedActor.MaxSprintDistance, - jumpdist, - this.FromButton.Ability.Def.IntParam2 - }; - var maxRange = ranges.Max(); - - if (this.FromButton.Ability.Def.Id == ModInit.modSettings.BattleArmorMountAndSwarmID) - { - if (!SelectedActor.IsMountedUnit() && !SelectedActor.IsSwarmingUnit() && - !SelectedActor.IsGarrisoned()) - { - var mountTargets = SelectedActor.GetAllFriendliesWithinRange(maxRange); - var swarmTargets = SelectedActor.GetAllEnemiesWithinRange(maxRange); - - mountTargets.RemoveAll(x => !this.CanTargetCombatant(x)); - swarmTargets.RemoveAll(x => !this.CanTargetCombatant(x)); - - HUD.InWorldMgr.HideMeleeTargets(); - HUD.InWorldMgr.ShowBATargetsMeleeIndicator(swarmTargets, SelectedActor); - - StrategicTargetIndicatorsManager.InitReticles(HUD, mountTargets.Count); - for (var index = 0; index < mountTargets.Count; index++) - { - StrategicTargetIndicatorsManager.ReticleGOs[index].SetActive(true); - var reticle = StrategicTargetIndicatorsManager.ReticleGOs[index] - .GetComponent(); - var isFriendly = mountTargets[index].team.IsFriendly(SelectedActor.team); - reticle.SetScaleAndLocation(mountTargets[index].CurrentPosition, 10f); - reticle.UpdateColorAndStyle(isFriendly, false, false); - ModInit.modLog?.Trace?.Write( - $"[HighlightPotentialTargets - BattleArmorMountAndSwarmID] Updating reticle at index {index}, isFriendly {isFriendly}."); - } - - StrategicTargetIndicatorsManager.ShowRoot(); - } - } - else if (this.FromButton.Ability.Def.Id == ModInit.modSettings.ResupplyConfig.ResupplyAbilityID) - { - if (!SelectedActor.IsMountedUnit() && !SelectedActor.IsSwarmingUnit() && - !SelectedActor.IsGarrisoned()) - { - var resupplyTargets = SelectedActor.GetAllFriendliesWithinRange(1000f); - - resupplyTargets.RemoveAll(x => !this.CanTargetCombatant(x)); - - HUD.InWorldMgr.HideMeleeTargets(); - StrategicTargetIndicatorsManager.InitReticles(HUD, resupplyTargets.Count); - for (var index = 0; index < resupplyTargets.Count; index++) - { - if (Vector3.Distance(SelectedActor.CurrentPosition, - resupplyTargets[index].CurrentPosition) <= this.FromButton.Ability.Def.IntParam2) - { - StrategicTargetIndicatorsManager.ReticleGOs[index].SetActive(true); - var reticle = StrategicTargetIndicatorsManager.ReticleGOs[index] - .GetComponent(); - var isFriendly = resupplyTargets[index].team.IsFriendly(SelectedActor.team); - reticle.SetScaleAndLocation(resupplyTargets[index].CurrentPosition, 10f); - reticle.UpdateColorAndStyle(isFriendly, true, true); - ModInit.modLog?.Trace?.Write( - $"[HighlightPotentialTargets - ResupplyAbility INRANGE] Updating reticle at index {index}, isFriendly {isFriendly}."); - } - else - { - StrategicTargetIndicatorsManager.ReticleGOs[index].SetActive(true); - var reticle = StrategicTargetIndicatorsManager.ReticleGOs[index] - .GetComponent(); - var isFriendly = resupplyTargets[index].team.IsFriendly(SelectedActor.team); - reticle.SetScaleAndLocation(resupplyTargets[index].CurrentPosition, 10f); - reticle.UpdateColorAndStyle(isFriendly, true, false); - ModInit.modLog?.Trace?.Write( - $"[HighlightPotentialTargets - ResupplyAbility ANY] Updating reticle at index {index}, isFriendly {isFriendly}."); - } - } - - StrategicTargetIndicatorsManager.ShowRoot(); - } - } - } - - public override void OnAddToStack() - { - base.OnAddToStack(); - this.showTargetingText(this.abilitySelectionText); - var jumpdist = 0f; - if (SelectedActor is Mech mech) - { - jumpdist = mech.JumpDistance; - if (float.IsNaN(jumpdist)) jumpdist = 0f; - } - - var ranges = new List() - { - SelectedActor.MaxWalkDistance, - SelectedActor.MaxSprintDistance, - jumpdist, - this.FromButton.Ability.Def.IntParam2 - }; - var maxRange = ranges.Max(); - //CombatTargetingReticle.Instance.UpdateReticle(SelectedActor.CurrentPosition, maxRange, false); - CombatTargetingReticle.Instance.ShowRangeIndicators(SelectedActor.CurrentPosition, 0f, maxRange, false, true); - CombatTargetingReticle.Instance.UpdateRangeIndicator(SelectedActor.CurrentPosition, false, true); - CombatTargetingReticle.Instance.ShowReticle(); - - if (SelectedActor.IsMountedUnit()) - { - var carrier = Combat.FindCombatantByGUID(ModState.PositionLockMount[SelectedActor.GUID]); - this.ProcessClickedCombatant(carrier); - } - else if (SelectedActor.IsSwarmingUnit()) - { - var carrier = Combat.FindCombatantByGUID(ModState.PositionLockSwarm[SelectedActor.GUID]); - this.ProcessClickedCombatant(carrier); - } - else if (SelectedActor.IsGarrisoned()) - { - var carrier = Combat.FindCombatantByGUID(ModState.PositionLockGarrison[SelectedActor.GUID].BuildingGUID); - this.ProcessClickedCombatant(carrier); - } - else - { - this.HighlightPotentialTargets(); - } - } public override bool CanTargetCombatant(ICombatant potentialTarget) { if (SelectedActor.VisibilityToTargetUnit(potentialTarget) == VisibilityLevel.None) @@ -504,14 +193,158 @@ public override bool CanTargetCombatant(ICombatant potentialTarget) { if (SelectedActor.GetHasAvailableExternalLiftCapacityForTarget(targetActor) && SelectedActor.GetCanAirliftHostiles()) { - return true; + return true; + } + + return false; + } + } + } + return true; + } + + public void HighlightPotentialTargets() + { + var jumpdist = 0f; + if (SelectedActor is Mech mech) + { + jumpdist = mech.JumpDistance; + if (float.IsNaN(jumpdist)) jumpdist = 0f; + } + + var ranges = new List() + { + SelectedActor.MaxWalkDistance, + SelectedActor.MaxSprintDistance, + jumpdist, + this.FromButton.Ability.Def.IntParam2 + }; + var maxRange = ranges.Max(); + + if (this.FromButton.Ability.Def.Id == ModInit.modSettings.BattleArmorMountAndSwarmID) + { + if (!SelectedActor.IsMountedUnit() && !SelectedActor.IsSwarmingUnit() && + !SelectedActor.IsGarrisoned()) + { + var mountTargets = SelectedActor.GetAllFriendliesWithinRange(maxRange); + var swarmTargets = SelectedActor.GetAllEnemiesWithinRange(maxRange); + + mountTargets.RemoveAll(x => !this.CanTargetCombatant(x)); + swarmTargets.RemoveAll(x => !this.CanTargetCombatant(x)); + + HUD.InWorldMgr.HideMeleeTargets(); + HUD.InWorldMgr.ShowBATargetsMeleeIndicator(swarmTargets, SelectedActor); + + StrategicTargetIndicatorsManager.InitReticles(HUD, mountTargets.Count); + for (var index = 0; index < mountTargets.Count; index++) + { + StrategicTargetIndicatorsManager.ReticleGOs[index].SetActive(true); + var reticle = StrategicTargetIndicatorsManager.ReticleGOs[index] + .GetComponent(); + var isFriendly = mountTargets[index].team.IsFriendly(SelectedActor.team); + reticle.SetScaleAndLocation(mountTargets[index].CurrentPosition, 10f); + reticle.UpdateColorAndStyle(isFriendly, false, false); + ModInit.modLog?.Trace?.Write( + $"[HighlightPotentialTargets - BattleArmorMountAndSwarmID] Updating reticle at index {index}, isFriendly {isFriendly}."); + } + + StrategicTargetIndicatorsManager.ShowRoot(); + } + } + else if (this.FromButton.Ability.Def.Id == ModInit.modSettings.ResupplyConfig.ResupplyAbilityID) + { + if (!SelectedActor.IsMountedUnit() && !SelectedActor.IsSwarmingUnit() && + !SelectedActor.IsGarrisoned()) + { + var resupplyTargets = SelectedActor.GetAllFriendliesWithinRange(1000f); + + resupplyTargets.RemoveAll(x => !this.CanTargetCombatant(x)); + + HUD.InWorldMgr.HideMeleeTargets(); + StrategicTargetIndicatorsManager.InitReticles(HUD, resupplyTargets.Count); + for (var index = 0; index < resupplyTargets.Count; index++) + { + if (Vector3.Distance(SelectedActor.CurrentPosition, + resupplyTargets[index].CurrentPosition) <= this.FromButton.Ability.Def.IntParam2) + { + StrategicTargetIndicatorsManager.ReticleGOs[index].SetActive(true); + var reticle = StrategicTargetIndicatorsManager.ReticleGOs[index] + .GetComponent(); + var isFriendly = resupplyTargets[index].team.IsFriendly(SelectedActor.team); + reticle.SetScaleAndLocation(resupplyTargets[index].CurrentPosition, 10f); + reticle.UpdateColorAndStyle(isFriendly, true, true); + ModInit.modLog?.Trace?.Write( + $"[HighlightPotentialTargets - ResupplyAbility INRANGE] Updating reticle at index {index}, isFriendly {isFriendly}."); + } + else + { + StrategicTargetIndicatorsManager.ReticleGOs[index].SetActive(true); + var reticle = StrategicTargetIndicatorsManager.ReticleGOs[index] + .GetComponent(); + var isFriendly = resupplyTargets[index].team.IsFriendly(SelectedActor.team); + reticle.SetScaleAndLocation(resupplyTargets[index].CurrentPosition, 10f); + reticle.UpdateColorAndStyle(isFriendly, true, false); + ModInit.modLog?.Trace?.Write( + $"[HighlightPotentialTargets - ResupplyAbility ANY] Updating reticle at index {index}, isFriendly {isFriendly}."); } - - return false; } + + StrategicTargetIndicatorsManager.ShowRoot(); } } - return true; + } + + public override void OnAddToStack() + { + base.OnAddToStack(); + this.showTargetingText(this.abilitySelectionText); + var jumpdist = 0f; + if (SelectedActor is Mech mech) + { + jumpdist = mech.JumpDistance; + if (float.IsNaN(jumpdist)) jumpdist = 0f; + } + + var ranges = new List() + { + SelectedActor.MaxWalkDistance, + SelectedActor.MaxSprintDistance, + jumpdist, + this.FromButton.Ability.Def.IntParam2 + }; + var maxRange = ranges.Max(); + //CombatTargetingReticle.Instance.UpdateReticle(SelectedActor.CurrentPosition, maxRange, false); + CombatTargetingReticle.Instance.ShowRangeIndicators(SelectedActor.CurrentPosition, 0f, maxRange, false, true); + CombatTargetingReticle.Instance.UpdateRangeIndicator(SelectedActor.CurrentPosition, false, true); + CombatTargetingReticle.Instance.ShowReticle(); + + if (SelectedActor.IsMountedUnit()) + { + var carrier = Combat.FindCombatantByGUID(ModState.PositionLockMount[SelectedActor.GUID]); + this.ProcessClickedCombatant(carrier); + } + else if (SelectedActor.IsSwarmingUnit()) + { + var carrier = Combat.FindCombatantByGUID(ModState.PositionLockSwarm[SelectedActor.GUID]); + this.ProcessClickedCombatant(carrier); + } + else if (SelectedActor.IsGarrisoned()) + { + var carrier = Combat.FindCombatantByGUID(ModState.PositionLockGarrison[SelectedActor.GUID].BuildingGUID); + this.ProcessClickedCombatant(carrier); + } + else + { + this.HighlightPotentialTargets(); + } + } + + public override void OnInactivate() + { + base.OnInactivate(); + CombatTargetingReticle.Instance.HideReticle(); + StrategicTargetIndicatorsManager.HideReticles(); + HUD.InWorldMgr.HideMeleeTargets(); } public override bool ProcessClickedCombatant(ICombatant combatant) @@ -718,14 +551,6 @@ public override bool ProcessClickedCombatant(ICombatant combatant) //return false; // should i be returning true here? } - public override void OnInactivate() - { - base.OnInactivate(); - CombatTargetingReticle.Instance.HideReticle(); - StrategicTargetIndicatorsManager.HideReticles(); - HUD.InWorldMgr.HideMeleeTargets(); - } - [HarmonyPatch(typeof(SelectionState), "GetNewSelectionStateByType", new Type[] { @@ -749,5 +574,184 @@ public static void Postfix(SelectionState __instance, SelectionType type, Combat } } } + + public static class StrategicTargetIndicatorsManager + { + public static GameObject BaseReticleObject = null; + public static CombatHUD HUD = null; + public static List ReticleGOs = new List(); + + public static void Clear() + { + StrategicTargetIndicatorsManager.ReticleGOs.Clear(); + UnityEngine.Object.Destroy(StrategicTargetIndicatorsManager.BaseReticleObject); + StrategicTargetIndicatorsManager.BaseReticleObject = null; + StrategicTargetIndicatorsManager.HUD = null; + } + + public static void HideReticles() + { + foreach (var reticle in StrategicTargetIndicatorsManager.ReticleGOs) + { + reticle.gameObject.SetActive(false); + } + } + + public static void InitReticles(CombatHUD hud, int count) + { + if (StrategicTargetIndicatorsManager.BaseReticleObject == null) + { + StrategicTargetIndicatorsManager.BaseReticleObject = new GameObject("StrategicTargetingReticles"); //was TargetingCircles + } + StrategicTargetIndicatorsManager.HUD = hud; + + while (ReticleGOs.Count < count) + { + ModInit.modLog?.Trace?.Write($"[InitReticle] Need to init new reticles; have {ReticleGOs.Count}"); + GameObject reticleGO = new GameObject("StrategicReticle"); //was Circle + StrategicTargetReticle reticle = reticleGO.AddComponent(); + //reticle.transform.SetParent(reticleGO.transform); + reticle.Init(StrategicTargetIndicatorsManager.HUD); + StrategicTargetIndicatorsManager.ReticleGOs.Add(reticleGO); + } + } + + public static void ShowRoot() + { + StrategicTargetIndicatorsManager.BaseReticleObject.SetActive(true); + } + } + + public class StrategicTargetReticle : MonoBehaviour + { + public CombatHUD HUD; + public GameObject ReticleObject; + + + public void Init(CombatHUD hud) + { + this.HUD = hud; + ReticleObject = UnityEngine.Object.Instantiate(CombatTargetingReticle.Instance.Circles[0]); + ReticleObject.transform.SetParent(base.transform); + Vector3 localScale = ReticleObject.transform.localScale; + localScale.x = 2f; + localScale.z = 2f; + ReticleObject.transform.localScale = localScale; + ReticleObject.transform.localPosition = Vector3.zero; + } + + public void SetScaleAndLocation(Vector3 loc, float radius) + { + Vector3 localScale = ReticleObject.transform.localScale; + localScale.x = radius * 2f; + localScale.z = radius * 2f; + ReticleObject.transform.localScale = localScale; + base.transform.position = loc; + ReticleObject.SetActive(true); + ReticleObject.gameObject.SetActive(true); + ModInit.modLog?.Trace?.Write($"[SetScaleAndLocation] Set location to {loc}"); + } + + public void UpdateColorAndStyle(bool IsFriendly, bool IsResupply, bool IsResupplyInRange) + { + var dm = UnityGameInstance.BattleTechGame.DataManager; + + Transform[] childComponents; + + childComponents = ReticleObject.GetComponentsInChildren(true); + + for (int i = 0; i < childComponents.Length; i++) + { + if (childComponents[i].name == "Thumper1") + { + childComponents[i].gameObject.SetActive(false); + continue; + } + + if (childComponents[i].name == "Mortar1") + { + childComponents[i].gameObject.SetActive(true); + var decalsFromCircle = childComponents[i].GetComponentsInChildren(); + for (int j = 0; j < decalsFromCircle.Length; j++) + { + if (decalsFromCircle[j].name == "ReticleDecalCircle") + { + if (IsFriendly && !IsResupply) + { + if (!string.IsNullOrEmpty(ModInit.modSettings.MountIndicatorAsset)) + { + var newTexture = dm.GetObjectOfType(ModInit.modSettings.MountIndicatorAsset, + BattleTechResourceType.Texture2D); + if (newTexture != null) decalsFromCircle[j].DecalPropertyBlock.SetTexture("_MainTex", newTexture); + } + + if (ModInit.modSettings.MountIndicatorColor != null) + { + var customColor = new Color(ModInit.modSettings.MountIndicatorColor.Rf, + ModInit.modSettings.MountIndicatorColor.Gf, + ModInit.modSettings.MountIndicatorColor.Bf); + decalsFromCircle[j].DecalPropertyBlock.SetColor("_Color", customColor); + } + else + { + decalsFromCircle[j].DecalPropertyBlock.SetColor("_Color", Color.blue); + } + } + + if (IsResupply && !IsResupplyInRange) + { + if (!string.IsNullOrEmpty(ModInit.modSettings.ResupplyConfig.ResupplyIndicatorAsset)) + { + var newTexture = dm.GetObjectOfType(ModInit.modSettings.ResupplyConfig.ResupplyIndicatorAsset, + BattleTechResourceType.Texture2D); + if (newTexture != null) decalsFromCircle[j].DecalPropertyBlock.SetTexture("_MainTex", newTexture); + } + + if (ModInit.modSettings.MountIndicatorColor != null) + { + var customColor = new Color(ModInit.modSettings.ResupplyConfig.ResupplyIndicatorColor.Rf, + ModInit.modSettings.ResupplyConfig.ResupplyIndicatorColor.Gf, + ModInit.modSettings.ResupplyConfig.ResupplyIndicatorColor.Bf); + decalsFromCircle[j].DecalPropertyBlock.SetColor("_Color", customColor); + + } + else + { + decalsFromCircle[j].DecalPropertyBlock.SetColor("_Color", Color.blue); + } + } + else if (IsResupply) + { + if (!string.IsNullOrEmpty(ModInit.modSettings.ResupplyConfig.ResupplyIndicatorInRangeAsset)) + { + var newTexture = dm.GetObjectOfType(ModInit.modSettings.ResupplyConfig.ResupplyIndicatorInRangeAsset, + BattleTechResourceType.Texture2D); + if (newTexture != null) decalsFromCircle[j].DecalPropertyBlock.SetTexture("_MainTex", newTexture); + } + + if (ModInit.modSettings.MountIndicatorColor != null) + { + var customColor = new Color(ModInit.modSettings.ResupplyConfig.ResupplyIndicatorInRangeColor.Rf, + ModInit.modSettings.ResupplyConfig.ResupplyIndicatorInRangeColor.Gf, + ModInit.modSettings.ResupplyConfig.ResupplyIndicatorInRangeColor.Bf); + decalsFromCircle[j].DecalPropertyBlock.SetColor("_Color", customColor); + + } + else + { + decalsFromCircle[j].DecalPropertyBlock.SetColor("_Color", Color.blue); + } + } + decalsFromCircle[j].gameObject.SetActive(true); + } + else + { + decalsFromCircle[j].gameObject.SetActive(false); + } + } + } + } + } + } } } diff --git a/StrategicOperations/StrategicOperations/Patches/SimGamePatches.cs b/StrategicOperations/StrategicOperations/Patches/SimGamePatches.cs index 81e2285..782c1f6 100644 --- a/StrategicOperations/StrategicOperations/Patches/SimGamePatches.cs +++ b/StrategicOperations/StrategicOperations/Patches/SimGamePatches.cs @@ -31,7 +31,7 @@ public static void Postfix(AAR_ContractObjectivesWidget __instance) [HarmonyPatch(typeof(Contract), "CompleteContract", new Type[] {typeof(MissionResult), typeof(bool)})] public static class Contract_CompleteContract_Patch { - public static void Postfix(Contract __instance, MissionResult result, bool isGoodFaithEffort) + public static void Postfix(Contract __instance, MissionResult result, bool isGoodFaithEffort) { if (UnityGameInstance.BattleTechGame.Simulation == null) return; if (ModState.CommandUses.Count <= 0) return; diff --git a/StrategicOperations/StrategicOperations/Patches/StrategicOperationsPatches.cs b/StrategicOperations/StrategicOperations/Patches/StrategicOperationsPatches.cs index 43e3b63..bff67a0 100644 --- a/StrategicOperations/StrategicOperations/Patches/StrategicOperationsPatches.cs +++ b/StrategicOperations/StrategicOperations/Patches/StrategicOperationsPatches.cs @@ -24,1211 +24,689 @@ namespace StrategicOperations.Patches { public class StrategicOperationsPatches { - [HarmonyPatch(typeof(MechStartupInvocation), "Invoke")] - [HarmonyPriority(Priority.First)] - public static class MechStartupInvocation_Invoke + [HarmonyPatch(typeof(Ability), "Activate", + new Type[] { typeof(AbstractActor), typeof(ICombatant) })] + public static class Ability_Activate_ICombatant { - public static void Prefix(ref bool __runOriginal, MechStartupInvocation __instance, CombatGameState combatGameState) + public static void Postfix(Ability __instance, AbstractActor creator, ICombatant target) { - if (!__runOriginal) return; - Mech mech = combatGameState.FindActorByGUID(__instance.MechGUID) as Mech; - if (mech == null) - { - //InvocationMessage.logger.LogError("MechStartupInvocation.Invoke failed! Unable to Mech!"); - __runOriginal = false; - return; - } + if (creator == null) return; + if (UnityGameInstance.BattleTechGame.Combat.ActiveContract.ContractTypeValue.IsSkirmish) return; - if (ModState.ResupplyShutdownPhases.ContainsKey(mech.GUID)) + if (__instance.IsAvailable) { - var txt = new Text("RESUPPLY IN PROGRESS: ABORTING RESTART AND SINKING HEAT"); - mech.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage( - new ShowActorInfoSequence(mech, txt, FloatieMessage.MessageNature.Buff, - false))); + if (target is AbstractActor targetActor) + { + if (__instance.Def.Id == ModInit.modSettings.ResupplyConfig.ResupplyAbilityID) + { + ModInit.modLog?.Trace?.Write($"[Ability.Activate] Activating resupply from unit {creator.DisplayName} and resupplier {targetActor.DisplayName}."); + var phases = creator.ProcessResupplyUnit(targetActor); + creator.InitiateShutdownForPhases(phases); + targetActor.InitiateShutdownForPhases(phases); + } - DoneWithActorSequence doneWithActorSequence = (DoneWithActorSequence)mech.GetDoneWithActorOrders(); - MechHeatSequence mechHeatSequence = new MechHeatSequence(OwningMech: mech, - performHeatSinkStep: true, applyStartupHeatSinks: false, instigatorID: "STARTUP"); - doneWithActorSequence.AddChildSequence(mechHeatSequence, mechHeatSequence.MessageIndex); + if (creator.HasSwarmingUnits() && creator.GUID == targetActor.GUID) + { + ModInit.modLog?.Trace?.Write($"[Ability.Activate - Unit has sawemers]."); + var swarmingUnits = ModState.PositionLockSwarm.Where(x => x.Value == creator.GUID).ToList(); - InvocationStackSequenceCreated message = - new InvocationStackSequenceCreated(doneWithActorSequence, __instance); - combatGameState.MessageCenter.PublishMessage(message); - AddSequenceToStackMessage.Publish(combatGameState.MessageCenter, doneWithActorSequence); - __runOriginal = false; - return; - } - __runOriginal = true; - return; - } - } + if (__instance.Def.Id == ModInit.modSettings.BattleArmorDeSwarmRoll) + { + creator.ProcessDeswarmRoll(swarmingUnits); + creator.FlagForKnockdown(); + creator.HandleKnockdown(-1, creator.GUID, Vector2.one, null); + return; + } + else if (__instance.Def.Id == ModInit.modSettings.BattleArmorDeSwarmSwat) + { + creator.ProcessDeswarmSwat(swarmingUnits); + return; + } - [HarmonyPatch(typeof(Team), "AddUnit", new Type[] { typeof(AbstractActor) })] - public static class Team_AddUnit_Patch - { - public static void Postfix(Team __instance, AbstractActor unit) - { - if (__instance.Combat.TurnDirector.CurrentRound > 1) - { - __instance.Combat.UpdateResupplyTeams(); - __instance.Combat.UpdateResupplyAbilitiesGetAllLivingActors(); - } + else if (__instance.Def.Id == ModInit.modSettings.DeswarmMovementConfig.AbilityDefID) + { + ModInit.modLog?.Trace?.Write($"[Ability.Activate - BattleArmorDeSwarm Movement]."); + creator.ProcessDeswarmMovement( + swarmingUnits); // need to patch ActorMovementSequence complete AND JumpSequence complete AND DFASequencecomplete, and then do magic logic in there. or just do it on + return; //return to avoid ending turn for player below. making AI use this properly is gonna suck hind tit. + } - if (__instance.IsLocalPlayer) - { - if (unit is Mech && !(unit is TrooperSquad) && !unit.IsCustomUnitVehicle()) - { - if (!string.IsNullOrEmpty(ModInit.modSettings.BattleArmorDeSwarmSwat)) - { - if (unit.GetPilot().Abilities - .All(x => x.Def.Id != ModInit.modSettings.BattleArmorDeSwarmSwat) && - unit.ComponentAbilities.All(y => - y.Def.Id != ModInit.modSettings.BattleArmorDeSwarmSwat)) + if (creator is Mech mech) { - unit.Combat.DataManager.AbilityDefs.TryGet(ModInit.modSettings.BattleArmorDeSwarmSwat, - out var def); - var ability = new Ability(def); - ModInit.modLog?.Trace?.Write( - $"[Team.AddUnit] Adding {ability.Def?.Description?.Id} to {unit.Description?.Name}."); - ability.Init(unit.Combat); - unit.GetPilot().Abilities.Add(ability); - unit.GetPilot().ActiveAbilities.Add(ability); + mech.GenerateAndPublishHeatSequence(-1, true, false, mech.GUID); + } + + if (creator.team.IsLocalPlayer) + { + var sequence = creator.DoneWithActor(); + creator.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(sequence)); + //creator.OnActivationEnd(creator.GUID, -1); } - } - if (!string.IsNullOrEmpty(ModInit.modSettings.BattleArmorDeSwarmRoll)) + return; + } + if (__instance.Def.Id == ModInit.modSettings.BattleArmorMountAndSwarmID) { - if (unit.GetPilot().Abilities - .All(x => x.Def.Id != ModInit.modSettings.BattleArmorDeSwarmRoll) && - unit.ComponentAbilities.All(y => - y.Def.Id != ModInit.modSettings.BattleArmorDeSwarmRoll)) + if (!creator.IsSwarmingUnit() && !creator.IsMountedUnit()) { - unit.Combat.DataManager.AbilityDefs.TryGet(ModInit.modSettings.BattleArmorDeSwarmRoll, - out var def); - var ability = new Ability(def); - ModInit.modLog?.Trace?.Write( - $"[Team.AddUnit] Adding {ability.Def?.Description?.Id} to {unit.Description?.Name}."); - ability.Init(unit.Combat); - unit.GetPilot().Abilities.Add(ability); - unit.GetPilot().ActiveAbilities.Add(ability); + if (target.team.IsFriendly(creator.team)) + { + //creator.ProcessMountFriendly(targetActor); // old handling, now try movement invocation. + MessageCenterMessage invocation = + new StrategicMovementInvocation(creator, true, targetActor, true, true); + creator.Combat.MessageCenter.PublishInvocationExternal(invocation); + } + + else if (target.team.IsEnemy(creator.team) && creator is Mech creatorMech && + creatorMech.CanSwarm()) + { + //creatorMech.ProcessSwarmEnemy(targetActor); + MessageCenterMessage invocation = + new StrategicMovementInvocation(creator, true, targetActor, false, true); + creator.Combat.MessageCenter.PublishInvocationExternal(invocation); + } } - } - } + else if (creator.IsSwarmingUnit()) + { + creator.DismountBA(targetActor, Vector3.zero, false); + } + else if (creator.IsMountedUnit()) + { + if (creator is TrooperSquad squad) + { + //ModInit.modLog?.Trace?.Write($"[Ability.Activate] Called DetachFromCarrier."); + //squad.DismountBA(targetActor, Vector3.zero, true, false, false); + squad.DetachFromCarrier(targetActor, true); + } + //creator.DismountBA(targetActor); - if (!string.IsNullOrEmpty(ModInit.modSettings.DeswarmMovementConfig.AbilityDefID)) - { - if (unit.GetPilot().Abilities - .All(x => x.Def.Id != ModInit.modSettings.DeswarmMovementConfig.AbilityDefID) && - unit.ComponentAbilities.All(y => - y.Def.Id != ModInit.modSettings.DeswarmMovementConfig.AbilityDefID)) - { - unit.Combat.DataManager.AbilityDefs.TryGet(ModInit.modSettings.DeswarmMovementConfig.AbilityDefID, - out var def); - var ability = new Ability(def); - ModInit.modLog?.Trace?.Write( - $"[Team.AddUnit] Adding {ability.Def?.Description?.Id} to {unit.Description?.Name}."); - ability.Init(unit.Combat); - unit.GetPilot().Abilities.Add(ability); - unit.GetPilot().ActiveAbilities.Add(ability); + } } - } - - //add a parent component to strafe/spawn pilot abilities. even though it wont be used. - - //need to disentangle from abilifier though. - if (false) - { - var list = unit.GetPilot().Abilities; - for (var index = list.Count - 1; index >= 0; index--) + else if (__instance.Def.Id == ModInit.modSettings.AirliftAbilityID) { - var pilotAbility = list[index]; - if (pilotAbility.Def.specialRules == AbilityDef.SpecialRules.Strafe || - pilotAbility.Def.specialRules == AbilityDef.SpecialRules.SpawnTurret) + ModInit.modLog?.Trace?.Write($"[Ability.Activate] - Creating airlift invocation for carrier {creator.DisplayName} and target {targetActor.DisplayName}."); + if (target.team.IsFriendly(creator.team)) { - var ability = new Ability(pilotAbility.Def); - ModInit.modLog?.Trace?.Write( - $"[Team.AddUnit] moving {pilotAbility.Def?.Description?.Id} to {unit.Description?.Name} component abilities."); - - var abilityComponent = unit.allComponents.FirstOrDefault(z => - ModInit.modSettings.crewOrCockpitCustomID.Any((string x) => - z.componentDef.GetComponents() - .Any((Category c) => c.CategoryID == x))); - if (abilityComponent == null) + //if dropoff, just drop dont do movement? + if (!creator.HasMovedThisRound && targetActor.IsAirlifted()) { - ModInit.modLog?.Info?.Write($"component was null; no CriticalComponents?"); + targetActor.DetachFromAirliftCarrier(creator, true); } - - if (abilityComponent?.parent == null) + else { - ModInit.modLog?.Info?.Write($"component parent was null; no parent actor???"); +// if (!targetActor.StatCollection.GetValue("irbtmu_immobile_unit")) +// { +// targetActor.StatCollection.Set("irbtmu_immobile_unit", true); +// targetActor.ResetPathing(); +// ModState.UnitPendingAirliftInvocation = targetActor.GUID; +// } + //ModState.AirliftDropoffEvasion = creator.EvasivePipsCurrent; + MessageCenterMessage invocation = + new StrategicMovementInvocation(creator, true, targetActor, true, false); + creator.Combat.MessageCenter.PublishInvocationExternal(invocation); } + } - ability.Init(unit.Combat, abilityComponent); - unit.GetPilot().Abilities.Remove(pilotAbility); - unit.GetPilot().ActiveAbilities.Remove(pilotAbility); - unit.GetPilot().Abilities.Add(ability); - unit.GetPilot().ActiveAbilities.Add(ability); + else if (target.team.IsEnemy(creator.team)) + { + if (!creator.HasMovedThisRound && targetActor.IsAirlifted()) + { + targetActor.DetachFromAirliftCarrier(creator, false); + } + else + { + MessageCenterMessage invocation = + new StrategicMovementInvocation(creator, true, targetActor, false, false); + creator.Combat.MessageCenter.PublishInvocationExternal(invocation); + } + } + //do airlifty things here + } + } + if (target is BattleTech.Building building && + __instance.Def.Id == ModInit.modSettings.BattleArmorMountAndSwarmID && creator is TrooperSquad squad2) + { + if (!building.HasGarrisonedUnits()) + { + MessageCenterMessage invocation = + new StrategicMovementInvocation(squad2, true, building, true, true); + squad2.Combat.MessageCenter.PublishInvocationExternal(invocation); + } + else + { + if (squad2.IsGarrisonedInTargetBuilding(building)) + { + squad2.DismountGarrison(building, Vector3.zero, false); } } } - - return; } + } + } - if (unit is Mech mech) - { - if (mech.EncounterTags.Contains("SpawnedFromAbility")) return; - } - - AI_Utils.GenerateAIStrategicAbilities(unit); - } - } - - [HarmonyPatch(typeof(AbstractActor), "InitEffectStats", - new Type[] { })] - public static class AbstractActor_InitEffectStats - { - public static void Postfix(AbstractActor __instance) - { - __instance.StatCollection.AddStatistic("CanSwarm", false); - __instance.StatCollection.AddStatistic("CanAirliftHostiles", false); - __instance.StatCollection.AddStatistic("BattleArmorInternalMountsOnly", false); - __instance.StatCollection.AddStatistic("InternalBattleArmorSquadCap", 0); - __instance.StatCollection.AddStatistic("InternalBattleArmorSquads", 0); - __instance.StatCollection.AddStatistic("HasBattleArmorMounts", false); - __instance.StatCollection.AddStatistic("HasExternalMountedBattleArmor", false); - __instance.StatCollection.AddStatistic("IsBattleArmorHandsy", false); - __instance.StatCollection.AddStatistic("IsUnmountableBattleArmor", false); - __instance.StatCollection.AddStatistic("IsUnswarmableBattleArmor", false); - __instance.StatCollection.AddStatistic("HasFiringPorts", false); - //__instance.StatCollection.AddStatistic("BattleArmorMount", false); - //__instance.StatCollection.AddStatistic("BattleArmorDeSwarmerSwat", 0.3f); - //__instance.StatCollection.AddStatistic("BattleArmorDeSwarmerRollInitPenalty", 0); - //__instance.StatCollection.AddStatistic("BattleArmorDeSwarmerSwatInitPenalty", 0); - //__instance.StatCollection.AddStatistic("BattleArmorDeSwarmerSwatDamage", 0f); - //__instance.StatCollection.AddStatistic("BattleArmorDeSwarmerRoll", 0.5f); - //__instance.StatCollection.AddStatistic("MovementDeSwarmMinChance", 0.0f); - //__instance.StatCollection.AddStatistic("MovementDeSwarmMaxChance", 1.0f); - //__instance.StatCollection.AddStatistic("MovementDeSwarmEvasivePipsFactor", 0f); - //__instance.StatCollection.AddStatistic("MovementDeSwarmEvasiveJumpMovementMultiplier", 1.0f); - //__instance.StatCollection.AddStatistic("Airlifting", false); - __instance.StatCollection.AddStatistic("InternalLiftCapacity", 0); - __instance.StatCollection.AddStatistic("InternalLiftCapacityUsed", 0); - __instance.StatCollection.AddStatistic("ExternalLiftCapacity", 0); - __instance.StatCollection.AddStatistic("ExternalLiftCapacityUsed", 0); - __instance.StatCollection.AddStatistic("OverrideGlobalCapacity", false); - __instance.StatCollection.AddStatistic("AAAFactor", 0f); - __instance.StatCollection.AddStatistic("UseAAAFactor", false); - } - } - [HarmonyPatch(typeof(AbstractActor), "FlagForDeath", - new Type[] {typeof(string), typeof(DeathMethod), typeof(DamageType), typeof(int), typeof(int), typeof(string), typeof(bool) })] - public static class AbstractActor_FlagForDeath + [HarmonyPatch(typeof(Ability), "Activate", + new Type[] { typeof(AbstractActor), typeof(Vector3), typeof(Vector3) })] + public static class Ability_Activate_TwoPoints { - public static void Postfix(AbstractActor __instance, string reason, DeathMethod deathMethod, DamageType damageType, int location, int stackItemID, string attackerID, bool isSilent) + public static void Prefix(ref bool __runOriginal, Ability __instance, AbstractActor creator, Vector3 positionA, Vector3 positionB) { - if (__instance.IsAirlifted()) + if (!__runOriginal) return; + ModInit.modLog?.Info?.Write($"[Ability.Activate - 2pts] Running Ability.Activate; check if skirmish."); // need to add blocks for self-apply EffectData + if (__instance.Combat.ActiveContract.ContractTypeValue.IsSkirmish) { - var carrier = __instance.Combat.FindActorByGUID(ModState.AirliftTrackers[__instance.GUID].CarrierGUID); - __instance.DetachFromAirliftCarrier(carrier, false); // try to drop destroyed units from airlifter + __runOriginal = true; + return; } - } - } - - [HarmonyPatch(typeof(SimGameState), "RequestDataManagerResources")] - public static class SimGameState_RequestDataManagerResources_Patch - { - public static void Postfix(SimGameState __instance) - { - LoadRequest loadRequest = __instance.DataManager.CreateLoadRequest(); - loadRequest.AddAllOfTypeBlindLoadRequest(BattleTechResourceType.TurretDef, - new bool?(true)); //but TurretDefs - loadRequest.ProcessRequests(10U); - } - } - - [HarmonyPatch(typeof(EncounterLayerData), "BuildItemRegistry", new Type[]{typeof(CombatGameState)})] - public static class EncounterLayerData_BuildItemRegistry - { - public static void Postfix(EncounterLayerData __instance, CombatGameState combat) - { - foreach (var team in combat.Teams) + if (!__instance.IsAvailable) { - if (!team.IsLocalPlayer && TeamDefinition.FullTeamDefinitionGuidNames.ContainsKey(team.GUID)) - { - Utils.FetchAISupportTeam(team); - } + ModInit.modLog?.Info?.Write( + $"[Ability.Activate - 2pts] Ability {__instance.Def.Description.Name} was unavailable, continuing to vanilla handling."); + __runOriginal = true; + return; } - } - } - [HarmonyPatch(typeof(TurnDirector), "OnInitializeContractComplete")] - public static class TurnDirector_OnInitializeContractComplete - { - public static void Postfix(TurnDirector __instance, MessageCenterMessage message) - { - if (__instance.Combat.ActiveContract.ContractTypeValue.IsSkirmish) return; - var dm = __instance.Combat.DataManager; - LoadRequest loadRequest = dm.CreateLoadRequest(); + AbilityDef.SpecialRules specialRules = __instance.Def.specialRules; - loadRequest.AddBlindLoadRequest(BattleTechResourceType.PilotDef, "pilot_sim_starter_dekker"); - ModInit.modLog?.Info?.Write($"Added loadrequest for PilotDef: pilot_sim_starter_dekker (hardcoded)"); - if (!string.IsNullOrEmpty(ModInit.modSettings.customSpawnReticleAsset)) - { - loadRequest.AddBlindLoadRequest(BattleTechResourceType.Texture2D, ModInit.modSettings.customSpawnReticleAsset); - ModInit.modLog?.Info?.Write($"Added loadrequest for Texture2D: {ModInit.modSettings.customSpawnReticleAsset}"); - } - if (!string.IsNullOrEmpty(ModInit.modSettings.MountIndicatorAsset)) - { - loadRequest.AddBlindLoadRequest(BattleTechResourceType.Texture2D, ModInit.modSettings.MountIndicatorAsset); - ModInit.modLog?.Info?.Write($"Added loadrequest for Texture2D: {ModInit.modSettings.MountIndicatorAsset}"); - } - foreach (var abilityDef in dm.AbilityDefs.Where(x => x.Key.StartsWith("AbilityDefCMD_"))) + var showPopup = false; + if (ModInit.modSettings.BeaconExclusionConfig.TryGetValue(creator.Combat.ActiveContract.ContractTypeValue.Name, out var configType)) { - var ability = new Ability(abilityDef.Value); - if (string.IsNullOrEmpty(ability.Def?.ActorResource)) continue; - if (!string.IsNullOrEmpty(ability.Def.getAbilityDefExtension().CMDPilotOverride)) - { - var pilotID = ability.Def.getAbilityDefExtension().CMDPilotOverride; - ModInit.modLog?.Info?.Write($"Added loadrequest for PilotDef: {pilotID}"); - loadRequest.AddBlindLoadRequest(BattleTechResourceType.PilotDef, pilotID); - } - - if (ability.Def.ActorResource.StartsWith("mechdef_")) - { - ModInit.modLog?.Info?.Write($"Added loadrequest for MechDef: {ability.Def.ActorResource}"); - loadRequest.AddBlindLoadRequest(BattleTechResourceType.MechDef, ability.Def.ActorResource); - } - - if (ability.Def.ActorResource.StartsWith("vehicledef_")) - { - ModInit.modLog?.Info?.Write($"Added loadrequest for VehicleDef: {ability.Def.ActorResource}"); - loadRequest.AddBlindLoadRequest(BattleTechResourceType.VehicleDef, ability.Def.ActorResource); - } - - if (ability.Def.ActorResource.StartsWith("turretdef_")) + if (creator.team.IsLocalPlayer) { - ModInit.modLog?.Info?.Write($"Added loadrequest for TurretDef: {ability.Def.ActorResource}"); - loadRequest.AddBlindLoadRequest(BattleTechResourceType.TurretDef, ability.Def.ActorResource); + if (specialRules == AbilityDef.SpecialRules.Strafe && configType.ExcludedPlayerStrafe) showPopup = true; + else if (specialRules == AbilityDef.SpecialRules.SpawnTurret && configType.ExcludedPlayerSpawn) showPopup = true; } } - foreach (var beacon in Utils.GetOwnedDeploymentBeacons()) + else if (ModInit.modSettings.BeaconExclusionConfig.TryGetValue(creator.Combat.ActiveContract.Override.ID, out var configID)) { - var pilotID = beacon.Def.ComponentTags.FirstOrDefault(x => - x.StartsWith("StratOpsPilot_")) - ?.Remove(0, 14); - if (!string.IsNullOrEmpty(pilotID)) + if (creator.team.IsLocalPlayer) { - ModInit.modLog?.Info?.Write($"Added loadrequest for PilotDef: {pilotID}"); - loadRequest.AddBlindLoadRequest(BattleTechResourceType.PilotDef, pilotID); + if (specialRules == AbilityDef.SpecialRules.Strafe && configID.ExcludedPlayerStrafe) showPopup = true; + else if (specialRules == AbilityDef.SpecialRules.SpawnTurret && configID.ExcludedPlayerSpawn) showPopup = true; } + } - var id = beacon.Def.ComponentTags.FirstOrDefault(x => - x.StartsWith("mechdef_") || x.StartsWith("vehicledef_") || - x.StartsWith("turretdef_")); - if (string.IsNullOrEmpty(id)) continue; + //if ((specialRules == AbilityDef.SpecialRules.Strafe || specialRules == AbilityDef.SpecialRules.SpawnTurret) && (ModInit.modSettings.BeaconExcludedContractIDs.Contains(__instance.Combat.ActiveContract.Override.ID) || ModInit.modSettings.BeaconExcludedContractTypes.Contains(__instance.Combat.ActiveContract.ContractTypeValue.Name))) + if (showPopup) + { + var popup = GenericPopupBuilder.Create(GenericPopupType.Info, $"Ability {__instance.Def.Description.Name} is unavailable during this contract!"); + popup.AddButton("Confirm", null, true, null); + popup.IsNestedPopupWithBuiltInFader().CancelOnEscape().Render(); + ModInit.modLog?.Info?.Write($"[Ability.Activate - 2pts] Ability {__instance.Def.Description.Name} unavailable due to exclusion settings. Aborting."); + __runOriginal = false; + return; + } - if (id.StartsWith("mechdef_")) - { - ModInit.modLog?.Info?.Write($"Added loadrequest for MechDef: {id}"); - loadRequest.AddBlindLoadRequest(BattleTechResourceType.MechDef, id); - } - else if (id.StartsWith("vehicledef_")) - { - ModInit.modLog?.Info?.Write($"Added loadrequest for VehicleDef: {id}"); - loadRequest.AddBlindLoadRequest(BattleTechResourceType.VehicleDef, id); - } - else if (id.StartsWith("turretdef_")) - { - ModInit.modLog?.Info?.Write($"Added loadrequest for TurretDef: {id}"); - loadRequest.AddBlindLoadRequest(BattleTechResourceType.TurretDef, id); - } + // maybe rewrite ActivateStrafe and ActivateSpawnTurret to pass actor? + if (specialRules == AbilityDef.SpecialRules.Strafe) + { + //Utils._activateStrafeMethod.Invoke(__instance, new object[] { creator.team, positionA, positionB, __instance.Def.FloatParam1 }); + //__instance.ActivateStrafe(creator.team, positionA, positionB, __instance.Def.FloatParam1); + __instance.ActivateStrafeFromActor(creator, creator.team, positionA, positionB, __instance.Def.FloatParam1); + ModInit.modLog?.Info?.Write($"[Ability.Activate - 2pts] {creator.Description?.Name}: ActivateStrafe invoked from Ability.Activate. Distance was {Vector3.Distance(positionA, positionB)}"); + __instance.Combat.MessageCenter.PublishMessage(new AbilityActivatedMessage(creator.GUID, + creator.GUID, __instance.Def.Id, positionA, positionB)); + __instance.ActivateCooldown(); + __instance.ApplyCreatorEffects(creator); + __runOriginal = false; + return; + } + else if (specialRules == AbilityDef.SpecialRules.SpawnTurret) + { + //Utils._activateSpawnTurretMethod.Invoke(__instance, new object[] { creator.team, positionA, positionB }); + //__instance.ActivateSpawnTurret(creator.team, positionA, positionB); + __instance.ActivateSpawnTurretFromActor(creator, creator.team, positionA, positionB); + ModInit.modLog?.Info?.Write($"[Ability.Activate - 2pts] {creator.Description?.Name}: ActivateSpawnTurret invoked from Ability.Activate. Distance was {Vector3.Distance(positionA, positionB)}"); + __instance.Combat.MessageCenter.PublishMessage(new AbilityActivatedMessage(creator.GUID, + creator.GUID, __instance.Def.Id, positionA, positionB)); + __instance.ActivateCooldown(); + __instance.ApplyCreatorEffects(creator); + __runOriginal = false; + return; } - loadRequest.ProcessRequests(1000U); + __runOriginal = true; + return; } } - //the following two patched on gamerep.update are solely to suppress the "safety teleport" for strafing units since they spawn offmap and move on their own - [HarmonyPatch(typeof(CustomMechRepresentation), "GameRepresentation_Update")] - public static class CustomMechRepresentation_GameRepresentation_Update + [HarmonyPatch(typeof(Ability), "ActivateSpawnTurret")] + public static class Ability_ActivateSpawnTurret { - public static void Prefix(ref bool __runOriginal, CustomMechRepresentation __instance) + public static void Prefix(ref bool __runOriginal, Ability __instance, Team team, Vector3 positionA, Vector3 positionB) { if (!__runOriginal) return; - if (__instance.__parentActor == null) + if (__instance.Combat.ActiveContract.ContractTypeValue.IsSkirmish) { __runOriginal = true; return; } + ModInit.modLog?.Info?.Write($"[Ability_ActivateSpawnTurret] Running Ability.ActivateSpawnTurret"); var combat = UnityGameInstance.BattleTechGame.Combat; - if (combat == null) + var dm = combat.DataManager; + var sim = UnityGameInstance.BattleTechGame.Simulation; + + var actorResource = __instance.Def.ActorResource; + var supportHeraldryDef = Utils.SwapHeraldryColors(team.HeraldryDef, dm); + //var actorGUID = __instance.parentComponent.GUID.Substring("Abilifier_ActorLink-".Length); + var quid = ""; + if (__instance?.parentComponent?.parent?.GUID != null) + { + ModInit.modLog?.Info?.Write($"[Ability_ActivateSpawnTurret] using {__instance.parentComponent.parent.GUID} from component parent"); + quid = __instance.Generate2PtCMDQuasiGUID(__instance.parentComponent.parent.GUID, positionA, positionB); + + } + else if (__instance?.parentComponent?.GUID != null) + { + var quidFromAbilifier = __instance.parentComponent.GUID.Substring(20); + ModInit.modLog?.Info?.Write($"[Ability_ActivateSpawnTurret] using {__instance.parentComponent.GUID} from abilifier component guid; processed down to {quidFromAbilifier}"); + quid = __instance.Generate2PtCMDQuasiGUID(quidFromAbilifier, positionA, positionB); + } + ModInit.modLog?.Info?.Write($"[Ability_ActivateSpawnTurret] Trying to find params with key {quid}"); + if (!ModState.StoredCmdParams.ContainsKey(quid)) { + ModInit.modLog?.Info?.Write($"[Ability_ActivateSpawnTurret] No strafe params stored, wtf"); __runOriginal = true; return; } - if (combat.ActiveContract.ContractTypeValue.IsSkirmish) + var playerControl = Utils.ShouldPlayerControlSpawn(team, __instance, quid); + var teamSelection = playerControl ? team : team.SupportTeam;//.SupportTeam; change to player control? + if (!team.IsLocalPlayer) + { + teamSelection = team as AITeam; + } + if (!ModState.StoredCmdParams.ContainsKey(quid)) { + ModInit.modLog?.Info?.Write($"[Ability_ActivateSpawnTurret] No spawn params stored, wtf"); __runOriginal = true; return; } - var registry = combat.ItemRegistry; - - if (__instance.__parentActor?.spawnerGUID == null) - { - //ModInit.modLog?.Info?.Write($"Couldn't find UnitSpawnPointGameLogic for {____parentActor?.DisplayName}. Should be CMD Ability actor; skipping safety teleport!"); - __runOriginal = false; - return; - } - - if (registry.GetItemByGUID(__instance.__parentActor?.spawnerGUID) != null) + if (!string.IsNullOrEmpty(ModState.StoredCmdParams[quid].ActorResource)) { - __runOriginal = true; - return; + actorResource = ModState.StoredCmdParams[quid].ActorResource; + //ModState.PopupActorResource = ""; } - __runOriginal = false; - return; - //ModInit.modLog?.Info?.Write($"Couldn't find UnitSpawnPointGameLogic for {____parentActor?.DisplayName}. Should be CMD Ability actor; skipping safety teleport!"); - } - - public static void Postfix(CustomMechRepresentation __instance) - { - if (__instance.HeightController.isInChangeHeight) + if (ModState.DeploymentAssetsStats.Any(x => x.ID == actorResource) && team.IsLocalPlayer && !ModState.DeferredSpawnerFromDelegate) { - var combat = __instance.parentActor.Combat; - if (__instance.parentActor.HasAirliftedUnits()) + var assetStatInfo = ModState.DeploymentAssetsStats.FirstOrDefault(x => x.ID == actorResource); + if (assetStatInfo != null) { - var airliftedUnits = ModState.AirliftTrackers.Where(x => - x.Value.CarrierGUID == __instance.parentActor.GUID); - foreach (var trackerInfo in airliftedUnits) + assetStatInfo.contractUses -= 1; + if (assetStatInfo.consumeOnUse) { - var targetActor = combat.FindActorByGUID(trackerInfo.Key); - if (targetActor == null) continue; - var pos = Vector3.zero; - if (__instance.parentActor is CustomMech mech) - { - pos = __instance.parentActor.CurrentPosition + Vector3.down * trackerInfo.Value.Offset + - Vector3.up * mech.custGameRep.HeightController.CurrentHeight; - targetActor.TeleportActorVisual(pos); - if (targetActor is CustomMech customMech) - { - customMech.custGameRep.j_Root.localRotation = Quaternion.identity; - } - - targetActor.GameRep.transform.rotation = - __instance.parentActor.GameRep.transform.rotation; - targetActor.CurrentRotation = __instance.parentActor.CurrentRotation; - } - else - { - pos = __instance.parentActor.CurrentPosition + Vector3.down * trackerInfo.Value.Offset; - targetActor.TeleportActorVisual(pos); - if (targetActor is CustomMech customMech) - { - customMech.custGameRep.j_Root.localRotation = Quaternion.identity; - } - - targetActor.GameRep.transform.rotation = - __instance.parentActor.GameRep.transform.rotation; - targetActor.CurrentRotation = __instance.parentActor.CurrentRotation; - } - - targetActor.MountedEvasion(__instance.parentActor); - ModInit.modLog?.Debug?.Write( - $"[CustomMechRepresentation_GameRepresentation_Update] PositionLockMount- Setting airlifted unit {targetActor.DisplayName} position to same as carrier unit {__instance.parentActor.DisplayName}"); - - ModState.CachedUnitCoordinates[__instance.parentActor.GUID] = __instance.parentActor.CurrentPosition; + sim?.CompanyStats.ModifyStat("StratOps", -1, assetStatInfo.stat, + StatCollection.StatOperation.Int_Subtract, 1); } } + ModInit.modLog?.Info?.Write($"[Ability_ActivateSpawnTurret] Decrementing count of {actorResource} in deploymentAssetsDict"); + } - if (__instance.parentActor.HasMountedUnits()) - { - var targetActorGUIDs = - ModState.PositionLockMount.Where(x => x.Value == __instance.parentActor.GUID); - foreach (var targetActorGUID in targetActorGUIDs) - { - var targetActor = combat.FindActorByGUID(targetActorGUID.Key); - if (targetActor == null) continue; - var pos = Vector3.zero; - if (__instance.parentActor is CustomMech mech) - { - pos = __instance.parentActor.CurrentPosition + - Vector3.up * mech.custGameRep.HeightController.CurrentHeight; - targetActor.TeleportActorVisual(pos); - } - else - { - targetActor.TeleportActorVisual(__instance.parentActor.CurrentPosition); - } - - targetActor.MountedEvasion(__instance.parentActor); - ModInit.modLog?.Debug?.Write( - $"[CustomMechRepresentation_GameRepresentation_Update] PositionLockMount- Setting riding unit {targetActor.DisplayName} position to same as carrier unit {__instance.parentActor.DisplayName}"); + var instanceGUID = + $"{__instance.Def.Id}_{team.Name}_{actorResource}_{positionA}_{positionB}@{actorResource}"; - ModState.CachedUnitCoordinates[__instance.parentActor.GUID] = __instance.parentActor.CurrentPosition; - } - } + if (ModState.DeferredInvokeSpawns.All(x => x.Key != instanceGUID) && !ModState.DeferredSpawnerFromDelegate) + { + ModInit.modLog?.Info?.Write( + $"[Ability_ActivateSpawnTurret] Deferred Spawner = null, creating delegate and returning false. Delegate should spawn {actorResource}"); - // removed return/else so swarming units are locked to carrier even if carrier has mounted units. derp. - if (__instance.parentActor.HasSwarmingUnits()) - { - var targetActorGUIDs = - ModState.PositionLockSwarm.Where(x => x.Value == __instance.parentActor.GUID); - foreach (var targetActorGUID in targetActorGUIDs) - { - var targetActor = combat.FindActorByGUID(targetActorGUID.Key); - if (targetActor == null) continue; - targetActor.TeleportActorVisual(__instance.parentActor.CurrentPosition); - targetActor.MountedEvasion(__instance.parentActor); - ModInit.modLog?.Debug?.Write( - $"[CustomMechRepresentation_GameRepresentation_Update] PositionLockMount- Setting riding unit {targetActor.DisplayName} position to same as carrier unit {__instance.parentActor.DisplayName}"); + void DeferredInvokeSpawn() => __instance.ActivateSpawnTurret(team, positionA, positionB);//Utils._activateSpawnTurretMethod.Invoke(__instance, new object[] { team, positionA, positionB }); - ModState.CachedUnitCoordinates[__instance.parentActor.GUID] = __instance.parentActor.CurrentPosition; - } - } + var kvp = new KeyValuePair(instanceGUID, DeferredInvokeSpawn); + ModState.DeferredInvokeSpawns.Add(kvp); + Utils.SpawnFlares(__instance, positionA, positionB, ModInit.modSettings.flareResourceID, 1, __instance.Def.ActivationETA, team.IsLocalPlayer); + // var flares = Traverse.Create(__instance).Method("SpawnFlares", + // new object[] {positionA, positionA, __instance.Def., 1, 1}); + // flares.GetValue(); + __runOriginal = false; + return; } - } - } - [HarmonyPatch(typeof(GameRepresentation), "Update")] - public static class GameRepresentation_Update - { - public static void Prefix(ref bool __runOriginal, GameRepresentation __instance) - { - if (!__runOriginal) return; - if (__instance._parentActor == null) + if (!string.IsNullOrEmpty(ModState.DeferredActorResource)) { - __runOriginal = true; - return; + actorResource = ModState.DeferredActorResource; + ModInit.modLog?.Info?.Write($"[Ability_ActivateSpawnTurret] {actorResource} restored from deferredActorResource"); } - var combat = UnityGameInstance.BattleTechGame.Combat; - if (combat == null) + var pilotID = "pilot_sim_starter_dekker"; + if (!string.IsNullOrEmpty(ModState.StoredCmdParams[quid].PilotOverride)) { - __runOriginal = true; - return; + //pilotID = ModState.PilotOverride; + pilotID = ModState.StoredCmdParams[quid].PilotOverride; } - - if (combat.ActiveContract.ContractTypeValue.IsSkirmish) + else if (!string.IsNullOrEmpty(__instance.Def.getAbilityDefExtension().CMDPilotOverride)) { - __runOriginal = true; - return; + pilotID = __instance.Def.getAbilityDefExtension().CMDPilotOverride; } - var registry = combat.ItemRegistry; + ModInit.modLog?.Info?.Write($"[Ability_ActivateSpawnTurret] Pilot should be {pilotID}"); + var cmdLance = new Lance(); + if (playerControl) + { + if (team.lances.Count > 0) cmdLance = team.lances[0]; + else + { + cmdLance = new Lance(); + ModInit.modLog?.Error?.Write($"[Ability_ActivateSpawnTurret] No lances found for team! This is fucked up!"); + } + } + else cmdLance = Utils.CreateOrFetchCMDLance(teamSelection); + + Quaternion quaternion = Quaternion.LookRotation(positionB - positionA); - if (__instance._parentActor?.spawnerGUID == null) + if (actorResource.StartsWith("mechdef_") || actorResource.StartsWith("vehicledef_")) { - //ModInit.modLog?.Info?.Write($"Couldn't find UnitSpawnPointGameLogic for {____parentActor?.DisplayName}. Should be CMD Ability actor; skipping safety teleport!"); + ModInit.modLog?.Info?.Write($"[Ability_ActivateSpawnTurret] Attempting to spawn {actorResource} as mech."); + var spawner = new Classes.CustomSpawner(team, __instance, combat, actorResource, cmdLance, teamSelection, positionA, quaternion, supportHeraldryDef, pilotID, playerControl); + spawner.SpawnBeaconUnitAtLocation(); __runOriginal = false; return; } - if (registry.GetItemByGUID(__instance._parentActor?.spawnerGUID) != null) +#if NO_CAC + if (actorResource.StartsWith("mechdef_") && false) { - __runOriginal = true; - return; - } + ModInit.modLog?.Info?.Write($"Attempting to spawn {actorResource} as mech."); + dm.MechDefs.TryGet(actorResource, out var supportActorMechDef); + supportActorMechDef.Refresh(); + var customEncounterTags = new TagSet(teamSelection.EncounterTags) {"SpawnedFromAbility"}; + var supportActorMech = ActorFactory.CreateMech(supportActorMechDef, supportPilotDef, + customEncounterTags, teamSelection.Combat, + teamSelection.GetNextSupportUnitGuid(), "", supportHeraldryDef); + supportActorMech.Init(positionA, quaternion.eulerAngles.y, false); + supportActorMech.InitGameRep(null); - __runOriginal = false; - return; - //return registry.GetItemByGUID(__instance._parentActor?.spawnerGUID) != null; - //ModInit.modLog?.Info?.Write($"Couldn't find UnitSpawnPointGameLogic for {____parentActor?.DisplayName}. Should be CMD Ability actor; skipping safety teleport!"); - } - } + teamSelection.AddUnit(supportActorMech); + supportActorMech.AddToTeam(teamSelection); - [HarmonyPatch(typeof(OrderSequence), "OnUpdate")] - public static class OrderSequence_OnUpdate - { - public static void Postfix(OrderSequence __instance) - { - if (__instance.owningActor == null) return; - if (__instance is not ActorMovementSequence && __instance is not MechJumpSequence && - __instance is not MechMeleeSequence && __instance is not MechDFASequence) return; - if (ModState.CachedUnitCoordinates.ContainsKey(__instance.owningActor.GUID)) - { - if (__instance.owningActor is CustomMech mech) - { - if (ModState.CachedUnitCoordinates[__instance.owningActor.GUID] == - __instance.owningActor.CurrentPosition && - !mech.custGameRep.HeightController.isInChangeHeight) return; - } - else if (ModState.CachedUnitCoordinates[__instance.owningActor.GUID] == - __instance.owningActor.CurrentPosition) - { - return; - } - } + supportActorMech.AddToLance(cmdLance); + cmdLance.AddUnitGUID(supportActorMech.GUID); + supportActorMech.SetBehaviorTree(BehaviorTreeIDEnum.CoreAITree); + //supportActorMech.BehaviorTree = BehaviorTreeFactory.MakeBehaviorTree(__instance.Combat.BattleTechGame, supportActorMech, BehaviorTreeIDEnum.CoreAITree); + //supportActorMech.GameRep.gameObject.SetActive(true); - var combat = __instance.owningActor.Combat; - if (__instance.owningActor.HasAirliftedUnits()) - { - var airliftedUnits = ModState.AirliftTrackers.Where(x => - x.Value.CarrierGUID == __instance.owningActor.GUID); - foreach (var trackerInfo in airliftedUnits) - { - var targetActor = combat.FindActorByGUID(trackerInfo.Key); - if (targetActor == null) continue; - var pos = Vector3.zero; - if (__instance.owningActor is CustomMech mech) - { - pos = __instance.owningActor.CurrentPosition + Vector3.down * trackerInfo.Value.Offset + - Vector3.up * mech.custGameRep.HeightController.CurrentHeight; - targetActor.TeleportActorVisual(pos); - if (targetActor is CustomMech customMech) - { - customMech.custGameRep.j_Root.localRotation = Quaternion.identity; - } + supportActorMech.OnPositionUpdate(positionA, quaternion, -1, true, null, false); + supportActorMech.DynamicUnitRole = UnitRole.Brawler; + UnitSpawnedMessage message = new UnitSpawnedMessage("FROM_ABILITY", supportActorMech.GUID); + __instance.Combat.MessageCenter.PublishMessage(message); + //supportActorMech.OnPlayerVisibilityChanged(VisibilityLevel.LOSFull); - targetActor.GameRep.transform.rotation = - __instance.owningActor.GameRep.transform.rotation; - targetActor.CurrentRotation = __instance.owningActor.CurrentRotation; - } - else - { - pos = __instance.owningActor.CurrentPosition + Vector3.down * trackerInfo.Value.Offset; - targetActor.TeleportActorVisual(pos); - if (targetActor is CustomMech customMech) - { - customMech.custGameRep.j_Root.localRotation = Quaternion.identity; - } + ModInit.modLog?.Info?.Write($"Added {supportActorMech?.MechDef?.Description?.Id} to SupportUnits"); - targetActor.GameRep.transform.rotation = - __instance.owningActor.GameRep.transform.rotation; - targetActor.CurrentRotation = __instance.owningActor.CurrentRotation; - } + //////////////// - targetActor.MountedEvasion(__instance.owningActor); - ModInit.modLog?.Debug?.Write( - $"[OrderSequence_OnUpdate] PositionLockMount- Setting airlifted unit {targetActor.DisplayName} position to same as carrier unit {__instance.owningActor.DisplayName}"); + //supportActorMech.PlaceFarAwayFromMap(); + var underMap = supportActorMech.CurrentPosition; + underMap.y = -1000f; + supportActorMech.TeleportActor(underMap); + combat.ItemRegistry.AddItem(supportActorMech); + combat.RebuildAllLists(); + EncounterLayerParent encounterLayerParent = combat.EncounterLayerData.gameObject.GetComponentInParent(); + DropPodUtils.DropPodSpawner dropSpawner = encounterLayerParent.gameObject.GetComponent(); + if (dropSpawner == null) { dropSpawner = encounterLayerParent.gameObject.AddComponent(); } - ModState.CachedUnitCoordinates[__instance.owningActor.GUID] = __instance.owningActor.CurrentPosition; - } - } + dropSpawner.Unit = supportActorMech; + dropSpawner.Combat = combat; + dropSpawner.Parent = UnityGameInstance.BattleTechGame.Combat.EncounterLayerData + .GetComponentInParent(); + dropSpawner.DropPodPosition = positionA; + dropSpawner.DropPodRotation = quaternion; - if (__instance.owningActor.HasMountedUnits()) - { - var targetActorGUIDs = - ModState.PositionLockMount.Where(x => x.Value == __instance.owningActor.GUID); - foreach (var targetActorGUID in targetActorGUIDs) - { - var targetActor = combat.FindActorByGUID(targetActorGUID.Key); - if (targetActor == null) continue; - var pos = Vector3.zero; - if (__instance.owningActor is CustomMech mech) - { - pos = __instance.owningActor.CurrentPosition + - Vector3.up * mech.custGameRep.HeightController.CurrentHeight; - targetActor.TeleportActorVisual(pos); - } - else - { - targetActor.TeleportActorVisual(__instance.owningActor.CurrentPosition); - } + ModInit.modLog?.Trace?.Write($"DropPodAnim location {positionA} is also {dropSpawner.DropPodPosition}"); + ModInit.modLog?.Trace?.Write($"Is dropAnim null fuckin somehow? {dropSpawner == null}"); + dropSpawner.DropPodVfxPrefab = dropSpawner.Parent.DropPodVfxPrefab; + dropSpawner.DropPodLandedPrefab = dropSpawner.Parent.dropPodLandedPrefab; + dropSpawner.LoadDropPodPrefabs(dropSpawner.DropPodVfxPrefab, dropSpawner.DropPodLandedPrefab); + ModInit.modLog?.Trace?.Write($"loaded prefabs success"); + dropSpawner.StartCoroutine(dropSpawner.StartDropPodAnimation(0f)); + ModInit.modLog?.Trace?.Write($"started drop pod anim"); - targetActor.MountedEvasion(__instance.owningActor); - ModInit.modLog?.Debug?.Write( - $"[OrderSequence_OnUpdate] PositionLockMount- Setting riding unit {targetActor.DisplayName} position to same as carrier unit {__instance.owningActor.DisplayName}"); + + //supportActorMech.OnPlayerVisibilityChanged(VisibilityLevel.LOSFull); - ModState.CachedUnitCoordinates[__instance.owningActor.GUID] = __instance.owningActor.CurrentPosition; - } - } + //Utils.DeployEvasion(supportActorMech); + /////////////// - // removed return/else so swarming units are locked to carrier even if carrier has mounted units. derp. - if (__instance.owningActor.HasSwarmingUnits()) - { - var targetActorGUIDs = - ModState.PositionLockSwarm.Where(x => x.Value == __instance.owningActor.GUID); - foreach (var targetActorGUID in targetActorGUIDs) + if (team.IsLocalPlayer && (ModInit.modSettings.commandUseCostsMulti > 0 || + __instance.Def.getAbilityDefExtension().CBillCost > 0)) { - var targetActor = combat.FindActorByGUID(targetActorGUID.Key); - if (targetActor == null) continue; - targetActor.TeleportActorVisual(__instance.owningActor.CurrentPosition); - targetActor.MountedEvasion(__instance.owningActor); - ModInit.modLog?.Debug?.Write( - $"[OrderSequence_OnUpdate] PositionLockMount- Setting riding unit {targetActor.DisplayName} position to same as carrier unit {__instance.owningActor.DisplayName}"); - - ModState.CachedUnitCoordinates[__instance.owningActor.GUID] = __instance.owningActor.CurrentPosition; - } - } - } - } + var unitName = ""; + var unitCost = 0; + var unitID = ""; - [HarmonyPatch(typeof(MechDisplacementSequence), "Update")] - public static class MechDisplacementSequence_Update - { - public static void Postfix(MechDisplacementSequence __instance) - { - if (__instance.OwningMech == null) return; - if (ModState.CachedUnitCoordinates.ContainsKey(__instance.OwningMech.GUID)) - { - if (__instance.OwningMech is CustomMech mech) - { - if (ModState.CachedUnitCoordinates[__instance.OwningMech.GUID] == - __instance.OwningMech.CurrentPosition && - !mech.custGameRep.HeightController.isInChangeHeight) return; - } - else if (ModState.CachedUnitCoordinates[__instance.OwningMech.GUID] == - __instance.OwningMech.CurrentPosition) - { - return; - } - } + unitName = supportActorMechDef.Description.UIName; + unitID = supportActorMechDef.Description.Id; + unitCost = supportActorMechDef.Chassis.Description.Cost; - var combat = __instance.OwningMech.Combat; - if (__instance.OwningMech.HasAirliftedUnits()) - { - var airliftedUnits = ModState.AirliftTrackers.Where(x => - x.Value.CarrierGUID == __instance.OwningMech.GUID); - foreach (var trackerInfo in airliftedUnits) - { - var targetActor = combat.FindActorByGUID(trackerInfo.Key); - if (targetActor == null) continue; - var pos = Vector3.zero; - if (__instance.OwningMech is CustomMech mech) + if (ModState.CommandUses.All(x => x.UnitID != actorResource)) { - pos = __instance.OwningMech.CurrentPosition + Vector3.down * trackerInfo.Value.Offset + - Vector3.up * mech.custGameRep.HeightController.CurrentHeight; - targetActor.TeleportActorVisual(pos); - if (targetActor is CustomMech customMech) - { - customMech.custGameRep.j_Root.localRotation = Quaternion.identity; - } + var commandUse = + new CmdUseInfo(unitID, __instance.Def.Description.Name, unitName, unitCost, + __instance.Def.getAbilityDefExtension().CBillCost); - targetActor.GameRep.transform.rotation = - __instance.OwningMech.GameRep.transform.rotation; - targetActor.CurrentRotation = __instance.OwningMech.CurrentRotation; + ModState.CommandUses.Add(commandUse); + ModInit.modLog?.Info?.Write( + $"Added usage cost for {commandUse.CommandName} - {commandUse.UnitName}. UnitUseCost (unadjusted): {unitCost}. Ability Use Cost: {__instance.Def.getAbilityDefExtension().CBillCost}"); } else { - pos = __instance.OwningMech.CurrentPosition + Vector3.down * trackerInfo.Value.Offset; - targetActor.TeleportActorVisual(pos); - if (targetActor is CustomMech customMech) + var cmdUse = ModState.CommandUses.FirstOrDefault(x => x.UnitID == actorResource); + if (cmdUse == null) { - customMech.custGameRep.j_Root.localRotation = Quaternion.identity; + ModInit.modLog?.Info?.Write($"ERROR: cmdUseInfo was null"); + } + else + { + cmdUse.UseCount += 1; + ModInit.modLog?.Info?.Write( + $"Added usage cost for {cmdUse.CommandName} - {cmdUse.UnitName}. UnitUseCost (unadjusted): {unitCost}. Ability Use Cost: {__instance.Def.getAbilityDefExtension().CBillCost}. Now used {cmdUse.UseCount} times."); } - - targetActor.GameRep.transform.rotation = - __instance.OwningMech.GameRep.transform.rotation; - targetActor.CurrentRotation = __instance.OwningMech.CurrentRotation; } - - targetActor.MountedEvasion(__instance.OwningMech); - ModInit.modLog?.Debug?.Write( - $"[MechDisplacementSequence_OnComplete] PositionLockMount- Setting airlifted unit {targetActor.DisplayName} position to same as carrier unit {__instance.OwningMech.DisplayName}"); - - ModState.CachedUnitCoordinates[__instance.OwningMech.GUID] = __instance.OwningMech.CurrentPosition; } } - - if (__instance.OwningMech.HasMountedUnits()) + else if (actorResource.StartsWith("vehicledef_") && false) //disable for CU { - var targetActorGUIDs = - ModState.PositionLockMount.Where(x => x.Value == __instance.OwningMech.GUID); - foreach (var targetActorGUID in targetActorGUIDs) - { - var targetActor = combat.FindActorByGUID(targetActorGUID.Key); - if (targetActor == null) continue; - var pos = Vector3.zero; - if (__instance.OwningMech is CustomMech mech) - { - pos = __instance.OwningMech.CurrentPosition + - Vector3.up * mech.custGameRep.HeightController.CurrentHeight; - targetActor.TeleportActorVisual(pos); - } - else - { - targetActor.TeleportActorVisual(__instance.OwningMech.CurrentPosition); - } + ModInit.modLog?.Info?.Write($"Attempting to spawn {actorResource} as vehicle."); + dm.VehicleDefs.TryGet(actorResource, out var supportActorVehicleDef); + supportActorVehicleDef.Refresh(); + var customEncounterTags = new TagSet(teamSelection.EncounterTags) {"SpawnedFromAbility"}; + var supportActorVehicle = ActorFactory.CreateVehicle(supportActorVehicleDef, supportPilotDef, + customEncounterTags, teamSelection.Combat, + teamSelection.GetNextSupportUnitGuid(), "", supportHeraldryDef); + supportActorVehicle.Init(positionA, quaternion.eulerAngles.y, false); + supportActorVehicle.InitGameRep(null); - targetActor.MountedEvasion(__instance.OwningMech); - ModInit.modLog?.Debug?.Write( - $"[MechDisplacementSequence_OnComplete] PositionLockMount- Setting riding unit {targetActor.DisplayName} position to same as carrier unit {__instance.OwningMech.DisplayName}"); + teamSelection.AddUnit(supportActorVehicle); + supportActorVehicle.AddToTeam(teamSelection); - ModState.CachedUnitCoordinates[__instance.OwningMech.GUID] = __instance.OwningMech.CurrentPosition; - } - } + supportActorVehicle.AddToLance(cmdLance); + cmdLance.AddUnitGUID(supportActorVehicle.GUID); + supportActorVehicle.SetBehaviorTree(BehaviorTreeIDEnum.CoreAITree); + //supportActorVehicle.BehaviorTree = BehaviorTreeFactory.MakeBehaviorTree(__instance.Combat.BattleTechGame, supportActorVehicle, BehaviorTreeIDEnum.CoreAITree); + //supportActorVehicle.GameRep.gameObject.SetActive(true); - // removed return/else so swarming units are locked to carrier even if carrier has mounted units. derp. - if (__instance.OwningMech.HasSwarmingUnits()) - { - var targetActorGUIDs = - ModState.PositionLockSwarm.Where(x => x.Value == __instance.OwningMech.GUID); - foreach (var targetActorGUID in targetActorGUIDs) - { - var targetActor = combat.FindActorByGUID(targetActorGUID.Key); - if (targetActor == null) continue; - targetActor.TeleportActorVisual(__instance.OwningMech.CurrentPosition); - targetActor.MountedEvasion(__instance.OwningMech); - ModInit.modLog?.Debug?.Write( - $"[MechDisplacementSequence_OnComplete] PositionLockMount- Setting riding unit {targetActor.DisplayName} position to same as carrier unit {__instance.OwningMech.DisplayName}"); + supportActorVehicle.OnPositionUpdate(positionA, quaternion, -1, true, null, false); + supportActorVehicle.DynamicUnitRole = UnitRole.Vehicle; - ModState.CachedUnitCoordinates[__instance.OwningMech.GUID] = __instance.OwningMech.CurrentPosition; - } - } - } - } + UnitSpawnedMessage message = new UnitSpawnedMessage("FROM_ABILITY", supportActorVehicle.GUID); - [HarmonyPatch(typeof(Team), "ActivateAbility")] - public static class Team_ActivateAbility - { - public static void Prefix(ref bool __runOriginal, Team __instance, AbilityMessage msg) - { - if (!__runOriginal) return; - if (__instance.Combat.ActiveContract.ContractTypeValue.IsSkirmish) - { - __runOriginal = true; - return; - } - Ability ability = ModState.CommandAbilities.Find((Ability x) => x.Def.Id == msg.abilityID); - if (ability == null) - { - ModInit.modLog?.Info?.Write( - $"Tried to use a CommandAbility the team doesnt have?"); - __runOriginal = false; - return; - } + __instance.Combat.MessageCenter.PublishMessage(message); + //supportActorVehicle.OnPlayerVisibilityChanged(VisibilityLevel.LOSFull); - switch (ability.Def.Targeting) - { - case AbilityDef.TargetingType.CommandInstant: - ability.Activate(__instance, null); - goto publishAbilityConfirmed; - case AbilityDef.TargetingType.CommandTargetSingleEnemy: - { - ICombatant combatant = __instance.Combat.FindCombatantByGUID(msg.affectedObjectGuid, false); - if (combatant == null) - { - ModInit.modLog?.Info?.Write( - $"Team.ActivateAbility couldn't find target with guid {msg.affectedObjectGuid}"); - __runOriginal = false; - return; - } - - ability.Activate(__instance, combatant); - goto publishAbilityConfirmed; - } - case AbilityDef.TargetingType.CommandTargetSinglePoint: - ability.Activate(__instance, msg.positionA); - goto publishAbilityConfirmed; - case AbilityDef.TargetingType.CommandTargetTwoPoints: - case AbilityDef.TargetingType.CommandSpawnPosition: - ability.Activate(__instance, msg.positionA, msg.positionB); - goto publishAbilityConfirmed; - case AbilityDef.TargetingType.NotSet: - break; - case AbilityDef.TargetingType.ActorSelf: - break; - case AbilityDef.TargetingType.ActorTarget: - break; - case AbilityDef.TargetingType.SensorLock: - break; - case AbilityDef.TargetingType.CommandTargetSingleAlly: - break; - case AbilityDef.TargetingType.ShadowMove: - break; - case AbilityDef.TargetingType.MultiFire: - break; - case AbilityDef.TargetingType.ConfirmCoolantVent: - break; - case AbilityDef.TargetingType.ActiveProbe: - break; - default: - throw new ArgumentOutOfRangeException(); - } - - ModInit.modLog?.Info?.Write( - $"Team.ActivateAbility needs to add handling for targetingtype {ability.Def.Targeting}"); - __runOriginal = false; - return; - publishAbilityConfirmed: - __instance.Combat.MessageCenter.PublishMessage(new AbilityConfirmedMessage(msg.actingObjectGuid, - msg.affectedObjectGuid, msg.abilityID, msg.positionA, msg.positionB)); - __runOriginal = false; - return; - } - } - - [HarmonyPatch(typeof(AbstractActor), "OnAbilityInvoked", - new Type[] { typeof(MessageCenterMessage) })] - public static class AbstractActor_OnAbilityInvoked - { - public static void Prefix(ref bool __runOriginal, AbstractActor __instance, MessageCenterMessage message) - { - if (!__runOriginal) return; - AbilityMessage msg = message as AbilityMessage; - if (msg.actingObjectGuid == __instance.GUID && msg.positionA != Vector3.zero && msg.positionB != Vector3.zero) - { - if (__instance.GetPilot().ActiveAbilities.Find((Ability x) => x.Def.Id == msg.abilityID) != null) - { - __instance.ActivateAbility(__instance, msg.abilityID, msg.affectedObjectGuid, msg.positionA, msg.positionB); - __runOriginal = false; - return; - } - } - __runOriginal = true; - return; - } - } - - [HarmonyPatch(typeof(AbstractActor), "ActivateAbility", - new Type[] { typeof(AbstractActor), typeof(string), typeof(string), typeof(Vector3), typeof(Vector3) })] - public static class AbstractActor_ActivateAbility - { - public static void Prefix(ref bool __runOriginal, AbstractActor __instance, AbstractActor pilotedActor, string abilityName, - string targetGUID, Vector3 posA, Vector3 posB) - { - if (!__runOriginal) return; - if (__instance.Combat.ActiveContract.ContractTypeValue.IsSkirmish) - { - __runOriginal = true; - return; - } - Ability ability = __instance.ComponentAbilities.Find((Ability x) => x.Def.Id == abilityName); - if (ability == null) - { - ModInit.modLog?.Trace?.Write($"[AbstractActor_ActivateAbility] - could not find ability {abilityName} in ComponentAbilities."); - ability = __instance.GetPilot().ActiveAbilities.Find((Ability x) => x.Def.Id == abilityName); - }; - if (ability == null) - { - ModInit.modLog?.Trace?.Write($"[AbstractActor_ActivateAbility] - could not find ability {abilityName} in ActiveAbilities."); - __runOriginal = true; - return; - } - if (ability.Def.Targeting == AbilityDef.TargetingType.CommandSpawnPosition) - { - ModInit.modLog?.Trace?.Write($"[AbstractActor_ActivateAbility] - activating turret spawny."); - ability.Activate(__instance, posA, posB); // added to try and unfuck strafe as regular ability - __runOriginal = false; - return; - } - if (ability.Def.Targeting == AbilityDef.TargetingType.CommandTargetTwoPoints) - { - ModInit.modLog?.Trace?.Write($"[AbstractActor_ActivateAbility] - activating strafe."); - ability.Activate(__instance, posA, posB); - __runOriginal = false; - return; - } - __runOriginal = true; - return; - } - } - - [HarmonyPatch(typeof(AbstractActor), "OnActorDestroyed", new Type[] {typeof(MessageCenterMessage)})] - public static class AbstractActor_OnActorDestroyed - { - public static void Prefix(ref bool __runOriginal, AbstractActor __instance) - { - if (!__runOriginal) return; - if (!__instance.IsAirlifted()) - { - __runOriginal = true; - return; - } - __runOriginal = false; - return; - //return !__instance.IsAirlifted(); // maybe prevent airlifted units from teleporting if over a building - } - } - - [HarmonyPatch(typeof(Ability), "IsAvailable", MethodType.Getter)] - public static class Ability_IsAvailable_Getter - { - public static void Postfix(Ability __instance, ref bool __result) - { - if (UnityGameInstance.BattleTechGame.Combat.ActiveContract.ContractTypeValue.IsSkirmish) return; - if (!__result) return; - if (!__instance.TryFetchParentFromAbility(out var parent)) return; - if (ModInit.modSettings.BeaconExclusionConfig.TryGetValue(__instance.Combat.ActiveContract.ContractTypeValue.Name, out var configType)) - { - if (parent.team.IsLocalPlayer) - { - if (__instance.Def.specialRules == AbilityDef.SpecialRules.Strafe) - __result = !configType.ExcludedPlayerStrafe; - else if (__instance.Def.specialRules == AbilityDef.SpecialRules.SpawnTurret) - __result = !configType.ExcludedPlayerSpawn; - } - else - { - if (__instance.Def.specialRules == AbilityDef.SpecialRules.Strafe) - __result = !configType.ExcludedAIStrafe; - else if (__instance.Def.specialRules == AbilityDef.SpecialRules.SpawnTurret) - __result = !configType.ExcludedAISpawn; - } - } - else if (ModInit.modSettings.BeaconExclusionConfig.TryGetValue(__instance.Combat.ActiveContract.Override.ID, out var configID)) - { - if (parent.team.IsLocalPlayer) - { - if (__instance.Def.specialRules == AbilityDef.SpecialRules.Strafe) - __result = !configID.ExcludedPlayerStrafe; - else if (__instance.Def.specialRules == AbilityDef.SpecialRules.SpawnTurret) - __result = !configID.ExcludedPlayerSpawn; - } - else - { - if (__instance.Def.specialRules == AbilityDef.SpecialRules.Strafe) - __result = !configID.ExcludedAIStrafe; - else if (__instance.Def.specialRules == AbilityDef.SpecialRules.SpawnTurret) - __result = !configID.ExcludedAISpawn; - } - } - } - } - - [HarmonyPatch(typeof(Ability), "Activate", - new Type[] { typeof(AbstractActor), typeof(ICombatant) })] - public static class Ability_Activate_ICombatant - { - public static void Postfix(Ability __instance, AbstractActor creator, ICombatant target) - { - if (creator == null) return; - if (UnityGameInstance.BattleTechGame.Combat.ActiveContract.ContractTypeValue.IsSkirmish) return; - - if (__instance.IsAvailable) - { - if (target is AbstractActor targetActor) - { - if (__instance.Def.Id == ModInit.modSettings.ResupplyConfig.ResupplyAbilityID) - { - ModInit.modLog?.Trace?.Write($"[Ability.Activate] Activating resupply from unit {creator.DisplayName} and resupplier {targetActor.DisplayName}."); - var phases = creator.ProcessResupplyUnit(targetActor); - creator.InitiateShutdownForPhases(phases); - targetActor.InitiateShutdownForPhases(phases); - } - - if (creator.HasSwarmingUnits() && creator.GUID == targetActor.GUID) - { - ModInit.modLog?.Trace?.Write($"[Ability.Activate - Unit has sawemers]."); - var swarmingUnits = ModState.PositionLockSwarm.Where(x => x.Value == creator.GUID).ToList(); + //Utils.DeployEvasion(supportActorVehicle); - if (__instance.Def.Id == ModInit.modSettings.BattleArmorDeSwarmRoll) - { - creator.ProcessDeswarmRoll(swarmingUnits); - creator.FlagForKnockdown(); - creator.HandleKnockdown(-1, creator.GUID, Vector2.one, null); - return; - } + ModInit.modLog?.Info?.Write( + $"Added {supportActorVehicle?.VehicleDef?.Description?.Id} to SupportUnits"); - else if (__instance.Def.Id == ModInit.modSettings.BattleArmorDeSwarmSwat) - { - creator.ProcessDeswarmSwat(swarmingUnits); - return; - } - else if (__instance.Def.Id == ModInit.modSettings.DeswarmMovementConfig.AbilityDefID) - { - ModInit.modLog?.Trace?.Write($"[Ability.Activate - BattleArmorDeSwarm Movement]."); - creator.ProcessDeswarmMovement( - swarmingUnits); // need to patch ActorMovementSequence complete AND JumpSequence complete AND DFASequencecomplete, and then do magic logic in there. or just do it on - return; //return to avoid ending turn for player below. making AI use this properly is gonna suck hind tit. - } + //////////////// - if (creator is Mech mech) - { - mech.GenerateAndPublishHeatSequence(-1, true, false, mech.GUID); - } - - if (creator.team.IsLocalPlayer) - { - var sequence = creator.DoneWithActor(); - creator.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(sequence)); - //creator.OnActivationEnd(creator.GUID, -1); - } + //supportActorMech.PlaceFarAwayFromMap(); + var underMap = supportActorVehicle.CurrentPosition; + underMap.y = -1000f; + supportActorVehicle.TeleportActor(underMap); + combat.ItemRegistry.AddItem(supportActorVehicle); + combat.RebuildAllLists(); + EncounterLayerParent encounterLayerParent = combat.EncounterLayerData.gameObject.GetComponentInParent(); + DropPodUtils.DropPodSpawner dropSpawner = encounterLayerParent.gameObject.GetComponent(); + if (dropSpawner == null) { dropSpawner = encounterLayerParent.gameObject.AddComponent(); } - return; - } - if (__instance.Def.Id == ModInit.modSettings.BattleArmorMountAndSwarmID) - { - if (!creator.IsSwarmingUnit() && !creator.IsMountedUnit()) - { - if (target.team.IsFriendly(creator.team)) - { - //creator.ProcessMountFriendly(targetActor); // old handling, now try movement invocation. - MessageCenterMessage invocation = - new StrategicMovementInvocation(creator, true, targetActor, true, true); - creator.Combat.MessageCenter.PublishInvocationExternal(invocation); - } + dropSpawner.Unit = supportActorVehicle; + dropSpawner.Combat = combat; + dropSpawner.Parent = UnityGameInstance.BattleTechGame.Combat.EncounterLayerData + .GetComponentInParent(); + dropSpawner.DropPodPosition = positionA; + dropSpawner.DropPodRotation = quaternion; - else if (target.team.IsEnemy(creator.team) && creator is Mech creatorMech && - creatorMech.CanSwarm()) - { - //creatorMech.ProcessSwarmEnemy(targetActor); - MessageCenterMessage invocation = - new StrategicMovementInvocation(creator, true, targetActor, false, true); - creator.Combat.MessageCenter.PublishInvocationExternal(invocation); - } - } + ModInit.modLog?.Trace?.Write($"DropPodAnim location {positionA} is also {dropSpawner.DropPodPosition}"); + ModInit.modLog?.Trace?.Write($"Is dropAnim null fuckin somehow? {dropSpawner == null}"); + dropSpawner.DropPodVfxPrefab = dropSpawner.Parent.DropPodVfxPrefab; + dropSpawner.DropPodLandedPrefab = dropSpawner.Parent.dropPodLandedPrefab; + dropSpawner.LoadDropPodPrefabs(dropSpawner.DropPodVfxPrefab, dropSpawner.DropPodLandedPrefab); + ModInit.modLog?.Trace?.Write($"loaded prefabs success"); + dropSpawner.StartCoroutine(dropSpawner.StartDropPodAnimation(0f)); + ModInit.modLog?.Trace?.Write($"started drop pod anim"); - else if (creator.IsSwarmingUnit()) - { - creator.DismountBA(targetActor, Vector3.zero, false); - } - else if (creator.IsMountedUnit()) - { - if (creator is TrooperSquad squad) - { - //ModInit.modLog?.Trace?.Write($"[Ability.Activate] Called DetachFromCarrier."); - //squad.DismountBA(targetActor, Vector3.zero, true, false, false); - squad.DetachFromCarrier(targetActor, true); - } - //creator.DismountBA(targetActor); - } - } - else if (__instance.Def.Id == ModInit.modSettings.AirliftAbilityID) - { - ModInit.modLog?.Trace?.Write($"[Ability.Activate] - Creating airlift invocation for carrier {creator.DisplayName} and target {targetActor.DisplayName}."); - if (target.team.IsFriendly(creator.team)) - { - //if dropoff, just drop dont do movement? - if (!creator.HasMovedThisRound && targetActor.IsAirlifted()) - { - targetActor.DetachFromAirliftCarrier(creator, true); - } - else - { -// if (!targetActor.StatCollection.GetValue("irbtmu_immobile_unit")) -// { -// targetActor.StatCollection.Set("irbtmu_immobile_unit", true); -// targetActor.ResetPathing(); -// ModState.UnitPendingAirliftInvocation = targetActor.GUID; -// } - //ModState.AirliftDropoffEvasion = creator.EvasivePipsCurrent; - MessageCenterMessage invocation = - new StrategicMovementInvocation(creator, true, targetActor, true, false); - creator.Combat.MessageCenter.PublishInvocationExternal(invocation); - } - } + //supportActorMech.OnPlayerVisibilityChanged(VisibilityLevel.LOSFull); - else if (target.team.IsEnemy(creator.team)) - { - if (!creator.HasMovedThisRound && targetActor.IsAirlifted()) - { - targetActor.DetachFromAirliftCarrier(creator, false); - } - else - { - MessageCenterMessage invocation = - new StrategicMovementInvocation(creator, true, targetActor, false, false); - creator.Combat.MessageCenter.PublishInvocationExternal(invocation); - } - } - //do airlifty things here - } - } - if (target is BattleTech.Building building && - __instance.Def.Id == ModInit.modSettings.BattleArmorMountAndSwarmID && creator is TrooperSquad squad2) + //Utils.DeployEvasion(supportActorVehicle); + /////////////// + + if (team.IsLocalPlayer && (ModInit.modSettings.commandUseCostsMulti > 0 || + __instance.Def.getAbilityDefExtension().CBillCost > 0)) { - if (!building.HasGarrisonedUnits()) + var unitName = ""; + var unitCost = 0; + var unitID = ""; + + unitName = supportActorVehicleDef.Description.UIName; + unitID = supportActorVehicleDef.Description.Id; + unitCost = supportActorVehicleDef.Chassis.Description.Cost; + + if (ModState.CommandUses.All(x => x.UnitID != actorResource)) { - MessageCenterMessage invocation = - new StrategicMovementInvocation(squad2, true, building, true, true); - squad2.Combat.MessageCenter.PublishInvocationExternal(invocation); + var commandUse = + new CmdUseInfo(unitID, __instance.Def.Description.Name, unitName, unitCost, + __instance.Def.getAbilityDefExtension().CBillCost); + + ModState.CommandUses.Add(commandUse); + ModInit.modLog?.Info?.Write( + $"Added usage cost for {commandUse.CommandName} - {commandUse.UnitName}. UnitUseCost (unadjusted): {unitCost}. Ability Use Cost: {__instance.Def.getAbilityDefExtension().CBillCost}"); } else { - if (squad2.IsGarrisonedInTargetBuilding(building)) + var cmdUse = ModState.CommandUses.FirstOrDefault(x => x.UnitID == actorResource); + if (cmdUse == null) { - squad2.DismountGarrison(building, Vector3.zero, false); + ModInit.modLog?.Info?.Write($"ERROR: cmdUseInfo was null"); + } + else + { + cmdUse.UseCount += 1; + ModInit.modLog?.Info?.Write( + $"Added usage cost for {cmdUse.CommandName} - {cmdUse.UnitName}. UnitUseCost (unadjusted): {unitCost}. Ability Use Cost: {__instance.Def.getAbilityDefExtension().CBillCost}. Now used {cmdUse.UseCount} times."); } } } } - } - } +#endif + else + { + ModInit.modLog?.Info?.Write($"[Ability_ActivateSpawnTurret] Attempting to spawn {actorResource} as turret."); + //var spawnTurretMethod = Traverse.Create(__instance).Method("SpawnTurret", new object[] { teamSelection, actorResource, positionA, quaternion }); + var turretActor = __instance.SpawnTurret(teamSelection, actorResource, positionA, quaternion);//spawnTurretMethod.GetValue()); + teamSelection.AddUnit(turretActor); + turretActor.AddToTeam(teamSelection); - [HarmonyPatch(typeof(Ability), "Activate", - new Type[] { typeof(AbstractActor), typeof(Vector3), typeof(Vector3) })] - public static class Ability_Activate_TwoPoints - { - public static void Prefix(ref bool __runOriginal, Ability __instance, AbstractActor creator, Vector3 positionA, Vector3 positionB) - { - if (!__runOriginal) return; - ModInit.modLog?.Info?.Write($"[Ability.Activate - 2pts] Running Ability.Activate; check if skirmish."); // need to add blocks for self-apply EffectData - if (__instance.Combat.ActiveContract.ContractTypeValue.IsSkirmish) - { - __runOriginal = true; - return; - } - if (!__instance.IsAvailable) - { - ModInit.modLog?.Info?.Write( - $"[Ability.Activate - 2pts] Ability {__instance.Def.Description.Name} was unavailable, continuing to vanilla handling."); - __runOriginal = true; - return; - } + turretActor.AddToLance(cmdLance); + cmdLance.AddUnitGUID(turretActor.GUID); + turretActor.SetBehaviorTree(BehaviorTreeIDEnum.CoreAITree); + //turretActor.BehaviorTree = BehaviorTreeFactory.MakeBehaviorTree(__instance.Combat.BattleTechGame, turretActor, BehaviorTreeIDEnum.CoreAITree); - AbilityDef.SpecialRules specialRules = __instance.Def.specialRules; + turretActor.OnPositionUpdate(positionA, quaternion, -1, true, null, false); + turretActor.DynamicUnitRole = UnitRole.Turret; + UnitSpawnedMessage message = new UnitSpawnedMessage("FROM_ABILITY", turretActor.GUID); - var showPopup = false; - if (ModInit.modSettings.BeaconExclusionConfig.TryGetValue(creator.Combat.ActiveContract.ContractTypeValue.Name, out var configType)) - { - if (creator.team.IsLocalPlayer) - { - if (specialRules == AbilityDef.SpecialRules.Strafe && configType.ExcludedPlayerStrafe) showPopup = true; - else if (specialRules == AbilityDef.SpecialRules.SpawnTurret && configType.ExcludedPlayerSpawn) showPopup = true; - } - } + __instance.Combat.MessageCenter.PublishMessage(message); + //turretActor.OnPlayerVisibilityChanged(VisibilityLevel.LOSFull); - else if (ModInit.modSettings.BeaconExclusionConfig.TryGetValue(creator.Combat.ActiveContract.Override.ID, out var configID)) - { - if (creator.team.IsLocalPlayer) + //supportActorMech.PlaceFarAwayFromMap(); + var underMap = turretActor.CurrentPosition; + underMap.y = -1000f; + turretActor.TeleportActor(underMap); + //combat.ItemRegistry.AddItem(turretActor); + combat.RebuildAllLists(); + EncounterLayerParent encounterLayerParent = combat.EncounterLayerData.gameObject.GetComponentInParent(); + DropPodUtils.DropPodSpawner dropSpawner = encounterLayerParent.gameObject.GetComponent(); + if (dropSpawner == null) { dropSpawner = encounterLayerParent.gameObject.AddComponent(); } + + dropSpawner.Unit = turretActor; + dropSpawner.Combat = combat; + dropSpawner.Parent = UnityGameInstance.BattleTechGame.Combat.EncounterLayerData + .GetComponentInParent(); + dropSpawner.DropPodPosition = positionA; + dropSpawner.DropPodRotation = quaternion; + + ModInit.modLog?.Trace?.Write($"[Ability_ActivateSpawnTurret] DropPodAnim location {positionA} is also {dropSpawner.DropPodPosition}"); + ModInit.modLog?.Trace?.Write($"[Ability_ActivateSpawnTurret] Is dropAnim null fuckin somehow? {dropSpawner == null}"); + dropSpawner.DropPodVfxPrefab = dropSpawner.Parent.DropPodVfxPrefab; + dropSpawner.DropPodLandedPrefab = dropSpawner.Parent.dropPodLandedPrefab; + dropSpawner.LoadDropPodPrefabs(dropSpawner.DropPodVfxPrefab, dropSpawner.DropPodLandedPrefab); + ModInit.modLog?.Trace?.Write($"[Ability_ActivateSpawnTurret] loaded prefabs success"); + dropSpawner.StartCoroutine(dropSpawner.StartDropPodAnimation(0f)); + ModInit.modLog?.Trace?.Write($"[Ability_ActivateSpawnTurret] started drop pod anim"); + + /////////////// + + if (team.IsLocalPlayer && (ModInit.modSettings.commandUseCostsMulti > 0 || + __instance.Def.getAbilityDefExtension().CBillCost > 0)) { - if (specialRules == AbilityDef.SpecialRules.Strafe && configID.ExcludedPlayerStrafe) showPopup = true; - else if (specialRules == AbilityDef.SpecialRules.SpawnTurret && configID.ExcludedPlayerSpawn) showPopup = true; - } - } + var unitName = ""; + var unitCost = 0; + var unitID = ""; - //if ((specialRules == AbilityDef.SpecialRules.Strafe || specialRules == AbilityDef.SpecialRules.SpawnTurret) && (ModInit.modSettings.BeaconExcludedContractIDs.Contains(__instance.Combat.ActiveContract.Override.ID) || ModInit.modSettings.BeaconExcludedContractTypes.Contains(__instance.Combat.ActiveContract.ContractTypeValue.Name))) - if (showPopup) - { - var popup = GenericPopupBuilder.Create(GenericPopupType.Info, $"Ability {__instance.Def.Description.Name} is unavailable during this contract!"); - popup.AddButton("Confirm", null, true, null); - popup.IsNestedPopupWithBuiltInFader().CancelOnEscape().Render(); - ModInit.modLog?.Info?.Write($"[Ability.Activate - 2pts] Ability {__instance.Def.Description.Name} unavailable due to exclusion settings. Aborting."); - __runOriginal = false; - return; - } + dm.TurretDefs.TryGet(actorResource, out var turretDef); + turretDef.Refresh(); + unitName = turretDef.Description.UIName; + unitID = turretDef.Description.Id; + unitCost = turretDef.Chassis.Description.Cost; - // maybe rewrite ActivateStrafe and ActivateSpawnTurret to pass actor? - if (specialRules == AbilityDef.SpecialRules.Strafe) - { - //Utils._activateStrafeMethod.Invoke(__instance, new object[] { creator.team, positionA, positionB, __instance.Def.FloatParam1 }); - //__instance.ActivateStrafe(creator.team, positionA, positionB, __instance.Def.FloatParam1); - __instance.ActivateStrafeFromActor(creator, creator.team, positionA, positionB, __instance.Def.FloatParam1); - ModInit.modLog?.Info?.Write($"[Ability.Activate - 2pts] {creator.Description?.Name}: ActivateStrafe invoked from Ability.Activate. Distance was {Vector3.Distance(positionA, positionB)}"); - __instance.Combat.MessageCenter.PublishMessage(new AbilityActivatedMessage(creator.GUID, - creator.GUID, __instance.Def.Id, positionA, positionB)); - __instance.ActivateCooldown(); - __instance.ApplyCreatorEffects(creator); - __runOriginal = false; - return; - } - else if (specialRules == AbilityDef.SpecialRules.SpawnTurret) - { - //Utils._activateSpawnTurretMethod.Invoke(__instance, new object[] { creator.team, positionA, positionB }); - //__instance.ActivateSpawnTurret(creator.team, positionA, positionB); - __instance.ActivateSpawnTurretFromActor(creator, creator.team, positionA, positionB); - ModInit.modLog?.Info?.Write($"[Ability.Activate - 2pts] {creator.Description?.Name}: ActivateSpawnTurret invoked from Ability.Activate. Distance was {Vector3.Distance(positionA, positionB)}"); - __instance.Combat.MessageCenter.PublishMessage(new AbilityActivatedMessage(creator.GUID, - creator.GUID, __instance.Def.Id, positionA, positionB)); - __instance.ActivateCooldown(); - __instance.ApplyCreatorEffects(creator); - __runOriginal = false; - return; + if (ModState.CommandUses.All(x => x.UnitID != actorResource)) + { + var commandUse = + new CmdUseInfo(unitID, __instance.Def.Description.Name, unitName, unitCost, + __instance.Def.getAbilityDefExtension().CBillCost); + + ModState.CommandUses.Add(commandUse); + ModInit.modLog?.Info?.Write( + $"[Ability_ActivateSpawnTurret] Added usage cost for {commandUse.CommandName} - {commandUse.UnitName}. UnitUseCost (unadjusted): {unitCost}. Ability Use Cost: {__instance.Def.getAbilityDefExtension().CBillCost}"); + } + else + { + var cmdUse = ModState.CommandUses.FirstOrDefault(x => x.UnitID == actorResource); + if (cmdUse == null) + { + ModInit.modLog?.Info?.Write($"ERROR: cmdUseInfo was null"); + } + else + { + cmdUse.UseCount += 1; + ModInit.modLog?.Info?.Write( + $"[Ability_ActivateSpawnTurret] Added usage cost for {cmdUse.CommandName} - {cmdUse.UnitName}. UnitUseCost (unadjusted): {unitCost}. Ability Use Cost: {__instance.Def.getAbilityDefExtension().CBillCost}. Now used {cmdUse.UseCount} times."); + } + } + } } - __runOriginal = true; + + __runOriginal = false; return; } } @@ -1371,636 +849,1013 @@ public static void Prefix(ref bool __runOriginal, Ability __instance, Team team, } } - [HarmonyPatch(typeof(Ability), "ActivateSpawnTurret")] - public static class Ability_ActivateSpawnTurret + [HarmonyPatch(typeof(Ability), "IsAvailable", MethodType.Getter)] + public static class Ability_IsAvailable_Getter + { + public static void Postfix(Ability __instance, ref bool __result) + { + if (UnityGameInstance.BattleTechGame.Combat.ActiveContract.ContractTypeValue.IsSkirmish) return; + if (!__result) return; + if (!__instance.TryFetchParentFromAbility(out var parent)) return; + if (ModInit.modSettings.BeaconExclusionConfig.TryGetValue(__instance.Combat.ActiveContract.ContractTypeValue.Name, out var configType)) + { + if (parent.team.IsLocalPlayer) + { + if (__instance.Def.specialRules == AbilityDef.SpecialRules.Strafe) + __result = !configType.ExcludedPlayerStrafe; + else if (__instance.Def.specialRules == AbilityDef.SpecialRules.SpawnTurret) + __result = !configType.ExcludedPlayerSpawn; + } + else + { + if (__instance.Def.specialRules == AbilityDef.SpecialRules.Strafe) + __result = !configType.ExcludedAIStrafe; + else if (__instance.Def.specialRules == AbilityDef.SpecialRules.SpawnTurret) + __result = !configType.ExcludedAISpawn; + } + } + else if (ModInit.modSettings.BeaconExclusionConfig.TryGetValue(__instance.Combat.ActiveContract.Override.ID, out var configID)) + { + if (parent.team.IsLocalPlayer) + { + if (__instance.Def.specialRules == AbilityDef.SpecialRules.Strafe) + __result = !configID.ExcludedPlayerStrafe; + else if (__instance.Def.specialRules == AbilityDef.SpecialRules.SpawnTurret) + __result = !configID.ExcludedPlayerSpawn; + } + else + { + if (__instance.Def.specialRules == AbilityDef.SpecialRules.Strafe) + __result = !configID.ExcludedAIStrafe; + else if (__instance.Def.specialRules == AbilityDef.SpecialRules.SpawnTurret) + __result = !configID.ExcludedAISpawn; + } + } + } + } + + [HarmonyPatch(typeof(Ability), "SpawnFlares")] + public static class Ability_SpawnFlares + { + static bool Prepare() => false; //disabled + + private static void Prefix(ref bool __runOriginal, Ability __instance, Vector3 positionA, Vector3 positionB, string prefabName, + int numFlares, int numPhases) + { + if (!__runOriginal) return; + if (__instance.Combat.ActiveContract.ContractTypeValue.IsSkirmish) + { + __runOriginal = true; + return; + } + Vector3 b = (positionB - positionA) / (float)(numFlares - 1); + + Vector3 line = positionB - positionA; + Vector3 left = Vector3.Cross(line, Vector3.up).normalized; + Vector3 right = -left; + + var startLeft = positionA + (left * __instance.Def.FloatParam1); + var startRight = positionA + (right * __instance.Def.FloatParam1); + + Vector3 vector = positionA; + + vector.y = __instance.Combat.MapMetaData.GetLerpedHeightAt(vector, false); + startLeft.y = __instance.Combat.MapMetaData.GetLerpedHeightAt(startLeft, false); + startRight.y = __instance.Combat.MapMetaData.GetLerpedHeightAt(startRight, false); + List list = new List(); + for (int i = 0; i < numFlares; i++) + { + ObjectSpawnData item = new ObjectSpawnData(prefabName, vector, Quaternion.identity, true, false); + list.Add(item); + vector += b; + vector.y = __instance.Combat.MapMetaData.GetLerpedHeightAt(vector, false); + } + + for (int i = 0; i < numFlares; i++) + { + ObjectSpawnData item = new ObjectSpawnData(prefabName, startLeft, Quaternion.identity, true, false); + list.Add(item); + startLeft += b; + startLeft.y = __instance.Combat.MapMetaData.GetLerpedHeightAt(startLeft, false); + } + + for (int i = 0; i < numFlares; i++) + { + ObjectSpawnData item = + new ObjectSpawnData(prefabName, startRight, Quaternion.identity, true, false); + list.Add(item); + startRight += b; + startRight.y = __instance.Combat.MapMetaData.GetLerpedHeightAt(startRight, false); + } + + SpawnObjectSequence spawnObjectSequence = new SpawnObjectSequence(__instance.Combat, list); + __instance.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(spawnObjectSequence)); + List spawnedObjects = spawnObjectSequence.spawnedObjects; + CleanupObjectSequence eventSequence = new CleanupObjectSequence(__instance.Combat, spawnedObjects); + TurnEvent tEvent = new TurnEvent(Guid.NewGuid().ToString(), __instance.Combat, numPhases, null, + eventSequence, __instance.Def, false); + __instance.Combat.TurnDirector.AddTurnEvent(tEvent); + __runOriginal = false; + return; + } + } + + [HarmonyPatch(typeof(AbstractActor), "ActivateAbility", + new Type[] { typeof(AbstractActor), typeof(string), typeof(string), typeof(Vector3), typeof(Vector3) })] + public static class AbstractActor_ActivateAbility + { + public static void Prefix(ref bool __runOriginal, AbstractActor __instance, AbstractActor pilotedActor, string abilityName, + string targetGUID, Vector3 posA, Vector3 posB) + { + if (!__runOriginal) return; + if (__instance.Combat.ActiveContract.ContractTypeValue.IsSkirmish) + { + __runOriginal = true; + return; + } + Ability ability = __instance.ComponentAbilities.Find((Ability x) => x.Def.Id == abilityName); + if (ability == null) + { + ModInit.modLog?.Trace?.Write($"[AbstractActor_ActivateAbility] - could not find ability {abilityName} in ComponentAbilities."); + ability = __instance.GetPilot().ActiveAbilities.Find((Ability x) => x.Def.Id == abilityName); + }; + if (ability == null) + { + ModInit.modLog?.Trace?.Write($"[AbstractActor_ActivateAbility] - could not find ability {abilityName} in ActiveAbilities."); + __runOriginal = true; + return; + } + if (ability.Def.Targeting == AbilityDef.TargetingType.CommandSpawnPosition) + { + ModInit.modLog?.Trace?.Write($"[AbstractActor_ActivateAbility] - activating turret spawny."); + ability.Activate(__instance, posA, posB); // added to try and unfuck strafe as regular ability + __runOriginal = false; + return; + } + if (ability.Def.Targeting == AbilityDef.TargetingType.CommandTargetTwoPoints) + { + ModInit.modLog?.Trace?.Write($"[AbstractActor_ActivateAbility] - activating strafe."); + ability.Activate(__instance, posA, posB); + __runOriginal = false; + return; + } + __runOriginal = true; + return; + } + } + + [HarmonyPatch(typeof(AbstractActor), "FlagForDeath", + new Type[] {typeof(string), typeof(DeathMethod), typeof(DamageType), typeof(int), typeof(int), typeof(string), typeof(bool) })] + public static class AbstractActor_FlagForDeath + { + public static void Postfix(AbstractActor __instance, string reason, DeathMethod deathMethod, DamageType damageType, int location, int stackItemID, string attackerID, bool isSilent) + { + if (__instance.IsAirlifted()) + { + var carrier = __instance.Combat.FindActorByGUID(ModState.AirliftTrackers[__instance.GUID].CarrierGUID); + __instance.DetachFromAirliftCarrier(carrier, false); // try to drop destroyed units from airlifter + } + } + } + + [HarmonyPatch(typeof(AbstractActor), "InitEffectStats", + new Type[] { })] + public static class AbstractActor_InitEffectStats + { + public static void Postfix(AbstractActor __instance) + { + __instance.StatCollection.AddStatistic("CanSwarm", false); + __instance.StatCollection.AddStatistic("CanAirliftHostiles", false); + __instance.StatCollection.AddStatistic("BattleArmorInternalMountsOnly", false); + __instance.StatCollection.AddStatistic("InternalBattleArmorSquadCap", 0); + __instance.StatCollection.AddStatistic("InternalBattleArmorSquads", 0); + __instance.StatCollection.AddStatistic("HasBattleArmorMounts", false); + __instance.StatCollection.AddStatistic("HasExternalMountedBattleArmor", false); + __instance.StatCollection.AddStatistic("IsBattleArmorHandsy", false); + __instance.StatCollection.AddStatistic("IsUnmountableBattleArmor", false); + __instance.StatCollection.AddStatistic("IsUnswarmableBattleArmor", false); + __instance.StatCollection.AddStatistic("HasFiringPorts", false); + //__instance.StatCollection.AddStatistic("BattleArmorMount", false); + //__instance.StatCollection.AddStatistic("BattleArmorDeSwarmerSwat", 0.3f); + //__instance.StatCollection.AddStatistic("BattleArmorDeSwarmerRollInitPenalty", 0); + //__instance.StatCollection.AddStatistic("BattleArmorDeSwarmerSwatInitPenalty", 0); + //__instance.StatCollection.AddStatistic("BattleArmorDeSwarmerSwatDamage", 0f); + //__instance.StatCollection.AddStatistic("BattleArmorDeSwarmerRoll", 0.5f); + //__instance.StatCollection.AddStatistic("MovementDeSwarmMinChance", 0.0f); + //__instance.StatCollection.AddStatistic("MovementDeSwarmMaxChance", 1.0f); + //__instance.StatCollection.AddStatistic("MovementDeSwarmEvasivePipsFactor", 0f); + //__instance.StatCollection.AddStatistic("MovementDeSwarmEvasiveJumpMovementMultiplier", 1.0f); + //__instance.StatCollection.AddStatistic("Airlifting", false); + __instance.StatCollection.AddStatistic("InternalLiftCapacity", 0); + __instance.StatCollection.AddStatistic("InternalLiftCapacityUsed", 0); + __instance.StatCollection.AddStatistic("ExternalLiftCapacity", 0); + __instance.StatCollection.AddStatistic("ExternalLiftCapacityUsed", 0); + __instance.StatCollection.AddStatistic("OverrideGlobalCapacity", false); + __instance.StatCollection.AddStatistic("AAAFactor", 0f); + __instance.StatCollection.AddStatistic("UseAAAFactor", false); + } + } + + [HarmonyPatch(typeof(AbstractActor), "OnAbilityInvoked", + new Type[] { typeof(MessageCenterMessage) })] + public static class AbstractActor_OnAbilityInvoked + { + public static void Prefix(ref bool __runOriginal, AbstractActor __instance, MessageCenterMessage message) + { + if (!__runOriginal) return; + AbilityMessage msg = message as AbilityMessage; + if (msg.actingObjectGuid == __instance.GUID && msg.positionA != Vector3.zero && msg.positionB != Vector3.zero) + { + if (__instance.GetPilot().ActiveAbilities.Find((Ability x) => x.Def.Id == msg.abilityID) != null) + { + __instance.ActivateAbility(__instance, msg.abilityID, msg.affectedObjectGuid, msg.positionA, msg.positionB); + __runOriginal = false; + return; + } + } + __runOriginal = true; + return; + } + } + + [HarmonyPatch(typeof(AbstractActor), "OnActorDestroyed", new Type[] {typeof(MessageCenterMessage)})] + public static class AbstractActor_OnActorDestroyed + { + public static void Prefix(ref bool __runOriginal, AbstractActor __instance) + { + if (!__runOriginal) return; + if (!__instance.IsAirlifted()) + { + __runOriginal = true; + return; + } + __runOriginal = false; + return; + //return !__instance.IsAirlifted(); // maybe prevent airlifted units from teleporting if over a building + } + } + + [HarmonyPatch(typeof(AbstractActor), "OnPhaseBegin")] + public static class AbstractActor_OnPhaseBegin { - public static void Prefix(ref bool __runOriginal, Ability __instance, Team team, Vector3 positionA, Vector3 positionB) + public static void Postfix(AbstractActor __instance) + { + if (ModState.ResupplyShutdownPhases.ContainsKey(__instance.GUID)) + { + ModState.ResupplyShutdownPhases[__instance.GUID]--; + if (ModState.ResupplyShutdownPhases[__instance.GUID] <= 0) + ModState.ResupplyShutdownPhases.Remove(__instance.GUID); + } + } + } + + [HarmonyPatch(typeof(CameraControl), "ShowActorCam")] + public static class CameraControl_ShowActorCam + { + static bool Prepare() => false; //disabled. im not fucking with the follow cam anymore, and apparently it causes problems with harmonyx for some goddamn reason? + + public static void Prefix(ref bool __runOriginal, CameraControl __instance, AbstractActor actor, Quaternion rotation, + float duration, ref AttachToActorCameraSequence __result) { if (!__runOriginal) return; - if (__instance.Combat.ActiveContract.ContractTypeValue.IsSkirmish) + var combat = UnityGameInstance.BattleTechGame.Combat; + if (combat.ActiveContract.ContractTypeValue.IsSkirmish) { __runOriginal = true; return; } - ModInit.modLog?.Info?.Write($"[Ability_ActivateSpawnTurret] Running Ability.ActivateSpawnTurret"); - var combat = UnityGameInstance.BattleTechGame.Combat; - var dm = combat.DataManager; - var sim = UnityGameInstance.BattleTechGame.Simulation; + Vector3 offset = new Vector3(0f, 50f, 50f); + __result = new AttachToActorCameraSequence(combat, actor.GameRep.transform, offset, rotation, duration, + true, false); + __runOriginal = false; + return; + } + } - var actorResource = __instance.Def.ActorResource; - var supportHeraldryDef = Utils.SwapHeraldryColors(team.HeraldryDef, dm); - //var actorGUID = __instance.parentComponent.GUID.Substring("Abilifier_ActorLink-".Length); - var quid = ""; - if (__instance?.parentComponent?.parent?.GUID != null) - { - ModInit.modLog?.Info?.Write($"[Ability_ActivateSpawnTurret] using {__instance.parentComponent.parent.GUID} from component parent"); - quid = __instance.Generate2PtCMDQuasiGUID(__instance.parentComponent.parent.GUID, positionA, positionB); + [HarmonyPatch(typeof(CombatAuraReticle), "RefreshActiveProbeRange")] + public static class CombatAuraReticle_RefreshActiveProbeRange + { + static bool Prepare() => false; //disabled + + public static void Postfix(CombatAuraReticle __instance, bool showActiveProbe) + { + if (!showActiveProbe || __instance.AuraBubble() != null) return; + float num = 0f; + if (__instance.owner.ComponentAbilities.Count > 0) + { + for (int i = 0; i < __instance.owner.ComponentAbilities.Count; i++) + { + if (__instance.owner.ComponentAbilities[i].Def.Targeting == AbilityDef.TargetingType.ActiveProbe) + { + num = __instance.owner.ComponentAbilities[i].Def.FloatParam1; + break; + } + } } - else if (__instance?.parentComponent?.GUID != null) + if (!Mathf.Approximately(num, __instance.currentAPRange)) { - var quidFromAbilifier = __instance.parentComponent.GUID.Substring(20); - ModInit.modLog?.Info?.Write($"[Ability_ActivateSpawnTurret] using {__instance.parentComponent.GUID} from abilifier component guid; processed down to {quidFromAbilifier}"); - quid = __instance.Generate2PtCMDQuasiGUID(quidFromAbilifier, positionA, positionB); + var apObject = __instance.activeProbeRangeScaledObject;//Traverse.Create(__instance).Property("activeProbeRangeScaledObject").GetValue(); + apObject.transform.localScale = new Vector3(num * 2f, 1f, num * 2f); } - ModInit.modLog?.Info?.Write($"[Ability_ActivateSpawnTurret] Trying to find params with key {quid}"); - if (!ModState.StoredCmdParams.ContainsKey(quid)) + __instance.currentAPRange = num; + } + } + + + [HarmonyPatch(typeof(CombatGameState), "OnCombatGameDestroyed")] + public static class CombatGameState_OnCombatGameDestroyed + { + private static void Postfix(CombatGameState __instance) + { + ModState.ResetAll(); + StrategicSelection.StrategicTargetIndicatorsManager.Clear(); + } + } + + [HarmonyPatch(typeof(CombatHUDActionButton), "ActivateCommandAbility", + new Type[] { typeof(string), typeof(Vector3), typeof(Vector3) })] + public static class CombatHUDActionButton_ActivateCommandAbility + { + public static void Prefix(ref bool __runOriginal, CombatHUDActionButton __instance, string teamGUID, + Vector3 positionA, //prefix to try and make command abilities behave like normal ones + Vector3 positionB) + { + if (!__runOriginal) return; + var HUD = __instance.HUD;//IRBTModUtils.SharedState.CombatHUD;//Traverse.Create(__instance).Property("HUD").GetValue(); + var theActor = HUD.SelectedActor; + if (theActor == null) { - ModInit.modLog?.Info?.Write($"[Ability_ActivateSpawnTurret] No strafe params stored, wtf"); __runOriginal = true; return; } - var playerControl = Utils.ShouldPlayerControlSpawn(team, __instance, quid); - var teamSelection = playerControl ? team : team.SupportTeam;//.SupportTeam; change to player control? - if (!team.IsLocalPlayer) - { - teamSelection = team as AITeam; - } - if (!ModState.StoredCmdParams.ContainsKey(quid)) + var combat = HUD.Combat; + if (combat.ActiveContract.ContractTypeValue.IsSkirmish) { - ModInit.modLog?.Info?.Write($"[Ability_ActivateSpawnTurret] No spawn params stored, wtf"); __runOriginal = true; return; } - if (!string.IsNullOrEmpty(ModState.StoredCmdParams[quid].ActorResource)) + if (__instance.Ability != null && + __instance.Ability.Def.ActivationTime == AbilityDef.ActivationTiming.CommandAbility && + (__instance.Ability.Def.Targeting == AbilityDef.TargetingType.CommandTargetTwoPoints || + __instance.Ability.Def.Targeting == AbilityDef.TargetingType.CommandSpawnPosition)) { - actorResource = ModState.StoredCmdParams[quid].ActorResource; - //ModState.PopupActorResource = ""; + MessageCenterMessage messageCenterMessage = new AbilityInvokedMessage(theActor.GUID, theActor.GUID, + __instance.Ability.Def.Id, positionA, positionB); + messageCenterMessage.IsNetRouted = true; + combat.MessageCenter.PublishMessage(messageCenterMessage); + messageCenterMessage = new AbilityConfirmedMessage(theActor.GUID, theActor.GUID, + __instance.Ability.Def.Id, positionA, positionB); + messageCenterMessage.IsNetRouted = true; + combat.MessageCenter.PublishMessage(messageCenterMessage); + __instance.DisableButton(); } + __runOriginal = false; + return; + } - if (ModState.DeploymentAssetsStats.Any(x => x.ID == actorResource) && team.IsLocalPlayer && !ModState.DeferredSpawnerFromDelegate) + public static void Postfix(CombatHUDActionButton __instance, string teamGUID, Vector3 positionA, + Vector3 positionB) + { + if (UnityGameInstance.BattleTechGame.Combat.ActiveContract.ContractTypeValue.IsSkirmish) return; + var def = __instance.Ability.Def; + var HUD = __instance.HUD;//IRBTModUtils.SharedState.CombatHUD; //Traverse.Create(__instance).Property("HUD").GetValue(); + var theActor = HUD.SelectedActor; + if (theActor == null) return; + if (def.specialRules == AbilityDef.SpecialRules.Strafe && + ModInit.modSettings.strafeEndsActivation) { - var assetStatInfo = ModState.DeploymentAssetsStats.FirstOrDefault(x => x.ID == actorResource); - if (assetStatInfo != null) + if (theActor is Mech mech) { - assetStatInfo.contractUses -= 1; - if (assetStatInfo.consumeOnUse) - { - sim?.CompanyStats.ModifyStat("StratOps", -1, assetStatInfo.stat, - StatCollection.StatOperation.Int_Subtract, 1); - } + mech.GenerateAndPublishHeatSequence(-1, true, false, theActor.GUID); } - ModInit.modLog?.Info?.Write($"[Ability_ActivateSpawnTurret] Decrementing count of {actorResource} in deploymentAssetsDict"); - } - var instanceGUID = - $"{__instance.Def.Id}_{team.Name}_{actorResource}_{positionA}_{positionB}@{actorResource}"; + var sequence = theActor.DoneWithActor(); + theActor.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(sequence)); + //theActor.OnActivationEnd(theActor.GUID, __instance.GetInstanceID()); + return; + } - if (ModState.DeferredInvokeSpawns.All(x => x.Key != instanceGUID) && !ModState.DeferredSpawnerFromDelegate) + if (def.specialRules == AbilityDef.SpecialRules.SpawnTurret && + ModInit.modSettings.spawnTurretEndsActivation) { - ModInit.modLog?.Info?.Write( - $"[Ability_ActivateSpawnTurret] Deferred Spawner = null, creating delegate and returning false. Delegate should spawn {actorResource}"); - - void DeferredInvokeSpawn() => __instance.ActivateSpawnTurret(team, positionA, positionB);//Utils._activateSpawnTurretMethod.Invoke(__instance, new object[] { team, positionA, positionB }); + if (theActor is Mech mech) + { + mech.GenerateAndPublishHeatSequence(-1, true, false, theActor.GUID); + } + var sequence = theActor.DoneWithActor(); + theActor.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(sequence)); + //theActor.OnActivationEnd(theActor.GUID, __instance.GetInstanceID()); + } + //need to make sure proccing from equipment button also ends turn? + } + } - var kvp = new KeyValuePair(instanceGUID, DeferredInvokeSpawn); - ModState.DeferredInvokeSpawns.Add(kvp); - Utils.SpawnFlares(__instance, positionA, positionB, ModInit.modSettings.flareResourceID, 1, __instance.Def.ActivationETA, team.IsLocalPlayer); - // var flares = Traverse.Create(__instance).Method("SpawnFlares", - // new object[] {positionA, positionA, __instance.Def., 1, 1}); - // flares.GetValue(); - __runOriginal = false; + [HarmonyPatch(typeof(CombatHUDEquipmentSlot), "ActivateCommandAbility", + new Type[] { typeof(string), typeof(Vector3), typeof(Vector3) })] + public static class CombatHUDEquipmentSlot_ActivateCommandAbility + { + public static void Prefix(ref bool __runOriginal, CombatHUDEquipmentSlot __instance, string teamGUID, Vector3 positionA, + Vector3 positionB) + { + if (!__runOriginal) return; + var HUD = __instance.HUD;//IRBTModUtils.SharedState.CombatHUD;// Traverse.Create(__instance).Property("HUD").GetValue(); + var theActor = HUD.SelectedActor; + if (theActor == null) + { + __runOriginal = true; return; } - - if (!string.IsNullOrEmpty(ModState.DeferredActorResource)) + var combat = HUD.Combat; + if (combat.ActiveContract.ContractTypeValue.IsSkirmish) { - actorResource = ModState.DeferredActorResource; - ModInit.modLog?.Info?.Write($"[Ability_ActivateSpawnTurret] {actorResource} restored from deferredActorResource"); + __runOriginal = true; + return; } - - var pilotID = "pilot_sim_starter_dekker"; - if (!string.IsNullOrEmpty(ModState.StoredCmdParams[quid].PilotOverride)) + if (__instance.Ability != null && + __instance.Ability.Def.ActivationTime == AbilityDef.ActivationTiming.CommandAbility && + (__instance.Ability.Def.Targeting == AbilityDef.TargetingType.CommandTargetTwoPoints || + __instance.Ability.Def.Targeting == AbilityDef.TargetingType.CommandSpawnPosition)) { - //pilotID = ModState.PilotOverride; - pilotID = ModState.StoredCmdParams[quid].PilotOverride; + MessageCenterMessage messageCenterMessage = new AbilityInvokedMessage(theActor.GUID, theActor.GUID, + __instance.Ability.Def.Id, positionA, positionB); + messageCenterMessage.IsNetRouted = true; + combat.MessageCenter.PublishMessage(messageCenterMessage); + messageCenterMessage = new AbilityConfirmedMessage(theActor.GUID, theActor.GUID, + __instance.Ability.Def.Id, positionA, positionB); + messageCenterMessage.IsNetRouted = true; + combat.MessageCenter.PublishMessage(messageCenterMessage); + __instance.DisableButton(); } - else if (!string.IsNullOrEmpty(__instance.Def.getAbilityDefExtension().CMDPilotOverride)) + __runOriginal = false; + return; + } + + public static void Postfix(CombatHUDEquipmentSlot __instance, string teamGUID, Vector3 positionA, + Vector3 positionB) + { + if (__instance.Ability.Combat.ActiveContract.ContractTypeValue.IsSkirmish) return; + var def = __instance.Ability.Def; + var HUD = __instance.HUD;//IRBTModUtils.SharedState.CombatHUD;//Traverse.Create(__instance).Property("HUD").GetValue(); + var theActor = HUD.SelectedActor; + if (theActor == null) return; + if (def.specialRules == AbilityDef.SpecialRules.Strafe && + ModInit.modSettings.strafeEndsActivation) { - pilotID = __instance.Def.getAbilityDefExtension().CMDPilotOverride; + if (theActor is Mech mech) + { + mech.GenerateAndPublishHeatSequence(-1, true, false, theActor.GUID); + } + + var sequence = theActor.DoneWithActor(); + theActor.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(sequence)); + //theActor.OnActivationEnd(theActor.GUID, __instance.GetInstanceID()); + return; } - ModInit.modLog?.Info?.Write($"[Ability_ActivateSpawnTurret] Pilot should be {pilotID}"); - var cmdLance = new Lance(); - if (playerControl) + if (def.specialRules == AbilityDef.SpecialRules.SpawnTurret && + ModInit.modSettings.spawnTurretEndsActivation) { - if (team.lances.Count > 0) cmdLance = team.lances[0]; - else + if (theActor is Mech mech) { - cmdLance = new Lance(); - ModInit.modLog?.Error?.Write($"[Ability_ActivateSpawnTurret] No lances found for team! This is fucked up!"); + mech.GenerateAndPublishHeatSequence(-1, true, false, theActor.GUID); } + var sequence = theActor.DoneWithActor(); + theActor.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(sequence)); + //theActor.OnActivationEnd(theActor.GUID, __instance.GetInstanceID()); } - else cmdLance = Utils.CreateOrFetchCMDLance(teamSelection); - - Quaternion quaternion = Quaternion.LookRotation(positionB - positionA); + } + } - if (actorResource.StartsWith("mechdef_") || actorResource.StartsWith("vehicledef_")) + [HarmonyPatch(typeof(CombatHUDEquipmentSlotEx), "ResetAbilityButton", + new Type[] { typeof(AbstractActor), typeof(CombatHUDActionButton), typeof(Ability), typeof(bool) })] + public static class CombatHUDEquipmentSlotEx_ResetAbilityButton + { + public static void Postfix(CombatHUDEquipmentSlotEx __instance, AbstractActor actor, + CombatHUDActionButton button, Ability ability, bool forceInactive) + { + if (UnityGameInstance.BattleTechGame.Combat.ActiveContract.ContractTypeValue.IsSkirmish) return; + if (actor == null || ability == null) return; + // if (button == __instance.FireButton) + // { + // ModInit.modLog?.Trace?.Write( + // $"Leaving Fire Button Enabled"); + // return; + // } + + if (actor.HasActivatedThisRound || !actor.IsAvailableThisPhase || + (actor.Combat.StackManager.IsAnyOrderActive && actor.Combat.TurnDirector.IsInterleaved)) { - ModInit.modLog?.Info?.Write($"[Ability_ActivateSpawnTurret] Attempting to spawn {actorResource} as mech."); - var spawner = new Classes.CustomSpawner(team, __instance, combat, actorResource, cmdLance, teamSelection, positionA, quaternion, supportHeraldryDef, pilotID, playerControl); - spawner.SpawnBeaconUnitAtLocation(); - __runOriginal = false; return; } -#if NO_CAC - if (actorResource.StartsWith("mechdef_") && false) + if (ability.Def.Id != ModInit.modSettings.BattleArmorMountAndSwarmID) { - ModInit.modLog?.Info?.Write($"Attempting to spawn {actorResource} as mech."); - dm.MechDefs.TryGet(actorResource, out var supportActorMechDef); - supportActorMechDef.Refresh(); - var customEncounterTags = new TagSet(teamSelection.EncounterTags) {"SpawnedFromAbility"}; - var supportActorMech = ActorFactory.CreateMech(supportActorMechDef, supportPilotDef, - customEncounterTags, teamSelection.Combat, - teamSelection.GetNextSupportUnitGuid(), "", supportHeraldryDef); - supportActorMech.Init(positionA, quaternion.eulerAngles.y, false); - supportActorMech.InitGameRep(null); - - teamSelection.AddUnit(supportActorMech); - supportActorMech.AddToTeam(teamSelection); - - supportActorMech.AddToLance(cmdLance); - cmdLance.AddUnitGUID(supportActorMech.GUID); - supportActorMech.SetBehaviorTree(BehaviorTreeIDEnum.CoreAITree); - //supportActorMech.BehaviorTree = BehaviorTreeFactory.MakeBehaviorTree(__instance.Combat.BattleTechGame, supportActorMech, BehaviorTreeIDEnum.CoreAITree); - //supportActorMech.GameRep.gameObject.SetActive(true); - - supportActorMech.OnPositionUpdate(positionA, quaternion, -1, true, null, false); - supportActorMech.DynamicUnitRole = UnitRole.Brawler; - UnitSpawnedMessage message = new UnitSpawnedMessage("FROM_ABILITY", supportActorMech.GUID); - __instance.Combat.MessageCenter.PublishMessage(message); - //supportActorMech.OnPlayerVisibilityChanged(VisibilityLevel.LOSFull); - - ModInit.modLog?.Info?.Write($"Added {supportActorMech?.MechDef?.Description?.Id} to SupportUnits"); - - //////////////// - - //supportActorMech.PlaceFarAwayFromMap(); - var underMap = supportActorMech.CurrentPosition; - underMap.y = -1000f; - supportActorMech.TeleportActor(underMap); - combat.ItemRegistry.AddItem(supportActorMech); - combat.RebuildAllLists(); - EncounterLayerParent encounterLayerParent = combat.EncounterLayerData.gameObject.GetComponentInParent(); - DropPodUtils.DropPodSpawner dropSpawner = encounterLayerParent.gameObject.GetComponent(); - if (dropSpawner == null) { dropSpawner = encounterLayerParent.gameObject.AddComponent(); } - - dropSpawner.Unit = supportActorMech; - dropSpawner.Combat = combat; - dropSpawner.Parent = UnityGameInstance.BattleTechGame.Combat.EncounterLayerData - .GetComponentInParent(); - dropSpawner.DropPodPosition = positionA; - dropSpawner.DropPodRotation = quaternion; - - ModInit.modLog?.Trace?.Write($"DropPodAnim location {positionA} is also {dropSpawner.DropPodPosition}"); - ModInit.modLog?.Trace?.Write($"Is dropAnim null fuckin somehow? {dropSpawner == null}"); - dropSpawner.DropPodVfxPrefab = dropSpawner.Parent.DropPodVfxPrefab; - dropSpawner.DropPodLandedPrefab = dropSpawner.Parent.dropPodLandedPrefab; - dropSpawner.LoadDropPodPrefabs(dropSpawner.DropPodVfxPrefab, dropSpawner.DropPodLandedPrefab); - ModInit.modLog?.Trace?.Write($"loaded prefabs success"); - dropSpawner.StartCoroutine(dropSpawner.StartDropPodAnimation(0f)); - ModInit.modLog?.Trace?.Write($"started drop pod anim"); - - - //supportActorMech.OnPlayerVisibilityChanged(VisibilityLevel.LOSFull); - - //Utils.DeployEvasion(supportActorMech); - /////////////// - - if (team.IsLocalPlayer && (ModInit.modSettings.commandUseCostsMulti > 0 || - __instance.Def.getAbilityDefExtension().CBillCost > 0)) + if (actor.IsMountedUnit()) { - var unitName = ""; - var unitCost = 0; - var unitID = ""; - - unitName = supportActorMechDef.Description.UIName; - unitID = supportActorMechDef.Description.Id; - unitCost = supportActorMechDef.Chassis.Description.Cost; - - if (ModState.CommandUses.All(x => x.UnitID != actorResource)) + if (!actor.IsMountedInternal()) { - var commandUse = - new CmdUseInfo(unitID, __instance.Def.Description.Name, unitName, unitCost, - __instance.Def.getAbilityDefExtension().CBillCost); - - ModState.CommandUses.Add(commandUse); - ModInit.modLog?.Info?.Write( - $"Added usage cost for {commandUse.CommandName} - {commandUse.UnitName}. UnitUseCost (unadjusted): {unitCost}. Ability Use Cost: {__instance.Def.getAbilityDefExtension().CBillCost}"); + button.DisableButton(); } else { - var cmdUse = ModState.CommandUses.FirstOrDefault(x => x.UnitID == actorResource); - if (cmdUse == null) - { - ModInit.modLog?.Info?.Write($"ERROR: cmdUseInfo was null"); - } - else + var carrier = actor.Combat.FindActorByGUID(ModState.PositionLockMount[actor.GUID]); + if (!carrier.HasFiringPorts()) { - cmdUse.UseCount += 1; - ModInit.modLog?.Info?.Write( - $"Added usage cost for {cmdUse.CommandName} - {cmdUse.UnitName}. UnitUseCost (unadjusted): {unitCost}. Ability Use Cost: {__instance.Def.getAbilityDefExtension().CBillCost}. Now used {cmdUse.UseCount} times."); + button.DisableButton(); } } } + else if (actor.IsSwarmingUnit()) + { + button.DisableButton(); + } } - else if (actorResource.StartsWith("vehicledef_") && false) //disable for CU - { - ModInit.modLog?.Info?.Write($"Attempting to spawn {actorResource} as vehicle."); - dm.VehicleDefs.TryGet(actorResource, out var supportActorVehicleDef); - supportActorVehicleDef.Refresh(); - var customEncounterTags = new TagSet(teamSelection.EncounterTags) {"SpawnedFromAbility"}; - var supportActorVehicle = ActorFactory.CreateVehicle(supportActorVehicleDef, supportPilotDef, - customEncounterTags, teamSelection.Combat, - teamSelection.GetNextSupportUnitGuid(), "", supportHeraldryDef); - supportActorVehicle.Init(positionA, quaternion.eulerAngles.y, false); - supportActorVehicle.InitGameRep(null); - - teamSelection.AddUnit(supportActorVehicle); - supportActorVehicle.AddToTeam(teamSelection); - - supportActorVehicle.AddToLance(cmdLance); - cmdLance.AddUnitGUID(supportActorVehicle.GUID); - supportActorVehicle.SetBehaviorTree(BehaviorTreeIDEnum.CoreAITree); - //supportActorVehicle.BehaviorTree = BehaviorTreeFactory.MakeBehaviorTree(__instance.Combat.BattleTechGame, supportActorVehicle, BehaviorTreeIDEnum.CoreAITree); - //supportActorVehicle.GameRep.gameObject.SetActive(true); - supportActorVehicle.OnPositionUpdate(positionA, quaternion, -1, true, null, false); - supportActorVehicle.DynamicUnitRole = UnitRole.Vehicle; - - UnitSpawnedMessage message = new UnitSpawnedMessage("FROM_ABILITY", supportActorVehicle.GUID); - - __instance.Combat.MessageCenter.PublishMessage(message); - //supportActorVehicle.OnPlayerVisibilityChanged(VisibilityLevel.LOSFull); - - //Utils.DeployEvasion(supportActorVehicle); + if (ability.Def.Id == ModInit.modSettings.BattleArmorDeSwarmRoll || + ability.Def.Id == ModInit.modSettings.BattleArmorDeSwarmSwat) + { + if (actor is Vehicle vehicle || actor.IsCustomUnitVehicle()) + { + button.DisableButton(); + } - ModInit.modLog?.Info?.Write( - $"Added {supportActorVehicle?.VehicleDef?.Description?.Id} to SupportUnits"); + if (!actor.HasSwarmingUnits()) + { + button.DisableButton(); + } + } + if (ability.Def.Id == ModInit.modSettings.AirliftAbilityID && ModInit.modSettings.CanDropOffAfterMoving)// && actor.MovingToPosition == null) // maybe need to check IsAnyOrderActive (but that might screw me) + { + if (actor.HasAirliftedUnits()) + { + //button.DisableButton(); + //if (!button.gameObject.activeSelf) + //{ + // button.gameObject.SetActive(true); + //} - //////////////// + //button.InitButton(CombatHUDMechwarriorTray.GetSelectionTypeFromTargeting(ability.Def.Targeting, false), ability, ability.Def.AbilityIcon, ability.Def.Description.Id, ability.Def.Description.Name, actor); + button.ResetButtonIfNotActive(actor); + } + } - //supportActorMech.PlaceFarAwayFromMap(); - var underMap = supportActorVehicle.CurrentPosition; - underMap.y = -1000f; - supportActorVehicle.TeleportActor(underMap); - combat.ItemRegistry.AddItem(supportActorVehicle); - combat.RebuildAllLists(); - EncounterLayerParent encounterLayerParent = combat.EncounterLayerData.gameObject.GetComponentInParent(); - DropPodUtils.DropPodSpawner dropSpawner = encounterLayerParent.gameObject.GetComponent(); - if (dropSpawner == null) { dropSpawner = encounterLayerParent.gameObject.AddComponent(); } + var specialRules = ability.Def.specialRules; - dropSpawner.Unit = supportActorVehicle; - dropSpawner.Combat = combat; - dropSpawner.Parent = UnityGameInstance.BattleTechGame.Combat.EncounterLayerData - .GetComponentInParent(); - dropSpawner.DropPodPosition = positionA; - dropSpawner.DropPodRotation = quaternion; - ModInit.modLog?.Trace?.Write($"DropPodAnim location {positionA} is also {dropSpawner.DropPodPosition}"); - ModInit.modLog?.Trace?.Write($"Is dropAnim null fuckin somehow? {dropSpawner == null}"); - dropSpawner.DropPodVfxPrefab = dropSpawner.Parent.DropPodVfxPrefab; - dropSpawner.DropPodLandedPrefab = dropSpawner.Parent.dropPodLandedPrefab; - dropSpawner.LoadDropPodPrefabs(dropSpawner.DropPodVfxPrefab, dropSpawner.DropPodLandedPrefab); - ModInit.modLog?.Trace?.Write($"loaded prefabs success"); - dropSpawner.StartCoroutine(dropSpawner.StartDropPodAnimation(0f)); - ModInit.modLog?.Trace?.Write($"started drop pod anim"); + if (ModInit.modSettings.BeaconExclusionConfig.TryGetValue(actor.Combat.ActiveContract.ContractTypeValue.Name, out var configType)) + { + if (actor.team.IsLocalPlayer) + { + if (specialRules == AbilityDef.SpecialRules.Strafe && configType.ExcludedPlayerStrafe) + button.DisableButton(); + else if (specialRules == AbilityDef.SpecialRules.SpawnTurret && configType.ExcludedPlayerSpawn) + button.DisableButton(); + } + } - //supportActorMech.OnPlayerVisibilityChanged(VisibilityLevel.LOSFull); + else if (ModInit.modSettings.BeaconExclusionConfig.TryGetValue(actor.Combat.ActiveContract.Override.ID, out var configID)) + { + if (actor.team.IsLocalPlayer) + { + if (specialRules == AbilityDef.SpecialRules.Strafe && configID.ExcludedPlayerStrafe) + button.DisableButton(); + else if (specialRules == AbilityDef.SpecialRules.SpawnTurret && configID.ExcludedPlayerSpawn) + button.DisableButton(); + } + } - //Utils.DeployEvasion(supportActorVehicle); - /////////////// + //if ((specialRules == AbilityDef.SpecialRules.Strafe || specialRules == AbilityDef.SpecialRules.SpawnTurret) && + // (ModInit.modSettings.BeaconExcludedContractIDs.Contains(ability.Combat.ActiveContract.Override.ID) || ModInit.modSettings.BeaconExcludedContractTypes.Contains(ability.Combat.ActiveContract + // .ContractTypeValue.Name))) + // { + // button.DisableButton(); + // } - if (team.IsLocalPlayer && (ModInit.modSettings.commandUseCostsMulti > 0 || - __instance.Def.getAbilityDefExtension().CBillCost > 0)) - { - var unitName = ""; - var unitCost = 0; - var unitID = ""; + if (actor.GetAbilityUsedFiring()) + { + if (ability.Def.ActivationTime == AbilityDef.ActivationTiming.ConsumedByFiring) button.DisableButton(); + } + } + } - unitName = supportActorVehicleDef.Description.UIName; - unitID = supportActorVehicleDef.Description.Id; - unitCost = supportActorVehicleDef.Chassis.Description.Cost; + [HarmonyPatch(typeof(CombatHUDMechwarriorTray), "ResetAbilityButton", + new Type[] { typeof(AbstractActor), typeof(CombatHUDActionButton), typeof(Ability), typeof(bool) })] + public static class CombatHUDMechwarriorTray_ResetAbilityButton_Patch + { + public static void Postfix(CombatHUDMechwarriorTray __instance, AbstractActor actor, CombatHUDActionButton button, Ability ability, bool forceInactive) + { + if (UnityGameInstance.BattleTechGame.Combat.ActiveContract.ContractTypeValue.IsSkirmish) return; + if (actor == null || ability == null) return; + // if (button == __instance.FireButton) + // { + // ModInit.modLog?.Trace?.Write( + // $"Leaving Fire Button Enabled"); + // return; + // } + if (actor.HasActivatedThisRound || !actor.IsAvailableThisPhase || + (actor.Combat.StackManager.IsAnyOrderActive && actor.Combat.TurnDirector.IsInterleaved)) + { + return; + } - if (ModState.CommandUses.All(x => x.UnitID != actorResource)) + if (ability.Def.Id != ModInit.modSettings.BattleArmorMountAndSwarmID) + { + if (actor.IsMountedUnit()) + { + if (!actor.IsMountedInternal()) { - var commandUse = - new CmdUseInfo(unitID, __instance.Def.Description.Name, unitName, unitCost, - __instance.Def.getAbilityDefExtension().CBillCost); - - ModState.CommandUses.Add(commandUse); - ModInit.modLog?.Info?.Write( - $"Added usage cost for {commandUse.CommandName} - {commandUse.UnitName}. UnitUseCost (unadjusted): {unitCost}. Ability Use Cost: {__instance.Def.getAbilityDefExtension().CBillCost}"); + button.DisableButton(); } else { - var cmdUse = ModState.CommandUses.FirstOrDefault(x => x.UnitID == actorResource); - if (cmdUse == null) - { - ModInit.modLog?.Info?.Write($"ERROR: cmdUseInfo was null"); - } - else + var carrier = actor.Combat.FindActorByGUID(ModState.PositionLockMount[actor.GUID]); + if (!carrier.HasFiringPorts()) { - cmdUse.UseCount += 1; - ModInit.modLog?.Info?.Write( - $"Added usage cost for {cmdUse.CommandName} - {cmdUse.UnitName}. UnitUseCost (unadjusted): {unitCost}. Ability Use Cost: {__instance.Def.getAbilityDefExtension().CBillCost}. Now used {cmdUse.UseCount} times."); + button.DisableButton(); } } } + else if (actor.IsSwarmingUnit()) + { + button.DisableButton(); + } } -#endif - else + + if (ability.Def.Id == ModInit.modSettings.BattleArmorDeSwarmRoll || + ability.Def.Id == ModInit.modSettings.BattleArmorDeSwarmSwat) { - ModInit.modLog?.Info?.Write($"[Ability_ActivateSpawnTurret] Attempting to spawn {actorResource} as turret."); - //var spawnTurretMethod = Traverse.Create(__instance).Method("SpawnTurret", new object[] { teamSelection, actorResource, positionA, quaternion }); - var turretActor = __instance.SpawnTurret(teamSelection, actorResource, positionA, quaternion);//spawnTurretMethod.GetValue()); + if (actor is Vehicle vehicle || actor.IsCustomUnitVehicle()) + { + button.DisableButton(); + } - teamSelection.AddUnit(turretActor); - turretActor.AddToTeam(teamSelection); + if (!actor.HasSwarmingUnits()) + { + button.DisableButton(); + } + } - turretActor.AddToLance(cmdLance); - cmdLance.AddUnitGUID(turretActor.GUID); - turretActor.SetBehaviorTree(BehaviorTreeIDEnum.CoreAITree); - //turretActor.BehaviorTree = BehaviorTreeFactory.MakeBehaviorTree(__instance.Combat.BattleTechGame, turretActor, BehaviorTreeIDEnum.CoreAITree); + if (ability.Def.Id == ModInit.modSettings.AirliftAbilityID && ModInit.modSettings.CanDropOffAfterMoving)// && actor.MovingToPosition == null) // maybe need to check IsAnyOrderActive (but that might screw me) + { + if (actor.HasAirliftedUnits()) + { + //button.DisableButton(); + //if (!button.gameObject.activeSelf) + //{ + // button.gameObject.SetActive(true); + //} - turretActor.OnPositionUpdate(positionA, quaternion, -1, true, null, false); - turretActor.DynamicUnitRole = UnitRole.Turret; + //button.InitButton(CombatHUDMechwarriorTray.GetSelectionTypeFromTargeting(ability.Def.Targeting, false), ability, ability.Def.AbilityIcon, ability.Def.Description.Id, ability.Def.Description.Name, actor); + button.ResetButtonIfNotActive(actor); + } + } + } + } - UnitSpawnedMessage message = new UnitSpawnedMessage("FROM_ABILITY", turretActor.GUID); + [HarmonyPatch(typeof(CombatHUDWeaponPanel), "ResetAbilityButton", + new Type[] { typeof(AbstractActor), typeof(CombatHUDActionButton), typeof(Ability), typeof(bool) })] + public static class CombatHUDWeaponPanel_ResetAbilityButton_Patch + { + public static void Postfix(CombatHUDWeaponPanel __instance, AbstractActor actor, CombatHUDActionButton button, Ability ability, bool forceInactive) + { + if (UnityGameInstance.BattleTechGame.Combat.ActiveContract.ContractTypeValue.IsSkirmish) return; + if (actor == null || ability == null) return; + // if (button == __instance.FireButton) + // { + // ModInit.modLog?.Trace?.Write( + // $"Leaving Fire Button Enabled"); + // return; + // } - __instance.Combat.MessageCenter.PublishMessage(message); - //turretActor.OnPlayerVisibilityChanged(VisibilityLevel.LOSFull); + if (actor.HasActivatedThisRound || !actor.IsAvailableThisPhase || + (actor.Combat.StackManager.IsAnyOrderActive && actor.Combat.TurnDirector.IsInterleaved)) + { + return; + } - //supportActorMech.PlaceFarAwayFromMap(); - var underMap = turretActor.CurrentPosition; - underMap.y = -1000f; - turretActor.TeleportActor(underMap); - //combat.ItemRegistry.AddItem(turretActor); - combat.RebuildAllLists(); - EncounterLayerParent encounterLayerParent = combat.EncounterLayerData.gameObject.GetComponentInParent(); - DropPodUtils.DropPodSpawner dropSpawner = encounterLayerParent.gameObject.GetComponent(); - if (dropSpawner == null) { dropSpawner = encounterLayerParent.gameObject.AddComponent(); } + if (ability.Def.Id != ModInit.modSettings.BattleArmorMountAndSwarmID) + { + if (actor.IsMountedUnit()) + { + if (!actor.IsMountedInternal()) + { + button.DisableButton(); + } + else + { + var carrier = actor.Combat.FindActorByGUID(ModState.PositionLockMount[actor.GUID]); + if (!carrier.HasFiringPorts()) + { + button.DisableButton(); + } + } + } + else if (actor.IsSwarmingUnit()) + { + button.DisableButton(); + } + } - dropSpawner.Unit = turretActor; - dropSpawner.Combat = combat; - dropSpawner.Parent = UnityGameInstance.BattleTechGame.Combat.EncounterLayerData - .GetComponentInParent(); - dropSpawner.DropPodPosition = positionA; - dropSpawner.DropPodRotation = quaternion; + if (ability.Def.Id == ModInit.modSettings.BattleArmorDeSwarmRoll || + ability.Def.Id == ModInit.modSettings.BattleArmorDeSwarmSwat) + { + if (actor is Vehicle vehicle || actor.IsCustomUnitVehicle()) + { + button.DisableButton(); + } - ModInit.modLog?.Trace?.Write($"[Ability_ActivateSpawnTurret] DropPodAnim location {positionA} is also {dropSpawner.DropPodPosition}"); - ModInit.modLog?.Trace?.Write($"[Ability_ActivateSpawnTurret] Is dropAnim null fuckin somehow? {dropSpawner == null}"); - dropSpawner.DropPodVfxPrefab = dropSpawner.Parent.DropPodVfxPrefab; - dropSpawner.DropPodLandedPrefab = dropSpawner.Parent.dropPodLandedPrefab; - dropSpawner.LoadDropPodPrefabs(dropSpawner.DropPodVfxPrefab, dropSpawner.DropPodLandedPrefab); - ModInit.modLog?.Trace?.Write($"[Ability_ActivateSpawnTurret] loaded prefabs success"); - dropSpawner.StartCoroutine(dropSpawner.StartDropPodAnimation(0f)); - ModInit.modLog?.Trace?.Write($"[Ability_ActivateSpawnTurret] started drop pod anim"); + if (!actor.HasSwarmingUnits()) + { + button.DisableButton(); + } + } - /////////////// + if (ability.Def.Id == ModInit.modSettings.AirliftAbilityID && ModInit.modSettings.CanDropOffAfterMoving)// && actor.MovingToPosition == null) // maybe need to check IsAnyOrderActive (but that might screw me) + { + if (actor.HasAirliftedUnits()) + { + //button.DisableButton(); + //if (!button.gameObject.activeSelf) + //{ + // button.gameObject.SetActive(true); + //} - if (team.IsLocalPlayer && (ModInit.modSettings.commandUseCostsMulti > 0 || - __instance.Def.getAbilityDefExtension().CBillCost > 0)) + //button.InitButton(CombatHUDMechwarriorTray.GetSelectionTypeFromTargeting(ability.Def.Targeting, false), ability, ability.Def.AbilityIcon, ability.Def.Description.Id, ability.Def.Description.Name, actor); + button.ResetButtonIfNotActive(actor); + } + } + } + } + + [HarmonyPatch(typeof(CombatSelectionHandler), "ProcessInput")] + public static class CombatSelectionHandler_ProcessInput + { + public static void Prefix(ref bool __runOriginal, CombatSelectionHandler __instance) + { + if (!__runOriginal) return; + if (__instance.SelectedActor != null && __instance.SelectedActor.team != null && __instance.SelectedActor.team.IsLocalPlayer) + { + var combat = UnityGameInstance.BattleTechGame.Combat; + if (!LazySingletonBehavior.Instance.ToggleUINode || + combat.StackManager.TopSequence is ArtilleryObjectiveSequence) { - var unitName = ""; - var unitCost = 0; - var unitID = ""; + __runOriginal = false; + return; + } - dm.TurretDefs.TryGet(actorResource, out var turretDef); - turretDef.Refresh(); - unitName = turretDef.Description.UIName; - unitID = turretDef.Description.Id; - unitCost = turretDef.Chassis.Description.Cost; + if (__instance.CombatInputBlocked) + { + __runOriginal = false; + return; + } + if (!combat.TurnDirector.GameHasBegun) + { + if (CameraControl.Instance != null && CameraControl.Instance.IsInTutorialMode) + { + __runOriginal = true; + return; + } + } - if (ModState.CommandUses.All(x => x.UnitID != actorResource)) + if (Input.anyKeyDown) + { + // if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) + + if (Input.GetKey(ModInit.modSettings.EquipmentButtonsHotkey)) { - var commandUse = - new CmdUseInfo(unitID, __instance.Def.Description.Name, unitName, unitCost, - __instance.Def.getAbilityDefExtension().CBillCost); + var hud = __instance.HUD;//IRBTModUtils.SharedState.CombatHUD; + if (hud.SelectedActor == __instance.SelectedActor) + { + //var slots = Traverse.Create(CombatHUDEquipmentPanel.Instance).Property("operatinalSlots").GetValue>(); + var slots = CombatHUDEquipmentPanel.Instance.operatinalSlots; + var lastActive = new Tuple(-1,-1); + var buttonList = new List(); + for (var slotIndex = 0; slotIndex < slots.Count; slotIndex++) + { + if (slots[slotIndex].buttons.Count > 0) + { + for (var buttonIndex = 0; + buttonIndex < slots[slotIndex].buttons.Count; + buttonIndex++) + { + if (slots[slotIndex].buttons[buttonIndex].isActiveAndEnabled && slots[slotIndex].buttons[buttonIndex].gameObject.activeSelf) + buttonList.Add(slots[slotIndex].buttons[buttonIndex]); + } + } + } - ModState.CommandUses.Add(commandUse); - ModInit.modLog?.Info?.Write( - $"[Ability_ActivateSpawnTurret] Added usage cost for {commandUse.CommandName} - {commandUse.UnitName}. UnitUseCost (unadjusted): {unitCost}. Ability Use Cost: {__instance.Def.getAbilityDefExtension().CBillCost}"); - } - else - { - var cmdUse = ModState.CommandUses.FirstOrDefault(x => x.UnitID == actorResource); - if (cmdUse == null) - { - ModInit.modLog?.Info?.Write($"ERROR: cmdUseInfo was null"); - } - else - { - cmdUse.UseCount += 1; - ModInit.modLog?.Info?.Write( - $"[Ability_ActivateSpawnTurret] Added usage cost for {cmdUse.CommandName} - {cmdUse.UnitName}. UnitUseCost (unadjusted): {unitCost}. Ability Use Cost: {__instance.Def.getAbilityDefExtension().CBillCost}. Now used {cmdUse.UseCount} times."); + if (buttonList.Count <= 0) + { + __runOriginal = false; + return; + } + for (var index = 0; index < buttonList.Count; index++) + { + if (buttonList[index].IsActive) + { + buttonList[index].OnClick(); + if (buttonList.Count > index) + { + buttonList[index + 1].OnClick(); + __runOriginal = false; + return; + } + } + } + buttonList[0].OnClick(); } + __runOriginal = false; + return; } } } - - __runOriginal = false; + __runOriginal = true; return; } } - [HarmonyPatch(typeof(Ability), "SpawnFlares")] - public static class Ability_SpawnFlares + [HarmonyPatch(typeof(CombatSpawningReticle), "ShowReticle")] + public static class CombatSpawningReticle_ShowReticle { - static bool Prepare() => false; //disabled - private static void Prefix(ref bool __runOriginal, Ability __instance, Vector3 positionA, Vector3 positionB, string prefabName, - int numFlares, int numPhases) + public static void Postfix(CombatSpawningReticle __instance) { - if (!__runOriginal) return; - if (__instance.Combat.ActiveContract.ContractTypeValue.IsSkirmish) + if (!string.IsNullOrEmpty(ModInit.modSettings.customSpawnReticleAsset)) { - __runOriginal = true; - return; - } - Vector3 b = (positionB - positionA) / (float)(numFlares - 1); - - Vector3 line = positionB - positionA; - Vector3 left = Vector3.Cross(line, Vector3.up).normalized; - Vector3 right = -left; - - var startLeft = positionA + (left * __instance.Def.FloatParam1); - var startRight = positionA + (right * __instance.Def.FloatParam1); - - Vector3 vector = positionA; + var childComponents = __instance.gameObject.GetComponentsInChildren(true); - vector.y = __instance.Combat.MapMetaData.GetLerpedHeightAt(vector, false); - startLeft.y = __instance.Combat.MapMetaData.GetLerpedHeightAt(startLeft, false); - startRight.y = __instance.Combat.MapMetaData.GetLerpedHeightAt(startRight, false); - List list = new List(); - for (int i = 0; i < numFlares; i++) - { - ObjectSpawnData item = new ObjectSpawnData(prefabName, vector, Quaternion.identity, true, false); - list.Add(item); - vector += b; - vector.y = __instance.Combat.MapMetaData.GetLerpedHeightAt(vector, false); + for (int i = 0; i < childComponents.Length; i++) + { + if (childComponents[i].name == "ReticleDecalCircle") + { + var decalFromCirle = childComponents[i].GetComponent(); + var dm = UnityGameInstance.BattleTechGame.DataManager; + var newTexture = dm.GetObjectOfType(ModInit.modSettings.customSpawnReticleAsset, + BattleTechResourceType.Texture2D); + decalFromCirle.DecalMaterial.mainTexture = newTexture; + } + } + //var circle1 = GameObject.Find("ReticleDecalCircle"); } - for (int i = 0; i < numFlares; i++) - { - ObjectSpawnData item = new ObjectSpawnData(prefabName, startLeft, Quaternion.identity, true, false); - list.Add(item); - startLeft += b; - startLeft.y = __instance.Combat.MapMetaData.GetLerpedHeightAt(startLeft, false); - } + var decals = __instance.gameObject.GetComponentsInChildren(); - for (int i = 0; i < numFlares; i++) + foreach (var decal in decals) { - ObjectSpawnData item = - new ObjectSpawnData(prefabName, startRight, Quaternion.identity, true, false); - list.Add(item); - startRight += b; - startRight.y = __instance.Combat.MapMetaData.GetLerpedHeightAt(startRight, false); + if (ModInit.modSettings.customSpawnReticleColor != null) + { + var customColor = new Color(ModInit.modSettings.customSpawnReticleColor.Rf, + ModInit.modSettings.customSpawnReticleColor.Gf, + ModInit.modSettings.customSpawnReticleColor.Bf); + decal.DecalMaterial.color = customColor; + } + else + { + decal.DecalMaterial.color = Color.green; + } } - - SpawnObjectSequence spawnObjectSequence = new SpawnObjectSequence(__instance.Combat, list); - __instance.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(spawnObjectSequence)); - List spawnedObjects = spawnObjectSequence.spawnedObjects; - CleanupObjectSequence eventSequence = new CleanupObjectSequence(__instance.Combat, spawnedObjects); - TurnEvent tEvent = new TurnEvent(Guid.NewGuid().ToString(), __instance.Combat, numPhases, null, - eventSequence, __instance.Def, false); - __instance.Combat.TurnDirector.AddTurnEvent(tEvent); - __runOriginal = false; - return; - } - } - - - [HarmonyPatch(typeof(CombatGameState), "OnCombatGameDestroyed")] - public static class CombatGameState_OnCombatGameDestroyed - { - private static void Postfix(CombatGameState __instance) - { - ModState.ResetAll(); - StrategicSelection.StrategicTargetIndicatorsManager.Clear(); } } - [HarmonyPatch(typeof(AbstractActor), "OnPhaseBegin")] - public static class AbstractActor_OnPhaseBegin + //the following two patched on gamerep.update are solely to suppress the "safety teleport" for strafing units since they spawn offmap and move on their own + [HarmonyPatch(typeof(CustomMechRepresentation), "GameRepresentation_Update")] + public static class CustomMechRepresentation_GameRepresentation_Update { - public static void Postfix(AbstractActor __instance) + public static void Prefix(ref bool __runOriginal, CustomMechRepresentation __instance) { - if (ModState.ResupplyShutdownPhases.ContainsKey(__instance.GUID)) + if (!__runOriginal) return; + if (__instance.__parentActor == null) { - ModState.ResupplyShutdownPhases[__instance.GUID]--; - if (ModState.ResupplyShutdownPhases[__instance.GUID] <= 0) - ModState.ResupplyShutdownPhases.Remove(__instance.GUID); + __runOriginal = true; + return; } - } - } - - [HarmonyPatch(typeof(Team), "OnRoundBegin")] - public static class TurnActor_OnRoundBegin - { - public static void Postfix(Team __instance) - { - if (__instance.Combat.ActiveContract.ContractTypeValue.IsSkirmish) return; - foreach (var ability in ModState.CommandAbilities) + var combat = UnityGameInstance.BattleTechGame.Combat; + if (combat == null) { - ability.OnNewRound(); + __runOriginal = true; + return; } - - foreach (var despawn in ModState.DeferredDespawnersFromStrafe) + if (combat.ActiveContract.ContractTypeValue.IsSkirmish) { - var msg = new DespawnActorMessage(EncounterLayerData.MapLogicGuid, despawn.Key, (DeathMethod)DespawnFloatieMessage.Escaped); - //Utils._despawnActorMethod.Invoke(despawn.Value, new object[] { msg }); - despawn.Value.DespawnActor(msg); + __runOriginal = true; + return; } + var registry = combat.ItemRegistry; - //var team = __instance as Team; - - if (__instance?.units != null) - foreach (var unit in __instance?.units) - { - var rep = unit.GameRep as PilotableActorRepresentation; - rep?.ClearForcedPlayerVisibilityLevel(__instance.Combat.GetAllLivingCombatants()); - } - // team?.ResetUnitVisibilityLevels(); - __instance?.RebuildVisibilityCacheAllUnits(__instance.Combat.GetAllLivingCombatants()); + if (__instance.__parentActor?.spawnerGUID == null) + { + //ModInit.modLog?.Info?.Write($"Couldn't find UnitSpawnPointGameLogic for {____parentActor?.DisplayName}. Should be CMD Ability actor; skipping safety teleport!"); + __runOriginal = false; + return; + } + if (registry.GetItemByGUID(__instance.__parentActor?.spawnerGUID) != null) + { + __runOriginal = true; + return; + } + __runOriginal = false; + return; + //ModInit.modLog?.Info?.Write($"Couldn't find UnitSpawnPointGameLogic for {____parentActor?.DisplayName}. Should be CMD Ability actor; skipping safety teleport!"); } - } - [HarmonyPatch(typeof(TurnDirector), "StartFirstRound")] - public static class TurnDirector_StartFirstRound - { - public static void Postfix(TurnDirector __instance) + + public static void Postfix(CustomMechRepresentation __instance) { - if (ModState.DeferredInvokeBattleArmor.Count > 0) + if (__instance.HeightController.isInChangeHeight) { - for (var index = 0; index < ModState.DeferredInvokeBattleArmor.Count; index++) + var combat = __instance.parentActor.Combat; + if (__instance.parentActor.HasAirliftedUnits()) { - var spawn = ModState.DeferredInvokeBattleArmor[index].Value; - ModInit.modLog?.Info?.Write( - $"[TurnDirector.StartFirstRound] Found deferred spawner at index {index} of {ModState.DeferredInvokeBattleArmor.Count - 1}, invoking and trying to spawn a battle armor of some kind."); - ModState.DeferredBattleArmorSpawnerFromDelegate = true; - spawn(); + var airliftedUnits = ModState.AirliftTrackers.Where(x => + x.Value.CarrierGUID == __instance.parentActor.GUID); + foreach (var trackerInfo in airliftedUnits) + { + var targetActor = combat.FindActorByGUID(trackerInfo.Key); + if (targetActor == null) continue; + var pos = Vector3.zero; + if (__instance.parentActor is CustomMech mech) + { + pos = __instance.parentActor.CurrentPosition + Vector3.down * trackerInfo.Value.Offset + + Vector3.up * mech.custGameRep.HeightController.CurrentHeight; + targetActor.TeleportActorVisual(pos); + if (targetActor is CustomMech customMech) + { + customMech.custGameRep.j_Root.localRotation = Quaternion.identity; + } - } - ModState.ResetDeferredBASpawners(); - } + targetActor.GameRep.transform.rotation = + __instance.parentActor.GameRep.transform.rotation; + targetActor.CurrentRotation = __instance.parentActor.CurrentRotation; + } + else + { + pos = __instance.parentActor.CurrentPosition + Vector3.down * trackerInfo.Value.Offset; + targetActor.TeleportActorVisual(pos); + if (targetActor is CustomMech customMech) + { + customMech.custGameRep.j_Root.localRotation = Quaternion.identity; + } - if (__instance.Combat.ActiveContract.ContractTypeValue.IsSkirmish) return; + targetActor.GameRep.transform.rotation = + __instance.parentActor.GameRep.transform.rotation; + targetActor.CurrentRotation = __instance.parentActor.CurrentRotation; + } - var playerTeam = __instance.Combat.Teams.First(x => x.IsLocalPlayer); - var dm = playerTeam.Combat.DataManager; + targetActor.MountedEvasion(__instance.parentActor); + ModInit.modLog?.Debug?.Write( + $"[CustomMechRepresentation_GameRepresentation_Update] PositionLockMount- Setting airlifted unit {targetActor.DisplayName} position to same as carrier unit {__instance.parentActor.DisplayName}"); - foreach (var abilityDefKVP in dm.AbilityDefs.Where(x => - x.Value.specialRules == AbilityDef.SpecialRules.SpawnTurret || - x.Value.specialRules == AbilityDef.SpecialRules.Strafe)) - { + ModState.CachedUnitCoordinates[__instance.parentActor.GUID] = __instance.parentActor.CurrentPosition; + } + } - if (playerTeam.units.Any(x => x.GetPilot().Abilities.Any(y => y.Def == abilityDefKVP.Value)) || - playerTeam.units.Any(x => x.ComponentAbilities.Any(z => z.Def == abilityDefKVP.Value))) + if (__instance.parentActor.HasMountedUnits()) { - //only do things for abilities that pilots have? move things here. also move AbstractActor initialization to ability start to minimize neutralTeam think time, etc. and then despawn? - done - var ability = new Ability(abilityDefKVP.Value); - ability.Init(playerTeam.Combat); - if (ModState.CommandAbilities.All(x => x != ability)) + var targetActorGUIDs = + ModState.PositionLockMount.Where(x => x.Value == __instance.parentActor.GUID); + foreach (var targetActorGUID in targetActorGUIDs) { - ModState.CommandAbilities.Add(ability); - } + var targetActor = combat.FindActorByGUID(targetActorGUID.Key); + if (targetActor == null) continue; + var pos = Vector3.zero; + if (__instance.parentActor is CustomMech mech) + { + pos = __instance.parentActor.CurrentPosition + + Vector3.up * mech.custGameRep.HeightController.CurrentHeight; + targetActor.TeleportActorVisual(pos); + } + else + { + targetActor.TeleportActorVisual(__instance.parentActor.CurrentPosition); + } - ModInit.modLog?.Info?.Write($"[TurnDirector.StartFirstRound] Added {ability?.Def?.Id} to CommandAbilities"); + targetActor.MountedEvasion(__instance.parentActor); + ModInit.modLog?.Debug?.Write( + $"[CustomMechRepresentation_GameRepresentation_Update] PositionLockMount- Setting riding unit {targetActor.DisplayName} position to same as carrier unit {__instance.parentActor.DisplayName}"); + ModState.CachedUnitCoordinates[__instance.parentActor.GUID] = __instance.parentActor.CurrentPosition; + } } - } - __instance.Combat.UpdateResupplyTeams(); - __instance.Combat.UpdateResupplyAbilitiesGetAllLivingActors(); - // now try to auto-mount?????? - if (ModState.PairingInfos.Count == 0) return; - if (DeployManualHelper.IsInManualSpawnSequence) return; - foreach (var unit in playerTeam.units) - { - if (ModState.PairingInfos.TryGetValue(unit.GetPilot().pilotDef.Description.Id, out var info)) + // removed return/else so swarming units are locked to carrier even if carrier has mounted units. derp. + if (__instance.parentActor.HasSwarmingUnits()) { - foreach (var squadPilot in info.PairedBattleArmor) + var targetActorGUIDs = + ModState.PositionLockSwarm.Where(x => x.Value == __instance.parentActor.GUID); + foreach (var targetActorGUID in targetActorGUIDs) { - foreach (var unit2 in playerTeam.units) - { - if (unit2 is TrooperSquad squad && - unit2.GetPilot().pilotDef.Description.Id == squadPilot) - { - unit.MountBattleArmorToChassis(squad, true, true); + var targetActor = combat.FindActorByGUID(targetActorGUID.Key); + if (targetActor == null) continue; + targetActor.TeleportActorVisual(__instance.parentActor.CurrentPosition); + targetActor.MountedEvasion(__instance.parentActor); + ModInit.modLog?.Debug?.Write( + $"[CustomMechRepresentation_GameRepresentation_Update] PositionLockMount- Setting riding unit {targetActor.DisplayName} position to same as carrier unit {__instance.parentActor.DisplayName}"); - if (unit is CustomMech custMech && custMech.FlyingHeight() > 1.5f) - { - var pos = custMech.CurrentPosition + - Vector3.up * custMech.custGameRep.HeightController.CurrentHeight; - squad.TeleportActorVisual(pos); - } - } - } + ModState.CachedUnitCoordinates[__instance.parentActor.GUID] = __instance.parentActor.CurrentPosition; } } } @@ -2039,181 +1894,368 @@ public static void Postfix(CombatHUD HUD, List positions) } } - [HarmonyPatch(typeof(TurnDirector), "IncrementActiveTurnActor")] - public static class TurnDirector_IncrementActiveTurnActor + [HarmonyPatch(typeof(EncounterLayerData), "BuildItemRegistry", new Type[]{typeof(CombatGameState)})] + public static class EncounterLayerData_BuildItemRegistry { - public static void Prefix(TurnDirector __instance) + public static void Postfix(EncounterLayerData __instance, CombatGameState combat) { - if (__instance.Combat.ActiveContract.ContractTypeValue.IsSkirmish) return; - if (ModState.DeferredInvokeSpawns.Count > 0 && __instance.ActiveTurnActor is Team activeTeam && - activeTeam.IsLocalPlayer) + foreach (var team in combat.Teams) { - for (var index = 0; index < ModState.DeferredInvokeSpawns.Count; index++) + if (!team.IsLocalPlayer && TeamDefinition.FullTeamDefinitionGuidNames.ContainsKey(team.GUID)) { - var spawn = ModState.DeferredInvokeSpawns[index].Value; - var resource = ModState.DeferredInvokeSpawns[index].Key.Split('@'); - ModState.DeferredActorResource = resource[1]; - ModInit.modLog?.Info?.Write( - $"Found deferred spawner at index {index} of {ModState.DeferredInvokeSpawns.Count - 1}, invoking and trying to spawn {ModState.DeferredActorResource}."); - ModState.DeferredSpawnerFromDelegate = true; - spawn(); - ModState.ResetDelegateInfos(); + Utils.FetchAISupportTeam(team); } - ModState.ResetDeferredSpawners(); } } } - [HarmonyPatch(typeof(CombatHUDActionButton), "ActivateCommandAbility", - new Type[] { typeof(string), typeof(Vector3), typeof(Vector3) })] - public static class CombatHUDActionButton_ActivateCommandAbility + [HarmonyPatch(typeof(GameRepresentation), "Update")] + public static class GameRepresentation_Update { - public static void Prefix(ref bool __runOriginal, CombatHUDActionButton __instance, string teamGUID, - Vector3 positionA, //prefix to try and make command abilities behave like normal ones - Vector3 positionB) + public static void Prefix(ref bool __runOriginal, GameRepresentation __instance) { if (!__runOriginal) return; - var HUD = __instance.HUD;//IRBTModUtils.SharedState.CombatHUD;//Traverse.Create(__instance).Property("HUD").GetValue(); - var theActor = HUD.SelectedActor; - if (theActor == null) + if (__instance._parentActor == null) { __runOriginal = true; return; } - var combat = HUD.Combat; + + var combat = UnityGameInstance.BattleTechGame.Combat; + if (combat == null) + { + __runOriginal = true; + return; + } + if (combat.ActiveContract.ContractTypeValue.IsSkirmish) { __runOriginal = true; return; } - if (__instance.Ability != null && - __instance.Ability.Def.ActivationTime == AbilityDef.ActivationTiming.CommandAbility && - (__instance.Ability.Def.Targeting == AbilityDef.TargetingType.CommandTargetTwoPoints || - __instance.Ability.Def.Targeting == AbilityDef.TargetingType.CommandSpawnPosition)) + + var registry = combat.ItemRegistry; + + if (__instance._parentActor?.spawnerGUID == null) { - MessageCenterMessage messageCenterMessage = new AbilityInvokedMessage(theActor.GUID, theActor.GUID, - __instance.Ability.Def.Id, positionA, positionB); - messageCenterMessage.IsNetRouted = true; - combat.MessageCenter.PublishMessage(messageCenterMessage); - messageCenterMessage = new AbilityConfirmedMessage(theActor.GUID, theActor.GUID, - __instance.Ability.Def.Id, positionA, positionB); - messageCenterMessage.IsNetRouted = true; - combat.MessageCenter.PublishMessage(messageCenterMessage); - __instance.DisableButton(); + //ModInit.modLog?.Info?.Write($"Couldn't find UnitSpawnPointGameLogic for {____parentActor?.DisplayName}. Should be CMD Ability actor; skipping safety teleport!"); + __runOriginal = false; + return; + } + + if (registry.GetItemByGUID(__instance._parentActor?.spawnerGUID) != null) + { + __runOriginal = true; + return; } + __runOriginal = false; return; + //return registry.GetItemByGUID(__instance._parentActor?.spawnerGUID) != null; + //ModInit.modLog?.Info?.Write($"Couldn't find UnitSpawnPointGameLogic for {____parentActor?.DisplayName}. Should be CMD Ability actor; skipping safety teleport!"); } + } - public static void Postfix(CombatHUDActionButton __instance, string teamGUID, Vector3 positionA, - Vector3 positionB) + [HarmonyPatch(typeof(Mech), "RecalcEvasivePips")] + public static class Mech_RecalcEvasivePips + { + static bool Prepare() => ModInit.modSettings.ReworkedCarrierEvasion; + public static void Postfix(Mech __instance) { - if (UnityGameInstance.BattleTechGame.Combat.ActiveContract.ContractTypeValue.IsSkirmish) return; - var def = __instance.Ability.Def; - var HUD = __instance.HUD;//IRBTModUtils.SharedState.CombatHUD; //Traverse.Create(__instance).Property("HUD").GetValue(); - var theActor = HUD.SelectedActor; - if (theActor == null) return; - if (def.specialRules == AbilityDef.SpecialRules.Strafe && - ModInit.modSettings.strafeEndsActivation) + if (__instance.IsMountedUnit()) { - if (theActor is Mech mech) + if (ModState.PositionLockMount.TryGetValue(__instance.GUID, out var carrierID)) { - mech.GenerateAndPublishHeatSequence(-1, true, false, theActor.GUID); + var carrier = __instance.Combat.FindActorByGUID(carrierID); + __instance.MountedEvasion(carrier); + } + } + else if (__instance.IsSwarmingUnit()) + { + if (ModState.PositionLockSwarm.TryGetValue(__instance.GUID, out var carrierID)) + { + var carrier = __instance.Combat.FindActorByGUID(carrierID); + __instance.MountedEvasion(carrier); + } + } + else if (__instance.IsAirlifted()) + { + if (ModState.AirliftTrackers.TryGetValue(__instance.GUID, out var carrierID)) + { + var carrier = __instance.Combat.FindActorByGUID(carrierID.CarrierGUID); + __instance.MountedEvasion(carrier); + } + } + } + } + + [HarmonyPatch(typeof(MechDisplacementSequence), "Update")] + public static class MechDisplacementSequence_Update + { + public static void Postfix(MechDisplacementSequence __instance) + { + if (__instance.OwningMech == null) return; + if (ModState.CachedUnitCoordinates.ContainsKey(__instance.OwningMech.GUID)) + { + if (__instance.OwningMech is CustomMech mech) + { + if (ModState.CachedUnitCoordinates[__instance.OwningMech.GUID] == + __instance.OwningMech.CurrentPosition && + !mech.custGameRep.HeightController.isInChangeHeight) return; + } + else if (ModState.CachedUnitCoordinates[__instance.OwningMech.GUID] == + __instance.OwningMech.CurrentPosition) + { + return; + } + } + + var combat = __instance.OwningMech.Combat; + if (__instance.OwningMech.HasAirliftedUnits()) + { + var airliftedUnits = ModState.AirliftTrackers.Where(x => + x.Value.CarrierGUID == __instance.OwningMech.GUID); + foreach (var trackerInfo in airliftedUnits) + { + var targetActor = combat.FindActorByGUID(trackerInfo.Key); + if (targetActor == null) continue; + var pos = Vector3.zero; + if (__instance.OwningMech is CustomMech mech) + { + pos = __instance.OwningMech.CurrentPosition + Vector3.down * trackerInfo.Value.Offset + + Vector3.up * mech.custGameRep.HeightController.CurrentHeight; + targetActor.TeleportActorVisual(pos); + if (targetActor is CustomMech customMech) + { + customMech.custGameRep.j_Root.localRotation = Quaternion.identity; + } + + targetActor.GameRep.transform.rotation = + __instance.OwningMech.GameRep.transform.rotation; + targetActor.CurrentRotation = __instance.OwningMech.CurrentRotation; + } + else + { + pos = __instance.OwningMech.CurrentPosition + Vector3.down * trackerInfo.Value.Offset; + targetActor.TeleportActorVisual(pos); + if (targetActor is CustomMech customMech) + { + customMech.custGameRep.j_Root.localRotation = Quaternion.identity; + } + + targetActor.GameRep.transform.rotation = + __instance.OwningMech.GameRep.transform.rotation; + targetActor.CurrentRotation = __instance.OwningMech.CurrentRotation; + } + + targetActor.MountedEvasion(__instance.OwningMech); + ModInit.modLog?.Debug?.Write( + $"[MechDisplacementSequence_OnComplete] PositionLockMount- Setting airlifted unit {targetActor.DisplayName} position to same as carrier unit {__instance.OwningMech.DisplayName}"); + + ModState.CachedUnitCoordinates[__instance.OwningMech.GUID] = __instance.OwningMech.CurrentPosition; } + } + + if (__instance.OwningMech.HasMountedUnits()) + { + var targetActorGUIDs = + ModState.PositionLockMount.Where(x => x.Value == __instance.OwningMech.GUID); + foreach (var targetActorGUID in targetActorGUIDs) + { + var targetActor = combat.FindActorByGUID(targetActorGUID.Key); + if (targetActor == null) continue; + var pos = Vector3.zero; + if (__instance.OwningMech is CustomMech mech) + { + pos = __instance.OwningMech.CurrentPosition + + Vector3.up * mech.custGameRep.HeightController.CurrentHeight; + targetActor.TeleportActorVisual(pos); + } + else + { + targetActor.TeleportActorVisual(__instance.OwningMech.CurrentPosition); + } - var sequence = theActor.DoneWithActor(); - theActor.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(sequence)); - //theActor.OnActivationEnd(theActor.GUID, __instance.GetInstanceID()); - return; + targetActor.MountedEvasion(__instance.OwningMech); + ModInit.modLog?.Debug?.Write( + $"[MechDisplacementSequence_OnComplete] PositionLockMount- Setting riding unit {targetActor.DisplayName} position to same as carrier unit {__instance.OwningMech.DisplayName}"); + + ModState.CachedUnitCoordinates[__instance.OwningMech.GUID] = __instance.OwningMech.CurrentPosition; + } } - if (def.specialRules == AbilityDef.SpecialRules.SpawnTurret && - ModInit.modSettings.spawnTurretEndsActivation) + // removed return/else so swarming units are locked to carrier even if carrier has mounted units. derp. + if (__instance.OwningMech.HasSwarmingUnits()) { - if (theActor is Mech mech) + var targetActorGUIDs = + ModState.PositionLockSwarm.Where(x => x.Value == __instance.OwningMech.GUID); + foreach (var targetActorGUID in targetActorGUIDs) { - mech.GenerateAndPublishHeatSequence(-1, true, false, theActor.GUID); + var targetActor = combat.FindActorByGUID(targetActorGUID.Key); + if (targetActor == null) continue; + targetActor.TeleportActorVisual(__instance.OwningMech.CurrentPosition); + targetActor.MountedEvasion(__instance.OwningMech); + ModInit.modLog?.Debug?.Write( + $"[MechDisplacementSequence_OnComplete] PositionLockMount- Setting riding unit {targetActor.DisplayName} position to same as carrier unit {__instance.OwningMech.DisplayName}"); + + ModState.CachedUnitCoordinates[__instance.OwningMech.GUID] = __instance.OwningMech.CurrentPosition; } - var sequence = theActor.DoneWithActor(); - theActor.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(sequence)); - //theActor.OnActivationEnd(theActor.GUID, __instance.GetInstanceID()); } - //need to make sure proccing from equipment button also ends turn? } } - [HarmonyPatch(typeof(CombatHUDEquipmentSlot), "ActivateCommandAbility", - new Type[] { typeof(string), typeof(Vector3), typeof(Vector3) })] - public static class CombatHUDEquipmentSlot_ActivateCommandAbility + [HarmonyPatch(typeof(MechStartupInvocation), "Invoke")] + [HarmonyPriority(Priority.First)] + public static class MechStartupInvocation_Invoke { - public static void Prefix(ref bool __runOriginal, CombatHUDEquipmentSlot __instance, string teamGUID, Vector3 positionA, - Vector3 positionB) + public static void Prefix(ref bool __runOriginal, MechStartupInvocation __instance, CombatGameState combatGameState) { if (!__runOriginal) return; - var HUD = __instance.HUD;//IRBTModUtils.SharedState.CombatHUD;// Traverse.Create(__instance).Property("HUD").GetValue(); - var theActor = HUD.SelectedActor; - if (theActor == null) + Mech mech = combatGameState.FindActorByGUID(__instance.MechGUID) as Mech; + if (mech == null) { - __runOriginal = true; + //InvocationMessage.logger.LogError("MechStartupInvocation.Invoke failed! Unable to Mech!"); + __runOriginal = false; return; } - var combat = HUD.Combat; - if (combat.ActiveContract.ContractTypeValue.IsSkirmish) + + if (ModState.ResupplyShutdownPhases.ContainsKey(mech.GUID)) { - __runOriginal = true; + var txt = new Text("RESUPPLY IN PROGRESS: ABORTING RESTART AND SINKING HEAT"); + mech.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage( + new ShowActorInfoSequence(mech, txt, FloatieMessage.MessageNature.Buff, + false))); + + DoneWithActorSequence doneWithActorSequence = (DoneWithActorSequence)mech.GetDoneWithActorOrders(); + MechHeatSequence mechHeatSequence = new MechHeatSequence(OwningMech: mech, + performHeatSinkStep: true, applyStartupHeatSinks: false, instigatorID: "STARTUP"); + doneWithActorSequence.AddChildSequence(mechHeatSequence, mechHeatSequence.MessageIndex); + + InvocationStackSequenceCreated message = + new InvocationStackSequenceCreated(doneWithActorSequence, __instance); + combatGameState.MessageCenter.PublishMessage(message); + AddSequenceToStackMessage.Publish(combatGameState.MessageCenter, doneWithActorSequence); + __runOriginal = false; return; } - if (__instance.Ability != null && - __instance.Ability.Def.ActivationTime == AbilityDef.ActivationTiming.CommandAbility && - (__instance.Ability.Def.Targeting == AbilityDef.TargetingType.CommandTargetTwoPoints || - __instance.Ability.Def.Targeting == AbilityDef.TargetingType.CommandSpawnPosition)) - { - MessageCenterMessage messageCenterMessage = new AbilityInvokedMessage(theActor.GUID, theActor.GUID, - __instance.Ability.Def.Id, positionA, positionB); - messageCenterMessage.IsNetRouted = true; - combat.MessageCenter.PublishMessage(messageCenterMessage); - messageCenterMessage = new AbilityConfirmedMessage(theActor.GUID, theActor.GUID, - __instance.Ability.Def.Id, positionA, positionB); - messageCenterMessage.IsNetRouted = true; - combat.MessageCenter.PublishMessage(messageCenterMessage); - __instance.DisableButton(); - } - __runOriginal = false; + __runOriginal = true; return; } + } - public static void Postfix(CombatHUDEquipmentSlot __instance, string teamGUID, Vector3 positionA, - Vector3 positionB) + [HarmonyPatch(typeof(OrderSequence), "OnUpdate")] + public static class OrderSequence_OnUpdate + { + public static void Postfix(OrderSequence __instance) { - if (__instance.Ability.Combat.ActiveContract.ContractTypeValue.IsSkirmish) return; - var def = __instance.Ability.Def; - var HUD = __instance.HUD;//IRBTModUtils.SharedState.CombatHUD;//Traverse.Create(__instance).Property("HUD").GetValue(); - var theActor = HUD.SelectedActor; - if (theActor == null) return; - if (def.specialRules == AbilityDef.SpecialRules.Strafe && - ModInit.modSettings.strafeEndsActivation) + if (__instance.owningActor == null) return; + if (__instance is not ActorMovementSequence && __instance is not MechJumpSequence && + __instance is not MechMeleeSequence && __instance is not MechDFASequence) return; + if (ModState.CachedUnitCoordinates.ContainsKey(__instance.owningActor.GUID)) { - if (theActor is Mech mech) + if (__instance.owningActor is CustomMech mech) { - mech.GenerateAndPublishHeatSequence(-1, true, false, theActor.GUID); + if (ModState.CachedUnitCoordinates[__instance.owningActor.GUID] == + __instance.owningActor.CurrentPosition && + !mech.custGameRep.HeightController.isInChangeHeight) return; + } + else if (ModState.CachedUnitCoordinates[__instance.owningActor.GUID] == + __instance.owningActor.CurrentPosition) + { + return; } + } - var sequence = theActor.DoneWithActor(); - theActor.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(sequence)); - //theActor.OnActivationEnd(theActor.GUID, __instance.GetInstanceID()); - return; + var combat = __instance.owningActor.Combat; + if (__instance.owningActor.HasAirliftedUnits()) + { + var airliftedUnits = ModState.AirliftTrackers.Where(x => + x.Value.CarrierGUID == __instance.owningActor.GUID); + foreach (var trackerInfo in airliftedUnits) + { + var targetActor = combat.FindActorByGUID(trackerInfo.Key); + if (targetActor == null) continue; + var pos = Vector3.zero; + if (__instance.owningActor is CustomMech mech) + { + pos = __instance.owningActor.CurrentPosition + Vector3.down * trackerInfo.Value.Offset + + Vector3.up * mech.custGameRep.HeightController.CurrentHeight; + targetActor.TeleportActorVisual(pos); + if (targetActor is CustomMech customMech) + { + customMech.custGameRep.j_Root.localRotation = Quaternion.identity; + } + + targetActor.GameRep.transform.rotation = + __instance.owningActor.GameRep.transform.rotation; + targetActor.CurrentRotation = __instance.owningActor.CurrentRotation; + } + else + { + pos = __instance.owningActor.CurrentPosition + Vector3.down * trackerInfo.Value.Offset; + targetActor.TeleportActorVisual(pos); + if (targetActor is CustomMech customMech) + { + customMech.custGameRep.j_Root.localRotation = Quaternion.identity; + } + + targetActor.GameRep.transform.rotation = + __instance.owningActor.GameRep.transform.rotation; + targetActor.CurrentRotation = __instance.owningActor.CurrentRotation; + } + + targetActor.MountedEvasion(__instance.owningActor); + ModInit.modLog?.Debug?.Write( + $"[OrderSequence_OnUpdate] PositionLockMount- Setting airlifted unit {targetActor.DisplayName} position to same as carrier unit {__instance.owningActor.DisplayName}"); + + ModState.CachedUnitCoordinates[__instance.owningActor.GUID] = __instance.owningActor.CurrentPosition; + } } - if (def.specialRules == AbilityDef.SpecialRules.SpawnTurret && - ModInit.modSettings.spawnTurretEndsActivation) + if (__instance.owningActor.HasMountedUnits()) { - if (theActor is Mech mech) + var targetActorGUIDs = + ModState.PositionLockMount.Where(x => x.Value == __instance.owningActor.GUID); + foreach (var targetActorGUID in targetActorGUIDs) { - mech.GenerateAndPublishHeatSequence(-1, true, false, theActor.GUID); + var targetActor = combat.FindActorByGUID(targetActorGUID.Key); + if (targetActor == null) continue; + var pos = Vector3.zero; + if (__instance.owningActor is CustomMech mech) + { + pos = __instance.owningActor.CurrentPosition + + Vector3.up * mech.custGameRep.HeightController.CurrentHeight; + targetActor.TeleportActorVisual(pos); + } + else + { + targetActor.TeleportActorVisual(__instance.owningActor.CurrentPosition); + } + + targetActor.MountedEvasion(__instance.owningActor); + ModInit.modLog?.Debug?.Write( + $"[OrderSequence_OnUpdate] PositionLockMount- Setting riding unit {targetActor.DisplayName} position to same as carrier unit {__instance.owningActor.DisplayName}"); + + ModState.CachedUnitCoordinates[__instance.owningActor.GUID] = __instance.owningActor.CurrentPosition; + } + } + + // removed return/else so swarming units are locked to carrier even if carrier has mounted units. derp. + if (__instance.owningActor.HasSwarmingUnits()) + { + var targetActorGUIDs = + ModState.PositionLockSwarm.Where(x => x.Value == __instance.owningActor.GUID); + foreach (var targetActorGUID in targetActorGUIDs) + { + var targetActor = combat.FindActorByGUID(targetActorGUID.Key); + if (targetActor == null) continue; + targetActor.TeleportActorVisual(__instance.owningActor.CurrentPosition); + targetActor.MountedEvasion(__instance.owningActor); + ModInit.modLog?.Debug?.Write( + $"[OrderSequence_OnUpdate] PositionLockMount- Setting riding unit {targetActor.DisplayName} position to same as carrier unit {__instance.owningActor.DisplayName}"); + + ModState.CachedUnitCoordinates[__instance.owningActor.GUID] = __instance.owningActor.CurrentPosition; } - var sequence = theActor.DoneWithActor(); - theActor.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(sequence)); - //theActor.OnActivationEnd(theActor.GUID, __instance.GetInstanceID()); } } } @@ -2224,58 +2266,18 @@ public static class SelectionStateCommand_OnAddToStack public static void Postfix(SelectionStateCommand __instance) { if (UnityGameInstance.BattleTechGame.Combat.ActiveContract.ContractTypeValue.IsSkirmish) return; - if (DeployManualHelper.IsInManualSpawnSequence) return; - if (__instance is SelectionStateCommandSpawnTarget || __instance is SelectionStateCommandTargetTwoPoints) - { - var HUD = __instance.HUD;//IRBTModUtils.SharedState.CombatHUD;//Traverse.Create(__instance).Property("HUD").GetValue(); - var theActor = HUD.SelectedActor; - if (theActor == null) return; - CombatTargetingReticle.Instance.HideReticle(); - var maxRange = Mathf.RoundToInt(__instance.FromButton.Ability.Def.IntParam2); - CombatTargetingReticle.Instance.ShowRangeIndicators(theActor.CurrentPosition, 0f, maxRange, false, true); - CombatTargetingReticle.Instance.UpdateRangeIndicator(theActor.CurrentPosition, false, true); - CombatTargetingReticle.Instance.ShowReticle(); - } - } - } - - [HarmonyPatch(typeof(SelectionStateCommandSpawnTarget), "ProcessMousePos")] - public static class SelectionStateCommandSpawnTarget_ProcessMousePos - { - public static void Prefix(ref bool __runOriginal, SelectionStateCommandSpawnTarget __instance, Vector3 worldPos) - { - if (!__runOriginal) return; - if (UnityGameInstance.BattleTechGame.Combat.ActiveContract.ContractTypeValue.IsSkirmish) - { - __runOriginal = true; - return; - } - CombatSpawningReticle.Instance.ShowReticle(); - var HUD = __instance.HUD;//IRBTModUtils.SharedState.CombatHUD;//Traverse.Create(__instance).Property("HUD").GetValue(); - var theActor = HUD.SelectedActor; - if (theActor == null) - { - __runOriginal = true; - return; - } - var distance = Mathf.RoundToInt(Vector3.Distance(theActor.CurrentPosition, worldPos)); - var maxRange = Mathf.RoundToInt(__instance.FromButton.Ability.Def.IntParam2); - //CombatTargetingReticle.Instance.ShowRangeIndicators(theActor.CurrentPosition, 0f, maxRange, true, true); - //CombatTargetingReticle.Instance.ShowReticle(); - if (__instance.FromButton.Ability.Def.specialRules == AbilityDef.SpecialRules.SpawnTurret && - distance > maxRange && __instance.numPositionsLocked == 0) + if (DeployManualHelper.IsInManualSpawnSequence) return; + if (__instance is SelectionStateCommandSpawnTarget || __instance is SelectionStateCommandTargetTwoPoints) { - ModState.OutOfRange = true; - CombatSpawningReticle.Instance.HideReticle(); - //CombatTargetingReticle.Instance.HideReticle(); - // ModInit.modLog?.Info?.Write($"Cannot spawn turret with coordinates farther than __instance.Ability.Def.IntParam2: {__instance.FromButton.Ability.Def.IntParam2}"); - __runOriginal = false; - return; + var HUD = __instance.HUD;//IRBTModUtils.SharedState.CombatHUD;//Traverse.Create(__instance).Property("HUD").GetValue(); + var theActor = HUD.SelectedActor; + if (theActor == null) return; + CombatTargetingReticle.Instance.HideReticle(); + var maxRange = Mathf.RoundToInt(__instance.FromButton.Ability.Def.IntParam2); + CombatTargetingReticle.Instance.ShowRangeIndicators(theActor.CurrentPosition, 0f, maxRange, false, true); + CombatTargetingReticle.Instance.UpdateRangeIndicator(theActor.CurrentPosition, false, true); + CombatTargetingReticle.Instance.ShowReticle(); } - - ModState.OutOfRange = false; - __runOriginal = true; - return; } } @@ -2288,10 +2290,10 @@ public static void Postfix(SelectionStateCommandSpawnTarget __instance) } } - [HarmonyPatch(typeof(SelectionStateCommandTargetTwoPoints), "ProcessMousePos")] - public static class SelectionStateCommandTargetTwoPoints_ProcessMousePos + [HarmonyPatch(typeof(SelectionStateCommandSpawnTarget), "ProcessMousePos")] + public static class SelectionStateCommandSpawnTarget_ProcessMousePos { - public static void Prefix(ref bool __runOriginal, SelectionStateCommandTargetTwoPoints __instance, Vector3 worldPos) + public static void Prefix(ref bool __runOriginal, SelectionStateCommandSpawnTarget __instance, Vector3 worldPos) { if (!__runOriginal) return; if (UnityGameInstance.BattleTechGame.Combat.ActiveContract.ContractTypeValue.IsSkirmish) @@ -2299,11 +2301,8 @@ public static void Prefix(ref bool __runOriginal, SelectionStateCommandTargetTwo __runOriginal = true; return; } - + CombatSpawningReticle.Instance.ShowReticle(); var HUD = __instance.HUD;//IRBTModUtils.SharedState.CombatHUD;//Traverse.Create(__instance).Property("HUD").GetValue(); - var positionA = __instance.positionA;//Traverse.Create(__instance).Property("positionA").GetValue(); - var positionB = __instance.positionB;//Traverse.Create(__instance).Property("positionB").GetValue(); - var theActor = HUD.SelectedActor; if (theActor == null) { @@ -2311,21 +2310,16 @@ public static void Prefix(ref bool __runOriginal, SelectionStateCommandTargetTwo return; } var distance = Mathf.RoundToInt(Vector3.Distance(theActor.CurrentPosition, worldPos)); - var distanceToA = Mathf.RoundToInt(Vector3.Distance(theActor.CurrentPosition, positionA)); - var distanceToB = Mathf.RoundToInt(Vector3.Distance(theActor.CurrentPosition, positionB)); - var maxRange = Mathf.RoundToInt(__instance.FromButton.Ability.Def.IntParam2); - var radius = __instance.FromButton.Ability.Def.FloatParam1; - CombatTargetingReticle.Instance.UpdateReticle(positionA, positionB, radius, false); //CombatTargetingReticle.Instance.ShowRangeIndicators(theActor.CurrentPosition, 0f, maxRange, true, true); - CombatTargetingReticle.Instance.ShowReticle(); - if (__instance.FromButton.Ability.Def.specialRules == AbilityDef.SpecialRules.Strafe && - (distance > maxRange && __instance.numPositionsLocked == 0) || - (distanceToA > maxRange && __instance.numPositionsLocked == 1)) + //CombatTargetingReticle.Instance.ShowReticle(); + if (__instance.FromButton.Ability.Def.specialRules == AbilityDef.SpecialRules.SpawnTurret && + distance > maxRange && __instance.numPositionsLocked == 0) { ModState.OutOfRange = true; - CombatTargetingReticle.Instance.HideReticle(); - // ModInit.modLog?.Info?.Write($"Cannot strafe with coordinates farther than __instance.Ability.Def.IntParam2: {__instance.FromButton.Ability.Def.IntParam2}"); + CombatSpawningReticle.Instance.HideReticle(); + //CombatTargetingReticle.Instance.HideReticle(); + // ModInit.modLog?.Info?.Write($"Cannot spawn turret with coordinates farther than __instance.Ability.Def.IntParam2: {__instance.FromButton.Ability.Def.IntParam2}"); __runOriginal = false; return; } @@ -2715,6 +2709,54 @@ public static void Postfix(SelectionStateCommandTargetTwoPoints __instance, Vect } } + [HarmonyPatch(typeof(SelectionStateCommandTargetTwoPoints), "ProcessMousePos")] + public static class SelectionStateCommandTargetTwoPoints_ProcessMousePos + { + public static void Prefix(ref bool __runOriginal, SelectionStateCommandTargetTwoPoints __instance, Vector3 worldPos) + { + if (!__runOriginal) return; + if (UnityGameInstance.BattleTechGame.Combat.ActiveContract.ContractTypeValue.IsSkirmish) + { + __runOriginal = true; + return; + } + + var HUD = __instance.HUD;//IRBTModUtils.SharedState.CombatHUD;//Traverse.Create(__instance).Property("HUD").GetValue(); + var positionA = __instance.positionA;//Traverse.Create(__instance).Property("positionA").GetValue(); + var positionB = __instance.positionB;//Traverse.Create(__instance).Property("positionB").GetValue(); + + var theActor = HUD.SelectedActor; + if (theActor == null) + { + __runOriginal = true; + return; + } + var distance = Mathf.RoundToInt(Vector3.Distance(theActor.CurrentPosition, worldPos)); + var distanceToA = Mathf.RoundToInt(Vector3.Distance(theActor.CurrentPosition, positionA)); + var distanceToB = Mathf.RoundToInt(Vector3.Distance(theActor.CurrentPosition, positionB)); + + var maxRange = Mathf.RoundToInt(__instance.FromButton.Ability.Def.IntParam2); + var radius = __instance.FromButton.Ability.Def.FloatParam1; + CombatTargetingReticle.Instance.UpdateReticle(positionA, positionB, radius, false); + //CombatTargetingReticle.Instance.ShowRangeIndicators(theActor.CurrentPosition, 0f, maxRange, true, true); + CombatTargetingReticle.Instance.ShowReticle(); + if (__instance.FromButton.Ability.Def.specialRules == AbilityDef.SpecialRules.Strafe && + (distance > maxRange && __instance.numPositionsLocked == 0) || + (distanceToA > maxRange && __instance.numPositionsLocked == 1)) + { + ModState.OutOfRange = true; + CombatTargetingReticle.Instance.HideReticle(); + // ModInit.modLog?.Info?.Write($"Cannot strafe with coordinates farther than __instance.Ability.Def.IntParam2: {__instance.FromButton.Ability.Def.IntParam2}"); + __runOriginal = false; + return; + } + + ModState.OutOfRange = false; + __runOriginal = true; + return; + } + } + [HarmonyPatch(typeof(SelectionStateCommandTargetTwoPoints), "ProcessPressedButton")] public static class SelectionStateCommandTargetTwoPoints_ProcessPressedButton { @@ -2748,447 +2790,473 @@ public static void Prefix(SelectionStateCommandTargetTwoPoints __instance, strin } } - [HarmonyPatch(typeof(CombatHUDMechwarriorTray), "ResetAbilityButton", - new Type[] { typeof(AbstractActor), typeof(CombatHUDActionButton), typeof(Ability), typeof(bool) })] - public static class CombatHUDMechwarriorTray_ResetAbilityButton_Patch + [HarmonyPatch(typeof(SimGameState), "RequestDataManagerResources")] + public static class SimGameState_RequestDataManagerResources_Patch { - public static void Postfix(CombatHUDMechwarriorTray __instance, AbstractActor actor, CombatHUDActionButton button, Ability ability, bool forceInactive) + public static void Postfix(SimGameState __instance) { - if (UnityGameInstance.BattleTechGame.Combat.ActiveContract.ContractTypeValue.IsSkirmish) return; - if (actor == null || ability == null) return; - // if (button == __instance.FireButton) - // { - // ModInit.modLog?.Trace?.Write( - // $"Leaving Fire Button Enabled"); - // return; - // } - if (actor.HasActivatedThisRound || !actor.IsAvailableThisPhase || - (actor.Combat.StackManager.IsAnyOrderActive && actor.Combat.TurnDirector.IsInterleaved)) - { - return; - } - - if (ability.Def.Id != ModInit.modSettings.BattleArmorMountAndSwarmID) - { - if (actor.IsMountedUnit()) - { - if (!actor.IsMountedInternal()) - { - button.DisableButton(); - } - else - { - var carrier = actor.Combat.FindActorByGUID(ModState.PositionLockMount[actor.GUID]); - if (!carrier.HasFiringPorts()) - { - button.DisableButton(); - } - } - } - else if (actor.IsSwarmingUnit()) - { - button.DisableButton(); - } - } - - if (ability.Def.Id == ModInit.modSettings.BattleArmorDeSwarmRoll || - ability.Def.Id == ModInit.modSettings.BattleArmorDeSwarmSwat) - { - if (actor is Vehicle vehicle || actor.IsCustomUnitVehicle()) - { - button.DisableButton(); - } - - if (!actor.HasSwarmingUnits()) - { - button.DisableButton(); - } - } - - if (ability.Def.Id == ModInit.modSettings.AirliftAbilityID && ModInit.modSettings.CanDropOffAfterMoving)// && actor.MovingToPosition == null) // maybe need to check IsAnyOrderActive (but that might screw me) - { - if (actor.HasAirliftedUnits()) - { - //button.DisableButton(); - //if (!button.gameObject.activeSelf) - //{ - // button.gameObject.SetActive(true); - //} - - //button.InitButton(CombatHUDMechwarriorTray.GetSelectionTypeFromTargeting(ability.Def.Targeting, false), ability, ability.Def.AbilityIcon, ability.Def.Description.Id, ability.Def.Description.Name, actor); - button.ResetButtonIfNotActive(actor); - } - } + LoadRequest loadRequest = __instance.DataManager.CreateLoadRequest(); + loadRequest.AddAllOfTypeBlindLoadRequest(BattleTechResourceType.TurretDef, + new bool?(true)); //but TurretDefs + loadRequest.ProcessRequests(10U); } } - [HarmonyPatch(typeof(CombatHUDWeaponPanel), "ResetAbilityButton", - new Type[] { typeof(AbstractActor), typeof(CombatHUDActionButton), typeof(Ability), typeof(bool) })] - public static class CombatHUDWeaponPanel_ResetAbilityButton_Patch + [HarmonyPatch(typeof(Team), "ActivateAbility")] + public static class Team_ActivateAbility { - public static void Postfix(CombatHUDWeaponPanel __instance, AbstractActor actor, CombatHUDActionButton button, Ability ability, bool forceInactive) + public static void Prefix(ref bool __runOriginal, Team __instance, AbilityMessage msg) { - if (UnityGameInstance.BattleTechGame.Combat.ActiveContract.ContractTypeValue.IsSkirmish) return; - if (actor == null || ability == null) return; - // if (button == __instance.FireButton) - // { - // ModInit.modLog?.Trace?.Write( - // $"Leaving Fire Button Enabled"); - // return; - // } - - if (actor.HasActivatedThisRound || !actor.IsAvailableThisPhase || - (actor.Combat.StackManager.IsAnyOrderActive && actor.Combat.TurnDirector.IsInterleaved)) + if (!__runOriginal) return; + if (__instance.Combat.ActiveContract.ContractTypeValue.IsSkirmish) { + __runOriginal = true; + return; + } + Ability ability = ModState.CommandAbilities.Find((Ability x) => x.Def.Id == msg.abilityID); + if (ability == null) + { + ModInit.modLog?.Info?.Write( + $"Tried to use a CommandAbility the team doesnt have?"); + __runOriginal = false; return; } - if (ability.Def.Id != ModInit.modSettings.BattleArmorMountAndSwarmID) + switch (ability.Def.Targeting) { - if (actor.IsMountedUnit()) - { - if (!actor.IsMountedInternal()) - { - button.DisableButton(); - } - else + case AbilityDef.TargetingType.CommandInstant: + ability.Activate(__instance, null); + goto publishAbilityConfirmed; + case AbilityDef.TargetingType.CommandTargetSingleEnemy: { - var carrier = actor.Combat.FindActorByGUID(ModState.PositionLockMount[actor.GUID]); - if (!carrier.HasFiringPorts()) + ICombatant combatant = __instance.Combat.FindCombatantByGUID(msg.affectedObjectGuid, false); + if (combatant == null) { - button.DisableButton(); + ModInit.modLog?.Info?.Write( + $"Team.ActivateAbility couldn't find target with guid {msg.affectedObjectGuid}"); + __runOriginal = false; + return; } - } - } - else if (actor.IsSwarmingUnit()) - { - button.DisableButton(); - } - } - - if (ability.Def.Id == ModInit.modSettings.BattleArmorDeSwarmRoll || - ability.Def.Id == ModInit.modSettings.BattleArmorDeSwarmSwat) - { - if (actor is Vehicle vehicle || actor.IsCustomUnitVehicle()) - { - button.DisableButton(); - } - if (!actor.HasSwarmingUnits()) - { - button.DisableButton(); - } + ability.Activate(__instance, combatant); + goto publishAbilityConfirmed; + } + case AbilityDef.TargetingType.CommandTargetSinglePoint: + ability.Activate(__instance, msg.positionA); + goto publishAbilityConfirmed; + case AbilityDef.TargetingType.CommandTargetTwoPoints: + case AbilityDef.TargetingType.CommandSpawnPosition: + ability.Activate(__instance, msg.positionA, msg.positionB); + goto publishAbilityConfirmed; + case AbilityDef.TargetingType.NotSet: + break; + case AbilityDef.TargetingType.ActorSelf: + break; + case AbilityDef.TargetingType.ActorTarget: + break; + case AbilityDef.TargetingType.SensorLock: + break; + case AbilityDef.TargetingType.CommandTargetSingleAlly: + break; + case AbilityDef.TargetingType.ShadowMove: + break; + case AbilityDef.TargetingType.MultiFire: + break; + case AbilityDef.TargetingType.ConfirmCoolantVent: + break; + case AbilityDef.TargetingType.ActiveProbe: + break; + default: + throw new ArgumentOutOfRangeException(); } - if (ability.Def.Id == ModInit.modSettings.AirliftAbilityID && ModInit.modSettings.CanDropOffAfterMoving)// && actor.MovingToPosition == null) // maybe need to check IsAnyOrderActive (but that might screw me) - { - if (actor.HasAirliftedUnits()) - { - //button.DisableButton(); - //if (!button.gameObject.activeSelf) - //{ - // button.gameObject.SetActive(true); - //} - - //button.InitButton(CombatHUDMechwarriorTray.GetSelectionTypeFromTargeting(ability.Def.Targeting, false), ability, ability.Def.AbilityIcon, ability.Def.Description.Id, ability.Def.Description.Name, actor); - button.ResetButtonIfNotActive(actor); - } - } + ModInit.modLog?.Info?.Write( + $"Team.ActivateAbility needs to add handling for targetingtype {ability.Def.Targeting}"); + __runOriginal = false; + return; + publishAbilityConfirmed: + __instance.Combat.MessageCenter.PublishMessage(new AbilityConfirmedMessage(msg.actingObjectGuid, + msg.affectedObjectGuid, msg.abilityID, msg.positionA, msg.positionB)); + __runOriginal = false; + return; } } - [HarmonyPatch(typeof(CombatHUDEquipmentSlotEx), "ResetAbilityButton", - new Type[] { typeof(AbstractActor), typeof(CombatHUDActionButton), typeof(Ability), typeof(bool) })] - public static class CombatHUDEquipmentSlotEx_ResetAbilityButton + + [HarmonyPatch(typeof(Team), "AddUnit", new Type[] { typeof(AbstractActor) })] + public static class Team_AddUnit_Patch { - public static void Postfix(CombatHUDEquipmentSlotEx __instance, AbstractActor actor, - CombatHUDActionButton button, Ability ability, bool forceInactive) + public static void Postfix(Team __instance, AbstractActor unit) { - if (UnityGameInstance.BattleTechGame.Combat.ActiveContract.ContractTypeValue.IsSkirmish) return; - if (actor == null || ability == null) return; - // if (button == __instance.FireButton) - // { - // ModInit.modLog?.Trace?.Write( - // $"Leaving Fire Button Enabled"); - // return; - // } - - if (actor.HasActivatedThisRound || !actor.IsAvailableThisPhase || - (actor.Combat.StackManager.IsAnyOrderActive && actor.Combat.TurnDirector.IsInterleaved)) + if (__instance.Combat.TurnDirector.CurrentRound > 1) { - return; + __instance.Combat.UpdateResupplyTeams(); + __instance.Combat.UpdateResupplyAbilitiesGetAllLivingActors(); } - if (ability.Def.Id != ModInit.modSettings.BattleArmorMountAndSwarmID) + if (__instance.IsLocalPlayer) { - if (actor.IsMountedUnit()) + if (unit is Mech && !(unit is TrooperSquad) && !unit.IsCustomUnitVehicle()) { - if (!actor.IsMountedInternal()) + if (!string.IsNullOrEmpty(ModInit.modSettings.BattleArmorDeSwarmSwat)) { - button.DisableButton(); + if (unit.GetPilot().Abilities + .All(x => x.Def.Id != ModInit.modSettings.BattleArmorDeSwarmSwat) && + unit.ComponentAbilities.All(y => + y.Def.Id != ModInit.modSettings.BattleArmorDeSwarmSwat)) + { + unit.Combat.DataManager.AbilityDefs.TryGet(ModInit.modSettings.BattleArmorDeSwarmSwat, + out var def); + var ability = new Ability(def); + ModInit.modLog?.Trace?.Write( + $"[Team.AddUnit] Adding {ability.Def?.Description?.Id} to {unit.Description?.Name}."); + ability.Init(unit.Combat); + unit.GetPilot().Abilities.Add(ability); + unit.GetPilot().ActiveAbilities.Add(ability); + } } - else + + if (!string.IsNullOrEmpty(ModInit.modSettings.BattleArmorDeSwarmRoll)) { - var carrier = actor.Combat.FindActorByGUID(ModState.PositionLockMount[actor.GUID]); - if (!carrier.HasFiringPorts()) + if (unit.GetPilot().Abilities + .All(x => x.Def.Id != ModInit.modSettings.BattleArmorDeSwarmRoll) && + unit.ComponentAbilities.All(y => + y.Def.Id != ModInit.modSettings.BattleArmorDeSwarmRoll)) { - button.DisableButton(); + unit.Combat.DataManager.AbilityDefs.TryGet(ModInit.modSettings.BattleArmorDeSwarmRoll, + out var def); + var ability = new Ability(def); + ModInit.modLog?.Trace?.Write( + $"[Team.AddUnit] Adding {ability.Def?.Description?.Id} to {unit.Description?.Name}."); + ability.Init(unit.Combat); + unit.GetPilot().Abilities.Add(ability); + unit.GetPilot().ActiveAbilities.Add(ability); } } - } - else if (actor.IsSwarmingUnit()) - { - button.DisableButton(); - } - } - if (ability.Def.Id == ModInit.modSettings.BattleArmorDeSwarmRoll || - ability.Def.Id == ModInit.modSettings.BattleArmorDeSwarmSwat) - { - if (actor is Vehicle vehicle || actor.IsCustomUnitVehicle()) - { - button.DisableButton(); } - if (!actor.HasSwarmingUnits()) + if (!string.IsNullOrEmpty(ModInit.modSettings.DeswarmMovementConfig.AbilityDefID)) { - button.DisableButton(); + if (unit.GetPilot().Abilities + .All(x => x.Def.Id != ModInit.modSettings.DeswarmMovementConfig.AbilityDefID) && + unit.ComponentAbilities.All(y => + y.Def.Id != ModInit.modSettings.DeswarmMovementConfig.AbilityDefID)) + { + unit.Combat.DataManager.AbilityDefs.TryGet(ModInit.modSettings.DeswarmMovementConfig.AbilityDefID, + out var def); + var ability = new Ability(def); + ModInit.modLog?.Trace?.Write( + $"[Team.AddUnit] Adding {ability.Def?.Description?.Id} to {unit.Description?.Name}."); + ability.Init(unit.Combat); + unit.GetPilot().Abilities.Add(ability); + unit.GetPilot().ActiveAbilities.Add(ability); + } } - } - if (ability.Def.Id == ModInit.modSettings.AirliftAbilityID && ModInit.modSettings.CanDropOffAfterMoving)// && actor.MovingToPosition == null) // maybe need to check IsAnyOrderActive (but that might screw me) - { - if (actor.HasAirliftedUnits()) + //add a parent component to strafe/spawn pilot abilities. even though it wont be used. + + //need to disentangle from abilifier though. + if (false) { - //button.DisableButton(); - //if (!button.gameObject.activeSelf) - //{ - // button.gameObject.SetActive(true); - //} + var list = unit.GetPilot().Abilities; + for (var index = list.Count - 1; index >= 0; index--) + { + var pilotAbility = list[index]; + if (pilotAbility.Def.specialRules == AbilityDef.SpecialRules.Strafe || + pilotAbility.Def.specialRules == AbilityDef.SpecialRules.SpawnTurret) + { + var ability = new Ability(pilotAbility.Def); + ModInit.modLog?.Trace?.Write( + $"[Team.AddUnit] moving {pilotAbility.Def?.Description?.Id} to {unit.Description?.Name} component abilities."); - //button.InitButton(CombatHUDMechwarriorTray.GetSelectionTypeFromTargeting(ability.Def.Targeting, false), ability, ability.Def.AbilityIcon, ability.Def.Description.Id, ability.Def.Description.Name, actor); - button.ResetButtonIfNotActive(actor); - } - } + var abilityComponent = unit.allComponents.FirstOrDefault(z => + ModInit.modSettings.crewOrCockpitCustomID.Any((string x) => + z.componentDef.GetComponents() + .Any((Category c) => c.CategoryID == x))); + if (abilityComponent == null) + { + ModInit.modLog?.Info?.Write($"component was null; no CriticalComponents?"); + } - var specialRules = ability.Def.specialRules; + if (abilityComponent?.parent == null) + { + ModInit.modLog?.Info?.Write($"component parent was null; no parent actor???"); + } + ability.Init(unit.Combat, abilityComponent); + unit.GetPilot().Abilities.Remove(pilotAbility); + unit.GetPilot().ActiveAbilities.Remove(pilotAbility); + unit.GetPilot().Abilities.Add(ability); + unit.GetPilot().ActiveAbilities.Add(ability); + } + } + } + return; + } - if (ModInit.modSettings.BeaconExclusionConfig.TryGetValue(actor.Combat.ActiveContract.ContractTypeValue.Name, out var configType)) + if (unit is Mech mech) { - if (actor.team.IsLocalPlayer) - { - if (specialRules == AbilityDef.SpecialRules.Strafe && configType.ExcludedPlayerStrafe) - button.DisableButton(); - else if (specialRules == AbilityDef.SpecialRules.SpawnTurret && configType.ExcludedPlayerSpawn) - button.DisableButton(); - } + if (mech.EncounterTags.Contains("SpawnedFromAbility")) return; } - else if (ModInit.modSettings.BeaconExclusionConfig.TryGetValue(actor.Combat.ActiveContract.Override.ID, out var configID)) + AI_Utils.GenerateAIStrategicAbilities(unit); + } + } + + [HarmonyPatch(typeof(Team), "OnRoundBegin")] + public static class TurnActor_OnRoundBegin + { + public static void Postfix(Team __instance) + { + if (__instance.Combat.ActiveContract.ContractTypeValue.IsSkirmish) return; + foreach (var ability in ModState.CommandAbilities) { - if (actor.team.IsLocalPlayer) - { - if (specialRules == AbilityDef.SpecialRules.Strafe && configID.ExcludedPlayerStrafe) - button.DisableButton(); - else if (specialRules == AbilityDef.SpecialRules.SpawnTurret && configID.ExcludedPlayerSpawn) - button.DisableButton(); - } + ability.OnNewRound(); } - //if ((specialRules == AbilityDef.SpecialRules.Strafe || specialRules == AbilityDef.SpecialRules.SpawnTurret) && - // (ModInit.modSettings.BeaconExcludedContractIDs.Contains(ability.Combat.ActiveContract.Override.ID) || ModInit.modSettings.BeaconExcludedContractTypes.Contains(ability.Combat.ActiveContract - // .ContractTypeValue.Name))) - // { - // button.DisableButton(); - // } - - if (actor.GetAbilityUsedFiring()) + foreach (var despawn in ModState.DeferredDespawnersFromStrafe) { - if (ability.Def.ActivationTime == AbilityDef.ActivationTiming.ConsumedByFiring) button.DisableButton(); + var msg = new DespawnActorMessage(EncounterLayerData.MapLogicGuid, despawn.Key, (DeathMethod)DespawnFloatieMessage.Escaped); + //Utils._despawnActorMethod.Invoke(despawn.Value, new object[] { msg }); + despawn.Value.DespawnActor(msg); } + + //var team = __instance as Team; + + if (__instance?.units != null) + foreach (var unit in __instance?.units) + { + var rep = unit.GameRep as PilotableActorRepresentation; + rep?.ClearForcedPlayerVisibilityLevel(__instance.Combat.GetAllLivingCombatants()); + } + // team?.ResetUnitVisibilityLevels(); + __instance?.RebuildVisibilityCacheAllUnits(__instance.Combat.GetAllLivingCombatants()); + } } - [HarmonyPatch(typeof(CameraControl), "ShowActorCam")] - public static class CameraControl_ShowActorCam + [HarmonyPatch(typeof(TurnDirector), "IncrementActiveTurnActor")] + public static class TurnDirector_IncrementActiveTurnActor { - static bool Prepare() => false; //disabled. im not fucking with the follow cam anymore, and apparently it causes problems with harmonyx for some goddamn reason? - public static void Prefix(ref bool __runOriginal, CameraControl __instance, AbstractActor actor, Quaternion rotation, - float duration, ref AttachToActorCameraSequence __result) + public static void Prefix(TurnDirector __instance) { - if (!__runOriginal) return; - var combat = UnityGameInstance.BattleTechGame.Combat; - if (combat.ActiveContract.ContractTypeValue.IsSkirmish) + if (__instance.Combat.ActiveContract.ContractTypeValue.IsSkirmish) return; + if (ModState.DeferredInvokeSpawns.Count > 0 && __instance.ActiveTurnActor is Team activeTeam && + activeTeam.IsLocalPlayer) { - __runOriginal = true; - return; + for (var index = 0; index < ModState.DeferredInvokeSpawns.Count; index++) + { + var spawn = ModState.DeferredInvokeSpawns[index].Value; + var resource = ModState.DeferredInvokeSpawns[index].Key.Split('@'); + ModState.DeferredActorResource = resource[1]; + ModInit.modLog?.Info?.Write( + $"Found deferred spawner at index {index} of {ModState.DeferredInvokeSpawns.Count - 1}, invoking and trying to spawn {ModState.DeferredActorResource}."); + ModState.DeferredSpawnerFromDelegate = true; + spawn(); + ModState.ResetDelegateInfos(); + } + ModState.ResetDeferredSpawners(); } - Vector3 offset = new Vector3(0f, 50f, 50f); - __result = new AttachToActorCameraSequence(combat, actor.GameRep.transform, offset, rotation, duration, - true, false); - __runOriginal = false; - return; } } - [HarmonyPatch(typeof(CombatSpawningReticle), "ShowReticle")] - public static class CombatSpawningReticle_ShowReticle + [HarmonyPatch(typeof(TurnDirector), "OnInitializeContractComplete")] + public static class TurnDirector_OnInitializeContractComplete { - public static void Postfix(CombatSpawningReticle __instance) + public static void Postfix(TurnDirector __instance, MessageCenterMessage message) { + if (__instance.Combat.ActiveContract.ContractTypeValue.IsSkirmish) return; + var dm = __instance.Combat.DataManager; + LoadRequest loadRequest = dm.CreateLoadRequest(); + + loadRequest.AddBlindLoadRequest(BattleTechResourceType.PilotDef, "pilot_sim_starter_dekker"); + ModInit.modLog?.Info?.Write($"Added loadrequest for PilotDef: pilot_sim_starter_dekker (hardcoded)"); if (!string.IsNullOrEmpty(ModInit.modSettings.customSpawnReticleAsset)) { - var childComponents = __instance.gameObject.GetComponentsInChildren(true); + loadRequest.AddBlindLoadRequest(BattleTechResourceType.Texture2D, ModInit.modSettings.customSpawnReticleAsset); + ModInit.modLog?.Info?.Write($"Added loadrequest for Texture2D: {ModInit.modSettings.customSpawnReticleAsset}"); + } + if (!string.IsNullOrEmpty(ModInit.modSettings.MountIndicatorAsset)) + { + loadRequest.AddBlindLoadRequest(BattleTechResourceType.Texture2D, ModInit.modSettings.MountIndicatorAsset); + ModInit.modLog?.Info?.Write($"Added loadrequest for Texture2D: {ModInit.modSettings.MountIndicatorAsset}"); + } - for (int i = 0; i < childComponents.Length; i++) + foreach (var abilityDef in dm.AbilityDefs.Where(x => x.Key.StartsWith("AbilityDefCMD_"))) + { + var ability = new Ability(abilityDef.Value); + if (string.IsNullOrEmpty(ability.Def?.ActorResource)) continue; + if (!string.IsNullOrEmpty(ability.Def.getAbilityDefExtension().CMDPilotOverride)) { - if (childComponents[i].name == "ReticleDecalCircle") - { - var decalFromCirle = childComponents[i].GetComponent(); - var dm = UnityGameInstance.BattleTechGame.DataManager; - var newTexture = dm.GetObjectOfType(ModInit.modSettings.customSpawnReticleAsset, - BattleTechResourceType.Texture2D); - decalFromCirle.DecalMaterial.mainTexture = newTexture; - } + var pilotID = ability.Def.getAbilityDefExtension().CMDPilotOverride; + ModInit.modLog?.Info?.Write($"Added loadrequest for PilotDef: {pilotID}"); + loadRequest.AddBlindLoadRequest(BattleTechResourceType.PilotDef, pilotID); } - //var circle1 = GameObject.Find("ReticleDecalCircle"); - } - var decals = __instance.gameObject.GetComponentsInChildren(); + if (ability.Def.ActorResource.StartsWith("mechdef_")) + { + ModInit.modLog?.Info?.Write($"Added loadrequest for MechDef: {ability.Def.ActorResource}"); + loadRequest.AddBlindLoadRequest(BattleTechResourceType.MechDef, ability.Def.ActorResource); + } - foreach (var decal in decals) - { - if (ModInit.modSettings.customSpawnReticleColor != null) + if (ability.Def.ActorResource.StartsWith("vehicledef_")) { - var customColor = new Color(ModInit.modSettings.customSpawnReticleColor.Rf, - ModInit.modSettings.customSpawnReticleColor.Gf, - ModInit.modSettings.customSpawnReticleColor.Bf); - decal.DecalMaterial.color = customColor; + ModInit.modLog?.Info?.Write($"Added loadrequest for VehicleDef: {ability.Def.ActorResource}"); + loadRequest.AddBlindLoadRequest(BattleTechResourceType.VehicleDef, ability.Def.ActorResource); } - else + + if (ability.Def.ActorResource.StartsWith("turretdef_")) { - decal.DecalMaterial.color = Color.green; + ModInit.modLog?.Info?.Write($"Added loadrequest for TurretDef: {ability.Def.ActorResource}"); + loadRequest.AddBlindLoadRequest(BattleTechResourceType.TurretDef, ability.Def.ActorResource); } } - } - } - - [HarmonyPatch(typeof(CombatAuraReticle), "RefreshActiveProbeRange")] - public static class CombatAuraReticle_RefreshActiveProbeRange - { - static bool Prepare() => false; //disabled - public static void Postfix(CombatAuraReticle __instance, bool showActiveProbe) - { - if (!showActiveProbe || __instance.AuraBubble() != null) return; - float num = 0f; - if (__instance.owner.ComponentAbilities.Count > 0) + foreach (var beacon in Utils.GetOwnedDeploymentBeacons()) { - for (int i = 0; i < __instance.owner.ComponentAbilities.Count; i++) + var pilotID = beacon.Def.ComponentTags.FirstOrDefault(x => + x.StartsWith("StratOpsPilot_")) + ?.Remove(0, 14); + if (!string.IsNullOrEmpty(pilotID)) { - if (__instance.owner.ComponentAbilities[i].Def.Targeting == AbilityDef.TargetingType.ActiveProbe) - { - num = __instance.owner.ComponentAbilities[i].Def.FloatParam1; - break; - } + ModInit.modLog?.Info?.Write($"Added loadrequest for PilotDef: {pilotID}"); + loadRequest.AddBlindLoadRequest(BattleTechResourceType.PilotDef, pilotID); } + + var id = beacon.Def.ComponentTags.FirstOrDefault(x => + x.StartsWith("mechdef_") || x.StartsWith("vehicledef_") || + x.StartsWith("turretdef_")); + if (string.IsNullOrEmpty(id)) continue; + + if (id.StartsWith("mechdef_")) + { + ModInit.modLog?.Info?.Write($"Added loadrequest for MechDef: {id}"); + loadRequest.AddBlindLoadRequest(BattleTechResourceType.MechDef, id); + } + else if (id.StartsWith("vehicledef_")) + { + ModInit.modLog?.Info?.Write($"Added loadrequest for VehicleDef: {id}"); + loadRequest.AddBlindLoadRequest(BattleTechResourceType.VehicleDef, id); + } + else if (id.StartsWith("turretdef_")) + { + ModInit.modLog?.Info?.Write($"Added loadrequest for TurretDef: {id}"); + loadRequest.AddBlindLoadRequest(BattleTechResourceType.TurretDef, id); + } + } - if (!Mathf.Approximately(num, __instance.currentAPRange)) - { - var apObject = __instance.activeProbeRangeScaledObject;//Traverse.Create(__instance).Property("activeProbeRangeScaledObject").GetValue(); - apObject.transform.localScale = new Vector3(num * 2f, 1f, num * 2f); - } - __instance.currentAPRange = num; + loadRequest.ProcessRequests(1000U); } } - [HarmonyPatch(typeof(CombatSelectionHandler), "ProcessInput")] - public static class CombatSelectionHandler_ProcessInput + [HarmonyPatch(typeof(TurnDirector), "StartFirstRound")] + public static class TurnDirector_StartFirstRound { - public static void Prefix(ref bool __runOriginal, CombatSelectionHandler __instance) + public static void Postfix(TurnDirector __instance) { - if (!__runOriginal) return; - if (__instance.SelectedActor != null && __instance.SelectedActor.team != null && __instance.SelectedActor.team.IsLocalPlayer) + if (ModState.DeferredInvokeBattleArmor.Count > 0) { - var combat = UnityGameInstance.BattleTechGame.Combat; - if (!LazySingletonBehavior.Instance.ToggleUINode || - combat.StackManager.TopSequence is ArtilleryObjectiveSequence) + for (var index = 0; index < ModState.DeferredInvokeBattleArmor.Count; index++) { - __runOriginal = false; - return; - } + var spawn = ModState.DeferredInvokeBattleArmor[index].Value; + ModInit.modLog?.Info?.Write( + $"[TurnDirector.StartFirstRound] Found deferred spawner at index {index} of {ModState.DeferredInvokeBattleArmor.Count - 1}, invoking and trying to spawn a battle armor of some kind."); + ModState.DeferredBattleArmorSpawnerFromDelegate = true; + spawn(); - if (__instance.CombatInputBlocked) - { - __runOriginal = false; - return; } + ModState.ResetDeferredBASpawners(); + } - if (!combat.TurnDirector.GameHasBegun) + if (__instance.Combat.ActiveContract.ContractTypeValue.IsSkirmish) return; + + var playerTeam = __instance.Combat.Teams.First(x => x.IsLocalPlayer); + var dm = playerTeam.Combat.DataManager; + + foreach (var abilityDefKVP in dm.AbilityDefs.Where(x => + x.Value.specialRules == AbilityDef.SpecialRules.SpawnTurret || + x.Value.specialRules == AbilityDef.SpecialRules.Strafe)) + { + + if (playerTeam.units.Any(x => x.GetPilot().Abilities.Any(y => y.Def == abilityDefKVP.Value)) || + playerTeam.units.Any(x => x.ComponentAbilities.Any(z => z.Def == abilityDefKVP.Value))) { - if (CameraControl.Instance != null && CameraControl.Instance.IsInTutorialMode) + //only do things for abilities that pilots have? move things here. also move AbstractActor initialization to ability start to minimize neutralTeam think time, etc. and then despawn? - done + var ability = new Ability(abilityDefKVP.Value); + ability.Init(playerTeam.Combat); + if (ModState.CommandAbilities.All(x => x != ability)) { - __runOriginal = true; - return; + ModState.CommandAbilities.Add(ability); } + + ModInit.modLog?.Info?.Write($"[TurnDirector.StartFirstRound] Added {ability?.Def?.Id} to CommandAbilities"); + } + } + __instance.Combat.UpdateResupplyTeams(); + __instance.Combat.UpdateResupplyAbilitiesGetAllLivingActors(); - if (Input.anyKeyDown) + // now try to auto-mount?????? + if (ModState.PairingInfos.Count == 0) return; + if (DeployManualHelper.IsInManualSpawnSequence) return; + foreach (var unit in playerTeam.units) + { + if (ModState.PairingInfos.TryGetValue(unit.GetPilot().pilotDef.Description.Id, out var info)) { - // if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) - - if (Input.GetKey(ModInit.modSettings.EquipmentButtonsHotkey)) + foreach (var squadPilot in info.PairedBattleArmor) { - var hud = __instance.HUD;//IRBTModUtils.SharedState.CombatHUD; - if (hud.SelectedActor == __instance.SelectedActor) + foreach (var unit2 in playerTeam.units) { - //var slots = Traverse.Create(CombatHUDEquipmentPanel.Instance).Property("operatinalSlots").GetValue>(); - var slots = CombatHUDEquipmentPanel.Instance.operatinalSlots; - var lastActive = new Tuple(-1,-1); - var buttonList = new List(); - for (var slotIndex = 0; slotIndex < slots.Count; slotIndex++) + if (unit2 is TrooperSquad squad && + unit2.GetPilot().pilotDef.Description.Id == squadPilot) { - if (slots[slotIndex].buttons.Count > 0) - { - for (var buttonIndex = 0; - buttonIndex < slots[slotIndex].buttons.Count; - buttonIndex++) - { - if (slots[slotIndex].buttons[buttonIndex].isActiveAndEnabled && slots[slotIndex].buttons[buttonIndex].gameObject.activeSelf) - buttonList.Add(slots[slotIndex].buttons[buttonIndex]); - } - } - } + unit.MountBattleArmorToChassis(squad, true, true); - if (buttonList.Count <= 0) - { - __runOriginal = false; - return; - } - for (var index = 0; index < buttonList.Count; index++) - { - if (buttonList[index].IsActive) + if (unit is CustomMech custMech && custMech.FlyingHeight() > 1.5f) { - buttonList[index].OnClick(); - if (buttonList.Count > index) - { - buttonList[index + 1].OnClick(); - __runOriginal = false; - return; - } + var pos = custMech.CurrentPosition + + Vector3.up * custMech.custGameRep.HeightController.CurrentHeight; + squad.TeleportActorVisual(pos); } } - buttonList[0].OnClick(); } - __runOriginal = false; - return; } } } - __runOriginal = true; - return; + } + } + [HarmonyPatch(typeof(Vehicle), "RecalcEvasivePips")] + public static class Vehicle_RecalcEvasivePips + { + static bool Prepare() => ModInit.modSettings.ReworkedCarrierEvasion; + public static void Postfix(Vehicle __instance) + { + if (__instance.IsMountedUnit()) + { + if (ModState.PositionLockMount.TryGetValue(__instance.GUID, out var carrierID)) + { + var carrier = __instance.Combat.FindActorByGUID(carrierID); + __instance.MountedEvasion(carrier); + } + } + else if (__instance.IsSwarmingUnit()) + { + if (ModState.PositionLockSwarm.TryGetValue(__instance.GUID, out var carrierID)) + { + var carrier = __instance.Combat.FindActorByGUID(carrierID); + __instance.MountedEvasion(carrier); + } + } + else if (__instance.IsAirlifted()) + { + if (ModState.AirliftTrackers.TryGetValue(__instance.GUID, out var carrierID)) + { + var carrier = __instance.Combat.FindActorByGUID(carrierID.CarrierGUID); + __instance.MountedEvasion(carrier); + } + } } } } diff --git a/StrategicOperations/StrategicOperations/StrategicOperations.csproj b/StrategicOperations/StrategicOperations/StrategicOperations.csproj index ec51c48..952e55f 100644 --- a/StrategicOperations/StrategicOperations/StrategicOperations.csproj +++ b/StrategicOperations/StrategicOperations/StrategicOperations.csproj @@ -33,8 +33,8 @@ Unsafe - 3.1.2.1 - 3.1.2.1 + 3.1.2.2 + 3.1.2.2