Skip to content

Commit

Permalink
Refactor Leap attack logic
Browse files Browse the repository at this point in the history
  • Loading branch information
abcdefg30 authored and reaperrr committed Dec 29, 2018
1 parent fb37d1e commit 2b948c3
Show file tree
Hide file tree
Showing 9 changed files with 300 additions and 63 deletions.
122 changes: 85 additions & 37 deletions OpenRA.Mods.Cnc/Activities/Leap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,72 +10,120 @@
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Activities;
using OpenRA.GameRules;
using OpenRA.Mods.Cnc.Traits;
using OpenRA.Mods.Common.Traits;
using OpenRA.Mods.Common.Traits.Render;
using OpenRA.Primitives;
using OpenRA.Traits;

namespace OpenRA.Mods.Cnc.Activities
{
class Leap : Activity
public class Leap : Activity
{
readonly Mobile mobile;
readonly WeaponInfo weapon;
readonly WPos destination, origin;
readonly CPos destinationCell;
readonly SubCell destinationSubCell = SubCell.Any;
readonly int length;
readonly AttackLeap attack;
readonly EdibleByLeap edible;
readonly Target target;

WPos from;
WPos to;
int ticks;
WAngle angle;
BitSet<DamageType> damageTypes;
bool canceled = false;
bool jumpComplete = false;
int ticks = 0;

public Leap(Actor self, Actor target, Armament a, WDist speed, WAngle angle, BitSet<DamageType> damageTypes)
public Leap(Actor self, Target target, Mobile mobile, Mobile targetMobile, int speed, AttackLeap attack, EdibleByLeap edible)
{
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;
this.damageTypes = damageTypes;
mobile = self.Trait<Mobile>();
mobile.SetLocation(mobile.FromCell, mobile.FromSubCell, targetMobile.FromCell, targetMobile.FromSubCell);
mobile.IsMoving = true;
this.mobile = mobile;
this.attack = attack;
this.target = target;
this.edible = edible;

destinationCell = target.Actor.Location;
if (targetMobile != null)
destinationSubCell = targetMobile.ToSubCell;

from = self.CenterPosition;
to = self.World.Map.CenterOfSubCell(targetMobile.FromCell, targetMobile.FromSubCell);
length = Math.Max((to - from).Length / speed.Length, 1);
origin = self.World.Map.CenterOfSubCell(self.Location, mobile.FromSubCell);
destination = self.World.Map.CenterOfSubCell(destinationCell, destinationSubCell);
length = Math.Max((origin - destination).Length / speed, 1);
}

// HACK: why isn't this using the interface?
self.Trait<WithInfantryBody>().Attacking(self, Target.FromActor(target), a);
protected override void OnFirstRun(Actor self)
{
// First check if we are still allowed to leap
// We need an extra boolean as using Cancel() in OnFirstRun doesn't work
canceled = !edible.GetLeapAtBy(self) || target.Type != TargetType.Actor;

if (weapon.Report != null && weapon.Report.Any())
Game.Sound.Play(SoundType.World, weapon.Report.Random(self.World.SharedRandom), self.CenterPosition);
IsInterruptible = false;

if (canceled)
return;

attack.GrantLeapCondition(self);
}

public override Activity Tick(Actor self)
{
if (ticks == 0 && IsCanceled)
if (canceled)
return NextActivity;

mobile.SetVisualPosition(self, WPos.LerpQuadratic(from, to, angle, ++ticks, length));
if (ticks >= length)
// Correct the visual position after we jumped
if (jumpComplete)
{
if (ChildActivity == null)
return NextActivity;

ChildActivity = ActivityUtils.RunActivity(self, ChildActivity);
return this;
}

var position = length > 1 ? WPos.Lerp(origin, target.CenterPosition, ticks, length - 1) : target.CenterPosition;
mobile.SetVisualPosition(self, position);

// We are at the destination
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, damageTypes));
// Revoke the run condition
attack.IsAiming = false;

return NextActivity;
// Move to the correct subcells, if our target actor uses subcells
// (This does not update the visual position!)
mobile.SetLocation(destinationCell, destinationSubCell, destinationCell, destinationSubCell);

// Revoke the condition before attacking, as it is usually used to pause the attack trait
attack.RevokeLeapCondition(self);
attack.DoAttack(self, target);

jumpComplete = true;
QueueChild(mobile.VisualMove(self, position, self.World.Map.CenterOfSubCell(destinationCell, destinationSubCell)));

return this;
}

mobile.IsMoving = true;

return this;
}

protected override void OnLastRun(Actor self)
{
attack.RevokeLeapCondition(self);
base.OnLastRun(self);
}

protected override void OnActorDispose(Actor self)
{
attack.RevokeLeapCondition(self);
base.OnActorDispose(self);
}

public override IEnumerable<Target> GetTargets(Actor self)
{
yield return Target.FromPos(ticks < length / 2 ? origin : destination);
}
}
}
104 changes: 104 additions & 0 deletions OpenRA.Mods.Cnc/Activities/LeapAttack.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#region Copyright & License Information
/*
* Copyright 2007-2018 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.Common.Activities;
using OpenRA.Mods.Cnc.Traits;
using OpenRA.Mods.Common.Traits;
using OpenRA.Traits;

namespace OpenRA.Mods.Cnc.Activities
{
public class LeapAttack : Activity
{
readonly Target target;
readonly AttackLeapInfo info;
readonly AttackLeap attack;
readonly Mobile mobile, targetMobile;
readonly EdibleByLeap edible;
readonly bool allowMovement;
readonly IFacing facing;

public LeapAttack(Actor self, Target target, bool allowMovement, AttackLeap attack, AttackLeapInfo info)
{
this.target = target;
this.info = info;
this.attack = attack;
this.allowMovement = allowMovement;

mobile = self.Trait<Mobile>();
facing = self.TraitOrDefault<IFacing>();

if (target.Type == TargetType.Actor)
{
targetMobile = target.Actor.TraitOrDefault<Mobile>();
edible = target.Actor.TraitOrDefault<EdibleByLeap>();
}

attack.IsAiming = true;
}

public override Activity Tick(Actor self)
{
if (IsCanceled || edible == null)
return NextActivity;

// Run this even if the target became invalid to avoid visual glitches
if (ChildActivity != null)
{
ChildActivity = ActivityUtils.RunActivity(self, ChildActivity);
return this;
}

if (target.Type != TargetType.Actor || !edible.CanLeap(self) || !target.IsValidFor(self) || !attack.HasAnyValidWeapons(target))
return NextActivity;

var minRange = attack.GetMinimumRangeVersusTarget(target);
var maxRange = attack.GetMaximumRangeVersusTarget(target);
if (!target.IsInRange(self.CenterPosition, maxRange) || target.IsInRange(self.CenterPosition, minRange))
{
if (!allowMovement)
return NextActivity;

QueueChild(new MoveWithinRange(self, target, minRange, maxRange));
return this;
}

if (attack.Armaments.All(a => a.IsReloading))
return this;

// Use CenterOfSubCell with ToSubCell instead of target.Centerposition
// to avoid continuous facing adjustments as the target moves
var targetSubcell = targetMobile != null ? targetMobile.ToSubCell : SubCell.Any;
var destination = self.World.Map.CenterOfSubCell(target.Actor.Location, targetSubcell);
var origin = self.World.Map.CenterOfSubCell(self.Location, mobile.FromSubCell);
var desiredFacing = (destination - origin).Yaw.Facing;
if (facing != null && facing.Facing != desiredFacing)
{
QueueChild(new Turn(self, desiredFacing));
return this;
}

QueueChild(new Leap(self, target, mobile, targetMobile, info.Speed.Length, attack, edible));

// Re-queue the child activities to kill the target if it didn't die in one go
return this;
}

protected override void OnLastRun(Actor self)
{
attack.IsAiming = false;
base.OnLastRun(self);
}
}
}
2 changes: 2 additions & 0 deletions OpenRA.Mods.Cnc/OpenRA.Mods.Cnc.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Activities\LeapAttack.cs" />
<Compile Include="CncLoadScreen.cs" />
<Compile Include="Traits\EdibleByLeap.cs" />
<Compile Include="Traits\Attack\AttackPopupTurreted.cs" />
<Compile Include="Traits\Buildings\ProductionAirdrop.cs" />
<Compile Include="Traits\Infiltration\InfiltrateForTransform.cs" />
Expand Down
60 changes: 39 additions & 21 deletions OpenRA.Mods.Cnc/Traits/Attack/AttackLeap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,53 +9,71 @@
*/
#endregion

using System.Collections.Generic;
using System.Linq;
using OpenRA.Activities;
using OpenRA.Mods.Cnc.Activities;
using OpenRA.Mods.Common.Traits;
using OpenRA.Primitives;
using OpenRA.Traits;

namespace OpenRA.Mods.Cnc.Traits
{
[Desc("Dogs use this attack model.")]
class AttackLeapInfo : AttackFrontalInfo
[Desc("Move onto the target then execute the attack.")]
public class AttackLeapInfo : AttackFrontalInfo, Requires<MobileInfo>
{
[Desc("Leap speed (in units/tick).")]
[Desc("Leap speed (in WDist units/tick).")]
public readonly WDist Speed = new WDist(426);

public readonly WAngle Angle = WAngle.FromDegrees(20);

[Desc("Types of damage that this trait causes. Leave empty for no damage types.")]
public readonly BitSet<DamageType> DamageTypes = default(BitSet<DamageType>);
[Desc("Conditions that last from start of the leap until the attack.")]
[GrantedConditionReference]
public readonly string LeapCondition = "attacking";

public override object Create(ActorInitializer init) { return new AttackLeap(init.Self, this); }
}

class AttackLeap : AttackFrontal
public class AttackLeap : AttackFrontal
{
readonly AttackLeapInfo info;

ConditionManager conditionManager;
int leapToken = ConditionManager.InvalidConditionToken;

public AttackLeap(Actor self, AttackLeapInfo info)
: base(self, info)
{
this.info = info;
}

public override void DoAttack(Actor self, Target target, IEnumerable<Armament> armaments = null)
protected override void Created(Actor self)
{
if (target.Type != TargetType.Actor || !CanAttack(self, target))
return;
conditionManager = self.TraitOrDefault<ConditionManager>();
base.Created(self);
}

var a = ChooseArmamentsForTarget(target, true).FirstOrDefault();
if (a == null)
return;
protected override bool CanAttack(Actor self, Target target)
{
if (target.Type != TargetType.Actor)
return false;

if (!target.IsInRange(self.CenterPosition, a.MaxRange()))
return;
if (self.Location == target.Actor.Location && HasAnyValidWeapons(target))
return true;

self.CancelActivity();
self.QueueActivity(new Leap(self, target.Actor, a, info.Speed, info.Angle, info.DamageTypes));
return base.CanAttack(self, target);
}

public void GrantLeapCondition(Actor self)
{
if (conditionManager != null && !string.IsNullOrEmpty(info.LeapCondition))
leapToken = conditionManager.GrantCondition(self, info.LeapCondition);
}

public void RevokeLeapCondition(Actor self)
{
if (leapToken != ConditionManager.InvalidConditionToken)
leapToken = conditionManager.RevokeCondition(self, leapToken);
}

public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack)
{
return new LeapAttack(self, newTarget, allowMove, this, info);
}
}
}
Loading

0 comments on commit 2b948c3

Please sign in to comment.