Skip to content

Commit a63a72d

Browse files
v1.2: fixed crit heal to roll 4d8 instead of (2d8)*2 (as written in rulebook). created new help function to assist in documentation/config options later
1 parent 0410d95 commit a63a72d

File tree

3 files changed

+394
-30
lines changed

3 files changed

+394
-30
lines changed

TreatWounds/1.2/treatwounds.js

+354
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,354 @@
1+
/*
2+
PF2E Treat Wounds Check
3+
4+
Version 1.2
5+
Author: Mark Stoecker
6+
Roll20: https://app.roll20.net/users/580967/mark-s
7+
BitBucket: https://bitbucket.org/desertwebdesigns/roll20/src/master/TreatWounds/
8+
9+
*/
10+
// Future versions:
11+
// make adjustments for medic archetype bonuses
12+
// expand help() function to send documentation and config info
13+
// Send error message back to user who called the function on failure, not just the GM
14+
// rename args array in handleInput for ease of use
15+
// Allow for generic tokens that don't represent a character (requires sending healing modifier with api call)
16+
// Check for Risky Surgery Feat before prompting
17+
// Check for Proficiency and build option list from there
18+
// Above two options require sending API Buttons back to user when calling API. User will call script and API will send back a button with the appropriate user prompts depending on character sheet (ie, don't prompt for Risky Surgery if user does not have it in feats, don't allow Master/Legendary difficulty if user is only Expert, etc)
19+
20+
var RLRGaming = RLRGaming || {};
21+
22+
RLRGaming.TreatWounds = RLRGaming.TreatWounds || (() => {
23+
'use strict';
24+
25+
const version = "1.2";
26+
27+
const getChar = (tokenorname) => {
28+
var character = getCharByToken(tokenorname);
29+
30+
switch (character) {
31+
case undefined:
32+
return false;
33+
break;
34+
case false:
35+
character = getCharByName(tokenorname);
36+
if (!character) {
37+
return false;
38+
}
39+
break;
40+
}
41+
42+
return character;
43+
};
44+
45+
const getCharByName = (charname) => {
46+
var chars = findObjs({type: 'character', name: charname});
47+
if (chars.length == 1)
48+
return chars[0];
49+
else
50+
return false;
51+
};
52+
53+
const getCharByToken = (tokenid) => {
54+
var token = getObj("graphic", tokenid);
55+
if (token === undefined)
56+
return false;
57+
else
58+
return getObj("character", token.get('represents'));
59+
};
60+
61+
const hasCharacterControl = (playerid, character) => {
62+
if (!character) {
63+
log("A valid character name/token was not supplied");
64+
return false;
65+
}
66+
67+
var charactercontrol = character.get('controlledby').split(",");
68+
69+
if (
70+
_.contains(charactercontrol,playerid) ||
71+
_.contains(charactercontrol,'all')
72+
)
73+
return true;
74+
else
75+
return false;
76+
};
77+
78+
const performSurgery = async (character, target) => {
79+
sendChat(character, "/em performs some risky surgery on " + target + " and deals [[1d8]] points of damage");
80+
return;
81+
};
82+
83+
const splitTargets = (targetList) => {
84+
var targetStr = '';
85+
switch(true) {
86+
case targetList.length == 1:
87+
targetStr = targetList[0];
88+
break;
89+
case targetList.length == 2:
90+
targetStr = targetList[0] + " and " + targetList[1];
91+
break;
92+
case targetList.length >= 3:
93+
_.each(targetList, (t,k) => {
94+
if (k == targetList.length - 1) {
95+
targetStr += "and " + t.trim();
96+
} else {
97+
targetStr += t.trim() + ", ";
98+
}
99+
});
100+
break;
101+
}
102+
return targetStr;
103+
};
104+
105+
106+
const performHeal = async (character, surgery, DC, player, target) => {
107+
if (surgery == "1") {
108+
var surgeryMod = 2;
109+
var riskySurgery = " + 2[Risky Surgery]";
110+
} else {
111+
var surgeryMod = 0;
112+
var riskySurgery = '';
113+
};
114+
115+
var medCheck = await new Promise((resolve,reject) => {
116+
sendChat(character, "/roll 1d20 + @{" + character + "|medicine} + " + surgeryMod, (ops) => {
117+
resolve(JSON.parse(ops[0].content));
118+
});
119+
});
120+
121+
var dieresult = medCheck.rolls[0].results[0].v;
122+
var medTotal = medCheck.total;
123+
var medResult = '';
124+
125+
var checkDC = DC.replace( /^\D+/g, '');
126+
switch (medTotal >= Number(checkDC)) {
127+
case true:
128+
if (
129+
(
130+
medTotal >= Number(checkDC) + 10 &&
131+
dieresult != 1
132+
) ||
133+
dieresult == 20
134+
) {
135+
medResult = 'cs';
136+
} else if (
137+
dieresult == 1 &&
138+
medTotal < Number(checkDC) + 10
139+
) {
140+
medResult = 'f';
141+
} else {
142+
medResult = 's';
143+
}
144+
break;
145+
case false:
146+
if (
147+
(
148+
medTotal <= Number(checkDC) - 10 &&
149+
dieresult != 20
150+
) ||
151+
dieresult == 1
152+
) {
153+
medResult = 'cf';
154+
} else if (
155+
dieresult == 20 &&
156+
medTotal > Number(checkDC) - 10
157+
) {
158+
medResult = 's';
159+
} else {
160+
medResult = 'f';
161+
}
162+
break;
163+
}
164+
165+
var critmsg = '';
166+
switch (medResult) {
167+
case 'cs':
168+
critmsg = "<br>Critical Success!";
169+
break;
170+
case 'cf':
171+
critmsg = "<br>Critical Failure!";
172+
break;
173+
case 's':
174+
if (surgery == "1") {
175+
medResult = 'cs';
176+
critmsg = "<br>Critical Success<br>due to Risky Surgery!";
177+
}
178+
break;
179+
}
180+
181+
if (medTotal >= checkDC) {
182+
switch (Number(checkDC)) {
183+
case 20:
184+
var healmod = 10;
185+
break;
186+
case 30:
187+
var healmod = 20;
188+
break;
189+
case 40:
190+
var healmod = 30;
191+
break;
192+
default:
193+
var healmod = 0;
194+
break;
195+
};
196+
var heal = true;
197+
} else {
198+
var heal = false;
199+
}
200+
201+
var healmsg = "&{template:rolls} {{charactername=" + character + "}} {{header=Treat Wounds Check}} {{subheader=Skill}} {{roll01=[[(" + dieresult + ") " + riskySurgery + " + [@{" + character + "|medicine_proficiency_display}] (@{" + character + "|medicine})[@{" + character + "|text_modifier}] + (@{" + character + "|query_roll_bonus})[@{" + character + "|text_bonus}]]]}} {{roll01_type=skill}}";
202+
203+
204+
if (medResult == 'cs' || medResult == 'cf') {
205+
healmsg += "{{roll01_info=" + critmsg + "}} ";
206+
}
207+
208+
if (heal) {
209+
var healRollString = '';
210+
211+
var healdice = (medResult == 'cs') ? '4d8' :'2d8';
212+
213+
var healResult = await new Promise((resolve,reject) => {
214+
sendChat(character, "/roll " + healdice + " + " + healmod, (ops) => {
215+
resolve(JSON.parse(ops[0].content));
216+
});
217+
});
218+
219+
_.each(healResult.rolls[0].results, (die) => {
220+
healRollString += "(" + die.v + ")+";
221+
});
222+
223+
healRollString += healmod;
224+
225+
healmsg += "{{roll02=[[" + healRollString + "]]}} {{roll02_type=heal}} {{roll02_info=HP Healed to " + target + "}} {{roll02_misc=hp healed}} ";
226+
}
227+
228+
healmsg += "{{roll01misc=" + DC + "}} {{notes_show=[[" + ((state.RLRGaming.TreatWounds.config.showNotes === true) ? 1 : 0) + "]]}} {{notes=@{" + character + "|medicine_notes}}}";
229+
230+
sendChat("player|" + player, healmsg);
231+
232+
if (medResult == 'cf') {
233+
sendChat(character, "/em causes [[1d8]] points of damage to " + target + " due to their carelessness");
234+
}
235+
};
236+
237+
const handleInput = async (msg) => {
238+
/* args = [
239+
0 => !treatwounds,
240+
1 => selected token or character name,
241+
2 => 1|0 (perform risky rurgery),
242+
3 => DC of check,
243+
4+ => Player(s) to heal
244+
]
245+
*/
246+
if(msg.type == "api") {
247+
var args = msg.content.split(",");
248+
249+
switch(args[0].toLowerCase()) {
250+
case '!treatwounds':
251+
switch(true) {
252+
case args.length >= 5:
253+
if (hasCharacterControl(msg.playerid, getChar(args[1]))) {
254+
var charname = getChar(args[1]).get("name");
255+
if (args[2] == 1)
256+
performSurgery(charname, splitTargets(args.slice(4,)));
257+
performHeal(charname, args[2], args[3], msg.playerid, splitTargets(args.slice(4,)));
258+
}
259+
break;
260+
case args.length == 3:
261+
if (args[1] == "config") {
262+
updateConfig(args[2]);
263+
}
264+
break;
265+
case args.length == 2:
266+
help();
267+
break;
268+
default:
269+
sendChat("GM", "/w GM Incorrect number of parameters sent to '!treatwounds'");
270+
return;
271+
break;
272+
}
273+
break;
274+
}
275+
}
276+
};
277+
278+
const help = () => {
279+
sendChat('','/w GM '+
280+
'<div style="border: 1px solid black; background-color: white; padding: 3px 3px;">' +
281+
getConfigOption_ShowNotes() +
282+
'</div>'
283+
);
284+
}
285+
286+
const updateConfig = (arg) => {
287+
switch (arg) {
288+
case 'shownotes':
289+
state.RLRGaming.TreatWounds.config.showNotes = !state.RLRGaming.TreatWounds.config.showNotes
290+
sendChat('','/w GM '+
291+
'<div style="border: 1px solid black; background-color: white; padding: 3px 3px;">'+
292+
getConfigOption_ShowNotes()+
293+
'</div>'
294+
);
295+
break;
296+
}
297+
}
298+
299+
const checkInstall = () => {
300+
if (!state.RLRGaming ||
301+
!state.RLRGaming.TreatWounds ||
302+
!state.RLRGaming.TreatWounds.version ||
303+
state.RLRGaming.TreatWounds.version !== version) {
304+
state.RLRGaming = state.RLRGaming || {};
305+
state.RLRGaming.TreatWounds = {
306+
version: version,
307+
gcUpdated: 0,
308+
config: {}
309+
};
310+
}
311+
checkGlobalConfig();
312+
}
313+
314+
const getConfigOption_ShowNotes = () => {
315+
var text = (state.RLRGaming.TreatWounds.config.showNotes ?
316+
'<span style="color: #007700; font-weight:bold; padding: 0px 4px;">ON</span><br>' :
317+
'<span style="color: #FF0000; font-weight:bold; padding: 0px 4px;">OFF</span><br>'
318+
);
319+
return '<div>'+
320+
'Show Notes in Rolls is currently '+
321+
text+
322+
'<a href="!treatwounds,config,shownotes">'+
323+
'Toggle'+
324+
'</a>'+
325+
'</div>';
326+
327+
};
328+
329+
const checkGlobalConfig = () => {
330+
var gc = globalconfig && globalconfig.treatwounds,
331+
st = state.RLRGaming.TreatWounds;
332+
333+
if (gc && gc.lastsaved && gc.lastsaved > st.gcUpdated) {
334+
st.gcUpdated = gc.lastsaved;
335+
st.config.showNotes = 'showMedNotes' === gc['Show notes in rolls'];
336+
}
337+
}
338+
339+
const registerEventHandlers = async () => {
340+
on('chat:message', handleInput);
341+
};
342+
343+
return {
344+
CheckInstall: checkInstall,
345+
RegisterEventHandlers: registerEventHandlers
346+
};
347+
})();
348+
349+
350+
on('ready', async () => {
351+
'use strict';
352+
RLRGaming.TreatWounds.CheckInstall();
353+
RLRGaming.TreatWounds.RegisterEventHandlers();
354+
});

TreatWounds/script.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"name": "Pathfinder 2 Treat Wounds",
33
"script": "treatwounds.js",
4-
"version": "1.1",
5-
"previousversions": ["1.0"],
4+
"version": "1.2",
5+
"previousversions": ["1.0","1.1"],
66
"description": "Treat Wounds is designed for Pathfinder 2nd Edition and aids in performing a Medicine check to treat wounds allowing for the user to pass in the specific DC of the check they want to attempt as well as opt to attempt Risky Surgery (per the feat). As with the PF2 ruleset, total healing is doubled on a critical success and will deal damage to the selected target on a critical failure. The script also accounts for the critical success/failure system with regards to beating/missing the DC by 10 or more and modifying results on a nat 1 or 20.",
77
"authors": "Mark S.",
88
"roll20userid": "580967",

0 commit comments

Comments
 (0)