Replace {gender} by {phenotype} and support this tag in VisualActor.

A random phenotype can be chosen by giving multiple tokens to the
template.
This allows giving different looks to the same template.

Comments By: stan, vladislav, elexis
Patch By: Freagarach
Reviewed By: wraitii
Differential Revision: https://code.wildfiregames.com/D1955
This was SVN commit r22586.
This commit is contained in:
wraitii 2019-08-01 19:14:40 +00:00
parent d7a93c3b35
commit eab4f9fdde
26 changed files with 199 additions and 130 deletions

View File

@ -17,7 +17,10 @@ Identity.prototype.Schema =
"</element>" +
"</optional>" +
"<optional>" +
"<element name='Gender' a:help='Unit gender for voices. Choices includes male or female.'>" +
"<element name='Phenotype' a:help='Unit phenotype for voices and visual. If more than one is specified a random one will be chosen.'>" +
"<attribute name='datatype'>" +
"<value>tokens</value>" +
"</attribute>" +
"<text/>" +
"</element>" +
"</optional>" +
@ -95,15 +98,12 @@ Identity.prototype.Init = function()
{
this.classesList = GetIdentityClasses(this.template);
this.visibleClassesList = GetVisibleIdentityClasses(this.template);
if (this.template.Phenotype)
this.phenotype = pickRandom(this.GetPossiblePhenotypes());
else
this.phenotype = "default";
};
Identity.prototype.Deserialize = function ()
{
this.Init();
};
Identity.prototype.Serialize = null; // we have no dynamic state to save
Identity.prototype.GetCiv = function()
{
return this.template.Civ;
@ -114,9 +114,22 @@ Identity.prototype.GetLang = function()
return this.template.Lang || "greek"; // ugly default
};
Identity.prototype.GetGender = function()
/**
* Get a list of possible Phenotypes.
* @return {string[]} A list of possible phenotypes.
*/
Identity.prototype.GetPossiblePhenotypes = function()
{
return this.template.Gender || "male"; // ugly default
return this.template.Phenotype._string.split(/\s+/);
};
/**
* Get the current Phenotype.
* @return {string} The current phenotype.
*/
Identity.prototype.GetPhenotype = function()
{
return this.phenotype;
};
Identity.prototype.GetRank = function()

View File

@ -35,16 +35,16 @@ Sound.prototype.PlaySoundGroup = function(name)
if (name in this.template.SoundGroups)
{
// Replace the "{lang}" codes with this entity's civ ID
var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
let cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
if (!cmpIdentity)
return;
var lang = cmpIdentity.GetLang();
// Replace the "{gender}" codes with this entity's gender ID
var gender = cmpIdentity.GetGender();
let lang = cmpIdentity.GetLang();
// Replace the "{phenotype}" codes with this entity's phenotype ID
let phenotype = cmpIdentity.GetPhenotype();
var soundName = this.template.SoundGroups[name].replace(/\{lang\}/g, lang)
.replace(/\{gender\}/g, gender);
var cmpSoundManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_SoundManager);
let soundName = this.template.SoundGroups[name].replace(/\{lang\}/g, lang)
.replace(/\{phenotype\}/g, phenotype);
let cmpSoundManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_SoundManager);
if (cmpSoundManager)
cmpSoundManager.PlaySoundGroup(soundName, this.entity);
}

View File

@ -2,12 +2,13 @@ Engine.LoadComponentScript("Identity.js");
let cmpIdentity = ConstructComponent(5, "Identity", {
"Civ": "iber",
"GenericName": "Iberian Skirmisher"
"GenericName": "Iberian Skirmisher",
"Phenotype": { "_string": "male" },
});
TS_ASSERT_EQUALS(cmpIdentity.GetCiv(), "iber");
TS_ASSERT_EQUALS(cmpIdentity.GetLang(), "greek");
TS_ASSERT_EQUALS(cmpIdentity.GetGender(), "male");
TS_ASSERT_EQUALS(cmpIdentity.GetPhenotype(), "male");
TS_ASSERT_EQUALS(cmpIdentity.GetRank(), "");
TS_ASSERT_UNEVAL_EQUALS(cmpIdentity.GetClassesList(), []);
TS_ASSERT_UNEVAL_EQUALS(cmpIdentity.GetVisibleClassesList(), []);
@ -20,7 +21,7 @@ TS_ASSERT_EQUALS(cmpIdentity.GetGenericName(), "Iberian Skirmisher");
cmpIdentity = ConstructComponent(6, "Identity", {
"Civ": "iber",
"Lang": "iberian",
"Gender": "female",
"Phenotype": { "_string": "female" },
"GenericName": "Iberian Skirmisher",
"SpecificName": "Lusitano Ezpatari",
"SelectionGroupName": "units/iber_infantry_javelinist_b",
@ -39,7 +40,7 @@ cmpIdentity = ConstructComponent(6, "Identity", {
TS_ASSERT_EQUALS(cmpIdentity.GetCiv(), "iber");
TS_ASSERT_EQUALS(cmpIdentity.GetLang(), "iberian");
TS_ASSERT_EQUALS(cmpIdentity.GetGender(), "female");
TS_ASSERT_EQUALS(cmpIdentity.GetPhenotype(), "female");
TS_ASSERT_EQUALS(cmpIdentity.GetRank(), "Basic");
TS_ASSERT_UNEVAL_EQUALS(cmpIdentity.GetClassesList(), ["CitizenSoldier", "Human", "Organic", "Javelin", "Basic"]);
TS_ASSERT_UNEVAL_EQUALS(cmpIdentity.GetVisibleClassesList(), ["Javelin"]);
@ -50,3 +51,12 @@ TS_ASSERT_EQUALS(cmpIdentity.CanUseFormation("special/formations/skirmish"), tru
TS_ASSERT_EQUALS(cmpIdentity.CanUseFormation("special/formations/line"), false);
TS_ASSERT_EQUALS(cmpIdentity.GetSelectionGroupName(), "units/iber_infantry_javelinist_b");
TS_ASSERT_EQUALS(cmpIdentity.GetGenericName(), "Iberian Skirmisher");
cmpIdentity = ConstructComponent(7, "Identity", {
"Phenotype": { "_string": "First Second" },
});
TS_ASSERT_UNEVAL_EQUALS(cmpIdentity.GetPossiblePhenotypes(), ["First", "Second"]);
TS_ASSERT(["First", "Second"].indexOf(cmpIdentity.GetPhenotype()) !== -1);
cmpIdentity = ConstructComponent(8, "Identity", {});
TS_ASSERT_EQUALS(cmpIdentity.GetPhenotype(), "default");

View File

@ -51,6 +51,7 @@
special/formations/flank
special/formations/battle_line
</Formations>
<Phenotype datatype="tokens">male</Phenotype>
<Undeletable>false</Undeletable>
</Identity>
<Looter/>

View File

@ -80,11 +80,11 @@
</Selectable>
<Sound>
<SoundGroups>
<select>voice/{lang}/civ/civ_{gender}_select.xml</select>
<order_walk>voice/{lang}/civ/civ_{gender}_walk.xml</order_walk>
<order_attack>voice/{lang}/civ/civ_{gender}_attack.xml</order_attack>
<order_gather>voice/{lang}/civ/civ_{gender}_gather.xml</order_gather>
<order_garrison>voice/{lang}/civ/civ_{gender}_garrison.xml</order_garrison>
<select>voice/{lang}/civ/civ_{phenotype}_select.xml</select>
<order_walk>voice/{lang}/civ/civ_{phenotype}_walk.xml</order_walk>
<order_attack>voice/{lang}/civ/civ_{phenotype}_attack.xml</order_attack>
<order_gather>voice/{lang}/civ/civ_{phenotype}_gather.xml</order_gather>
<order_garrison>voice/{lang}/civ/civ_{phenotype}_garrison.xml</order_garrison>
<walk>actor/mounted/movement/walk.xml</walk>
<run>actor/mounted/movement/walk.xml</run>
<attack_impact_ranged>attack/impact/arrow_metal.xml</attack_impact_ranged>

View File

@ -42,11 +42,11 @@
</Selectable>
<Sound>
<SoundGroups>
<select>voice/{lang}/civ/civ_{gender}_select.xml</select>
<order_walk>voice/{lang}/civ/civ_{gender}_walk.xml</order_walk>
<order_attack>voice/{lang}/civ/civ_{gender}_attack.xml</order_attack>
<order_gather>voice/{lang}/civ/civ_{gender}_gather.xml</order_gather>
<order_garrison>voice/{lang}/civ/civ_{gender}_garrison.xml</order_garrison>
<select>voice/{lang}/civ/civ_{phenotype}_select.xml</select>
<order_walk>voice/{lang}/civ/civ_{phenotype}_walk.xml</order_walk>
<order_attack>voice/{lang}/civ/civ_{phenotype}_attack.xml</order_attack>
<order_gather>voice/{lang}/civ/civ_{phenotype}_gather.xml</order_gather>
<order_garrison>voice/{lang}/civ/civ_{phenotype}_garrison.xml</order_garrison>
<walk>actor/mounted/movement/walk.xml</walk>
<run>actor/mounted/movement/walk.xml</run>
<attack_melee>attack/weapon/sword.xml</attack_melee>

View File

@ -32,16 +32,16 @@
<Sound>
<SoundGroups>
<trained>interface/alarm/alarm_create_infantry.xml</trained>
<select>voice/{lang}/civ/civ_{gender}_select.xml</select>
<order_walk>voice/{lang}/civ/civ_{gender}_walk.xml</order_walk>
<order_attack>voice/{lang}/civ/civ_{gender}_attack.xml</order_attack>
<order_gather>voice/{lang}/civ/civ_{gender}_gather.xml</order_gather>
<order_repair>voice/{lang}/civ/civ_{gender}_repair.xml</order_repair>
<order_garrison>voice/{lang}/civ/civ_{gender}_garrison.xml</order_garrison>
<select>voice/{lang}/civ/civ_{phenotype}_select.xml</select>
<order_walk>voice/{lang}/civ/civ_{phenotype}_walk.xml</order_walk>
<order_attack>voice/{lang}/civ/civ_{phenotype}_attack.xml</order_attack>
<order_gather>voice/{lang}/civ/civ_{phenotype}_gather.xml</order_gather>
<order_repair>voice/{lang}/civ/civ_{phenotype}_repair.xml</order_repair>
<order_garrison>voice/{lang}/civ/civ_{phenotype}_garrison.xml</order_garrison>
<walk>actor/human/movement/walk.xml</walk>
<run>actor/human/movement/walk.xml</run>
<attack_melee>attack/weapon/sword.xml</attack_melee>
<death>actor/human/death/{gender}_death.xml</death>
<death>actor/human/death/{phenotype}_death.xml</death>
</SoundGroups>
</Sound>
<Vision>

View File

@ -54,20 +54,20 @@
</Selectable>
<Sound>
<SoundGroups>
<select>voice/{lang}/civ/civ_{gender}_select.xml</select>
<select>voice/{lang}/civ/civ_{phenotype}_select.xml</select>
<trained>interface/alarm/alarm_create_infantry.xml</trained>
<order_heal>voice/{lang}/civ/civ_{gender}_heal.xml</order_heal>
<order_walk>voice/{lang}/civ/civ_{gender}_walk.xml</order_walk>
<order_attack>voice/{lang}/civ/civ_{gender}_attack.xml</order_attack>
<order_gather>voice/{lang}/civ/civ_{gender}_gather.xml</order_gather>
<order_repair>voice/{lang}/civ/civ_{gender}_repair.xml</order_repair>
<order_garrison>voice/{lang}/civ/civ_{gender}_garrison.xml</order_garrison>
<order_heal>voice/{lang}/civ/civ_{phenotype}_heal.xml</order_heal>
<order_walk>voice/{lang}/civ/civ_{phenotype}_walk.xml</order_walk>
<order_attack>voice/{lang}/civ/civ_{phenotype}_attack.xml</order_attack>
<order_gather>voice/{lang}/civ/civ_{phenotype}_gather.xml</order_gather>
<order_repair>voice/{lang}/civ/civ_{phenotype}_repair.xml</order_repair>
<order_garrison>voice/{lang}/civ/civ_{phenotype}_garrison.xml</order_garrison>
<attack_melee>attack/weapon/sword.xml</attack_melee>
<attack_impact_ranged>attack/impact/arrow_metal.xml</attack_impact_ranged>
<attack_ranged>attack/weapon/arrowfly.xml</attack_ranged>
<walk>actor/human/movement/walk.xml</walk>
<run>actor/human/movement/walk.xml</run>
<death>actor/human/death/{gender}_death.xml</death>
<death>actor/human/death/{phenotype}_death.xml</death>
</SoundGroups>
</Sound>
<TrainingRestrictions>

View File

@ -100,18 +100,18 @@
</Selectable>
<Sound>
<SoundGroups>
<select>voice/{lang}/civ/civ_{gender}_select.xml</select>
<order_walk>voice/{lang}/civ/civ_{gender}_walk.xml</order_walk>
<order_attack>voice/{lang}/civ/civ_{gender}_attack.xml</order_attack>
<order_gather>voice/{lang}/civ/civ_{gender}_gather.xml</order_gather>
<order_repair>voice/{lang}/civ/civ_{gender}_repair.xml</order_repair>
<order_garrison>voice/{lang}/civ/civ_{gender}_garrison.xml</order_garrison>
<select>voice/{lang}/civ/civ_{phenotype}_select.xml</select>
<order_walk>voice/{lang}/civ/civ_{phenotype}_walk.xml</order_walk>
<order_attack>voice/{lang}/civ/civ_{phenotype}_attack.xml</order_attack>
<order_gather>voice/{lang}/civ/civ_{phenotype}_gather.xml</order_gather>
<order_repair>voice/{lang}/civ/civ_{phenotype}_repair.xml</order_repair>
<order_garrison>voice/{lang}/civ/civ_{phenotype}_garrison.xml</order_garrison>
<walk>actor/human/movement/walk.xml</walk>
<run>actor/human/movement/run.xml</run>
<attack_impact_ranged>attack/impact/arrow_metal.xml</attack_impact_ranged>
<attack_melee>attack/weapon/sword.xml</attack_melee>
<attack_ranged>attack/weapon/arrowfly.xml</attack_ranged>
<death>actor/human/death/{gender}_death.xml</death>
<death>actor/human/death/{phenotype}_death.xml</death>
<build>resource/construction/con_wood.xml</build>
<gather_fruit>resource/foraging/forage_leaves.xml</gather_fruit>
<gather_grain>resource/farming/farm.xml</gather_grain>

View File

@ -48,7 +48,7 @@
</Health>
<Identity>
<GenericName>Female Citizen</GenericName>
<Gender>female</Gender>
<Phenotype datatype="tokens">female</Phenotype>
<Classes datatype="tokens">FemaleCitizen</Classes>
<VisibleClasses datatype="tokens">Citizen Worker</VisibleClasses>
<Formations disable=""/>
@ -70,15 +70,15 @@
<Sound>
<SoundGroups>
<trained>interface/alarm/alarm_create_female.xml</trained>
<select>voice/{lang}/civ/civ_{gender}_select.xml</select>
<order_walk>voice/{lang}/civ/civ_{gender}_walk.xml</order_walk>
<order_attack>voice/{lang}/civ/civ_{gender}_attack.xml</order_attack>
<order_build>voice/{lang}/civ/civ_{gender}_build.xml</order_build>
<order_gather>voice/{lang}/civ/civ_{gender}_gather.xml</order_gather>
<order_repair>voice/{lang}/civ/civ_{gender}_repair.xml</order_repair>
<order_garrison>voice/{lang}/civ/civ_{gender}_garrison.xml</order_garrison>
<select>voice/{lang}/civ/civ_{phenotype}_select.xml</select>
<order_walk>voice/{lang}/civ/civ_{phenotype}_walk.xml</order_walk>
<order_attack>voice/{lang}/civ/civ_{phenotype}_attack.xml</order_attack>
<order_build>voice/{lang}/civ/civ_{phenotype}_build.xml</order_build>
<order_gather>voice/{lang}/civ/civ_{phenotype}_gather.xml</order_gather>
<order_repair>voice/{lang}/civ/civ_{phenotype}_repair.xml</order_repair>
<order_garrison>voice/{lang}/civ/civ_{phenotype}_garrison.xml</order_garrison>
<attack_melee>attack/weapon/sword.xml</attack_melee>
<death>actor/human/death/{gender}_death.xml</death>
<death>actor/human/death/{phenotype}_death.xml</death>
<build>resource/construction/con_wood.xml</build>
<gather_fruit>resource/foraging/forage_leaves.xml</gather_fruit>
<gather_grain>resource/farming/farm.xml</gather_grain>

View File

@ -42,16 +42,16 @@
<Sound>
<SoundGroups>
<trained>interface/alarm/alarm_create_infantry.xml</trained>
<select>voice/{lang}/civ/civ_{gender}_select.xml</select>
<order_walk>voice/{lang}/civ/civ_{gender}_walk.xml</order_walk>
<order_attack>voice/{lang}/civ/civ_{gender}_attack.xml</order_attack>
<order_gather>voice/{lang}/civ/civ_{gender}_gather.xml</order_gather>
<order_repair>voice/{lang}/civ/civ_{gender}_repair.xml</order_repair>
<order_heal>voice/{lang}/civ/civ_{gender}_heal.xml</order_heal>
<order_garrison>voice/{lang}/civ/civ_{gender}_garrison.xml</order_garrison>
<select>voice/{lang}/civ/civ_{phenotype}_select.xml</select>
<order_walk>voice/{lang}/civ/civ_{phenotype}_walk.xml</order_walk>
<order_attack>voice/{lang}/civ/civ_{phenotype}_attack.xml</order_attack>
<order_gather>voice/{lang}/civ/civ_{phenotype}_gather.xml</order_gather>
<order_repair>voice/{lang}/civ/civ_{phenotype}_repair.xml</order_repair>
<order_heal>voice/{lang}/civ/civ_{phenotype}_heal.xml</order_heal>
<order_garrison>voice/{lang}/civ/civ_{phenotype}_garrison.xml</order_garrison>
<walk>actor/human/movement/walk.xml</walk>
<run>actor/human/movement/run.xml</run>
<death>actor/human/death/{gender}_death.xml</death>
<death>actor/human/death/{phenotype}_death.xml</death>
</SoundGroups>
</Sound>
<Vision>

View File

@ -65,14 +65,14 @@
</ResourceGatherer>
<Sound>
<SoundGroups>
<select>voice/{lang}/civ/civ_{gender}_select.xml</select>
<order_walk>voice/{lang}/civ/civ_{gender}_walk.xml</order_walk>
<order_gather>voice/{lang}/civ/civ_{gender}_gather.xml</order_gather>
<order_repair>voice/{lang}/civ/civ_{gender}_repair.xml</order_repair>
<order_garrison>voice/{lang}/civ/civ_{gender}_garrison.xml</order_garrison>
<select>voice/{lang}/civ/civ_{phenotype}_select.xml</select>
<order_walk>voice/{lang}/civ/civ_{phenotype}_walk.xml</order_walk>
<order_gather>voice/{lang}/civ/civ_{phenotype}_gather.xml</order_gather>
<order_repair>voice/{lang}/civ/civ_{phenotype}_repair.xml</order_repair>
<order_garrison>voice/{lang}/civ/civ_{phenotype}_garrison.xml</order_garrison>
<walk>actor/human/movement/walk.xml</walk>
<run>actor/human/movement/run.xml</run>
<death>actor/human/death/{gender}_death.xml</death>
<death>actor/human/death/{phenotype}_death.xml</death>
<build>resource/construction/con_wood.xml</build>
<gather_fruit>resource/foraging/forage_leaves.xml</gather_fruit>
<gather_grain>resource/farming/farm.xml</gather_grain>

View File

@ -19,16 +19,16 @@
</Identity>
<Sound>
<SoundGroups>
<select>voice/{lang}/civ/civ_{gender}_select.xml</select>
<order_trade>voice/{lang}/civ/civ_{gender}_trade.xml</order_trade>
<order_walk>voice/{lang}/civ/civ_{gender}_walk.xml</order_walk>
<order_attack>voice/{lang}/civ/civ_{gender}_attack.xml</order_attack>
<order_gather>voice/{lang}/civ/civ_{gender}_gather.xml</order_gather>
<order_repair>voice/{lang}/civ/civ_{gender}_repair.xml</order_repair>
<select>voice/{lang}/civ/civ_{phenotype}_select.xml</select>
<order_trade>voice/{lang}/civ/civ_{phenotype}_trade.xml</order_trade>
<order_walk>voice/{lang}/civ/civ_{phenotype}_walk.xml</order_walk>
<order_attack>voice/{lang}/civ/civ_{phenotype}_attack.xml</order_attack>
<order_gather>voice/{lang}/civ/civ_{phenotype}_gather.xml</order_gather>
<order_repair>voice/{lang}/civ/civ_{phenotype}_repair.xml</order_repair>
<walk>actor/human/movement/walk.xml</walk>
<run>actor/human/movement/run.xml</run>
<attack_melee>attack/weapon/sword.xml</attack_melee>
<death>actor/human/death/{gender}_death.xml</death>
<death>actor/human/death/{phenotype}_death.xml</death>
<build>resource/construction/con_wood.xml</build>
<gather_fruit>resource/foraging/forage_leaves.xml</gather_fruit>
<gather_grain>resource/farming/farm.xml</gather_grain>

View File

@ -12,7 +12,7 @@
<VisibleClasses datatype="tokens">Chariot</VisibleClasses>
<GenericName>Boudicca (Chariot)</GenericName>
<SpecificName>Boadicea</SpecificName>
<Gender>female</Gender>
<Phenotype datatype="tokens">female</Phenotype>
<Icon>units/brit_hero_boudicca.png</Icon>
</Identity>
<VisualActor>

View File

@ -7,7 +7,7 @@
<Civ>brit</Civ>
<GenericName>Boudicca (Sword)</GenericName>
<SpecificName>Boadicea</SpecificName>
<Gender>female</Gender>
<Phenotype datatype="tokens">female</Phenotype>
<Icon>units/brit_hero_boudicca.png</Icon>
</Identity>
<VisualActor>

View File

@ -7,7 +7,7 @@
<Civ>brit</Civ>
<GenericName>Boudicca (Sword)</GenericName>
<SpecificName>Boadicea</SpecificName>
<Gender>female</Gender>
<Phenotype datatype="tokens">female</Phenotype>
<Icon>units/brit_hero_boudicca.png</Icon>
</Identity>
<VisualActor>

View File

@ -2,7 +2,7 @@
<Entity parent="template_unit_support_healer">
<Identity>
<Civ>cart</Civ>
<Gender>female</Gender>
<Phenotype datatype="tokens">female</Phenotype>
<SelectionGroupName>units/cart_support_healer_b</SelectionGroupName>
<SpecificName>Kehinit</SpecificName>
<Icon>units/cart_support_healer.png</Icon>

View File

@ -2,7 +2,7 @@
<Entity parent="template_unit_support_healer">
<Identity>
<Civ>iber</Civ>
<Gender>female</Gender>
<Phenotype datatype="tokens">female</Phenotype>
<SelectionGroupName>units/iber_support_healer_b</SelectionGroupName>
<GenericName>Priestess of Ataekina</GenericName>
<SpecificName>Emakumezko Apaiz de Ataekina</SpecificName>

View File

@ -7,7 +7,7 @@
<Civ>kush</Civ>
<GenericName>Amanirenas</GenericName>
<SpecificName>Amnirense qore li kdwe li</SpecificName>
<Gender>female</Gender>
<Phenotype datatype="tokens">female</Phenotype>
<Icon>units/kush_hero_amanirenas.png</Icon>
</Identity>
<VisualActor>

View File

@ -7,7 +7,7 @@
<Civ>kush</Civ>
<GenericName>Amanirenas</GenericName>
<SpecificName>Amnirense qore li kdwe li</SpecificName>
<Gender>female</Gender>
<Phenotype datatype="tokens">female</Phenotype>
<Icon>units/kush_hero_amanirenas.png</Icon>
</Identity>
<VisualActor>

View File

@ -2,7 +2,7 @@
<Entity parent="template_unit_champion_infantry_swordsman">
<Identity>
<Civ>maur</Civ>
<Gender>female</Gender>
<Phenotype datatype="tokens">female</Phenotype>
<GenericName>Maiden Guard</GenericName>
<SpecificName>Visha Kanya</SpecificName>
<SelectionGroupName>units/maur_champion_maiden</SelectionGroupName>

View File

@ -2,7 +2,7 @@
<Entity parent="template_unit_champion_infantry_archer">
<Identity>
<Civ>maur</Civ>
<Gender>female</Gender>
<Phenotype datatype="tokens">female</Phenotype>
<GenericName>Maiden Guard Archer</GenericName>
<SpecificName>Visha Kanya</SpecificName>
<Icon>units/maur_champion_maiden_archer.png</Icon>

View File

@ -7,7 +7,7 @@
</Auras>
<Identity>
<Civ>ptol</Civ>
<Gender>female</Gender>
<Phenotype datatype="tokens">female</Phenotype>
<GenericName>Cleopatra VII</GenericName>
<SpecificName>Kleopatra H' Philopator</SpecificName>
<Icon>units/ptol_hero_cleopatra.png</Icon>

View File

@ -23,6 +23,7 @@
#include "simulation2/MessageTypes.h"
#include "ICmpFootprint.h"
#include "ICmpIdentity.h"
#include "ICmpUnitRenderer.h"
#include "ICmpOwnership.h"
#include "ICmpPosition.h"
@ -59,6 +60,7 @@ public:
componentManager.SubscribeToMessageType(MT_OwnershipChanged);
componentManager.SubscribeToMessageType(MT_ValueModification);
componentManager.SubscribeToMessageType(MT_TerrainChanged);
componentManager.SubscribeToMessageType(MT_Create);
componentManager.SubscribeToMessageType(MT_Destroy);
}
@ -70,6 +72,7 @@ private:
// Not initialized in non-visual mode
CUnit* m_Unit;
CModelAbstract::CustomSelectionShape* m_ShapeDescriptor = nullptr;
fixed m_R, m_G, m_B; // shading color
@ -91,6 +94,10 @@ private:
bool m_VisibleInAtlasOnly;
bool m_IsActorOnly; // an in-world entity should not have this or it might not be rendered.
bool m_SilhouetteDisplay;
bool m_SilhouetteOccluder;
bool m_DisableShadows;
ICmpUnitRenderer::tag_t m_ModelTag;
public:
@ -195,17 +202,23 @@ public:
m_Seed = GetEntityId();
m_IsFoundationActor = paramNode.GetChild("Foundation").IsOk() && paramNode.GetChild("FoundationActor").IsOk();
if (m_IsFoundationActor)
m_BaseActorName = m_ActorName = paramNode.GetChild("FoundationActor").ToString();
else
m_BaseActorName = m_ActorName = paramNode.GetChild("Actor").ToString();
m_BaseActorName = paramNode.GetChild(m_IsFoundationActor ? "FoundationActor" : "Actor").ToString();
ParseActorName(m_BaseActorName);
m_VisibleInAtlasOnly = paramNode.GetChild("VisibleInAtlasOnly").ToBool();
m_IsActorOnly = paramNode.GetChild("ActorOnly").IsOk();
InitModel(paramNode);
m_SilhouetteDisplay = paramNode.GetChild("SilhouetteDisplay").ToBool();
m_SilhouetteOccluder = paramNode.GetChild("SilhouetteOccluder").ToBool();
m_DisableShadows = paramNode.GetChild("DisableShadows").ToBool();
SelectAnimation("idle");
// Initialize the model's selection shape descriptor. This currently relies on the component initialization order; the
// Footprint component must be initialized before this component (VisualActor) to support the ability to use the footprint
// shape for the selection box (instead of the default recursive bounding box). See TypeList.h for the order in
// which components are initialized; if for whatever reason you need to get rid of this dependency, you can always just
// initialize the selection shape descriptor on-demand.
InitSelectionShapeDescriptor(paramNode);
}
virtual void Deinit()
@ -260,6 +273,9 @@ public:
SerializeCommon(deserialize);
InitModel();
SelectAnimation("idle");
// If we serialized a different seed or different actor, reload actor
if (oldSeed != GetActorSeed() || m_BaseActorName != m_ActorName)
ReloadActor();
@ -309,7 +325,7 @@ public:
newActorName = cmpValueModificationManager->ApplyModifications(L"VisualActor/Actor", m_BaseActorName, GetEntityId());
if (newActorName != m_ActorName)
{
m_ActorName = newActorName;
ParseActorName(newActorName);
ReloadActor();
}
break;
@ -324,6 +340,13 @@ public:
}
break;
}
case MT_Create:
{
InitModel();
SelectAnimation("idle");
break;
}
case MT_Destroy:
{
if (m_ModelTag.valid())
@ -523,8 +546,11 @@ public:
}
private:
// Replace {phenotype} with the correct value in m_ActorName
void ParseActorName(std::wstring base);
/// Helper function shared by component init and actor reloading
void InitModel(const CParamNode& paramNode);
void InitModel();
/// Helper method; initializes the model selection shape descriptor from XML. Factored out for readability of @ref Init.
void InitSelectionShapeDescriptor(const CParamNode& paramNode);
@ -540,7 +566,24 @@ REGISTER_COMPONENT_TYPE(VisualActor)
// ------------------------------------------------------------------------------------------------------------------
void CCmpVisualActor::InitModel(const CParamNode& paramNode)
void CCmpVisualActor::ParseActorName(std::wstring base)
{
CmpPtr<ICmpIdentity> cmpIdentity(GetEntityHandle());
const std::wstring pattern = L"{phenotype}";
if (cmpIdentity)
{
size_t pos = base.find(pattern);
while (pos != std::string::npos)
{
base.replace(pos, pattern.size(), cmpIdentity->GetPhenotype());
pos = base.find(pattern, pos + pattern.size());
}
}
m_ActorName = base;
}
void CCmpVisualActor::InitModel()
{
if (!GetSimContext().HasUnitManager())
return;
@ -558,10 +601,10 @@ void CCmpVisualActor::InitModel(const CParamNode& paramNode)
{
u32 modelFlags = 0;
if (paramNode.GetChild("SilhouetteDisplay").ToBool())
if (m_SilhouetteDisplay)
modelFlags |= MODELFLAG_SILHOUETTE_DISPLAY;
if (paramNode.GetChild("SilhouetteOccluder").ToBool())
if (m_SilhouetteOccluder)
modelFlags |= MODELFLAG_SILHOUETTE_OCCLUDER;
CmpPtr<ICmpVisibility> cmpVisibility(GetEntityHandle());
@ -571,7 +614,7 @@ void CCmpVisualActor::InitModel(const CParamNode& paramNode)
model.ToCModel()->AddFlagsRec(modelFlags);
}
if (paramNode.GetChild("DisableShadows").IsOk())
if (m_DisableShadows)
{
if (model.ToCModel())
model.ToCModel()->RemoveShadowsRec();
@ -579,13 +622,6 @@ void CCmpVisualActor::InitModel(const CParamNode& paramNode)
model.ToCModelDecal()->RemoveShadows();
}
// Initialize the model's selection shape descriptor. This currently relies on the component initialization order; the
// Footprint component must be initialized before this component (VisualActor) to support the ability to use the footprint
// shape for the selection box (instead of the default recursive bounding box). See TypeList.h for the order in
// which components are initialized; if for whatever reason you need to get rid of this dependency, you can always just
// initialize the selection shape descriptor on-demand.
InitSelectionShapeDescriptor(paramNode);
m_Unit->SetID(GetEntityId());
bool floating = m_Unit->GetObject().m_Base->m_Properties.m_FloatOnWater;
@ -612,12 +648,16 @@ void CCmpVisualActor::InitModel(const CParamNode& paramNode)
m_ModelTag = cmpModelRenderer->AddUnit(GetEntityHandle(), m_Unit, boundSphere, flags);
}
}
// the model is now responsible for cleaning up the descriptor
if (m_ShapeDescriptor != nullptr)
m_Unit->GetModel().SetCustomSelectionShape(m_ShapeDescriptor);
}
void CCmpVisualActor::InitSelectionShapeDescriptor(const CParamNode& paramNode)
{
// by default, we don't need a custom selection shape and we can just keep the default behaviour
CModelAbstract::CustomSelectionShape* shapeDescriptor = NULL;
m_ShapeDescriptor = nullptr;
const CParamNode& shapeNode = paramNode.GetChild("SelectionShape");
if (shapeNode.IsOk())
@ -649,11 +689,11 @@ void CCmpVisualActor::InitSelectionShapeDescriptor(const CParamNode& paramNode)
size1 *= 2;
}
shapeDescriptor = new CModelAbstract::CustomSelectionShape;
shapeDescriptor->m_Type = CModelAbstract::CustomSelectionShape::BOX;
shapeDescriptor->m_Size0 = size0;
shapeDescriptor->m_Size1 = size1;
shapeDescriptor->m_Height = fpHeight.ToFloat();
m_ShapeDescriptor = new CModelAbstract::CustomSelectionShape;
m_ShapeDescriptor->m_Type = CModelAbstract::CustomSelectionShape::BOX;
m_ShapeDescriptor->m_Size0 = size0;
m_ShapeDescriptor->m_Size1 = size1;
m_ShapeDescriptor->m_Height = fpHeight.ToFloat();
}
else
{
@ -663,11 +703,11 @@ void CCmpVisualActor::InitSelectionShapeDescriptor(const CParamNode& paramNode)
else if (shapeNode.GetChild("Box").IsOk())
{
// TODO: we might need to support the ability to specify a different box center in the future
shapeDescriptor = new CModelAbstract::CustomSelectionShape;
shapeDescriptor->m_Type = CModelAbstract::CustomSelectionShape::BOX;
shapeDescriptor->m_Size0 = shapeNode.GetChild("Box").GetChild("@width").ToFixed().ToFloat();
shapeDescriptor->m_Size1 = shapeNode.GetChild("Box").GetChild("@depth").ToFixed().ToFloat();
shapeDescriptor->m_Height = shapeNode.GetChild("Box").GetChild("@height").ToFixed().ToFloat();
m_ShapeDescriptor = new CModelAbstract::CustomSelectionShape;
m_ShapeDescriptor->m_Type = CModelAbstract::CustomSelectionShape::BOX;
m_ShapeDescriptor->m_Size0 = shapeNode.GetChild("Box").GetChild("@width").ToFixed().ToFloat();
m_ShapeDescriptor->m_Size1 = shapeNode.GetChild("Box").GetChild("@depth").ToFixed().ToFloat();
m_ShapeDescriptor->m_Height = shapeNode.GetChild("Box").GetChild("@height").ToFixed().ToFloat();
}
else if (shapeNode.GetChild("Cylinder").IsOk())
{
@ -679,10 +719,6 @@ void CCmpVisualActor::InitSelectionShapeDescriptor(const CParamNode& paramNode)
LOGERROR("[VisualActor] No selection shape specified");
}
}
ENSURE(m_Unit);
// the model is now responsible for cleaning up the descriptor
m_Unit->GetModel().SetCustomSelectionShape(shapeDescriptor);
}
void CCmpVisualActor::ReloadActor()
@ -703,7 +739,9 @@ void CCmpVisualActor::ReloadActor()
const CParamNode* node = cmpTemplateManager->LoadLatestTemplate(GetEntityId());
ENSURE(node && node->GetChild("VisualActor").IsOk());
InitModel(node->GetChild("VisualActor"));
InitSelectionShapeDescriptor(node->GetChild("VisualActor"));
InitModel();
ReloadUnitAnimation();

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2011 Wildfire Games.
/* Copyright (C) 2019 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -35,6 +35,11 @@ public:
{
return m_Script.Call<std::string>("GetSelectionGroupName");
}
virtual std::wstring GetPhenotype()
{
return m_Script.Call<std::wstring>("GetPhenotype");
}
};
REGISTER_COMPONENT_SCRIPT_WRAPPER(IdentityScripted)

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2011 Wildfire Games.
/* Copyright (C) 2019 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -29,6 +29,8 @@ class ICmpIdentity : public IComponent
public:
virtual std::string GetSelectionGroupName() = 0;
virtual std::wstring GetPhenotype() = 0;
DECLARE_INTERFACE_TYPE(Identity)
};