// Translate the strings in the config to enum types private void BuildWeightTable() { foreach (string weightS in this.Ambush.AmbushWeights) { AmbushType enumVal = (AmbushType)Enum.Parse(typeof(AmbushType), weightS); this.Ambush.AmbushTypes.Add(enumVal); } }
static void Prefix(TurnDirector __instance) { if (!__instance.IsInterleaved && !__instance.IsInterleavePending && __instance.CurrentRound >= Mod.Config.Ambush.EnableOnRound && ModState.PotentialAmbushOrigins.Count != 0 && __instance.ActiveTurnActor is Team activeTeam && activeTeam.IsLocalPlayer) { // Re-Filter the candidates to try to catch buildings that were marked contract objectives CandidateBuildingsHelper.FilterOnTurnActorIncrement(__instance.Combat); // Determine potential ambush sites based upon the origins Dictionary <Vector3, List <BattleTech.Building> > ambushSites = new Dictionary <Vector3, List <BattleTech.Building> >(); foreach (Vector3 origin in ModState.PotentialAmbushOrigins) { // For the given origin, find how many potential buildings there are. List <BattleTech.Building> originCandidates = CandidateBuildingsHelper.ClosestCandidatesToPosition(origin, Mod.Config.Ambush.SearchRadius); Mod.Log.Debug?.Write($" Found {originCandidates.Count} candidate buildings for originPos: {origin}"); ambushSites.Add(origin, originCandidates); } ModState.PotentialAmbushOrigins.Clear(); // reset potential origins for next round // Determine if the unit was ambushed this turn bool wasAmbushed = false; if (ModState.Ambushes < Mod.Config.Ambush.MaxPerMap && ambushSites.Count > 0) { float roll = Mod.Random.Next(1, 100); int threshold = (int)Math.Ceiling(ModState.CurrentAmbushChance * 100f); if (roll <= threshold) { Mod.Log.Info?.Write($" Roll: {roll} is under current threshold: {threshold}, enabling possible ambush"); wasAmbushed = true; } else { ModState.CurrentAmbushChance += Mod.Config.Ambush.ChancePerActor; Mod.Log.Info?.Write($" Roll: {roll} was over threshold: {threshold}, increasing ambush chance to: {ModState.CurrentAmbushChance} for next position."); } } if (wasAmbushed) { // Sort the ambushSites by number of buildings to maximize ambush success Mod.Log.Debug?.Write("Sorting sites by potential buildings."); List <KeyValuePair <Vector3, List <BattleTech.Building> > > sortedSites = ambushSites.ToList(); sortedSites.Sort((x, y) => x.Value.Count.CompareTo(y.Value.Count)); Vector3 ambushOrigin = sortedSites[0].Key; Mod.Log.Debug?.Write($"Spawning an ambush at position: {ambushOrigin}"); // Randomly determine an ambush type by weight List <AmbushType> shuffledTypes = new List <AmbushType>(); shuffledTypes.AddRange(Mod.Config.Ambush.AmbushTypes); shuffledTypes.Shuffle <AmbushType>(); AmbushType ambushType = shuffledTypes[0]; Mod.Log.Info?.Write($"Ambush type of: {ambushType} will be applied at position: {ambushOrigin}"); switch (ambushType) { case AmbushType.Explosion: ExplosionAmbushHelper.SpawnAmbush(ambushOrigin); break; case AmbushType.Infantry: InfantryAmbushHelper.SpawnAmbush(ambushOrigin); break; case AmbushType.BattleArmor: SpawnAmbushHelper.SpawnAmbush(ambushOrigin, AmbushType.BattleArmor); break; case AmbushType.Mech: SpawnAmbushHelper.SpawnAmbush(ambushOrigin, AmbushType.Mech); break; case AmbushType.Vehicle: SpawnAmbushHelper.SpawnAmbush(ambushOrigin, AmbushType.Vehicle); break; default: Mod.Log.Error?.Write($"UNKNOWN AMBUSH TYPE: {ambushType} - CANNOT PROCEED!"); break; } // Record a successful ambush and reset the weighting ModState.AmbushOrigins.Add(ambushOrigin); ModState.Ambushes++; ModState.CurrentAmbushChance = Mod.Config.Ambush.BaseChance; } } }
public static void SpawnAmbush(Vector3 ambushOrigin, AmbushType ambushType) { // Determine how many units we're spawning int minSpawns = 0, maxSpawns = 0; if (ambushType == AmbushType.BattleArmor) { if (!Mod.Config.BattleArmorAmbush.Enabled) { return; } minSpawns = ModState.BattleArmorAmbushDefForContract.MinSpawns; maxSpawns = ModState.BattleArmorAmbushDefForContract.MaxSpawns; } else if (ambushType == AmbushType.Mech) { if (!Mod.Config.MechAmbush.Enabled) { return; } minSpawns = ModState.MechAmbushDefForContract.MinSpawns; maxSpawns = ModState.MechAmbushDefForContract.MaxSpawns; } else if (ambushType == AmbushType.Vehicle) { if (!Mod.Config.VehicleAmbush.Enabled) { return; } minSpawns = ModState.VehicleAmbushDefForContract.MinSpawns; maxSpawns = ModState.VehicleAmbushDefForContract.MaxSpawns; } int actorsToSpawn = Mod.Random.Next(minSpawns, maxSpawns); Mod.Log.Debug?.Write($"Spawning {actorsToSpawn} actors as part of this ambush."); // Starting with the closest building, look through the buildings and determine how many locations can support a unit. List <BattleTech.Building> candidates = CandidateBuildingsHelper.ClosestCandidatesToPosition(ambushOrigin, Mod.Config.Ambush.SearchRadius); if (candidates.Count < minSpawns) { Mod.Log.Debug?.Write($"Insufficient candidate buildings for a spawn ambush. Skipping."); return; } // Create a new lance in the target team Lance ambushLance = TeamHelper.CreateAmbushLance(ModState.AmbushTeam); ModState.CurrentSpawningLance = ambushLance; EncounterLayerData encounterLayerData = ModState.Combat.EncounterLayerData; List <BattleTech.Building> buildingsToLevel = new List <BattleTech.Building>(); List <AbstractActor> spawnedActors = new List <AbstractActor>(); foreach (BattleTech.Building building in candidates) { if (actorsToSpawn == 0) { break; // nothing more to do, end processing. } // Spawn one unit at the origin of the building buildingsToLevel.Add(building); Mod.Log.Debug?.Write("Spawning actor at building origin."); AbstractActor spawnedActor = null; if (ambushType == AmbushType.BattleArmor) { spawnedActor = SpawnAmbushMech(ModState.AmbushTeam, ambushLance, ambushOrigin, building.CurrentPosition, building.CurrentRotation, ModState.BattleArmorAmbushDefForContract.SpawnPool); } else if (ambushType == AmbushType.Mech) { spawnedActor = SpawnAmbushMech(ModState.AmbushTeam, ambushLance, ambushOrigin, building.CurrentPosition, building.CurrentRotation, ModState.MechAmbushDefForContract.SpawnPool); } else if (ambushType == AmbushType.Vehicle) { spawnedActor = SpawnAmbushVehicle(ModState.AmbushTeam, ambushLance, ambushOrigin, building.CurrentPosition, building.CurrentRotation); } spawnedActors.Add(spawnedActor); actorsToSpawn--; // Iterate through adjacent hexes to see if we can spawn more units in the building List <Vector3> adjacentHexes = ModState.Combat.HexGrid.GetGridPointsAroundPointWithinRadius(ambushOrigin, 3); // 3 hexes should cover most large buidlings foreach (Vector3 adjacentHex in adjacentHexes) { if (actorsToSpawn == 0) { break; } Point cellPoint = new Point(ModState.Combat.MapMetaData.GetXIndex(adjacentHex.x), ModState.Combat.MapMetaData.GetZIndex(adjacentHex.z)); Mod.Log.Debug?.Write($" Evaluating point {cellPoint.X}, {cellPoint.Z} for potential ambush."); if (encounterLayerData.mapEncounterLayerDataCells[cellPoint.Z, cellPoint.X].HasSpecifiedBuilding(building.GUID)) { Mod.Log.Debug?.Write($"Spawning actor at adjacent hex at position: {adjacentHex}"); AbstractActor additionalSpawn = null; if (ambushType == AmbushType.BattleArmor) { additionalSpawn = SpawnAmbushMech(ModState.AmbushTeam, ambushLance, ambushOrigin, adjacentHex, building.CurrentRotation, ModState.BattleArmorAmbushDefForContract.SpawnPool); } else if (ambushType == AmbushType.Mech) { additionalSpawn = SpawnAmbushMech(ModState.AmbushTeam, ambushLance, ambushOrigin, adjacentHex, building.CurrentRotation, ModState.MechAmbushDefForContract.SpawnPool); } else if (ambushType == AmbushType.Vehicle) { additionalSpawn = SpawnAmbushVehicle(ModState.AmbushTeam, ambushLance, ambushOrigin, adjacentHex, building.CurrentRotation); } spawnedActors.Add(additionalSpawn); actorsToSpawn--; } else { Mod.Log.Debug?.Write($" Hex {adjacentHex} is outside of the main building, skipping."); } } } // Remove any buildings that are part of this ambush from candidates ModState.CandidateBuildings.RemoveAll(x => buildingsToLevel.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); } } } bool applyAttacks = false; if (ambushType == AmbushType.BattleArmor && Mod.Config.BattleArmorAmbush.FreeAttackEnabled) { applyAttacks = true; } if (ambushType == AmbushType.Mech && Mod.Config.MechAmbush.FreeAttackEnabled) { applyAttacks = true; } if (ambushType == AmbushType.Vehicle && Mod.Config.VehicleAmbush.FreeAttackEnabled) { applyAttacks = true; } Mod.Log.Info?.Write($"Adding SpawnAmbushSequence for {spawnedActors.Count} actors and {buildingsToLevel.Count} buildings to be leveled."); try { SpawnAmbushSequence ambushSequence = new SpawnAmbushSequence(ModState.Combat, ambushOrigin, spawnedActors, buildingsToLevel, targets, applyAttacks); ModState.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(ambushSequence)); } catch (Exception e) { Mod.Log.Error?.Write(e, "Failed to create AES sequence due to error!"); } }