/* */var API_Meta = API_Meta || {};
var API_Meta = API_Meta || {};
API_Meta.Rest = { offset: Number.MAX_SAFE_INTEGER, lineCount: -1 };
{ try { throw new Error(''); } catch (e) { API_Meta.Rest.offset = (parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/, '$1'), 10) - (4)); } }
const Rest = (() => {
const scriptName = 'Rest';
API_Meta[scriptName].version = '1.0.0';
let rest = {
isOpen: false,
startingValues: {}, // object tracking food, water, and hp for each character involved
involved: [], // array of character IDs who can choose to eat/recover
claimed: [], // array of character IDs of those who have elected to eat/recover
open() {
this.close();
this.isOpen = true;
},
close() {
this.isOpen = false;
let report = this.involved.filter(cid => !this.claimed.includes(cid)).reduce((m,cid) => {
(hp = findObjs({ type: 'attribute', characterid: cid, name: 'hp' })[0])?.set('current', Math.max(0, parseIntOrZero(hp.get('current')) - 2)); // Apply penalty
return [...m, cid];
},[]);
this.startingValues = {};
this.involved = [];
this.claimed = [];
if (report.length) {
sendChat(scriptName, `/w gm The following characters suffered a healing penalty due to lack of sustenance: ${report.map(cid => getObj('character', cid).get('name')).join(', ')}`);
}
}
};
const getPlayerCharacters = () => findObjs({ type: 'character' })
.filter(c => (pid = (c.get('controlledby') || '').split(/\s*,\s*/)[0]).length && pid.toLowerCase() !== 'all' && !playerIsGM(pid));
const getCharacter = (q, pid) => {
if (typeof q !== 'string') { return; }
let someCheck = id => id.toLowerCase() === 'all' || id === pid;
const hasAccess = (c) => {
if (!c) { return; }
return (c.get('controlledby') || '').split(/\s*,\s*/).some(someCheck);
};
let query = q.trim();
let character = findObjs({ type: 'character' }).filter(c => [c.id, c.get('name')].includes(query) && (pid ? hasAccess(c) : true))[0];
if (character) {
return character;
}
character = getObj('character', (getObj('graphic', query) || { get: () => { return ''; } }).get('represents'));
if (character) {
if (!pid || (pid && hasAccess(character))) {
return character;
} else { return; }
}
};
const gmRequired = pid => sendchat(scriptName, 'You must be a GM to do that.');
const parseIntOrZero = (val) => isNaN(tempPI = parseInt(val,10)) ? 0 : tempPI;
const getWhisperTo = (who) => who.toLowerCase() === 'api' ? 'gm' : who.replace(/\s\(gm\)$/i, '');
on('ready', function () {
on('chat:message', msg => {
/**
* !rest
* !rest --[open/close]
* !rest --recover|characterid
* !rest --rollback|characterid
* !rest --request|characterid
* !rest --share|[water/food]|characterid-give|characterid-receive
*/
if (!(msg.type === 'api' && /^!rest/i.test(msg.content))) { return; }
let args = (msg.content.split(/\s+--/)).slice(1).map(a => [(ret = (/([^|#]+)(?:(?:\||#)(.*))?/.exec(a)))[1].toLowerCase(), ret[2] ? ret[2].trim() : undefined]);
let chars = getPlayerCharacters();
args.forEach(arg => {
let food, water, hp, shareAttr, recAttr, char, shareChar;
switch (arg[0]) {
case 'close':
if (!playerIsGM(msg.playerid)) {
gmRequired(msg.playerid);
return;
}
if (!rest.isOpen) { return; }
rest.close();
sendChat(scriptName, '/w gm Rest period is closed. No further recoveries will be allowed.');
sendChat(scriptName, `&{template:default}{{name=Rest Over}}{{=This opportunity for rest and recovery is now closed. You may still share ` +
`rations using existing <b>Request</b> and <b>Share</b> buttons, but there will be no opportunity for recovery until you rest again.}}`);
break;
case 'recover':
char = chars.filter(c => [c.id, c.get('name')].includes(arg[1]))[0];
if (!char) { // no character supplied, or character not found
sendChat(scriptName, `/w ${getWhisperTo(msg.who)} &{template:default}{{name=No Character}}` +
`{{=You must supply a character who is involved in this rest.}}`);
return;
}
if (!rest.involved.includes(arg[1])) { // character not involved in rest
sendChat(scriptName, `/w ${getWhisperTo(msg.who)} &{template:default}{{name=Character Not Resting}}` +
`{{=You must supply a character who is involved in this rest.}}`);
return;
}
if (rest.claimed.includes(arg[1])) { // recovery already claimed
sendChat(scriptName, `/w ${getWhisperTo(msg.who)} &{template:default}{{name=${char.get('name')} Already Recovered}}` +
`{{=This character already recovered during this rest.}}`);
return;
}
if (!rest.isOpen) { // rest was closed by GM
sendChat(scriptName, `/w ${char.get('name')} &{template:default} {{name=No Recovery Available}}` +
`{{=The opportunity to recover during this rest has passed.}}`);
return;
}
food = findObjs({ type: 'attribute', characterid: char.id, name: 'food' })[0];
water = findObjs({ type: 'attribute', characterid: char.id, name: 'water' })[0];
hp = findObjs({ type: 'attribute', characterid: char.id, name: 'hp' })[0];
if (parseIntOrZero(food.get('current')) < 1 || parseIntOrZero(water.get('current')) < 1) {
sendChat(scriptName, `/w ${char.get('name')} &{template:default}{{name=Not Enough Rations}}` +
`{{=You lack the necessary rations to recover. Most likely you shared with a party memeber.}}`);
return;
}
let currentHP = hp?.get('current');
let adjustedHP = Math.min(parseIntOrZero(hp?.get('max')), parseIntOrZero(hp?.get('current')) + 5); // Adjust healing value as needed
hp?.set('current', adjustedHP);
food?.set('current', parseIntOrZero(food.get('current')) - 1);
water?.set('current', parseIntOrZero(water.get('current')) - 1);
rest.claimed.push(char.id);
sendChat(scriptName, `/w ${char.get('name')} &{template:default}{{name=Recovered}} {{=You rested and recovered ${adjustedHP - currentHP}hp ` +
`to be at ${adjustedHP}hp. ${`${adjustedHP}` === `${hp?.get('max')}` ? 'You are at full health.' : ''} }}`);
break;
case 'request':
char = getCharacter(arg[1], msg.playerid);
if (!char) { return; }
let needFood = parseIntOrZero(findObjs({ type: 'attribute', characterid: char.id, name: 'food' })[0]?.get('current')) < 1;
let needWater = parseIntOrZero(findObjs({ type: 'attribute', characterid: char.id, name: 'water' })[0]?.get('current')) < 1;
(rest.isOpen ? rest.involved : chars.map(c => c.id)).filter(cid => cid !== arg[1]).forEach(cid => {
shareChar = getObj('character', cid);
food = parseIntOrZero(findObjs({ type: 'attribute', characterid: shareChar.id, name: 'food' })[0]?.get('current'));
water = parseIntOrZero(findObjs({ type: 'attribute', characterid: shareChar.id, name: 'water' })[0]?.get('current'));
hp = `${(tempAttr = findObjs({ type: 'attribute', characterid: shareChar.id, name: 'hp' })[0]).get('current')} / ${tempAttr.get('max')}`;
let foodRequest = needFood ? `{{ =<div style="text-align:right"><a href="!&#13;!rest --share|food|${shareChar.id}|${char.id}">Share Food</a></div>}}` : '';
let waterRequest = needWater ? `{{ =<div style="text-align:right"><a href="!&#13;!rest --share|water|${shareChar.id}|${char.id}">Share Water</a></div>}}` : '';
if ((food < 1 && water < 1) || (needFood && !needWater && food < 1) || (needWater && !needFood && water < 1)) { return; }
sendChat(scriptName, `/w ${shareChar.get('name')} &{template:default}{{name=Sharing Request}}{{=${char.get('name')} lacks ` +
`adequate rations to recover and is asking for help. Will you share?%NEWLINE%%NEWLINE%**YOUR STATUS:**<ul>` +
`<li><b>Food</b>: ${food}</li>` +
`<li><b>Water</b>: ${water}</li>` +
`<li><b>HP</b>: ${hp}</li>` +
`</ul>}}` +
`${foodRequest}${waterRequest}`);
});
sendChat(scriptName, `/w ${char.get('name')} &{template:default}{{name=Request Made}}{{=Your request has been made.}}`);
break;
case 'share':
let parts = arg[1].split(/(?:\||#)/);
let shareType = ['food', 'water'].includes(parts[0].toLowerCase()) ? parts[0].toLowerCase() : undefined;
shareChar = getCharacter(parts[1], msg.playerid);
char = getCharacter(parts[2]);
if (!shareType) {
sendChat(scriptName, `/w ${getWhisperTo(msg.who)} &{template:default}{{name=Invalid Share Type}}{{=You must include either ` +
`'food' or 'water' as the thing to share.}}`);
return;
} else if (!shareChar || !char) {
sendChat(scriptName, `/w ${getWhisperTo(msg.who)} &{template:default}{{name=Can't Find Characters}}{{=Could not find one or both ` +
`of the characters required based on the information you supplied.}}`);
return;
}
// if we're still here, then this character has chosen to share
// sharing character reduction
shareAttr = findObjs({ type: 'attribute', characterid: shareChar.id, name: shareType })[0];
if (shareAttr && parseIntOrZero(shareAttr.get('current')) >= 1) {
shareAttr.set('current', parseIntOrZero(shareAttr.get('current')) - 1);
if (rest.isOpen) {
rest.startingValues[shareChar.id][shareType] = rest.startingValues[shareChar.id][shareType] - 1;
}
// receiving character adjustment
recAttr = findObjs({ type: 'attribute', characterid: char.id, name: shareType })[0];
if (recAttr) {
recAttr.set('current', parseIntOrZero(recAttr.get('current')) + 1);
if (rest.isOpen) {
rest.startingValues[char.id][shareType] = rest.startingValues[char.id][shareType] + 1;
}
}
// report to sharing character
food = rest.isOpen
? rest.startingValues[shareChar.id].food
: parseIntOrZero(findObjs({ type: 'attribute', characterid: shareChar.id, name: 'food' })[0].get('current'))
;
water = rest.isOpen
? rest.startingValues[shareChar.id].water
: parseIntOrZero(findObjs({ type: 'attribute', characterid: shareChar.id, name: 'water' })[0].get('current'))
;
hp = `${(tempAttr = findObjs({ type: 'attribute', characterid: shareChar.id, name: 'hp' })[0]).get('current')} / ${tempAttr.get('max')}`;
sendChat(scriptName, `/w ${shareChar.get('name')} &{template:default}{{name=Success}}{{=You shared with ${char.get('name')}. ` +
`Don't ever let them forget it.%NEWLINE%%NEWLINE%**STATUS:**<ul>` +
`<li><b>Food</b>: ${food}</li>` +
`<li><b>Water</b>: ${water}</li>` +
`<li><b>HP</b>: ${hp}</li>` +
`</ul>}}`);
// report to receiving character
food = rest.isOpen
? rest.startingValues[char.id].food
: parseIntOrZero(findObjs({ type: 'attribute', characterid: char.id, name: 'food' })[0].get('current'))
;
water = rest.isOpen
? rest.startingValues[char.id].water
: parseIntOrZero(findObjs({ type: 'attribute', characterid: char.id, name: 'water' })[0].get('current'))
;
hp = `${(tempAttr = findObjs({ type: 'attribute', characterid: char.id, name: 'hp' })[0]).get('current')} / ${tempAttr.get('max')}`;
let lackingRation = food >= 1 && water >= 1
? ''
: food < 1
? 'food'
: 'water'
;
let status = `%NEWLINE%%NEWLINE%**STATUS:**<ul>` +
`<li><b>Food</b>: ${food}</li>` +
`<li><b>Water</b>: ${water}</li>` +
`<li><b>HP</b>: ${hp}</li>` +
`</ul>`;
let nextAction = rest.isOpen
? lackingRation.length
? `You still lack ${lackingRation} rations before you can recover.${status}}}`
: `You may now recover.${status}}}{{ =<div style="text-align:right"><a href="!&#13;!rest --recover|${char.id}">Recover</a></div>}}`
: lackingRation.length
? `This rest period is closed, but you still lack a ${lackingRation} before you can recover in the next rest period.${status}}}`
: `This rest period is closed, but you now have sufficient rations to recover the next time you are resting.${status}}}`
;
sendChat(scriptName, `/w ${char.get('name')} &{template:default}{{name=You Have Received a Gift}}{{=${shareChar.get('name')} ` +
`shared ${shareType} with you. ${nextAction}`);
} else {
food = parseIntOrZero(findObjs({ type: 'attribute', characterid: shareChar.id, name: 'food' })[0].get('current'));
water = parseIntOrZero(findObjs({ type: 'attribute', characterid: shareChar.id, name: 'water' })[0].get('current'));
hp = `${(tempAttr = findObjs({ type: 'attribute', characterid: shareChar.id, name: 'hp' })[0]).get('current')} / ${tempAttr.get('max')}`;
sendChat(scriptName, `/w ${getWhisperTo(msg.who)} &{template:default}{{name=Unable to Share}}{{=You no longer have enough ${shareType} ` +
`to share. It is possible you recovered and consumed your last ration, or that you already shared it.` +
`%NEWLINE%%NEWLINE%**STATUS:**<ul>` +
`<li><b>Food</b>: ${food}</li>` +
`<li><b>Water</b>: ${water}</li>` +
`<li><b>HP</b>: ${hp}</li>` +
`</ul>` +
`}}`);
return;
}
break;
case 'rollback':
if (!playerIsGM(msg.playerid)) {
gmRequired(msg.playerid);
return;
}
char = getCharacter(arg[1]);
if (rest.startingValues.hasOwnProperty(char.id)) {
findObjs({ type: 'attribute', characterid: char.id, name: 'food' })[0]?.set('current', rest.startingValues[char.id].food);
findObjs({ type: 'attribute', characterid: char.id, name: 'water' })[0]?.set('current', rest.startingValues[char.id].water);
findObjs({ type: 'attribute', characterid: char.id, name: 'hp' })[0]?.set('current', rest.startingValues[char.id].hp);
rest.claimed = rest.claimed.filter(cid => cid !== char.id);
sendChat(scriptName, `/w gm Successfully rolled back that character. Their **[Recovery]** button should work again.`);
}
break;
case 'open':
if (!playerIsGM(msg.playerid)) {
gmRequired(msg.playerid);
return;
}
rest.open();
if (!chars.length) {
sendChat(scriptName, '/w gm No player characters found.');
return;
}
rest = {
...rest,
...chars.reduce((m, c) => {
let foodAttr = findObjs({ type: 'attribute', characterid: c.id, name: 'food' })[0];
let waterAttr = findObjs({ type: 'attribute', characterid: c.id, name: 'water' })[0];
let hpAttr = findObjs({ type: 'attribute', characterid: c.id, name: 'hp' })[0];
if (!hpAttr || !waterAttr || !foodAttr) { // character is missing attributes
sendChat(scriptName, `/w gm ${c.get('name')} does not have all three attributes (hp, food, water) required ` +
`to take part in the Rest script. They will be excluded until you correct that situation, then open the ` +
`rest period again.`);
} else {
food = parseIntOrZero(foodAttr.get('current'));
water = parseIntOrZero(waterAttr.get('current'));
hp = parseIntOrZero(hpAttr.get('current'));
m.involved.push(c.id);
m.startingValues = {
...m.startingValues,
[c.id]: { hp, food, water }
};
}
return m;
}, rest)
};
let menuParts = chars.map(c => {
food = rest.startingValues[c.id].food;
water = rest.startingValues[c.id].water;
hp = `${(tempAttr = findObjs({ type: 'attribute', characterid: c.id, name: 'hp' })[0]).get('current')} / ${tempAttr.get('max')}`;
if (rest.involved.includes(c.id)) {
if (food < 1 || water < 1) {
sendChat(scriptName, `/w ${c.get('name')} &{template:default} {{name=${c.get('name')} No Recovery Opportunity}}` +
`{{=You are resting, but you lack the necessary food and water in order to recover HP and/or ` +
`avoid taking damage from a lack of sustenance. Do you wish to request help?%NEWLINE%%NEWLINE%**STATUS:**<ul>` +
`<li><b>Food</b>: ${food}</li>` +
`<li><b>Water</b>: ${water}</li>` +
`<li><b>HP</b>: ${hp}</li>` +
`</ul>}}` +
`{{ =<div style="text-align:right"><a href="!&#13;!rest --request|${c.id}">Request</a></div>}}`);
} else {
sendChat(scriptName, `/w ${c.get('name')} &{template:default} {{name=${c.get('name')} Recovery Opportunity}}` +
`{{=You are resting. Do you wish to consume food and water rations in order to gain HP?%NEWLINE%%NEWLINE%**STATUS:**<ul>` +
`<li><b>Food</b>: ${food}</li>` +
`<li><b>Water</b>: ${water}</li>` +
`<li><b>HP</b>: ${hp}</li>` +
`</ul>}}` +
`{{ =<div style="text-align:right"><a href="!&#13;!rest --recover|${c.id}">Recover</a></div>}}`);
}
return `{{${c.get('name')}%NEWLINE%` +
`F: ${food}, W: ${water}, HP: ${hp}=` +
`<a href="!&#13;!rest --rollback|${c.id}">Rollback</a>}}`;
}
});
sendChat(scriptName, `/w gm &{template:default}{{name=Rest Opened}}{{Rest period has opened, and the characters have received their messages. ` +
`Use this menu to rollback any character who requires it, or to close the rest period.}} ${menuParts.join('')} ` +
`{{Close Rest Period=<a href="!&#13;!rest --close">Close</a>}}`);
default:
}
});
});
});
})();
{ try { throw new Error(''); } catch (e) { API_Meta.Rest.lineCount = (parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/, '$1'), 10) - API_Meta.Rest.offset); } }
/* */ .