private void SetIsTargetObjective(ICombatant combatant) { Main.LogDebug($"[SetUnitsInRegionToBeTaggedObjectiveTargetsResult] Setting isObjectiveTarget '{IsObjectiveTarget}' for '{combatant.GameRep.name} - {combatant.DisplayName}'"); ObstructionGameLogic obstructionGameLogic = combatant.GameRep.GetComponent <ObstructionGameLogic>(); obstructionGameLogic.isObjectiveTarget = true; if (Type == "Building") { BattleTech.Building building = combatant as BattleTech.Building; AccessTools.Field(typeof(BattleTech.Building), "isObjectiveTarget").SetValue(building, true); building.BuildingRep.IsTargetable = true; } CombatHUDInWorldElementMgr inworldElementManager = GameObject.Find("uixPrfPanl_HUD(Clone)").GetComponent <CombatHUDInWorldElementMgr>(); AccessTools.Method(typeof(CombatHUDInWorldElementMgr), "AddTickMark").Invoke(inworldElementManager, new object[] { combatant }); AccessTools.Method(typeof(CombatHUDInWorldElementMgr), "AddInWorldActorElements").Invoke(inworldElementManager, new object[] { combatant }); if (Type == "Building") { CombatHUDNumFlagHex numFlagEx = inworldElementManager.GetNumFlagForCombatant(combatant); CombatHUDFloatieStackActor floatie = inworldElementManager.GetFloatieStackForCombatant(combatant); numFlagEx.anchorPosition = CombatHUDInWorldScalingActorInfo.AnchorPosition.Feet; floatie.anchorPosition = CombatHUDInWorldScalingActorInfo.AnchorPosition.Feet; } }
public static void ScaleHealth(this BattleTech.Building building) { Mod.Log.Info?.Write($"Scaling health for building: {building.DistinctId()}" + $" => currentStructure: {building.CurrentStructure} startingStructure: {building.StartingStructure} ratio: {building.HealthAsRatio}"); float adjustedStruct = (float)Math.Floor((building.CurrentStructure * ModState.ActiveContractBuildingScaling.Multi) + ModState.ActiveContractBuildingScaling.Mod); Mod.Log.Info?.Write($" -- adjustedStructure: {adjustedStruct} = ( currentStruct: {building.CurrentStructure} " + $"x scaleMulti: {ModState.ActiveContractBuildingScaling.Multi} ) + scaleMod: {ModState.ActiveContractBuildingScaling.Mod}"); // Update Structure stat and StartingStructure value building.StatCollection.ModifyStat("IRTweaks", -1, ModStats.HBS_Building_Structure, StatCollection.StatOperation.Set, adjustedStruct); Traverse startingStructT = Traverse.Create(building).Property("StartingStructure"); startingStructT.SetValue(adjustedStruct); // Update the destructable group if (building.DestructibleObjectGroup != null) { building.DestructibleObjectGroup.health = adjustedStruct; building.DestructibleObjectGroup.fullHealth = adjustedStruct; Mod.Log.Debug?.Write($" -- new stats for destructibleObjectGroup => health: {building.DestructibleObjectGroup.health} fullHealth: {building.DestructibleObjectGroup.fullHealth}"); } }
// This method works around issues with the HBS code requiring an AbstractActor as the source of an attack. public static void DamageBuilding(this BattleTech.Building target, Vector3 attackOrigin, WeaponHitInfo hitInfo, Weapon weapon, float damageAmount, float directStructureDamage, int hitIndex) { float totalDamage = damageAmount + directStructureDamage; ModState.Combat.MessageCenter.PublishMessage(new TakeDamageMessage(hitInfo.attackerId, target.GUID, totalDamage)); Mod.Log.Debug?.Write("Published damage message for building."); target.StatCollection.ModifyStat <float>(hitInfo.attackerId, hitInfo.stackItemUID, "Structure", StatCollection.StatOperation.Float_Subtract, totalDamage, -1, true); Mod.Log.Debug?.Write("Modified structure"); Vector3 vector = hitInfo.hitPositions[0] - attackOrigin; vector.Normalize(); Mod.Log.Debug?.Write($"Normalized attack vector is: {vector}"); if (target.DestructibleObjectGroup != null) { target.DestructibleObjectGroup.TakeDamage(hitInfo.hitPositions[hitIndex], vector, totalDamage + ModState.Combat.Constants.ResolutionConstants.BuildingDestructionForceMultiplier, totalDamage); } Mod.Log.Debug?.Write($"Post destructible objects"); if (target.UrbanDestructible != null) { target.UrbanDestructible.TakeDamage(hitInfo, hitIndex, weapon, vector, totalDamage); } Mod.Log.Debug?.Write($"Post urban destructible"); target.ResolveWeaponDamage(hitInfo); Mod.Log.Debug?.Write($"Resolving damage outcomes"); }
public static void DevestateBuildings() { if (!Mod.Config.Devastation.Enabled) { return; } Mod.Log.Debug?.Write("Processing buildings for pre-battle devestation."); List <BattleTech.Building> shuffledBuildings = new List <BattleTech.Building>(); shuffledBuildings.AddRange(ModState.CandidateBuildings); // Randomize the buildings by shuffling them shuffledBuildings.Shuffle(); int minNum = (int)(Mod.Config.Devastation.DefaultRange.MinDevastation * 100f); int maxNum = (int)(Mod.Config.Devastation.DefaultRange.MaxDevastation * 100f); int destroyPercentile = Mod.Random.Next(minNum, maxNum); float destroyPercent = (float)destroyPercentile / 100f; int destroyedBuildings = (int)Math.Floor(shuffledBuildings.Count * destroyPercent); Mod.Log.Debug?.Write($"Destruction percentile: {destroyPercent} applied to {shuffledBuildings.Count} buildings = {destroyedBuildings} destroyed buildings."); for (int i = 0; i < destroyedBuildings; i++) { BattleTech.Building building = shuffledBuildings.ElementAt(i); Mod.Log.Debug?.Write($"Destroying building: {CombatantUtils.Label(building)}"); building.FlagForDeath("CG_PREMAP_DESTROY", DeathMethod.DespawnedNoMessage, DamageType.NOT_SET, 1, -1, "0", true); building.HandleDeath("0"); ModState.CandidateBuildings.Remove(building); } }
static void Postfix(BattleTech.Building __instance, ref bool __result) { if (ModState.IsUrbanBiome && ModState.AmbushBuildingGUIDToTurrets.ContainsKey(__instance.GUID)) { __result = true; } }
static void Postfix(TurnDirector __instance, MessageCenterMessage message) { Mod.Log.Trace?.Write("TD:OICC - entered."); // Iterate contract objectives List <ITaggedItem> objectsOfType = SharedState.Combat.ItemRegistry.GetObjectsOfType(TaggedObjectType.Objective); List <ObjectiveGameLogic> objectives = objectsOfType.ConvertAll((ITaggedItem x) => x as ObjectiveGameLogic); foreach (ObjectiveGameLogic ogl in objectives) { Mod.Log.Debug?.Write($" -- objective: {ogl.DisplayName} has: {ogl.GetTargetUnits().Count} targets"); foreach (ICombatant combatant in ogl.GetTargetUnits()) { if (combatant is BattleTech.Building building) { BattleTech.Building scaledBuilding = building; if (!ModState.ScaledObjectiveBuildings.Contains(building.DistinctId())) { building.ScaleHealth(); ModState.ScaledObjectiveBuildings.Add(building.DistinctId()); } else { Mod.Log.Debug?.Write($" -- building: {building.DistinctId()} was already scaled, skipping"); } } } } }
static void Postfix(BattleTech.Building __instance, ObjectiveGameLogic objective) { if (!ModState.ScaledObjectiveBuildings.Contains(__instance.DistinctId())) { __instance.ScaleHealth(); ModState.ScaledObjectiveBuildings.Add(__instance.DistinctId()); } else { Mod.Log.Info?.Write($" -- building: {__instance.DistinctId()} was already scaled, skipping"); } }
private void CollapseNextBuilding() { this.timeSinceLastCollapse += Time.deltaTime; if (this.timeSinceLastCollapse > this.timeBetweenBuildingCollapses) { if (this.BuildingsToCollapse.Count > 0) { BattleTech.Building buildingToCollapse = this.BuildingsToCollapse[0]; this.BuildingsToCollapse.RemoveAt(0); Mod.Log.Debug?.Write($"Collapsing ambush building: {CombatantUtils.Label(buildingToCollapse)}"); buildingToCollapse.FlagForDeath("Ambush Collapse", DeathMethod.Unknown, DamageType.Artillery, 0, -1, "0", false); buildingToCollapse.HandleDeath("0"); } this.timeSinceLastCollapse = 0f; } }
public void ResetAllBuildingData() { Main.LogDebug($"[EncounterDataManager.ResetAllBuildingData] Resetting all old building data"); List <BuildingRepresentation> buildingsInMap = GameObjextExtensions.GetBuildingsInMap(); foreach (BuildingRepresentation buildingRep in buildingsInMap) { BattleTech.Building building = buildingRep.ParentBuilding; if (building != null) { AccessTools.Field(typeof(BattleTech.Building), "isObjectiveTarget").SetValue(building, false); AccessTools.Field(typeof(BattleTech.Building), "isObjectiveActive").SetValue(building, false); building.objectiveGUIDS.Clear(); } ObstructionGameLogic obstructionGameLogic = buildingRep.GetComponent <ObstructionGameLogic>(); if (obstructionGameLogic != null) { obstructionGameLogic.isObjectiveTarget = false; } } }
static void Prefix(BattleTech.Building __instance) { // Skip if we aren't enabled or we're not placed on a building if (__instance == null || !ModState.IsUrbanBiome) { return; } Mod.Log.Debug?.Write("Current ambush building shells"); foreach (string guid in ModState.AmbushBuildingGUIDToTurrets.Keys) { Mod.Log.Debug?.Write($" -- Building GUID: {guid}"); } // If we contain a linked turret, destroy it before we die. if (ModState.AmbushBuildingGUIDToTurrets.ContainsKey(__instance.GUID)) { ModState.KillingLinkedUnitsSource = __instance.GUID; // Despawn the associated turret Turret linkedTurret = ModState.AmbushBuildingGUIDToTurrets[__instance.GUID]; Mod.Log.Info?.Write($"Building {CombatantUtils.Label(__instance)} is destroyed, destroying associated turret: {CombatantUtils.Label(linkedTurret)}"); DespawnActorMessage despawnMessage = new DespawnActorMessage(__instance.GUID, linkedTurret.GUID, DeathMethod.VitalComponentDestroyed); __instance.Combat.MessageCenter.PublishMessage(despawnMessage); ModState.KillingLinkedUnitsSource = null; ModState.AmbushBuildingGUIDToTurrets.Remove(__instance.GUID); ModState.AmbushTurretGUIDtoBuilding.Remove(linkedTurret.GUID); } // If the building is in candidates, remove it. if (ModState.CandidateBuildings.Contains(__instance)) { Mod.Log.Debug?.Write($"Removing building as a candidate for ambushes: {__instance.GUID}"); ModState.CandidateBuildings.Remove(__instance); } }
public override void Trigger(MessageCenterMessage inMessage, string triggeringName) { Main.LogDebug($"[SetIsObjectiveTargetByTagResult] Setting IsObjectiveTarget '{IsObjectiveTarget}' with tags '{String.Concat(Tags)}'"); List <ICombatant> combatants = ObjectiveGameLogic.GetTaggedCombatants(UnityGameInstance.BattleTechGame.Combat, new TagSet(Tags)); Main.LogDebug($"[SetIsObjectiveTargetByTagResult] Found '{combatants.Count}' combatants"); foreach (ICombatant combatant in combatants) { BattleTech.Building building = combatant as BattleTech.Building; if (building != null) { Main.LogDebug($"[SetIsObjectiveTargetByTagResult] Found building '{building.GameRep.name} - {building.DisplayName}'"); ObstructionGameLogic obstructionGameLogic = building.GameRep.GetComponent <ObstructionGameLogic>(); obstructionGameLogic.isObjectiveTarget = true; AccessTools.Field(typeof(BattleTech.Building), "isObjectiveTarget").SetValue(combatant, true); } CombatHUDInWorldElementMgr inworldElementManager = GameObject.Find("uixPrfPanl_HUD(Clone)").GetComponent <CombatHUDInWorldElementMgr>(); AccessTools.Method(typeof(CombatHUDInWorldElementMgr), "AddTickMark").Invoke(inworldElementManager, new object[] { combatant }); AccessTools.Method(typeof(CombatHUDInWorldElementMgr), "AddInWorldActorElements").Invoke(inworldElementManager, new object[] { combatant }); } }
public static AbstractActor SpawnAmbushTurret(Team team, Lance ambushLance, BattleTech.Building building, Vector3 ambushOrigin) { // Randomly determine one of the spawnpairs from the current ambushdef List <TurretAndPilotDef> shuffledSpawns = new List <TurretAndPilotDef>(); shuffledSpawns.AddRange(ModState.InfantryAmbushDefForContract.SpawnPool); shuffledSpawns.Shuffle(); TurretAndPilotDef ambushDef = shuffledSpawns[0]; PilotDef pilotDef = ModState.Combat.DataManager.PilotDefs.Get(ambushDef.PilotDefId); TurretDef turretDef = ModState.Combat.DataManager.TurretDefs.GetOrCreate(ambushDef.TurretDefId); turretDef.Refresh(); // determine a position somewhere up the building's axis EncounterLayerData encounterLayerData = ModState.Combat.EncounterLayerData; Point cellPoint = new Point( ModState.Combat.MapMetaData.GetXIndex(building.CurrentPosition.x), ModState.Combat.MapMetaData.GetZIndex(building.CurrentPosition.z)); MapEncounterLayerDataCell melDataCell = encounterLayerData.mapEncounterLayerDataCells[cellPoint.Z, cellPoint.X]; float buildingHeight = melDataCell.GetBuildingHeight(); float terrainHeight = ModState.Combat.MapMetaData.GetLerpedHeightAt(building.CurrentPosition, true); float heightDelta = (buildingHeight - terrainHeight) * 0.7f; float adjustedY = terrainHeight + heightDelta; Mod.Log.Debug?.Write($"At building position, terrain height is: {terrainHeight} while buildingHeight is: {buildingHeight}. " + $" Calculated 70% of building height + terrain as {adjustedY}."); Vector3 newPosition = building.GameRep.transform.position; newPosition.y = adjustedY; Mod.Log.Debug?.Write($"Changing transform position from: {building.GameRep.transform.position} to {newPosition}"); /// Rotate to face the ambush origin Vector3 spawnDirection = Vector3.RotateTowards(building.CurrentRotation.eulerAngles, ambushOrigin, 1f, 0f); Quaternion spawnRotation = Quaternion.LookRotation(spawnDirection); // Create the turret Turret turret = ActorFactory.CreateTurret(turretDef, pilotDef, team.EncounterTags, ModState.Combat, team.GetNextSupportUnitGuid(), "", null); turret.Init(newPosition, spawnRotation.eulerAngles.y, true); turret.InitGameRep(null); if (turret == null) { Mod.Log.Error?.Write($"Failed to spawn turretDefId: {ambushDef.TurretDefId} + pilotDefId: {ambushDef.PilotDefId} !"); } Mod.Log.Debug?.Write($" Spawned trap turret, adding to team."); team.AddUnit(turret); turret.AddToTeam(team); turret.AddToLance(ambushLance); turret.BehaviorTree = BehaviorTreeFactory.MakeBehaviorTree(ModState.Combat.BattleTechGame, turret, BehaviorTreeIDEnum.CoreAITree); Mod.Log.Debug?.Write("Updated turret behaviorTree"); ModState.AmbushBuildingGUIDToTurrets.Add(building.GUID, turret); ModState.AmbushTurretGUIDtoBuilding.Add(turret.GUID, building); // Associate the building withe the team building.AddToTeam(team); building.BuildingRep.IsTargetable = true; building.BuildingRep.SetHighlightColor(ModState.Combat, team); building.BuildingRep.RefreshEdgeCache(); // Increase the building's health to the current value + turret structure float combinedStructure = (float)Math.Ceiling(building.CurrentStructure + turret.GetCurrentStructure(BuildingLocation.Structure)); Mod.Log.Debug?.Write($"Setting ambush structure to: {combinedStructure} = building.currentStructure: {building.CurrentStructure} + " + $"turret.currentStructure: {turret.GetCurrentStructure(BuildingLocation.Structure)}"); building.StatCollection.Set <float>("Structure", combinedStructure); // Finally notify others UnitSpawnedMessage message = new UnitSpawnedMessage("CJ_TRAP", turret.GUID); ModState.Combat.MessageCenter.PublishMessage(message); // Finally force the turret to be fully visible turret.OnPlayerVisibilityChanged(VisibilityLevel.LOSFull); return(turret); }
public static void SpawnAmbush(Vector3 ambushOrigin) { if (!Mod.Config.InfantryAmbush.Enabled) { return; } int infantrySpawns = Mod.Random.Next(ModState.InfantryAmbushDefForContract.MinSpawns, ModState.InfantryAmbushDefForContract.MaxSpawns); Mod.Log.Debug?.Write($"Spawning up to {infantrySpawns} infantry spawns as part of this ambush."); // Create a new lance in the target team Lance ambushLance = TeamHelper.CreateAmbushLance(ModState.AmbushTeam); // Build list of candidate trap buildings List <BattleTech.Building> candidates = CandidateBuildingsHelper.ClosestCandidatesToPosition(ambushOrigin, Mod.Config.Ambush.SearchRadius); if (candidates.Count < ModState.InfantryAmbushDefForContract.MinSpawns) { Mod.Log.Debug?.Write($"Insufficient candidate buildings to spawn an infantry ambush. Skipping."); return; } // Make sure we don't spawn more turrets than buildings if (infantrySpawns > candidates.Count) { infantrySpawns = candidates.Count; } List <AbstractActor> spawnedActors = new List <AbstractActor>(); List <BattleTech.Building> spawnBuildings = new List <BattleTech.Building>(); for (int i = 0; i < infantrySpawns; i++) { BattleTech.Building spawnBuildingShell = candidates.ElementAt(i); // Spawn a turret trap AbstractActor ambushTurret = SpawnAmbushTurret(ModState.AmbushTeam, ambushLance, spawnBuildingShell, ambushOrigin); spawnedActors.Add(ambushTurret); spawnBuildings.Add(spawnBuildingShell); Mod.Log.Info?.Write($"Spawned turret: {ambushTurret.DisplayName} in building: {spawnBuildingShell.DisplayName}"); } // Remove any buildings that are part of this ambush from candidates ModState.CandidateBuildings.RemoveAll(x => spawnBuildings.Contains(x)); // Determine the targets that should be prioritized by the enemies List <ICombatant> targets = new List <ICombatant>(); foreach (ICombatant combatant in ModState.Combat.GetAllCombatants()) { if (!combatant.IsDead && !combatant.IsFlaggedForDeath && combatant.team != null && ModState.Combat.HostilityMatrix.IsLocalPlayerFriendly(combatant.team)) { if (Vector3.Distance(ambushOrigin, combatant.CurrentPosition) <= Mod.Config.Ambush.SearchRadius) { targets.Add(combatant); } } } Mod.Log.Info?.Write($"Adding InfantryAmbushSequence for {spawnedActors.Count} actors."); try { InfantryAmbushSequence ambushSequence = new InfantryAmbushSequence(ModState.Combat, ambushOrigin, spawnedActors, spawnBuildings, targets, Mod.Config.InfantryAmbush.FreeAttackEnabled); ModState.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(ambushSequence)); } catch (Exception e) { Mod.Log.Error?.Write(e, "Failed to create AES sequence due to error!"); } }
public static void LogBuildingDamage(BattleTech.Building __instance) { try { LogActorDamage(0, __instance.CurrentStructure); } catch (Exception ex) { Error(ex); } }
public static void RecordBuildingDamage(BattleTech.Building __instance, float totalDamage) { RecordUnitDamage(BuildingLocation.Structure.ToString(), totalDamage, 0, __instance.CurrentStructure); }
public static void Postfix(CombatHUDActorInfo __instance, AbstractActor ___displayedActor, BattleTech.Building ___displayedBuilding, ICombatant ___displayedCombatant) { if (__instance == null || ___displayedActor == null) { return; } try { bool isEnemyOrNeutral = false; VisibilityLevel visibilityLevel = VisibilityLevel.None; if (___displayedCombatant != null) { if (___displayedCombatant.IsForcedVisible) { visibilityLevel = VisibilityLevel.LOSFull; } else if (___displayedBuilding != null) { visibilityLevel = __instance.Combat.LocalPlayerTeam.VisibilityToTarget(___displayedBuilding); } else if (___displayedActor != null) { if (__instance.Combat.HostilityMatrix.IsLocalPlayerFriendly(___displayedActor.team)) { visibilityLevel = VisibilityLevel.LOSFull; } else { visibilityLevel = __instance.Combat.LocalPlayerTeam.VisibilityToTarget(___displayedActor); isEnemyOrNeutral = true; } } } Traverse setGOActiveMethod = Traverse.Create(__instance).Method("SetGOActive", new Type[] { typeof(MonoBehaviour), typeof(bool) }); // The actual method should handle allied and friendly units fine, so we can just change it for enemies if (isEnemyOrNeutral && visibilityLevel >= VisibilityLevel.Blip0Minimum && ___displayedActor != null) { SensorScanType scanType = SensorLockHelper.CalculateSharedLock(___displayedActor, ModState.LastPlayerActorActivated); bool hasVisualScan = VisualLockHelper.CanSpotTarget(ModState.LastPlayerActorActivated, ModState.LastPlayerActorActivated.CurrentPosition, ___displayedActor, ___displayedActor.CurrentPosition, ___displayedActor.CurrentRotation, ___displayedActor.Combat.LOS); Mod.Log.Debug?.Write($"Updating item visibility for enemy: {CombatantUtils.Label(___displayedActor)} to scanType: {scanType} and " + $"hasVisualScan: {hasVisualScan} from lastActivated: {CombatantUtils.Label(ModState.LastPlayerActorActivated)}"); // Values that are always displayed setGOActiveMethod.GetValue(__instance.NameDisplay, true); setGOActiveMethod.GetValue(__instance.PhaseDisplay, true); if (scanType >= SensorScanType.StructAndWeaponID) { // Show unit summary setGOActiveMethod.GetValue(__instance.DetailsDisplay, true); // Show active state setGOActiveMethod.GetValue(__instance.InspiredDisplay, false); // Show armor and struct setGOActiveMethod.GetValue(__instance.ArmorBar, true); setGOActiveMethod.GetValue(__instance.StructureBar, true); if (___displayedActor as Mech != null) { setGOActiveMethod.GetValue(__instance.StabilityDisplay, true); setGOActiveMethod.GetValue(__instance.HeatDisplay, true); } else { setGOActiveMethod.GetValue(__instance.StabilityDisplay, false); setGOActiveMethod.GetValue(__instance.HeatDisplay, false); } } else if (scanType >= SensorScanType.ArmorAndWeaponType || hasVisualScan) { // Show unit summary setGOActiveMethod.GetValue(__instance.DetailsDisplay, false); // Show active state setGOActiveMethod.GetValue(__instance.InspiredDisplay, false); // Show armor and struct setGOActiveMethod.GetValue(__instance.ArmorBar, true); setGOActiveMethod.GetValue(__instance.StructureBar, true); setGOActiveMethod.GetValue(__instance.StabilityDisplay, false); setGOActiveMethod.GetValue(__instance.HeatDisplay, false); } else { // Hide unit summary setGOActiveMethod.GetValue(__instance.DetailsDisplay, false); // Hide active state setGOActiveMethod.GetValue(__instance.InspiredDisplay, false); // Hide armor and struct setGOActiveMethod.GetValue(__instance.ArmorBar, false); setGOActiveMethod.GetValue(__instance.StructureBar, false); setGOActiveMethod.GetValue(__instance.StabilityDisplay, false); setGOActiveMethod.GetValue(__instance.HeatDisplay, false); } // TODO: DEBUG TESTING if (__instance.MarkDisplay != null) { setGOActiveMethod.GetValue(__instance.MarkDisplay, true); } CombatHUDStateStack stateStack = (CombatHUDStateStack)Traverse.Create(__instance).Property("StateStack").GetValue(); setGOActiveMethod.GetValue(stateStack, false); } else { if (__instance.MarkDisplay != null && ___displayedActor != null) { setGOActiveMethod.GetValue(__instance.MarkDisplay, ___displayedActor.IsMarked); } } } catch (Exception e) { Mod.Log.Info?.Write($"Error updating item visibility! Error was: {e.Message}"); } }
public static void FixZombieBuilding(BattleTech.Building __instance, ref float totalDamage) { try { KillZombie("building", __instance.DisplayName, __instance.CurrentStructure, ref totalDamage); } catch (Exception ex) { Error(ex); } }