state:empty
state:<translated
state:>=translated
state:needs-editing
has:suggestion
has:variant
has:screenshot
has:label
has:context
state:<translated AND NOT has:suggestion
has:comment
has:check
state:approved
state:translated
Loading…
String added in the repository
The string uses three dots (...) instead of an ellipsis character (…)
Reset
The string is used as plural, but not using plural forms
Additional explanation to clarify meaning or usage of the string.
Additional comma-separated flags to influence Weblate behavior.
* This macro should help to apply damage, healing, gain or lose MP and/or
* IP for a set of tokens.
* When applying damage, it respects the token's current damage affinity,
* doubling for Vulnerability, halving for Resistance, negating for Immunity,
* and always adding for Absorb.
*
* Written by Erica, Based on a macro by Dark Magician Girl posted in the
* Rooster Games Discord server.
* **************************************************************************
*/
/**
* Poor man's i18n. Edit the values in the hash below to replace the various labels on the dialog
* generated by this macro.
*/
const localization = {
okButtonLabel: "Ok",
cancelButtonLabel: "Cancel",
dialogTitle: "Modify Resources",
hpLabel: "HP",
mpLabel: "MP",
ipLabel: "IP",
hpChangeLabel: "HP Change:",
mpChangeLabel: "MP Change:",
ipChangeLabel: "IP Change:",
recoverAllHPLabel: "Recover All HP",
recoverAllMPLabel: "Recover All MP",
recoverAllIPLabel: "Recover All IP",
recoverAllResourcesLabel: "Recover All HP, MP, and IP",
setHPToCrisisLabel: "Set HP to Crisis score",
loseAllHPLabel: "Set HP to 0",
loseAllMPLabel: "Set MP to 0",
loseAllIPLabel: "Set IP to 0",
noTokenSelectedError: "No token(s) selected",
bypassClampLabel: "Bypass value clamping",
damageTypes: {
physical: { lower: "physical", upper: "Physical" },
air: { lower: "air", upper: "Air" },
bolt: { lower: "bolt", upper: "Bolt" },
dark: { lower: "dark", upper: "Dark" },
earth: { lower: "earth", upper: "Earth" },
fire: { lower: "fire", upper: "Fire" },
ice: { lower: "ice", upper: "Ice" },
light: { lower: "light", upper: "Light" },
poison: { lower: "poison", upper: "Poison" },
untyped: { lower: "untyped", upper: "Untyped" },
},
};
/** From here on things get fucky. */
const targetedTokens = canvas.tokens.controlled;
/** Check for early exit */
if (!targetedTokens?.length) {
ui.notifications.error(localization.noTokenSelectedError);
return;
}
const DAMAGE_TYPES = [
"physical",
"air",
"bolt",
"dark",
"earth",
"fire",
"light",
"poison",
"untyped",
];
renderModifyStatsDialog();
function renderModifyStatsDialog() {
let dialogContent = buildDialogContent(targetedTokens);
let dialog = new Dialog({
title: localization.dialogTitle,
content: dialogContent,
render: onDialogRendered,
default: "ok",
buttons: {
ok: {
label: `<i class='fas fa-check'></i>${localization.okButtonLabel}`,
callback: onOkClick,
},
cancel: {
label: `<i class='fas fa-times'></i>${localization.cancelButtonLabel}`,
},
},
});
dialog.render(true);
}
function generateInfoRow(token, resource, label) {
return `<div><strong>${localization[label]}:</strong> ${token.actor.system.resources[resource].value} / ${token.actor.system.resources[resource].max}</div>`;
}
function generateTokenInfo(token) {
const isPC = token.actor.type === "character";
return [
`<div class="flexcol">`,
`<img src="${token.actor.img}" style="width:36px;height:36px;vertical-align:middle;margin-right: 5px;">`,
`<strong>${token.name}</strong>`,
generateInfoRow(token, "hp", "hpLabel"),
generateInfoRow(token, "mp", "mpLabel"),
isPC ? generateInfoRow(token, "ip", "ipLabel") : "<div></div>",
`</div>`,
].join("\n");
}
function generateDamageTypeOption(damageType) {
return `<option value='${damageType}'>${localization.damageTypes[damageType].upper}</option>`;
}
function buildDialogContent() {
const hasPC =
targetedTokens.find((token) => token.actor.type === "character") !==
undefined;
const tokenInfo = targetedTokens.map(generateTokenInfo);
const damageTypeOptions = DAMAGE_TYPES.map(generateDamageTypeOption);
let ipInfoRow = "";
let ipLossCheck = "";
let ipRecoverCheck = "";
if (hasPC) {
ipInfoRow = `
<div class="flexrow">
<p class="flex2"><label for="attributeIP">${localization.ipChangeLabel}</label></p>
<div class="flex3"><input type="number" id="attributeIP" value=0 step=1></div>
</div>`;
ipRecoverCheck = `
<div>
<input type="checkbox" id="recoverAllIP"><label for="recoverAllIP">${localization.recoverAllIPLabel}</label>
</div>
`;
ipLossCheck = `
<div>
<input type="checkbox" id="loseAllIP"><label for="loseAllIP">${localization.loseAllIPLabel}</label>
</div>
`;
}
return `
<form onsubmit="return false;">
<div class="flexrow">${tokenInfo}</div>
<hr>
<div class="flexrow">
<p class="flex2"><label for="attributeHP">${localization.hpChangeLabel}</label></p>
<div class="flex2"><input type="number" id="attributeHP" value=0 step=1 autofocus></div>
<div class="flex1"><select style="width:100%" id="damageType">${damageTypeOptions}</select></div>
</div>
<div class="flexrow">
<p class="flex2"><label for="attributeMP">${localization.mpChangeLabel}</label></p>
<div class="flex3"><input type="number" id="attributeMP" value=0 step=1></div>
</div>
${ipInfoRow}
<div class="flexrow">
<div>
<input type="checkbox" id="recoverAllHP"><label for="recoverAllHP">${localization.recoverAllHPLabel}</label>
</div>
<div>
<input type="checkbox" id="recoverAllMP"><label for="recoverAllMP">${localization.recoverAllMPLabel}</label>
</div>
${ipRecoverCheck}
</div>
<div class="flexrow">
<div>
<input type="checkbox" id="loseAllHP"><label for="loseAllHP">${localization.loseAllHPLabel}</label>
</div>
<div>
<input type="checkbox" id="loseAllMP"><label for="loseAllMP">${localization.loseAllMPLabel}</label>
</div>
${ipLossCheck}
</div>
<div class="flexrow">
<div>
<input type="checkbox" id="recoverAllResources"><label for="recoverAllResources">${localization.recoverAllResourcesLabel}</label>
</div>
<div>
<input type="checkbox" id="setHPToCrisis"><label for='setHPToCrisis'>${localization.setHPToCrisisLabel}</label>
</div>
</div>
<div class="flexrow">
<input type="checkbox" id="bypassClamp"><label for="bypassClamp">${localization.bypassClampLabel}</label>
</div>
</form>
`;
}
function iterateElements(html, ids, callback) {
ids
.map((id) => html.find(`#${id}`)[0])
.filter((elem) => typeof elem !== "undefined")
.forEach(callback);
}
function hookFocusEvent(html, id) {
const elem = html.find(`#${id}`)[0];
if (elem) {
elem.addEventListener("focus", ({target}) => { target.select(); });
}
}
function hookCheckEvent(html, id, toUncheck, toDisable) {
const elem = html.find(`#${id}`)[0];
if (elem) {
elem.addEventListener("change", (event) => {
if (event.target.checked)
iterateElements(html, toUncheck, (elem) => (elem.checked = false));
if (toDisable?.length)
iterateElements(html, toDisable, (elem) => (elem.disabled = event.target.checked));
});
}
}
function onDialogRendered(html) {
// Handle unchecking conflicting checkboxes
hookCheckEvent(html, "recoverAllHP", ["loseAllHP", "setHPToCrisis", "recoverAllResources"], ["attributeHP", "damageType"]);
hookCheckEvent(html, "loseAllHP", ["recoverAllHP", "recoverAllResources", "setHPToCrisis"], ["attributeHP", "damageType"]);
hookCheckEvent(html, "setHPToCrisis", ["recoverAllHP", "loseAllHP", "recoverAllResources"], ["attributeHP", "damageType"]);
hookCheckEvent(html, "recoverAllMP", ["loseAllMP", "recoverAlResources"], ["attributeMP"]);
hookCheckEvent(html, "loseAllMP", ["recoverAllMP", "recoverAllResources"], ["attributeMP"]);
hookCheckEvent(html, "recoverAllIP", ["loseAllIP", "recoverAllResources"], ["attributeIP"]);
hookCheckEvent(html, "loseAllIP", ["recoverAllIP", "recoverAllResources"], ["attributeIP"]);
hookCheckEvent(html, "recoverAllResources", ["loseAllHP", "loseAllMP", "loseAllIP", "setHPToCrisis", "recoverAllHP", "recoverAllMP", "recoverAllIP"], ["attributeHP", "attributeMP", "attributeIP", "damageType"]);
hookCheckEvent(html, "setHPToCrisis", ["loseAllHP", "recoverAllHP", "recoverAllResources"], ["attributeHP", "damageType"]);
// Handle selecting the contents of an input field when focused, since it does not do so automatically on mouse click
hookFocusEvent(html, "attributeHP");
hookFocusEvent(html, "attributeMP");
hookFocusEvent(html, "attributeIP");
}
function adjustDamageForAffinities(amount, affinity) {
switch (affinity) {
case -1:
return amount * 2;
break;
case 1:
return Math.floor(amount / 2);
break;
case 2:
return 0;
break;
case 3:
return Math.abs(amount);
break;
default:
return amount;
}
}
function onOkClick(html) {
const updates = targetedTokens.forEach((token) => {
let deltaHP = parseInt(html.find("#attributeHP")[0]?.value) || 0;
let deltaMP = parseInt(html.find("#attributeMP")[0]?.value) || 0;
let deltaIP = parseInt(html.find("#attributeIP")[0]?.value) || 0;
const { actor } = token;
const { system } = actor;
const { resources, affinities } = system;
const recoverAll = html.find("#recoverAllResources")[0]?.checked ?? false;
// Coerce updated MP value
let newMP;
if (recoverAll || html.find("#recoverAllMP")[0]?.checked) newMP = resources.mp.max;
else if (html.find("#loseAllMP")[0]?.checked) newMP = 0;
else newMP = resources.mp.value + deltaMP;
// Coerce updated IP value
let newIP;
if (recoverAll || html.find("#recoverAllIP")[0]?.checked) newIP = resources.ip.max;
else if (html.find("#loseAllIP")[0]?.checked) newIP = 0;
else newIP = resources.ip.value + deltaIP;
// Coerce updated HP vaue
let newHP;
if (recoverAll || html.find("#recoverAllHP")[0]?.checked) newHP = resources.hp.max;
else if (html.find("#loseAllHP")[0]?.checked) newHP = 0;
else if (html.find("#setHPToCrisis")[0]?.checked) newHP = Math.floor(resources.hp.max / 2);
else newHP = resources.hp.value + adjustDamageForAffinities(deltaHP, affinities[html.find("#damageType")[0].value].current ?? 0);
const bypassClamp = html.find("#bypassClamp")[0]?.checked || false;
if (!bypassClamp) {
newHP = Math.clamped(newHP, 0, resources.hp.max);
newMP = Math.clamped(newMP, 0, resources.mp.max);
newIP = Math.clamped(newIP, 0, resources.ip.max);
}
const update = {
...(resources.hp.value !== newHP ? { "system.resources.hp.value": newHP } : {}),
...(resources.mp.value !== newMP ? { "system.resources.mp.value": newMP } : {}),
...(actor.type === "character" && resources.ip.value !== newIP ? { "system.resources.ip.value": newIP } : {})
};
console.log("Submitting update:", update);
if (Object.keys(update).length) token.actor.update(update);
});
}
* This macro should help to apply damage, healing, gain or lose MP and/or
* IP for a set of tokens.
* When applying damage, it respects the token's current damage affinity,
* doubling for Vulnerability, halving for Resistance, negating for Immunity,
* and always adding for Absorb.
*
* Written by Erica, Based on a macro by Dark Magician Girl posted in the
* Rooster Games Discord server.
* **************************************************************************
*/
/**
* Poor man's i18n. Edit the values in the hash below to replace the various labels on the dialog
* generated by this macro.
*/
const localization = {
okButtonLabel: "Ok",
cancelButtonLabel: "Cancel",
dialogTitle: "Modify Resources",
hpLabel: "HP",
mpLabel: "MP",
ipLabel: "IP",
hpChangeLabel: "HP Change:",
mpChangeLabel: "MP Change:",
ipChangeLabel: "IP Change:",
recoverAllHPLabel: "Recover All HP",
recoverAllMPLabel: "Recover All MP",
recoverAllIPLabel: "Recover All IP",
recoverAllResourcesLabel: "Recover All HP, MP, and IP",
setHPToCrisisLabel: "Set HP to Crisis score",
loseAllHPLabel: "Set HP to 0",
loseAllMPLabel: "Set MP to 0",
loseAllIPLabel: "Set IP to 0",
noTokenSelectedError: "No token(s) selected",
bypassClampLabel: "Bypass value clamping",
damageTypes: {
physical: { lower: "physical", upper: "Physical" },
air: { lower: "air", upper: "Air" },
bolt: { lower: "bolt", upper: "Bolt" },
dark: { lower: "dark", upper: "Dark" },
earth: { lower: "earth", upper: "Earth" },
fire: { lower: "fire", upper: "Fire" },
ice: { lower: "ice", upper: "Ice" },
light: { lower: "light", upper: "Light" },
poison: { lower: "poison", upper: "Poison" },
untyped: { lower: "untyped", upper: "Untyped" },
},
};
/** From here on things get fucky. */
const targetedTokens = canvas.tokens.controlled;
/** Check for early exit */
if (!targetedTokens?.length) {
ui.notifications.error(localization.noTokenSelectedError);
return;
}
const DAMAGE_TYPES = [
"physical",
"air",
"bolt",
"dark",
"earth",
"fire",
"light",
"poison",
"untyped",
];
renderModifyStatsDialog();
function renderModifyStatsDialog() {
let dialogContent = buildDialogContent(targetedTokens);
let dialog = new Dialog({
title: localization.dialogTitle,
content: dialogContent,
render: onDialogRendered,
default: "ok",
buttons: {
ok: {
label: `<i class='fas fa-check'></i>${localization.okButtonLabel}`,
callback: onOkClick,
},
cancel: {
label: `<i class='fas fa-times'></i>${localization.cancelButtonLabel}`,
},
},
});
dialog.render(true);
}
function generateInfoRow(token, resource, label) {
return `<div><strong>${localization[label]}:</strong> ${token.actor.system.resources[resource].value} / ${token.actor.system.resources[resource].max}</div>`;
}
function generateTokenInfo(token) {
const isPC = token.actor.type === "character";
return [
`<div class="flexcol">`,
`<img src="${token.actor.img}" style="width:36px;height:36px;vertical-align:middle;margin-right: 5px;">`,
`<strong>${token.name}</strong>`,
generateInfoRow(token, "hp", "hpLabel"),
generateInfoRow(token, "mp", "mpLabel"),
isPC ? generateInfoRow(token, "ip", "ipLabel") : "<div></div>",
`</div>`,
].join("\n");
}
function generateDamageTypeOption(damageType) {
return `<option value='${damageType}'>${localization.damageTypes[damageType].upper}</option>`;
}
function buildDialogContent() {
const hasPC =
targetedTokens.find((token) => token.actor.type === "character") !==
undefined;
const tokenInfo = targetedTokens.map(generateTokenInfo);
const damageTypeOptions = DAMAGE_TYPES.map(generateDamageTypeOption);
let ipInfoRow = "";
let ipLossCheck = "";
let ipRecoverCheck = "";
if (hasPC) {
ipInfoRow = `
<div class="flexrow">
<p class="flex2"><label for="attributeIP">${localization.ipChangeLabel}</label></p>
<div class="flex3"><input type="number" id="attributeIP" value=0 step=1></div>
</div>`;
ipRecoverCheck = `
<div>
<input type="checkbox" id="recoverAllIP"><label for="recoverAllIP">${localization.recoverAllIPLabel}</label>
</div>
`;
ipLossCheck = `
<div>
<input type="checkbox" id="loseAllIP"><label for="loseAllIP">${localization.loseAllIPLabel}</label>
</div>
`;
}
return `
<form onsubmit="return false;">
<div class="flexrow">${tokenInfo}</div>
<hr>
<div class="flexrow">
<p class="flex2"><label for="attributeHP">${localization.hpChangeLabel}</label></p>
<div class="flex2"><input type="number" id="attributeHP" value=0 step=1 autofocus></div>
<div class="flex1"><select style="width:100%" id="damageType">${damageTypeOptions}</select></div>
</div>
<div class="flexrow">
<p class="flex2"><label for="attributeMP">${localization.mpChangeLabel}</label></p>
<div class="flex3"><input type="number" id="attributeMP" value=0 step=1></div>
</div>
${ipInfoRow}
<div class="flexrow">
<div>
<input type="checkbox" id="recoverAllHP"><label for="recoverAllHP">${localization.recoverAllHPLabel}</label>
</div>
<div>
<input type="checkbox" id="recoverAllMP"><label for="recoverAllMP">${localization.recoverAllMPLabel}</label>
</div>
${ipRecoverCheck}
</div>
<div class="flexrow">
<div>
<input type="checkbox" id="loseAllHP"><label for="loseAllHP">${localization.loseAllHPLabel}</label>
</div>
<div>
<input type="checkbox" id="loseAllMP"><label for="loseAllMP">${localization.loseAllMPLabel}</label>
</div>
${ipLossCheck}
</div>
<div class="flexrow">
<div>
<input type="checkbox" id="recoverAllResources"><label for="recoverAllResources">${localization.recoverAllResourcesLabel}</label>
</div>
<div>
<input type="checkbox" id="setHPToCrisis"><label for='setHPToCrisis'>${localization.setHPToCrisisLabel}</label>
</div>
</div>
<div class="flexrow">
<input type="checkbox" id="bypassClamp"><label for="bypassClamp">${localization.bypassClampLabel}</label>
</div>
</form>
`;
}
function iterateElements(html, ids, callback) {
ids
.map((id) => html.find(`#${id}`)[0])
.filter((elem) => typeof elem !== "undefined")
.forEach(callback);
}
function hookFocusEvent(html, id) {
const elem = html.find(`#${id}`)[0];
if (elem) {
elem.addEventListener("focus", ({target}) => { target.select(); });
}
}
function hookCheckEvent(html, id, toUncheck, toDisable) {
const elem = html.find(`#${id}`)[0];
if (elem) {
elem.addEventListener("change", (event) => {
if (event.target.checked)
iterateElements(html, toUncheck, (elem) => (elem.checked = false));
if (toDisable?.length)
iterateElements(html, toDisable, (elem) => (elem.disabled = event.target.checked));
});
}
}
function onDialogRendered(html) {
// Handle unchecking conflicting checkboxes
hookCheckEvent(html, "recoverAllHP", ["loseAllHP", "setHPToCrisis", "recoverAllResources"], ["attributeHP", "damageType"]);
hookCheckEvent(html, "loseAllHP", ["recoverAllHP", "recoverAllResources", "setHPToCrisis"], ["attributeHP", "damageType"]);
hookCheckEvent(html, "setHPToCrisis", ["recoverAllHP", "loseAllHP", "recoverAllResources"], ["attributeHP", "damageType"]);
hookCheckEvent(html, "recoverAllMP", ["loseAllMP", "recoverAlResources"], ["attributeMP"]);
hookCheckEvent(html, "loseAllMP", ["recoverAllMP", "recoverAllResources"], ["attributeMP"]);
hookCheckEvent(html, "recoverAllIP", ["loseAllIP", "recoverAllResources"], ["attributeIP"]);
hookCheckEvent(html, "loseAllIP", ["recoverAllIP", "recoverAllResources"], ["attributeIP"]);
hookCheckEvent(html, "recoverAllResources", ["loseAllHP", "loseAllMP", "loseAllIP", "setHPToCrisis", "recoverAllHP", "recoverAllMP", "recoverAllIP"], ["attributeHP", "attributeMP", "attributeIP", "damageType"]);
hookCheckEvent(html, "setHPToCrisis", ["loseAllHP", "recoverAllHP", "recoverAllResources"], ["attributeHP", "damageType"]);
// Handle selecting the contents of an input field when focused, since it does not do so automatically on mouse click
hookFocusEvent(html, "attributeHP");
hookFocusEvent(html, "attributeMP");
hookFocusEvent(html, "attributeIP");
}
function adjustDamageForAffinities(amount, affinity) {
switch (affinity) {
case -1:
return amount * 2;
break;
case 1:
return Math.floor(amount / 2);
break;
case 2:
return 0;
break;
case 3:
return Math.abs(amount);
break;
default:
return amount;
}
}
function onOkClick(html) {
const updates = targetedTokens.forEach((token) => {
let deltaHP = parseInt(html.find("#attributeHP")[0]?.value) || 0;
let deltaMP = parseInt(html.find("#attributeMP")[0]?.value) || 0;
let deltaIP = parseInt(html.find("#attributeIP")[0]?.value) || 0;
const { actor } = token;
const { system } = actor;
const { resources, affinities } = system;
const recoverAll = html.find("#recoverAllResources")[0]?.checked ?? false;
// Coerce updated MP value
let newMP;
if (recoverAll || html.find("#recoverAllMP")[0]?.checked) newMP = resources.mp.max;
else if (html.find("#loseAllMP")[0]?.checked) newMP = 0;
else newMP = resources.mp.value + deltaMP;
// Coerce updated IP value
let newIP;
if (recoverAll || html.find("#recoverAllIP")[0]?.checked) newIP = resources.ip.max;
else if (html.find("#loseAllIP")[0]?.checked) newIP = 0;
else newIP = resources.ip.value + deltaIP;
// Coerce updated HP vaue
let newHP;
if (recoverAll || html.find("#recoverAllHP")[0]?.checked) newHP = resources.hp.max;
else if (html.find("#loseAllHP")[0]?.checked) newHP = 0;
else if (html.find("#setHPToCrisis")[0]?.checked) newHP = Math.floor(resources.hp.max / 2);
else newHP = resources.hp.value + adjustDamageForAffinities(deltaHP, affinities[html.find("#damageType")[0].value].current ?? 0);
const bypassClamp = html.find("#bypassClamp")[0]?.checked || false;
if (!bypassClamp) {
newHP = Math.clamped(newHP, 0, resources.hp.max);
newMP = Math.clamped(newMP, 0, resources.mp.max);
newIP = Math.clamped(newIP, 0, resources.ip.max);
}
const update = {
...(resources.hp.value !== newHP ? { "system.resources.hp.value": newHP } : {}),
...(resources.mp.value !== newMP ? { "system.resources.mp.value": newMP } : {}),
...(actor.type === "character" && resources.ip.value !== newIP ? { "system.resources.ip.value": newIP } : {})
};
console.log("Submitting update:", update);
if (Object.keys(update).length) token.actor.update(update);
});
}