Non-gm users updating combat documents?

Hio. Working on an implementation of the Doomsong system, in which actors pre-plan their actions at the top of a combat round. My question is as follows: What is the intended method of allowing users to have ownership of combatants for tokens they have ownership of? I am attempting to store planned actions on the combatant itself (it seemed convenient) within the .system, and though this works great for the GM, I discovered to my dismay during my first playtest that players do not own their own combatants. Updating initiative works but my system does not use initiative. permission is returning 3, this appears to be rejected in the backend. Is there a workaround for this? Or a solution, rather. I'm well aware that the easiest workaround would just be to jam this data back into the actor's system data but isolating just felt so elegant at the time.
12 Replies
Bolts
Bolts2mo ago
Combatant ownership should match the ownership of the associated actor (if any), but non-GMs have limited properties they can update. Players are able to update the system, flags, and initiative properties of combatants that they own I just checked on HELLPIERCERS and I had no problem updating a combatant system propery there on a player account.
WHITESPINE
WHITESPINEOP2mo ago
You're totally right. I don't know why it wasn't working earlier ah, wait I see what I've done haha
WHITESPINE
WHITESPINEOP2mo ago
I brought over a legacy piece of code from when I was developing ICON which was designed to minimize arrays randomly getting obliterated
No description
WHITESPINE
WHITESPINEOP2mo ago
But i think it was polluting the update with junk fields that couldn't be used
Bolts
Bolts2mo ago
I think it's probably best to just accept that arrays have to be full updated every time or to switch to TypedObjectField rather than putting an abstraction layer over this stuff.
WHITESPINE
WHITESPINEOP2mo ago
Yeah. good to know about typed object field though! Thanks! it's been so long I forget how to trigger the bot thanks @Bolts
Leo The League Lion
@WHITESPINE gave :vote: LeaguePoints™ to @Bolts (#40 • 78)
WHITESPINE
WHITESPINEOP2mo ago
Could you elaborate slightly on this? I'm sure this is a more or less solved problem, but I haven't actually interfaced with it in such a long time that I don't recall how we did it in lancer. To narrow down the exact problem space: I have a field moves defined as follows:
const MoveField = () => new fields.SchemaField({
name: new fields.StringField({ nullable: false, initial: "New Move"}),
text: new fields.StringField({ nullable: false, initial: "Do something"}),
// TODO: more attributes, such as check type, bonuses, etc
});

const MovesList = () => new fields.ArrayField(MoveField(), {nullable: false});

export class ActorModel extends DoomsongDataModel {
// Some schema elements are consistent across all actor types. Define them here
static defineSchema() {
return {
...
moves: new fields.SchemaField({
1: MovesList(),
2: MovesList(),
3: MovesList(),
4: MovesList(),
5: MovesList(),
6: MovesList(),
}),

// Descriptions etc
traits: new fields.ArrayField(new fields.StringField({ nullable: false }))
};
}
...
}
const MoveField = () => new fields.SchemaField({
name: new fields.StringField({ nullable: false, initial: "New Move"}),
text: new fields.StringField({ nullable: false, initial: "Do something"}),
// TODO: more attributes, such as check type, bonuses, etc
});

const MovesList = () => new fields.ArrayField(MoveField(), {nullable: false});

export class ActorModel extends DoomsongDataModel {
// Some schema elements are consistent across all actor types. Define them here
static defineSchema() {
return {
...
moves: new fields.SchemaField({
1: MovesList(),
2: MovesList(),
3: MovesList(),
4: MovesList(),
5: MovesList(),
6: MovesList(),
}),

// Descriptions etc
traits: new fields.ArrayField(new fields.StringField({ nullable: false }))
};
}
...
}
The problem I was facing is that if I do
_token.actor.update({
"system.moves.1.0.name": "test"
})
_token.actor.update({
"system.moves.1.0.name": "test"
})
Then system.moves.1.0.text returns to the default value. And vice versa, if I set text, name resets. Though I know the short term solution is just to do an
_token.actor.update({
"system.moves.1.0": {
name: "test",
text: _token.actor.system.moves[1][0].text
}
})
_token.actor.update({
"system.moves.1.0": {
name: "test",
text: _token.actor.system.moves[1][0].text
}
})
This is, obviously, kinda a pain in the ass and hard to generalize for E.x. custom svelte components that manipulate this sort of thing Is there an elegant general solution?
WHITESPINE
WHITESPINEOP2mo ago
the typedobjectfield documention leaves a bit to be desired. Is it via subclassing and using _applyChangeCustom to safely wrap the data into a full object?
No description
Bolts
Bolts2mo ago
I don't know how well foundry is going to handle numeric object keys since IIRC expandObject will expand thos to (sparse?) arrays. As for TypeObjectField, you pass another data field as the first element of the constructor and then it uses that data field for the values. I think there's a way to validate the keys as well, but you'd probably get better answers in the system dev channel of the main foundry discord. I'm not super experienced with the ins and outs of TOF. As for updating arrays, I'm not displaying editable fields for them directly on sheets. I'm making buttons that openout an editor app for the element, and then taking the returned updated element, copy the array, mutate the copy to have the new element, and then update the array property. Example:
/**
* @param {Event} _ev
* @param {HTMLElement} target
*/
static async #toggleUpgrade(_ev, target) {
if (!this.isEditable) return;
const index = Number.parseInt(target.closest("[data-index]")?.dataset.index);
const upgrades = this.item.system.toObject().upgrades;
upgrades[index].unlocked = !upgrades[index].unlocked;
await this.item.update({ "system.upgrades": upgrades });
}
/**
* @param {Event} _ev
* @param {HTMLElement} target
*/
static async #toggleUpgrade(_ev, target) {
if (!this.isEditable) return;
const index = Number.parseInt(target.closest("[data-index]")?.dataset.index);
const upgrades = this.item.system.toObject().upgrades;
upgrades[index].unlocked = !upgrades[index].unlocked;
await this.item.update({ "system.upgrades": upgrades });
}
WHITESPINE
WHITESPINEOP2mo ago
I suppose this will work in the short term but I'm not happy about it :/ thanks!
Leo The League Lion
@WHITESPINE gave :vote: LeaguePoints™ to @Bolts (#38 • 79)

Did you find this page helpful?