Skip to content

Commit

Permalink
Merge pull request #1594 from snap01/fixes
Browse files Browse the repository at this point in the history
Update CoC ID, compedium filtering, and minor fixes
  • Loading branch information
snap01 authored Sep 9, 2024
2 parents bc546cd + 596bc8a commit 81c8d19
Show file tree
Hide file tree
Showing 17 changed files with 848 additions and 374 deletions.
194 changes: 173 additions & 21 deletions .github/ABANDONED.md

Large diffs are not rendered by default.

185 changes: 73 additions & 112 deletions .github/TRANSLATIONS.md

Large diffs are not rendered by default.

10 changes: 9 additions & 1 deletion docs/en/coc-id-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ Several FoundryVTT documents have an ID button accessable to "Game Master" and "

**System ID (CoC ID)**: Setting a skill's ID to i.skill.dodge will let the system know to treat that skill as Dodge on the combat card.

**Language**: Which languge is this version of the Item in.

**Cthulhu Flavors**: You can also control the era a skill is for, Animal Handling has different base values based on the era

**System ID Priority**: The system will check your world documents then compendiums and return the document with the highest priority
**System ID Priority**: The highest number will be considered best

## Item
i.setup.example - The Investigator Wizard will use these you need to pick a single era to assign it to
Expand All @@ -19,3 +21,9 @@ i.skill.example - Setups, Investigator Wizard, and creating Actors will use thes

## RollTable
rt..backstory-example - If the example part matches the backstory title in kebab case it will give a roll option in the backstory section of the Investigator Wizard

## Translations
You can use the Keeper's tools on the left hand menu to update Actors based on CoC ID Items you have in your world, you need a scene to be able to access this menu

## RQID
This system is based on RQID from the "[Runequest Glorantha](https://foundryvtt.com/packages/rqg)" system, documentation for it can be found here [https://sun-dragon-cult.github.io/rqg-system/api/rqid](https://sun-dragon-cult.github.io/rqg-system/api/rqid)
17 changes: 14 additions & 3 deletions lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"CoC7.Archetype": "Archetype",
"CoC7.Occupation": "Occupation",
"CoC7.Age": "Age",
"CoC7.Sex": "Sex",
"CoC7.Sex": "Pronoun",
"CoC7.Residence": "Residence",
"CoC7.Birthplace": "Birthplace",
"CoC7.Organization": "Organization",
Expand Down Expand Up @@ -1330,6 +1330,17 @@

"CoC7.CoCIDBatch.title": "Batch set System ID (CoC ID)",
"CoC7.CoCIDBatch.summary": "The CoC7 System has introduced a System ID (CoC ID). This allows the system to identify FoundryVTT documents for example skills. This page will allow you to set the id for existing {type} documents. The System ID should use the English translation of the name to support localization across all languages.",
"CoC7.ActorCoCIDItemsBest": "CoC ID: Actor Item replacement",
"CoC7.ActorCoCIDItemsWarning": "This action can not be undone, please backup your world before updating your Actors",
"CoC7.ActorCoCIDItemsWhich": "Check Item's CoC ID in active compendiums, world compendiums, and Items directory replacing the Actor's Items with the best choice based on these rules",
"CoC7.ActorCoCIDItemsRules1": "The Item must match the the current era \"{era}\" or have no eras selected",
"CoC7.ActorCoCIDItemsRules2": "Check Item's that match the language \"{lang}\" or \"English\" if no translated Items exist",
"CoC7.ActorCoCIDItemsRules3": "Select the Item with the highest priority",
"CoC7.ActorCoCIDItemsSceneTokens": "Update all tokens on this scene",
"CoC7.ActorCoCIDItemsUnlinkedToken": "Update unlinked Actor in directory for each Token first",
"CoC7.ActorCoCIDItemsActorSheets": "Update open Actor sheets",
"CoC7.ActorCoCIDItemsActorDirectory": "Update all Actors in directory",
"CoC7.ActorCoCIDItemsUpdate": "Update",

"CoC7.TokenCreationRoll.Title": "Rollable detected",
"CoC7.TokenCreationRoll.Prompt": "This token has rollables characteristics or skills.<br>What do you want to do ?",
Expand All @@ -1338,7 +1349,6 @@
"CoC7.TokenCreationRoll.Rolled": "{name} characteristics and skills rolled",
"CoC7.TokenCreationRoll.Averaged": "{name} characteristics and skills averaged",


"CoC7.RealRollDecaderPlaceholderName": "10's",

"CoC7.Temporary": "Temporary",
Expand All @@ -1353,5 +1363,6 @@
"CoC7.ErrorRollAlreadyCompleted": "This roll has already been completed",
"CoC7.ErrorNoActorPermission": "You are not the Owner of this Actor",
"CoC7.ErrorCombinedRollsRequireSingleActor": "You can not have more than one actor in a combined roll",
"CoC7.ErrorOpposedRollsLimitedToTwoActors": "An opposed card requires two actors"
"CoC7.ErrorOpposedRollsLimitedToTwoActors": "An opposed card requires two actors",
"CoC7.PauseName": "Time Stop"
}
19 changes: 15 additions & 4 deletions module/actors/actor.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,17 @@ export class CoCActor extends Actor {
}
}

static defaultImg (type) {
switch (type) {
case 'container':
return 'icons/svg/chest.svg'
case 'creature':
return 'systems/CoC7/assets/icons/floating-tentacles.svg'
case 'npc':
return 'systems/CoC7/assets/icons/cultist.svg'
}
}

/** @override */
static async create (data, options = {}) {
if (data.type === 'character') {
Expand All @@ -237,15 +248,15 @@ export class CoCActor extends Actor {
})
} else if (data.type === 'npc') {
if (typeof data.img === 'undefined' || data.img === 'icons/svg/mystery-man.svg') {
data.img = 'systems/CoC7/assets/icons/cultist.svg'
data.img = CoCActor.defaultImg(data.type)
}
} else if (data.type === 'creature') {
if (typeof data.img === 'undefined' || data.img === 'icons/svg/mystery-man.svg') {
data.img = 'systems/CoC7/assets/icons/floating-tentacles.svg'
data.img = CoCActor.defaultImg(data.type)
}
} else if (data.type === 'container') {
if (typeof data.img === 'undefined' || data.img === 'icons/svg/mystery-man.svg') {
data.img = 'icons/svg/chest.svg'
data.img = CoCActor.defaultImg(data.type)
}
data.prototypeToken = foundry.utils.mergeObject(data.prototypeToken || {}, {
actorLink: true
Expand Down Expand Up @@ -3501,7 +3512,7 @@ export class CoCActor extends Actor {
get firearmSkills () {
const skillList = []
for (const value of this.items) {
if (value.type === 'skill' && (value.system.properties.firearm || value.system.properties.ranged)) {
if (value.type === 'skill' && (value.system.properties.firearm || value.system.properties.ranged || value.flags.CoC7?.cocidFlag?.id === 'i.skill.fighting-throw')) {
skillList.push(value)
}
}
Expand Down
14 changes: 14 additions & 0 deletions module/actors/sheets/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { CoC7ChatMessage } from '../../apps/coc7-chat-message.js'
import { CoC7Check } from '../../check.js'
import { CoC7ContentLinkDialog } from '../../apps/coc7-content-link-dialog.js'
import { COC7 } from '../../config.js'
import { CoCActor } from '../../actors/actor.js'
import { CoC7Item } from '../../items/item.js'
import { CoC7MeleeInitiator } from '../../chat/combat/melee-initiator.js'
import { CoC7RangeInitiator } from '../../chat/rangecombat.js'
Expand Down Expand Up @@ -1834,6 +1835,19 @@ export class CoC7ActorSheet extends ActorSheet {
)
}

if (this.object.img !== formData.img && (this.object.token ?? this.object.prototypeToken).texture.src === CoCActor.defaultImg(this.object.type)) {
// Image was changed and it was the default, so also update the token image
if (this.object.token) {
this.object.token.update({
'texture.src': formData.img
})
} else {
this.object.prototypeToken.update({
'texture.src': formData.img
})
}
}

if (event.currentTarget) {
if (event.currentTarget.classList) {
if (event.currentTarget.classList.contains('skill-adjustment')) {
Expand Down
149 changes: 149 additions & 0 deletions module/apps/coc-id-actor-update-items.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/* global Actor, ActorSheet, canvas, CONFIG, FormApplication, foundry, fromUuid, game, ui */
import { COC7 } from '../config.js'

export default class CoCIDActorUpdateItems extends FormApplication {
static get defaultOptions () {
return foundry.utils.mergeObject(super.defaultOptions, {
id: 'coc-id-actor-update-items',
classes: ['coc7', 'dialog', 'investigator-wizard'],
title: game.i18n.localize('CoC7.ActorCoCIDItemsBest'),
template: 'systems/CoC7/templates/apps/coc-id-actor-update-items.hbs',
width: 520,
height: 410,
closeOnSubmit: false
})
}

async getData () {
const sheetData = await super.getData()

sheetData.lang = CONFIG.supportedLanguages[game.i18n.lang] ?? '?'
const defaultEra = game.settings.get('CoC7', 'worldEra')
sheetData.isEn = game.i18n.lang === 'en'
sheetData.era = game.i18n.format(COC7.eras[defaultEra] ?? 'CoC7.CoCIDFlag.error.unknown-era', { era: defaultEra })

return sheetData
}

getUpdateData (item) {
const output = {
flags: {
CoC7: {
cocidFlag: item.flags.CoC7.cocidFlag
}
},
name: item.name,
system: {}
}
for (const key of ['chat', 'keeper', 'notes', 'opposingDifficulty', 'pushedFaillureConsequences', 'special', 'value']) {
if (typeof item.system.description?.[key] === 'string' && item.system.description[key].length) {
if (!Object.prototype.hasOwnProperty.call(output.system, 'description')) {
output.system.description = {}
}
output.system.description[key] = item.system.description[key]
}
}
switch (item.type) {
case 'archetype':
output.system.suggestedOccupations = item.system.suggestedOccupations
output.system.suggestedTraits = item.system.suggestedTraits
break
case 'book':
break
case 'occupation':
output.system.suggestedContacts = item.system.suggestedContacts
break
case 'skill':
output.system.skillName = item.system.skillName
output.system.specialization = item.system.specialization
break
case 'spell':
break
case 'status':
break
case 'weapon':
break
}
return output
}

async updateActors (actorList, parent) {
if (parent) {
const unlinkedActors = await actorList.filter(a => a.token?.actorLink === false).map(a => a.id).filter((a, o, v) => v.indexOf(a) === o).reduce(async (c, i) => {
c.push(await fromUuid('Actor.' + i))
return c
}, [])
actorList = unlinkedActors.concat(actorList)
}
const ids = {}
const anys = {}
for (const actor of actorList) {
for (const item of actor.items.contents) {
if (typeof item.flags?.CoC7?.cocidFlag?.id === 'string') {
if (item.flags.CoC7.cocidFlag.id.match(/-any$/)) {
if (!Object.prototype.hasOwnProperty.call(anys, item.flags.CoC7.cocidFlag.id)) {
anys[item.flags.CoC7.cocidFlag.id] = []
}
anys[item.flags.CoC7.cocidFlag.id].push(actor.name)
} else {
ids[item.flags.CoC7.cocidFlag.id] = {}
}
}
}
}
const found = await game.system.api.cocid.fromCoCIDRegexBest({ cocidRegExp: game.system.api.cocid.makeGroupRegEx(Object.keys(ids)), type: 'i', showLoading: true })
for (const item of found) {
ids[item.flags.CoC7.cocidFlag.id] = this.getUpdateData(item.toObject())
}
if (Object.keys(anys).length) {
console.log('Invalid any keys on Actors', anys)
}
const allUpdates = []
for (const actor of actorList) {
const updates = []
for (const item of actor.items.contents) {
if (Object.prototype.hasOwnProperty.call(ids, item.flags?.CoC7?.cocidFlag?.id)) {
updates.push(foundry.utils.mergeObject({
_id: item.id
}, ids[item.flags.CoC7.cocidFlag.id]))
}
}
if (updates.length) {
allUpdates.push({
_id: actor.id,
items: updates
})
}
}
if (allUpdates.length) {
Actor.updateDocuments(allUpdates)
}
}

async _updateObject (event, formData) {
if (event.submitter?.dataset.button === 'update') {
if (event.submitter.className.indexOf('currently-submitting') > -1) {
return
}
event.submitter.className = event.submitter.className + ' currently-submitting'
const parent = typeof formData['coc-id-actor-update-items-parent'] === 'string'
const which = (formData['coc-id-actor-update-items-which'] ?? '').toString()
switch (which) {
case '1':
await this.updateActors(canvas.scene.tokens.contents.map(d => d.object.actor), parent)
break
case '2':
await this.updateActors(Object.values(ui.windows).filter(s => s instanceof ActorSheet).map(s => s.object), parent)
break
case '3':
await this.updateActors(game.actors.contents, false)
break
}
}
this.close()
}

static async create (options = {}) {
new CoCIDActorUpdateItems(options).render(true)
}
}
22 changes: 20 additions & 2 deletions module/apps/coc-id-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ export class CoCIDEditor extends FormApplication {
for (const era of eras) {
usedEras[era] = COC7.eras[era] ?? '?'
}
const folders = []
let e = d?.folder
while (e?.name) {
folders.unshift(e?.name)
e = e.folder
}
return {
eras: eras.reduce(function (all, current) {
all[current] = true
Expand All @@ -88,7 +94,7 @@ export class CoCIDEditor extends FormApplication {
priority: d.flags.CoC7.cocidFlag.priority,
lang: d.flags.CoC7.cocidFlag.lang ?? 'en',
link: await TextEditor.enrichHTML(d.link, { async: true }),
folder: d?.folder?.name
folder: folders.map(n => n.indexOf(' ') > -1 ? '"' + n + '"' : n).join(' &gt; ')
}
}))
if (Object.entries(uniqueWorldPriority).filter(c => c[1] > 1).length > 0) {
Expand Down Expand Up @@ -118,6 +124,18 @@ export class CoCIDEditor extends FormApplication {
for (const era of eras) {
usedEras[era] = COC7.eras[era] ?? '?'
}
const folders = []
let e = d?.folder
while (e?.name) {
folders.unshift(e?.name)
e = e.folder
}
folders.unshift(d.compendium.metadata.label)
e = d?.compendium.folder
while (e?.name) {
folders.unshift(e?.name)
e = e.folder
}
return {
eras: eras.reduce(function (all, current) {
all[current] = true
Expand All @@ -126,7 +144,7 @@ export class CoCIDEditor extends FormApplication {
priority: d.flags.CoC7.cocidFlag.priority,
lang: d.flags.CoC7.cocidFlag.lang ?? 'en',
link: await TextEditor.enrichHTML(d.link, { async: true }),
folder: d?.folder?.name ?? ''
folder: folders.map(n => n.indexOf(' ') > -1 ? '"' + n + '"' : n).join(' &gt; ')
}
}))
if (Object.entries(uniqueCompendiumPriority).filter(c => c[1] > 1).length > 0) {
Expand Down
11 changes: 9 additions & 2 deletions module/dice.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* global ChatMessage, CONFIG, game, Roll */
/* global ChatMessage, CONFIG, foundry, game, Roll */

export class CoC7Dice {
static async roll (modif = 0, rollMode = null, hideDice = false) {
Expand All @@ -8,6 +8,11 @@ export class CoC7Dice {
alternativeDice = game.settings.get('CoC7', 'tenDiePenalty')
} else if (modif > 0) {
alternativeDice = game.settings.get('CoC7', 'tenDieBonus')
// Temporary fix for bronze texture in DsN
// FoundryVTT v12
if (alternativeDice === 'bronze' && foundry.utils.isNewerVersion(game.modules.get('dice-so-nice').version, '5.0.0') && !foundry.utils.isNewerVersion(game.modules.get('dice-so-nice').version, '5.0.5')) {
alternativeDice = 'bronze01'
}
}
}
let roll
Expand Down Expand Up @@ -111,8 +116,10 @@ export class CoC7Dice {
}
if (bonusDice > 0) {
pool.push(
// Temporary fix for bronze texture in DsN
// FoundryVTT v12
(hasDSN
? '+1do[' + game.settings.get('CoC7', 'tenDieBonus') + ']'
? '+1do[' + (game.settings.get('CoC7', 'tenDieBonus') === 'bronze' && foundry.utils.isNewerVersion(game.modules.get('dice-so-nice').version, '5.0.0') && !foundry.utils.isNewerVersion(game.modules.get('dice-so-nice').version, '5.0.5') ? 'bronze01' : game.settings.get('CoC7', 'tenDieBonus')) + ']'
: '+1dt'
).repeat(Math.abs(bonusDice))
)
Expand Down
Loading

0 comments on commit 81c8d19

Please sign in to comment.