Refactor Leap attack logic

This commit is contained in:
abcdefg30
2018-12-25 13:57:57 +01:00
committed by reaperrr
parent 9c4cb9091e
commit 0ff4e466ee
9 changed files with 299 additions and 62 deletions

View File

@@ -10,72 +10,120 @@
#endregion #endregion
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using OpenRA.Activities; using OpenRA.Activities;
using OpenRA.GameRules; using OpenRA.Mods.Cnc.Traits;
using OpenRA.Mods.Common.Traits; using OpenRA.Mods.Common.Traits;
using OpenRA.Mods.Common.Traits.Render;
using OpenRA.Primitives;
using OpenRA.Traits; using OpenRA.Traits;
namespace OpenRA.Mods.Cnc.Activities namespace OpenRA.Mods.Cnc.Activities
{ {
class Leap : Activity public class Leap : Activity
{ {
readonly Mobile mobile; readonly Mobile mobile;
readonly WeaponInfo weapon; readonly WPos destination, origin;
readonly CPos destinationCell;
readonly SubCell destinationSubCell = SubCell.Any;
readonly int length; readonly int length;
readonly AttackLeap attack;
readonly EdibleByLeap edible;
readonly Target target;
WPos from; bool canceled = false;
WPos to; bool jumpComplete = false;
int ticks; int ticks = 0;
WAngle angle;
BitSet<DamageType> damageTypes;
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>(); this.mobile = mobile;
if (targetMobile == null) this.attack = attack;
throw new InvalidOperationException("Leap requires a target actor with the Mobile trait"); this.target = target;
this.edible = edible;
this.weapon = a.Weapon; destinationCell = target.Actor.Location;
this.angle = angle; if (targetMobile != null)
this.damageTypes = damageTypes; destinationSubCell = targetMobile.ToSubCell;
mobile = self.Trait<Mobile>();
mobile.SetLocation(mobile.FromCell, mobile.FromSubCell, targetMobile.FromCell, targetMobile.FromSubCell);
mobile.IsMoving = true;
from = self.CenterPosition; origin = self.World.Map.CenterOfSubCell(self.Location, mobile.FromSubCell);
to = self.World.Map.CenterOfSubCell(targetMobile.FromCell, targetMobile.FromSubCell); destination = self.World.Map.CenterOfSubCell(destinationCell, destinationSubCell);
length = Math.Max((to - from).Length / speed.Length, 1); length = Math.Max((origin - destination).Length / speed, 1);
}
// HACK: why isn't this using the interface? protected override void OnFirstRun(Actor self)
self.Trait<WithInfantryBody>().Attacking(self, Target.FromActor(target), a); {
// 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()) IsInterruptible = false;
Game.Sound.Play(SoundType.World, weapon.Report.Random(self.World.SharedRandom), self.CenterPosition);
if (canceled)
return;
attack.GrantLeapCondition(self);
} }
public override Activity Tick(Actor self) public override Activity Tick(Actor self)
{ {
if (ticks == 0 && IsCanceled) if (canceled)
return NextActivity; return NextActivity;
mobile.SetVisualPosition(self, WPos.LerpQuadratic(from, to, angle, ++ticks, length)); // Correct the visual position after we jumped
if (ticks >= length) 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; mobile.IsMoving = false;
self.World.ActorMap.GetActorsAt(mobile.ToCell, mobile.ToSubCell) // Revoke the run condition
.Except(new[] { self }).Where(t => weapon.IsValidAgainst(t, self)) attack.IsAiming = false;
.Do(t => t.Kill(self, damageTypes));
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; 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);
}
} }
} }

View File

@@ -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.Cnc.Traits;
using OpenRA.Mods.Common.Activities;
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);
}
}
}

View File

@@ -62,7 +62,9 @@
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Activities\LeapAttack.cs" />
<Compile Include="CncLoadScreen.cs" /> <Compile Include="CncLoadScreen.cs" />
<Compile Include="Traits\EdibleByLeap.cs" />
<Compile Include="Traits\Attack\AttackPopupTurreted.cs" /> <Compile Include="Traits\Attack\AttackPopupTurreted.cs" />
<Compile Include="Traits\Buildings\ProductionAirdrop.cs" /> <Compile Include="Traits\Buildings\ProductionAirdrop.cs" />
<Compile Include="Traits\Infiltration\InfiltrateForTransform.cs" /> <Compile Include="Traits\Infiltration\InfiltrateForTransform.cs" />

View File

@@ -9,53 +9,71 @@
*/ */
#endregion #endregion
using System.Collections.Generic; using OpenRA.Activities;
using System.Linq;
using OpenRA.Mods.Cnc.Activities; using OpenRA.Mods.Cnc.Activities;
using OpenRA.Mods.Common.Traits; using OpenRA.Mods.Common.Traits;
using OpenRA.Primitives;
using OpenRA.Traits; using OpenRA.Traits;
namespace OpenRA.Mods.Cnc.Traits namespace OpenRA.Mods.Cnc.Traits
{ {
[Desc("Dogs use this attack model.")] [Desc("Move onto the target then execute the attack.")]
class AttackLeapInfo : AttackFrontalInfo 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 WDist Speed = new WDist(426);
public readonly WAngle Angle = WAngle.FromDegrees(20); [Desc("Conditions that last from start of the leap until the attack.")]
[GrantedConditionReference]
[Desc("Types of damage that this trait causes. Leave empty for no damage types.")] public readonly string LeapCondition = "attacking";
public readonly BitSet<DamageType> DamageTypes = default(BitSet<DamageType>);
public override object Create(ActorInitializer init) { return new AttackLeap(init.Self, this); } public override object Create(ActorInitializer init) { return new AttackLeap(init.Self, this); }
} }
class AttackLeap : AttackFrontal public class AttackLeap : AttackFrontal
{ {
readonly AttackLeapInfo info; readonly AttackLeapInfo info;
ConditionManager conditionManager;
int leapToken = ConditionManager.InvalidConditionToken;
public AttackLeap(Actor self, AttackLeapInfo info) public AttackLeap(Actor self, AttackLeapInfo info)
: base(self, info) : base(self, info)
{ {
this.info = 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)) conditionManager = self.TraitOrDefault<ConditionManager>();
return; base.Created(self);
}
var a = ChooseArmamentsForTarget(target, true).FirstOrDefault(); protected override bool CanAttack(Actor self, Target target)
if (a == null) {
return; if (target.Type != TargetType.Actor)
return false;
if (!target.IsInRange(self.CenterPosition, a.MaxRange())) if (self.Location == target.Actor.Location && HasAnyValidWeapons(target))
return; return true;
self.CancelActivity(); return base.CanAttack(self, target);
self.QueueActivity(new Leap(self, target.Actor, a, info.Speed, info.Angle, info.DamageTypes)); }
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);
} }
} }
} }

View File

@@ -0,0 +1,37 @@
#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 OpenRA.Traits;
namespace OpenRA.Mods.Cnc.Traits
{
[Desc("Allows this actor to be the target of an attack leap.")]
public class EdibleByLeapInfo : TraitInfo<EdibleByLeap> { }
public class EdibleByLeap
{
Actor leaper;
public bool CanLeap(Actor targeter)
{
return leaper == null || leaper.IsDead || leaper == targeter;
}
public bool GetLeapAtBy(Actor targeter)
{
if (leaper != null && !leaper.IsDead && leaper != targeter)
return false;
leaper = targeter;
return true;
}
}
}

View File

@@ -429,6 +429,7 @@
Radius: 128 Radius: 128
MapEditorData: MapEditorData:
Categories: Infantry Categories: Infantry
EdibleByLeap:
^Soldier: ^Soldier:
Inherits: ^Infantry Inherits: ^Infantry

View File

@@ -20,6 +20,7 @@ DOG:
Mobile: Mobile:
Speed: 99 Speed: 99
Voice: Move Voice: Move
RequiresCondition: !attack-cooldown && !eating
Guard: Guard:
Voice: Move Voice: Move
Passenger: Passenger:
@@ -28,10 +29,17 @@ DOG:
Range: 5c0 Range: 5c0
Armament: Armament:
Weapon: DogJaw Weapon: DogJaw
ReloadingCondition: attack-cooldown
AttackLeap: AttackLeap:
Voice: Attack Voice: Attack
PauseOnCondition: attacking || attack-cooldown
AttackMove: AttackMove:
Voice: Move Voice: Move
GrantConditionOnAttack:
Condition: eating
RevokeDelay: 45
GrantConditionWhileAiming:
Condition: run
AutoTarget: AutoTarget:
InitialStance: AttackAnything InitialStance: AttackAnything
AutoTargetPriority@DEFAULT: AutoTargetPriority@DEFAULT:
@@ -39,8 +47,16 @@ DOG:
Targetable: Targetable:
TargetTypes: Ground, Infantry TargetTypes: Ground, Infantry
WithInfantryBody: WithInfantryBody:
DefaultAttackSequence: shoot MoveSequence: walk
StandSequences: stand StandSequences: stand
DefaultAttackSequence: eat
RequiresCondition: !attacking && !run
WithInfantryBody@RUN:
MoveSequence: run
RequiresCondition: run
SpeedMultiplier:
Modifier: 150
RequiresCondition: run
IgnoresDisguise: IgnoresDisguise:
DetectCloaked: DetectCloaked:
CloakTypes: Cloak, Thief CloakTypes: Cloak, Thief

View File

@@ -499,11 +499,21 @@ e2:
dog: dog:
stand: stand:
Facings: 8 Facings: 8
run: walk:
Start: 8 Start: 8
Length: 6 Length: 6
Facings: 8 Facings: 8
Tick: 80 Tick: 80
run:
Start: 56
Length: 6
Facings: 8
Tick: 80
eat:
Start: 104
Length: 14
Facings: 8
Tick: 120
idle1: idle1:
Start: 216 Start: 216
Length: 7 Length: 7
@@ -537,7 +547,7 @@ dog:
TilesetOverrides: TilesetOverrides:
DESERT: TEMPERAT DESERT: TEMPERAT
INTERIOR: TEMPERAT INTERIOR: TEMPERAT
shoot: dogbullt jump: dogbullt
Length: 4 Length: 4
Facings: 8 Facings: 8
icon: dogicon icon: dogicon

View File

@@ -109,8 +109,9 @@ DogJaw:
ReloadDelay: 10 ReloadDelay: 10
Range: 3c0 Range: 3c0
Report: dogg5p.aud Report: dogg5p.aud
Warhead@1Dam: SpreadDamage TargetActorCenter: true
Spread: 213 Projectile: InstantHit
Warhead@1Dam: TargetDamage
Damage: 10000 Damage: 10000
ValidTargets: Infantry ValidTargets: Infantry
InvalidTargets: Ant InvalidTargets: Ant