diff --git a/OpenRA.Game/GameRules/WeaponInfo.cs b/OpenRA.Game/GameRules/WeaponInfo.cs index 37962ee8f4..f6dff5861a 100644 --- a/OpenRA.Game/GameRules/WeaponInfo.cs +++ b/OpenRA.Game/GameRules/WeaponInfo.cs @@ -47,6 +47,8 @@ namespace OpenRA.GameRules public readonly DamageModel DamageModel = DamageModel.Normal; [Desc("Whether we should prevent prone response for infantry.")] public readonly bool PreventProne = false; + [Desc("Whether to use the provided Damage value as a maximum health percentage.")] + public readonly bool ScaleDamageByTargetHealth = false; public float EffectivenessAgainst(ActorInfo ai) { @@ -106,6 +108,7 @@ namespace OpenRA.GameRules public readonly bool Charges = false; public readonly bool Underwater = false; public readonly string[] ValidTargets = { "Ground", "Water" }; + public readonly string[] InvalidTargets = { }; public readonly int BurstDelay = 5; public readonly float MinRange = 0; @@ -140,7 +143,8 @@ namespace OpenRA.GameRules public bool IsValidAgainst(Actor a) { var targetable = a.TraitOrDefault(); - if (targetable == null || !ValidTargets.Intersect(targetable.TargetTypes).Any()) + if (targetable == null || !ValidTargets.Intersect(targetable.TargetTypes).Any() + || InvalidTargets.Intersect(targetable.TargetTypes).Any()) return false; if (Warheads.All(w => w.EffectivenessAgainst(a.Info) <= 0)) @@ -152,7 +156,8 @@ namespace OpenRA.GameRules public bool IsValidAgainst(FrozenActor a) { var targetable = a.Info.Traits.GetOrDefault(); - if (targetable == null || !ValidTargets.Intersect(targetable.GetTargetTypes()).Any()) + if (targetable == null || !ValidTargets.Intersect(targetable.GetTargetTypes()).Any() + || InvalidTargets.Intersect(targetable.GetTargetTypes()).Any()) return false; if (Warheads.All(w => w.EffectivenessAgainst(a.Info) <= 0)) diff --git a/OpenRA.Mods.RA/Combat.cs b/OpenRA.Mods.RA/Combat.cs index c50613a2a0..b327278fe5 100755 --- a/OpenRA.Mods.RA/Combat.cs +++ b/OpenRA.Mods.RA/Combat.cs @@ -108,7 +108,7 @@ namespace OpenRA.Mods.RA foreach (var victim in hitActors) { - var damage = (int)GetDamageToInflict(pos, victim, warhead, weapon, firepowerModifier); + var damage = (int)GetDamageToInflict(pos, victim, warhead, weapon, firepowerModifier, true); victim.InflictDamage(firedBy, damage, warhead); } } break; @@ -117,8 +117,10 @@ namespace OpenRA.Mods.RA { foreach (var t in world.FindTilesInCircle(targetTile, warhead.Size[0])) foreach (var unit in world.FindActorsInBox(t, t)) - unit.InflictDamage(firedBy, - (int)(warhead.Damage * warhead.EffectivenessAgainst(unit.Info)), warhead); + { + var damage = (int)GetDamageToInflict(pos, unit, warhead, weapon, firepowerModifier, false); + unit.InflictDamage(firedBy, damage, warhead); + } } break; } } @@ -161,21 +163,26 @@ namespace OpenRA.Mods.RA return (falloff[u] * (1 - t)) + (falloff[u + 1] * t); } - static float GetDamageToInflict(WPos pos, Actor target, WarheadInfo warhead, WeaponInfo weapon, float modifier) + static float GetDamageToInflict(WPos pos, Actor target, WarheadInfo warhead, WeaponInfo weapon, float modifier, bool withFalloff) { // don't hit air units with splash from ground explosions, etc if (!weapon.IsValidAgainst(target)) - return 0f; + return 0; - var health = target.Info.Traits.GetOrDefault(); - if( health == null ) return 0f; + var healthInfo = target.Info.Traits.GetOrDefault(); + if (healthInfo == null) + return 0; - var distance = (int)Math.Max(0, (target.CenterPosition - pos).Length * Game.CellSize / 1024 - health.Radius); - var falloff = (float)GetDamageFalloff(distance / warhead.Spread); - var rawDamage = (float)(warhead.Damage * modifier * falloff); - var multiplier = (float)warhead.EffectivenessAgainst(target.Info); - - return (float)(rawDamage * multiplier); + var rawDamage = (float)warhead.Damage; + if (warhead.ScaleDamageByTargetHealth) + rawDamage = (float)(rawDamage / 100 * healthInfo.HP); + if (withFalloff) + { + var distance = (int)Math.Max(0, (target.CenterPosition - pos).Length * Game.CellSize / 1024 - healthInfo.Radius); + var falloff = (float)GetDamageFalloff(distance / warhead.Spread); + rawDamage = (float)(falloff * rawDamage); + } + return (float)(rawDamage * modifier * (float)warhead.EffectivenessAgainst(target.Info)); } } } diff --git a/OpenRA.Mods.RA/DemoTruck.cs b/OpenRA.Mods.RA/DemoTruck.cs index 3ae88887dd..b0339b01bf 100644 --- a/OpenRA.Mods.RA/DemoTruck.cs +++ b/OpenRA.Mods.RA/DemoTruck.cs @@ -33,14 +33,14 @@ namespace OpenRA.Mods.RA { get { - yield return new TargetTypeOrderTargeter("DemoTruck", "DemoAttack", 5, "attack", true, false) { ForceAttack = false }; - yield return new DeployOrderTargeter("DemoDeploy", 5); + yield return new TargetTypeOrderTargeter("DetonateAttack", "DetonateAttack", 5, "attack", true, false) { ForceAttack = false }; + yield return new DeployOrderTargeter("Detonate", 5); } } public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued) { - if (order.OrderID != "DemoAttack" && order.OrderID != "DemoDeploy") + if (order.OrderID != "DetonateAttack" && order.OrderID != "Detonate") return null; if (target.Type == TargetType.FrozenActor) @@ -56,7 +56,7 @@ namespace OpenRA.Mods.RA public void ResolveOrder(Actor self, Order order) { - if (order.OrderString == "DemoAttack") + if (order.OrderString == "DetonateAttack") { var target = self.ResolveFrozenActorOrder(order, Color.Red); if (target.Type != TargetType.Actor) @@ -70,7 +70,7 @@ namespace OpenRA.Mods.RA self.QueueActivity(new CallFunc(() => Explode(self))); } - if (order.OrderString == "DemoDeploy") + else if (order.OrderString == "Detonate") Explode(self); } } diff --git a/OpenRA.Mods.RA/MadTank.cs b/OpenRA.Mods.RA/MadTank.cs new file mode 100644 index 0000000000..04e5c0c205 --- /dev/null +++ b/OpenRA.Mods.RA/MadTank.cs @@ -0,0 +1,160 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 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. For more information, + * see COPYING. + */ +#endregion + +using System.Collections.Generic; +using System.Drawing; +using OpenRA.FileFormats; +using OpenRA.Mods.RA.Activities; +using OpenRA.Mods.RA.Move; +using OpenRA.Mods.RA.Orders; +using OpenRA.Mods.RA.Render; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA +{ + class MadTankInfo : ITraitInfo, Requires, Requires + { + public readonly string ThumpSequence = "piston"; + public readonly int ThumpInterval = 8; + [WeaponReference] + public readonly string ThumpDamageWeapon = "MADTankThump"; + public readonly int ThumpShakeIntensity = 3; + public readonly float2 ThumpShakeMultiplier = new float2(1, 0); + public readonly int ThumpShakeTime = 10; + + public readonly int ChargeDelay = 96; + public readonly string ChargeSound = "madchrg2.aud"; + + public readonly int DetonationDelay = 42; + public readonly string DetonationSound = "madexplo.aud"; + [WeaponReference] + public readonly string DetonationWeapon = "MADTankDetonate"; + + [ActorReference] + public readonly string DriverActor = "e1"; + + public object Create(ActorInitializer init) { return new MadTank(init.self, this); } + } + + class MadTank : IIssueOrder, IResolveOrder, IOrderVoice, ITick + { + readonly Actor self; + readonly MadTankInfo info; + readonly RenderUnit renderUnit; + readonly ScreenShaker screenShaker; + bool deployed; + int tick; + + public MadTank(Actor self, MadTankInfo info) + { + this.self = self; + this.info = info; + renderUnit = self.Trait(); + screenShaker = self.World.WorldActor.Trait(); + } + + public void Tick(Actor self) + { + if (!deployed) + return; + + if (++tick >= info.ThumpInterval) + { + if (info.ThumpDamageWeapon != null) + Combat.DoExplosion(self, info.ThumpDamageWeapon, self.CenterPosition); + screenShaker.AddEffect(info.ThumpShakeTime, self.CenterPosition, info.ThumpShakeIntensity, info.ThumpShakeMultiplier); + tick = 0; + } + } + + public IEnumerable Orders + { + get + { + yield return new TargetTypeOrderTargeter("DetonateAttack", "DetonateAttack", 5, "attack", true, false) { ForceAttack = false }; + yield return new DeployOrderTargeter("Detonate", 5); + } + } + + public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued) + { + if (order.OrderID != "DetonateAttack" && order.OrderID != "Detonate") + return null; + + if (target.Type == TargetType.FrozenActor) + return new Order(order.OrderID, self, queued) { ExtraData = target.FrozenActor.ID }; + + return new Order(order.OrderID, self, queued) { TargetActor = target.Actor }; + } + + public string VoicePhraseForOrder(Actor self, Order order) + { + return "Attack"; + } + + void Detonate() + { + self.World.AddFrameEndTask(w => + { + if (info.DetonationWeapon != null) + Combat.DoExplosion(self, info.DetonationWeapon, self.CenterPosition); + self.Kill(self); + }); + } + + void EjectDriver() + { + var driver = self.World.CreateActor(info.DriverActor.ToLowerInvariant(), new TypeDictionary + { + new LocationInit(self.Location), + new OwnerInit(self.Owner) + }); + var driverMobile = driver.TraitOrDefault(); + if (driverMobile != null) + driverMobile.Nudge(driver, driver, true); + } + + void StartDetonationSequence() + { + self.World.AddFrameEndTask(w => EjectDriver()); + if (info.ThumpSequence != null) + renderUnit.PlayCustomAnimRepeating(self, info.ThumpSequence); + deployed = true; + self.QueueActivity(new Wait(info.ChargeDelay, false)); + self.QueueActivity(new CallFunc(() => Sound.Play(info.ChargeSound, self.CenterPosition))); + self.QueueActivity(new Wait(info.DetonationDelay, false)); + self.QueueActivity(new CallFunc(() => Sound.Play(info.DetonationSound, self.CenterPosition))); + self.QueueActivity(new CallFunc(Detonate)); + } + + public void ResolveOrder(Actor self, Order order) + { + if (order.OrderString == "DetonateAttack") + { + var target = self.ResolveFrozenActorOrder(order, Color.Red); + if (target.Type != TargetType.Actor) + return; + + if (!order.Queued) + self.CancelActivity(); + + self.SetTargetLine(target, Color.Red); + self.QueueActivity(new MoveAdjacentTo(target)); + self.QueueActivity(new CallFunc(StartDetonationSequence)); + } + + else if (order.OrderString == "Detonate") + { + self.CancelActivity(); + self.QueueActivity(new CallFunc(StartDetonationSequence)); + } + } + } +} diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index c627a2aef8..da1468c08b 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -251,6 +251,7 @@ + diff --git a/OpenRA.Mods.RA/Widgets/WorldCommandWidget.cs b/OpenRA.Mods.RA/Widgets/WorldCommandWidget.cs index d6ec34dd07..8243c153f1 100644 --- a/OpenRA.Mods.RA/Widgets/WorldCommandWidget.cs +++ b/OpenRA.Mods.RA/Widgets/WorldCommandWidget.cs @@ -129,7 +129,7 @@ namespace OpenRA.Mods.RA.Widgets PerformKeyboardOrderOnSelection(a => new Order("ReturnToBase", a, false)); PerformKeyboardOrderOnSelection(a => new Order("DeployTransform", a, false)); PerformKeyboardOrderOnSelection(a => new Order("Unload", a, false)); - PerformKeyboardOrderOnSelection(a => new Order("DemoDeploy", a, false)); + PerformKeyboardOrderOnSelection(a => new Order("Detonate", a, false)); return true; } diff --git a/mods/ra/bits/madchrg2.aud b/mods/ra/bits/madchrg2.aud new file mode 100644 index 0000000000..be920c1693 Binary files /dev/null and b/mods/ra/bits/madchrg2.aud differ diff --git a/mods/ra/bits/madexplo.aud b/mods/ra/bits/madexplo.aud new file mode 100644 index 0000000000..d9ade0660f Binary files /dev/null and b/mods/ra/bits/madexplo.aud differ diff --git a/mods/ra/bits/qtnk.shp b/mods/ra/bits/qtnk.shp new file mode 100644 index 0000000000..94853253b8 Binary files /dev/null and b/mods/ra/bits/qtnk.shp differ diff --git a/mods/ra/bits/qtnkicon.shp b/mods/ra/bits/qtnkicon.shp new file mode 100644 index 0000000000..10ab428a8a Binary files /dev/null and b/mods/ra/bits/qtnkicon.shp differ diff --git a/mods/ra/rules/defaults.yaml b/mods/ra/rules/defaults.yaml index 5743e46b79..4cc6308dde 100644 --- a/mods/ra/rules/defaults.yaml +++ b/mods/ra/rules/defaults.yaml @@ -215,7 +215,7 @@ Selectable: Priority: 3 TargetableBuilding: - TargetTypes: Ground, C4, DemoTruck + TargetTypes: Ground, C4, DetonateAttack Building: Dimensions: 1,1 Footprint: x @@ -272,7 +272,7 @@ Selectable: Priority: 1 TargetableBuilding: - TargetTypes: Ground, C4, DemoTruck + TargetTypes: Ground, C4, DetonateAttack RenderBuildingWall: HasMakeAnimation: false Palette: terrain @@ -323,7 +323,7 @@ Armor: Type: Light TargetableBuilding: - TargetTypes: Ground, DemoTruck + TargetTypes: Ground, DetonateAttack ^CivInfantry: Inherits: ^Infantry diff --git a/mods/ra/rules/vehicles.yaml b/mods/ra/rules/vehicles.yaml index e0fea03a4e..94550af14f 100644 --- a/mods/ra/rules/vehicles.yaml +++ b/mods/ra/rules/vehicles.yaml @@ -766,3 +766,37 @@ CTNK: LocalYaw: -100 AttackFrontal: ChronoshiftDeploy: + +QTNK: + Inherits: ^Tank + Buildable: + Queue: Vehicle + BuildPaletteOrder: 150 + Prerequisites: fix,stek + Owner: soviet + Hotkey: q + Valued: + Cost: 2500 + Tooltip: + Name: MAD Tank + Description: Deals seismic damage to nearby vehicles\nand structures.\n Strong vs Vehicles, Buildings\n Weak vs Infantry + Health: + HP: 900 + Armor: + Type: Heavy + Mobile: + Speed: 4 + Crushes: wall, atmine, crate, infantry + RevealsShroud: + Range: 6 + Selectable: + Bounds: 44,38,0,-4 + RenderUnit: + AttackMove: + JustMove: True + Explodes: + Weapon: UnitExplodeSmall + MadTank: + -EjectOnDeath: + TargetableUnit: + TargetTypes: Ground, MADTank \ No newline at end of file diff --git a/mods/ra/sequences/vehicles.yaml b/mods/ra/sequences/vehicles.yaml index b1688f105d..a257dab998 100644 --- a/mods/ra/sequences/vehicles.yaml +++ b/mods/ra/sequences/vehicles.yaml @@ -253,4 +253,15 @@ ctnk: Start: 0 Length: 5 icon: ctnkicon + Start: 0 + +qtnk: + idle: + Start: 0 + Facings: 32 + piston: + Start: 32 + Facings: 8 + Length: 8 + icon: qtnkicon Start: 0 \ No newline at end of file diff --git a/mods/ra/weapons.yaml b/mods/ra/weapons.yaml index 219395caab..ef5f841e74 100644 --- a/mods/ra/weapons.yaml +++ b/mods/ra/weapons.yaml @@ -1327,3 +1327,26 @@ Mandible: Concrete: 10% InfDeath: 1 Damage: 60 + +MADTankThump: + InvalidTargets: MADTank + Warhead: + DamageModel: PerCell + Damage: 1 + ScaleDamageByTargetHealth: True + Versus: + None: 0% + Size: 7,6 + +MADTankDetonate: + InvalidTargets: MADTank + Warhead: + DamageModel: PerCell + Damage: 19 + ScaleDamageByTargetHealth: True + Versus: + None: 0% + Size: 7,6 + Explosion: med_explosion + ImpactSound: mineblo1.aud + SmudgeType: Crater