Merge pull request #12571 from reaperrr/bullet-bounce

Implement bullet bouncing logic
This commit is contained in:
Oliver Brakmann
2017-01-21 22:06:23 +01:00
committed by GitHub
2 changed files with 68 additions and 10 deletions

View File

@@ -63,6 +63,16 @@ namespace OpenRA.Mods.Common.Projectiles
[Desc("Arc in WAngles, two values indicate variable arc.")] [Desc("Arc in WAngles, two values indicate variable arc.")]
public readonly WAngle[] LaunchAngle = { WAngle.Zero }; public readonly WAngle[] LaunchAngle = { WAngle.Zero };
[Desc("Up to how many times does this bullet bounce when touching ground without hitting a target.",
"0 implies exploding on contact with the originally targeted position.")]
public readonly int BounceCount = 0;
[Desc("Modify distance of each bounce by this percentage of previous distance.")]
public readonly int BounceRangeModifier = 60;
[Desc("If projectile touches an actor with one of these stances during or after the first bounce, trigger explosion.")]
public readonly Stance ValidBounceBlockerStances = Stance.Enemy | Stance.Neutral;
[Desc("Interval in ticks between each spawned Trail animation.")] [Desc("Interval in ticks between each spawned Trail animation.")]
public readonly int TrailInterval = 2; public readonly int TrailInterval = 2;
@@ -96,10 +106,11 @@ namespace OpenRA.Mods.Common.Projectiles
ContrailRenderable contrail; ContrailRenderable contrail;
string trailPalette; string trailPalette;
[Sync] WPos pos, target; [Sync] WPos pos, target, source;
int length; int length;
[Sync] int facing; [Sync] int facing;
int ticks, smokeTicks; int ticks, smokeTicks;
int remainingBounces;
public Actor SourceActor { get { return args.SourceActor; } } public Actor SourceActor { get { return args.SourceActor; } }
@@ -108,6 +119,7 @@ namespace OpenRA.Mods.Common.Projectiles
this.info = info; this.info = info;
this.args = args; this.args = args;
pos = args.Source; pos = args.Source;
source = args.Source;
var world = args.SourceActor.World; var world = args.SourceActor.World;
@@ -150,6 +162,7 @@ namespace OpenRA.Mods.Common.Projectiles
trailPalette += args.SourceActor.Owner.InternalName; trailPalette += args.SourceActor.Owner.InternalName;
smokeTicks = info.TrailDelay; smokeTicks = info.TrailDelay;
remainingBounces = info.BounceCount;
} }
int GetEffectiveFacing() int GetEffectiveFacing()
@@ -171,7 +184,7 @@ namespace OpenRA.Mods.Common.Projectiles
anim.Tick(); anim.Tick();
var lastPos = pos; var lastPos = pos;
pos = WPos.LerpQuadratic(args.Source, target, angle, ticks, length); pos = WPos.LerpQuadratic(source, target, angle, ticks, length);
// Check for walls or other blocking obstacles // Check for walls or other blocking obstacles
var shouldExplode = false; var shouldExplode = false;
@@ -185,7 +198,7 @@ namespace OpenRA.Mods.Common.Projectiles
if (!string.IsNullOrEmpty(info.TrailImage) && --smokeTicks < 0) if (!string.IsNullOrEmpty(info.TrailImage) && --smokeTicks < 0)
{ {
var delayedPos = WPos.LerpQuadratic(args.Source, target, angle, ticks - info.TrailDelay, length); var delayedPos = WPos.LerpQuadratic(source, target, angle, ticks - info.TrailDelay, length);
world.AddFrameEndTask(w => w.Add(new SpriteEffect(delayedPos, w, info.TrailImage, info.TrailSequences.Random(world.SharedRandom), world.AddFrameEndTask(w => w.Add(new SpriteEffect(delayedPos, w, info.TrailImage, info.TrailSequences.Random(world.SharedRandom),
trailPalette, false, false, GetEffectiveFacing()))); trailPalette, false, false, GetEffectiveFacing())));
@@ -195,8 +208,30 @@ namespace OpenRA.Mods.Common.Projectiles
if (info.ContrailLength > 0) if (info.ContrailLength > 0)
contrail.Update(pos); contrail.Update(pos);
var flightLengthReached = ticks++ >= length;
var shouldBounce = remainingBounces > 0;
if (flightLengthReached && shouldBounce)
{
shouldExplode |= AnyValidTargetsInRadius(world, pos, info.Width + info.TargetExtraSearchRadius, args.SourceActor, true);
target += (pos - source) * info.BounceRangeModifier / 100;
var dat = world.Map.DistanceAboveTerrain(target);
target += new WVec(0, 0, -dat.Length);
length = Math.Max((target - pos).Length / speed.Length, 1);
ticks = 0;
source = pos;
remainingBounces--;
}
// Flight length reached / exceeded // Flight length reached / exceeded
shouldExplode |= ticks++ >= length; shouldExplode |= flightLengthReached && !shouldBounce;
// Driving into cell with higher height level
shouldExplode |= world.Map.DistanceAboveTerrain(pos).Length < 0;
// After first bounce, check for targets each tick
if (remainingBounces < info.BounceCount)
shouldExplode |= AnyValidTargetsInRadius(world, pos, info.Width + info.TargetExtraSearchRadius, args.SourceActor, true);
if (shouldExplode) if (shouldExplode)
Explode(world); Explode(world);
@@ -236,5 +271,27 @@ namespace OpenRA.Mods.Common.Projectiles
args.Weapon.Impact(Target.FromPos(pos), args.SourceActor, args.DamageModifiers); args.Weapon.Impact(Target.FromPos(pos), args.SourceActor, args.DamageModifiers);
} }
bool AnyValidTargetsInRadius(World world, WPos pos, WDist radius, Actor firedBy, bool checkTargetType)
{
foreach (var victim in world.FindActorsInCircle(pos, radius))
{
if (checkTargetType && !Target.FromActor(victim).IsValidFor(firedBy))
continue;
if (!info.ValidBounceBlockerStances.HasStance(victim.Owner.Stances[firedBy.Owner]))
continue;
var healthInfo = victim.Info.TraitInfoOrDefault<HealthInfo>();
if (healthInfo == null)
continue;
// If the impact position is within any actor's HitShape, we have a direct hit
if (healthInfo.Shape.DistanceFromEdge(pos, victim).Length <= 0)
return true;
}
return false;
}
} }
} }

View File

@@ -1,15 +1,16 @@
Grenade: Grenade:
ReloadDelay: 60 ReloadDelay: 60
Range: 4c512 Range: 4c512
Projectile: Bullet # TODO: Add bounce effect Projectile: Bullet
Speed: 85 Speed: 160
Blockable: false Blockable: true
Shadow: true Shadow: true
LaunchAngle: 45 LaunchAngle: 60
Inaccuracy: 384 Inaccuracy: 0
Image: DISCUS Image: DISCUS
BounceCount: 2
Warhead@1Dam: SpreadDamage Warhead@1Dam: SpreadDamage
Spread: 171 Spread: 192
Damage: 40 Damage: 40
Versus: Versus:
None: 100 None: 100