From 2333b1814e6b75d324f9e58edb19ebbdca9d75c6 Mon Sep 17 00:00:00 2001 From: wraitii Date: Sun, 15 Sep 2019 09:24:52 +0000 Subject: [PATCH] GUI support for Status Effects and extend their functionality to all attack effects following 16b452cf91 This fixes status effects following 16b452cf91 where the GiveStatus name change broke them. This lets status effects deal all types of damage, capture, or inflict other status effects, like any attack. This further adds some basic GUI spport to status effects, by showing up to 5 icons in the top-right of the portrait, and including status effects in the tooltips. Status effects can specify a custom icon. Differential Revision: https://code.wildfiregames.com/D2218 This was SVN commit r22901. --- .../session/icons/status_effects/default.png | 3 + .../data/mods/public/gui/common/tooltips.js | 55 ++++++++++++++++--- .../public/gui/session/selection_details.js | 20 +++++++ .../single_details_area.xml | 9 +++ .../simulation/components/GuiInterface.js | 4 ++ .../components/StatusEffectsReceiver.js | 35 +++++++----- .../components/tests/test_GuiInterface.js | 1 + .../tests/test_StatusEffectsReceiver.js | 16 ++++-- .../public/simulation/helpers/Attacking.js | 34 +++++++----- 9 files changed, 135 insertions(+), 42 deletions(-) create mode 100755 binaries/data/mods/public/art/textures/ui/session/icons/status_effects/default.png diff --git a/binaries/data/mods/public/art/textures/ui/session/icons/status_effects/default.png b/binaries/data/mods/public/art/textures/ui/session/icons/status_effects/default.png new file mode 100755 index 0000000000..2313d734aa --- /dev/null +++ b/binaries/data/mods/public/art/textures/ui/session/icons/status_effects/default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:db2f0b822606a94a0769680265a043c40ac51a1306652dee5d04164d419acbae +size 15527 diff --git a/binaries/data/mods/public/gui/common/tooltips.js b/binaries/data/mods/public/gui/common/tooltips.js index 83686a85de..9de103566c 100644 --- a/binaries/data/mods/public/gui/common/tooltips.js +++ b/binaries/data/mods/public/gui/common/tooltips.js @@ -185,15 +185,16 @@ function getArmorTooltip(template) function attackRateDetails(interval, projectiles) { - // ToDo: Get the name of a projectile from the template. - - if (!interval || projectiles == 0) + if (!interval) return ""; + if (projectiles === 0) + return translate("Garrison to fire arrows"); + let attackRateString = getSecondsString(interval / 1000); let header = headerFont(translate("Interval:")); - if (+projectiles > 1) + if (projectiles && +projectiles > 1) { header = headerFont(translate("Rate:")); let projectileString = sprintf(translatePlural("%(projectileCount)s %(projectileName)s", "%(projectileCount)s %(projectileName)s", projectiles), { @@ -258,7 +259,7 @@ function damageDetails(damageTemplate) return Object.keys(damageTemplate).filter(dmgType => damageTemplate[dmgType]).map( dmgType => sprintf(translate("%(damage)s %(damageType)s"), { - "damage": damageTemplate[dmgType].toFixed(1), + "damage": (+damageTemplate[dmgType]).toFixed(1), "damageType": unitFont(translateWithContext("damage type", dmgType)) })).join(commaFont(translate(", "))); } @@ -269,11 +270,21 @@ function captureDetails(captureTemplate) return ""; return sprintf(translate("%(amount)s %(name)s"), { - "amount": captureTemplate.toFixed(1), + "amount": (+captureTemplate).toFixed(1), "name": unitFont(translateWithContext("damage type", "Capture")) }); } +function giveStatusDetails(giveStatusTemplate) +{ + if (!giveStatusTemplate) + return ""; + + return sprintf(translate("gives %(name)s"), { + "name": Object.keys(giveStatusTemplate).map(x => unitFont(translateWithContext("status effect", x))).join(', '), + }); +} + function attackEffectsDetails(attackTypeTemplate) { if (!attackTypeTemplate) @@ -281,7 +292,8 @@ function attackEffectsDetails(attackTypeTemplate) let effects = [ captureDetails(attackTypeTemplate.Capture || undefined), - damageDetails(attackTypeTemplate.Damage || undefined) + damageDetails(attackTypeTemplate.Damage || undefined), + giveStatusDetails(attackTypeTemplate.GiveStatus || undefined) ]; return effects.filter(effect => effect).join(commaFont(translate(", "))); } @@ -309,11 +321,19 @@ function getAttackTooltip(template) if (template.buildingAI) projectiles = template.buildingAI.arrowCount || template.buildingAI.defaultArrowCount; - tooltips.push(sprintf(translate("%(attackLabel)s: %(effects)s, %(range)s, %(rate)s"), { + // Show the effects of status effects below + let statusEffectsDetails = []; + if (attackTypeTemplate.GiveStatus) + for (let status in attackTypeTemplate.GiveStatus) + statusEffectsDetails.push("\n " + getStatusEffectsTooltip(status, attackTypeTemplate.GiveStatus[status])); + statusEffectsDetails = statusEffectsDetails.join(""); + + tooltips.push(sprintf(translate("%(attackLabel)s: %(effects)s, %(range)s, %(rate)s%(statusEffects)s"), { "attackLabel": attackLabel, "effects": attackEffectsDetails(attackTypeTemplate), "range": rangeDetails(attackTypeTemplate), - "rate": attackRateDetails(attackTypeTemplate.repeatTime, projectiles) + "rate": attackRateDetails(attackTypeTemplate.repeatTime, projectiles), + "statusEffects": statusEffectsDetails })); } return tooltips.join("\n"); @@ -351,6 +371,23 @@ function getSplashDamageTooltip(template) return tooltips.join("\n"); } +function getStatusEffectsTooltip(name, template) +{ + let durationString = ""; + if (template.Duration) + durationString = sprintf(translate(", %(durName)s: %(duration)s"), { + "durName": headerFont(translate("Duration")), + "duration": getSecondsString((template.TimeElapsed ? +template.Duration - template.TimeElapsed : +template.Duration) / 1000), + }); + + return sprintf(translate("%(statusName)s: %(effects)s, %(rate)s%(durationString)s"), { + "statusName": headerFont(translateWithContext("status effect", name)), + "effects": attackEffectsDetails(template), + "rate": attackRateDetails(+template.Interval), + "durationString": durationString + }); +} + function getGarrisonTooltip(template) { if (!template.garrisonHolder) diff --git a/binaries/data/mods/public/gui/session/selection_details.js b/binaries/data/mods/public/gui/session/selection_details.js index 4ccabd68d4..ec36c835ca 100644 --- a/binaries/data/mods/public/gui/session/selection_details.js +++ b/binaries/data/mods/public/gui/session/selection_details.js @@ -93,6 +93,26 @@ function displaySingle(entState) Engine.GetGUIObjectByName("rankIcon").tooltip = ""; } + if (entState.statusEffects) + { + let statusIcons = Engine.GetGUIObjectByName("statusEffectsIcons").children; + let i = 0; + for (let effectName in entState.statusEffects) + { + let effect = entState.statusEffects[effectName]; + statusIcons[i].hidden = false; + statusIcons[i].sprite = "stretched:session/icons/status_effects/" + (effect.Icon || "default") + ".png"; + statusIcons[i].tooltip = getStatusEffectsTooltip(effectName, effect); + let size = statusIcons[i].size; + size.top = i * 18; + size.bottom = i * 18 + 16; + statusIcons[i].size = size; + i++; + } + for (; i < statusIcons.length; ++i) + statusIcons[i].hidden = true; + } + let showHealth = entState.hitpoints; let showResource = entState.resourceSupply; diff --git a/binaries/data/mods/public/gui/session/selection_panels_middle/single_details_area.xml b/binaries/data/mods/public/gui/session/selection_panels_middle/single_details_area.xml index 9cc21b9e05..ea2bb83ca2 100644 --- a/binaries/data/mods/public/gui/session/selection_panels_middle/single_details_area.xml +++ b/binaries/data/mods/public/gui/session/selection_panels_middle/single_details_area.xml @@ -82,9 +82,18 @@ Rank + + + + + + + + + diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js index 3444a989be..139ed82a3f 100644 --- a/binaries/data/mods/public/simulation/components/GuiInterface.js +++ b/binaries/data/mods/public/simulation/components/GuiInterface.js @@ -291,6 +291,10 @@ GuiInterface.prototype.GetEntityState = function(player, ent) "template": cmpUpgrade.GetUpgradingTo() }; + let cmpStatusEffects = Engine.QueryInterface(ent, IID_StatusEffectsReceiver); + if (cmpStatusEffects) + ret.statusEffects = cmpStatusEffects.GetActiveStatuses(); + let cmpProductionQueue = Engine.QueryInterface(ent, IID_ProductionQueue); if (cmpProductionQueue) ret.production = { diff --git a/binaries/data/mods/public/simulation/components/StatusEffectsReceiver.js b/binaries/data/mods/public/simulation/components/StatusEffectsReceiver.js index 565bf3d8f2..5d6b68fe69 100755 --- a/binaries/data/mods/public/simulation/components/StatusEffectsReceiver.js +++ b/binaries/data/mods/public/simulation/components/StatusEffectsReceiver.js @@ -5,6 +5,11 @@ StatusEffectsReceiver.prototype.Init = function() this.activeStatusEffects = {}; }; +StatusEffectsReceiver.prototype.GetActiveStatuses = function() +{ + return this.activeStatusEffects; +}; + // Called by attacking effects. StatusEffectsReceiver.prototype.GiveStatus = function(effectData, attacker, attackerOwner, bonusMultiplier) { @@ -23,23 +28,23 @@ StatusEffectsReceiver.prototype.AddStatus = function(statusName, data) this.activeStatusEffects[statusName] = {}; let status = this.activeStatusEffects[statusName]; - status.duration = +data.Duration; - status.interval = +data.Interval; - status.damage = +data.Damage; - status.timeElapsed = 0; - status.firstTime = true; + Object.assign(status, data); + status.Interval = +data.Interval; + status.TimeElapsed = 0; + status.FirstTime = true; let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); - status.timer = cmpTimer.SetInterval(this.entity, IID_StatusEffectsReceiver, "ExecuteEffect", 0, +status.interval, statusName); + status.Timer = cmpTimer.SetInterval(this.entity, IID_StatusEffectsReceiver, "ExecuteEffect", 0, +status.Interval, statusName); }; -StatusEffectsReceiver.prototype.RemoveStatus = function(statusName) { +StatusEffectsReceiver.prototype.RemoveStatus = function(statusName) +{ if (!this.activeStatusEffects[statusName]) return; let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); - cmpTimer.CancelTimer(this.activeStatusEffects[statusName].timer); - this.activeStatusEffects[statusName] = undefined; + cmpTimer.CancelTimer(this.activeStatusEffects[statusName].Timer); + delete this.activeStatusEffects[statusName]; }; StatusEffectsReceiver.prototype.ExecuteEffect = function(statusName, lateness) @@ -48,17 +53,17 @@ StatusEffectsReceiver.prototype.ExecuteEffect = function(statusName, lateness) if (!status) return; - if (status.firstTime) + if (status.FirstTime) { - status.firstTime = false; - status.timeElapsed += lateness; + status.FirstTime = false; + status.TimeElapsed += lateness; } else - status.timeElapsed += status.interval + lateness; + status.TimeElapsed += status.Interval + lateness; - Attacking.HandleAttackEffects(statusName, { "Damage": { [statusName]: status.damage } }, this.entity, -1, -1); + Attacking.HandleAttackEffects(statusName, status, this.entity, -1, -1); - if (status.timeElapsed >= status.duration) + if (status.Duration && status.TimeElapsed >= +status.Duration) this.RemoveStatus(statusName); }; diff --git a/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js b/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js index 190a16da9b..311305a3b5 100644 --- a/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js +++ b/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js @@ -31,6 +31,7 @@ Engine.LoadComponentScript("interfaces/TechnologyManager.js"); Engine.LoadComponentScript("interfaces/Trader.js"); Engine.LoadComponentScript("interfaces/Timer.js"); Engine.LoadComponentScript("interfaces/StatisticsTracker.js"); +Engine.LoadComponentScript("interfaces/StatusEffectsReceiver.js"); Engine.LoadComponentScript("interfaces/UnitAI.js"); Engine.LoadComponentScript("interfaces/Upgrade.js"); Engine.LoadComponentScript("interfaces/BuildingAI.js"); diff --git a/binaries/data/mods/public/simulation/components/tests/test_StatusEffectsReceiver.js b/binaries/data/mods/public/simulation/components/tests/test_StatusEffectsReceiver.js index 0e4703f7d1..c71ca31661 100755 --- a/binaries/data/mods/public/simulation/components/tests/test_StatusEffectsReceiver.js +++ b/binaries/data/mods/public/simulation/components/tests/test_StatusEffectsReceiver.js @@ -28,7 +28,9 @@ function testInflictEffects() cmpStatusReceiver.AddStatus(statusName, { "Duration": 20000, "Interval": 10000, - "Damage": 1 + "Damage": { + [statusName]: 1 + } }); cmpTimer.OnUpdate({ "turnLength": 1 }); @@ -65,12 +67,16 @@ function testMultipleEffects() "Burn": { "Duration": 20000, "Interval": 10000, - "Damage": 10 + "Damage": { + "Burn": 10 + } }, "Poison": { "Duration": 3000, "Interval": 1000, - "Damage": 1 + "Damage": { + "Poison": 1 + } } }); @@ -102,7 +108,9 @@ function testRemoveStatus() cmpStatusReceiver.AddStatus(statusName, { "Duration": 20000, "Interval": 10000, - "Damage": 1 + "Damage": { + [statusName]: 1 + } }); cmpTimer.OnUpdate({ "turnLength": 1 }); diff --git a/binaries/data/mods/public/simulation/helpers/Attacking.js b/binaries/data/mods/public/simulation/helpers/Attacking.js index 25a1976714..17319037b4 100644 --- a/binaries/data/mods/public/simulation/helpers/Attacking.js +++ b/binaries/data/mods/public/simulation/helpers/Attacking.js @@ -5,38 +5,44 @@ function Attacking() {} /** * Builds a RelaxRNG schema of possible attack effects. - * Currently harcoded to "Damage", "Capture" and "StatusEffects". + * See globalscripts/AttackEffects.js for possible elements. * Attacks may also have a "Bonuses" element. * * @return {string} - RelaxNG schema string */ +const DamageSchema = "" + + "" + + "" + + "" + + // Armour requires Foundation to not be a damage type. + "Foundation" + + "" + + "" + + "" + + ""; + Attacking.prototype.BuildAttackEffectsSchema = function() { return "" + "" + "" + "" + - "" + - "" + - "" + - // Armour requires Foundation to not be a damage type. - "Foundation" + - "" + - "" + - "" + - "" + + DamageSchema + "" + "" + "" + "" + - "" + + "" + "" + "" + "" + "" + + "" + + "" + + "" + "" + "" + - "" + + "" + DamageSchema + "" + "" + "" + "" + @@ -79,8 +85,8 @@ Attacking.prototype.GetAttackEffectsData = function(valueModifRoot, template, en if (template.Capture) ret.Capture = ApplyValueModificationsToEntity(valueModifRoot + "/Capture", +(template.Capture || 0), entity); - if (template.StatusEffects) - ret.StatusEffects = template.StatusEffects; + if (template.GiveStatus) + ret.GiveStatus = template.GiveStatus; if (template.Bonuses) ret.Bonuses = template.Bonuses;