import { BoLDefaultRoll, BoLRoll } from "../controllers/bol-rolls.js"; import { BoLUtility } from "../system/bol-utility.js"; /** * Extend the base Actor entity by defining a custom roll data structure which is ideal for the Simple system. * @extends {Actor} */ export class BoLActor extends Actor { static async create(data, options) { // Case of compendium global import if (data instanceof Array) { return super.create(data, options); } // If the created actor has items (only applicable to duplicated actors) bypass the new actor creation logic if (data.items) { let actor = super.create(data, options); return actor; } if (data.type == 'encounter') { data.system = { resources: { hero : { max : 0, value: 0 } } } } if (data.type == 'horde') { let weapon = { name: game.i18n.localize("BOL.ui.hordeAttack"), type: "item", img: "ui/icons/attaque_melee.webp", system: foundry.utils.duplicate(game.bol.config.defaultNaturalWeapon) } weapon.system.properties.attackModifiers = 1 weapon.system.properties.damage = "1d6M" data.items = [weapon] data.img = "systems/bol/ui/icons/icon-horde-token.webp" data.prototypeToken = { texture: "systems/bol/ui/icons/icon-horde-token.webp" } } return super.create(data, options); } /** @override */ prepareData() { if (this.type === 'character') { this.chartype = 'player' this.villainy = false } if (this.type === 'encounter') { this.chartype = 'tough' this.villainy = true } if (this.type === 'creature') { this.villainy = true } if (this.type == "horde") { let weapon = this.items.find(i => i.type === "item" && i.system.subtype === "weapon") // Check if the horde attack is inline with the hordesize if (weapon?.system?.properties?.attackModifiers != this.system.hordesize) { this.updateEmbeddedDocuments('Item', [{ _id: weapon.id, 'system.properties.attackModifiers': this.system.hordesize }]) } } super.prepareData() } /* -------------------------------------------- */ async _onCreateOperation(documents, operation, user) { await super._onCreateOperation(documents, operation, user); } /* -------------------------------------------- */ getTokenSizeFromHordeSize(hordeSize) { hordeSize = hordeSize || this.system.hordesize // If size > 50 then max is 50 let size = Math.min(hordeSize, 20) // Compute the size of the token from 1 to 5 let tokenSize = Math.max((size / 4), 1) // Never below 1 return tokenSize } /* -------------------------------------------- */ async _preCreate(data, options, user) { await super._preCreate(data, options, user); // Configure prototype token settings const prototypeToken = {}; if (this.type === "character") { Object.assign(prototypeToken, { sight: { enabled: true }, actorLink: true, disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY }); this.updateSource({ prototypeToken }); } } /* -------------------------------------------- */ isHeroAdversary() { if (this.type === 'character') { return true } return (this.type === 'encounter' && this.chartype == "adversary") } /* -------------------------------------------- */ getCharType() { if (this.type === 'character') { return "player" } return this.system.chartype } /* -------------------------------------------- */ getVillainy() { return ( (this.type === 'encounter' && this.chartype == "adversary") || this.chartype == "creature") } isUndead() { return (this.type == "encounter" && this.system.isundead) } /* -------------------------------------------- */ getInitiativeMalus() { if (this.type === 'encounter' && (this.chartype == "adversary" || this.chartype == "tough")) { return this.system.aptitudes.init.value } return 0 } /* -------------------------------------------- */ getBougette() { if (this.type == "character") { let b = foundry.utils.duplicate(this.system.bougette) b.label = game.i18n.localize(game.bol.config.bougetteState[String(this.system.bougette.value)]) b.diceImg = "icons/dice/" + game.bol.config.bougetteDice[String(this.system.bougette.value)] + "black.svg" return b } return undefined } /* -------------------------------------------- */ async rollBougette() { if (this.type == "character") { let attribute = foundry.utils.duplicate(this.system.attributes.vigor) let rollData = BoLRoll.getCommonRollData(this, "bougette", attribute, undefined) rollData.formula = game.bol.config.bougetteDice[String(this.system.bougette.value)] let r = new BoLDefaultRoll(rollData) r.roll() } } /* -------------------------------------------- */ decBougette() { if (this.type == "character") { let bougette = foundry.utils.duplicate(this.system.bougette) bougette.value = Math.max(Number(bougette.value) - 1, 0) this.update({ 'system.bougette': bougette }) } } /* -------------------------------------------- */ updateResourcesData() { if (this.type == 'character') { let newVitality = 10 + this.system.attributes.vigor.value + this.system.resources.hp.bonus if (this.system.resources.hp.max != newVitality) { let actor = this setTimeout(function () { actor.update({ 'system.resources.hp.max': newVitality }) }, 800) } let newPower = 10 + this.system.attributes.mind.value + this.system.resources.power.bonus if (this.system.resources.power.max != newPower) { let actor = this setTimeout(function () { actor.update({ 'system.resources.power.max': newPower }) }, 800) } } } /* -------------------------------------------- */ prepareDerivedData() { if (this.type == "vehicle") { } else { super.prepareDerivedData() if (this.id) { this.updateResourcesData() this.manageHealthState() } } } /* -------------------------------------------- */ get details() { return this.system.details } addEffectModifiers(myList, dataPath) { for (let attr of myList) { attr.numModifier = 0 attr.diceModifier = "" let effects = this.items.filter(i => i.type === "feature" && i.system.subtype === "boleffect" && i.system.properties.identifier == dataPath + attr.key) for (let effect of effects) { if (Number(effect.system.properties.modifier)) { attr.numModifier += Number(effect.system.properties.modifier) } else { attr.diceModifier += "+" + effect.system.properties.modifier } } } } get attributes() { let attrList = foundry.utils.duplicate(Object.values(this.system.attributes)) this.addEffectModifiers(attrList, "system.attributes.") return attrList } get aptitudes() { let aptList = Object.values(this.system.aptitudes) this.addEffectModifiers(aptList, "system.aptitudes.") return aptList } /* -------------------------------------------- */ clearRoundModifiers() { // Process data/items that are finished at end of a round let foList = this.fightoptions for (let fo of foList) { if (fo.system.properties.used) { this.updateEmbeddedDocuments("Item", [{ _id: fo._id, 'system.properties.used': false }]) } } } /* -------------------------------------------- */ get defenseValue() { let defMod = 0 let fo = this.getActiveFightOption() if (fo && fo.system.properties.fightoptiontype == "intrepid") { defMod += -2 } if (fo && fo.system.properties.fightoptiontype == "fulldefense") { defMod += 2 } if (fo && fo.system.properties.fightoptiontype == "twoweaponsdef" && !fo.system.properties.used) { defMod += 1 this.updateEmbeddedDocuments("Item", [{ _id: fo._id, 'system.properties.used': true }]) } if (fo && fo.system.properties.fightoptiontype == "defense") { defMod += 1 } if (fo && fo.system.properties.fightoptiontype == "attack") { defMod += -1 } // Apply defense effects for (let i of this.items) { if (i.type === "feature" && i.system.subtype === "boleffect" && i.system.properties.identifier.includes("aptitudes.def")) { defMod += Number(i.system.properties.modifier) } } console.log("Defense : ", defMod) return this.system.aptitudes.def.value + defMod } /* -------------------------------------------- */ getActiveFightOption() { let it = this.items.find(i => i.type === "feature" && i.system.subtype === "fightoption" && i.system.properties.activated) if (it) { return foundry.utils.duplicate(it) } return undefined } /* -------------------------------------------- */ addXPLog(type, name, cost, value) { let xplog = { name: "XP : " + game.i18n.localize(type), type: "feature", img: "icons/magic/symbols/chevron-elipse-circle-blue.webp", system: { subtype: "xplog", properties: { xptype: type, xpdate: new Date().toLocaleDateString(), xpname: name, xpcost: cost, xpvalue: value } } } this.createEmbeddedDocuments('Item', [xplog]) } /* -------------------------------------------- */ incAttributeXP(key) { let attr = foundry.utils.duplicate(this.system.attributes[key]) if (attr) { let nextXP = (attr.value == -1) ? 2 : attr.value + (attr.value + 1) let xp = foundry.utils.duplicate(this.system.xp) if (xp.total - xp.spent >= nextXP) { attr.value += 1 xp.spent += nextXP this.update({ [`system.attributes.${key}`]: attr, [`system.xp`]: xp }) this.addXPLog("attribute", key, nextXP, attr.value) } else { ui.notifications.warn("Pas assez de points d'expérience !") } } } /* -------------------------------------------- */ incAptitudeXP(key) { let apt = foundry.utils.duplicate(this.system.aptitudes[key]) if (apt) { let nextXP = (apt.value == -1) ? 1 : apt.value + 2 let xp = foundry.utils.duplicate(this.system.xp) if (xp.total - xp.spent >= nextXP) { apt.value += 1 xp.spent += nextXP this.update({ [`system.aptitudes.${key}`]: apt, [`system.xp`]: xp }) this.addXPLog("aptitude", key, nextXP, apt.value) } else { ui.notifications.warn("Pas assez de points d'expérience !") } } } /* -------------------------------------------- */ incCareerXP(itemId) { let career = this.items.get(itemId) if (career) { career = foundry.utils.duplicate(career) let nextXP = career.system.rank + 1 let xp = foundry.utils.duplicate(this.system.xp) if (xp.total - xp.spent >= nextXP) { xp.spent += nextXP this.update({ [`system.xp`]: xp }) this.updateEmbeddedDocuments('Item', [{ _id: career._id, 'system.rank': career.system.rank + 1 }]) this.addXPLog("career", career.name, nextXP, career.system.rank + 1) } else { ui.notifications.warn("Pas assez de points d'expérience !") } } } /* -------------------------------------------- */ async toggleFightOption(itemId) { let fightOption = this.items.get(itemId) let state let updates = [] if (fightOption) { fightOption = foundry.utils.duplicate(fightOption) if (fightOption.system.properties.activated) { state = false } else { state = true } updates.push({ _id: fightOption._id, 'system.properties.activated': state }) // Update the selected one await this.updateEmbeddedDocuments("Item", updates) // Apply all changes // Then notify ChatMessage.create({ alias: this.name, whisper: BoLUtility.getWhisperRecipientsAndGMs(this.name), content: await renderTemplate('systems/bol/templates/chat/chat-activate-fight-option.hbs', { name: this.name, img: fightOption.img, foName: fightOption.name, state: state }) }) } } /*-------------------------------------------- */ get armorMalusValue() { // used for Fight Options for (let armor of this.armors) { if (armor.system.properties.armorQuality?.includes("light")) { return 1 } if (armor.system.properties.armorQuality?.includes("medium")) { return 2 } if (armor.system.properties.armorQuality?.includes("heavy")) { return 3 } } return 0 } get resources() { return Object.values(this.system.resources) } get boleffects() { return this.items.filter(i => i.type === "feature" && i.system.subtype === "boleffect") } get xplog() { return this.items.filter(i => i.type === "feature" && i.system.subtype === "xplog") } get horoscopes() { return this.items.filter(i => i.type === "feature" && i.system.subtype === "horoscope") } get boons() { return foundry.utils.duplicate(this.items.filter(i => i.type === "feature" && i.system.subtype === "boon") || []); } get flaws() { return foundry.utils.duplicate(this.items.filter(i => i.type === "feature" && i.system.subtype === "flaw") || []); } get careers() { return foundry.utils.duplicate(this.items.filter(i => i.type === "feature" && i.system.subtype === "career") || []) } get origins() { return this.items.filter(i => i.type === "feature" && i.system.subtype === "origin"); } get races() { return this.items.filter(i => i.type === "feature" && i.system.subtype === "race"); } get languages() { return this.items.filter(i => i.type === "feature" && i.system.subtype === "language") } get fightoptions() { return this.items.filter(i => i.type === "feature" && i.system.subtype === "fightoption") } get godsfaith() { return this.items.filter(i => i.type === "feature" && i.system.subtype === "godsfaith") } get features() { return this.items.filter(i => i.type === "feature") } get equipment() { return this.items.filter(i => i.type === "item") } get equipmentCreature() { return this.items.filter(i => i.type === "item" && i.system.category === "equipment" && ((i.system.subtype === "weapon" && i.system.properties.natural === true) || (i.system.subtype === "armor"))) } get armors() { return this.items.filter(i => i.type === "item" && i.system.category === "equipment" && i.system.subtype === "armor"); } get helms() { return this.items.filter(i => i.type === "item" && i.system.category === "equipment" && i.system.subtype === "helm"); } get shields() { return this.items.filter(i => i.type === "item" && i.system.category === "equipment" && i.system.subtype === "shield"); } get vehicleWeapons() { return this.items.filter(i => i.type === "item" && i.system.category === "vehicleweapon") } get weapons() { return this.items.filter(i => i.type === "item" && i.system.category === "equipment" && i.system.subtype === "weapon") } get protections() { return this.armors.concat(this.helms).concat(this.shields) } get spells() { return this.items.filter(i => i.type === "item" && i.system.category === "spell"); } get alchemy() { return this.items.filter(i => i.type === "item" && i.system.category === "alchemy"); } get melee() { return this.weapons.filter(i => i.system.properties.melee === true); } get natural() { return this.weapons.filter(i => i.system.properties.natural === true); } get ranged() { return this.weapons.filter(i => i.system.properties.ranged === true); } get containers() { return this.items.filter(i => i.type === "item" && i.system.category === "equipment" && i.system.subtype === "container"); } get treasure() { return this.items.filter(i => i.type === "item" && i.system.category === "equipment" && i.system.subtype === "currency"); } get vehicles() { return this.items.filter(i => i.type === "item" && i.system.category === "vehicle"); } get ammos() { return this.items.filter(i => i.type === "item" && i.system.category === "equipment" && i.system.subtype === "ammunition"); } get misc() { return this.items.filter(i => i.type === "item" && i.system.category === "equipment" && (i.system.subtype === "other" || i.system.subtype === "container" || i.system.subtype === "scroll" || i.system.subtype === "jewel")); } get bonusBoons() { let boons = this.items.filter(i => i.type === "feature" && i.system.subtype === "boon" && i.system.properties.isbonusdice) return foundry.utils.duplicate(boons || []) } get malusFlaws() { return foundry.utils.duplicate(this.items.filter(i => i.type === "feature" && i.system.subtype === "flaw" && i.system.properties.ismalusdice) || []); } isSorcerer() { return (this.careers.find(item => item.system.properties.sorcerer)) } isAlchemist() { return (this.careers.find(item => item.system.properties.alchemist)) } isAstrologer() { return (this.careers.find(item => item.system.properties.astrologer)) } isPriest() { return (this.careers.find(item => item.system.properties.priest)) } /*-------------------------------------------- */ getPPCostArmor() { let armors = this.armors let ppCostArmor = 0 for (let armor of armors) { if (armor.system.worn) { ppCostArmor += Number(armor.system.properties.modifiers.powercost) || 0 } } return ppCostArmor } /*-------------------------------------------- */ getDamageAttributeValue(attrDamage) { let attrDamageValue = 0 if (attrDamage.includes("vigor")) { attrDamageValue = this.system.attributes.vigor.value if (attrDamage.includes("half")) { attrDamageValue = Math.floor(attrDamageValue / 2) } // Apply vigor effects for (let i of this.items) { if (i.type === "feature" && i.system.subtype === "boleffect" && i.system.properties.identifier.includes("vigor")) { attrDamageValue += Number(i.system.properties.modifier) } } } return attrDamageValue } /*-------------------------------------------- */ getArmorAgiMalus() { let malusAgi = 0 for (let armor of this.protections) { if (armor.system.worn) { malusAgi += Number(armor.system.properties.modifiers.agility) || 0 } } return malusAgi } /*-------------------------------------------- */ getArmorInitMalus() { let malusInit = 0 for (let armor of this.protections) { if (armor.system.worn) { malusInit += Number(armor.system.properties.modifiers.init) || 0 } } return malusInit } /*-------------------------------------------- */ spendPowerPoint(ppCost) { let newPP = this.system.resources.power.value - ppCost newPP = (newPP < 0) ? 0 : newPP this.update({ 'system.resources.power.value': newPP }) return newPP } /*-------------------------------------------- */ resetAlchemyStatus(alchemyId) { let alchemy = this.items.get(alchemyId) if (alchemy) { this.updateEmbeddedDocuments('Item', [{ _id: alchemy.id, 'system.properties.pccurrent': 0 }]) } } /*-------------------------------------------- */ spentAstrologyPoints(points) { let astrology = foundry.utils.duplicate(this.system.resources.astrologypoints) astrology.value -= points astrology.value = Math.max(astrology.value, 0) this.update({ 'system.resources.astrologypoints': astrology }) } /*-------------------------------------------- */ getHoroscopesBonus() { let astro = this.items.filter(it => it.type == "feature" && it.system.subtype == "horoscope" && !it.system.properties.ishoroscopemajor && it.system.properties.horoscopeanswer == "favorable") return astro } /*-------------------------------------------- */ getHoroscopesMalus() { let astro = this.items.filter(it => it.type == "feature" && it.system.subtype == "horoscope" && !it.system.properties.ishoroscopemajor && it.system.properties.horoscopeanswer == "unfavorable") return astro } /*-------------------------------------------- */ manageHoroscope(rollData) { //Spent points this.spentAstrologyPoints(rollData.astrologyPointsCost) if (rollData.horoscopeType == "minor") { let horoscope = { name: "SITUATION A SPECIFIER", type: "feature", img: "icons/magic/perception/eye-ringed-glow-angry-large-red.webp", system: { subtype: "horoscope", properties: { ishoroscopemajor: false, horoscopeanswer: (rollData.isSuccess) ? "favorable" : "unfavorable", rank: rollData.careerBonus } } } this.createEmbeddedDocuments('Item', [horoscope]) } if (rollData.horoscopeType == "major") { let actorHoroscope = this if (rollData.targetId) { let token = game.scenes.current.tokens.get(rollData.targetId) actorHoroscope = token.actor } if (rollData.isSuccess) { actorHoroscope.addHeroPoints(1) } else { actorHoroscope.subHeroPoints(1) } rollData.horoscopeName = actorHoroscope.name } if (rollData.horoscopeType == "majorgroup") { let rID = foundry.utils.randomID(16) let horoscopes = foundry.utils.duplicate(game.settings.get("bol", "horoscope-group")) horoscopes[rID] = { id: rID, name: game.i18n.localize("BOL.ui.groupHoroscope") + this.name, maxDice: rollData.careerBonus, availableDice: rollData.careerBonus, type: (rollData.isSuccess) ? "bonus" : "malus" } game.settings.set("bol", "horoscope-group", horoscopes) } } /*-------------------------------------------- */ getAstrologyPoints() { return this.system.resources.astrologypoints.value } /*-------------------------------------------- */ removeHoroscopeMinor(rollData) { let toDel = [] for (let horo of rollData.selectedHoroscope) { toDel.push(horo._id) } if (toDel.length > 0) { this.deleteEmbeddedDocuments('Item', toDel) } } /*-------------------------------------------- */ async spendAlchemyPoint(alchemyId, pcCost) { let alchemy = this.items.get(alchemyId) if (alchemy) { pcCost = Number(pcCost) || 0 if (this.system.resources.alchemypoints.value >= pcCost) { let newPC = this.system.resources.alchemypoints.value - pcCost newPC = (newPC < 0) ? 0 : newPC this.update({ 'data.resources.alchemypoints.value': newPC }) newPC = alchemy.system.properties.pccurrent + pcCost await this.updateEmbeddedDocuments('Item', [{ _id: alchemy.id, 'system.properties.pccurrent': newPC }]) } else { ui.notifications.warn(game.i18n.localize("BOL.ui.nomorealchemypoints")) } } } /*-------------------------------------------- */ getAstrologerBonus() { let astrologer = this.careers.find(item => item.system.properties.astrologer) if (astrologer) { return astrologer.system.rank } return 0; } /*-------------------------------------------- */ getAlchemistBonus() { let sorcerer = this.careers.find(item => item.system.properties.alchemist) if (sorcerer) { return sorcerer.system.rank } return 0; } /*-------------------------------------------- */ getSorcererBonus() { let sorcerer = this.careers.find(item => item.system.properties.sorcerer) if (sorcerer) { return sorcerer.system.rank } return 0; } /*-------------------------------------------- */ heroReroll() { if (this.type == 'character' || this.system.villainy == 'adversary') { return this.system.resources.hero.value > 0; } return false } /*-------------------------------------------- */ getHeroPoints() { if (this.type == 'character' || this.system.villainy == 'adversary') { return this.system.resources.hero.value } return 0 } /*-------------------------------------------- */ getResourcesFromType() { let resources = {}; if (this.type == 'encounter') { resources['hp'] = this.system.resources.hp; if (this.system.chartype != 'base') { resources['faith'] = this.system.resources.faith resources['power'] = this.system.resources.power } if (this.system.chartype == 'adversary' || this.system.chartype == 'creature') { resources['hero'] = foundry.utils.duplicate(this.system.resources.hero) resources['hero'].label = "BOL.resources.villainy" } } else { resources = this.system.resources; } return resources } buildFeatures() { return { "careers": { "label": "BOL.featureCategory.careers", "ranked": true, "items": this.careers }, "origins": { "label": "BOL.featureCategory.origins", "ranked": false, "items": this.origins }, "races": { "label": "BOL.featureCategory.races", "ranked": false, "items": this.races }, "boons": { "label": "BOL.featureCategory.boons", "ranked": false, "items": this.boons }, "flaws": { "label": "BOL.featureCategory.flaws", "ranked": false, "items": this.flaws }, "languages": { "label": "BOL.featureCategory.languages", "ranked": false, "items": this.languages }, "fightoptions": { "label": "BOL.featureCategory.fightoptions", "ranked": false, "items": this.fightoptions }, "godsfaith": { "label": "BOL.featureSubtypes.gods", "ranked": false, "items": this.godsfaith }, "boleffects": { "label": "BOL.featureSubtypes.effects", "ranked": false, "items": this.boleffects } } } buildCombat() { return { "melee": { "label": "BOL.combatCategory.melee", "weapon": true, "protection": false, "blocking": false, "ranged": false, "options": false, "items": this.melee }, "natural": { "label": "BOL.combatCategory.natural", "weapon": true, "protection": false, "blocking": false, "ranged": false, "options": false, "items": this.natural }, "ranged": { "label": "BOL.combatCategory.ranged", "weapon": true, "protection": false, "blocking": false, "ranged": true, "options": false, "items": this.ranged }, "protections": { "label": "BOL.combatCategory.protections", "weapon": false, "protection": true, "blocking": false, "ranged": false, "options": false, "items": this.protections }, "shields": { "label": "BOL.combatCategory.shields", "weapon": false, "protection": false, "blocking": true, "ranged": false, "options": false, "items": this.shields }, "fightoptions": { "label": "BOL.combatCategory.fightOptions", "weapon": false, "protection": false, "blocking": false, "ranged": false, "options": true, "items": this.fightoptions } } } /*-------------------------------------------- */ buildRollList() { let rolls = [] for (let key in this.system.attributes) { let attr = this.system.attributes[key] rolls.push({ key: key, value: attr.value, name: attr.label, type: "attribute" }) } for (let key in this.system.aptitudes) { if (key != "def") { let apt = this.system.aptitudes[key] rolls.push({ key: key, value: apt.value, name: apt.label, type: "aptitude" }) } } return rolls } /*-------------------------------------------- */ buildListeActions() { return this.melee.concat(this.ranged).concat(this.natural).concat(this.fightoptions) } /*-------------------------------------------- */ async manageHealthState() { let hpID = "lastHP" + this.id let lastHP = await this.getFlag("world", hpID) if (lastHP != this.system.resources.hp.value && game.user.isGM) { // Only GM sends this await this.setFlag("world", hpID, this.system.resources.hp.value) let prone = this.effects.find(ef => ef.name == game.i18n.localize("EFFECT.StatusProne")) let dead = this.effects.find(ef => ef.name == game.i18n.localize("EFFECT.StatusDead")) if (this.system.resources.hp.value <= 0) { if (!prone) { await this.createEmbeddedDocuments("ActiveEffect", [ { name: game.i18n.localize('EFFECT.StatusProne'), icon: 'icons/svg/falling.svg', statuses: 'prone' } ]) } if (this.system.resources.hp.value < -5 && !dead) { await this.createEmbeddedDocuments("ActiveEffect", [ { name: game.i18n.localize('EFFECT.StatusDead'), icon: 'icons/svg/skull.svg', statuses: 'dead' } ]) } ChatMessage.create({ alias: this.name, whisper: BoLUtility.getWhisperRecipientsAndGMs(this.name), content: await renderTemplate('systems/bol/templates/chat/chat-vitality-zero.hbs', { name: this.name, img: this.img, hp: this.system.resources.hp.value, isHeroAdversary: this.isHeroAdversary() }) }) } else { if (prone) { await this.deleteEmbeddedDocuments("ActiveEffect", [prone.id]) } if (dead) { await this.deleteEmbeddedDocuments("ActiveEffect", [dead.id]) } } } } /*-------------------------------------------- */ async registerInit(rollData) { rollData.actor = undefined // Cleanup if present await this.setFlag("world", "last-initiative", rollData) } /*-------------------------------------------- */ storeVitaliteCombat() { this.setFlag("world", "vitalite-before-combat", foundry.utils.duplicate(this.system.resources.hp)) } /*-------------------------------------------- */ async displayRecuperation() { let previousHP = this.getFlag("world", "vitalite-before-combat") let lossHP = previousHP.value - this.system.resources.hp.value //console.log(">>>>> RECUP INFO", previousHP, this.system.resources.hp.value) if (previousHP && lossHP > 0 && this.system.resources.hp.value > 0) { let msg = await ChatMessage.create({ alias: this.name, whisper: BoLUtility.getWhisperRecipientsAndGMs(this.name), content: await renderTemplate('systems/bol/templates/chat/chat-recup-information.hbs', { name: this.name, img: this.img, actorId: this.id, lossHP: lossHP, recupHP: Math.ceil(lossHP / 2) }) }) } this.unsetFlag("world", "vitalite-before-combat") } /*-------------------------------------------- */ async applyRecuperation(recupHP) { let hp = foundry.utils.duplicate(this.system.resources.hp) //console.log("RECUP !!!!", hp, recupHP) hp.value += Number(recupHP) hp.value = Math.min(hp.value, hp.max) this.update({ 'system.resources.hp': hp }) let msg = await ChatMessage.create({ alias: this.name, whisper: BoLUtility.getWhisperRecipientsAndGMs(this.name), content: game.i18n.format("BOL.chat.inforecup", { name: this.name, recupHP: recupHP }) }) } /*-------------------------------------------- */ clearInitiative() { this.unsetFlag("world", "last-initiative") } /*-------------------------------------------- */ getSize() { if (this.system.details.size.length > 0 && game.bol.config.creatureSize[this.system.details.size]) { return game.bol.config.creatureSize[this.system.details.size].order } return game.bol.config.creatureSize["medium"].order // Medium size per default } /*-------------------------------------------- */ checkNumeric(myObject) { if (myObject) { for (let key in myObject) { if (myObject[key].value === null || isNaN(myObject[key].value)) { myObject[key].value = 0 } } } } /*-------------------------------------------- */ _preUpdate(data, options, userId) { if (data.system?.attributes) { this.checkNumeric(data.system.attributes) } if (data.system?.aptitudes) { this.checkNumeric(data.system.aptitudes) } if (data.system?.resources) { this.checkNumeric(data.system.resources) } // Apply changes in Horde size to Token width/height if (this.type == "horde") { if (data?.system?.hordesize) { // If horde size is changed} let newHP = data.system.hordesize * this.system.hordebasehp if (newHP != this.system.resources.hp.value) { data.system.resources = { hp: { value: newHP, max: newHP } } // Update HP } let tokenSize = this.getTokenSizeFromHordeSize(data?.system?.hordesize) if (this.isToken && (tokenSize !== this.token.width)) { this.token.update({ width: tokenSize, height: tokenSize }) } else { if (tokenSize && (tokenSize !== this.prototypeToken.width)) { if (!foundry.utils.hasProperty(data, "prototypeToken.width")) { data.prototypeToken ||= {}; data.prototypeToken.height = tokenSize; data.prototypeToken.width = tokenSize; } } } } if (data?.system?.hordebasehp) { let newHP = this.system.hordesize * data.system.hordebasehp if (newHP != this.system.resources.hp.value) { data.system.resources = { hp: { value: newHP, max: newHP } } } } } super._preUpdate(data, options, userId) } /*-------------------------------------------- */ getInitiativeRank(rollData = undefined, isCombat = false, combatData = undefined) { let fvttInit = 4 // Pietaille par defaut if (this.type == 'character') { fvttInit = 5 if (!rollData) { if (isCombat) { if (game.user.isGM) { if (this.hasPlayerOwner) { game.socket.emit("system.bol", { name: "msg_request_init_roll", data: { actorId: this.id, combatData } }) } else { BoLRoll.aptitudeCheck(this, "init", undefined, combatData); } } } } else { if (rollData.isLegendary) { fvttInit = 10 } else if (rollData.isCritical) { fvttInit = 9 } else if (rollData.isSuccess) { fvttInit = 8 } else if (rollData.isFumble) { fvttInit = 3 } } } if (this.getCharType() == 'adversary') { fvttInit = 7 } if (this.getCharType() == 'tough') { fvttInit = 6 } if (this.getCharType() == 'creature') { let mySize = this.getSize() let sizeSmall = game.bol.config.creatureSize["small"].order let sizeMedium = game.bol.config.creatureSize["medium"].order if (mySize >= sizeSmall && mySize <= sizeMedium) { fvttInit = 6 } if (mySize > sizeMedium) { fvttInit = 7 } } return fvttInit } /*-------------------------------------------- */ async subHeroPoints(nb) { let newHeroP = this.system.resources.hero.value - nb; newHeroP = (newHeroP < 0) ? 0 : newHeroP; await this.update({ 'system.resources.hero.value': newHeroP }); } /*-------------------------------------------- */ async addHeroPoints(nb) { let newHeroP = this.system.resources.hero.value + nb; newHeroP = (newHeroP < 0) ? 0 : newHeroP; await this.update({ 'system.resources.hero.value': newHeroP }); } /*-------------------------------------------- */ incDecResources(target, value) { let newValue = this.system.resources[target].value + value this.update({ [`system.resources.${target}.value`]: newValue }) } /*-------------------------------------------- */ async sufferDamage(damage) { let updates = {} let newHP = this.system.resources.hp.value - damage updates['system.resources.hp.value'] = newHP if (this.type == "horde") { let newSize = Math.ceil(newHP / this.system.hordebasehp) updates['system.hordesize'] = newSize } await this.update(updates) } /* -------------------------------------------- */ getArmorFormula() { let protectWorn = this.protections.filter(item => item.system.worn) let formula = "" for (let protect of protectWorn) { if (protect.system.subtype == 'helm') { formula += "+1" } else if (protect.system.subtype == 'armor') { if (BoLUtility.getRollArmor()) { if (!protect.system.properties.soak.formula || protect.system.properties.soak.formula == "") { ui.notifications.warn(game.i18n.localize("BOL.ui.armornoformula", protect.name)) } else { formula += "+" + " max(" + protect.system.properties.soak.formula + ",0)" } } else { if (protect.system.properties.soak.value == undefined) { ui.notifications.warn(game.i18n.localize("BOL.ui.armornoformula", protect.name)) } else { formula += "+ " + protect.system.properties.soak.value } } } } console.log("Protect Formula", formula) return (formula == "") ? "0" : formula; } /* -------------------------------------------- */ async rollProtection(itemId) { let armor = foundry.utils.duplicate(this.items.get(itemId)) if (armor) { let armorFormula = "max(" + armor.system.properties.soak.formula + ", 0)" let rollArmor = new Roll(armorFormula) await rollArmor.roll() rollArmor.toMessage() } } /* -------------------------------------------- */ rollWeaponDamage(itemId) { let weapon = foundry.utils.duplicate(this.items.get(itemId)) if (weapon) { let r = new BoLDefaultRoll({ id: foundry.utils.randomID(16), isSuccess: true, mode: "weapon", weapon: weapon, actorId: this.id, actor: this }) r.setSuccess(true) r.rollDamage() } } /* -------------------------------------------- */ toggleEquipItem(item) { const equipable = item.system.properties.equipable; if (equipable) { let itemData = foundry.utils.duplicate(item); itemData.system.worn = !itemData.system.worn; return item.update(itemData); } } }