0187daa1e5
Release Creation / build (release) Successful in 1m57s
- active le drag & drop inverse des objets depuis les fiches d’acteur - corrige le calcul des PV des créatures selon la taille - ajoute les options d’armes de créature manquantes - met à jour les styles et les packs générés
323 lines
12 KiB
JavaScript
323 lines
12 KiB
JavaScript
import { LESOUBLIES_CONFIG } from "./les-oublies-config.js"
|
|
import { LesOubliesUtility } from "./les-oublies-utility.js"
|
|
import { LesOubliesRolls } from "./les-oublies-rolls.js"
|
|
|
|
export class LesOubliesActor extends Actor {
|
|
static CREATION_ITEM_TYPES = new Set(["race", "tribu", "metier"])
|
|
static THREAD_RESOURCE_KEYS = new Set(["songesThreads", "cauchemarThreads", "emptyGlobes"])
|
|
|
|
prepareDerivedData() {
|
|
super.prepareDerivedData()
|
|
|
|
if (this.type === "personnage") {
|
|
const system = this.system
|
|
const sizeValue = Math.clamp(Number(system.size?.value ?? 1), 1, 4)
|
|
const hpMax = Math.max(sizeValue * 4 + Number(system.hp?.bonus ?? 0), 0)
|
|
system.hp.max = hpMax
|
|
system.hp.value = Math.min(Number(system.hp.value ?? hpMax), hpMax)
|
|
|
|
const songesValue = Number(system.songes?.value ?? 0)
|
|
const cauchemarValue = Number(system.cauchemar?.value ?? 0)
|
|
const totals = LesOubliesUtility.computeDreamPointTotals(songesValue, cauchemarValue)
|
|
system.songes.max = totals.songesPoints
|
|
system.cauchemar.max = totals.cauchemarPoints
|
|
system.songes.points = Math.clamp(Number(system.songes.points ?? totals.songesPoints), 0, totals.songesPoints)
|
|
system.cauchemar.points = Math.clamp(Number(system.cauchemar.points ?? totals.cauchemarPoints), 0, totals.cauchemarPoints)
|
|
system.reserves.songesThreads = Math.max(Number(system.reserves?.songesThreads ?? 0), 0)
|
|
system.reserves.cauchemarThreads = Math.max(Number(system.reserves?.cauchemarThreads ?? 0), 0)
|
|
system.reserves.emptyGlobes = Math.max(Number(system.reserves?.emptyGlobes ?? 0), 0)
|
|
return
|
|
}
|
|
|
|
if (this.type === "compagnie") {
|
|
const system = this.system
|
|
system.power.sharedDreamPoints = Math.max(Number(system.power?.sharedDreamPoints ?? 0), 0)
|
|
system.reserves.songesThreads = Math.max(Number(system.reserves?.songesThreads ?? 0), 0)
|
|
system.reserves.cauchemarThreads = Math.max(Number(system.reserves?.cauchemarThreads ?? 0), 0)
|
|
system.reserves.emptyGlobes = Math.max(Number(system.reserves?.emptyGlobes ?? 0), 0)
|
|
return
|
|
}
|
|
|
|
if (this.type !== "creature") return
|
|
|
|
const system = this.system
|
|
const sizeValue = Math.clamp(Number(system.size?.value ?? 1), 1, 8)
|
|
const hpMax = Math.max(sizeValue * 4, 0)
|
|
const hpValue = Math.max(Number(system.hp?.value ?? hpMax), 0)
|
|
system.hp.max = hpMax
|
|
system.hp.value = Math.min(hpValue, hpMax)
|
|
const songesPoints = Math.max(Number(system.songes?.points ?? 0), 0)
|
|
const cauchemarPoints = Math.max(Number(system.cauchemar?.points ?? 0), 0)
|
|
system.songes.max = Math.max(Number(system.songes?.max ?? songesPoints), songesPoints)
|
|
system.cauchemar.max = Math.max(Number(system.cauchemar?.max ?? cauchemarPoints), cauchemarPoints)
|
|
system.songes.points = Math.min(songesPoints, system.songes.max)
|
|
system.cauchemar.points = Math.min(cauchemarPoints, system.cauchemar.max)
|
|
}
|
|
|
|
getProfileValue(profileKey) {
|
|
return Number(this.system.profils?.[profileKey] ?? 0)
|
|
}
|
|
|
|
getCreationItem(type) {
|
|
if (!LesOubliesActor.CREATION_ITEM_TYPES.has(type)) return this.items.find((item) => item.type === type) ?? null
|
|
const referenceId = this.system.references?.[`${type}Id`] ?? ""
|
|
if (referenceId) {
|
|
const referencedItem = this.items.get(referenceId)
|
|
if (referencedItem?.type === type) return referencedItem
|
|
}
|
|
return this.items.find((item) => item.type === type) ?? null
|
|
}
|
|
|
|
getEmbeddedItems(type) {
|
|
const items = this.itemTypes?.[type] ?? this.items.filter((item) => item.type === type)
|
|
return LesOubliesUtility.sortByName(items)
|
|
}
|
|
|
|
async assignCreationItem(sourceItem) {
|
|
if (!sourceItem || !LesOubliesActor.CREATION_ITEM_TYPES.has(sourceItem.type)) return null
|
|
|
|
const previousItem = this.getCreationItem(sourceItem.type)
|
|
const itemData = sourceItem.toObject()
|
|
delete itemData._id
|
|
|
|
const existingIds = this.getEmbeddedItems(sourceItem.type).map((item) => item.id)
|
|
if (existingIds.length) {
|
|
await this.deleteEmbeddedDocuments("Item", existingIds, { renderSheet: false })
|
|
}
|
|
|
|
const [createdItem] = await this.createEmbeddedDocuments("Item", [itemData], { renderSheet: false })
|
|
if (!createdItem) return null
|
|
|
|
await this.update({
|
|
[`system.references.${sourceItem.type}Id`]: createdItem.id,
|
|
})
|
|
|
|
if (sourceItem.type === "race") {
|
|
await this.syncRaceProfiles({ currentRace: createdItem })
|
|
await this.syncRaceDomains({ currentRace: createdItem, previousRace: previousItem })
|
|
}
|
|
|
|
return createdItem
|
|
}
|
|
|
|
async clearCreationItem(type) {
|
|
if (!LesOubliesActor.CREATION_ITEM_TYPES.has(type)) return
|
|
|
|
const previousItem = this.getCreationItem(type)
|
|
|
|
const existingIds = this.getEmbeddedItems(type).map((item) => item.id)
|
|
if (existingIds.length) {
|
|
await this.deleteEmbeddedDocuments("Item", existingIds, { renderSheet: false })
|
|
}
|
|
|
|
await this.update({
|
|
[`system.references.${type}Id`]: "",
|
|
})
|
|
|
|
if (type === "race") {
|
|
await this.syncRaceProfiles({ currentRace: null })
|
|
await this.syncRaceDomains({ currentRace: null, previousRace: previousItem })
|
|
}
|
|
}
|
|
|
|
getCompagnie() {
|
|
const compagnieId = this.system.references?.compagnieId
|
|
return compagnieId ? game.actors.get(compagnieId) ?? null : null
|
|
}
|
|
|
|
getThreadReserveOwner(source = "actor") {
|
|
if (source === "company" || source === "compagnie") return this.getCompagnie()
|
|
return this
|
|
}
|
|
|
|
getThreadReserves(source = "actor") {
|
|
const owner = this.getThreadReserveOwner(source)
|
|
return {
|
|
owner,
|
|
songesThreads: Math.max(Number(owner?.system?.reserves?.songesThreads ?? 0), 0),
|
|
cauchemarThreads: Math.max(Number(owner?.system?.reserves?.cauchemarThreads ?? 0), 0),
|
|
emptyGlobes: Math.max(Number(owner?.system?.reserves?.emptyGlobes ?? 0), 0),
|
|
}
|
|
}
|
|
|
|
async transferThreadReserve(resourceKey, amount, direction = "toCompany") {
|
|
if (!LesOubliesActor.THREAD_RESOURCE_KEYS.has(resourceKey)) return false
|
|
const company = this.getCompagnie()
|
|
if (!company) return false
|
|
|
|
const transferAmount = Math.max(Math.trunc(Number(amount ?? 0)), 0)
|
|
if (transferAmount < 1) return false
|
|
|
|
const fromActor = direction === "toCompany" ? this : company
|
|
const toActor = direction === "toCompany" ? company : this
|
|
const current = Math.max(Number(fromActor.system?.reserves?.[resourceKey] ?? 0), 0)
|
|
if (current < transferAmount) return false
|
|
|
|
const path = `system.reserves.${resourceKey}`
|
|
const targetCurrent = Math.max(Number(toActor.system?.reserves?.[resourceKey] ?? 0), 0)
|
|
|
|
await fromActor.update({ [path]: current - transferAmount })
|
|
await toActor.update({ [path]: targetCurrent + transferAmount })
|
|
return true
|
|
}
|
|
|
|
getCompetenceByKey(skillKey) {
|
|
return this.getEmbeddedItems("competence").find((item) => item.system.key === skillKey) ?? null
|
|
}
|
|
|
|
getRaceLanguageDomains(race = this.getCreationItem("race")) {
|
|
return LesOubliesUtility.uniqueStrings(race?.system?.languageDomains ?? [])
|
|
}
|
|
|
|
getRaceProfiles(race = this.getCreationItem("race")) {
|
|
const profiles = LesOubliesUtility.createEmptyProfiles()
|
|
for (const key of Object.keys(profiles)) {
|
|
profiles[key] = Math.trunc(Number(race?.system?.profiles?.[key] ?? 0))
|
|
}
|
|
return profiles
|
|
}
|
|
|
|
async syncRaceProfiles({ currentRace = this.getCreationItem("race") } = {}) {
|
|
if (this.type !== "personnage") return false
|
|
const profiles = this.getRaceProfiles(currentRace)
|
|
const updateData = Object.fromEntries(
|
|
Object.entries(profiles).map(([key, value]) => [`system.profils.${key}`, value]),
|
|
)
|
|
await this.update(updateData)
|
|
if (currentRace) {
|
|
ui.notifications.info(`Profils raciaux appliqués depuis ${currentRace.name}.`)
|
|
}
|
|
return true
|
|
}
|
|
|
|
async syncRaceDomains({ currentRace = this.getCreationItem("race"), previousRace = null } = {}) {
|
|
if (this.type !== "personnage") return false
|
|
|
|
const competence = this.getCompetenceByKey("langues")
|
|
if (!competence) return false
|
|
|
|
const currentAutoDomains = LesOubliesUtility.uniqueStrings(competence.system.fixedDomains ?? [])
|
|
const previousRaceDomains = previousRace
|
|
? this.getRaceLanguageDomains(previousRace)
|
|
: currentAutoDomains
|
|
const autoDomainsToReplace = currentAutoDomains.length ? currentAutoDomains : previousRaceDomains
|
|
const nextAutoDomains = this.getRaceLanguageDomains(currentRace)
|
|
const manualDomains = LesOubliesUtility.uniqueStrings(
|
|
(competence.system.domains ?? []).filter((domain) => !autoDomainsToReplace.includes(domain)),
|
|
)
|
|
|
|
await competence.update({
|
|
"system.fixedDomains": nextAutoDomains,
|
|
"system.domains": LesOubliesUtility.uniqueStrings([...manualDomains, ...nextAutoDomains]),
|
|
})
|
|
return true
|
|
}
|
|
|
|
getSkillScoreByKey(skillKey) {
|
|
const competence = this.getCompetenceByKey(skillKey)
|
|
return competence ? this.computeSkillValue(competence) : 0
|
|
}
|
|
|
|
computeSkillValue(item) {
|
|
const base = Number(item.system.base ?? 0)
|
|
const profileValue = this.getProfileValue(item.system.profileKey)
|
|
if (item.system.closed && base === 0) return 0
|
|
return base + profileValue
|
|
}
|
|
|
|
getCompetences() {
|
|
return this.getEmbeddedItems("competence").map((item) => ({
|
|
item,
|
|
finalValue: this.computeSkillValue(item),
|
|
profileLabel: LESOUBLIES_CONFIG.profileLabels[item.system.profileKey] ?? item.system.profileKey,
|
|
domains: LesOubliesUtility.uniqueStrings(item.system.domains ?? []),
|
|
fixedDomains: LesOubliesUtility.uniqueStrings(item.system.fixedDomains ?? []),
|
|
}))
|
|
}
|
|
|
|
getGroupedCompetences() {
|
|
return LESOUBLIES_CONFIG.profiles.map((profile) => ({
|
|
...profile,
|
|
profileValue: this.getProfileValue(profile.id),
|
|
items: this.getCompetences().filter((entry) => entry.item.system.profileKey === profile.id),
|
|
}))
|
|
}
|
|
|
|
getDerivedOverview() {
|
|
const hpValue = Number(this.system.hp?.value ?? 0)
|
|
const hpMax = Number(this.system.hp?.max ?? 0)
|
|
const hpDisplay = this.type === "creature"
|
|
? (this.system.hp?.display || (hpValue === hpMax ? String(hpValue) : `${hpValue}/${hpMax}`))
|
|
: `${hpValue}/${hpMax}`
|
|
|
|
return {
|
|
sizeLabel: LESOUBLIES_CONFIG.sizes[this.system.size?.value] ?? this.system.size?.value,
|
|
hpMax,
|
|
hpValue,
|
|
hpDisplay,
|
|
songesMax: this.system.songes?.max ?? this.system.songes?.points ?? 0,
|
|
cauchemarMax: this.system.cauchemar?.max ?? this.system.cauchemar?.points ?? 0,
|
|
songesPoints: this.system.songes?.points ?? 0,
|
|
cauchemarPoints: this.system.cauchemar?.points ?? 0,
|
|
reserves: this.getThreadReserves(),
|
|
companyReserves: this.getThreadReserves("company"),
|
|
race: this.getCreationItem("race"),
|
|
tribu: this.getCreationItem("tribu"),
|
|
metier: this.getCreationItem("metier"),
|
|
compagnie: this.getCompagnie(),
|
|
}
|
|
}
|
|
|
|
async openTestRollDialog(preset = {}) {
|
|
return LesOubliesRolls.openTestDialog(this, preset)
|
|
}
|
|
|
|
async openConfrontationRollDialog() {
|
|
return LesOubliesRolls.openConfrontationDialog(this)
|
|
}
|
|
|
|
async openInitiativeRollDialog() {
|
|
return LesOubliesRolls.openInitiativeDialog(this)
|
|
}
|
|
|
|
async openAttackRollDialog({ itemId = null, mode = null } = {}) {
|
|
return LesOubliesRolls.openAttackDialog(this, { itemId, mode })
|
|
}
|
|
|
|
async openDamageDialog({ itemId = null } = {}) {
|
|
return LesOubliesRolls.openDamageDialog(this, { itemId })
|
|
}
|
|
|
|
async openSpellActivationDialog(itemId) {
|
|
return LesOubliesRolls.openSpellDialog(this, itemId)
|
|
}
|
|
|
|
async openCombatPresetDialog(actionKey) {
|
|
return LesOubliesRolls.openCombatPresetDialog(this, actionKey)
|
|
}
|
|
|
|
async openThreadHarvestDialog() {
|
|
return LesOubliesRolls.openThreadHarvestDialog(this)
|
|
}
|
|
|
|
async rollProfile(profileKey) {
|
|
return this.openTestRollDialog({
|
|
label: LESOUBLIES_CONFIG.profileLabels[profileKey] ?? profileKey,
|
|
score: this.getProfileValue(profileKey),
|
|
difficulty: 0,
|
|
rollMode: LesOubliesRolls.getDefaultRollMode(this),
|
|
})
|
|
}
|
|
|
|
async rollCompetence(itemId) {
|
|
const item = this.items.get(itemId)
|
|
if (!item) return null
|
|
return this.openTestRollDialog({
|
|
label: item.name,
|
|
score: this.computeSkillValue(item),
|
|
difficulty: 0,
|
|
rollMode: LesOubliesRolls.getDefaultRollMode(this),
|
|
})
|
|
}
|
|
}
|