Skip to content

Instantly share code, notes, and snippets.

@joesinghaus
Last active June 21, 2023 18:11
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save joesinghaus/9fd52108d07e5ef83ed879a7c6fdc68f to your computer and use it in GitHub Desktop.
Save joesinghaus/9fd52108d07e5ef83ed879a7c6fdc68f to your computer and use it in GitHub Desktop.
ApplyDamage
/* global log, _, getObj, HealthColors, playerIsGM, sendChat, on */
const ApplyDamage = (() => {
"use strict";
const version = "1.1",
observers = {
"change": [],
},
boundedBar = false,
checkInstall = () => {
log(`-=> ApplyDamage v${version} <=-`);
},
defaultOpts = {
type: "half",
ids: "",
saves: "",
DC: "-1",
dmg: "0",
bar: "1"
},
statusMarkers = [
"red", "blue", "green", "brown", "purple", "pink", "yellow", "dead", "skull", "sleepy", "half-heart",
"half-haze", "interdiction", "snail", "lightning-helix", "spanner", "chained-heart", "chemical-bolt",
"death-zone", "drink-me", "edge-crack", "ninja-mask", "stopwatch", "fishing-net", "overdrive", "strong",
"fist", "padlock", "three-leaves", "fluffy-wing", "pummeled", "tread", "arrowed", "aura", "back-pain",
"black-flag", "bleeding-eye", "bolt-shield", "broken-heart", "cobweb", "broken-shield", "flying-flag",
"radioactive", "trophy", "broken-skull", "frozen-orb", "rolling-bomb", "white-tower", "grab", "screaming",
"grenade", "sentry-gun", "all-for-one", "angel-outfit", "archery-target"
],
getWhisperPrefix = (playerid) => {
const player = getObj("player", playerid);
if (player && player.get("_displayname")) {
return `/w "${player.get("_displayname")}" `;
}
else {
return "/w GM ";
}
},
parseOpts = (content, hasValue) => {
return content
.replace(/<br\/>\n/g, " ")
.replace(/({{(.*?)\s*}}\s*$)/g, "$2")
.split(/\s+--/)
.slice(1)
.reduce((opts, arg) => {
const kv = arg.split(/\s(.+)/);
if (hasValue.includes(kv[0])) {
opts[kv[0]] = (kv[1] || "");
} else {
opts[arg] = true;
}
return opts;
}, {});
},
processInlinerolls = function (msg) {
if (msg.inlinerolls && msg.inlinerolls.length) {
return msg.inlinerolls.map(v => {
const ti = v.results.rolls.filter(v2 => v2.table)
.map(v2 => v2.results.map(v3 => v3.tableItem.name).join(", "))
.join(", ");
return (ti.length && ti) || v.results.total || 0;
}).reduce((m, v, k) => m.replace(`$[[${k}]]`, v), msg.content);
} else {
return msg.content;
}
},
handleError = (whisper, errorMsg) => {
const output = `${whisper}<div style="border:1px solid black;background:#FFBABA;padding:3px">` +
`<h4>Error</h4><p>${errorMsg}</p></div>`;
sendChat("ApplyDamage", output);
},
finalApply = (results, dmg, type, bar, status) => {
const barCur = `bar${bar}_value`,
barMax = `bar${bar}_max`;
Object.entries(results).forEach(([id, saved]) => {
const token = getObj("graphic", id),
prev = JSON.parse(JSON.stringify(token || {}));
let newValue;
if (token && !saved) {
if (boundedBar) {
newValue = Math.min(Math.max(parseInt(token.get(barCur)) - dmg, 0), parseInt(token.get(barMax)));
} else {
newValue = parseInt(token.get(barCur)) - dmg;
}
if (status) token.set(`status_${status}`, true);
}
else if (token && type === "half") {
if (boundedBar) {
newValue = Math.min(Math.max(parseInt(token.get(barCur)) - Math.floor(dmg / 2), 0), parseInt(token.get(barMax)));
} else {
newValue = parseInt(token.get(barCur)) - Math.floor(dmg / 2);
}
}
if (!_.isUndefined(newValue)) {
if (Number.isNaN(newValue)) newValue = token.get(barCur);
token.set(barCur, newValue);
notifyObservers("change", token, prev);
}
});
},
handleInput = (msg) => {
if (msg.type === "api" && msg.content.search(/^!apply-damage\b/) !== -1) {
const hasValue = ["ids", "saves", "DC", "type", "dmg", "bar", "status"],
opts = Object.assign({}, defaultOpts, parseOpts(processInlinerolls(msg), hasValue));
opts.ids = opts.ids.split(/,\s*/g);
opts.saves = opts.saves.split(/,\s*/g);
opts.DC = parseInt(opts.DC);
opts.dmg = parseInt(opts.dmg);
if (!playerIsGM(msg.playerid) && getObj("player", msg.playerid)) {
handleError(getWhisperPrefix(msg.playerid), "Permission denied.");
return;
}
if (!["1", "2", "3"].includes(opts.bar)) {
handleError(getWhisperPrefix(msg.playerid), "Invalid bar.");
return;
}
if (opts.status === "none") {
delete opts.status;
}
if (opts.status && !statusMarkers.includes(opts.status)) {
handleError(getWhisperPrefix(msg.playerid), "Invalid status.");
return;
}
const results = _.reduce(opts.ids, function (m, id, k) {
m[id] = parseInt(opts.saves[k] || "0") >= opts.DC;
return m;
}, {});
finalApply(results, opts.dmg, opts.type, opts.bar, opts.status);
const output = `${
getWhisperPrefix(msg.playerid)
}<div style="border:1px solid black;background:#FFF;padding:3px"><p>${
(opts.dmg ? `${opts.dmg} damage applied to tokens, with ${
(opts.type === "half" ? "half" : "no")
} damage on a successful saving throw.` : "")}${
(opts.status ? ` ${opts.status} status marker applied to tokens that failed the save.` : "")
}</p></div>`;
sendChat("ApplyDamage", output, null, { noarchive: true });
}
return;
},
notifyObservers = (event, obj, prev) => {
observers[event].forEach(observer => observer(obj, prev));
},
registerObserver = (event, observer) => {
if (observer && _.isFunction(observer) && observers.hasOwnProperty(event)) {
observers[event].push(observer);
} else {
log("ApplyDamage event registration unsuccessful.");
}
},
registerEventHandlers = () => {
on("chat:message", handleInput);
};
return {
checkInstall,
registerEventHandlers,
registerObserver
};
})();
on("ready", () => {
"use strict";
ApplyDamage.checkInstall();
ApplyDamage.registerEventHandlers();
if ("undefined" !== typeof HealthColors) {
ApplyDamage.registerObserver("change", HealthColors.Update);
}
});
@Bragunetzki
Copy link

Is there any way I can make this apply damage to a specific bar?

@BronxBob
Copy link

I was having trouble getting the "-- bar" to work in macros so I just changed line 18 to default 3. Would the proper syntax be "!apply-damage --bar3"? hmm also it is being put in a button... so brackets of somekind are needed?

@cemckinstry
Copy link

I have the GroupCheck API installed and your ApplyDamage as well. When I do a group check and apply the damage afterwards, it works. But then when I try to change an individual token's hp it affects all of them. So, the API works correctly, but afterwards any changes made to an individual token will apply to all.

@gvarnadoe
Copy link

gvarnadoe commented Jul 8, 2021

Is there a way to add new status markers to the script? I have "Markers by Jinx" installed and would like to be able to use some of them in place of the defaults, but I get "invalid status" when I try to add them to the list (added new status name to script and macro).

edit: there is apparently a new "token_markers" API function, but I don't know enough about js to incorporate it into the script.

@midkick28
Copy link

is there a way to use this API with the It's A Trap API to apply the damage from the trap? More specific how do i call this api to be used by the Its a trap API?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment