Merge pull request #8210 from matija-hustic/ra2_veterancy_icon

Veteran unit production & unlocking through spies.
This commit is contained in:
Matthias Mailänder
2015-07-06 09:38:09 +02:00
23 changed files with 360 additions and 10 deletions

View File

@@ -295,6 +295,7 @@
<Compile Include="Traits\Buildings\TargetableBuilding.cs" />
<Compile Include="Traits\Burns.cs" />
<Compile Include="Traits\C4Demolition.cs" />
<Compile Include="Traits\VeteranProductionIconOverlay.cs" />
<Compile Include="Traits\Capturable.cs" />
<Compile Include="Traits\CaptureNotification.cs" />
<Compile Include="Traits\Captures.cs" />
@@ -473,6 +474,7 @@
<Compile Include="Traits\TransformOnPassenger.cs" />
<Compile Include="Traits\Transforms.cs" />
<Compile Include="Traits\Turreted.cs" />
<Compile Include="Traits\ProduceableWithLevel.cs" />
<Compile Include="Traits\Upgrades\DeployToUpgrade.cs" />
<Compile Include="Traits\Upgrades\DisableUpgrade.cs" />
<Compile Include="Traits\Upgrades\GainsStatUpgrades.cs" />

View File

@@ -18,7 +18,7 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[Desc("This actor's experience increases when it has killed a GivesExperience actor.")]
public class GainsExperienceInfo : ITraitInfo, Requires<ValuedInfo>
public class GainsExperienceInfo : ITraitInfo, Requires<ValuedInfo>, Requires<UpgradeManagerInfo>
{
[FieldLoader.LoadUsing("LoadUpgrades")]
[Desc("Upgrades to grant at each level",
@@ -29,6 +29,9 @@ namespace OpenRA.Mods.Common.Traits
[Desc("Palette for the level up sprite.")]
public readonly string LevelUpPalette = "effect";
[Desc("Should the level-up animation be suppressed when actor is created?")]
public readonly bool SuppressLevelupAnimation = true;
public object Create(ActorInitializer init) { return new GainsExperience(init, this); }
static object LoadUpgrades(MiniYaml y)
@@ -56,6 +59,7 @@ namespace OpenRA.Mods.Common.Traits
{
readonly Actor self;
readonly GainsExperienceInfo info;
readonly UpgradeManager um;
readonly List<Pair<int, string[]>> nextLevel = new List<Pair<int, string[]>>();
@@ -77,18 +81,20 @@ namespace OpenRA.Mods.Common.Traits
nextLevel.Add(Pair.New(kv.Key * cost, kv.Value));
if (init.Contains<ExperienceInit>())
GiveExperience(init.Get<ExperienceInit, int>());
GiveExperience(init.Get<ExperienceInit, int>(), info.SuppressLevelupAnimation);
um = self.Trait<UpgradeManager>();
}
public bool CanGainLevel { get { return Level < MaxLevel; } }
public void GiveLevels(int numLevels)
public void GiveLevels(int numLevels, bool silent = false)
{
var newLevel = Math.Min(Level + numLevels, MaxLevel);
GiveExperience(nextLevel[newLevel - 1].First - experience);
GiveExperience(nextLevel[newLevel - 1].First - experience, silent);
}
public void GiveExperience(int amount)
public void GiveExperience(int amount, bool silent = false)
{
experience += amount;
@@ -98,11 +104,12 @@ namespace OpenRA.Mods.Common.Traits
Level++;
var um = self.TraitOrDefault<UpgradeManager>();
if (um != null)
foreach (var u in upgrades)
um.GrantUpgrade(self, u, this);
}
if (!silent)
{
Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Sounds", "LevelUp", self.Owner.Country.Race);
self.World.AddFrameEndTask(w => w.Add(new CrateEffect(self, "levelup", info.LevelUpPalette)));
}

View File

@@ -0,0 +1,52 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 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 OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[Desc("Actors possessing this trait should define the GainsExperience trait. When the prerequisites are fulfilled, ",
"this trait grants a level-up to newly spawned actors. If additionally the actor's owning player defines the ProductionIconOverlay ",
"trait, the production queue icon renders with an overlay defined in that trait.")]
public class ProduceableWithLevelInfo : ITraitInfo, Requires<GainsExperienceInfo>
{
public readonly string[] Prerequisites = { };
[Desc("Number of levels to give to the actor on creation.")]
public readonly int InitialLevels = 1;
[Desc("Should the level-up animation be suppressed when actor is created?")]
public readonly bool SuppressLevelupAnimation = true;
public object Create(ActorInitializer init) { return new ProduceableWithLevel(init, this); }
}
public class ProduceableWithLevel : INotifyCreated
{
readonly ProduceableWithLevelInfo info;
public ProduceableWithLevel(ActorInitializer init, ProduceableWithLevelInfo info)
{
this.info = info;
}
public void Created(Actor self)
{
if (!self.Owner.PlayerActor.Trait<TechTree>().HasPrerequisites(info.Prerequisites))
return;
var ge = self.Trait<GainsExperience>();
if (!ge.CanGainLevel)
return;
ge.GiveLevels(info.InitialLevels, info.SuppressLevelupAnimation);
}
}
}

View File

@@ -40,7 +40,7 @@ namespace OpenRA.Mods.Common.Traits
/// Abstract base for enabling and disabling trait using upgrades.
/// Requires basing *Info on UpgradableTraitInfo and using base(info) constructor.
/// Note that EnabledByUpgrade is not called at creation even if this starts as enabled.
/// </summary>,
/// </summary>
public abstract class UpgradableTrait<InfoType> : IUpgradable, IDisabledTrait, ISync where InfoType : UpgradableTraitInfo
{
public readonly InfoType Info;

View File

@@ -0,0 +1,151 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 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 OpenRA.Graphics;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[Desc("Attach this to the player actor. When attached, enables all actors possessing the LevelupWhenCreated ",
"trait to have their production queue icons render with an overlay defined in this trait. ",
"The icon change occurs when LevelupWhenCreated.Prerequisites are met.")]
public class VeteranProductionIconOverlayInfo : ITraitInfo, Requires<TechTreeInfo>
{
[Desc("Image used for the overlay.")]
public readonly string Image = null;
[Desc("Sequence used for the overlay (cannot be animated).")]
[SequenceReference("Image")] public readonly string Sequence = null;
[Desc("Palette to render the sprite in. Reference the world actor's PaletteFrom* traits.")]
public readonly string Palette = "chrome";
[Desc("Point on the production icon's used as reference for offsetting the overlay. ",
"Possible values are any combination of Top, VCenter, Bottom and Left, HCenter, Right separated by a comma.")]
public readonly ReferencePoints ReferencePoint = ReferencePoints.Top | ReferencePoints.Left;
[Desc("Pixel offset relative to the icon's reference point.")]
public readonly int2 Offset = int2.Zero;
[Desc("Visual scale of the overlay.")]
public readonly float Scale = 1f;
public object Create(ActorInitializer init) { return new VeteranProductionIconOverlay(init, this); }
}
public class VeteranProductionIconOverlay : ITechTreeElement, IProductionIconOverlay
{
// HACK: TechTree doesn't associate Watcher.Key with the registering ITechTreeElement.
// So in a situation where multiple ITechTreeElements register Watchers with the same Key,
// and one removes its Watcher, all other ITechTreeElements' Watchers get removed too.
// This makes sure that the keys are unique with respect to the registering ITechTreeElement.
const string Prefix = "ProductionIconOverlay.";
readonly Actor self;
readonly Sprite sprite;
readonly VeteranProductionIconOverlayInfo info;
Dictionary<ActorInfo, bool> overlayActive = new Dictionary<ActorInfo, bool>();
public VeteranProductionIconOverlay(ActorInitializer init, VeteranProductionIconOverlayInfo info)
{
self = init.Self;
var anim = new Animation(self.World, info.Image);
anim.Play(info.Sequence);
sprite = anim.Image;
this.info = info;
var ttc = self.Trait<TechTree>();
foreach (var a in self.World.Map.Rules.Actors.Values)
{
var uwc = a.Traits.GetOrDefault<ProduceableWithLevelInfo>();
if (uwc != null)
ttc.Add(MakeKey(a.Name), uwc.Prerequisites, 0, this);
}
}
public Sprite Sprite()
{
return sprite;
}
public string Palette()
{
return info.Palette;
}
public float Scale()
{
return info.Scale;
}
public float2 Offset(float2 iconSize)
{
float offsetX = 0, offsetY = 0;
switch (info.ReferencePoint & (ReferencePoints)3)
{
case ReferencePoints.Top:
offsetY = (-iconSize.Y + sprite.Size.Y) / 2;
break;
case ReferencePoints.VCenter:
break;
case ReferencePoints.Bottom:
offsetY = (iconSize.Y - sprite.Size.Y) / 2;
break;
}
switch (info.ReferencePoint & (ReferencePoints)(3 << 2))
{
case ReferencePoints.Left:
offsetX = (-iconSize.X + sprite.Size.X) / 2;
break;
case ReferencePoints.HCenter:
break;
case ReferencePoints.Right:
offsetX = (iconSize.X - sprite.Size.X) / 2;
break;
}
return new float2(offsetX, offsetY) + info.Offset;
}
public bool IsOverlayActive(ActorInfo ai)
{
bool isActive;
overlayActive.TryGetValue(ai, out isActive);
return isActive;
}
static string MakeKey(string name)
{
return Prefix + name;
}
static string GetName(string key)
{
return key.Substring(Prefix.Length);
}
public void PrerequisitesAvailable(string key)
{
var ai = self.World.Map.Rules.Actors[GetName(key)];
overlayActive[ai] = true;
}
public void PrerequisitesUnavailable(string key) { }
public void PrerequisitesItemHidden(string key) { }
public void PrerequisitesItemVisible(string key) { }
}
}

View File

@@ -75,6 +75,15 @@ namespace OpenRA.Mods.Common.Traits
void PrerequisitesItemVisible(string key);
}
public interface IProductionIconOverlay
{
Sprite Sprite();
string Palette();
float Scale();
float2 Offset(float2 iconSize);
bool IsOverlayActive(ActorInfo ai);
}
public interface INotifyTransform { void BeforeTransform(Actor self); void OnTransform(Actor self); void AfterTransform(Actor toActor); }
public interface IAcceptResources

View File

@@ -79,6 +79,11 @@ namespace OpenRA.Mods.Common.Widgets
var location = new float2(RenderBounds.Location) + new float2(queue.i * (IconWidth + IconSpacing), 0);
WidgetUtils.DrawSHPCentered(icon.Image, location + 0.5f * iconSize, worldRenderer.Palette(bi.IconPalette), 0.5f);
var pio = queue.Trait.Actor.Owner.PlayerActor.TraitsImplementing<IProductionIconOverlay>().FirstOrDefault();
if (pio != null && pio.IsOverlayActive(actor))
WidgetUtils.DrawSHPCentered(pio.Sprite(), location + 0.5f * iconSize + pio.Offset(0.5f * iconSize),
worldRenderer.Palette(pio.Palette()), 0.5f * pio.Scale());
var clock = clocks[queue.Trait];
clock.PlayFetchIndex("idle",
() => current.TotalTime == 0 ? 0 : ((current.TotalTime - current.RemainingTime)

View File

@@ -351,11 +351,18 @@ namespace OpenRA.Mods.Common.Widgets
var buildableItems = CurrentQueue.BuildableItems();
var pio = currentQueue.Actor.Owner.PlayerActor.TraitsImplementing<IProductionIconOverlay>().FirstOrDefault();
var pioOffset = pio != null ? pio.Offset(IconSize) : new float2(0, 0);
// Icons
foreach (var icon in icons.Values)
{
WidgetUtils.DrawSHPCentered(icon.Sprite, icon.Pos + iconOffset, icon.Palette);
// Draw the ProductionIconOverlay's sprite
if (pio != null && pio.IsOverlayActive(icon.Actor))
WidgetUtils.DrawSHPCentered(pio.Sprite(), icon.Pos + iconOffset + pioOffset, worldRenderer.Palette(pio.Palette()), pio.Scale());
// Build progress
if (icon.Queued.Count > 0)
{

Binary file not shown.

Binary file not shown.

View File

@@ -122,6 +122,8 @@ MIG:
SmokeTrailWhenDamaged:
Offset: -853,0,171
Interval: 2
ProduceableWithLevel:
Prerequisites: aircraft.upgraded
YAK:
Inherits: ^Plane
@@ -178,6 +180,8 @@ YAK:
SmokeTrailWhenDamaged:
Offset: -853,0,0
Interval: 2
ProduceableWithLevel:
Prerequisites: aircraft.upgraded
TRAN:
Inherits: ^Helicopter
@@ -265,6 +269,8 @@ HELI:
HuskActor: HELI.Husk
SmokeTrailWhenDamaged:
Offset: -427,0,0
ProduceableWithLevel:
Prerequisites: aircraft.upgraded
HIND:
Inherits: ^Helicopter
@@ -317,6 +323,8 @@ HIND:
HuskActor: HIND.Husk
SmokeTrailWhenDamaged:
Offset: -427,0,0
ProduceableWithLevel:
Prerequisites: aircraft.upgraded
U2:
Inherits: ^Plane

View File

@@ -67,6 +67,8 @@ E1:
AttackFrontal:
WithInfantryBody:
AttackSequence: shoot
ProduceableWithLevel:
Prerequisites: barracks.upgraded
E2:
Inherits: ^Soldier
@@ -98,6 +100,8 @@ E2:
Explodes:
Weapon: UnitExplodeSmall
Chance: 50
ProduceableWithLevel:
Prerequisites: barracks.upgraded
E3:
Inherits: ^Soldier
@@ -125,6 +129,8 @@ E3:
AttackFrontal:
WithInfantryBody:
AttackSequence: shoot
ProduceableWithLevel:
Prerequisites: barracks.upgraded
E4:
Inherits: ^Soldier
@@ -150,6 +156,8 @@ E4:
AttackFrontal:
WithInfantryBody:
AttackSequence: shoot
ProduceableWithLevel:
Prerequisites: barracks.upgraded
E6:
Inherits: ^Soldier
@@ -278,6 +286,8 @@ E7:
AnnounceOnKill:
Voiced:
VoiceSet: TanyaVoice
ProduceableWithLevel:
Prerequisites: barracks.upgraded
MEDI:
Inherits: ^Soldier
@@ -477,6 +487,8 @@ SHOK:
AttackSequence: shoot
Voiced:
VoiceSet: ShokVoice
ProduceableWithLevel:
Prerequisites: barracks.upgraded
SNIPER:
Inherits: ^Soldier
@@ -516,6 +528,8 @@ SNIPER:
DetectCloaked:
Range: 6
-MustBeDestroyed:
ProduceableWithLevel:
Prerequisites: barracks.upgraded
Zombie:
Inherits: ^Soldier

View File

@@ -362,6 +362,18 @@ powerproxy.paratroopers:
DisplayBeacon: true
BeaconPoster: pinficon
barracks.upgraded:
AlwaysVisible:
ProvidesPrerequisite:
vehicles.upgraded:
AlwaysVisible:
ProvidesPrerequisite:
aircraft.upgraded:
AlwaysVisible:
ProvidesPrerequisite:
mpspawn:
AlwaysVisible:
Immobile:

View File

@@ -10,6 +10,10 @@
Filename: temperat.pal
ShadowIndex: 3
AllowModifiers: false
PaletteFromFile@cameo-chevron:
Name: cameo-chevron
Filename: cameo-chevron.pal
AllowModifiers: false
PaletteFromFile@effect:
Name: effect
Filename: temperat.pal

View File

@@ -65,4 +65,9 @@ Player:
Prerequisites: techlevel.infonly, techlevel.low, techlevel.medium, techlevel.unrestricted
GlobalUpgradeManager:
EnemyWatcher:
VeteranProductionIconOverlay:
Offset: 2, 2
Image: cameo-chevron
Sequence: idle
Palette: cameo-chevron

View File

@@ -855,6 +855,10 @@ WEAP:
Power:
Amount: -30
ProvidesPrerequisite@buildingname:
TargetableBuilding:
TargetTypes: Ground, C4, DetonateAttack, Structure, SpyInfiltrate
InfiltrateForSupportPower:
Proxy: vehicles.upgraded
FACT:
Inherits: ^Building
@@ -1080,6 +1084,10 @@ HPAD:
RequiresPrerequisites: structures.germany
Prerequisite: aircraft.germany
ProvidesPrerequisite@buildingname:
TargetableBuilding:
TargetTypes: Ground, C4, DetonateAttack, Structure, SpyInfiltrate
InfiltrateForSupportPower:
Proxy: aircraft.upgraded
AFLD:
Inherits: ^Building
@@ -1185,6 +1193,10 @@ AFLD:
Power:
Amount: -20
ProvidesPrerequisite@buildingname:
TargetableBuilding:
TargetTypes: Ground, C4, DetonateAttack, Structure, SpyInfiltrate
InfiltrateForSupportPower:
Proxy: aircraft.upgraded
POWR:
Inherits: ^Building
@@ -1352,6 +1364,10 @@ BARR:
Power:
Amount: -20
ProvidesPrerequisite@buildingname:
InfiltrateForSupportPower:
Proxy: barracks.upgraded
TargetableBuilding:
TargetTypes: Ground, C4, DetonateAttack, Structure, SpyInfiltrate
KENN:
Inherits: ^Building
@@ -1455,6 +1471,10 @@ TENT:
Power:
Amount: -20
ProvidesPrerequisite@buildingname:
InfiltrateForSupportPower:
Proxy: barracks.upgraded
TargetableBuilding:
TargetTypes: Ground, C4, DetonateAttack, Structure, SpyInfiltrate
FIX:
Inherits: ^Building

View File

@@ -29,6 +29,8 @@ V2RL:
WithAttackAnimation:
AimSequence: aim
ReloadPrefix: empty-
ProduceableWithLevel:
Prerequisites: vehicles.upgraded
1TNK:
Inherits: ^Tank
@@ -67,6 +69,8 @@ V2RL:
EmptyWeapon: UnitExplodeSmall
LeavesHusk:
HuskActor: 1TNK.Husk
ProduceableWithLevel:
Prerequisites: vehicles.upgraded
2TNK:
Inherits: ^Tank
@@ -107,6 +111,8 @@ V2RL:
HuskActor: 2TNK.Husk
SelectionDecorations:
VisualBounds: 28,28
ProduceableWithLevel:
Prerequisites: vehicles.upgraded
3TNK:
Inherits: ^Tank
@@ -147,6 +153,8 @@ V2RL:
HuskActor: 3TNK.Husk
SelectionDecorations:
VisualBounds: 28,28
ProduceableWithLevel:
Prerequisites: vehicles.upgraded
4TNK:
Inherits: ^Tank
@@ -201,6 +209,8 @@ V2RL:
DamageCooldown: 150
SelectionDecorations:
VisualBounds: 44,38,0,-4
ProduceableWithLevel:
Prerequisites: vehicles.upgraded
ARTY:
Inherits: ^Tank
@@ -232,6 +242,8 @@ ARTY:
Weapon: UnitExplode
Chance: 75
AutoTarget:
ProduceableWithLevel:
Prerequisites: vehicles.upgraded
HARV:
Inherits: ^Vehicle
@@ -354,6 +366,8 @@ JEEP:
Types: Infantry
MaxWeight: 1
PipCount: 1
ProduceableWithLevel:
Prerequisites: vehicles.upgraded
APC:
Inherits: ^Tank
@@ -386,6 +400,8 @@ APC:
Types: Infantry
MaxWeight: 5
PipCount: 5
ProduceableWithLevel:
Prerequisites: vehicles.upgraded
MNLY.AP:
Inherits: ^Tank
@@ -569,6 +585,8 @@ TTNK:
SelectionDecorations:
VisualBounds: 30,30
AutoTarget:
ProduceableWithLevel:
Prerequisites: vehicles.upgraded
FTRK:
Inherits: ^Vehicle
@@ -604,6 +622,8 @@ FTRK:
AutoTarget:
SelectionDecorations:
VisualBounds: 28,28
ProduceableWithLevel:
Prerequisites: vehicles.upgraded
DTRK:
Inherits: ^Vehicle
@@ -668,6 +688,8 @@ CTNK:
LocalYaw: -100
AttackFrontal:
PortableChrono:
ProduceableWithLevel:
Prerequisites: vehicles.upgraded
QTNK:
Inherits: ^Tank
@@ -737,4 +759,6 @@ STNK:
DetectCloaked:
Range: 6
-MustBeDestroyed:
ProduceableWithLevel:
Prerequisites: vehicles.upgraded

View File

@@ -382,6 +382,11 @@ rank:
rank:
Length: *
cameo-chevron:
idle:
Length: *
BlendMode: Additive
atomic:
up: atomicup
Length: *

View File

@@ -41,6 +41,8 @@ UMAGON:
Voice: Attack
WithInfantryBody:
AttackSequence: shoot
ProduceableWithLevel:
Prerequisites: barracks.upgraded
CHAMSPY:
Inherits: ^Soldier
@@ -96,6 +98,8 @@ MUTANT:
Voice: Attack
WithInfantryBody:
AttackSequence: shoot
ProduceableWithLevel:
Prerequisites: barracks.upgraded
MWMN:
Inherits: ^Soldier
@@ -119,6 +123,8 @@ MWMN:
Voice: Attack
WithInfantryBody:
AttackSequence: shoot
ProduceableWithLevel:
Prerequisites: barracks.upgraded
MUTANT3:
Inherits: ^Soldier
@@ -142,6 +148,8 @@ MUTANT3:
Voice: Attack
WithInfantryBody:
AttackSequence: shoot
ProduceableWithLevel:
Prerequisites: barracks.upgraded
TRATOS:
Inherits: ^Soldier

View File

@@ -641,6 +641,9 @@ CAARMR:
HP: 800
RenderBuilding:
Palette: player
ProvidesPrerequisite:
Prerequisite: barracks.upgraded
Capturable:
CABHUT:
Inherits: ^CivBuilding

View File

@@ -21,6 +21,8 @@ E2:
Voice: Attack
WithInfantryBody:
AttackSequence: throw
ProduceableWithLevel:
Prerequisites: barracks.upgraded
MEDIC:
Inherits: ^Soldier
@@ -81,6 +83,8 @@ JUMPJET:
WithInfantryBody:
AttackSequence: shoot
-TakeCover:
ProduceableWithLevel:
Prerequisites: barracks.upgraded
GHOST:
Inherits: ^Soldier
@@ -117,4 +121,6 @@ GHOST:
Voice: Attack
WithInfantryBody:
AttackSequence: shoot
ProduceableWithLevel:
Prerequisites: barracks.upgraded

View File

@@ -22,6 +22,8 @@ E3:
Voice: Attack
WithInfantryBody:
AttackSequence: shoot
ProduceableWithLevel:
Prerequisites: barracks.upgraded
CYBORG:
Inherits: ^Cyborg
@@ -53,6 +55,8 @@ CYBORG:
Voice: Attack
SelectionDecorations:
VisualBounds: 16,31,0,-10
ProduceableWithLevel:
Prerequisites: barracks.upgraded
CYC2:
Inherits: ^Cyborg
@@ -86,6 +90,8 @@ CYC2:
Voice: Attack
SelectionDecorations:
VisualBounds: 16,32,-1,-12
ProduceableWithLevel:
Prerequisites: barracks.upgraded
MHIJACK:
Inherits: ^Soldier

View File

@@ -26,6 +26,8 @@ E1:
Voice: Attack
WithInfantryBody:
AttackSequence: shoot
ProduceableWithLevel:
Prerequisites: barracks.upgraded
ENGINEER:
Inherits: ^Soldier