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
+ } ) ;
0 commit comments