mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
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.
This commit is contained in:
parent
89e511def9
commit
2333b1814e
9 changed files with 135 additions and 42 deletions
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:db2f0b822606a94a0769680265a043c40ac51a1306652dee5d04164d419acbae
|
||||
size 15527
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -82,9 +82,18 @@
|
|||
<object z="20" size="4 4 20 20" name="rankIcon" type="image" tooltip_style="sessionToolTip">
|
||||
<translatableAttribute id="tooltip">Rank</translatableAttribute>
|
||||
</object>
|
||||
|
||||
<!-- Status Effecst icons -->
|
||||
<object name="statusEffectsIcons" size="100%-20 4 100%-4 100%">
|
||||
<repeat count="5">
|
||||
<object type="image" size="0 0 16 16" z="200" tooltip_style="sessionToolTip"/>
|
||||
</repeat>
|
||||
</object>
|
||||
|
||||
</object>
|
||||
</object>
|
||||
|
||||
|
||||
<!-- Names (this must come before the attack and armor icon to avoid clipping issues) -->
|
||||
<object size="2 96 100%-2 100%-36" name="statsArea" type="image" sprite="edgedPanelShader">
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
|
|
|
|||
|
|
@ -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 = "" +
|
||||
"<oneOrMore>" +
|
||||
"<element a:help='One or more elements describing damage types'>" +
|
||||
"<anyName>" +
|
||||
// Armour requires Foundation to not be a damage type.
|
||||
"<except><name>Foundation</name></except>" +
|
||||
"</anyName>" +
|
||||
"<ref name='nonNegativeDecimal' />" +
|
||||
"</element>" +
|
||||
"</oneOrMore>";
|
||||
|
||||
Attacking.prototype.BuildAttackEffectsSchema = function()
|
||||
{
|
||||
return "" +
|
||||
"<oneOrMore>" +
|
||||
"<choice>" +
|
||||
"<element name='Damage'>" +
|
||||
"<oneOrMore>" +
|
||||
"<element a:help='One or more elements describing damage types'>" +
|
||||
"<anyName>" +
|
||||
// Armour requires Foundation to not be a damage type.
|
||||
"<except><name>Foundation</name></except>" +
|
||||
"</anyName>" +
|
||||
"<ref name='nonNegativeDecimal' />" +
|
||||
"</element>" +
|
||||
"</oneOrMore>" +
|
||||
DamageSchema +
|
||||
"</element>" +
|
||||
"<element name='Capture' a:help='Capture points value'>" +
|
||||
"<ref name='nonNegativeDecimal'/>" +
|
||||
"</element>" +
|
||||
"<element name='StatusEffects' a:help='Effects like poisoning or burning a unit.'>" +
|
||||
"<element name='GiveStatus' a:help='Effects like poisoning or burning a unit.'>" +
|
||||
"<oneOrMore>" +
|
||||
"<element>" +
|
||||
"<anyName/>" +
|
||||
"<interleave>" +
|
||||
"<optional>" +
|
||||
"<element name='Icon' a:help='Icon for the status effect'><text/></element>" +
|
||||
"</optional>" +
|
||||
"<element name='Duration' a:help='The duration of the status while the effect occurs.'><ref name='nonNegativeDecimal'/></element>" +
|
||||
"<element name='Interval' a:help='Interval between the occurances of the effect.'><ref name='nonNegativeDecimal'/></element>" +
|
||||
"<element name='Damage' a:help='Damage caused by the effect.'><ref name='nonNegativeDecimal'/></element>" +
|
||||
"<element name='Damage' a:help='Damage caused by the effect.'>" + DamageSchema + "</element>" +
|
||||
"</interleave>" +
|
||||
"</element>" +
|
||||
"</oneOrMore>" +
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue