Index: binaries/data/mods/public/globalscripts/Templates.js =================================================================== --- binaries/data/mods/public/globalscripts/Templates.js +++ binaries/data/mods/public/globalscripts/Templates.js @@ -401,6 +401,7 @@ ret.requiredTechnology = template.Identity.RequiredTechnology; ret.visibleIdentityClasses = GetVisibleIdentityClasses(template.Identity); ret.nativeCiv = template.Identity.Civ; + ret.composition = template.Identity.Composition; } if (template.UnitMotion) Index: binaries/data/mods/public/gui/common/tooltips.js =================================================================== --- binaries/data/mods/public/gui/common/tooltips.js +++ binaries/data/mods/public/gui/common/tooltips.js @@ -742,6 +742,40 @@ return tooltips.join("\n"); } +function getCompositionTooltip(template) +{ + if (!template.composition) + return ""; + + let composition = []; + if (template.composition.MutuallyExclusive == "true") + { + let total = 0; + for (let entry in template.composition.Entries) + total += +template.composition.Entries[entry].Chance; + + for (let entry in template.composition.Entries) + composition.push(sprintf(translate("• %(amount)s %(entity)s (%(chance)s %%)"), { + "amount": template.composition.Entries[entry].Amount, + "entity": getEntityNames(GetTemplateData(template.composition.Entries[entry].Template)), + "chance": (template.composition.Entries[entry].Chance / total * 100).toFixed(0), + })); + } + else + for (let entry in template.composition.Entries) + composition.push(sprintf(translate("• %(amount)s %(entity)s"), { + "amount": template.composition.Entries[entry].Amount, + "entity": getEntityNames(GetTemplateData(template.composition.Entries[entry].Template)) + })); + + let tooltip = sprintf(translate("%(label)s\n%(composition)s"), { + "label": headerFont(translate("Composition:")), + "composition": composition.join("\n") + }); + + return tooltip; +} + function getEntityNames(template) { if (!template.name.specific) Index: binaries/data/mods/public/gui/reference/common/draw.js =================================================================== --- binaries/data/mods/public/gui/reference/common/draw.js +++ binaries/data/mods/public/gui/reference/common/draw.js @@ -11,6 +11,7 @@ */ var g_StatsFunctions = [ getHealthTooltip, + getCompositionTooltip, getHealerTooltip, getAttackTooltip, getSplashDamageTooltip, Index: binaries/data/mods/public/simulation/components/Identity.js =================================================================== --- binaries/data/mods/public/simulation/components/Identity.js +++ binaries/data/mods/public/simulation/components/Identity.js @@ -92,7 +92,32 @@ "" + "" + "" + - ""; + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""; Identity.prototype.Init = function() { Index: binaries/data/mods/public/simulation/components/ProductionQueue.js =================================================================== --- binaries/data/mods/public/simulation/components/ProductionQueue.js +++ binaries/data/mods/public/simulation/components/ProductionQueue.js @@ -68,6 +68,10 @@ this.entityCache = []; this.spawnNotified = false; + + this.compositionCount = {}; + this.compositionSpawned = {}; + this.compositionEntries = {}; }; /* @@ -401,6 +405,9 @@ Engine.DestroyEntity(ent); this.entityCache = []; + this.compositionCount = {}; + this.compositionSpawned = {}; + this.compositionEntries = {}; for (var i = 0; i < this.queue.length; ++i) { @@ -726,7 +733,54 @@ if (item.unitTemplate) { - var numSpawned = this.SpawnUnits(item.unitTemplate, item.count, item.metadata); + let cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); + template = cmpGuiInterface.GetTemplateData(item.player, item.unitTemplate); + let numSpawned = 0; + if (template.composition) + { + if (Object.keys(this.compositionEntries).length == 0) + { + this.compositionEntries = template.composition.Entries; + if (template.composition.MutuallyExclusive != "false") + { + let weightedListOfTemplates = []; + for (let entry in this.compositionEntries) + { + // Construct something of a weighted list. + let chance = +(this.compositionEntries[entry].Chance || 1); + for (let i = 0; i < chance; i++) + weightedListOfTemplates.push(this.compositionEntries[entry]); + } + this.compositionEntries = {}; + for (let i = 0; i < item.count; i++) + this.compositionEntries[i] = pickRandom(weightedListOfTemplates); + item.count = 1; + } + } + let finished = true; + for (let entry in this.compositionEntries) + { + let amount = +this.compositionEntries[entry].Amount * item.count - (this.compositionSpawned[entry] || 0); + if (this.compositionCount[entry] == undefined) + { + this.compositionCount[entry] = amount; + this.compositionSpawned[entry] = 0; + } + this.compositionSpawned[entry] += this.SpawnUnits(this.compositionEntries[entry].Template, amount, {}); + if (finished) + finished = this.compositionSpawned[entry] == this.compositionCount[entry]; + } + + if (finished) + { + numSpawned = item.count; + this.compositionCount = {}; + this.compositionSpawned = {}; + this.compositionEntries = {}; + } + } + else + numSpawned = this.SpawnUnits(item.unitTemplate, item.count, item.metadata); if (numSpawned == item.count) { // All entities spawned, this batch finished Index: binaries/data/mods/public/simulation/components/tests/test_ProductionQueue.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_ProductionQueue.js +++ binaries/data/mods/public/simulation/components/tests/test_ProductionQueue.js @@ -202,7 +202,8 @@ }); AddMock(SYSTEM_ENTITY, IID_GuiInterface, { - "PushNotification": () => {} + "PushNotification": () => {}, + "GetTemplateData": playerID => ({}) }); AddMock(SYSTEM_ENTITY, IID_Trigger, { Index: binaries/data/mods/public/simulation/templates/structures/athen_civil_centre.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/athen_civil_centre.xml +++ binaries/data/mods/public/simulation/templates/structures/athen_civil_centre.xml @@ -12,6 +12,7 @@ units/{civ}_infantry_spearman_b units/{civ}_infantry_slinger_b units/{civ}_cavalry_javelinist_b + units/compositions/athen_example Index: binaries/data/mods/public/simulation/templates/units/compositions/athen_example.xml =================================================================== --- /dev/null +++ binaries/data/mods/public/simulation/templates/units/compositions/athen_example.xml @@ -0,0 +1,38 @@ + + + + athen + Some generic name. + Some specific name. + Some elaborate tooltip. + units/viking_longship.png + phase_town + + true + + + + 5 + 1 + + + + 5 + 2 + + + + true + + + 2 + 5 + 0 + + 500 + 0 + 0 + 100 + + + Index: binaries/data/mods/public/simulation/templates/units/compositions/group.xml =================================================================== --- /dev/null +++ binaries/data/mods/public/simulation/templates/units/compositions/group.xml @@ -0,0 +1,24 @@ + + + + + + gaia + Group. + Group S. + Group T. + phase_village + true + + + 0 + 0 + 0 + + 0 + 0 + 0 + 0 + + +