public Squad(HackyAI bot, SquadType type, Actor target) { Bot = bot; World = bot.World; Random = bot.Random; Type = type; Target = Target.FromActor(target); FuzzyStateMachine = new StateMachine(); switch (type) { case SquadType.Assault: case SquadType.Rush: FuzzyStateMachine.ChangeState(this, new GroundUnitsIdleState(), true); break; case SquadType.Air: FuzzyStateMachine.ChangeState(this, new AirIdleState(), true); break; case SquadType.Protection: FuzzyStateMachine.ChangeState(this, new UnitsForProtectionIdleState(), true); break; } }
public BaseBuilder(HackyAI ai, string category, Player p, PowerManager pm, PlayerResources pr) { this.ai = ai; world = p.World; player = p; playerPower = pm; playerResources = pr; this.category = category; }
public AIHarvesterManager(HackyAI ai, Player p) { this.ai = ai; world = p.World; pathfinder = world.WorldActor.Trait <IPathFinder>(); domainIndex = world.WorldActor.Trait <DomainIndex>(); resLayer = world.WorldActor.TraitOrDefault <ResourceLayer>(); claimLayer = world.WorldActor.TraitOrDefault <ResourceClaimLayer>(); }
public BaseBuilder(HackyAI ai, string category, Player p, PowerManager pm, PlayerResources pr) { this.ai = ai; world = p.World; player = p; playerPower = pm; playerResources = pr; this.category = category; failRetryTicks = ai.Info.StructureProductionResumeDelay; }
bool TickQueue(ProductionQueue queue) { var currentBuilding = queue.CurrentItem(); // Waiting to build something if (currentBuilding == null) { var item = ChooseBuildingToBuild(queue); if (item == null) { return(false); } HackyAI.BotDebug("AI: {0} is starting production of {1}".F(player, item.Name)); world.IssueOrder(Order.StartProduction(queue.Actor, item.Name, 1)); } else if (currentBuilding.Done) { // Production is complete // Choose the placement logic // HACK: HACK HACK HACK var type = BuildingType.Building; if (world.Map.Rules.Actors[currentBuilding.Item].Traits.Contains <AttackBaseInfo>()) { type = BuildingType.Defense; } else if (world.Map.Rules.Actors[currentBuilding.Item].Traits.Contains <RefineryInfo>()) { type = BuildingType.Refinery; } var location = ai.ChooseBuildLocation(currentBuilding.Item, true, type); if (location == null) { HackyAI.BotDebug("AI: {0} has nowhere to place {1}".F(player, currentBuilding.Item)); world.IssueOrder(Order.CancelProduction(queue.Actor, currentBuilding.Item, 1)); } else { world.IssueOrder(new Order("PlaceBuilding", player.PlayerActor, false) { TargetLocation = location.Value, TargetString = currentBuilding.Item, TargetActor = queue.Actor, SuppressVisualFeedback = true }); return(true); } } return(true); }
public AISupportPowerManager(HackyAI ai, Player p) { this.ai = ai; world = p.World; player = p; frozenLayer = p.PlayerActor.Trait <FrozenActorLayer>(); supportPowerManager = p.PlayerActor.TraitOrDefault <SupportPowerManager>(); foreach (var decision in ai.Info.SupportPowerDecisions) { powerDecisions.Add(decision.OrderName, decision); } }
public RenderDebugState(Actor self, RenderDebugStateInfo info) { var buildingInfo = self.Info.TraitInfoOrDefault<BuildingInfo>(); var yOffset = buildingInfo == null ? 1 : buildingInfo.Dimensions.Y; offset = new WVec(0, 512 * yOffset, 0); this.self = self; color = GetColor(); font = Game.Renderer.Fonts[info.Font]; var localPlayer = self.World.LocalPlayer; devMode = localPlayer != null ? localPlayer.PlayerActor.Trait<DeveloperMode>() : null; ai = self.Owner.PlayerActor.TraitsImplementing<HackyAI>().FirstOrDefault(x => x.IsEnabled); }
public void Tick(List <Actor> activeUnits) { if (resLayer == null || resLayer.IsResourceLayerEmpty) { return; } // Find idle harvesters and give them orders: foreach (var harvester in activeUnits) { var harv = harvester.TraitOrDefault <Harvester>(); if (harv == null) { continue; } if (!harv.IsEmpty) { continue; } if (!harvester.IsIdle) { var act = harvester.CurrentActivity; if (!harv.LastSearchFailed || act.NextActivity == null || act.NextActivity.GetType() != typeof(FindResources)) { continue; } } var para = harvester.TraitOrDefault <Parachutable>(); if (para != null && para.IsInAir) { continue; } // Tell the idle harvester to quit slacking: var newSafeResourcePatch = FindNextResource(harvester, harv); HackyAI.BotDebug("AI: Harvester {0} is idle. Ordering to {1} in search for new resources.".F(harvester, newSafeResourcePatch)); ai.QueueOrder(new Order("Harvest", harvester, Target.FromCell(world, newSafeResourcePatch), false)); } }
/// <summary>Scans the map in chunks, evaluating all actors in each.</summary> CPos?FindCoarseAttackLocationToSupportPower(SupportPowerInstance readyPower) { CPos?bestLocation = null; var bestAttractiveness = 0; var powerDecision = powerDecisions[readyPower.Info.OrderName]; if (powerDecision == null) { HackyAI.BotDebug("Bot Bug: FindAttackLocationToSupportPower, couldn't find powerDecision for {0}", readyPower.Info.OrderName); return(null); } var map = world.Map; var checkRadius = powerDecision.CoarseScanRadius; for (var i = 0; i < map.MapSize.X; i += checkRadius) { for (var j = 0; j < map.MapSize.Y; j += checkRadius) { var tl = new MPos(i, j); var br = new MPos(i + checkRadius, j + checkRadius); var region = new CellRegion(map.Grid.Type, tl, br); // HACK: The AI code should not be messing with raw coordinate transformations var wtl = world.Map.CenterOfCell(tl.ToCPos(map)); var wbr = world.Map.CenterOfCell(br.ToCPos(map)); var targets = world.ActorMap.ActorsInBox(wtl, wbr); var frozenTargets = frozenLayer.FrozenActorsInRegion(region); var consideredAttractiveness = powerDecision.GetAttractiveness(targets, player) + powerDecision.GetAttractiveness(frozenTargets, player); if (consideredAttractiveness <= bestAttractiveness || consideredAttractiveness < powerDecision.MinimumAttractiveness) { continue; } bestAttractiveness = consideredAttractiveness; bestLocation = new MPos(i, j).ToCPos(map); } } return(bestLocation); }
/// <summary>Detail scans an area, evaluating positions.</summary> CPos?FindFineAttackLocationToSupportPower(SupportPowerInstance readyPower, CPos checkPos, int extendedRange = 1) { CPos?bestLocation = null; var bestAttractiveness = 0; var powerDecision = powerDecisions[readyPower.Info.OrderName]; if (powerDecision == null) { HackyAI.BotDebug("Bot Bug: FindAttackLocationToSupportPower, couldn't find powerDecision for {0}", readyPower.Info.OrderName); return(null); } var checkRadius = powerDecision.CoarseScanRadius; var fineCheck = powerDecision.FineScanRadius; for (var i = 0 - extendedRange; i <= (checkRadius + extendedRange); i += fineCheck) { var x = checkPos.X + i; for (var j = 0 - extendedRange; j <= (checkRadius + extendedRange); j += fineCheck) { var y = checkPos.Y + j; var pos = world.Map.CenterOfCell(new CPos(x, y)); var consideredAttractiveness = 0; consideredAttractiveness += powerDecision.GetAttractiveness(pos, player, frozenLayer); if (consideredAttractiveness <= bestAttractiveness || consideredAttractiveness < powerDecision.MinimumAttractiveness) { continue; } bestAttractiveness = consideredAttractiveness; bestLocation = new CPos(x, y); } } return(bestLocation); }
public int GetNextScanTime(HackyAI ai) { return ai.Random.Next(MinimumScanTimeInterval, MaximumScanTimeInterval); }
public Squad(HackyAI bot, SquadType type) : this(bot, type, null) { }
public void TryToUseSupportPower(Actor self) { if (supportPowerManager == null) { return; } foreach (var sp in supportPowerManager.Powers.Values) { if (sp.Disabled) { continue; } // Add power to dictionary if not in delay dictionary yet if (!waitingPowers.ContainsKey(sp)) { waitingPowers.Add(sp, 0); } if (waitingPowers[sp] > 0) { waitingPowers[sp]--; } // If we have recently tried and failed to find a use location for a power, then do not try again until later var isDelayed = waitingPowers[sp] > 0; if (sp.Ready && !isDelayed && powerDecisions.ContainsKey(sp.Info.OrderName)) { var powerDecision = powerDecisions[sp.Info.OrderName]; if (powerDecision == null) { HackyAI.BotDebug("Bot Bug: FindAttackLocationToSupportPower, couldn't find powerDecision for {0}", sp.Info.OrderName); continue; } var attackLocation = FindCoarseAttackLocationToSupportPower(sp); if (attackLocation == null) { HackyAI.BotDebug("AI: {1} can't find suitable coarse attack location for support power {0}. Delaying rescan.", sp.Info.OrderName, player.PlayerName); waitingPowers[sp] += powerDecision.GetNextScanTime(ai); continue; } // Found a target location, check for precise target attackLocation = FindFineAttackLocationToSupportPower(sp, (CPos)attackLocation); if (attackLocation == null) { HackyAI.BotDebug("AI: {1} can't find suitable final attack location for support power {0}. Delaying rescan.", sp.Info.OrderName, player.PlayerName); waitingPowers[sp] += powerDecision.GetNextScanTime(ai); continue; } // Valid target found, delay by a few ticks to avoid rescanning before power fires via order HackyAI.BotDebug("AI: {2} found new target location {0} for support power {1}.", attackLocation, sp.Info.OrderName, player.PlayerName); waitingPowers[sp] += 10; ai.QueueOrder(new Order(sp.Key, supportPowerManager.Self, Target.FromCell(world, attackLocation.Value), false) { SuppressVisualFeedback = true }); } } }
ActorInfo ChooseBuildingToBuild(ProductionQueue queue) { var buildableThings = queue.BuildableItems(); // First priority is to get out of a low power situation if (playerPower.ExcessPower < 0) { var power = GetProducibleBuilding("Power", buildableThings, a => a.Traits.WithInterface <PowerInfo>().Where(i => i.UpgradeMinEnabledLevel < 1).Sum(p => p.Amount)); if (power != null && power.Traits.WithInterface <PowerInfo>().Where(i => i.UpgradeMinEnabledLevel < 1).Sum(p => p.Amount) > 0) { // TODO: Handle the case when of when we actually do need a power plant because we don't have enough but are also suffering from a power outage if (playerPower.PowerOutageRemainingTicks <= 0) { HackyAI.BotDebug("AI: {0} decided to build {1}: Priority override (low power)", queue.Actor.Owner, power.Name); return(power); } } } // Next is to build up a strong economy if (!ai.HasAdequateProc() || !ai.HasMinimumProc()) { var refinery = GetProducibleBuilding("Refinery", buildableThings); if (refinery != null) { HackyAI.BotDebug("AI: {0} decided to build {1}: Priority override (refinery)", queue.Actor.Owner, refinery.Name); return(refinery); } } // Make sure that we can can spend as fast as we are earning if (ai.Info.NewProductionCashThreshold > 0 && playerResources.Resources > ai.Info.NewProductionCashThreshold) { var production = GetProducibleBuilding("Production", buildableThings); if (production != null) { HackyAI.BotDebug("AI: {0} decided to build {1}: Priority override (production)", queue.Actor.Owner, production.Name); return(production); } } // Create some head room for resource storage if we really need it if (playerResources.AlertSilo) { var silo = GetProducibleBuilding("Silo", buildableThings); if (silo != null) { HackyAI.BotDebug("AI: {0} decided to build {1}: Priority override (silo)", queue.Actor.Owner, silo.Name); return(silo); } } // Build everything else foreach (var frac in ai.Info.BuildingFractions.Shuffle(ai.Random)) { var name = frac.Key; // Can we build this structure? if (!buildableThings.Any(b => b.Name == name)) { continue; } // Do we want to build this structure? var count = playerBuildings.Count(a => a.Info.Name == name); if (count > frac.Value * playerBuildings.Length) { continue; } if (ai.Info.BuildingLimits.ContainsKey(name) && ai.Info.BuildingLimits[name] <= count) { continue; } // Will this put us into low power? var actor = world.Map.Rules.Actors[frac.Key]; var pis = actor.Traits.WithInterface <PowerInfo>().Where(i => i.UpgradeMinEnabledLevel < 1); if (playerPower.ExcessPower < 0 || playerPower.ExcessPower < pis.Sum(pi => pi.Amount)) { // Try building a power plant instead var power = GetProducibleBuilding("Power", buildableThings, a => a.Traits.WithInterface <PowerInfo>().Where(i => i.UpgradeMinEnabledLevel < 1).Sum(pi => pi.Amount)); if (power != null && power.Traits.WithInterface <PowerInfo>().Where(i => i.UpgradeMinEnabledLevel < 1).Sum(pi => pi.Amount) > 0) { // TODO: Handle the case when of when we actually do need a power plant because we don't have enough but are also suffering from a power outage if (playerPower.PowerOutageRemainingTicks > 0) { HackyAI.BotDebug("AI: {0} is suffering from a power outage; not going to build {1}", queue.Actor.Owner, power.Name); } else { HackyAI.BotDebug("{0} decided to build {1}: Priority override (would be low power)", queue.Actor.Owner, power.Name); return(power); } } } // Lets build this HackyAI.BotDebug("{0} decided to build {1}: Desired is {2} ({3} / {4}); current is {5} / {4}", queue.Actor.Owner, name, frac.Value, frac.Value * playerBuildings.Length, playerBuildings.Length, count); return(actor); } // Too spammy to keep enabled all the time, but very useful when debugging specific issues. // HackyAI.BotDebug("{0} couldn't decide what to build for queue {1}.", queue.Actor.Owner, queue.Info.Group); return(null); }
public int GetNextScanTime(HackyAI ai) { return(ai.Random.Next(MinimumScanTimeInterval, MaximumScanTimeInterval)); }
bool TickQueue(ProductionQueue queue) { var currentBuilding = queue.CurrentItem(); // Waiting to build something if (currentBuilding == null && failCount < ai.Info.MaximumFailedPlacementAttempts) { var item = ChooseBuildingToBuild(queue); if (item == null) { return(false); } ai.QueueOrder(Order.StartProduction(queue.Actor, item.Name, 1)); } else if (currentBuilding != null && currentBuilding.Done) { // Production is complete // Choose the placement logic // HACK: HACK HACK HACK // TODO: Derive this from BuildingCommonNames instead var type = BuildingType.Building; if (world.Map.Rules.Actors[currentBuilding.Item].HasTraitInfo <AttackBaseInfo>()) { type = BuildingType.Defense; } else if (world.Map.Rules.Actors[currentBuilding.Item].HasTraitInfo <RefineryInfo>()) { type = BuildingType.Refinery; } var location = ai.ChooseBuildLocation(currentBuilding.Item, true, type); if (location == null) { HackyAI.BotDebug("AI: {0} has nowhere to place {1}".F(player, currentBuilding.Item)); ai.QueueOrder(Order.CancelProduction(queue.Actor, currentBuilding.Item, 1)); failCount += failCount; // If we just reached the maximum fail count, cache the number of current structures if (failCount == ai.Info.MaximumFailedPlacementAttempts) { cachedBuildings = world.ActorsHavingTrait <Building>().Count(a => a.Owner == player); cachedBases = world.ActorsHavingTrait <BaseProvider>().Count(a => a.Owner == player); } } else { failCount = 0; ai.QueueOrder(new Order("PlaceBuilding", player.PlayerActor, false) { TargetLocation = location.Value, TargetString = currentBuilding.Item, TargetActor = queue.Actor, SuppressVisualFeedback = true }); return(true); } } return(true); }
ActorInfo ChooseBuildingToBuild(ProductionQueue queue) { var buildableThings = queue.BuildableItems(); // This gets used quite a bit, so let's cache it here var power = GetProducibleBuilding(ai.Info.BuildingCommonNames.Power, buildableThings, a => a.TraitInfos <PowerInfo>().Where(i => i.UpgradeMinEnabledLevel < 1).Sum(p => p.Amount)); // First priority is to get out of a low power situation if (playerPower.ExcessPower < ai.Info.MinimumExcessPower) { if (power != null && power.TraitInfos <PowerInfo>().Where(i => i.UpgradeMinEnabledLevel < 1).Sum(p => p.Amount) > 0) { HackyAI.BotDebug("AI: {0} decided to build {1}: Priority override (low power)", queue.Actor.Owner, power.Name); return(power); } } // Next is to build up a strong economy if (!ai.HasAdequateProc() || !ai.HasMinimumProc()) { var refinery = GetProducibleBuilding(ai.Info.BuildingCommonNames.Refinery, buildableThings); if (refinery != null && HasSufficientPowerForActor(refinery)) { HackyAI.BotDebug("AI: {0} decided to build {1}: Priority override (refinery)", queue.Actor.Owner, refinery.Name); return(refinery); } if (power != null && refinery != null && !HasSufficientPowerForActor(refinery)) { HackyAI.BotDebug("{0} decided to build {1}: Priority override (would be low power)", queue.Actor.Owner, power.Name); return(power); } } // Make sure that we can spend as fast as we are earning if (ai.Info.NewProductionCashThreshold > 0 && playerResources.Resources > ai.Info.NewProductionCashThreshold) { var production = GetProducibleBuilding(ai.Info.BuildingCommonNames.Production, buildableThings); if (production != null && HasSufficientPowerForActor(production)) { HackyAI.BotDebug("AI: {0} decided to build {1}: Priority override (production)", queue.Actor.Owner, production.Name); return(production); } if (power != null && production != null && !HasSufficientPowerForActor(production)) { HackyAI.BotDebug("{0} decided to build {1}: Priority override (would be low power)", queue.Actor.Owner, power.Name); return(power); } } // Only consider building this if there is enough water inside the base perimeter and there are close enough adjacent buildings if (waterState == Water.EnoughWater && ai.Info.NewProductionCashThreshold > 0 && playerResources.Resources > ai.Info.NewProductionCashThreshold && ai.CloseEnoughToWater()) { var navalproduction = GetProducibleBuilding(ai.Info.BuildingCommonNames.NavalProduction, buildableThings); if (navalproduction != null && HasSufficientPowerForActor(navalproduction)) { HackyAI.BotDebug("AI: {0} decided to build {1}: Priority override (navalproduction)", queue.Actor.Owner, navalproduction.Name); return(navalproduction); } if (power != null && navalproduction != null && !HasSufficientPowerForActor(navalproduction)) { HackyAI.BotDebug("{0} decided to build {1}: Priority override (would be low power)", queue.Actor.Owner, power.Name); return(power); } } // Create some head room for resource storage if we really need it if (playerResources.Resources > 0.8 * playerResources.ResourceCapacity) { var silo = GetProducibleBuilding(ai.Info.BuildingCommonNames.Silo, buildableThings); if (silo != null && HasSufficientPowerForActor(silo)) { HackyAI.BotDebug("AI: {0} decided to build {1}: Priority override (silo)", queue.Actor.Owner, silo.Name); return(silo); } if (power != null && silo != null && !HasSufficientPowerForActor(silo)) { HackyAI.BotDebug("{0} decided to build {1}: Priority override (would be low power)", queue.Actor.Owner, power.Name); return(power); } } // Build everything else foreach (var frac in ai.Info.BuildingFractions.Shuffle(ai.Random)) { var name = frac.Key; // Can we build this structure? if (!buildableThings.Any(b => b.Name == name)) { continue; } // Do we want to build this structure? var count = playerBuildings.Count(a => a.Info.Name == name); if (count > frac.Value * playerBuildings.Length) { continue; } if (ai.Info.BuildingLimits.ContainsKey(name) && ai.Info.BuildingLimits[name] <= count) { continue; } // If we're considering to build a naval structure, check whether there is enough water inside the base perimeter // and any structure providing buildable area close enough to that water. // TODO: Extend this check to cover any naval structure, not just production. if (ai.Info.BuildingCommonNames.NavalProduction.Contains(name) && (waterState == Water.NotEnoughWater || !ai.CloseEnoughToWater())) { continue; } // Will this put us into low power? var actor = world.Map.Rules.Actors[name]; if (playerPower.ExcessPower < ai.Info.MinimumExcessPower || !HasSufficientPowerForActor(actor)) { // Try building a power plant instead if (power != null && power.TraitInfos <PowerInfo>().Where(i => i.UpgradeMinEnabledLevel < 1).Sum(pi => pi.Amount) > 0) { if (playerPower.PowerOutageRemainingTicks > 0) { HackyAI.BotDebug("{0} decided to build {1}: Priority override (is low power)", queue.Actor.Owner, power.Name); } else { HackyAI.BotDebug("{0} decided to build {1}: Priority override (would be low power)", queue.Actor.Owner, power.Name); } return(power); } } // Lets build this HackyAI.BotDebug("{0} decided to build {1}: Desired is {2} ({3} / {4}); current is {5} / {4}", queue.Actor.Owner, name, frac.Value, frac.Value * playerBuildings.Length, playerBuildings.Length, count); return(actor); } // Too spammy to keep enabled all the time, but very useful when debugging specific issues. // HackyAI.BotDebug("{0} couldn't decide what to build for queue {1}.", queue.Actor.Owner, queue.Info.Group); return(null); }