-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathFoundry_VTT_Sneak_Attack.js
183 lines (151 loc) · 7.81 KB
/
Foundry_VTT_Sneak_Attack.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// DISCLAIMER: This macro is heavily based on the original D&D 5e Rage Macro masterwork written by Felix#6196.
// Norc#5108 created and is maintaining this macro.
//
// Updates: 1. 2020/06/05: Initial version.
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//!!! Bonus Tip: Sneak Attack as a Condition
//!!! If you use the Combat Utility Belt module's Condition Lab, try adding a condition called "Sneaky" with the same icon
//!!! as the optional sneak attack icon overlay, 'icons/svg/mystery-man-black.svg' by default. See EXPERIMENTAL MACRO ICON/NAME TOGGLE below.
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//!!! OPTIONAL TOKEN ICON- On by default. If a path to a sneak attack icon is defined, it displays like a condition on the sneaking rogue.
//!!! To use a different icon, manually change the filepath below or leave it empty ('') to disable the effect.
//!!!
const sneakIconPath = 'icons/svg/mystery-man-black.svg';
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//!!! EXPERIMENTAL MACRO ICON/NAME TOGGLE If enabled, the macro icon and name toggles based on whether the rogue is currently sneaking.
//!!! CAUTIONS: 1. This feature is off by default and is intended for ADVANCED USERS ONLY.
//!!! 2. Requires configuration using "The Furnace" module for a player to run!
//!!! The GM needs to grant The Furnace's "Run as GM" permission for this macro.
//!!! 3. Works best with only one rogue using this feature at a time.
//To auto-toggle the macro's icon/name, override toggleMacro to true below.
const toggleMacro = false;
//To use a different icon, manually change the filepath here
const stopSneakIconPath = 'icons/svg/cowled.svg';
//You must update the following constant to this macro's exact name for the macro icon toggling to work.
const sneakMacroName = 'Sneak Attack';
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
let toggleResult = false;
let enabled = false;
let errorReason = '';
let sneakAttack = {};
let rogue = {};
let rogueLvls = 0;
let sneakDice = 0;
let chatMsg = '';
let oldMDmg = '';
let oldRDmg = '';
let macroActor = actor;
let macroToken = token;
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//!!! BASIC LOCALIZATION SUPPORT Sets names of D&D5E features as constants instead of hardcoding to allow easier translation.
//!!! Sets error messages as constants also for easier translation.
const rogueClassName = 'Rogue';
const sneakAttackFeatureName = 'Sneak Attack';
const errorSelectRogue = 'Please select a single rogue token.';
const warnMacroNotFound = ' is not a valid macro name, please fix. Sneak attack toggle successful but unable to alter macro.';
const errorSelectToken = 'Please select a token.';
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//check to ensure token is selected and attempt to define the sneak attack feature
if (macroActor !== null && macroActor !== undefined) {
sneakAttack = macroActor.items.find(i => i.name == `${sneakAttackFeatureName}`);
} else {
errorReason = `${errorSelectToken}`;
}
//check to ensure token is a rogue
if (errorReason == '' && macroActor.items.find(i => i.name == `${rogueClassName}`) !== undefined) {
rogue = macroActor.items.find(i => i.name == `${rogueClassName}`);
} else {
errorReason = `${errorSelectRogue}`;
}
console.log(`Error reason is: ${errorReason}`);
//main execution now that errors are caught
if (errorReason == '') {
chatMsg = '';
let enabled = false;
// store the state of the sneak attack toggle in flags
if (macroActor.data.flags.sneakMacro !== null && macroActor.data.flags.sneakMacro !== undefined) {
enabled = true;
}
// if sneak attack is active, disable it
if (enabled) {
chatMsg = `${macroActor.name} is no longer sneak attacking.`;
// ranged and melee weapon attack bonus
let obj = {};
obj['flags.sneakMacro'] = null;
obj['data.bonuses.mwak.damage'] = macroActor.data.flags.sneakMacro.oldMDmg;
obj['data.bonuses.rwak.damage'] = macroActor.data.flags.sneakMacro.oldRDmg;
macroActor.update(obj);
// if sneak attack is disabled, enable it
} else {
chatMsg = `${macroActor.name} starts sneak attacking!`;
let obj = {};
obj['flags.sneakMacro.enabled'] = true;
// Preserve old mwak damage bonus if there was one
let oldMDmg = macroActor.data.data.bonuses.mwak.damage;
if (oldMDmg==null || oldMDmg == undefined || oldMDmg == '') oldMDmg = 0;
obj['flags.sneakMacro.oldMDmg'] = JSON.parse(JSON.stringify(oldMDmg));
// Preserve old rwak damage bonus if there was one
let oldRDmg = macroActor.data.data.bonuses.rwak.damage;
if (oldRDmg==null || oldRDmg == undefined || oldRDmg == '') oldRDmg = 0;
obj['flags.sneakMacro.oldRDmg'] = JSON.parse(JSON.stringify(oldRDmg));
// Determining the rogue level
rogueLvls = rogue.data.data.levels;
// Formula to determine the sneak attack damage depending on rogue level
sneakDice = Math.ceil(rogueLvls/2);
//actually add the bonus sneak attack damage to the previous bonus damage
//respect roll formulas if present.
if (oldMDmg==null || oldMDmg == undefined || oldMDmg == '' || oldMDmg == 0) {
obj['data.bonuses.mwak.damage'] = `${sneakDice}d6`;
} else {
obj['data.bonuses.mwak.damage'] = `${oldMDmg} + ${sneakDice}d6`;
}
if (oldRDmg==null || oldRDmg == undefined || oldRDmg == '' || oldRDmg == 0) {
obj['data.bonuses.rwak.damage'] = `${sneakDice}d6`;
} else {
obj['data.bonuses.rwak.damage'] = `${oldRDmg} + ${sneakDice}d6`;
}
macroActor.update(obj);
}
//mark or unmark character's token with Sneaky effect icon, if sneakIconPath is defined
(async () => {
toggleResult = await macroToken.toggleEffect(sneakIconPath);
if (toggleResult == enabled) macroToken.toggleEffect(sneakIconPath);
})();
//toggle macro icon and name, if enabled
if (toggleMacro) {
// Norc's preferred icons, not sure if publicly available
// sneakyMacroImgPath = 'systems/dnd5e/icons/skills/shadow_17.jpg';
// stopSneakIconPath = 'systems/dnd5e/icons/skills/yellow_11.jpg';
let sneakMacro = game.macros.getName(sneakMacroName);
//Also check for name of macro in its "off" form
if (sneakMacro == null || sneakMacro == undefined) {
sneakMacro = game.macros.getName('Stop ' + sneakMacroName);
}
let obj = {};
if ( (sneakMacro !== null && sneakMacro !== undefined) &&
+ (stopSneakIconPath !== null && stopSneakIconPath !== undefined && stopSneakIconPath !== '') ) {
if (enabled) {
obj['img'] = sneakIconPath;
obj['name'] = sneakMacroName;
} else {
obj['img'] = stopSneakIconPath;
obj['name'] = 'Stop ' + sneakMacroName;
}
sneakMacro.update(obj);
} else {
ui.notifications.warn(`${sneakMacroName} ${warnMacroNotFound}`);
}
}
} else {
ui.notifications.error(`${errorReason}`);
}
if (chatMsg !== '') {
let chatData = {
user: game.user._id,
speaker: ChatMessage.getSpeaker(),
content: chatMsg
};
ChatMessage.create(chatData, {});
}