Skip to content

Commit 5f13549

Browse files
author
Nic Bradley
committed
ChatSetAttr | Additional Fixes
1 parent 054603d commit 5f13549

18 files changed

Lines changed: 2102 additions & 307 deletions

ChatSetAttr/1.11/ChatSetAttr.js

Lines changed: 150 additions & 54 deletions
Large diffs are not rendered by default.

ChatSetAttr/README.md

Lines changed: 457 additions & 61 deletions
Large diffs are not rendered by default.

ChatSetAttr/build/src/classes/APIWrapper.ts

Lines changed: 56 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export class APIWrapper {
9292
_characterid: character?.id,
9393
};
9494
const newAttr = createObj("attribute", newObjProps);
95-
await APIWrapper.setAttribute(character.id, name, value, max);
95+
await APIWrapper.setAttribute(character, name, value, max);
9696
globalSubscribeManager.publish("add", newAttr);
9797
// This is how the previous script was doing it
9898
globalSubscribeManager.publish("change", newAttr, newAttr);
@@ -110,12 +110,18 @@ export class APIWrapper {
110110
const messages: string[] = [];
111111
const attr = await APIWrapper.getAttribute(character, name)
112112
if (!attr) {
113-
errors.push(`updateAttribute: Attribute ${name} does not exist for character with ID ${character?.id}.`);
113+
errors.push(`Attribute <strong>${name}</strong> does not exist for character <strong>${character.get("name")}</strong>.`);
114114
return [{ messages, errors }];
115115
}
116116
const oldAttr = JSON.parse(JSON.stringify(attr));
117-
await APIWrapper.setAttribute(character.id, name, value, max);
118-
messages.push(`Setting <strong>${name}</strong> to <strong>${value}</strong> for character <strong>${character?.get("name")}</strong>.`);
117+
await APIWrapper.setAttribute(character, name, value, max);
118+
if (value && max) {
119+
messages.push(`Setting <strong>${name}</strong> to <strong>${value}</strong> and <strong>${name}_max</strong> to <strong>${max}</strong> for character <strong>${character?.get("name")}</strong>.`);
120+
} else if (value) {
121+
messages.push(`Setting <strong>${name}</strong> to <strong>${value}</strong> for character <strong>${character?.get("name")}</strong>, max remains unchanged.`);
122+
} else if (max) {
123+
messages.push(`Setting <strong>${name}_max</strong> to <strong>${max}</strong> for character <strong>${character?.get("name")}</strong>, current remains unchanged.`);
124+
}
119125
globalSubscribeManager.publish("change", { name, current: value, max }, { name, current: oldAttr.value, max: oldAttr.max });
120126
return [{ messages, errors }];
121127
};
@@ -164,22 +170,22 @@ export class APIWrapper {
164170
};
165171

166172
private static async setAttribute(
167-
characterID: string,
173+
character: Roll20Character,
168174
attr: string,
169175
value?: string,
170176
max?: string
171177
): Promise<void> {
172178
if (state.ChatSetAttr.useWorkers) {
173-
await APIWrapper.setWithWorker(characterID, attr, value, max);
179+
await APIWrapper.setWithWorker(character.id, attr, value, max);
174180
return;
175181
}
176182
if (value) {
177-
const newValue = await setSheetItem(characterID, attr, value, "current");
178-
log(`setAttribute: Setting ${attr} to ${JSON.stringify(newValue)} for character with ID ${characterID}.`);
183+
await setSheetItem(character.id, attr, value, "current");
184+
log(`Setting <strong>${attr}</strong> to <strong>${value}</strong> for character with ID <strong>${character.get("name")}</strong>.`);
179185
}
180186
if (max) {
181-
const newMax = await setSheetItem(characterID, attr, max, "max");
182-
log(`setAttribute: Setting ${attr} max to ${JSON.stringify(newMax)} for character with ID ${characterID}.`);
187+
await setSheetItem(character.id, attr, max, "max");
188+
log(`Setting <strong>${attr}</strong> max to <strong>${max}</strong> for character <strong>${character.get("name")}</strong>.`);
183189
}
184190
};
185191

@@ -193,7 +199,7 @@ export class APIWrapper {
193199
for (const [key, value] of entries) {
194200
const attribute = await APIWrapper.getAttribute(character, key);
195201
if (!attribute?.value) {
196-
errors.push(`setAttribute: ${key} does not exist for character with ID ${character?.id}.`);
202+
errors.push(`Attribute <strong>${key}</strong> does not exist for character <strong>${character.get("name")}</strong>.`);
197203
return [{ messages, errors }];
198204
}
199205
const stringValue = value.value ? value.value.toString() : undefined;
@@ -237,15 +243,46 @@ export class APIWrapper {
237243
const errors: string[] = [];
238244
const messages: string[] = [];
239245
for (const attribute of attributes) {
246+
const { section, repeatingID, attribute: attrName } = APIWrapper.extractRepeatingDetails(attribute) || {};
247+
if (section && repeatingID && !attrName) {
248+
return APIWrapper.deleteRepeatingRow(character, section, repeatingID);
249+
}
240250
const attr = await APIWrapper.getAttr(character, attribute);
241251
if (!attr) {
242-
errors.push(`deleteAttributes: Attribute ${attribute} does not exist for character with ID ${character?.id}.`);
252+
errors.push(`Attribute <strong>${attribute}</strong> does not exist for character <strong>${character.get("name")}</strong>.`);
243253
continue;
244254
}
245255
const oldAttr = JSON.parse(JSON.stringify(attr));
246256
attr.remove();
247257
globalSubscribeManager.publish("destroy", oldAttr);
248-
messages.push(`Attribute ${attribute} deleted for character with ID ${character?.id}.`);
258+
messages.push(`Attribute <strong>${attribute}</strong> deleted for character <strong>${character.get("name")}</strong>.`);
259+
}
260+
return [{ messages, errors }];
261+
};
262+
263+
public static async deleteRepeatingRow(
264+
character: Roll20Character,
265+
section: string,
266+
repeatingID: string
267+
): Promise<[ErrorResponse]> {
268+
const errors: string[] = [];
269+
const messages: string[] = [];
270+
const repeatingAttrs = findObjs<"attribute">({
271+
_type: "attribute",
272+
_characterid: character.id,
273+
}).filter(attr => {
274+
const name = attr.get("name");
275+
return name.startsWith(`repeating_${section}_${repeatingID}_`);
276+
});
277+
if (repeatingAttrs.length === 0) {
278+
errors.push(`No repeating attributes found for section <strong>${section}</strong> and ID <strong>${repeatingID}</strong> for character <strong>${character.get("name")}</strong>.`);
279+
return [{ messages, errors }];
280+
}
281+
for (const attr of repeatingAttrs) {
282+
const oldAttr = JSON.parse(JSON.stringify(attr));
283+
attr.remove();
284+
globalSubscribeManager.publish("destroy", oldAttr);
285+
messages.push(`Repeating attribute <strong>${attr.get("name")}</strong> deleted for character <strong>${character.get("name")}</strong>.`);
249286
}
250287
return [{ messages, errors }];
251288
};
@@ -260,17 +297,17 @@ export class APIWrapper {
260297
const attr = await APIWrapper.getAttribute(character, attribute);
261298
const value = attr?.value;
262299
if (!value) {
263-
errors.push(`resetAttributes: Attribute ${attribute} does not exist for character with ID ${character?.id}.`);
300+
errors.push(`Attribute <strong>${attribute}</strong> does not exist for character <strong>${character.get("name")}</strong>.`);
264301
continue;
265302
}
266303
const max = attr.max;
267304
if (!max) {
268305
continue;
269306
}
270307
const oldAttr = JSON.parse(JSON.stringify(attr));
271-
APIWrapper.setAttribute(character.id, attribute, max);
308+
APIWrapper.setAttribute(character, attribute, max);
272309
globalSubscribeManager.publish("change", attr, oldAttr);
273-
messages.push(`Attribute ${attribute} reset for character with ID ${character?.id}.`);
310+
messages.push(`Attribute <strong>${attribute}</strong> reset for character <strong>${character.get("name")}</strong>.`);
274311
}
275312
return [{ messages, errors }];
276313
};
@@ -281,13 +318,10 @@ export class APIWrapper {
281318
public static extractRepeatingDetails(attributeName: string) {
282319
const [, section, repeatingID, ...attributeParts] = attributeName.split("_");
283320
const attribute = attributeParts.join("_");
284-
if (!section || !repeatingID || !attribute) {
285-
return null;
286-
}
287321
return {
288-
section,
289-
repeatingID,
290-
attribute,
322+
section: section || undefined,
323+
repeatingID: repeatingID || undefined,
324+
attribute: attribute || undefined,
291325
};
292326
};
293327

ChatSetAttr/build/src/classes/AttrProcessor.ts

Lines changed: 51 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,9 @@ export class AttrProcessor {
189189
deltaCurrent = this.replaceMarks(deltaCurrent);
190190
deltaMax = this.replaceMarks(deltaMax);
191191
}
192+
if (hasRepeating) {
193+
deltaName = this.parseRepeating(deltaName);
194+
}
192195
if (this.parse) {
193196
deltaCurrent = this.parseDelta(deltaCurrent);
194197
deltaMax = this.parseDelta(deltaMax);
@@ -202,10 +205,7 @@ export class AttrProcessor {
202205
deltaMax = this.modifyDelta(deltaMax, deltaName, true);
203206
}
204207
if (this.constrain) {
205-
deltaCurrent = this.constrainDelta(deltaCurrent, deltaMax);
206-
}
207-
if (hasRepeating) {
208-
deltaName = this.parseRepeating(deltaName);
208+
deltaCurrent = this.constrainDelta(deltaCurrent, deltaMax, deltaName);
209209
}
210210
final[deltaName] = {
211211
value: deltaCurrent.toString(),
@@ -256,7 +256,6 @@ export class AttrProcessor {
256256
return value;
257257
}
258258
} catch (error) {
259-
this.errors.push(`Error evaluating expression ${value}`);
260259
return value;
261260
}
262261
};
@@ -268,9 +267,11 @@ export class AttrProcessor {
268267
return delta; // Return delta value if attribute not found
269268
}
270269
const current = isMax ? attribute.get("max") : attribute.get("current");
271-
if (delta === undefined || delta === null) {
272-
this.errors.push(`Attribute ${name} has no value to modify.`);
273-
return current; // Return original value if no value is found
270+
if (delta === undefined || delta === null || delta === "") {
271+
if (!isMax) {
272+
this.errors.push(`Attribute ${name} has no value to modify.`);
273+
}
274+
return ""; // Return original value if no value is found
274275
}
275276
const deltaAsNumber = Number(delta);
276277
const currentAsNumber = Number(current);
@@ -282,7 +283,9 @@ export class AttrProcessor {
282283
return modified.toString();
283284
};
284285

285-
private constrainDelta(value: string, max: string): string {
286+
private constrainDelta(value: string, maxDelta: string, name: string): string {
287+
const attribute = this.attributes.find(attr => attr.get("name") === name);
288+
const max = maxDelta ? maxDelta : attribute?.get("max");
286289
const valueAsNumber = Number(value);
287290
const maxAsNumber = max === "" ? Infinity : Number(max);
288291
if (isNaN(valueAsNumber) || isNaN(maxAsNumber)) {
@@ -303,7 +306,7 @@ export class AttrProcessor {
303306

304307
private parseRepeating(name: string): string {
305308
const { section, repeatingID, attribute } = APIWrapper.extractRepeatingDetails(name) ?? {};
306-
if (!section || !attribute) {
309+
if (!section) {
307310
this.errors.push(`Invalid repeating attribute name: ${name}`);
308311
return name; // Return original name if invalid
309312
}
@@ -315,7 +318,9 @@ export class AttrProcessor {
315318
if (matches) {
316319
const index = Number(matches[0].slice(1));
317320
const repeatingID = this.repeating.repeatingOrders[section]?.[index];
318-
if (repeatingID) {
321+
if (repeatingID && !attribute) {
322+
return `repeating_${section}_${repeatingID}`;
323+
} else if (repeatingID) {
319324
return `repeating_${section}_${repeatingID}_${attribute}`;
320325
}
321326
}
@@ -329,24 +334,49 @@ export class AttrProcessor {
329334
const entries = Object.entries(this.delta);
330335
const finalEntries = Object.entries(this.final);
331336
const newMessage = message
332-
.replace(/_NAME(\d+)_/g, (_, index) => {
333-
const attr = entries[parseInt(index, 10)];
337+
.replace(/_NAME(\d+)_/g, (match, index) => {
338+
if (index > entries.length - 1) {
339+
this.errors.push(`Invalid index ${index} in _NAME${index}_ placeholder.`);
340+
return match; // Return original match if index is invalid
341+
}
342+
const actualIndex = parseInt(index, 10);
343+
const attr = entries[actualIndex];
334344
return attr[0] ?? "";
335345
})
336-
.replace(/_TCUR(\d+)_/g, (_, index) => {
337-
const attr = entries[parseInt(index, 10)];
346+
.replace(/_TCUR(\d+)_/g, (match, index) => {
347+
if (index > entries.length - 1) {
348+
this.errors.push(`Invalid index ${index} in _NAME${index}_ placeholder.`);
349+
return match; // Return original match if index is invalid
350+
}
351+
const actualIndex = parseInt(index, 10);
352+
const attr = entries[actualIndex];
338353
return attr[1].value ?? "";
339354
})
340-
.replace(/_TMAX(\d+)_/g, (_, index) => {
341-
const attr = entries[parseInt(index, 10)];
355+
.replace(/_TMAX(\d+)_/g, (match, index) => {
356+
if (index > entries.length - 1) {
357+
this.errors.push(`Invalid index ${index} in _NAME${index}_ placeholder.`);
358+
return match; // Return original match if index is invalid
359+
}
360+
const actualIndex = parseInt(index, 10);
361+
const attr = entries[actualIndex];
342362
return attr[1].max ?? "";
343363
})
344-
.replace(/_CUR(\d+)_/g, (_, index) => {
345-
const attr = finalEntries[parseInt(index, 10)];
364+
.replace(/_CUR(\d+)_/g, (match, index) => {
365+
if (index > entries.length - 1) {
366+
this.errors.push(`Invalid index ${index} in _NAME${index}_ placeholder.`);
367+
return match; // Return original match if index is invalid
368+
}
369+
const actualIndex = parseInt(index, 10);
370+
const attr = finalEntries[actualIndex];
346371
return attr[1].value ?? "";
347372
})
348-
.replace(/_MAX(\d+)_/g, (_, index) => {
349-
const attr = finalEntries[parseInt(index, 10)];
373+
.replace(/_MAX(\d+)_/g, (match, index) => {
374+
if (index > entries.length - 1) {
375+
this.errors.push(`Invalid index ${index} in _NAME${index}_ placeholder.`);
376+
return match; // Return original match if index is invalid
377+
}
378+
const actualIndex = parseInt(index, 10);
379+
const attr = finalEntries[actualIndex];
350380
return attr[1].max ?? "";
351381
})
352382
.replace(/_CHARNAME_/g, this.character.get("name") ?? "");

ChatSetAttr/build/src/classes/ChatOutput.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,13 @@ export class ChatOutput {
6969
output += this.createHeader();
7070
output += this.createContent();
7171
output += this.closeWrapper();
72-
log(`ChatOutput: ${output}`);
72+
log(`Output: ${output}`);
7373
sendChat(this.from, output, undefined, { noarchive });
7474
};
7575

7676
private createWhisper() {
77-
if (!this.whisper) {
77+
if (this.whisper === false) {
78+
log(`ChatOutput: Not sending as whisper`);
7879
return ``;
7980
}
8081
if (this.playerID === "GM") {

ChatSetAttr/build/src/classes/ChatSetAttr.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export class ChatSetAttr {
3939
command,
4040
flags,
4141
attributes
42-
} = this.InputParser.parse(msg.content);
42+
} = this.InputParser.parse(msg);
4343

4444
// #region Command
4545
if (commandType === CommandType.NONE || !command) {
@@ -57,6 +57,11 @@ export class ChatSetAttr {
5757

5858
// #region Targets
5959
const targets = this.getTargets(msg, flags);
60+
if (targets.length === 0 && actualCommand !== Commands.SET_ATTR_CONFIG) {
61+
this.errors.push(`No valid targets found for command ${actualCommand}.`);
62+
this.sendMessages();
63+
return;
64+
}
6065

6166
// #region Act
6267
TimerManager.start("chatsetattr", 8000, this.sendDelayMessage);
@@ -73,6 +78,7 @@ export class ChatSetAttr {
7378
}
7479
const isMuted = flags.some(flag => flag.name === Flags.MUTE);
7580
if (isMuted) {
81+
this.messages = [];
7682
this.errors = [];
7783
}
7884
if (response.errors.length > 0 || response.messages.length > 0) {
@@ -123,11 +129,14 @@ export class ChatSetAttr {
123129

124130
private getTargets(msg: Roll20ChatMessage, flags: Option[]): Roll20Character[] {
125131
const target = this.targetFromOptions(flags);
132+
log(`[ChatSetAttr] Target strategy: ${target}`);
126133
if (!target) {
127134
return [];
128135
}
129136
const targetStrategy = this.getTargetStrategy(target);
137+
log(`[ChatSetAttr] Target message: ${msg.selected}`);
130138
const targets = this.getTargetsFromOptions(target, flags, msg.selected);
139+
log(`[ChatSetAttr] Targets: ${targets.join(", ")}`);
131140
const [validTargets, { messages, errors }] = targetStrategy.parse(targets, msg.playerid);
132141
this.messages.push(...messages);
133142
this.errors.push(...errors);
@@ -208,7 +217,7 @@ export class ChatSetAttr {
208217
private sendMessages(feedback?: Feedback | null) {
209218
const sendErrors = this.errors.length > 0;
210219
const from = feedback?.sender || "ChatSetAttr";
211-
const whisper = feedback?.whisper || true;
220+
const whisper = feedback?.whisper ?? true;
212221
if (sendErrors) {
213222
const header = "ChatSetAttr Error";
214223
const content = this.errors.map(error => error.startsWith("<") ? error : `<p>${error}</p>`).join("");
@@ -217,12 +226,11 @@ export class ChatSetAttr {
217226
content,
218227
from,
219228
type: "error",
220-
whisper: true
229+
whisper,
221230
});
222231
error.send();
223232
this.errors = [];
224233
this.messages = [];
225-
return;
226234
}
227235
const sendMessage = this.messages.length > 0 || feedback;
228236
if (!sendMessage) {
@@ -259,7 +267,7 @@ export class ChatSetAttr {
259267
const publicFlag = flags.find(flag => flag.name === Flags.FB_PUBLIC);
260268
const header = headerFlag?.value;
261269
const sender = fromFlag?.value;
262-
const whisper = publicFlag?.value === "false" ? false : true;
270+
const whisper = publicFlag === undefined;
263271
return {
264272
header,
265273
sender,

0 commit comments

Comments
 (0)