Skip to content

Commit 5135661

Browse files
authored
Merge pull request #1986 from birdbrainiac/hunters-mark
Hunters-Mark
2 parents 55e1c45 + 103f1ed commit 5135661

File tree

4 files changed

+412
-68
lines changed

4 files changed

+412
-68
lines changed

HuntersMark/0.4.0/huntersmark.js

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
/*
2+
!hunters-mark
3+
!hunters-mark add
4+
!hunters-mark delete
5+
!hunters-mark help
6+
!hunters-mark show
7+
!hunters-mark menu
8+
!hunters-mark @{selected|character_id} @{target|token_id}
9+
10+
add adds selected character as a new hunter.
11+
must have exactly one marker
12+
delete removes currently selected character from hunters list
13+
help shows help menu, and description of each feature
14+
show shows current hunters in state
15+
menu shows the menu of buttons
16+
17+
if none of above
18+
assumes arg[1] is a hunter character_id, and arg[2] is target token id.
19+
if not, will send a warning and end script.
20+
21+
*/
22+
const HUNTERSMARK = (() => { // eslint-disable-line no-unused-vars
23+
24+
const script_name = 'HUNTERSMARK';
25+
const version = '0.3.0';
26+
const lastUpdate = 1593500895369;
27+
28+
const tokenName = token => token.get('name') ? token.get('name') : (token.get('_id') ? token.get('_id') : 'Unknown');
29+
const findHunter = (hunter, hunted = 'hunter') => state.HUNTERSMARK.hunters.findIndex(item => item[hunted] === hunter);
30+
const getWho = who => who.split(' (GM)')[0];
31+
const mark = '@{selected|character_id} @{target|token_id}';
32+
const CSS = {
33+
container: 'border: 1pt solid green; background-color: white; font-size: 0.9em; border-radius: 10px;',
34+
table: '<table style="border:0;">',
35+
trow: '<tr style="border-bottom: 1px solid #ddd; border-top: 1px solid #ddd;"><td style="font-weight:bold; padding-left: 5px; padding-right: 5px;">',
36+
tmiddle: '</td><td>' ,
37+
trowend: '</td></tr>',
38+
tend: '</table>',
39+
button: 'border:0; margin-left: 3px; margin-right: 3px; padding-left: 5px; padding-right: 5px; border-radius: 7px; background:green;color:white;font-weight:bold;',
40+
center: 'text-align: center;',
41+
leftpad: 'padding-left: 10px;',
42+
heading: 'text-align: center; text-decoration: underline; font-size: 16px; line-height: 24px;'
43+
};
44+
45+
const checkState = () => {
46+
if(!state.hasOwnProperty('HUNTERSMARK')) {
47+
state.HUNTERSMARK = {
48+
schema: 0.0,
49+
hunters: []
50+
};
51+
}
52+
/* hunter: {
53+
hunter: id of character,
54+
mark: tag of status marker to assign,
55+
marked: id of last character marked, or ''
56+
}
57+
*/
58+
};
59+
60+
const checkInstall = () => {
61+
log(`-=> ${script_name.toUpperCase()} v${version} <=- [${new Date(lastUpdate)}]`);
62+
// include state checking here
63+
checkState();
64+
65+
};
66+
67+
const handleInput = (msg) => {
68+
if (msg.type !== 'api' || !/!hunters-mark\b/.test(msg.content.toLowerCase())) {
69+
return;
70+
}
71+
72+
const args = msg.content.split(/\s/);
73+
const command = args[1] || '';
74+
75+
if(!command || command.toLowerCase() === 'help') {
76+
showHelp(getWho(msg.who));
77+
} else if(command.toLowerCase() === 'add') {
78+
hunter(msg, 1);
79+
} else if(command.toLowerCase() === 'delete') {
80+
hunter(msg, -1);
81+
} else if(command.toLowerCase() === 'show') {
82+
showState(getWho(msg.who));
83+
} else if(command.toLowerCase() === 'menu') {
84+
showMenu(getWho(msg.who));
85+
/*} else if(command.toLowerCase() === 'mark') {
86+
sendChat('player|' + getWho(msg.who),`!hunters-mark ${mark}`);*/
87+
} else {
88+
if(msg.selected > 1) {
89+
sendChat(script_name,`/w "${getWho(msg.who)}" You must have only one token selected.`);
90+
return;
91+
}
92+
tokenMarker(args[1], args[2], getWho(msg.who));
93+
}
94+
};
95+
96+
const showHelp = who => {
97+
const help = {
98+
show: 'This shows the current list of hunters, and their marks.',
99+
add: 'To add a new hunter, select a token representing the character and apply the status marker you want to use as their mark. Then click Add.',
100+
'delete': 'To remove a character from the list of hunters, select a token representing them and click Delete.',
101+
help: 'Show this description.',
102+
menu: "Show a set of buttons to activate the script's features.",
103+
'mark a target': `<p>To mark a target, use <code>!hunters-mark [character id of hunter] [token id of target]. </code></p><p>A good way to do this is <code>!hunters-mark ${mark}</code></p>`
104+
};
105+
let output = `<div style="${CSS.container}"><h3 style="${CSS.heading}">Hunter's Mark Instructions</h3><p3>Use <code>!hunters-mark</code> followed by one of the commands below.</p>${CSS.table}`;
106+
Object.entries(help).forEach(([key, value]) => {
107+
output += `${CSS.trow}${key}${CSS.tmiddle}${value}${CSS.trowend}`;
108+
});
109+
output += CSS.tend + '</div>';
110+
sendChat(script_name, `/w "${who}" ${output}`);
111+
showMenu(who);
112+
};
113+
114+
const showMenu = (who) => {
115+
const buttons = {
116+
Show: 'show',
117+
Add: 'add',
118+
'Delete': 'delete',
119+
Help: 'help'
120+
121+
};
122+
const output = `<div style="${CSS.container}"><h3 style="${CSS.heading}">Hunters Mark Menu</h3>` +
123+
`<p style="${CSS.center}">${makeButton('Mark / Unmark Target', mark, 'width: 192px; font-size: 1.1em; text-align:center;')}</p>`
124+
+ `<p style="${CSS.center}">${Object.entries(buttons).reduce((list, [key, value]) => list + makeButton(key, value), '')}</p></div>`;
125+
sendChat(script_name, `/w "${who}" ${output}`);
126+
};
127+
128+
const makeButton = (label, button, width='') => {
129+
return `<a style="${CSS.button}${width}" href="!hunters-mark ${button}">${label}</a>`;
130+
};
131+
132+
const showState = (who) => {
133+
const tokenMarkers = JSON.parse(Campaign().get('token_markers'));
134+
const getIcon = tag => tokenMarkers.find(item => tag === item.tag).url;
135+
const hunters = state.HUNTERSMARK.hunters.map(hunter => `<tr><td style="${CSS.leftpad}"><img src="${getIcon(hunter.mark)}"></td><td style="${CSS.leftpad}"><p>**${getObj('character', hunter.hunter).get('name')}**${hunter.marked ? ` </p><p>Marked: ${getObj('graphic',hunter.marked).get('name')}` : ''}</p></td></tr>`);
136+
sendChat(script_name, `/w "${who}" <div style="${CSS.container}"><h3 style="${CSS.heading}">Hunter Details</h3><table>${hunters.join('')}</table> </div>`);
137+
};
138+
139+
const hunter = (msg, addordelete) => {
140+
if (!msg.selected) {
141+
sendChat(script_name,`/w "${getWho(msg.who)}" You need to select at least one character's token, and each must have a single status marker assigned.`);
142+
return;
143+
}
144+
let showstate = false;
145+
let excluded = [];
146+
(msg.selected||[]).forEach((obj) => {
147+
let token = getObj('graphic', obj._id);
148+
if (token) {
149+
let character = getObj('character', token.get('represents'));
150+
if (character) {
151+
if(addordelete === -1) {
152+
// delete selected characters from state
153+
const found = findHunter(character.get('_id'));
154+
if(found === -1) {
155+
excluded.push(tokenName(token));
156+
} else {
157+
state.HUNTERSMARK.hunters.splice(found, 1);
158+
}
159+
} else if (addordelete === 1) {
160+
// only need to check marker if adding.
161+
const marker = token.get('statusmarkers').split(',');
162+
if(marker.length === 0 || marker.length > 1 || marker[0] === '') {
163+
excluded.push(tokenName(token));
164+
} else {
165+
const newHunter = {
166+
hunter: character.get('_id'),
167+
marked: '',
168+
mark: marker[0]
169+
};
170+
const found = findHunter(newHunter.hunter);
171+
if(found === -1) {
172+
state.HUNTERSMARK.hunters.push(newHunter);
173+
} else {
174+
state.HUNTERSMARK.hunters.splice(found, 1, newHunter);
175+
}
176+
}
177+
}
178+
showstate = true;
179+
// report characters in state.
180+
} else {
181+
excluded.push(tokenName(token));
182+
}
183+
}
184+
185+
});
186+
187+
if(showstate) {
188+
showState(getWho(msg.who));
189+
}
190+
if(excluded.length > 0) {
191+
sendChat(script_name, `/w "${getWho(msg.who)}" The following tokens were either missing elements or had too many markers, and were not updated: ${excluded.join(', ')}.`);
192+
}
193+
};
194+
195+
const tokenMarker = (hunter_id, target_id, who) => {
196+
const hunter_index = findHunter(hunter_id);
197+
if(hunter_index === -1) {
198+
sendChat(script_name, `/w "${who}" Hunter is not found. Check they are set up properly.`);
199+
return;
200+
}
201+
const token = getObj('graphic', target_id);
202+
if(!token) {
203+
sendChat(script_name, `/w "${who}" Target token is not a valid target.`);
204+
return;
205+
}
206+
/* here starts the actual work of the script */
207+
if(target_id == state.HUNTERSMARK.hunters[hunter_index].marked) {
208+
// the target token matches the id stored in owner.
209+
// This character is already marked, so unmark him and clear mark_id
210+
state.HUNTERSMARK.hunters[hunter_index].marked = '';
211+
changeMarker(target_id, state.HUNTERSMARK.hunters[hunter_index].mark, 'remove');
212+
} else {
213+
// marking a new target so:
214+
// get old mark, and remove mark from previous character
215+
// update mark_id and add marker
216+
const oldmark = state.HUNTERSMARK.hunters[hunter_index].marked;
217+
if(oldmark !== '') {
218+
// find old character, remove mark from them, then:
219+
changeMarker(oldmark, state.HUNTERSMARK.hunters[hunter_index].mark, 'remove');
220+
}
221+
state.HUNTERSMARK.hunters[hunter_index].marked = target_id;
222+
changeMarker(target_id, state.HUNTERSMARK.hunters[hunter_index].mark, 'add');
223+
224+
}
225+
};
226+
227+
const changeMarker = (tid, marker, addorremove = 'add') => {
228+
const token = getObj('graphic', tid);
229+
if(token) {
230+
let tokenMarkers = token.get('statusmarkers').split(',');
231+
if(addorremove === 'add') {
232+
if(!tokenMarkers.includes(marker)) {
233+
tokenMarkers.push(marker);
234+
}
235+
} else if(addorremove === 'remove') {
236+
tokenMarkers = tokenMarkers.filter(item => item !== marker);
237+
} else {
238+
return;
239+
}
240+
token.set('statusmarkers', tokenMarkers.join(','));
241+
}
242+
};
243+
244+
const registerEventHandlers = () => {
245+
on('chat:message', handleInput);
246+
};
247+
248+
on('ready', () => {
249+
checkInstall();
250+
registerEventHandlers();
251+
});
252+
253+
})();

HuntersMark/README.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,28 @@
11
Hunter's Mark
22
=============
33

4-
A script that lets each character have their own custom Status Marker, which they can use to mark other tokens. You can make only one target at a time. When you mark a new target, the old marker is removed. This is perfect for abilities like D&D's Hunter's Mark.
4+
A script that lets each character have their own custom Status Marker, which they can use to mark other tokens. There are two settings. If you are a hunter-like character, you can mark only one target at a time. When you mark a new target, the old marker is removed frm all tokens but yourself and the target. This is perfect for abilities like D&D's Hunter's Mark.
5+
The second setting lets you mark any number of characters.
56

6-
Run it with !hunters-mark followed by one of the commands below.
7+
Run this script with `!hunters-mark ` followed by one of the commands below.
78

89
Available Commands
910
==================
1011

11-
* `help`: Shows a help file like this one.
12-
* `add`: Select a character, and make sure they have one status marker applied. Then click Add, and that character will be added to the list of hunters, and the marker will be the one they use to mark targets.
13-
* `delete`: Delete a character from the list of hunters.
14-
* `show`: Show a list of hunters, their markers, and their current marked target, if any.
12+
* `help`: Shows a detailed help file, and the menu buttons afterwards.
13+
* `add`: Select a character who has exactly one token marker assigned. Then click Add or Hunter, and that character will be added to the list of hunter-like characters, and the marker will be the one they use to mark targets.
14+
* `bard`: exactly as above. Select a character with one status mark assigned. But they rae added to rhe list of bard-like characters, and can assign marks to multiple targets simultaneously. Maybe all characters will be added here, depending on how you use marks.
15+
* `delete`: Delete a character from all displayed lists.
16+
* `show`: Show a list of characters with their markers, and the menu buttons afterwards.
1517
* `menu`: prints a set of buttons, to activate the scripts commands.
1618

1719
Marking or Unmarking a Target
1820
=============================
1921

20-
* `!hunters-mark @{selected|character_id} @{target|token_id}`: To mark or unmark a target, you need to supply your character id, and the token id of a target. The same command is used to mark or unmark a target.
22+
* `!hunters-mark @{selected|token_id} @{target|token_id}`: To mark or unmark a target, you need to supply your own token id, and the token id of a target. The same command is used to mark or unmark a target.
23+
Important: this has changed with version 0.4 to support bard-like characters. Previous versions used to use `@{selected|character_id}` - but you MUST now use `@{selected|token_id}`. Update any macros (this script's buttons are automatically updated).
2124

25+
Hunter and Bard
26+
===============
27+
The two types of behaviour are classified as Hunter and Bard, because most people will be familiar with D&D. If you can think of alternate terms for these, send then to Gigs on the roll20 forums.
2228

0 commit comments

Comments
 (0)