Index: ps/trunk/source/simulation2/components/ICmpIdentity.h =================================================================== --- ps/trunk/source/simulation2/components/ICmpIdentity.h (revision 22585) +++ ps/trunk/source/simulation2/components/ICmpIdentity.h (revision 22586) @@ -1,35 +1,37 @@ -/* 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 * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_ICMPIDENTITY #define INCLUDED_ICMPIDENTITY #include "simulation2/system/Interface.h" /** * Identity data. * (This interface only includes the functions needed by native code for entity selection) */ class ICmpIdentity : public IComponent { public: virtual std::string GetSelectionGroupName() = 0; + virtual std::wstring GetPhenotype() = 0; + DECLARE_INTERFACE_TYPE(Identity) }; #endif // INCLUDED_ICMPIDENTITY Index: ps/trunk/binaries/data/mods/public/simulation/components/Identity.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Identity.js (revision 22585) +++ ps/trunk/binaries/data/mods/public/simulation/components/Identity.js (revision 22586) @@ -1,169 +1,182 @@ function Identity() {} Identity.prototype.Schema = "Specifies various names and values associated with the unit type, typically for GUI display to users." + "" + "athen" + "Athenian Hoplite" + "Hoplī́tēs Athēnaïkós" + "units/athen_infantry_spearman.png" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + - "" + + "" + + "" + + "tokens" + + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "Basic" + "Advanced" + "Elite" + "" + "" + "" + "" + "" + "" + "tokens" + "" + "" + "" + "" + "" + "" + "" + "tokens" + "" + "" + "" + "" + "" + "" + "" + "tokens" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + ""; 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; }; 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.Phenotype._string.split(/\s+/); +}; + +/** + * Get the current Phenotype. + * @return {string} The current phenotype. + */ +Identity.prototype.GetPhenotype = function() { - return this.template.Gender || "male"; // ugly default + return this.phenotype; }; Identity.prototype.GetRank = function() { return this.template.Rank || ""; }; Identity.prototype.GetClassesList = function() { return this.classesList; }; Identity.prototype.GetVisibleClassesList = function() { return this.visibleClassesList; }; Identity.prototype.HasClass = function(name) { return this.GetClassesList().indexOf(name) != -1; }; Identity.prototype.GetFormationsList = function() { if (this.template.Formations && this.template.Formations._string) return this.template.Formations._string.split(/\s+/); return []; }; Identity.prototype.CanUseFormation = function(template) { return this.GetFormationsList().indexOf(template) != -1; }; Identity.prototype.GetSelectionGroupName = function() { return this.template.SelectionGroupName || ""; }; Identity.prototype.GetGenericName = function() { return this.template.GenericName; }; Identity.prototype.IsUndeletable = function() { return this.template.Undeletable == "true"; }; Engine.RegisterComponentType(IID_Identity, "Identity", Identity); Index: ps/trunk/binaries/data/mods/public/simulation/components/Sound.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Sound.js (revision 22585) +++ ps/trunk/binaries/data/mods/public/simulation/components/Sound.js (revision 22586) @@ -1,53 +1,53 @@ function Sound() {} Sound.prototype.Schema = "Lists the sound groups associated with this unit." + "" + "" + "actor/human/movement/walk.xml" + "actor/human/movement/walk.xml" + "attack/weapon/sword.xml" + "actor/human/death/death.xml" + "" + "" + "" + "" + /* TODO: make this more specific, like a list of specific elements */ "" + "" + "" + "" + "" + ""; Sound.prototype.Init = function() { }; Sound.prototype.Serialize = null; // we have no dynamic state to save Sound.prototype.GetSoundGroup = function(name) { return this.template.SoundGroups[name] || ""; }; 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(); - - var soundName = this.template.SoundGroups[name].replace(/\{lang\}/g, lang) - .replace(/\{gender\}/g, gender); - var cmpSoundManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_SoundManager); + let lang = cmpIdentity.GetLang(); + // Replace the "{phenotype}" codes with this entity's phenotype ID + let phenotype = cmpIdentity.GetPhenotype(); + + 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); } }; Engine.RegisterComponentType(IID_Sound, "Sound", Sound); Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Identity.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Identity.js (revision 22585) +++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Identity.js (revision 22586) @@ -1,52 +1,62 @@ 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(), []); TS_ASSERT_EQUALS(cmpIdentity.HasClass("CitizenSoldier"), false); TS_ASSERT_UNEVAL_EQUALS(cmpIdentity.GetFormationsList(), []); TS_ASSERT_EQUALS(cmpIdentity.CanUseFormation("special/formations/skirmish"), false); TS_ASSERT_EQUALS(cmpIdentity.GetSelectionGroupName(), ""); 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", "Tooltip": "Basic ranged infantry", "History": "Iberians, especially the Lusitanians, were good at" + " ranged combat and ambushing enemy columns. They throw heavy iron" + " javelins and sometimes even add burning pitch to them, making them" + " good as a cheap siege weapon.", "Rank": "Basic", "Classes": { "_string": "CitizenSoldier Human Organic" }, "VisibleClasses": { "_string": "Javelin" }, "Formations": { "_string": "special/formations/skirmish" }, "Icon": "units/iber_infantry_javelinist.png", "RequiredTechnology": "phase_town" }); 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"]); TS_ASSERT_EQUALS(cmpIdentity.HasClass("CitizenSoldier"), true); TS_ASSERT_EQUALS(cmpIdentity.HasClass("Female"), false); TS_ASSERT_UNEVAL_EQUALS(cmpIdentity.GetFormationsList(), ["special/formations/skirmish"]); TS_ASSERT_EQUALS(cmpIdentity.CanUseFormation("special/formations/skirmish"), true); 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"); Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit.xml (revision 22585) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit.xml (revision 22586) @@ -1,145 +1,146 @@ 1 1 15 1 0 1 0 0 0 0 false false 30.0 0.01 0.0 2.5 corpse 100 0 0 false gaia Unit Unit ConquestCritical special/formations/null special/formations/box special/formations/column_closed special/formations/line_closed special/formations/column_open special/formations/line_open special/formations/flank special/formations/battle_line + male false unit true true false false true false false false 0 upright false 0.0 6.0 2.0 1.0 1 10 10 10 10 circle/128x128.png circle/128x128_mask.png interface/alarm/alarm_attackplayer.xml attack/weapon/sword.xml 2.0 0.333 5.0 aggressive 12.0 false true true false default 9.0 1.67 false false false false 12 false true false false Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_cavalry.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_cavalry.xml (revision 22585) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_cavalry.xml (revision 22586) @@ -1,106 +1,106 @@ 3 1 15 2 4 1000 Field Palisade SiegeWall StoneWall 100.0 0.0 0.0 2 1 15 100 7.5 160 CitizenSoldier Human Organic Cavalry Citizen Soldier Cavalry Basic special/formations/wedge 130 10 5 0 0 pitch 150 2.0 1.0 5 20 20 20 20 circle/128x128.png circle/128x128_mask.png - - voice/{lang}/civ/civ_{gender}_walk.xml - voice/{lang}/civ/civ_{gender}_attack.xml - voice/{lang}/civ/civ_{gender}_gather.xml - voice/{lang}/civ/civ_{gender}_garrison.xml + + voice/{lang}/civ/civ_{phenotype}_walk.xml + voice/{lang}/civ/civ_{phenotype}_attack.xml + voice/{lang}/civ/civ_{phenotype}_gather.xml + voice/{lang}/civ/civ_{phenotype}_garrison.xml actor/mounted/movement/walk.xml actor/mounted/movement/walk.xml attack/impact/arrow_metal.xml attack/weapon/sword.xml attack/weapon/arrowfly.xml actor/fauna/death/death_horse.xml interface/alarm/alarm_create_cav.xml 6.5 1.95 92 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_champion_cavalry.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_champion_cavalry.xml (revision 22585) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_champion_cavalry.xml (revision 22586) @@ -1,66 +1,66 @@ 7 5 20 1 30 250 0 0 100 5.0 240 Champion Cavalry Cavalry special/formations/wedge pitch arrow/256x256.png arrow/256x256_mask.png - - voice/{lang}/civ/civ_{gender}_walk.xml - voice/{lang}/civ/civ_{gender}_attack.xml - voice/{lang}/civ/civ_{gender}_gather.xml - voice/{lang}/civ/civ_{gender}_garrison.xml + + voice/{lang}/civ/civ_{phenotype}_walk.xml + voice/{lang}/civ/civ_{phenotype}_attack.xml + voice/{lang}/civ/civ_{phenotype}_gather.xml + voice/{lang}/civ/civ_{phenotype}_garrison.xml actor/mounted/movement/walk.xml actor/mounted/movement/walk.xml attack/weapon/sword.xml actor/fauna/death/death_horse.xml interface/alarm/alarm_create_cav.xml 6.5 2.25 96 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry.xml (revision 22585) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry.xml (revision 22586) @@ -1,50 +1,50 @@ 5 5 20 20 125 0 0 75 100 Infantry Champion Infantry arrow/128x128.png arrow/128x128_mask.png interface/alarm/alarm_create_infantry.xml - - voice/{lang}/civ/civ_{gender}_walk.xml - voice/{lang}/civ/civ_{gender}_attack.xml - voice/{lang}/civ/civ_{gender}_gather.xml - voice/{lang}/civ/civ_{gender}_repair.xml - voice/{lang}/civ/civ_{gender}_garrison.xml + + voice/{lang}/civ/civ_{phenotype}_walk.xml + voice/{lang}/civ/civ_{phenotype}_attack.xml + voice/{lang}/civ/civ_{phenotype}_gather.xml + voice/{lang}/civ/civ_{phenotype}_repair.xml + voice/{lang}/civ/civ_{phenotype}_garrison.xml actor/human/movement/walk.xml actor/human/movement/walk.xml attack/weapon/sword.xml - actor/human/death/{gender}_death.xml + actor/human/death/{phenotype}_death.xml 84 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_hero.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_hero.xml (revision 22585) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_hero.xml (revision 22586) @@ -1,79 +1,79 @@ 10.0 20.0 15.0 15 4 1000 Field Palisade SiegeWall StoneWall units/heroes/hero_garrison 2 40 100 250 600 Hero Organic Human Hero Soldier phase_city 400 10 0 0 25 hero star/256x256.png star/256x256_mask.png - + interface/alarm/alarm_create_infantry.xml - voice/{lang}/civ/civ_{gender}_heal.xml - voice/{lang}/civ/civ_{gender}_walk.xml - voice/{lang}/civ/civ_{gender}_attack.xml - voice/{lang}/civ/civ_{gender}_gather.xml - voice/{lang}/civ/civ_{gender}_repair.xml - voice/{lang}/civ/civ_{gender}_garrison.xml + voice/{lang}/civ/civ_{phenotype}_heal.xml + voice/{lang}/civ/civ_{phenotype}_walk.xml + voice/{lang}/civ/civ_{phenotype}_attack.xml + voice/{lang}/civ/civ_{phenotype}_gather.xml + voice/{lang}/civ/civ_{phenotype}_repair.xml + voice/{lang}/civ/civ_{phenotype}_garrison.xml attack/weapon/sword.xml attack/impact/arrow_metal.xml attack/weapon/arrowfly.xml actor/human/movement/walk.xml actor/human/movement/walk.xml - actor/human/death/{gender}_death.xml + actor/human/death/{phenotype}_death.xml Hero 88 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_infantry.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_infantry.xml (revision 22585) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_infantry.xml (revision 22586) @@ -1,128 +1,128 @@ 2 4 15 2 4 1000 Field Palisade SiegeWall StoneWall 50.0 0.0 0.0 2 1.0 structures/{civ}_house structures/{civ}_storehouse structures/{civ}_farmstead structures/{civ}_field structures/{civ}_corral structures/{civ}_outpost other/wallset_palisade structures/{civ}_sentry_tower structures/{civ}_dock structures/{civ}_barracks structures/{civ}_blacksmith structures/{civ}_temple structures/{civ}_market structures/{civ}_defense_tower structures/{civ}_wallset_stone structures/{civ}_civil_centre structures/{civ}_fortress structures/{civ}_wonder 10 50 0 0 0 2.5 80 Infantry CitizenSoldier Human Organic Citizen Infantry Soldier Worker Basic 100 5 0 0 0 100 2.0 1.0 0.5 0.25 1 0.75 5 0.5 2 0.5 circle/128x128.png circle/128x128_mask.png - - voice/{lang}/civ/civ_{gender}_walk.xml - voice/{lang}/civ/civ_{gender}_attack.xml - voice/{lang}/civ/civ_{gender}_gather.xml - voice/{lang}/civ/civ_{gender}_repair.xml - voice/{lang}/civ/civ_{gender}_garrison.xml + + voice/{lang}/civ/civ_{phenotype}_walk.xml + voice/{lang}/civ/civ_{phenotype}_attack.xml + voice/{lang}/civ/civ_{phenotype}_gather.xml + voice/{lang}/civ/civ_{phenotype}_repair.xml + voice/{lang}/civ/civ_{phenotype}_garrison.xml actor/human/movement/walk.xml actor/human/movement/run.xml attack/impact/arrow_metal.xml attack/weapon/sword.xml attack/weapon/arrowfly.xml - actor/human/death/{gender}_death.xml + actor/human/death/{phenotype}_death.xml resource/construction/con_wood.xml resource/foraging/forage_leaves.xml resource/farming/farm.xml resource/lumbering/lumbering.xml resource/mining/pickaxe.xml resource/mining/mining.xml resource/mining/mining.xml interface/alarm/alarm_create_infantry.xml 80 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_support_female_citizen.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_support_female_citizen.xml (revision 22585) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_support_female_citizen.xml (revision 22586) @@ -1,97 +1,97 @@ 2.0 0 0.0 3 500 1000 25.0 0.0 0.0 2 units/female_inspiration 1.0 structures/{civ}_house structures/{civ}_storehouse structures/{civ}_farmstead structures/{civ}_field structures/{civ}_corral structures/{civ}_dock structures/{civ}_temple structures/{civ}_market structures/{civ}_civil_centre structures/{civ}_wonder 50 25 Female Citizen - female + female FemaleCitizen Citizen Worker 2.0 1.0 1 0.5 1 0.7 5 0.35 2 0.35 interface/alarm/alarm_create_female.xml - - voice/{lang}/civ/civ_{gender}_walk.xml - voice/{lang}/civ/civ_{gender}_attack.xml - voice/{lang}/civ/civ_{gender}_build.xml - voice/{lang}/civ/civ_{gender}_gather.xml - voice/{lang}/civ/civ_{gender}_repair.xml - voice/{lang}/civ/civ_{gender}_garrison.xml + + voice/{lang}/civ/civ_{phenotype}_walk.xml + voice/{lang}/civ/civ_{phenotype}_attack.xml + voice/{lang}/civ/civ_{phenotype}_build.xml + voice/{lang}/civ/civ_{phenotype}_gather.xml + voice/{lang}/civ/civ_{phenotype}_repair.xml + voice/{lang}/civ/civ_{phenotype}_garrison.xml attack/weapon/sword.xml - actor/human/death/{gender}_death.xml + actor/human/death/{phenotype}_death.xml resource/construction/con_wood.xml resource/foraging/forage_leaves.xml resource/farming/farm.xml resource/lumbering/lumbering.xml resource/mining/pickaxe.xml resource/mining/mining.xml resource/mining/mining.xml false 32 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_support_healer.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_support_healer.xml (revision 22585) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_support_healer.xml (revision 22586) @@ -1,60 +1,60 @@ 250 12 5 2000 Human heal_overlay_range.png heal_overlay_range_mask.png 0.35 85 -ConquestCritical Healer Basic phase_town Heal units. Healer 150 plus/128x128.png plus/128x128_mask.png interface/alarm/alarm_create_infantry.xml - - voice/{lang}/civ/civ_{gender}_walk.xml - voice/{lang}/civ/civ_{gender}_attack.xml - voice/{lang}/civ/civ_{gender}_gather.xml - voice/{lang}/civ/civ_{gender}_repair.xml - voice/{lang}/civ/civ_{gender}_heal.xml - voice/{lang}/civ/civ_{gender}_garrison.xml + + voice/{lang}/civ/civ_{phenotype}_walk.xml + voice/{lang}/civ/civ_{phenotype}_attack.xml + voice/{lang}/civ/civ_{phenotype}_gather.xml + voice/{lang}/civ/civ_{phenotype}_repair.xml + voice/{lang}/civ/civ_{phenotype}_heal.xml + voice/{lang}/civ/civ_{phenotype}_garrison.xml actor/human/movement/walk.xml actor/human/movement/run.xml - actor/human/death/{gender}_death.xml + actor/human/death/{phenotype}_death.xml 30 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_support_slave.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_support_slave.xml (revision 22585) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_support_slave.xml (revision 22586) @@ -1,85 +1,85 @@ 0.5 structures/{civ}_house structures/{civ}_storehouse structures/{civ}_farmstead structures/{civ}_field structures/{civ}_corral structures/{civ}_outpost other/wallset_palisade structures/{civ}_dock structures/{civ}_barracks structures/{civ}_blacksmith structures/{civ}_temple structures/{civ}_market structures/{civ}_defense_tower structures/{civ}_wallset_stone structures/{civ}_civil_centre structures/{civ}_fortress 0 20 0 0 0 50 -0.25 true Slave Worker Slave units/global_slave.png Gatherer with a finite life span. Bonused at mining and lumbering. 1 0 1 1 1 1.0 0.5 0.5 0.35 1 1.0 5 1.0 5 1.0 - - voice/{lang}/civ/civ_{gender}_walk.xml - voice/{lang}/civ/civ_{gender}_gather.xml - voice/{lang}/civ/civ_{gender}_repair.xml - voice/{lang}/civ/civ_{gender}_garrison.xml + + voice/{lang}/civ/civ_{phenotype}_walk.xml + voice/{lang}/civ/civ_{phenotype}_gather.xml + voice/{lang}/civ/civ_{phenotype}_repair.xml + voice/{lang}/civ/civ_{phenotype}_garrison.xml actor/human/movement/walk.xml actor/human/movement/run.xml - actor/human/death/{gender}_death.xml + actor/human/death/{phenotype}_death.xml resource/construction/con_wood.xml resource/foraging/forage_leaves.xml resource/farming/farm.xml resource/lumbering/lumbering.xml resource/mining/pickaxe.xml resource/mining/mining.xml actor/singlesteps/steps_gravel_trained.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_support_trader.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_support_trader.xml (revision 22585) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_support_trader.xml (revision 22586) @@ -1,53 +1,53 @@ 15 100 80 100 -ConquestCritical Trader Bribable Trader Trade resources between your own markets and those of your allies. - - voice/{lang}/civ/civ_{gender}_trade.xml - voice/{lang}/civ/civ_{gender}_walk.xml - voice/{lang}/civ/civ_{gender}_attack.xml - voice/{lang}/civ/civ_{gender}_gather.xml - voice/{lang}/civ/civ_{gender}_repair.xml + + voice/{lang}/civ/civ_{phenotype}_trade.xml + voice/{lang}/civ/civ_{phenotype}_walk.xml + voice/{lang}/civ/civ_{phenotype}_attack.xml + voice/{lang}/civ/civ_{phenotype}_gather.xml + voice/{lang}/civ/civ_{phenotype}_repair.xml actor/human/movement/walk.xml actor/human/movement/run.xml attack/weapon/sword.xml - actor/human/death/{gender}_death.xml + actor/human/death/{phenotype}_death.xml resource/construction/con_wood.xml resource/foraging/forage_leaves.xml resource/farming/farm.xml resource/lumbering/lumbering.xml resource/mining/pickaxe.xml resource/mining/mining.xml 0.75 false false 60 true Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/brit_hero_boudicca.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/units/brit_hero_boudicca.xml (revision 22585) +++ ps/trunk/binaries/data/mods/public/simulation/templates/units/brit_hero_boudicca.xml (revision 22586) @@ -1,21 +1,21 @@ units/heroes/brit_hero_boudicca 5.0 brit Chariot Boudicca (Chariot) Boadicea - female + female units/brit_hero_boudicca.png units/britons/hero_chariot_javelinist_boudicca_m.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/brit_hero_boudicca_cavalry_javelinist.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/units/brit_hero_boudicca_cavalry_javelinist.xml (revision 22585) +++ ps/trunk/binaries/data/mods/public/simulation/templates/units/brit_hero_boudicca_cavalry_javelinist.xml (revision 22586) @@ -1,16 +1,16 @@ units/heroes/brit_hero_boudicca brit Boudicca (Sword) Boadicea - female + female units/brit_hero_boudicca.png units/britons/hero_cavalry_javelinist_boudicca_m.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/brit_hero_boudicca_sword.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/units/brit_hero_boudicca_sword.xml (revision 22585) +++ ps/trunk/binaries/data/mods/public/simulation/templates/units/brit_hero_boudicca_sword.xml (revision 22586) @@ -1,16 +1,16 @@ units/heroes/brit_hero_boudicca brit Boudicca (Sword) Boadicea - female + female units/brit_hero_boudicca.png units/britons/hero_infantry_swordsman_boudicca.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/cart_support_healer_b.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/units/cart_support_healer_b.xml (revision 22585) +++ ps/trunk/binaries/data/mods/public/simulation/templates/units/cart_support_healer_b.xml (revision 22586) @@ -1,17 +1,17 @@ cart - female + female units/cart_support_healer_b Kehinit units/cart_support_healer.png Basic units/cart_support_healer_a units/carthaginians/healer.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_support_healer_b.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_support_healer_b.xml (revision 22585) +++ ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_support_healer_b.xml (revision 22586) @@ -1,18 +1,18 @@ iber - female + female units/iber_support_healer_b Priestess of Ataekina Emakumezko Apaiz de Ataekina units/iber_support_healer.png Basic units/iber_support_healer_a units/iberians/healer.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/kush_hero_amanirenas.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/units/kush_hero_amanirenas.xml (revision 22585) +++ ps/trunk/binaries/data/mods/public/simulation/templates/units/kush_hero_amanirenas.xml (revision 22586) @@ -1,16 +1,16 @@ units/heroes/kush_hero_amanirenas kush Amanirenas Amnirense qore li kdwe li - female + female units/kush_hero_amanirenas.png units/kushites/hero_infantry_archer_amanirenas.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/kush_hero_amanirenas_chariot.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/units/kush_hero_amanirenas_chariot.xml (revision 22585) +++ ps/trunk/binaries/data/mods/public/simulation/templates/units/kush_hero_amanirenas_chariot.xml (revision 22586) @@ -1,16 +1,16 @@ units/heroes/kush_hero_amanirenas kush Amanirenas Amnirense qore li kdwe li - female + female units/kush_hero_amanirenas.png units/kushites/hero_chariot_archer_amanirenas_m.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/maur_champion_maiden.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/units/maur_champion_maiden.xml (revision 22585) +++ ps/trunk/binaries/data/mods/public/simulation/templates/units/maur_champion_maiden.xml (revision 22586) @@ -1,14 +1,14 @@ maur - female + female Maiden Guard Visha Kanya units/maur_champion_maiden units/maur_champion_maiden.png units/mauryas/infantry_swordsman_c.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/maur_champion_maiden_archer.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/units/maur_champion_maiden_archer.xml (revision 22585) +++ ps/trunk/binaries/data/mods/public/simulation/templates/units/maur_champion_maiden_archer.xml (revision 22586) @@ -1,13 +1,13 @@ maur - female + female Maiden Guard Archer Visha Kanya units/maur_champion_maiden_archer.png units/mauryas/infantry_archer_c.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/ptol_hero_cleopatra.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/units/ptol_hero_cleopatra.xml (revision 22585) +++ ps/trunk/binaries/data/mods/public/simulation/templates/units/ptol_hero_cleopatra.xml (revision 22586) @@ -1,18 +1,18 @@ units/heroes/ptol_hero_cleopatra_1 units/heroes/ptol_hero_cleopatra_2 units/heroes/ptol_hero_cleopatra_3 ptol - female + female Cleopatra VII Kleopatra H' Philopator units/ptol_hero_cleopatra.png units/ptolemies/hero_infantry_archer_cleopatra.xml Index: ps/trunk/source/simulation2/components/CCmpVisualActor.cpp =================================================================== --- ps/trunk/source/simulation2/components/CCmpVisualActor.cpp (revision 22585) +++ ps/trunk/source/simulation2/components/CCmpVisualActor.cpp (revision 22586) @@ -1,740 +1,778 @@ /* 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 * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "simulation2/system/Component.h" #include "ICmpVisual.h" #include "simulation2/MessageTypes.h" #include "ICmpFootprint.h" +#include "ICmpIdentity.h" #include "ICmpUnitRenderer.h" #include "ICmpOwnership.h" #include "ICmpPosition.h" #include "ICmpTemplateManager.h" #include "ICmpTerrain.h" #include "ICmpUnitMotion.h" #include "ICmpValueModificationManager.h" #include "ICmpVisibility.h" #include "ICmpSound.h" #include "simulation2/serialization/SerializeTemplates.h" #include "graphics/Decal.h" #include "graphics/Frustum.h" #include "graphics/Model.h" #include "graphics/ObjectBase.h" #include "graphics/ObjectEntry.h" #include "graphics/Unit.h" #include "graphics/UnitAnimation.h" #include "graphics/UnitManager.h" #include "maths/BoundingSphere.h" #include "maths/Matrix3D.h" #include "maths/Vector3D.h" #include "ps/CLogger.h" #include "ps/GameSetup/Config.h" #include "renderer/Scene.h" class CCmpVisualActor : public ICmpVisual { public: static void ClassInit(CComponentManager& componentManager) { componentManager.SubscribeToMessageType(MT_InterpolatedPositionChanged); componentManager.SubscribeToMessageType(MT_OwnershipChanged); componentManager.SubscribeToMessageType(MT_ValueModification); componentManager.SubscribeToMessageType(MT_TerrainChanged); + componentManager.SubscribeToMessageType(MT_Create); componentManager.SubscribeToMessageType(MT_Destroy); } DEFAULT_COMPONENT_ALLOCATOR(VisualActor) private: std::wstring m_BaseActorName, m_ActorName; bool m_IsFoundationActor; // Not initialized in non-visual mode CUnit* m_Unit; + CModelAbstract::CustomSelectionShape* m_ShapeDescriptor = nullptr; fixed m_R, m_G, m_B; // shading color // Current animation state std::string m_AnimName; bool m_AnimOnce; fixed m_AnimSpeed; std::wstring m_SoundGroup; fixed m_AnimDesync; fixed m_AnimSyncRepeatTime; // 0.0 if not synced fixed m_AnimSyncOffsetTime; std::map m_VariantSelections; u32 m_Seed; // seed used for random variations bool m_ConstructionPreview; 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: static std::string GetSchema() { return "Display the unit using the engine's actor system." "" "units/hellenes/infantry_spearman_b.xml" "" "" "structures/hellenes/barracks.xml" "structures/fndn_4x4.xml" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "0.0" "" "" "" "" "0.0" "" "" "" "" "0.0" "" "" "" "" "" "" "0.0" "" "" "" "" "0.0" "" "" "" "" "" "" "" "" ""; } virtual void Init(const CParamNode& paramNode) { m_Unit = NULL; m_R = m_G = m_B = fixed::FromInt(1); m_ConstructionPreview = paramNode.GetChild("ConstructionPreview").IsOk(); 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); - - SelectAnimation("idle"); + m_SilhouetteDisplay = paramNode.GetChild("SilhouetteDisplay").ToBool(); + m_SilhouetteOccluder = paramNode.GetChild("SilhouetteOccluder").ToBool(); + m_DisableShadows = paramNode.GetChild("DisableShadows").ToBool(); + + // 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() { if (m_Unit) { GetSimContext().GetUnitManager().DeleteUnit(m_Unit); m_Unit = NULL; } } template void SerializeCommon(S& serialize) { serialize.NumberFixed_Unbounded("r", m_R); serialize.NumberFixed_Unbounded("g", m_G); serialize.NumberFixed_Unbounded("b", m_B); serialize.StringASCII("anim name", m_AnimName, 0, 256); serialize.Bool("anim once", m_AnimOnce); serialize.NumberFixed_Unbounded("anim speed", m_AnimSpeed); serialize.String("sound group", m_SoundGroup, 0, 256); serialize.NumberFixed_Unbounded("anim desync", m_AnimDesync); serialize.NumberFixed_Unbounded("anim sync repeat time", m_AnimSyncRepeatTime); serialize.NumberFixed_Unbounded("anim sync offset time", m_AnimSyncOffsetTime); SerializeMap()(serialize, "variation", m_VariantSelections); serialize.NumberU32_Unbounded("seed", m_Seed); serialize.String("actor", m_ActorName, 0, 256); // TODO: store actor variables? } virtual void Serialize(ISerializer& serialize) { // TODO: store the actor name, if !debug and it differs from the template if (serialize.IsDebug()) { serialize.String("base actor", m_BaseActorName, 0, 256); } SerializeCommon(serialize); } virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize) { Init(paramNode); u32 oldSeed = GetActorSeed(); SerializeCommon(deserialize); + InitModel(); + SelectAnimation("idle"); + // If we serialized a different seed or different actor, reload actor if (oldSeed != GetActorSeed() || m_BaseActorName != m_ActorName) ReloadActor(); else ReloadUnitAnimation(); if (m_Unit) { CmpPtr cmpOwnership(GetEntityHandle()); if (cmpOwnership) m_Unit->GetModel().SetPlayerID(cmpOwnership->GetOwner()); } } virtual void HandleMessage(const CMessage& msg, bool UNUSED(global)) { switch (msg.GetType()) { case MT_OwnershipChanged: { if (!m_Unit) break; const CMessageOwnershipChanged& msgData = static_cast (msg); m_Unit->GetModel().SetPlayerID(msgData.to); break; } case MT_TerrainChanged: { if (!m_Unit) break; const CMessageTerrainChanged& msgData = static_cast (msg); m_Unit->GetModel().SetTerrainDirty(msgData.i0, msgData.j0, msgData.i1, msgData.j1); break; } case MT_ValueModification: { const CMessageValueModification& msgData = static_cast (msg); if (msgData.component != L"VisualActor") break; CmpPtr cmpValueModificationManager(GetSystemEntity()); std::wstring newActorName; if (m_IsFoundationActor) newActorName = cmpValueModificationManager->ApplyModifications(L"VisualActor/FoundationActor", m_BaseActorName, GetEntityId()); else newActorName = cmpValueModificationManager->ApplyModifications(L"VisualActor/Actor", m_BaseActorName, GetEntityId()); if (newActorName != m_ActorName) { - m_ActorName = newActorName; + ParseActorName(newActorName); ReloadActor(); } break; } case MT_InterpolatedPositionChanged: { const CMessageInterpolatedPositionChanged& msgData = static_cast (msg); if (m_ModelTag.valid()) { CmpPtr cmpModelRenderer(GetSystemEntity()); cmpModelRenderer->UpdateUnitPos(m_ModelTag, msgData.inWorld, msgData.pos0, msgData.pos1); } break; } + case MT_Create: + { + InitModel(); + + SelectAnimation("idle"); + break; + } case MT_Destroy: { if (m_ModelTag.valid()) { CmpPtr cmpModelRenderer(GetSystemEntity()); cmpModelRenderer->RemoveUnit(m_ModelTag); m_ModelTag = ICmpUnitRenderer::tag_t(); } break; } } } virtual CBoundingBoxAligned GetBounds() const { if (!m_Unit) return CBoundingBoxAligned::EMPTY; return m_Unit->GetModel().GetWorldBounds(); } virtual CUnit* GetUnit() { return m_Unit; } virtual CBoundingBoxOriented GetSelectionBox() const { if (!m_Unit) return CBoundingBoxOriented::EMPTY; return m_Unit->GetModel().GetSelectionBox(); } virtual CVector3D GetPosition() const { if (!m_Unit) return CVector3D(0, 0, 0); return m_Unit->GetModel().GetTransform().GetTranslation(); } virtual std::wstring GetActorShortName() const { if (!m_Unit) return L""; return m_Unit->GetObject().m_Base->m_ShortName; } virtual std::wstring GetProjectileActor() const { if (!m_Unit) return L""; return m_Unit->GetObject().m_ProjectileModelName; } virtual CFixedVector3D GetProjectileLaunchPoint() const { if (!m_Unit) return CFixedVector3D(); if (m_Unit->GetModel().ToCModel()) { // Ensure the prop transforms are correct CmpPtr cmpUnitRenderer(GetSystemEntity()); CmpPtr cmpPosition(GetEntityHandle()); if (cmpUnitRenderer && cmpPosition) { float frameOffset = cmpUnitRenderer->GetFrameOffset(); CMatrix3D transform(cmpPosition->GetInterpolatedTransform(frameOffset)); m_Unit->GetModel().SetTransform(transform); m_Unit->GetModel().ValidatePosition(); } CModelAbstract* ammo = m_Unit->GetModel().ToCModel()->FindFirstAmmoProp(); if (ammo) { CVector3D vector = ammo->GetTransform().GetTranslation(); return CFixedVector3D(fixed::FromFloat(vector.X), fixed::FromFloat(vector.Y), fixed::FromFloat(vector.Z)); } } return CFixedVector3D(); } virtual void SetVariant(const CStr& key, const CStr& selection) { if (m_VariantSelections[key] == selection) return; m_VariantSelections[key] = selection; if (m_Unit) { m_Unit->SetEntitySelection(key, selection); if (m_Unit->GetAnimation()) m_Unit->GetAnimation()->ReloadAnimation(); } } virtual std::string GetAnimationName() const { return m_AnimName; } virtual void SelectAnimation(const std::string& name, bool once = false, fixed speed = fixed::FromInt(1)) { m_AnimName = name; m_AnimOnce = once; m_AnimSpeed = speed; m_SoundGroup = L""; m_AnimDesync = fixed::FromInt(1)/20; // TODO: make this an argument m_AnimSyncRepeatTime = fixed::Zero(); m_AnimSyncOffsetTime = fixed::Zero(); SetVariant("animation", m_AnimName); CmpPtr cmpSound(GetEntityHandle()); if (cmpSound) m_SoundGroup = cmpSound->GetSoundGroup(wstring_from_utf8(m_AnimName)); if (!m_Unit || !m_Unit->GetAnimation() || !m_Unit->GetID()) return; m_Unit->GetAnimation()->SetAnimationState(m_AnimName, m_AnimOnce, m_AnimSpeed.ToFloat(), m_AnimDesync.ToFloat(), m_SoundGroup.c_str()); } virtual void SelectMovementAnimation(const std::string& name, fixed speed) { ENSURE(name == "idle" || name == "walk" || name == "run"); if (m_AnimName != "idle" && m_AnimName != "walk" && m_AnimName != "run") return; SelectAnimation(name, false, speed); } virtual void SetAnimationSyncRepeat(fixed repeattime) { m_AnimSyncRepeatTime = repeattime; if (m_Unit && m_Unit->GetAnimation()) m_Unit->GetAnimation()->SetAnimationSyncRepeat(m_AnimSyncRepeatTime.ToFloat()); } virtual void SetAnimationSyncOffset(fixed actiontime) { m_AnimSyncOffsetTime = actiontime; if (m_Unit && m_Unit->GetAnimation()) m_Unit->GetAnimation()->SetAnimationSyncOffset(m_AnimSyncOffsetTime.ToFloat()); } virtual void SetShadingColor(fixed r, fixed g, fixed b, fixed a) { m_R = r; m_G = g; m_B = b; UNUSED2(a); // TODO: why is this even an argument? if (m_Unit) { CModelAbstract& model = m_Unit->GetModel(); model.SetShadingColor(CColor(m_R.ToFloat(), m_G.ToFloat(), m_B.ToFloat(), 1.0f)); } } virtual void SetVariable(const std::string& name, float value) { if (m_Unit) m_Unit->GetModel().SetEntityVariable(name, value); } virtual u32 GetActorSeed() const { return m_Seed; } virtual void SetActorSeed(u32 seed) { if (seed == m_Seed) return; m_Seed = seed; ReloadActor(); } virtual bool HasConstructionPreview() const { return m_ConstructionPreview; } virtual void Hotload(const VfsPath& name) { if (!m_Unit) return; if (name != m_ActorName) return; ReloadActor(); } 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); // ReloadActor is used when the actor or seed changes. void ReloadActor(); // ReloadUnitAnimation is used for a minimal reloading upon deserialization, when the actor and seed are identical. // It is also used by ReloadActor. void ReloadUnitAnimation(); }; REGISTER_COMPONENT_TYPE(VisualActor) // ------------------------------------------------------------------------------------------------------------------ -void CCmpVisualActor::InitModel(const CParamNode& paramNode) +void CCmpVisualActor::ParseActorName(std::wstring base) +{ + CmpPtr 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; std::set selections; std::wstring actorName = m_ActorName; if (actorName.find(L".xml") == std::wstring::npos) actorName += L".xml"; m_Unit = GetSimContext().GetUnitManager().CreateUnit(actorName, GetActorSeed(), selections); if (!m_Unit) return; CModelAbstract& model = m_Unit->GetModel(); if (model.ToCModel()) { 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 cmpVisibility(GetEntityHandle()); if (cmpVisibility && cmpVisibility->GetAlwaysVisible()) modelFlags |= MODELFLAG_IGNORE_LOS; model.ToCModel()->AddFlagsRec(modelFlags); } - if (paramNode.GetChild("DisableShadows").IsOk()) + if (m_DisableShadows) { if (model.ToCModel()) model.ToCModel()->RemoveShadowsRec(); else if (model.ToCModelDecal()) 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; CmpPtr cmpPosition(GetEntityHandle()); if (cmpPosition) cmpPosition->SetActorFloating(floating); if (!m_ModelTag.valid()) { CmpPtr cmpModelRenderer(GetSystemEntity()); if (cmpModelRenderer) { // TODO: this should account for all possible props, animations, etc, // else we might accidentally cull the unit when it should be visible CBoundingBoxAligned bounds = m_Unit->GetModel().GetWorldBoundsRec(); CBoundingSphere boundSphere = CBoundingSphere::FromSweptBox(bounds); int flags = 0; if (m_IsActorOnly) flags |= ICmpUnitRenderer::ACTOR_ONLY; if (m_VisibleInAtlasOnly) flags |= ICmpUnitRenderer::VISIBLE_IN_ATLAS_ONLY; 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()) { if (shapeNode.GetChild("Bounds").IsOk()) { // default; no need to take action } else if (shapeNode.GetChild("Footprint").IsOk()) { CmpPtr cmpFootprint(GetEntityHandle()); if (cmpFootprint) { ICmpFootprint::EShape fpShape; // fp stands for "footprint" entity_pos_t fpSize0, fpSize1, fpHeight; // fp stands for "footprint" cmpFootprint->GetShape(fpShape, fpSize0, fpSize1, fpHeight); float size0 = fpSize0.ToFloat(); float size1 = fpSize1.ToFloat(); // TODO: we should properly distinguish between CIRCLE and SQUARE footprint shapes here, but since cylinders // aren't implemented yet and are almost indistinguishable from boxes for small enough sizes anyway, // we'll just use boxes for either case. However, for circular footprints the size0 and size1 values both // represent the radius, so we do have to adjust them to match the size1 and size0's of square footprints // (which represent the full width and depth). if (fpShape == ICmpFootprint::CIRCLE) { size0 *= 2; 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 { LOGERROR("[VisualActor] Cannot apply footprint-based SelectionShape; Footprint component not initialized."); } } 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()) { LOGWARNING("[VisualActor] TODO: Cylinder selection shapes are not yet implemented; defaulting to recursive bounding boxes"); } else { // shouldn't happen by virtue of validation against schema 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() { if (!m_Unit) return; // Save some data from the old unit CColor shading = m_Unit->GetModel().GetShadingColor(); player_id_t playerID = m_Unit->GetModel().GetPlayerID(); // Replace with the new unit GetSimContext().GetUnitManager().DeleteUnit(m_Unit); // HACK: selection shape needs template data, but rather than storing all that data // in the component, we load the template here and pass it into a helper function CmpPtr cmpTemplateManager(GetSystemEntity()); const CParamNode* node = cmpTemplateManager->LoadLatestTemplate(GetEntityId()); ENSURE(node && node->GetChild("VisualActor").IsOk()); - InitModel(node->GetChild("VisualActor")); + InitSelectionShapeDescriptor(node->GetChild("VisualActor")); + + InitModel(); ReloadUnitAnimation(); m_Unit->GetModel().SetShadingColor(shading); m_Unit->GetModel().SetPlayerID(playerID); if (m_ModelTag.valid()) { CmpPtr cmpModelRenderer(GetSystemEntity()); CBoundingBoxAligned bounds = m_Unit->GetModel().GetWorldBoundsRec(); CBoundingSphere boundSphere = CBoundingSphere::FromSweptBox(bounds); cmpModelRenderer->UpdateUnit(m_ModelTag, m_Unit, boundSphere); } } void CCmpVisualActor::ReloadUnitAnimation() { if (!m_Unit) return; m_Unit->SetEntitySelection(m_VariantSelections); if (!m_Unit->GetAnimation()) return; m_Unit->GetAnimation()->SetAnimationState(m_AnimName, m_AnimOnce, m_AnimSpeed.ToFloat(), m_AnimDesync.ToFloat(), m_SoundGroup.c_str()); // We'll lose the exact synchronisation but we should at least make sure it's going at the correct rate if (!m_AnimSyncRepeatTime.IsZero()) m_Unit->GetAnimation()->SetAnimationSyncRepeat(m_AnimSyncRepeatTime.ToFloat()); if (!m_AnimSyncOffsetTime.IsZero()) m_Unit->GetAnimation()->SetAnimationSyncOffset(m_AnimSyncOffsetTime.ToFloat()); } Index: ps/trunk/source/simulation2/components/ICmpIdentity.cpp =================================================================== --- ps/trunk/source/simulation2/components/ICmpIdentity.cpp (revision 22585) +++ ps/trunk/source/simulation2/components/ICmpIdentity.cpp (revision 22586) @@ -1,40 +1,45 @@ -/* 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 * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "ICmpIdentity.h" #include "simulation2/system/InterfaceScripted.h" #include "simulation2/scripting/ScriptComponent.h" BEGIN_INTERFACE_WRAPPER(Identity) END_INTERFACE_WRAPPER(Identity) class CCmpIdentityScripted : public ICmpIdentity { public: DEFAULT_SCRIPT_WRAPPER(IdentityScripted) virtual std::string GetSelectionGroupName() { return m_Script.Call("GetSelectionGroupName"); } + + virtual std::wstring GetPhenotype() + { + return m_Script.Call("GetPhenotype"); + } }; REGISTER_COMPONENT_SCRIPT_WRAPPER(IdentityScripted)