Merge Mods.RA into Mods.Cnc
This commit is contained in:
60
OpenRA.Mods.Cnc/Activities/Infiltrate.cs
Normal file
60
OpenRA.Mods.Cnc/Activities/Infiltrate.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Activities
|
||||
{
|
||||
class Infiltrate : Enter
|
||||
{
|
||||
readonly Actor target;
|
||||
readonly Stance validStances;
|
||||
readonly Cloak cloak;
|
||||
readonly string notification;
|
||||
readonly int experience;
|
||||
|
||||
public Infiltrate(Actor self, Actor target, EnterBehaviour enterBehaviour, Stance validStances, string notification, int experience)
|
||||
: base(self, target, enterBehaviour)
|
||||
{
|
||||
this.target = target;
|
||||
this.validStances = validStances;
|
||||
this.notification = notification;
|
||||
this.experience = experience;
|
||||
cloak = self.TraitOrDefault<Cloak>();
|
||||
}
|
||||
|
||||
protected override void OnInside(Actor self)
|
||||
{
|
||||
if (target.IsDead)
|
||||
return;
|
||||
|
||||
var stance = self.Owner.Stances[target.Owner];
|
||||
if (!validStances.HasStance(stance))
|
||||
return;
|
||||
|
||||
if (cloak != null && cloak.Info.UncloakOn.HasFlag(UncloakType.Infiltrate))
|
||||
cloak.Uncloak();
|
||||
|
||||
foreach (var t in target.TraitsImplementing<INotifyInfiltrated>())
|
||||
t.Infiltrated(target, self);
|
||||
|
||||
var exp = self.Owner.PlayerActor.TraitOrDefault<PlayerExperience>();
|
||||
if (exp != null)
|
||||
exp.GiveExperience(experience);
|
||||
|
||||
if (!string.IsNullOrEmpty(notification))
|
||||
Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech",
|
||||
notification, self.Owner.Faction.InternalName);
|
||||
}
|
||||
}
|
||||
}
|
||||
109
OpenRA.Mods.Cnc/Activities/LayMines.cs
Normal file
109
OpenRA.Mods.Cnc/Activities/LayMines.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Cnc.Traits;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Activities
|
||||
{
|
||||
// Assumes you have Minelayer on that unit
|
||||
public class LayMines : Activity
|
||||
{
|
||||
readonly Minelayer minelayer;
|
||||
readonly MinelayerInfo info;
|
||||
readonly AmmoPool[] ammoPools;
|
||||
readonly IMove movement;
|
||||
readonly HashSet<string> rearmBuildings;
|
||||
|
||||
public LayMines(Actor self)
|
||||
{
|
||||
minelayer = self.TraitOrDefault<Minelayer>();
|
||||
info = self.Info.TraitInfo<MinelayerInfo>();
|
||||
ammoPools = self.TraitsImplementing<AmmoPool>().ToArray();
|
||||
movement = self.Trait<IMove>();
|
||||
rearmBuildings = info.RearmBuildings;
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (IsCanceled)
|
||||
return NextActivity;
|
||||
|
||||
if (ammoPools != null && ammoPools.Any(p => p.Info.Name == info.AmmoPoolName && !p.HasAmmo()))
|
||||
{
|
||||
// Rearm (and possibly repair) at rearm building, then back out here to refill the minefield some more
|
||||
var rearmTarget = self.World.Actors.Where(a => self.Owner.Stances[a.Owner] == Stance.Ally
|
||||
&& rearmBuildings.Contains(a.Info.Name))
|
||||
.ClosestTo(self);
|
||||
|
||||
if (rearmTarget == null)
|
||||
return new Wait(20);
|
||||
|
||||
return ActivityUtils.SequenceActivities(
|
||||
new MoveAdjacentTo(self, Target.FromActor(rearmTarget)),
|
||||
movement.MoveTo(self.World.Map.CellContaining(rearmTarget.CenterPosition), rearmTarget),
|
||||
new Rearm(self),
|
||||
new Repair(self, rearmTarget),
|
||||
this);
|
||||
}
|
||||
|
||||
if (minelayer.Minefield.Contains(self.Location) && ShouldLayMine(self, self.Location))
|
||||
{
|
||||
LayMine(self);
|
||||
return ActivityUtils.SequenceActivities(new Wait(20), this); // A little wait after placing each mine, for show
|
||||
}
|
||||
|
||||
if (minelayer.Minefield.Length > 0)
|
||||
{
|
||||
// Don't get stuck forever here
|
||||
for (var n = 0; n < 20; n++)
|
||||
{
|
||||
var p = minelayer.Minefield.Random(self.World.SharedRandom);
|
||||
if (ShouldLayMine(self, p))
|
||||
return ActivityUtils.SequenceActivities(movement.MoveTo(p, 0), this);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Return somewhere likely to be safe (near rearm building) so we're not sitting out in the minefield.
|
||||
return new Wait(20); // nothing to do here
|
||||
}
|
||||
|
||||
static bool ShouldLayMine(Actor self, CPos p)
|
||||
{
|
||||
// If there is no unit (other than me) here, we want to place a mine here
|
||||
return self.World.ActorMap.GetActorsAt(p).All(a => a == self);
|
||||
}
|
||||
|
||||
void LayMine(Actor self)
|
||||
{
|
||||
if (ammoPools != null)
|
||||
{
|
||||
var pool = ammoPools.FirstOrDefault(x => x.Info.Name == info.AmmoPoolName);
|
||||
if (pool == null)
|
||||
return;
|
||||
pool.TakeAmmo();
|
||||
}
|
||||
|
||||
self.World.AddFrameEndTask(
|
||||
w => w.CreateActor(info.Mine, new TypeDictionary
|
||||
{
|
||||
new LocationInit(self.Location),
|
||||
new OwnerInit(self.Owner),
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
78
OpenRA.Mods.Cnc/Activities/Leap.cs
Normal file
78
OpenRA.Mods.Cnc/Activities/Leap.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Mods.Common.Traits.Render;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Activities
|
||||
{
|
||||
class Leap : Activity
|
||||
{
|
||||
readonly Mobile mobile;
|
||||
readonly WeaponInfo weapon;
|
||||
readonly int length;
|
||||
|
||||
WPos from;
|
||||
WPos to;
|
||||
int ticks;
|
||||
WAngle angle;
|
||||
|
||||
public Leap(Actor self, Actor target, Armament a, WDist speed, WAngle angle)
|
||||
{
|
||||
var targetMobile = target.TraitOrDefault<Mobile>();
|
||||
if (targetMobile == null)
|
||||
throw new InvalidOperationException("Leap requires a target actor with the Mobile trait");
|
||||
|
||||
this.weapon = a.Weapon;
|
||||
this.angle = angle;
|
||||
mobile = self.Trait<Mobile>();
|
||||
mobile.SetLocation(mobile.FromCell, mobile.FromSubCell, targetMobile.FromCell, targetMobile.FromSubCell);
|
||||
mobile.IsMoving = true;
|
||||
|
||||
from = self.CenterPosition;
|
||||
to = self.World.Map.CenterOfSubCell(targetMobile.FromCell, targetMobile.FromSubCell);
|
||||
length = Math.Max((to - from).Length / speed.Length, 1);
|
||||
|
||||
// HACK: why isn't this using the interface?
|
||||
self.Trait<WithInfantryBody>().Attacking(self, Target.FromActor(target), a);
|
||||
|
||||
if (weapon.Report != null && weapon.Report.Any())
|
||||
Game.Sound.Play(SoundType.World, weapon.Report.Random(self.World.SharedRandom), self.CenterPosition);
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (ticks == 0 && IsCanceled)
|
||||
return NextActivity;
|
||||
|
||||
mobile.SetVisualPosition(self, WPos.LerpQuadratic(from, to, angle, ++ticks, length));
|
||||
if (ticks >= length)
|
||||
{
|
||||
mobile.SetLocation(mobile.ToCell, mobile.ToSubCell, mobile.ToCell, mobile.ToSubCell);
|
||||
mobile.FinishedMoving(self);
|
||||
mobile.IsMoving = false;
|
||||
|
||||
self.World.ActorMap.GetActorsAt(mobile.ToCell, mobile.ToSubCell)
|
||||
.Except(new[] { self }).Where(t => weapon.IsValidAgainst(t, self))
|
||||
.Do(t => t.Kill(self));
|
||||
|
||||
return NextActivity;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
130
OpenRA.Mods.Cnc/Activities/Teleport.cs
Normal file
130
OpenRA.Mods.Cnc/Activities/Teleport.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2017 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Cnc.Traits;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Mods.Common.Traits.Render;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Activities
|
||||
{
|
||||
public interface IPreventsTeleport { bool PreventsTeleport(Actor self); }
|
||||
|
||||
public class Teleport : Activity
|
||||
{
|
||||
readonly Actor teleporter;
|
||||
readonly int? maximumDistance;
|
||||
CPos destination;
|
||||
bool killCargo;
|
||||
bool screenFlash;
|
||||
string sound;
|
||||
|
||||
public Teleport(Actor teleporter, CPos destination, int? maximumDistance, bool killCargo, bool screenFlash, string sound)
|
||||
{
|
||||
var max = teleporter.World.Map.Grid.MaximumTileSearchRange;
|
||||
if (maximumDistance > max)
|
||||
throw new InvalidOperationException("Teleport distance cannot exceed the value of MaximumTileSearchRange ({0}).".F(max));
|
||||
|
||||
this.teleporter = teleporter;
|
||||
this.destination = destination;
|
||||
this.maximumDistance = maximumDistance;
|
||||
this.killCargo = killCargo;
|
||||
this.screenFlash = screenFlash;
|
||||
this.sound = sound;
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
var pc = self.TraitOrDefault<PortableChrono>();
|
||||
if (teleporter == self && pc != null && !pc.CanTeleport)
|
||||
return NextActivity;
|
||||
|
||||
foreach (var condition in self.TraitsImplementing<IPreventsTeleport>())
|
||||
if (condition.PreventsTeleport(self))
|
||||
return NextActivity;
|
||||
|
||||
var bestCell = ChooseBestDestinationCell(self, destination);
|
||||
if (bestCell == null)
|
||||
return NextActivity;
|
||||
|
||||
destination = bestCell.Value;
|
||||
|
||||
Game.Sound.Play(SoundType.World, sound, self.CenterPosition);
|
||||
Game.Sound.Play(SoundType.World, sound, self.World.Map.CenterOfCell(destination));
|
||||
|
||||
self.Trait<IPositionable>().SetPosition(self, destination);
|
||||
self.Generation++;
|
||||
|
||||
if (killCargo)
|
||||
{
|
||||
var cargo = self.TraitOrDefault<Cargo>();
|
||||
if (cargo != null && teleporter != null)
|
||||
{
|
||||
while (!cargo.IsEmpty(self))
|
||||
{
|
||||
var a = cargo.Unload(self);
|
||||
|
||||
// Kill all the units that are unloaded into the void
|
||||
// Kill() handles kill and death statistics
|
||||
a.Kill(teleporter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Consume teleport charges if this wasn't triggered via chronosphere
|
||||
if (teleporter == self && pc != null)
|
||||
pc.ResetChargeTime();
|
||||
|
||||
// Trigger screen desaturate effect
|
||||
if (screenFlash)
|
||||
foreach (var a in self.World.ActorsWithTrait<ChronoshiftPaletteEffect>())
|
||||
a.Trait.Enable();
|
||||
|
||||
if (teleporter != null && self != teleporter && !teleporter.Disposed)
|
||||
{
|
||||
var building = teleporter.TraitOrDefault<WithSpriteBody>();
|
||||
if (building != null && building.DefaultAnimation.HasSequence("active"))
|
||||
building.PlayCustomAnimation(teleporter, "active");
|
||||
}
|
||||
|
||||
return NextActivity;
|
||||
}
|
||||
|
||||
CPos? ChooseBestDestinationCell(Actor self, CPos destination)
|
||||
{
|
||||
if (teleporter == null)
|
||||
return null;
|
||||
|
||||
var restrictTo = maximumDistance == null ? null : self.World.Map.FindTilesInCircle(self.Location, maximumDistance.Value);
|
||||
|
||||
if (maximumDistance != null)
|
||||
destination = restrictTo.MinBy(x => (x - destination).LengthSquared);
|
||||
|
||||
var pos = self.Trait<IPositionable>();
|
||||
if (pos.CanEnterCell(destination) && teleporter.Owner.Shroud.IsExplored(destination))
|
||||
return destination;
|
||||
|
||||
var max = maximumDistance != null ? maximumDistance.Value : teleporter.World.Map.Grid.MaximumTileSearchRange;
|
||||
foreach (var tile in self.World.Map.FindTilesInCircle(destination, max))
|
||||
{
|
||||
if (teleporter.Owner.Shroud.IsExplored(tile)
|
||||
&& (restrictTo == null || (restrictTo != null && restrictTo.Contains(tile)))
|
||||
&& pos.CanEnterCell(tile))
|
||||
return tile;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user