Trouble with Synthetic Actors (unlinked tokens)

When I create a token from an actor and try to update something like its hp, I get the following error: "actors is not a valid embedded Document within the Token Document". If I link the token to the actor, everything works as expected. Though, being linked, it updates all tokens. This would be problematic If I want to create one "goblin" actor, and have several different goblins in a scene.
Is there something different you need to do to handle updates when the token is unlinked?
18 Replies
Forien
Forien•11mo ago
it's tokenDocument.actor, not tokenDocument.actors. And you update it just as usual, tokenDocument.actor.update(updateData), you do not need to know or care if it's linked or unlinked, Foundry does that part.
Eclipxs
EclipxsOP•11mo ago
I don't have any tokenDocument.actor or .actors calls. The only thing I see that would be executing in this path is: this.actor.receiveDamage(...) from a sheet, which then calls: this.update({ "system.lifePoints.value": remainingLifePoints }); from the actor class oh and there is a second path that comes from a button in a chat message. It grabs the current users target and then calls the receiveDamage on the targeted actor. I tried changing that path to go through tokenDocument.actor, but got the same error.
Forien
Forien•11mo ago
Seeing actual error that points to specific file and related code would definitely help
Eclipxs
EclipxsOP•11mo ago
one sec
Eclipxs
EclipxsOP•11mo ago
chat handler:
function getTarget(actor = {}) {
const targets = game.users.current.targets;
let target = actor;
for (const t of targets) {
target = t.actor;
}
return target;
}

function onRenderChatMessage(app, html, data) {
html.find(".apply-outcome")
.click(async e => {
//todo apply outcomes: Bonus damage from good rolls?
const currentCharacter = game.users?.current?.character;
const target = getTarget(currentCharacter);
const message = data.message;
if (target && message?.flags?.rsk?.actionType) {
const dialog = RSKApplyDamageDialog.create(foundry.utils.deepClone(message.flags.rsk));
const result = await dialog();
if (!(result && result.confirmed)) return;
target.receiveDamage({ ...result });
}
});
}
function getTarget(actor = {}) {
const targets = game.users.current.targets;
let target = actor;
for (const t of targets) {
target = t.actor;
}
return target;
}

function onRenderChatMessage(app, html, data) {
html.find(".apply-outcome")
.click(async e => {
//todo apply outcomes: Bonus damage from good rolls?
const currentCharacter = game.users?.current?.character;
const target = getTarget(currentCharacter);
const message = data.message;
if (target && message?.flags?.rsk?.actionType) {
const dialog = RSKApplyDamageDialog.create(foundry.utils.deepClone(message.flags.rsk));
const result = await dialog();
if (!(result && result.confirmed)) return;
target.receiveDamage({ ...result });
}
});
}
actor method:
async receiveDamage(damage) {
const { puncture, damageEntries, attackType, defenseRoll } = { ...damage };
const damageTaken = this.calculateDamageTaken(damageEntries, attackType, puncture, defenseRoll);
const remainingLifePoints = game.rsk.math.clamp_value(
this.system.lifePoints.value - damageTaken,
{ min: 0 });
if (remainingLifePoints < 1 && !this.statuses.has("dead")) {
const death = rskStatusEffects.find(x => x.id === "dead");
await this.createEmbeddedDocuments("ActiveEffect", [statusToEffect(death)]);
}
this.update({ "system.lifePoints.value": remainingLifePoints });
}
async receiveDamage(damage) {
const { puncture, damageEntries, attackType, defenseRoll } = { ...damage };
const damageTaken = this.calculateDamageTaken(damageEntries, attackType, puncture, defenseRoll);
const remainingLifePoints = game.rsk.math.clamp_value(
this.system.lifePoints.value - damageTaken,
{ min: 0 });
if (remainingLifePoints < 1 && !this.statuses.has("dead")) {
const death = rskStatusEffects.find(x => x.id === "dead");
await this.createEmbeddedDocuments("ActiveEffect", [statusToEffect(death)]);
}
this.update({ "system.lifePoints.value": remainingLifePoints });
}
the actual errors are in the screenshots. They are almost the same, but slightly different. the one in common.js:8099 was from clicking my chat button. and the one in foundry.js:6629 is from editing the sheet from the unlinked actor directly.
No description
Eclipxs
EclipxsOP•11mo ago
accidentally attached same image twice, here is the other one
No description
Forien
Forien•11mo ago
It's hard to tell... errors definitely say that something tries to update embedded document called "actors"... maybe some _onUpdate() or _preUpdate() or some hook? I never encountered this before...
Eclipxs
EclipxsOP•11mo ago
I was able to get rid of the error by removing my update call. But I do need to deal damage to my npc :/. I'll go look at my on update and pre updates and see if something weird is in there nope I don't use preUpdate or onUpdate anywhere what do you do when you want to be able to update an unlinked actor? or has it just worked for you is what you are saying?
Forien
Forien•11mo ago
unlinked actor is still an Actor Document, so I just go actor.update() or token.actor?.update(). There really is no difference on higher level between linked and unlinked tokens and in most cases you should not need to care about distinction 🤔
Eclipxs
EclipxsOP•11mo ago
well that is what I was hoping to hear... but somehow it makes me more sad haha
Forien
Forien•11mo ago
what Foundry version are you using?
Eclipxs
EclipxsOP•11mo ago
v11 build 315
Forien
Forien•11mo ago
and socket.js is your system file?
Eclipxs
EclipxsOP•11mo ago
socket.js would be part of foundry no? it isn't mine
Forien
Forien•11mo ago
hm, could be, but can't find it ;P
Eclipxs
EclipxsOP•11mo ago
my files in the stack are "RSK*"
Forien
Forien•11mo ago
anyway, I think you should try asking on Foundry discord in the #dev-support channel
Eclipxs
EclipxsOP•11mo ago
ok thanks don't do this: CONFIG.Actor.documentClass = SomeActorProxy;
Want results from more Discord servers?
Add your server