diff --git a/OpenRa.Game/Actor.cs b/OpenRa.Game/Actor.cs index 56d7aaf44f..da9204fe25 100755 --- a/OpenRa.Game/Actor.cs +++ b/OpenRa.Game/Actor.cs @@ -94,7 +94,7 @@ namespace OpenRa.Game public bool IsDead { get { return Health <= 0; } } - public void InflictDamage(Actor attacker, Bullet inflictor, int damage) + public void InflictDamage(Actor attacker, int damage) { /* todo: auto-retaliate, etc */ /* todo: death sequence for infantry based on inflictor */ diff --git a/OpenRa.Game/Combat.cs b/OpenRa.Game/Combat.cs new file mode 100644 index 0000000000..69bb75fd54 --- /dev/null +++ b/OpenRa.Game/Combat.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using OpenRa.Game.GameRules; +using OpenRa.Game.Effects; + +namespace OpenRa.Game +{ + static class Combat /* some utility bits that are shared between various things */ + { + public static void DoImpact(int2 loc, int2 visualLoc, + WeaponInfo weapon, ProjectileInfo projectile, WarheadInfo warhead, Actor firedBy) + { + var targetTile = ((1f / Game.CellSize) * loc.ToFloat2()).ToInt2(); + + var isWater = Game.IsWater(targetTile); + var hitWater = Game.IsCellBuildable(targetTile, UnitMovementType.Float); + + if (warhead.Explosion != 0) + Game.world.AddFrameEndTask( + w => w.Add(new Explosion(visualLoc, warhead.Explosion, hitWater))); + + var impactSound = warhead.ImpactSound; + if (hitWater && warhead.WaterImpactSound != null) + impactSound = warhead.WaterImpactSound; + if (impactSound != null) Sound.Play(impactSound + ".aud"); + + if (!isWater) Smudge.AddSmudge(targetTile, warhead); + if (warhead.Ore) Ore.Destroy(targetTile.X, targetTile.Y); + + var maxSpread = GetMaximumSpread(weapon, warhead); + var hitActors = Game.FindUnitsInCircle(loc, maxSpread); + + foreach (var victim in hitActors) + victim.InflictDamage(firedBy, (int)GetDamageToInflict(victim, loc, weapon, warhead)); + } + + static float GetMaximumSpread(WeaponInfo weapon, WarheadInfo warhead) + { + return (int)(warhead.Spread * Math.Log(weapon.Damage, 2)); + } + + static float GetDamageToInflict(Actor target, int2 loc, WeaponInfo weapon, WarheadInfo warhead) + { + /* todo: some things can't be damaged AT ALL by certain weapons! */ + var distance = (target.CenterLocation - loc).Length; + var rawDamage = weapon.Damage * (float)Math.Exp(-distance / warhead.Spread); + var multiplier = warhead.EffectivenessAgainst(target.Info.Armor); + + return rawDamage * multiplier; + } + } +} diff --git a/OpenRa.Game/Bullet.cs b/OpenRa.Game/Effects/Bullet.cs similarity index 54% rename from OpenRa.Game/Bullet.cs rename to OpenRa.Game/Effects/Bullet.cs index f3827a7bce..784795b346 100644 --- a/OpenRa.Game/Bullet.cs +++ b/OpenRa.Game/Effects/Bullet.cs @@ -3,11 +3,11 @@ using System.Collections.Generic; using OpenRa.Game.GameRules; using OpenRa.Game.Graphics; -namespace OpenRa.Game +namespace OpenRa.Game.Effects { class Bullet : IEffect { - public Player Owner { get; private set; } + readonly Player Owner; readonly Actor FiredBy; readonly WeaponInfo Weapon; readonly ProjectileInfo Projectile; @@ -40,10 +40,7 @@ namespace OpenRa.Game { anim = new Animation(Projectile.Image); if (Projectile.Rotates) - anim.PlayFetchIndex("idle", - () => Traits.Util.QuantizeFacing( - Traits.Util.GetFacing((dest - src).ToFloat2(), 0), - anim.CurrentSequence.Length)); + Traits.Util.PlayFacing(anim, "idle", () => Traits.Util.GetFacing((dest - src).ToFloat2(), 0)); else anim.PlayRepeating("idle"); } @@ -61,36 +58,10 @@ namespace OpenRa.Game if (t > TotalTime()) /* remove finished bullets */ { Game.world.AddFrameEndTask(w => w.Remove(this)); - DoImpact(); + Combat.DoImpact(Dest, VisualDest, Weapon, Projectile, Warhead, FiredBy); } } - void DoImpact() - { - var targetTile = ((1f / Game.CellSize) * Dest.ToFloat2()).ToInt2(); - - var isWater = Game.IsWater(targetTile); - var hitWater = Game.IsCellBuildable(targetTile, UnitMovementType.Float); - - if (Warhead.Explosion != 0) - Game.world.AddFrameEndTask( - w => w.Add(new Explosion(VisualDest, Warhead.Explosion, hitWater))); - - var impactSound = Warhead.ImpactSound; - if (hitWater && Warhead.WaterImpactSound != null) - impactSound = Warhead.WaterImpactSound; - if (impactSound != null) Sound.Play(impactSound + ".aud"); - - if (!isWater) Smudge.AddSmudge(targetTile, Warhead); - if (Warhead.Ore) Ore.Destroy(targetTile.X, targetTile.Y); - - var maxSpread = GetMaximumSpread(); - var hitActors = Game.FindUnitsInCircle(Dest, GetMaximumSpread()); - - foreach (var victim in hitActors) - victim.InflictDamage(FiredBy, this, (int)GetDamageToInflict(victim)); - } - const float height = .1f; public IEnumerable> Render() @@ -116,20 +87,5 @@ namespace OpenRa.Game yield return Tuple.New(anim.Image, pos, Owner.Palette); } } - - float GetMaximumSpread() - { - return (int)(Warhead.Spread * Math.Log(Weapon.Damage, 2)); - } - - float GetDamageToInflict(Actor target) - { - /* todo: some things can't be damaged AT ALL by certain weapons! */ - var distance = (target.CenterLocation - Dest).Length; - var rawDamage = Weapon.Damage * (float)Math.Exp(-distance / Warhead.Spread); - var multiplier = Warhead.EffectivenessAgainst(target.Info.Armor); - - return rawDamage * multiplier; - } } } diff --git a/OpenRa.Game/Explosion.cs b/OpenRa.Game/Effects/Explosion.cs similarity index 91% rename from OpenRa.Game/Explosion.cs rename to OpenRa.Game/Effects/Explosion.cs index f98c67a637..8de120a3b7 100644 --- a/OpenRa.Game/Explosion.cs +++ b/OpenRa.Game/Effects/Explosion.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using OpenRa.Game.Graphics; -namespace OpenRa.Game +namespace OpenRa.Game.Effects { class Explosion : IEffect { diff --git a/OpenRa.Game/IEffect.cs b/OpenRa.Game/Effects/IEffect.cs similarity index 73% rename from OpenRa.Game/IEffect.cs rename to OpenRa.Game/Effects/IEffect.cs index 503023b686..43fb1453a2 100644 --- a/OpenRa.Game/IEffect.cs +++ b/OpenRa.Game/Effects/IEffect.cs @@ -1,12 +1,11 @@ using System.Collections.Generic; using OpenRa.Game.Graphics; -namespace OpenRa.Game +namespace OpenRa.Game.Effects { interface IEffect { void Tick(); IEnumerable> Render(); - Player Owner { get; } } } diff --git a/OpenRa.Game/Effects/Missile.cs b/OpenRa.Game/Effects/Missile.cs new file mode 100644 index 0000000000..c4a69710bc --- /dev/null +++ b/OpenRa.Game/Effects/Missile.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using OpenRa.Game.Graphics; +using OpenRa.Game.GameRules; + +namespace OpenRa.Game.Effects +{ + class Missile : IEffect + { + readonly Player Owner; + readonly Actor FiredBy; + readonly WeaponInfo Weapon; + readonly ProjectileInfo Projectile; + readonly WarheadInfo Warhead; + float2 Pos; + readonly Actor Target; + readonly Animation anim; + int Facing; + int t; + + public Missile(string weapon, Player owner, Actor firedBy, + int2 src, Actor target) + { + Weapon = Rules.WeaponInfo[weapon]; + Projectile = Rules.ProjectileInfo[Weapon.Projectile]; + Warhead = Rules.WarheadInfo[Weapon.Warhead]; + FiredBy = firedBy; + Owner = owner; + Target = target; + Pos = src.ToFloat2(); + + /* todo: initial facing should be turret facing, or unit facing if we're not turreted */ + Facing = Traits.Util.GetFacing( Target.CenterLocation - src.ToFloat2(), 0 ); + + if (Projectile.Image != null && Projectile.Image != "none") + { + anim = new Animation(Projectile.Image); + + if (Projectile.Rotates) + Traits.Util.PlayFacing(anim, "idle", () => Facing); + else + anim.PlayRepeating("idle"); + } + } + + const int MissileCloseEnough = 7; + const float Scale = .3f; + + public void Tick() + { + if (t == 0) + Sound.Play(Weapon.Report + ".aud"); + + t += 40; + + Traits.Util.TickFacing(ref Facing, + Traits.Util.GetFacing(Target.CenterLocation - Pos, Facing), + Projectile.ROT); + + anim.Tick(); + + var dist = Target.CenterLocation - Pos; + if (dist.LengthSquared < MissileCloseEnough * MissileCloseEnough || Target.IsDead) + { + Game.world.AddFrameEndTask(w => w.Remove(this)); + + if (t > Projectile.Arm * 40) /* don't blow up in our launcher's face! */ + Combat.DoImpact(Pos.ToInt2(), Pos.ToInt2(), Weapon, Projectile, Warhead, FiredBy); + return; + } + + var move = (Scale * Weapon.Speed / dist.Length) * dist; + Pos += move; + + if (Projectile.Animates) + Game.world.AddFrameEndTask(w => w.Add(new Smoke((Pos - 1.5f * move).ToInt2()))); + + // todo: running out of fuel + // todo: turbo boost vs aircraft + } + + public IEnumerable> Render() + { + yield return Tuple.New(anim.Image, Pos - 0.5f * anim.Image.size, 0); + } + } +} diff --git a/OpenRa.Game/Effects/Smoke.cs b/OpenRa.Game/Effects/Smoke.cs new file mode 100644 index 0000000000..21fd4f237c --- /dev/null +++ b/OpenRa.Game/Effects/Smoke.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using OpenRa.Game.Graphics; + +namespace OpenRa.Game.Effects +{ + class Smoke : IEffect + { + readonly int2 pos; + readonly Animation anim = new Animation("smokey"); + + public Smoke(int2 pos) + { + this.pos = pos; + anim.PlayThen("idle", + () => Game.world.AddFrameEndTask( + w => w.Remove(this))); + } + + public void Tick() + { + anim.Tick(); + } + + public IEnumerable> Render() + { + yield return Tuple.New(anim.Image, pos.ToFloat2() - .5f * anim.Image.size, 0); + } + } +} diff --git a/OpenRa.Game/Graphics/WorldRenderer.cs b/OpenRa.Game/Graphics/WorldRenderer.cs index 8b798853ba..36263ac982 100644 --- a/OpenRa.Game/Graphics/WorldRenderer.cs +++ b/OpenRa.Game/Graphics/WorldRenderer.cs @@ -5,6 +5,7 @@ using IjwFramework.Types; using System.Collections.Generic; using OpenRa.Game.Traits; using OpenRa.Game.Support; +using OpenRa.Game.Effects; namespace OpenRa.Game.Graphics { @@ -61,7 +62,7 @@ namespace OpenRa.Game.Graphics .Select(u => u.traits.Get())) DrawSpriteList(rect, a.RenderRoof(a.self)); - foreach (IEffect e in Game.world.Effects) + foreach (var e in Game.world.Effects) DrawSpriteList(rect, e.Render()); uiOverlay.Draw(); diff --git a/OpenRa.Game/OpenRa.Game.csproj b/OpenRa.Game/OpenRa.Game.csproj index f262a8333a..d1c5f7b95b 100644 --- a/OpenRa.Game/OpenRa.Game.csproj +++ b/OpenRa.Game/OpenRa.Game.csproj @@ -79,14 +79,17 @@ + + - + + @@ -104,10 +107,10 @@ - + - + diff --git a/OpenRa.Game/Traits/AttackBase.cs b/OpenRa.Game/Traits/AttackBase.cs index d1a241fa28..d679c3f480 100644 --- a/OpenRa.Game/Traits/AttackBase.cs +++ b/OpenRa.Game/Traits/AttackBase.cs @@ -1,4 +1,5 @@ using System; +using OpenRa.Game.Effects; namespace OpenRa.Game.Traits { @@ -63,10 +64,16 @@ namespace OpenRa.Game.Traits if (weapon.Range * weapon.Range < (target.Location - self.Location).LengthSquared) return false; fireDelay = weapon.ROF; + var projectile = Rules.ProjectileInfo[weapon.Projectile]; - Game.world.Add(new Bullet(weaponName, self.Owner, self, - self.CenterLocation.ToInt2() + Util.GetTurretPosition(self, unit, offset, 0f).ToInt2(), - target.CenterLocation.ToInt2())); + var firePos = self.CenterLocation.ToInt2() + Util.GetTurretPosition(self, unit, offset, 0f).ToInt2(); + + if (projectile.ROT != 0) + Game.world.Add(new Missile(weaponName, self.Owner, self, + firePos, target)); + else + Game.world.Add(new Bullet(weaponName, self.Owner, self, + firePos, target.CenterLocation.ToInt2())); return true; } diff --git a/OpenRa.Game/Traits/RenderBuilding.cs b/OpenRa.Game/Traits/RenderBuilding.cs index bc11951b61..0975f20921 100644 --- a/OpenRa.Game/Traits/RenderBuilding.cs +++ b/OpenRa.Game/Traits/RenderBuilding.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using OpenRa.Game.Graphics; +using OpenRa.Game.Effects; namespace OpenRa.Game.Traits { diff --git a/OpenRa.Game/World.cs b/OpenRa.Game/World.cs index 4c3507ab88..fff8797889 100644 --- a/OpenRa.Game/World.cs +++ b/OpenRa.Game/World.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using OpenRa.Game.Graphics; using OpenRa.Game.Traits; +using OpenRa.Game.Effects; namespace OpenRa.Game { diff --git a/sequences.xml b/sequences.xml index 78ee18e470..cb97504f5f 100644 --- a/sequences.xml +++ b/sequences.xml @@ -603,4 +603,10 @@ + + + + + + \ No newline at end of file diff --git a/session.ini b/session.ini index 7c5bb62817..0139c33220 100644 --- a/session.ini +++ b/session.ini @@ -9,4 +9,5 @@ s1=Multi2,mcv,600,12445,0,Guard,None s2=Multi1,mcv,600,12505,0,Guard,None s3=Multi3,mcv,600,2910,0,Guard,None +s4=Multi7,3tnk,600,13017,0,Guard,None diff --git a/units.ini b/units.ini index a735981e70..ca963a9373 100755 --- a/units.ini +++ b/units.ini @@ -99,7 +99,8 @@ Traits=Unit, Mobile, RenderUnit Description=Destroyer WaterBound=yes BuiltAt=syrd -Traits=Unit, Mobile, RenderUnit +Traits=Unit, Mobile, Turreted, AttackTurreted, RenderUnitTurreted +PrimaryOffset=0,-8,0,-3 [CA] Description=Cruiser WaterBound=yes @@ -556,7 +557,6 @@ Fireball - [WarheadTypes] SA HE