diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index 10a4e47150..6e48aa3145 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -60,6 +60,7 @@ + diff --git a/OpenRA.Mods.RA/ProximityCapturable.cs b/OpenRA.Mods.RA/ProximityCapturable.cs new file mode 100644 index 0000000000..2a6ff39b31 --- /dev/null +++ b/OpenRA.Mods.RA/ProximityCapturable.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using OpenRA.Effects; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA +{ + public class ProximityCapturableInfo : ITraitInfo + { + public readonly bool Permanent = false; + public readonly int Range = 5; + public readonly bool MustBeClear = false; + + public object Create(ActorInitializer init) { return new ProximityCapturable(init.self, this); } + } + + public class ProximityCapturable : ITick + { + [Sync] + public Player Owner; + + [Sync] + public readonly Player OriginalOwner; + + public ProximityCapturableInfo Info; + + [Sync] + public int Range; + + [Sync] + public bool Permanent; + + [Sync] + public bool Captured = false; + + [Sync] + public bool MustBeClear = false; + + public ProximityCapturable(Actor self, ProximityCapturableInfo info) + { + Owner = self.Owner; + OriginalOwner = Owner; + Info = info; + Range = info.Range; + Permanent = info.Permanent; + MustBeClear = info.MustBeClear; + } + + public Player GetCurrentOwner() + { + if (!Captured) return OriginalOwner; + + return Owner ?? OriginalOwner; + } + + public void Tick(Actor self) + { + if (Captured && Permanent) return; // Permanent capture + + var playersNear = CountPlayersNear(self, OriginalOwner, Range); + + if (!Captured) + { + if (MustBeClear && playersNear != 1) return; + + var captor = GetInRange(self, OriginalOwner, Range); + + if (captor != null) ChangeOwnership(self, captor, OriginalOwner); + + return; + } + + // if the area must be clear, and there is more than 1 player nearby => return ownership to default + if (MustBeClear && playersNear != 1) + { + // Revert Ownership + ChangeOwnership(self, GetCurrentOwner(), OriginalOwner); + } + // See if the 'temporary' owner still is in range + else if (!IsStillInRange(self, self.Owner, Range)) + { + // no.. So find a new one + var captor = GetInRange(self, OriginalOwner, Range); + + if (captor != null) // got one + { + ChangeOwnership(self, captor, GetCurrentOwner()); + return; + } + + // Revert Ownership otherwise + ChangeOwnership(self, GetCurrentOwner(), OriginalOwner); + } + } + + private void ChangeOwnership(Actor self, Player previousOwner, Player originalOwner) + { + self.World.AddFrameEndTask(w => + { + if (self.Destroyed) return; + + // momentarily remove from world so the ownership queries don't get confused + w.Remove(self); + self.Owner = originalOwner; + w.Add(self); + + if (self.Owner == self.World.LocalPlayer) + w.Add(new FlashTarget(self)); + + Captured = false; + Owner = null; + + foreach (var t in self.TraitsImplementing()) + t.OnCapture(self, self, previousOwner, self.Owner); + }); + } + + private void ChangeOwnership(Actor self, Actor captor, Player previousOwner) + { + self.World.AddFrameEndTask(w => + { + if (self.Destroyed || (captor.Destroyed || !captor.IsInWorld)) return; + + // 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)); + + Captured = true; + Owner = captor.Owner; + + foreach (var t in self.TraitsImplementing()) + t.OnCapture(self, captor, previousOwner, self.Owner); + }); + } + + // TODO exclude other NeutralActor that arent permanent + public static bool IsStillInRange(Actor self, Player currentOwner, int range) + { + var unitsInRange = self.World.FindUnitsInCircle(self.CenterLocation, Game.CellSize * range); + + return unitsInRange + .Where(a => a.Owner != null && a.Owner == currentOwner && !a.Destroyed && a.IsInWorld && a != self) + .Any(); + } + + // TODO exclude other NeutralActor that arent permanent + public static Actor GetInRange(Actor self, Player originalOwner, int range) + { + var unitsInRange = self.World.FindUnitsInCircle(self.CenterLocation, Game.CellSize * range); + + return unitsInRange + .Where(a => a.Owner != null && a.Owner != originalOwner && !a.Destroyed && a.IsInWorld && a != self) + .Where(a => !a.Owner.PlayerRef.OwnsWorld) + .Where(a => !a.Owner.PlayerRef.NonCombatant) + .OrderBy(a => (a.CenterLocation - self.CenterLocation).LengthSquared) + .FirstOrDefault(); + } + + public static int CountPlayersNear(Actor self, Player ignoreMe, int range) + { + var unitsInRange = self.World.FindUnitsInCircle(self.CenterLocation, Game.CellSize * range); + + return unitsInRange + .Where(a => a.Owner != null && a.Owner != ignoreMe && !a.Destroyed && a.IsInWorld && a != self) + .Where(a => !a.Owner.PlayerRef.OwnsWorld) + .Where(a => !a.Owner.PlayerRef.NonCombatant) + .Select(a => a.Owner) + .Distinct() + .Count(); + } + } +}