#region Copyright & License Information /* * Copyright 2007-2014 The OpenRA Developers (see AUTHORS) * This file is part of OpenRA, which is free software. It is made * available to you under the terms of the GNU General Public License * as published by the Free Software Foundation. For more information, * see COPYING. */ #endregion using System; using System.Collections.Generic; using System.Linq; using System.Text; using OpenRA; using OpenRA.Mods.RA.AI; using OpenRA.Traits; namespace OpenRA.Mods.RA { [Desc("Adds metadata for the AI bots.")] public class SupportPowerDecision { [Desc("What is the minimum attractiveness we will use this power for?")] public readonly int MinimumAttractiveness = 1; [Desc("What support power does this decision apply to?")] public readonly string OrderName = "AirstrikePowerInfoOrder"; [Desc("What is the coarse scan radius of this power?","For finding the general target area, before doing a detail scan","Should be 10 or more to avoid lag")] public readonly int CoarseScanRadius = 20; [Desc("What is the fine scan radius of this power?", "For doing a detailed scan in the general target area.", "Minimum is 1")] public readonly int FineScanRadius = 2; [FieldLoader.LoadUsing("LoadConsiderations")] [Desc("The decisions associated with this power")] readonly List Considerations = new List(); [Desc("Minimum ticks to wait until next Decision scan attempt.")] public readonly int MinimumScanTimeInterval = 250; [Desc("Maximum ticks to wait until next Decision scan attempt.")] public readonly int MaximumScanTimeInterval = 262; public SupportPowerDecision(MiniYaml yaml) { FieldLoader.Load(this, yaml); } static object LoadConsiderations(MiniYaml yaml) { var ret = new List(); foreach (var d in yaml.Nodes) if (d.Key.Split('@')[0] == "Consideration") ret.Add(new Consideration(d.Value)); return ret; } ///Evaluates the attractiveness of a position according to all considerations public int GetAttractiveness(WPos pos, Player firedBy) { var answer = 0; var world = firedBy.World; var targetTile = world.Map.CellContaining(pos); if (!world.Map.Contains(targetTile)) return 0; foreach (var consideration in Considerations) { var radiusToUse = new WRange(consideration.CheckRadius.Range); var checkActors = world.FindActorsInCircle(pos, radiusToUse); foreach (var scrutinized in checkActors) answer += consideration.GetAttractiveness(scrutinized, firedBy.Stances[scrutinized.Owner]); } return answer; } ///Evaluates the attractiveness of a group of actors according to all considerations public int GetAttractiveness(IEnumerable actors, Player firedBy) { var answer = 0; foreach (var consideration in Considerations) foreach (var scrutinized in actors) answer += consideration.GetAttractiveness(scrutinized, firedBy.Stances[scrutinized.Owner]); return answer; } public int GetNextScanTime(HackyAI ai) { return ai.random.Next(MinimumScanTimeInterval, MaximumScanTimeInterval); } ///Makes up part of a decision, describing how to evaluate a target. class Consideration { public enum DecisionMetric { Health, Value, None }; [Desc("Against whom should this power be used?", "Allowed keywords: Ally, Neutral, Enemy")] public readonly Stance Against = Stance.Enemy; [Desc("What types should the desired targets of this power be?")] public readonly string[] Types = { "Air", "Ground", "Water" }; [Desc("How attractive are these types of targets?")] public readonly int Attractiveness = 100; [Desc("Weight the target attractiveness by this property", "Allowed keywords: Health, Value, None")] public readonly DecisionMetric TargetMetric = DecisionMetric.None; [Desc("What is the check radius of this decision?")] public readonly WRange CheckRadius = WRange.FromCells(5); public Consideration(MiniYaml yaml) { FieldLoader.Load(this, yaml); } ///Evaluates a single actor according to the rules defined in this consideration public int GetAttractiveness(Actor a, Stance stance) { if (stance != Against) return 0; if (a == null) return 0; var targetable = a.TraitOrDefault(); if (targetable == null) return 0; if (Types.Intersect(targetable.TargetTypes).Any()) { switch (TargetMetric) { case DecisionMetric.Value: var valueInfo = a.Info.Traits.GetOrDefault(); return (valueInfo != null) ? valueInfo.Cost * Attractiveness : 0; case DecisionMetric.Health: var health = a.TraitOrDefault(); return (health != null) ? (health.HP / health.MaxHP) * Attractiveness : 0; default: return Attractiveness; } } return 0; } } } }