item to actors script

BBadIdeasBureau11/3/2021
I have this macro for updating actors in a compendium with spells from a compendium of spells (i.e. replacing by name any spells on the actors, so I can push my improvements to spell automation onto the monsters in my compendium). It works, but I'm wondering if I can make it more efficient - currently, it's doing an individual update per actor, and that feels like it should be possible to batch into a single update (which would also allow me to construct the update list by getting an extended index with the items for the monsters, and not have to getDocument on each monster). There's definitely a bunch of smaller improvements that can be made as well - this is slightly sketchy code and I'm out of practice.

let pack = game.packs.get("DDBImports.spells")
let index = pack.index
let monsterpack = game.packs.get("DDBImports.monsters")
//await monsterpack.getIndex({fields:["items"]}) 
let monsterindex = Array.from(monsterpack.index)
for (let monster of monsterindex){
    console.log(monster.name)
    let act = await monsterpack.getDocument(monster._id)
    let items = Array.from(act.items)
    let updates = []
    for (let item of items){
        if(item.type !== "spell") continue //only replacing spells
        let newitem = await pack.getDocument(index.find(i => i.name === item.name)?._id) //find a spell with the same name
        if(!newitem) continue //if none, then skip to next
        let update = duplicate(newitem.data)
        update.data.preparation = item.data.data.preparation;
        update.data.uses = item.data.data.uses
        update.data.consume = item.data.data.consume
        update._id = item.data._id
        updates.push(update)
    }
    if(!updates) continue
    console.log(updates)
    await act.updateEmbeddedDocuments("Item", updates)
}
CCalego11/3/2021
this is an interesting problem
CCalego11/3/2021
if you have the inclination, playing around and trying to get updateAll to work would be a solution.
https://foundryvtt.com/api/CompendiumCollection.html#updateAll

But I suspect it's an "accidentally" inherited method that won't work with compendiums at all.
CCalego11/3/2021
I think the getIndex might be the best answer to this problem. but I do not know how well (if at all) one can index items on actors in a compendium
CCalego11/3/2021
like ideally you'd be able to index in such a way that only figures out if the one item needed exists
CCalego11/3/2021
Hmmmm updating all spells in the compendium at once prevents that too
CCalego11/3/2021
getDocuments with an nedb query feels like the best solution here, but it's going to be a gnarly query you'll need to construct based on the spell pack index
CCalego11/3/2021
const docsToUpdate = await game.packs.get("DDBImports.monsters").getDocuments({
  items: {$elemMatch: { type: 'spell' }}
});

const documentUpdates = docsToUpdate.map((actor) => {
  // some Promise.all shennanegins will be needed in here somewhere
  const itemsToUpdate = actor.items
    .filter(({type}) => type === 'spell')
    .map((item) => {
      // how to get the compendium items somewhat performantly? probably getDocument is fine as it caches individual documents as needed
      const newItemId = game.packs.get("DDBImports.spells").index.find(({name}) => name === item.name)?._id;
      const newItemData = await game.packs.get("DDBImports.spells").getDocument(newItemId).toJSON();
      return {
        ...newItemData,
        _id: item._id,
      }
    });

  return {
    _id: actor._id,
    items: itemsToUpdate
  }
});

Actor.updateDocuments(documentUpdates, {pack: 'DDBImports.monsters'});
BBadIdeasBureau11/3/2021
There's actually a way of doing updateAll on a compendium via Document.updateDocuments with an appropriate context modifier, e.g.:

let updated = await Actor.updateDocuments(updates, {pack: packname})


It's more just how to construct the updates object for that
CCalego11/3/2021
the promise of updateAll was being able to use a function instead of an object for the update operation
BBadIdeasBureau11/3/2021
aaaah, right, yeah that makes sense
BBadIdeasBureau11/3/2021
Though I think doing getIndex({fields:[items]}) would give you enough to manipulate to construct the update object without grabbing the full Documents (since all you'd need is {_id, items}....

Which is actually the answer here, isn't it? Grab the expanded index, copy each entry with spells into updates = [{_id, items},...], futz with the items objects to swap out the spells, Actor.updateDocuments(updates, pack).

I think that should work, unless there's some additional stuff updateEmbeddedDocuments does which would be bad to skip over...

(Also if it helps with the query you can assume that any monster with an item of type "spell" needs an update - the spells compendium is a complete import from D&DBeyond, as is the monsters compendium)
BBadIdeasBureau11/3/2021
Hmmm.... I should probably prep for the session tonight before poking this more (first session of Witchlight, so the players would have to make some very poor life choices to end up in a combat where this matters, and I need to be ready to speak a whole lot in rhyme...)
CCalego11/3/2021
Alright @BadIdeasBureau that's as good as I got. I know there's some Promise shennanegins that'll need to be sorted out in there for sure but the concept seems sound
CCalego11/3/2021
Oh shit you can do .find with index? that's dope
CCalego11/3/2021
does Foundry add .find to the Map prototype?? no, not on all the maps
CCalego11/3/2021
:mindblown:
BBadIdeasBureau11/3/2021
My general assumption with Foundry at this point is that if I want to treat it like an Array it's probably worth a shot 😛
CCalego11/3/2021
Ahh it's a Collection under the hood, neat
CCalego11/3/2021
I hope it goes without saying that you should try this with something tame in a test world first 😛