/// <summary> /// Adds to a fleet without refreshing the GUI. /// </summary> /// <param name="vehicle"></param> /// <param name="fleet"></param> private void DoAddToFleet(IMobileSpaceObject vehicle, Fleet fleet) { if (vehicle == null) { return; } if (fleet == null) { return; } JoinFleetCommand cmd; if (!newFleets.Contains(fleet)) { // fleet already exists, we can add to it cmd = new JoinFleetCommand(vehicle, fleet); } else { // fleet is new, we need to reference it by its command cmd = new JoinFleetCommand(vehicle, newCommands.OfType <CreateFleetCommand>().Single(c => c.Fleet == fleet)); } newCommands.Add(cmd); }
public IEnumerable <LogMessage> GetErrors(IMobileSpaceObject executor, IRecyclable target) { if (target == null) { yield return(new GenericLogMessage("A scrap order was issued for a nonexistent target.")); yield break; } if (target.IsDisposed) { yield return(target.CreateLogMessage($"{target} cannot be scrapped because it is already destroyed.")); } if (target.RecycleContainer != executor) { yield return(target.CreateLogMessage(target + " cannot be scrapped by " + executor + " because " + target + " does not belong to " + executor + ".")); } if ((target is Ship || target is Base) && !executor.Sector.SpaceObjects.Any(sobj => sobj.Owner == executor.Owner && sobj.HasAbility("Space Yard"))) { yield return(target.CreateLogMessage(target + " cannot be scrapped at " + executor.Sector + " because there is no space yard present in that sector.")); } if ((target is IUnit) && !executor.Sector.SpaceObjects.Any(sobj => sobj.Owner == executor.Owner && (sobj is Planet || sobj.HasAbility("Space Yard")))) { yield return(target.CreateLogMessage(target + " cannot be scrapped at " + executor.Sector + " because there is no space yard or colony present in that sector.")); } }
/// <summary> /// Adds a vehicle or fleet to a fleet. /// </summary> /// <param name="vehicle"></param> /// <param name="fleet"></param> private void AddToFleet(IMobileSpaceObject vehicle, Fleet fleet) { DoAddToFleet(vehicle, fleet); BindVehicles(); BindFleets(fleet); changed = true; }
/// <summary> /// Recursively builds a tree node for a fleet or vehicle. /// </summary> /// <param name="tree">Should be a TreeView or TreeNode.</param> /// <param name="sobj">The fleet or vehicle.</param> private void BuildVehicleTreeNode(object tree, IMobileSpaceObject sobj) { TreeNode node; string CalculateSupplyStatus(int remaining, int storage) { if (remaining == 0) { return("Supplies Empty"); } if (remaining < storage * 0.5) { return("Low Supplies"); } return(""); } string CalculateStatus(IMobileSpaceObject sobj2) { var s = "Speed " + sobj2.StrategicSpeed; var sup = CalculateSupplyStatus(sobj2.SupplyRemaining, sobj2.SupplyStorage); if (sup == null) { return(s); } else { return($"{s}, {sup}"); } } var namestr = $"{sobj.Name}: {CalculateStatus(sobj)}"; if (tree is TreeView) { node = ((TreeView)tree).AddItemWithImage(namestr, sobj, sobj.Icon); } else if (tree is TreeNode) { node = ((TreeView)tree).AddItemWithImage(namestr, sobj, sobj.Icon); } else { throw new ArgumentException("Tree for BuildVehicleTreeNode must be a TreeView or TreeNode.", "tree"); } if (sobj is Fleet) { foreach (var sobj2 in ((Fleet)sobj).Vehicles) { BuildVehicleTreeNode(node, sobj2); } } }
/// <summary> /// Navigation for space objects. /// </summary> /// <param name="me"></param> /// <param name="start"></param> /// <param name="end"></param> /// <param name="avoidEnemies"></param> /// <returns></returns> public static IEnumerable <Sector> Pathfind(IMobileSpaceObject me, Sector start, Sector end, bool avoidEnemies, bool avoidDamagingSectors, IDictionary <PathfinderNode <Sector>, ISet <PathfinderNode <Sector> > > map) { bool cacheEnabled = Galaxy.Current.IsAbilityCacheEnabled; if (!cacheEnabled) { Galaxy.Current.EnableAbilityCache(); } if (end == null || end.StarSystem == null || start == end) { return(Enumerable.Empty <Sector>()); } if (me != null && me.StrategicSpeed < 1) { return(Enumerable.Empty <Sector>()); } if (map == null) { map = CreateDijkstraMap(me, start, end, avoidEnemies, avoidDamagingSectors); } if (!map.Any()) { return(Enumerable.Empty <Sector>()); // nowhere to go! } var nodes = new List <PathfinderNode <Sector> >(); PathfinderNode <Sector> node; if (map.Keys.Any(n => n.Location == end)) { // can reach it node = map.Keys.Where(n => n.Location == end).OrderBy(n => n.Cost).First(); } else { // can't reach it; get as close as possible var dist = map.Keys.Min(n => n.MinimumCostRemaining); node = map.Keys.First(n => n.MinimumCostRemaining == dist); } while (node != null) { nodes.Add(node); node = node.PreviousNode; } if (!cacheEnabled) { Galaxy.Current.DisableAbilityCache(); } return(nodes.Select(n => n.Location).Where(s => s != start).Reverse()); }
private TreeNode CreateNode(TreeView parent, IMobileSpaceObject v) { var node = parent.AddItemWithImage(v.Name, v, v.Icon); if (v is Fleet) { foreach (var sub in ((Fleet)v).Vehicles) { CreateNode(node, sub); } } return(node); }
private TreeNode CreateNode(TreeNode parent, IMobileSpaceObject v) { TreeNode node; node = parent.AddItemWithImage(v.Name + ": " + CalculateStatus(v), v, v.Icon); if (v is Fleet) { foreach (var sub in ((Fleet)v).Vehicles) { CreateNode(node, sub); } } return(node); }
private string CalculateStatus(IMobileSpaceObject sobj2) { var s = "Speed " + sobj2.StrategicSpeed; var sup = CalculateSupplyStatus(sobj2.SupplyRemaining, sobj2.SupplyStorage); if (sup == null) { return(s); } else { return($"{s}, {sup}"); } }
/// <summary> /// Finds the path for executing this order. /// </summary> /// <param name="sobj">The space object executing the order.</param> /// <returns></returns> public override IEnumerable <Sector> Pathfind(IMobileSpaceObject me, Sector start) { if (Target is IMobileSpaceObject) { if (me.CanWarp && !Target.CanWarp) { // warping via any warp point that leads outside the system should be safe, so prioritize those! var sys = me.FindStarSystem(); var paths = sys.FindSpaceObjects <WarpPoint>() .Where(wp => wp.TargetStarSystemLocation == null || wp.TargetStarSystemLocation.Item != sys) .Select(wp => new { WarpPoint = wp, Path = Pathfinder.Pathfind(me, start, wp.Sector, AvoidEnemies, true, me.DijkstraMap) }); if (paths.Any()) { // found a warp point to flee to! var shortest = paths.WithMin(path => path.Path.Count()).PickRandom(); return(shortest.Path.Concat(new Sector[] { shortest.WarpPoint.Target })); } } // see how he can reach us, and go somewhere away from him (that would take longer for him to get to than var dijkstraMap = Pathfinder.CreateDijkstraMap((IMobileSpaceObject)Target, Target.Sector, me.FindSector(), false, true); var canMoveTo = Pathfinder.GetPossibleMoves(me.Sector, me.CanWarp, me.Owner); var goodMoves = canMoveTo.Where(s => !dijkstraMap.Values.SelectMany(set => set).Any(n => n.Location == s)); if (goodMoves.Any()) { // just go there and recompute the path next time we can move - the enemy may have moved too return(new Sector[] { goodMoves.PickRandom() }); } else { // trapped... return(Enumerable.Empty <Sector>()); } } else { // target is immobile! no need to flee, unless it's in the same sector if (Target.Sector == me.FindSector()) { // don't need to go through warp points to evade it, the warp points might be one way! var moves = Pathfinder.GetPossibleMoves(me.Sector, false, me.Owner); return(new Sector[] { moves.PickRandom() }); } else { return(Enumerable.Empty <Sector>()); } } }
public ActivateAbilityForm(IMobileSpaceObject sobj) { InitializeComponent(); this.sobj = sobj; var abils = sobj.ActivatableAbilities().Select(kvp => new { Ability = kvp.Key, Source = kvp.Value, IsDestroyedOnUse = kvp.Value.HasAbility("Destroyed On Use"), // TODO - "Space Object Destroyed On Use" ability }); gridAbilities.DataSource = abils; }
private TreeNode FindNode(TreeNode parent, IMobileSpaceObject v) { foreach (TreeNode n in parent.Nodes) { if (n.Tag == v) { return(n); } var sub = FindNode(n, v); if (sub != null) { return(sub); } } return(null); }
/// <summary> /// Removes a vehicle or fleet from its fleet. /// </summary> /// <param name="sobj"></param> private void RemoveFromFleet(IMobileSpaceObject vehicle) { if (vehicle == null) { return; } if (vehicle.Container == null) { return; } var cmd = new LeaveFleetCommand(vehicle); newCommands.Add(cmd); BindVehicles(vehicle); var node = treeFleets.GetAllNodes().Single(x => x.Tag == vehicle); node.Remove(); changed = true; }
protected abstract bool AreWeThereYet(IMobileSpaceObject me);
/// <summary> /// Finds the path for executing this order. /// </summary> /// <param name="sobj">The space object executing the order.</param> /// <param name="start">The start location (need not be the current location, in case there are prior orders queued).</param> /// <returns></returns> public abstract IEnumerable<Sector> Pathfind(IMobileSpaceObject me, Sector start);
public IDictionary<PathfinderNode<Sector>, ISet<PathfinderNode<Sector>>> CreateDijkstraMap(IMobileSpaceObject me, Sector start) { return Pathfinder.CreateDijkstraMap(me, start, Destination, AvoidEnemies, true); }
/// <summary> /// Finds the path for executing this order. /// </summary> /// <param name="sobj">The space object executing the order.</param> /// <returns></returns> public IEnumerable <Sector> Pathfind(IMobileSpaceObject me, Sector start) { return(Pathfinder.Pathfind(me, start, Destination, AvoidEnemies, true, me.DijkstraMap)); }
protected override bool AreWeThereYet(IMobileSpaceObject me) { // gotta keep on running... return(Target == null || Target.IsDisposed); }
public static IDictionary <PathfinderNode <Sector>, ISet <PathfinderNode <Sector> > > CreateDijkstraMap(IMobileSpaceObject me, Sector start, Sector end, bool avoidEnemies, bool avoidDamagingSectors) { // step 2a (do it here so we can return map if start or end is null): empty map with nodes, their costs, and previous-node references var map = new Dictionary <PathfinderNode <Sector>, ISet <PathfinderNode <Sector> > >(); // if we are nowhere or we're going nowhere, that's impossible! if (start == null || end == null) { return(map); } var startSys = start.StarSystem; // pathfind! // step 1: empty priority queue with cost to reach each node var queue = new Dictionary <int, ISet <PathfinderNode <Sector> > >(); // step 2b: empty set of previously visited nodes var visited = new HashSet <Sector>(); // step 3: add start node and cost queue.Add(0, new HashSet <PathfinderNode <Sector> >()); queue[0].Add(new PathfinderNode <Sector>(start, 0, null, EstimateDistance(start, end, me == null ? null : me.Owner))); // step 4: quit if there are no nodes (all paths exhausted without finding goal) bool success = false; while (queue.SelectMany(kvp => kvp.Value).Any() && !success) { // step 5: take lowest cost node out of queue // TODO - also prefer straight line movement to diagonal? var minCost = queue.Keys.Min(); while (!queue[minCost].Any()) { queue.Remove(minCost); minCost = queue.Keys.Min(); } var node = queue[minCost].First(); queue[minCost].Remove(node); map.Add(node, new HashSet <PathfinderNode <Sector> >()); // step 6: if node is the goal, stop after it's done - success! if (node.Location == end) { success = true; } // step 7: check possible moves var moves = GetPossibleMoves(node.Location, me == null ? true : me.CanWarp, me == null ? null : me.Owner); // step 7a: remove blocked points (aka calculate cost) if (avoidEnemies) { // avoid enemies, except at the destination moves = moves.Where(m => m == null || m == end || !m.SpaceObjects.Where(sobj => sobj.CheckVisibility(me.Owner) >= Visibility.Visible || sobj.FindMemory(me.Owner)?.Sector == m).OfType <ICombatant>().Any(sobj => sobj.IsHostileTo(me == null ? null : me.Owner))); } if (avoidDamagingSectors) { // don't avoid the destination, even if it is a damaging sector moves = moves.Where(m => m == end || m == null || !m.SpaceObjects.Where(sobj => sobj.CheckVisibility(me.Owner) >= Visibility.Visible || sobj.FindMemory(me.Owner)?.Sector == m).Any(sobj => sobj.GetAbilityValue("Sector - Damage").ToInt() > 0)); } // step 7b: update priority queue Action <Sector> f = move => { // When we lock the queue, we do so because it is being checked and modified by other threads, // and we don't want them stepping on each other's toes. // e.g. thread 1 is checking for the existence of an item in the queue // while thread 2 is busy adding that same item! if (!visited.Contains(move)) { // didn't visit yet var newnode = new PathfinderNode <Sector>(move, node.Cost + 1, node, EstimateDistance(move, end, me == null ? null : me.Owner)); lock (queue) { if (!queue.ContainsKey(newnode.Cost)) { queue.Add(newnode.Cost, new HashSet <PathfinderNode <Sector> >()); } queue[newnode.Cost].Add(newnode); } if (!map.ContainsKey(node)) // don't need to lock map, we're only adding a node which should only get added once per run { map.Add(node, new HashSet <PathfinderNode <Sector> >()); } map[node].Add(newnode); visited.Add(move); } else { // already visited - but is it by a longer path? var moreCost = queue.Where(kvp => kvp.Key > node.Cost + 1).SelectMany(kvp => kvp.Value); var items = moreCost.Where(n => n.Location == move && n.Cost > node.Cost + 1); if (items.Any()) { PathfinderNode <Sector> newnode; foreach (var old in items.ToArray()) { lock (queue[old.Cost]) queue[old.Cost].Remove(old); map.Remove(old); } newnode = new PathfinderNode <Sector>(move, node.Cost + 1, node); lock (queue[newnode.Cost]) queue[newnode.Cost].Add(newnode); if (!map.ContainsKey(node)) // don't need to lock map, we're only adding a node which should only get added once per run { map.Add(node, new HashSet <PathfinderNode <Sector> >()); } map[node].Add(newnode); } } }; moves.SafeForeach(f); } return(map); }
protected override bool AreWeThereYet(IMobileSpaceObject me) { return(me.Sector == Destination); }
private void BindVehicles(IMobileSpaceObject selected = null) { var vehicles = new HashSet <IVehicle>(); // find vehicles in sector that are not fleets foreach (var v in sector.SpaceObjects.OfType <SpaceVehicle>().OwnedBy(Empire.Current)) { vehicles.Add(v); } // add vehicles that are being removed from fleets (but not fleets themselves, those go in the fleets tree) foreach (var v in newCommands.OfType <LeaveFleetCommand>().Select(c => c.Executor).OfType <SpaceVehicle>()) { vehicles.Add(v); } foreach (var v in newCommands.OfType <DisbandFleetCommand>().SelectMany(c => c.Executor.Vehicles.OfType <SpaceVehicle>())) { vehicles.Add(v); } // remove vehicles that are being added to fleets foreach (var v in newCommands.OfType <JoinFleetCommand>().Select(c => c.Executor).OfType <SpaceVehicle>()) { vehicles.Remove(v); } // make a tree of vehicles treeVehicles.Initialize(32); foreach (var vtGroup in vehicles.GroupBy(v => v.Design.VehicleType)) { var vtNode = treeVehicles.AddItemWithImage(vtGroup.Key.ToSpacedString(), vtGroup.Key, Pictures.GetVehicleTypeImage(Empire.Current.ShipsetPath, vtGroup.Key)); foreach (var roleGroup in vtGroup.GroupBy(v => v.Design.Role)) { var roleNode = vtNode.AddItemWithImage(roleGroup.Key, roleGroup.Key, Pictures.GetVehicleTypeImage(Empire.Current.ShipsetPath, vtGroup.Key)); foreach (var designGroup in roleGroup.GroupBy(v => v.Design)) { var designNode = roleNode.AddItemWithImage(designGroup.Key.Name, designGroup.Key, designGroup.Key.Icon); foreach (var vehicle in designGroup) { TreeNode vehicleNode; if (vehicle is IMobileSpaceObject sobj) // yay pattern matching! :D { vehicleNode = designNode.AddItemWithImage(vehicle.Name + ": " + CalculateStatus(sobj), vehicle, vehicle.Icon); } else { vehicleNode = designNode.AddItemWithImage(vehicle.Name, vehicle, vehicle.Icon); } if (vehicle == selected) { treeVehicles.SelectedNode = vehicleNode; } } } } if (vtNode.Nodes.Count == 0) { vtNode.Remove(); } } // expand the treeeee! treeVehicles.ExpandAll(); }