#region Copyright & License Information /* * Copyright 2007-2015 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.Collections.Generic; using System.Linq; using OpenRA.Effects; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { [Desc("Actor can be captured by units in a specified proximity.")] public class ProximityCapturableInfo : ITraitInfo { public readonly bool Permanent = false; public readonly WDist Range = WDist.FromCells(5); public readonly bool MustBeClear = false; public readonly HashSet CaptorTypes = new HashSet { "Vehicle", "Tank", "Infantry" }; public object Create(ActorInitializer init) { return new ProximityCapturable(init.Self, this); } } public class ProximityCapturable : ITick { public readonly Player OriginalOwner; public bool Captured { get { return Self.Owner != OriginalOwner; } } public ProximityCapturableInfo Info; public Actor Self; public ProximityCapturable(Actor self, ProximityCapturableInfo info) { Info = info; Self = self; OriginalOwner = self.Owner; } public void Tick(Actor self) { if (Captured && Info.Permanent) return; // Permanent capture if (!Captured) { var captor = GetInRange(self); if (captor != null) if (!Info.MustBeClear || IsClear(self, captor.Owner, OriginalOwner)) ChangeOwnership(self, captor); return; } // if the area must be clear, and there is more than 1 player nearby => return ownership to default if (Info.MustBeClear && !IsClear(self, self.Owner, OriginalOwner)) { // Revert Ownership ChangeOwnership(self, OriginalOwner.PlayerActor); return; } // See if the 'temporary' owner still is in range if (!IsStillInRange(self)) { // no.. So find a new one var captor = GetInRange(self); // got one if (captor != null) { ChangeOwnership(self, captor); return; } // Revert Ownership otherwise ChangeOwnership(self, OriginalOwner.PlayerActor); } } static void ChangeOwnership(Actor self, Actor captor) { self.World.AddFrameEndTask(w => { if (self.Disposed || captor.Disposed) return; var previousOwner = self.Owner; // momentarily remove from world so the ownership queries don't get confused w.Remove(self); self.Owner = captor.Owner; w.Add(self); if (self.Owner == self.World.LocalPlayer) w.Add(new FlashTarget(self)); foreach (var t in self.TraitsImplementing()) t.OnCapture(self, captor, previousOwner, self.Owner); }); } bool CanBeCapturedBy(Actor a) { var pc = a.Info.TraitInfoOrDefault(); return pc != null && pc.Types.Overlaps(Info.CaptorTypes); } IEnumerable UnitsInRange() { return Self.World.FindActorsInCircle(Self.CenterPosition, Info.Range) .Where(a => a.IsInWorld && a != Self && !a.Disposed && !a.Owner.NonCombatant); } bool IsClear(Actor self, Player currentOwner, Player originalOwner) { return UnitsInRange() .All(a => a.Owner == originalOwner || a.Owner == currentOwner || WorldUtils.AreMutualAllies(a.Owner, currentOwner) || !CanBeCapturedBy(a)); } // TODO exclude other NeutralActor that aren't permanent bool IsStillInRange(Actor self) { return UnitsInRange().Any(a => a.Owner == self.Owner && CanBeCapturedBy(a)); } IEnumerable CaptorsInRange(Actor self) { return UnitsInRange() .Where(a => a.Owner != OriginalOwner && CanBeCapturedBy(a)); } // TODO exclude other NeutralActor that aren't permanent Actor GetInRange(Actor self) { return CaptorsInRange(self).ClosestTo(self); } int CountPlayersNear(Actor self, Player ignoreMe) { return CaptorsInRange(self).Select(a => a.Owner).Where(p => p != ignoreMe) .Distinct().Count(); } } }