Skip to content

Instantly share code, notes, and snippets.

@keithcurtis1
Last active July 3, 2023 18:02
Show Gist options
  • Save keithcurtis1/d2a56da7d469fde45c81b983cb6d0f8f to your computer and use it in GitHub Desktop.
Save keithcurtis1/d2a56da7d469fde45c81b983cb6d0f8f to your computer and use it in GitHub Desktop.
var API_Meta = API_Meta || {}; //eslint-disable-line no-var
API_Meta.Condefinition = {
offset: Number.MAX_SAFE_INTEGER,
lineCount: -1
}; {
try {
throw new Error('');
} catch (e) {
API_Meta.Condefinition.offset = (parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/, '$1'), 10) - (7));
}
}
/* globals libTokenMarkers, TokenMod, SmartAoE */
on('ready', () => {
let applyDamageIsInstalled = false;
//check for dependencies
if (typeof ApplyDamage !== "undefined") {
applyDamageIsInstalled = true;
}
let groupCheckIsInstalled = false;
if (typeof GroupCheck !== "undefined") {
groupCheckIsInstalled = true;
}
const version = '0.0.0b12';
log('Condefinitions v' + version + ' is ready! --offset ' + API_Meta.Condefinition.offset + ' for the D&D 5th Edition by Roll20 Sheet.');
const buttonstyle = `"background-color:#702c91; border-style:none;border-radius:0px 0px 0px 0px; margin:3px 2px 3px 0px; text-decoration:none;display:inline-block;color:#e6e6e6; font-family:Arial; font-size:13px; padding:0px 7px"`;
const publicButtonStyle = `"background-color:#702c91; border-style:none;border-radius:10px 0px 0px 10px; margin:3px 2px 3px 3px; text-decoration:none;display:inline-block;color:#e6e6e6; font-family:Arial; font-size:13px; padding:0px 3px"`;
const markerButtonStyle = `"background-color:#702c91; border-style:none;border-radius:0px 10px 10px 0px; margin:3px 3px 3px 0px; text-decoration:none;display:inline-block;color:#e6e6e6; font-family:Arial; font-size:13px; padding:0px 3px"`;
const saveButtonstyle = `"background-color:#ce0f69; border-width:0px;border-radius:13px; margin:3px; text-transform:capitalize;text-decoration:none;display:inline-block;color:#fff; font-family:Arial; font-size:13px; padding:0px 7px"`;
const groupSaveButtonstyle = `"background-color:#ce0f69; border-width:0px;border-radius:13px 0px 0px 13px; margin:3px 1px 3px 3px; text-transform:capitalize;text-decoration:none;display:inline-block;color:#fff; font-family:Arial; font-size:13px; padding:0px 7px"`;
const applyDamageButtonstyle = `"background-color:#ce0f69; border-width:0px;border-radius:0px 13px 13px 0px; margin:3px 3px 3px 1px; text-transform:capitalize;text-decoration:none;display:inline-block;color:#fff; font-family:Arial; font-size:13px; padding:0px 7px"`;
const conditionnamestyle = `"background-color:#702c91; border-style:none;border-radius:6px 6px 0px 0px; margin:-8px -8px 3px -8px; font-weight:bold;text-transform:capitalize;text-decoration:none;display:block;text-align:center;color:#e6e6e6; font-family:Arial; font-size:13px; padding:0px 7px"`;
const reportstyle = `"display: block; position:relative;left: -5px; top: -30px; margin-bottom: -34px; background-color:#aaa; border-radius:6px; text-decoration:none;color:#000; font-family:Arial; font-size:13px; padding: 8px;"`;
const preportstyle = `"display: block; background-color:#aaa; border-radius:6px; text-decoration:none;color:#000; font-family:Arial; font-size:13px; padding: 8px;"`;
const regexForTitles = /<a style='background-color: transparent;padding: 0px;color: #702c91;display: inline-block;border: none;' href='!condef incapacitated'>incapacitated<\/a>/;
const buttonbox = `<div style='position:relative;left: -5px; top: -30px; margin-bottom: -34px; min-height:30px; border: solid 1px #444; border-radius:4px; background-color:#000;display:block'>`;
const buttonboxUnshifted = `<div style='position:relative;left: -5px; top: min-height:30px; border: solid 1px #444; border-radius:4px; background-color:#000;display:block'>`;
const printChar = '<span style="font-family: pictos">w</a>';
// SET STATUS CONDITIONS OR TOKEN MARKERS HERE
const condefConcentrationMarker = "frozen-orb";
// Double colons in Custom Token Marker names must use double semicolons
const conditionsArray = [
["blinded",
/(are|be|and|is|magically|become|becomes|is either) blind|blinded|blinded condition/,
"• A blinded creature can’t see and automatically fails any ability check that requires sight.<BR>• Attack rolls against the creature have advantage, and the creature’s attack rolls have disadvantage.",
`bleeding-eye`
],
[
"charmed",
/(be|and|is|magically|become|becomes) charmed|charmed condition/,
"• A charmed creature can’t attack the charmer or target the charmer with harmful abilities or magical effects.<BR>• The charmer has advantage on any ability check to interact socially with the creature.",
`chained-heart`
],
[
"concentrating",
/concentration| up to /,
"Some spells require you to maintain concentration in order to keep their magic active. If you lose concentration, such a spell ends.<BR>Normal activity, such as moving and attacking, doesn’t interfere with concentration. The following factors can break concentration:<BR>• Casting another spell that requires concentration. You lose concentration on a spell if you cast another spell that requires concentration. You can’t concentrate on two spells at once.<BR>• Taking damage. Whenever you take damage while you are concentrating on a spell, you must make a Constitution saving throw to maintain your concentration. The DC equals 10 or half the damage you take, whichever number is higher. If you take damage from multiple sources, such as an arrow and a dragon’s breath, you make a separate saving throw for each source of damage.<BR>• Being incapacitated or killed. You lose concentration on a spell if you are incapacitated or if you die.<BR>• The DM might also decide that certain environmental phenomena, such as a wave crashing over you while you’re on a storm-tossed ship, require you to succeed on a DC 10 Constitution saving throw to maintain concentration on a spell.",
`stopwatch`
],
[
"deafened",
/(be|and|is|magically|become|becomes) deafened|deafened condition/,
"• A deafened creature can’t hear and automatically fails any ability check that requires hearing.",
`overdrive`
],
[
"exhaustion",
/(be|and|is|magically|become|becomes) exhausted|(of|magical) exhaustion|exhausted condition/,
"Some special ablities and environmental hazards, such as starvation and the long-term effects of freezing or scorching temperatures, can lead to a spcial condition called exhaustion. Exhaustion is measured in six levels. An Effect can give a creature one or more levels of exhaustion, as specified in the effect’s description.<BR><BR><table><thead><tr><th>Lvl&nbsp;</th><th>Effect</th></tr></thead><tbody><tr><td>1</td><td>Disadvantage on Ability Checks</td></tr><tr><td>2</td><td>Speed halved</td></tr><tr><td>3</td><td>Disadvantage on attack rolls and Saving Throws<br></td></tr><tr><td>4</td><td>Hit point maximum halved</td></tr><tr><td>5</td><td>Speed reduced to 0</td></tr><tr><td>6</td><td>Death</td></tr></tbody></table><BR>If an already exhausted creature suffers another effect that causes exhaustion, its current level of exhaustion increases by the amount specified in the effect’s description.<BR><BR>A creature suffers the effect of its current level of exhaustion as well as all lower levels. For example, a creature suffering level 2 exhaustion has its speed halved and has disadvantage on Ability Checks.<BR><BR>An Effect that removes exhaustion reduces its level as specified in the effect’s description, with all exhaustion Effects Ending if a creature’s exhaustion level is reduced below 1.<BR><BR>Finishing a Long Rest reduces a creature’s exhaustion level by 1, provided that the creature has also ingested some food and drink. Also, being raised from the dead reduces a creature's exhaustion level by 1.",
`half-haze`
],
[
"frightened",
/(be|and|is|magically|become|becomes) frightened|frightened condition/,
"• A frightened creature has disadvantage on Ability Checks and attack rolls while the source of its fear is within Line of Sight.<BR>• The creature can’t willingly move closer to the source of its fear.",
`screaming`
],
[
"grappled",
/(be|and|is|magically|becomes|considered|to) grapple|grappled|grappled condition/,
"• A grappled creature’s speed becomes 0, and it can’t benefit from any bonus to its speed.<BR>• The condition ends if the grappler is <a style='background-color: transparent;padding: 0px;color: #702c91;display: inline-block;border: none;' href='!condef incapacitated'>incapacitated</a>.<BR>• The condition also ends if an effect removes the grappled creature from the reach of the Grappler or Grappling Effect, such as when a creature is hurled away by the thunderwave spell.",
`grab`
],
[
"incapacitated",
/(be|and|is|magically|become|becomes) incapacitated|incapacitated condition/,
"• An incapacitated creature can’t take actions or reactions.",
`interdiction`
],
[
"invisible",
/(be|and|is|magically|become|becomes) invisible|invisible condition/,
"• An invisible creature is impossible to see without the aid of magic or a special sense. For the purpose of hiding, the creature is heavily obscured. The creature’s location can be detected by any noise it makes or any tracks it leaves.<BR>• Attack rolls against the creature have disadvantage, and the creature’s Attack rolls have advantage.",
`ninja-mask`
],
[
"paralyzed",
/(be|and|is|magically|become|becomes) paralyzed|paralyzed condition/,
"• A paralyzed creature is <a style='background-color: transparent;padding: 0px;color: #702c91;display: inline-block;border: none;' href='!condef incapacitated'>incapacitated</a> and can’t move or speak.<BR>• The creature automatically fails Strength and Dexterity Saving Throws.<BR>• Attack rolls against the creature have advantage.<BR>• Any Attack that hits the creature is a critical hit if the attacker is within 5 feet of the creature.",
`aura`
],
[
"petrified",
/(be|and|is|magically|become|becomes|becoming) petrified|(turns to|turning to) stone|petrified condition/,
"• A petrified creature is transformed, along with any nonmagical object it is wearing or carrying, into a solid inanimate substance (usually stone). Its weight increases by a factor of ten, and it ceases aging.<BR>• The creature is <a style='background-color: transparent;padding: 0px;color: #702c91;display: inline-block;border: none;' href='!condef incapacitated'>incapacitated</a>, can’t move or speak, and is unaware of its surroundings.<BR>• Attack rolls against the creature have advantage.<BR>• The creature automatically fails Strength and Dexterity Saving Throws.<BR>The creature has Resistance to all damage.<BR>• The creature is immune to poison and disease, although a poison or disease already in its system is suspended, not neutralized.",
`chemical-bolt`
],
[
"poisoned",
/(be|and|is|magically|become|becomes) poisoned|poisoned condition/,
"• A poisoned creature has disadvantage on attack rolls and Ability Checks.",
`skull`
],
[
"prone",
/(be|and|is|magically|become|becomes|knocked|fall|falls) prone|prone condition/,
"• A prone creature’s only Movement option is to crawl, unless it stands up and thereby ends the condition.<BR>• The creature has disadvantage on attack rolls. <BR>• An attack roll against the creature has advantage if the attacker is within 5 feet of the creature. Otherwise, the attack roll has disadvantage.",
`back-pain`
],
[
"restrained",
/(be|and|is|magically|become|becomes) restrained|restrained condition/,
"• A restrained creature’s speed becomes 0, and it can’t benefit from any bonus to its speed.<BR>• Attack rolls against the creature have advantage, and the creature’s Attack rolls have disadvantage.<BR>• The creature has disadvantage on Dexterity Saving Throws.",
`cobweb`
],
[
"stunned",
/(be|and|is|magically|become|becomes) stunned|stunned condition/,
"• A stunned creature is <a style='background-color: transparent;padding: 0px;color: #702c91;display: inline-block;border: none;' href='!condef incapacitated'>incapacitated</a>, can’t move, and can speak only falteringly.<BR>• The creature automatically fails Strength and Dexterity Saving Throws.<BR>• Attack rolls against the creature have advantage.",
`broken-skull`
],
[
"unconscious",
/(be|and|is|magically|become|becomes) unconscious|unconscious condition/,
"• An unconscious creature is <a style='background-color: transparent;padding: 0px;color: #702c91;display: inline-block;border: none;' href='!condef incapacitated'>incapacitated</a>, can’t move or speak, and is unaware of its surroundings<BR>• The creature drops whatever it’s holding and falls prone.<BR>• The creature automatically fails Strength and Dexterity Saving Throws.<BR>• Attack rolls against the creature have advantage.<BR>• Any Attack that hits the creature is a critical hit if the attacker is within 5 feet of the creature.",
`death-zone`
]
];
// END SET STATUS CONDITIONS OR TOKEN MARKERS
let buttons = "";
let reportText = "";
function makeButton(conditionName, descriptionText, tokenMarker, tokenID) {
let tmLabel = `<span style="font-family: pictos">L</span>`;
let markerName = ((tokenMarker.includes("::")) ? tokenMarker.replace(/::/, ";;") : tokenMarker);
markerName = ((tokenMarker.includes(";;")) ? tokenMarker.split(";;")[0] : tokenMarker);
if ('undefined' !== typeof libTokenMarkers && undefined !== libTokenMarkers.getStatus(markerName).url) {
let tmImage = ((tokenMarker.includes(";;")) ? libTokenMarkers.getStatus(markerName).url.split(/\?/)[0] : libTokenMarkers.getStatus(markerName).url);
if (undefined !== tmImage && tmImage.length > 0) {
tmLabel = `<img src="${tmImage}"style="margin-top:-1px; height:12px;">`;
}
}
buttons = buttons + `<div style="display:inline-block"><a href="!pcondef ${conditionName}" title="${descriptionText.replace(/<BR>/g, " ").replace(regexForTitles, "incapacitated")}" style=${publicButtonStyle}>${printChar}</a><a href="!condef ${conditionName}" title="${descriptionText.replace(/<BR>/g, " ").replace(regexForTitles, "incapacitated")}" style=${buttonstyle}>${conditionName}</a><a href="!condef-toggle ${tokenMarker} ${((undefined !== tokenID) ? " --ignore-selected --ids " + tokenID : "")}" style=${markerButtonStyle}>${tmLabel}</a></div>`;
}
//Concentration Observer
function concentrationCheck(obj, prev) {
if (obj.get("statusmarkers").includes(condefConcentrationMarker) || obj.get(condefConcentrationMarker)) {
// if (obj.get(condefConcentrationMarker)) {
if (prev["bar1_value"] > obj.get("bar1_value")) {
let final_conc_DC = 10;
let calc_conc_DC = (prev["bar1_value"] - obj.get("bar1_value")) / 2;
if (calc_conc_DC > final_conc_DC) {
final_conc_DC = Math.floor(calc_conc_DC);
}
// let tokenName = obj.get("name");
let theMessage = `! &{template:spell} {{name=Concentration}}{{savedc=${final_conc_DC}}} {{description=Make a DC ${final_conc_DC} Constitution saving throw}}{{Concentration=1}}`;
// let theMessage = `this should print`;
sendChat("gm", theMessage);
log("HERE");
}
}
}
//Event Handlers
on("change:graphic:bar1_value", concentrationCheck);
if (typeof(TokenMod) === 'object') TokenMod.ObserveTokenChange(concentrationCheck);
if ('undefined' !== typeof SmartAoE && SmartAoE.ObserveTokenChange) {
SmartAoE.ObserveTokenChange(function(obj, prev) {
concentrationCheck(obj, prev);
});
}
//Condition report creator. Use !condef for GM only, pcondef for player report. Follow by condition names separated by spaces. !condef-all returns buttons for all conditions, for reference
on('chat:message', function(msg) {
if ('api' === msg.type && msg.content.match(/^!(condef|pcondef|condef-all|condef-report|condef-toggle)/)) {
let args = msg.content.split(" ");
let sender = msg.who;
if (msg.content === '!condef-all') {
let message = `! &{template:noecho} {{description= blinded condition charmed condition deafened condition exhausted condition frightened condition grappled condition incapacitated condition invisible condition paralyzed condition petrified condition poisoned condition prone condition restrained condition stunned condition unconscious condition}}`;
sendChat("gm", message, null, {
noarchive: true
});
message = "";
return;
}
let messagePrefix = ((msg.content.includes('pcondef')) ? '' : '/w ' + sender + ' ');
let theCommand = ((msg.content.includes("pcondef")) ? "!pcondef" : "!condef");
//selected vs target handler
if (msg.content.includes('condef-toggle ') && msg.content.length > 15) {
let tokenMarker = msg.content.split('condef-toggle ')[1];
let message = '';
if (msg.selected) {
//message = `!token-mod --set statusmarkers|!${tokenMarker}`;
let ids = msg.selected.reduce((m,o)=>[...m,o._id],[]);
message = `!token-mod --api-as ${msg.playerid} --set statusmarkers|!${tokenMarker} --ids ${ids.join(' ')}`;
//message = `!token-mod --api-as ${msg.playerid} --set statusmarkers|!${tokenMarker}`;
} else {
message = messagePrefix + buttonboxUnshifted + '<a style = ' + buttonstyle + ' href ="!token-mod --set statusmarkers|!' + tokenMarker + ' --ids &#64;{target|token_id}">No token is selected.<BR>Click here to pick a target.</a></div>';
}
log ("message = " +message);
sendChat("", message, null, {
noarchive: true
});
return;
}
if (msg.content === '!condef-report') {
buttons = "";
let selected = (msg.selected);
if (selected) {
_.each(selected, function(token) {
let tok = getObj("graphic", token._id);
let tokName = tok.get("name") || "unnamed token";
let tokID = tok.get("_id");
let markers = tok.get("statusmarkers").replace(/::/g, ";;");
if (markers) {
for (let i = 0; i < conditionsArray.length; i++) {
let name = conditionsArray[i][0];
let descriptionText = conditionsArray[i][2];
let tokenMarker = conditionsArray[i][3];
if (markers.includes(tokenMarker)) {
makeButton(name, descriptionText, tokenMarker, tokID);
}
}
let message = buttonbox + "<span style= 'font-weight:bold; margin: 0px 2px 0px 3px'>" + tokName + "</span>" + buttons + "</div>";
sendChat("", messagePrefix + message, null, {
noarchive: true
});
buttons = "";
}
});
} else {
let message = buttonbox + "Select one or more tokens with conditions on them" + "</div>";
sendChat("", messagePrefix + message, null, {
noarchive: true
});
}
return;
}
for (let i = 0; i < conditionsArray.length; i++) {
let reportName = conditionsArray[i][0];
let reportDescription = conditionsArray[i][2];
if (undefined !== args[1] && args[1] === reportName) {
reportDescription = reportDescription.replace("!condef", theCommand);
reportText = `<div style=${((messagePrefix === "") ? preportstyle : reportstyle)}><div style=${conditionnamestyle}>${reportName} </div>${reportDescription}</div>`;
}
}
if (undefined !== reportText && reportText.length > 0) {
sendChat("", messagePrefix + reportText, null, {
noarchive: true
});
}
}
//Roll Template Interception
if (undefined !== msg.rolltemplate && msg.rolltemplate.match(/npcfullatk|npcdmg|npcaction|traits|atkdmg|spell|condefinitions|noecho/g)) {
let sender = msg.who;
let messagePrefix = '/w ' + sender + ' ';
let saveAbility = "";
let saveMatches = "";
let theAbility = "";
let saveDC = "";
let conCheck = "";
let concentrationButton = "";
saveMatches = msg.content.match(/DC\s(\d\d)\s(Strength|Dexterity|Constitution|Intelligence|Wisdom|Charisma)\ssaving throw/i);
if (msg.rolltemplate !== "spell" && msg.rolltemplate !== "atkdmg" && null !== saveMatches && saveMatches.length === 3) {
saveDC = saveMatches[1];
saveAbility = saveMatches[2];
}
if (msg.rolltemplate === "spell" || msg.rolltemplate === "atkdmg") {
saveAbility = msg.content.match(/(Strength|Dexterity|Constitution|Intelligence|Wisdom|Charisma)\ssaving throw/i) || "";
if (saveAbility.length > 0) {
saveAbility = saveAbility[1] || "";
}
saveDC = ((msg.rolltemplate === "spell") ? msg.content.match(/{{savedc=(\d+)}}/) : msg.content.match(/{{mod=DC(\d+)}}/)) || "";
conCheck = ((msg.rolltemplate === "spell" && msg.content.match(/{{name=Concentration/)) ? "Concentration Check" : "");
// if (conCheck!==""){
// makeButton("Concentrating", "makes a con check", "concentrating");
// }
// concentrationButton = ((conCheck==="") ? "" : "<BR>" + buttons);
log("concentrationButton = " + concentrationButton);
if (saveDC.length > 0) {
saveDC = saveDC[1] || "";
}
}
if (undefined !== saveAbility && null !== saveAbility) {
theAbility = saveAbility.replace(/Strength/i, "str")
.replace(/Dexterity/i, "dex")
.replace(/Constitution/i, "con")
.replace(/Intelligence/i, "int")
.replace(/Wisdom/i, "wis")
.replace(/Charisma/i, "cha");
}
let saveButton = "";
if (theAbility.match(/(str|dex|con|int|wis|cha)/)) {
//for regular saves
saveButton = `<a href="!&#13;&#37;{target|npc_${theAbility}}" style=${saveButtonstyle}>DC${saveDC} ${theAbility}</a> <span style = "color:#fff">${conCheck}${concentrationButton}</span>`;
//for groupcheck
if (groupCheckIsInstalled) {
saveAbility = saveAbility.charAt(0).toUpperCase() + saveAbility.slice(1);
saveButton = `<a href="!group-check --${saveAbility} Save" style=${groupSaveButtonstyle}>DC${saveDC} ${theAbility}</a> <span style = "color:#fff">${conCheck}${concentrationButton}</span>`;
if (applyDamageIsInstalled && conCheck !== "Concentration Check") {
saveButton = saveButton + `<a href="!group-check --hideformula --public --${saveAbility} Save --process --subheader vs DC ${saveDC} --button ApplyDamage !apply-damage ~dmg &#63;{Damage} ~type &#63;{Damage on Save|Half,half|None,none} ~DC ${saveDC} ~saves RESULTS(,) ~ids IDS(,)" style=${applyDamageButtonstyle}>Dmg</a>`;
}
}
}
for (let i = 0; i < conditionsArray.length; i++) {
let name = conditionsArray[i][0];
//let regex = conditionsArray[i][1];
let descriptionText = conditionsArray[i][2];
let tokenMarker = conditionsArray[i][3];
if (undefined !== name) {
if (msg.content.match(conditionsArray[i][1])) {
makeButton(name, descriptionText, tokenMarker);
}
}
}
let GMSaveButton = saveButton;
if (!playerIsGM(msg.playerid) && msg.playerid !== "API") {
saveButton = "";
}
if (buttons.length > 0 || saveButton.length > 0) {
let message = buttonbox + saveButton + buttons + "</div>";
sendChat("", messagePrefix + message, null, {
noarchive: true
});
if (!messagePrefix.includes(' (GM)') && msg.rolltemplate !== "noecho") {
message = buttonbox + GMSaveButton + buttons + "</div>";
if (conCheck !== "Concentration Check"){
sendChat("", '/w gm ' + message, null, {
noarchive: true
});
}
}
buttons = "";
}
}
});
});
{ try { throw new Error(''); } catch (e) { API_Meta.Condefinition.lineCount = (parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/, '$1'), 10) - API_Meta.Condefinition.offset); } }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment