/* -------------------------------------------- */ import { PegasusUtility } from "./pegasus-utility.js"; import { PegasusRollDialog } from "./pegasus-roll-dialog.js"; /* -------------------------------------------- */ const coverBonusTable = { "nocover": 0, "lightcover": 2, "heavycover": 4, "entrenchedcover": 6 }; const statThreatLevel = ["agi", "str", "phy", "com", "def", "per"] const __subkey2title = { "melee-dmg": "Melee Damage", "melee-atk": "Melee Attack", "ranged-atk": "Ranged Attack", "ranged-dmg": "Ranged Damage", "dmg-res": "Damare Resistance" } /* -------------------------------------------- */ /* -------------------------------------------- */ /** * Extend the base Actor entity by defining a custom roll data structure which is ideal for the Simple system. * @extends {Actor} */ export class PegasusActor extends Actor { /* -------------------------------------------- */ /** * Override the create() function to provide additional SoS functionality. * * This overrided create() function adds initial items * Namely: Basic skills, money, * * @param {Object} data Barebones actor data which this function adds onto. * @param {Object} options (Unused) Additional options which customize the creation workflow. * */ 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 == 'character') { } if (data.type == 'npc') { } return super.create(data, options); } /* -------------------------------------------- */ prepareBaseData() { } /* -------------------------------------------- */ async prepareData() { super.prepareData(); } /* -------------------------------------------- */ prepareDerivedData() { if (this.type == 'character') { this.computeNRGHealth(); this.data.data.encCapacity = this.getEncumbranceCapacity() this.buildContainerTree() } super.prepareDerivedData(); } /* -------------------------------------------- */ _preUpdate(changed, options, user) { super._preUpdate(changed, options, user); } /* -------------------------------------------- */ getEncumbranceCapacity() { return this.data.data.statistics.str.value * 25 } /* -------------------------------------------- */ getActivePerks() { let perks = this.data.items.filter(item => item.type == 'perk' && item.data.data.active); return perks; } /* -------------------------------------------- */ getAbilities() { let ab = this.data.items.filter(item => item.type == 'ability'); return ab; } /* -------------------------------------------- */ getPerks() { let comp = this.data.items.filter(item => item.type == 'perk'); return comp; } /* -------------------------------------------- */ getEffects() { let comp = this.data.items.filter(item => item.type == 'effect'); return comp; } /* -------------------------------------------- */ getPowers() { let comp = this.data.items.filter(item => item.type == 'power'); return comp; } /* -------------------------------------------- */ getMoneys() { let comp = this.data.items.filter(item => item.type == 'money'); return comp; } /* -------------------------------------------- */ getVirtues() { let comp = this.data.items.filter(item => item.type == 'virtue'); return comp; } /* -------------------------------------------- */ getVices() { let comp = this.data.items.filter(item => item.type == 'vice'); return comp; } /* -------------------------------------------- */ getArmors() { let comp = duplicate(this.data.items.filter(item => item.type == 'armor') || []); return comp; } /* -------------------------------------------- */ getShields() { let comp = this.data.items.filter(item => item.type == 'shield') return comp; } getRace() { let race = this.data.items.filter(item => item.type == 'race') return race[0] ?? []; } getRole() { let role = this.data.items.filter(item => item.type == 'role') return role[0] ?? []; } /* -------------------------------------------- */ checkAndPrepareEquipment(item) { if (item.data.resistance) { item.data.resistanceDice = PegasusUtility.getDiceFromLevel(item.data.resistance) } if (item.data.idr) { item.data.idrDice = PegasusUtility.getDiceFromLevel(item.data.idr) } if (item.data.damage) { item.data.damageDice = PegasusUtility.getDiceFromLevel(item.data.damage) } if (item.data.level) { item.data.levelDice = PegasusUtility.getDiceFromLevel(item.data.level) } } /* -------------------------------------------- */ checkAndPrepareEquipments(listItem) { for (let item of listItem) { this.checkAndPrepareEquipment(item) } return listItem } /* -------------------------------------------- */ getWeapons() { let comp = duplicate(this.data.items.filter(item => item.type == 'weapon') || []); return comp; } /* -------------------------------------------- */ getItemById(id) { let item = this.data.items.find(item => item.id == id); if (item) { item = duplicate(item) if (item.type == 'specialisation') { item.data.dice = PegasusUtility.getDiceFromLevel(item.data.level); } } return item; } /* -------------------------------------------- */ getSpecs() { let comp = duplicate(this.data.items.filter(item => item.type == 'specialisation') || []); for (let c of comp) { c.data.dice = PegasusUtility.getDiceFromLevel(c.data.level); } return comp; } /* -------------------------------------------- */ async manageWorstFear(flag) { if (flag) { let effect = await PegasusUtility.getEffectFromCompendium("Worst Fear") effect.data.worstfear = true this.createEmbeddedDocuments('Item', [effect]) } else { let effect = this.data.items.find(item => item.type == "effect" && item.data.data.worstfear) if (effect) { this.deleteEmbeddedDocuments('Item', [effect.id]) } } } /* -------------------------------------------- */ async manageDesires(flag) { if (flag) { let effect = await PegasusUtility.getEffectFromCompendium("Desires") effect.data.desires = true this.createEmbeddedDocuments('Item', [effect]) } else { let effect = this.data.items.find(item => item.type == "effect" && item.data.data.desires) if (effect) { this.deleteEmbeddedDocuments('Item', [effect.id]) } } } /* -------------------------------------------- */ getRelevantSpec(statKey) { let comp = duplicate(this.data.items.filter(item => item.type == 'specialisation' && item.data.data.statistic == statKey) || []); for (let c of comp) { c.data.dice = PegasusUtility.getDiceFromLevel(c.data.level); } return comp; } /* -------------------------------------------- */ async activatePerk(perkId) { let item = this.data.items.find(item => item.id == perkId); if (item && item.data.data) { let update = { _id: item.id, "data.active": !item.data.data.active }; await this.updateEmbeddedDocuments('Item', [update]); // Updates one EmbeddedEntity } } /* -------------------------------------------- */ async activateViceOrVirtue(itemId) { let item = this.data.items.find(item => item.id == itemId) if (item && item.data.data) { let nrg = duplicate(this.data.data.nrg) if (!item.data.data.activated) { // Current value let effects = [] for (let effect of item.data.data.effectsgained) { effect.data.powerId = itemId // Link to the perk, in order to dynamically remove them effects.push(effect) } if (effects.length) { await this.createEmbeddedDocuments('Item', effects) } } else { let toRem = [] for (let item of this.data.items) { if (item.type == 'effect' && item.data.data.powerId == itemId) { toRem.push(item.id) } } if (toRem.length) { await this.deleteEmbeddedDocuments('Item', toRem) } } let update = { _id: item.id, "data.activated": !item.data.data.activated } await this.updateEmbeddedDocuments('Item', [update]) // Updates one EmbeddedEntity } } /* -------------------------------------------- */ async activatePower(itemId) { let item = this.data.items.find(item => item.id == itemId) if (item && item.data.data) { let nrg = duplicate(this.data.data.nrg) if (!item.data.data.activated) { // Current value if (item.data.data.costspent > nrg.value || item.data.data.costspent > nrg.max) { return ui.notifications.warn("Not enough NRG to activate the Power " + item.name) } nrg.activated += item.data.data.costspent nrg.value -= item.data.data.costspent nrg.max -= item.data.data.costspent await this.update({ 'data.nrg': nrg }) let effects = [] for (let effect of item.data.data.effectsgained) { effect.data.powerId = itemId // Link to the perk, in order to dynamically remove them effects.push(effect) } if (effects.length) { await this.createEmbeddedDocuments('Item', effects) } } else { nrg.activated -= item.data.data.costspent nrg.max += item.data.data.costspent await this.update({ 'data.nrg': nrg }) let toRem = [] for (let item of this.data.items) { if (item.type == 'effect' && item.data.data.powerId == itemId) { toRem.push(item.id) } } if (toRem.length) { await this.deleteEmbeddedDocuments('Item', toRem) } } let update = { _id: item.id, "data.activated": !item.data.data.activated } await this.updateEmbeddedDocuments('Item', [update]) // Updates one EmbeddedEntity } } /* -------------------------------------------- */ async equipItem(itemId) { let item = this.data.items.find(item => item.id == itemId); if (item && item.data.data) { let update = { _id: item.id, "data.equipped": !item.data.data.equipped }; await this.updateEmbeddedDocuments('Item', [update]); // Updates one EmbeddedEntity } } /* -------------------------------------------- */ compareName(a, b) { if (a.name < b.name) { return -1; } if (a.name > b.name) { return 1; } return 0; } /* ------------------------------------------- */ getEquipments() { return this.data.items.filter(item => item.type == 'shield' || item.type == 'armor' || item.type == "weapon" || item.type == "equipment"); } /* ------------------------------------------- */ getEquipmentsOnly() { return duplicate(this.data.items.filter(item => item.type == "equipment") || []) } /* ------------------------------------------- */ computeThreatLevel() { let tl = 0 for (let key of statThreatLevel) { // Init with concerned stats tl += PegasusUtility.getDiceValue(this.data.data.statistics[key].value) } let powers = duplicate(this.getPowers() || []) if (powers.length > 0) { // Then add some mental ones of powers tl += PegasusUtility.getDiceValue(this.data.data.statistics.foc.value) tl += PegasusUtility.getDiceValue(this.data.data.statistics.mnd.value) } tl += PegasusUtility.getDiceValue(this.data.data.mr.value) let specThreat = this.data.items.filter(it => it.type == "specialisation" && it.data.data.isthreatlevel) || [] for (let spec of specThreat) { tl += PegasusUtility.getDiceValue(spec.data.data.level) } tl += this.data.data.nrg.absolutemax + this.data.data.secondary.health.max + this.data.data.secondary.delirium.max tl += this.getPerks().length * 5 let weapons = this.getWeapons() for (let weapon of weapons) { tl += PegasusUtility.getDiceValue(weapon.data.damage) } let armors = this.getArmors() for (let armor of armors) { tl += PegasusUtility.getDiceValue(armor.data.resistance) } let shields = duplicate(this.getShields()) for (let shield of shields) { tl += PegasusUtility.getDiceValue(shield.data.level) } let abilities = duplicate(this.getAbilities()) for (let ability of abilities) { tl += ability.data.threatlevel } let equipments = this.getEquipmentsOnly() for (let equip of equipments) { tl += equip.data.threatlevel } if (tl != this.data.data.biodata.threatlevel) { this.update({ 'data.biodata.threatlevel': tl }) } } /* ------------------------------------------- */ async buildContainerTree() { let equipments = duplicate(this.data.items.filter(item => item.type == "equipment") || []) for (let equip1 of equipments) { if (equip1.data.iscontainer) { equip1.data.contents = [] equip1.data.contentsEnc = 0 for (let equip2 of equipments) { if (equip1._id != equip2._id && equip2.data.containerid == equip1._id) { equip1.data.contents.push(equip2) let q = equip2.data.quantity ?? 1 equip1.data.contentsEnc += q * equip2.data.weight } } } } // Compute whole enc let enc = 0 for (let item of equipments) { item.data.idrDice = PegasusUtility.getDiceFromLevel(Number(item.data.idr)) if (item.data.equipped) { if (item.data.iscontainer) { enc += item.data.contentsEnc } else if (item.data.containerid == "") { let q = item.data.quantity ?? 1 enc += q * item.data.weight } } } for (let item of this.data.items) { // Process items/shields/armors if ((item.type == "weapon" || item.type == "shield" || item.type == "armor") && item.data.data.equipped) { let q = item.data.data.quantity ?? 1 enc += q * item.data.data.weight } } // Store local values this.encCurrent = enc this.containersTree = equipments.filter(item => item.data.containerid == "") // Returns the root of equipements without container // Manages slow effect let overCapacity = Math.floor(this.encCurrent / this.getEncumbranceCapacity()) this.encHindrance = Math.floor(this.encCurrent / this.getEncumbranceCapacity()) //console.log("Capacity", overCapacity, this.encCurrent / this.getEncumbranceCapacity() ) let effect = this.data.items.find(item => item.type == "effect" && item.data.data.slow) if (overCapacity >= 4) { if (!effect) { effect = await PegasusUtility.getEffectFromCompendium("Slowed") effect.data.slow = true this.createEmbeddedDocuments('Item', [effect]) } } else { if (effect) { this.deleteEmbeddedDocuments('Item', [effect.id]) } } } /* -------------------------------------------- */ modifyStun(incDec) { let combat = duplicate(this.data.data.combat) combat.stunlevel += incDec if (combat.stunlevel >= 0) { this.update({ 'data.combat': combat }) let chatData = { user: game.user.id, rollMode: game.settings.get("core", "rollMode"), whisper: [game.user.id].concat(ChatMessage.getWhisperRecipients('GM')) } if (incDec > 0) { chatData.content = `
${this.name} suffered a Stun level.${this.name} recovered a Stun level. 0 && stunAbove > 0) { let delirium = duplicate(this.data.data.secondary.delirium) delirium.value -= incDec this.update({ 'data.secondary.delirium': delirium }) } } /* -------------------------------------------- */ modifyMomentum(incDec) { let momentum = duplicate(this.data.data.momentum) momentum.value += incDec this.update({ 'data.momentum': momentum }) let chatData = { user: game.user.id, rollMode: game.settings.get("core", "rollMode"), whisper: [game.user.id].concat(ChatMessage.getWhisperRecipients('GM')) } if (incDec > 0) { chatData.content = `
${this.name} has gained a Momentum${this.name} has used a Momentum= 0) { PegasusUtility.showMomentumDialog(this.id) } } /* -------------------------------------------- */ getActiveEffects(matching = it => true) { let array = Array.from(this.getEmbeddedCollection("ActiveEffect").values()); return Array.from(this.getEmbeddedCollection("ActiveEffect").values()).filter(it => matching(it)); } /* -------------------------------------------- */ getEffectByLabel(label) { return this.getActiveEffects().find(it => it.data.label == label); } /* -------------------------------------------- */ getEffectById(id) { return this.getActiveEffects().find(it => it.id == id); } /* -------------------------------------------- */ getAttribute(attrKey) { return this.data.data.attributes[attrKey]; } /* -------------------------------------------- */ async addObjectToContainer(itemId, containerId) { let container = this.data.items.find(item => item.id == containerId && item.data.data.iscontainer) let object = this.data.items.find(item => item.id == itemId) if (container) { if (object.data.data.iscontainer) { ui.notifications.warn("Only 1 level of container allowed") return } let alreadyInside = this.data.items.filter(item => item.data.data.containerid && item.data.data.containerid == containerId); if (alreadyInside.length >= container.data.data.containercapacity) { ui.notifications.warn("Container is already full !") return } else { await this.updateEmbeddedDocuments("Item", [{ _id: object.id, 'data.containerid': containerId }]) } } else if (object && object.data.data.containerid) { // remove from container console.log("Removeing: ", object) await this.updateEmbeddedDocuments("Item", [{ _id: object.id, 'data.containerid': "" }]); } } /* -------------------------------------------- */ checkVirtue(virtue) { let vices = this.getVices() for (let vice of vices) { let nonVirtues = vice.data.data.unavailablevirtue for (let blockedVirtue of nonVirtues) { if (blockedVirtue.name.toLowerCase() == virtue.name.toLowerCase()) { return false } } } return true } /* -------------------------------------------- */ checkVice(vice) { let virtues = this.getVirtues() for (let virtue of virtues) { let nonVices = virtue.data.data.unavailablevice for (let blockedVice of nonVices) { if (blockedVice.name.toLowerCase() == vice.name.toLowerCase()) { return false } } } return true } /* -------------------------------------------- */ async preprocessItem(event, item, onDrop = false) { if (item.data.type == 'race') { this.applyRace(item.data) } else if (item.data.type == 'role') { this.applyRole(item.data) } else if (item.data.type == 'ability') { this.applyAbility(item.data, [], true) if (!onDrop) { await this.createEmbeddedDocuments('Item', [item.data]) } } else { if (!onDrop) { await this.createEmbeddedDocuments('Item', [item.data]) } } // Check virtue/vice validity if (item.data.type == "virtue") { if (!this.checkVirtue(item)) { ui.notifications.info("Virtue is not allowed due to Vice.") return false } } if (item.data.type == "vice") { if (!this.checkVice(item)) { ui.notifications.info("Vice is not allowed due to Virtue.") return false } } let dropID = $(event.target).parents(".item").attr("data-item-id") // Only relevant if container drop let objectID = item.id || item._id this.addObjectToContainer(objectID, dropID) return true } /* -------------------------------------------- */ async equipGear(equipmentId) { let item = this.data.items.find(item => item.id == equipmentId); if (item && item.data.data) { let update = { _id: item.id, "data.equipped": !item.data.data.equipped }; await this.updateEmbeddedDocuments('Item', [update]); // Updates one EmbeddedEntity } } /* -------------------------------------------- */ getInitiativeScore(combatId, combatantId) { if (this.type == 'character') { this.rollMR(true, combatId, combatantId) } console.log("Init required !!!!") return -1; } /* -------------------------------------------- */ getSubActors() { let subActors = []; for (let id of this.data.data.subactors) { subActors.push(duplicate(game.actors.get(id))) } return subActors; } /* -------------------------------------------- */ async addSubActor(subActorId) { let subActors = duplicate(this.data.data.subactors); subActors.push(subActorId); await this.update({ 'data.subactors': subActors }); } /* -------------------------------------------- */ async delSubActor(subActorId) { let newArray = []; for (let id of this.data.data.subactors) { if (id != subActorId) { newArray.push(id); } } await this.update({ 'data.subactors': newArray }); } /* -------------------------------------------- */ syncRoll(rollData) { let linkedRollId = PegasusUtility.getDefenseState(this.id); if (linkedRollId) { rollData.linkedRollId = linkedRollId; } this.lastRollId = rollData.rollId; PegasusUtility.saveRollData(rollData); } /* -------------------------------------------- */ getStat(statKey) { let stat if (statKey == 'mr') { stat = duplicate(this.data.data.mr); } else { stat = duplicate(this.data.data.statistics[statKey]); } stat.dice = PegasusUtility.getDiceFromLevel(stat.value); return stat; } /* -------------------------------------------- */ getOneSpec(specId) { let spec = this.data.items.find(item => item.type == 'specialisation' && item.id == specId) if (spec) { spec = duplicate(spec); spec.data.dice = PegasusUtility.getDiceFromLevel(spec.data.level); } return spec; } /* -------------------------------------------- */ specPowerActivate(specId) { let spec = this.getOneSpec(specId) if (spec) { let powers = [] for (let power of spec.data.powers) { power.data.specId = specId powers.push(power) } if (powers.length > 0) { this.createEmbeddedDocuments('Item', powers) } this.updateEmbeddedDocuments('Item', [{ _id: specId, 'data.powersactivated': true }]) } } /* -------------------------------------------- */ specPowerDeactivate(specId) { let toRem = [] for (let power of this.data.items) { if (power.type == "power" && power.data.data.specId && power.data.data.specId == specId) { toRem.push(power.id) } } if (toRem.length > 0) { this.deleteEmbeddedDocuments('Item', toRem) } this.updateEmbeddedDocuments('Item', [{ _id: specId, 'data.powersactivated': false }]) } /* -------------------------------------------- */ equipActivate(itemId) { let item = this.items.get(itemId) if (item) { let effects = [] for (let effect of item.data.data.effects) { effect.data.itemId = itemId // Keep link effects.push(effect) } if (effects.length > 0) { this.createEmbeddedDocuments('Item', effects) } this.updateEmbeddedDocuments('Item', [{ _id: itemId, 'data.activated': true }]) } } /* -------------------------------------------- */ equipDeactivate(itemId) { let toRem = [] for (let item of this.data.items) { if (item.data.data.itemId && item.data.data.itemId == itemId) { toRem.push(item.id) } } if (toRem.length > 0) { this.deleteEmbeddedDocuments('Item', toRem) } this.updateEmbeddedDocuments('Item', [{ _id: itemId, 'data.activated': false }]) } /* -------------------------------------------- */ async perkEffectUsed(itemId) { let effect = this.items.get(itemId) if (effect) { PegasusUtility.createChatWithRollMode(effect.name, { content: await renderTemplate(`systems/fvtt-pegasus-rpg/templates/chat-effect-used.html`, effect.data) }); this.deleteEmbeddedDocuments('Item', [effect.id]) } } /* -------------------------------------------- */ disableWeaverPerk(perk) { if (perk.data.data.isweaver) { for (let spec of this.data.items) { if (spec.type == 'specialisation' && spec.data.data.ispowergroup) { this.specPowerDeactivate(spec.id) } } } } /* -------------------------------------------- */ enableWeaverPerk(perk) { if (perk.data.data.isweaver) { for (let spec of this.data.items) { if (spec.type == 'specialisation' && spec.data.data.ispowergroup) { this.specPowerActivate(spec.id) } } } } /* -------------------------------------------- */ async cleanPerkEffects(itemId) { let effects = [] for (let item of this.data.items) { if (item.type == "effect" && item.data.data.perkId == itemId) { effects.push(item.id) } } if (effects.length > 0) { console.log("DELET!!!!", effects, this) await this.deleteEmbeddedDocuments('Item', effects) } } /* -------------------------------------------- */ async updatePerkUsed(itemId, index, checked) { let item = this.items.get(itemId) if (item && index) { let key = "data.used" + index await this.updateEmbeddedDocuments('Item', [{ _id: itemId, [`${key}`]: checked }]) item = this.items.get(itemId) // Refresh if (item.data.data.nbuse == "next1action" && item.data.data.used1) { this.cleanPerkEffects(itemId) } if (item.data.data.nbuse == "next2action" && item.data.data.used1 && item.data.data.used2) { this.cleanPerkEffects(itemId) } if (item.data.data.nbuse == "next3action" && item.data.data.used1 && item.data.data.used2 && item.data.data.used3) { this.cleanPerkEffects(itemId) } } } /* -------------------------------------------- */ async updatePowerSpentCost(itemId, value) { let item = this.items.get(itemId) if (item && value) { value = Number(value) || 0 await this.updateEmbeddedDocuments('Item', [{ _id: itemId, 'data.costspent': value }]) } } /* -------------------------------------------- */ async cleanupPerksIfTrauma() { if (this.getTraumaState() == "severetrauma") { for (let perk of this.data.items) { if (perk.type == "perk") { this.cleanPerkEffects(perk.id) this.updateEmbeddedDocuments('Item', [{ _id: perk.id, 'data.status': "ready", 'data.used1': false, 'data.used2': false, 'data.used3': false }]) ChatMessage.create({ content: `Perk ${perk.name} has been deactivated due to Severe Trauma state !` }) } } } } /* -------------------------------------------- */ incDecNRG(value) { let nrg = duplicate(this.data.data.nrg) nrg.value += value if (nrg.value >= 0 && nrg.value <= nrg.max) { this.update({ 'data.nrg': nrg }) } } /* -------------------------------------------- */ async updatePerkStatus(itemId, status) { let item = this.items.get(itemId) if (item) { if (item.data.data.status == status) return;// Ensure we are really changing the status // Severe Trauma management if (this.getTraumaState() == "severetrauma") { if ( !this.severeTraumaMessage) { let chatData = { user: game.user.id, rollMode: game.settings.get("core", "rollMode"), whisper: [game.user.id].concat(ChatMessage.getWhisperRecipients('GM')) } chatData.content = `
${this.name} is suffering from Severe Trauma and cannot use Perks at this time.= item.data.data.features.nrgcost.value) && (this.data.data.nrg.max >= item.data.data.features.nrgcost.value)) { let nrg = duplicate(this.data.data.nrg) nrg.activated += item.data.data.features.nrgcost.value nrg.value -= item.data.data.features.nrgcost.value nrg.max -= item.data.data.features.nrgcost.value await this.update({ 'data.nrg': nrg }) } else { updateOK = false ui.notifications.warn("Not enough NRG to activate the Perk " + item.name) } } if (item.data.data.features.bonushealth.flag) { let health = duplicate(this.data.data.secondary.health) health.value += Number(item.data.data.features.bonushealth.value) || 0 health.max += Number(item.data.data.features.bonushealth.value) || 0 await this.update({ 'data.secondary.health': health }) } if (item.data.data.features.bonusdelirium.flag) { let delirium = duplicate(this.data.data.secondary.delirium) delirium.value += Number(item.data.data.features.bonusdelirium.value) || 0 delirium.max += Number(item.data.data.features.bonusdelirium.value) || 0 await this.update({ 'data.secondary.delirium': delirium }) } if (item.data.data.features.bonusnrg.flag) { let nrg = duplicate(this.data.data.nrg) nrg.value += Number(item.data.data.features.bonusnrg.value) || 0 nrg.max += Number(item.data.data.features.bonusnrg.value) || 0 await this.update({ 'data.nrg': nrg }) } PegasusUtility.createChatWithRollMode(item.name, { content: await renderTemplate(`systems/fvtt-pegasus-rpg/templates/chat-perk-activated.html`, { name: this.name, perk: item }) }) this.enableWeaverPerk(item) } if (updateOK) { await this.updateEmbeddedDocuments('Item', [{ _id: item.id, 'data.status': status }]) } } } /* -------------------------------------------- */ async deleteAllItemsByType(itemType) { let items = this.data.items.filter(item => item.type == itemType); await this.deleteEmbeddedDocuments('Item', items); } /* -------------------------------------------- */ async addItemWithoutDuplicate(newItem) { let item = this.data.items.find(item => item.type == newItem.type && item.name.toLowerCase() == newItem.name.toLowerCase()) if (!item) { await this.createEmbeddedDocuments('Item', [newItem]); } } /* -------------------------------------------- */ getTraumaState() { this.traumaState = "none" let negDelirium = -Math.floor((this.data.data.secondary.delirium.max + 1) / 2) if (this.type == "character") { if (this.data.data.secondary.delirium.value <= 0 && this.data.data.secondary.delirium.value >= negDelirium) { this.traumaState = "trauma" } if (this.data.data.secondary.delirium.value < negDelirium) { this.traumaState = "severetrauma" } } return this.traumaState } /* -------------------------------------------- */ getLevelRemaining() { return this.data.data.biodata.currentlevelremaining } /* -------------------------------------------- */ modifyHeroLevelRemaining(incDec) { let biodata = duplicate(this.data.data.biodata) biodata.currentlevelremaining = Math.max(biodata.currentlevelremaining + incDec, 0) this.update({ "data.biodata": biodata }) ChatMessage.create({ content: `${this.name} has used a Hero Level to reroll !` }) return biodata.currentlevelremaining } /* -------------------------------------------- */ async computeNRGHealth() { if (this.isOwner || game.user.isGM) { let updates = {} let phyDiceValue = PegasusUtility.getDiceValue(this.data.data.statistics.phy.value) + this.data.data.secondary.health.bonus + this.data.data.statistics.phy.mod; if (phyDiceValue != this.data.data.secondary.health.max) { updates['data.secondary.health.max'] = phyDiceValue } if (this.computeValue) { updates['data.secondary.health.value'] = phyDiceValue } let mndDiceValue = PegasusUtility.getDiceValue(this.data.data.statistics.mnd.value) + this.data.data.secondary.delirium.bonus + this.data.data.statistics.mnd.mod; if (mndDiceValue != this.data.data.secondary.delirium.max) { updates['data.secondary.delirium.max'] = mndDiceValue } if (this.computeValue) { updates['data.secondary.delirium.value'] = mndDiceValue } let stlDiceValue = PegasusUtility.getDiceValue(this.data.data.statistics.stl.value) + this.data.data.secondary.stealthhealth.bonus + this.data.data.statistics.stl.mod; if (stlDiceValue != this.data.data.secondary.stealthhealth.max) { updates['data.secondary.stealthhealth.max'] = stlDiceValue } if (this.computeValue) { updates['data.secondary.stealthhealth.value'] = stlDiceValue } let socDiceValue = PegasusUtility.getDiceValue(this.data.data.statistics.soc.value) + this.data.data.secondary.socialhealth.bonus + this.data.data.statistics.soc.mod; if (socDiceValue != this.data.data.secondary.socialhealth.max) { updates['data.secondary.socialhealth.max'] = socDiceValue } if (this.computeValue) { updates['data.secondary.socialhealth.value'] = socDiceValue } let nrgValue = PegasusUtility.getDiceValue(this.data.data.statistics.foc.value) + this.data.data.nrg.mod + this.data.data.statistics.foc.mod if (nrgValue != this.data.data.nrg.absolutemax) { updates['data.nrg.absolutemax'] = nrgValue } if (this.computeValue) { updates['data.nrg.max'] = nrgValue updates['data.nrg.value'] = nrgValue } nrgValue = PegasusUtility.getDiceValue(this.data.data.statistics.mnd.value) + this.data.data.statistics.mnd.mod; if (nrgValue != this.data.data.combat.stunthreshold) { updates['data.combat.stunthreshold'] = nrgValue } let momentum = this.data.data.statistics.foc.value + this.data.data.statistics.foc.mod if (momentum != this.data.data.momentum.max) { updates['data.momentum.value'] = 0 updates['data.momentum.max'] = momentum } let mrLevel = (this.data.data.statistics.agi.value + this.data.data.statistics.str.value) - this.data.data.statistics.phy.value mrLevel = (mrLevel < 1) ? 1 : mrLevel; if (mrLevel != this.data.data.mr.value) { updates['data.mr.value'] = mrLevel } let moralitythreshold = - (Number(PegasusUtility.getDiceValue(this.data.data.statistics.foc.value)) + Number(this.data.data.statistics.foc.mod)) if (moralitythreshold != this.data.data.biodata.moralitythreshold) { updates['data.biodata.moralitythreshold'] = moralitythreshold } if ( !this.isToken) { if (this.warnMorality != this.data.data.biodata.morality && this.data.data.biodata.morality < 0) { console.log("CHAR", this) ChatMessage.create({ content: "WARNING: Your character is dangerously close to becoming corrupted and defeated. Start on a path of redemption!" }) } if (this.warnMorality != this.data.data.biodata.morality) { this.warnMorality = this.data.data.biodata.morality } } let race = this.getRace() if (race && race.name && (race.name != this.data.data.biodata.racename)) { updates['data.biodata.racename'] = race.name } let role = this.getRole() if (role && role.name && (role.name != this.data.data.biodata.rolename)) { updates['data.biodata.rolename'] = role.name } if (Object.entries(updates).length > 0) { await this.update(updates) this.computeThreatLevel() } } if (this.isOwner || game.user.isGM) { // Update current hindrance level let hindrance = this.data.data.combat.hindrancedice if (this.data.data.secondary.health.value < 0) { if (this.data.data.secondary.health.value < -Math.floor((this.data.data.secondary.health.max + 1) / 2)) { // Severe wounded hindrance += 3 } else { hindrance += 1 } } this.data.data.combat.hindrancedice = hindrance this.getTraumaState() this.cleanupPerksIfTrauma() } } /* -------------------------------------------- */ async modStat(key, inc = 1) { let stat = duplicate(this.data.data.statistics[key]) stat.mod += parseInt(inc) await this.update({ [`data.statistics.${key}`]: stat }) } /* -------------------------------------------- */ async valueStat(key, inc = 1) { key = key.toLowerCase() let stat = duplicate(this.data.data.statistics[key]) stat.value += parseInt(inc) await this.update({ [`data.statistics.${key}`]: stat }) } /* -------------------------------------------- */ async addIncSpec(spec, inc = 1) { console.log("Using spec : ", spec, inc) let specExist = this.data.items.find(item => item.type == 'specialisation' && item.name.toLowerCase() == spec.name.toLowerCase()) if (specExist) { specExist = duplicate(specExist) specExist.data.level += inc; let update = { _id: specExist._id, "data.level": specExist.data.level }; await this.updateEmbeddedDocuments('Item', [update]); } else { spec.data.level += inc; await this.createEmbeddedDocuments('Item', [spec]); } } /* -------------------------------------------- */ async incDecQuantity(objetId, incDec = 0) { let objetQ = this.data.items.get(objetId) if (objetQ) { let newQ = objetQ.data.data.quantity + incDec if (newQ >= 0) { const updated = await this.updateEmbeddedDocuments('Item', [{ _id: objetQ.id, 'data.quantity': newQ }]) // pdates one EmbeddedEntity } } } /* -------------------------------------------- */ async incDecAmmo(objetId, incDec = 0) { let objetQ = this.data.items.get(objetId) if (objetQ) { let newQ = objetQ.data.data.ammocurrent + incDec; if (newQ >= 0 && newQ <= objetQ.data.data.ammomax) { const updated = await this.updateEmbeddedDocuments('Item', [{ _id: objetQ.id, 'data.ammocurrent': newQ }]); // pdates one EmbeddedEntity } } } /* -------------------------------------------- */ async applyAbility(ability, updates = [], directUpdate = false) { // manage stat bonus if (ability.data.affectedstat != "notapplicable") { let stat = duplicate(this.data.data.statistics[ability.data.affectedstat]) stat.mod += Number(ability.data.statmodifier) updates[`data.statistics.${ability.data.affectedstat}`] = stat } // manage status bonus if (ability.data.statusaffected != "notapplicable") { if (ability.data.statusaffected == 'nrg') { let nrg = duplicate(this.data.data.nrg) nrg.mod += Number(ability.data.statusmodifier) updates[`data.nrg`] = nrg } if (ability.data.statusaffected == 'health') { let health = duplicate(this.data.data.secondary.health) health.bonus += Number(ability.data.statusmodifier) updates[`data.secondary.health`] = health } if (ability.data.statusaffected == 'delirium') { let delirium = duplicate(this.data.data.secondary.delirium) delirium.bonus += Number(ability.data.statusmodifier) updates[`data.secondary.delirium`] = delirium } } if (directUpdate) { await this.update(updates) } let newItems = [] if (ability.data.effectsgained) { for (let effect of ability.data.effectsgained) { newItems.push(effect); } } if (ability.data.powersgained) { for (let power of ability.data.powersgained) { newItems.push(power); } } if (ability.data.specialisations) { for (let spec of ability.data.specialisations) { newItems.push(spec); } } if (ability.data.attackgained) { for (let weapon of ability.data.attackgained) { newItems.push(weapon); } } if (ability.data.armorgained) { for (let armor of ability.data.armorgained) { newItems.push(armor); } } console.log("Ability : adding", newItems) await this.createEmbeddedDocuments('Item', newItems) } /* -------------------------------------------- */ async applyRace(race) { let updates = { 'data.biodata.racename': race.name } let newItems = [] await this.deleteAllItemsByType('race') newItems.push(race); for (let ability of race.data.abilities) { newItems.push(ability) this.applyAbility(ability, updates) } if (race.data.perksgained) { for (let power of race.data.perks) { newItems.push(power); } } await this.update(updates) await this.createEmbeddedDocuments('Item', newItems) console.log("Updates", updates, newItems) console.log("Updated actor", this) } /* -------------------------------------------- */ getIncreaseStatValue(updates, statKey) { let stat = duplicate(this.data.data.statistics[statKey]) stat.value += 1; updates[`data.statistics.${statKey}`] = stat } /* -------------------------------------------- */ async applyRole(role) { console.log("ROLE", role) let updates = { 'data.biodata.rolename': role.name } let newItems = [] await this.deleteAllItemsByType('role') newItems.push(role) this.getIncreaseStatValue(updates, role.data.statincrease1) this.getIncreaseStatValue(updates, role.data.statincrease2) if (role.data.specialability.length > 0) { console.log("Adding ability", role.data.specialability) newItems = newItems.concat(duplicate(role.data.specialability)) // Add new ability this.applyAbility(role.data.specialability[0], newItems) } await this.update(updates) await this.createEmbeddedDocuments('Item', newItems) } /* -------------------------------------------- */ addHindrancesList(effectsList) { if (this.data.data.combat.stunlevel > 0) { effectsList.push({ label: "Stun Hindrance", type: "hindrance", foreign: true, actorId: this.id, applied: false, value: this.data.data.combat.stunlevel }) } if (this.data.data.combat.hindrancedice > 0) { effectsList.push({ label: "Wounds Hindrance", type: "hindrance", foreign: true, actorId: this.id, applied: false, value: this.data.data.combat.hindrancedice }) } let overCapacity = Math.floor(this.encCurrent / this.getEncumbranceCapacity()) if (overCapacity > 0) { effectsList.push({ label: "Encumbrance Hindrance", type: "hindrance", foreign: true, actorId: this.id, applied: false, value: overCapacity }) } if (this.data.data.biodata.morality <= 0) { effectsList.push({ label: "Morality Hindrance", type: "hindrance", foreign: true, actorId: this.id, applied: false, value: 3 }) } let effects = this.data.items.filter(item => item.type == 'effect') for (let effect of effects) { effect = duplicate(effect) if (effect.data.hindrance) { effectsList.push({ label: effect.name, type: "effect", foreign: true, actorId: this.id, applied: false, effect: effect, value: effect.data.effectlevel }) } } } /* -------------------------------------------- */ /* ROLL SECTION /* -------------------------------------------- */ /* -------------------------------------------- */ addEffects(rollData) { let effects = this.data.items.filter(item => item.type == 'effect') for (let effect of effects) { effect = duplicate(effect) if (!effect.data.hindrance && (effect.data.stataffected != "notapplicable" || effect.data.specaffected.length > 0) && effect.data.stataffected != "special") { if (effect.data.effectstatlevel) { effect.data.effectlevel = this.data.data.statistics[effect.data.effectstat].value } if (this.getTraumaState() == "none") { rollData.effectsList.push({ label: effect.name, type: "effect", applied: false, effect: effect, value: effect.data.effectlevel }) } else { if (!effect.data.bonusdice) { // Do not push bonus dice effect when TraumaState is activated rollData.effectsList.push({ label: effect.name, type: "effect", applied: false, effect: effect, value: effect.data.effectlevel }) } } } } } /* -------------------------------------------- */ addArmorsShields(rollData, statKey = "none", useShield = false) { if (statKey == 'phy') { let armors = this.getArmors() for (let armor of armors) { rollData.armorsList.push({ label: `Armor ${armor.name}`, type: "armor", applied: false, value: armor.data.resistance }) } } if (useShield) { let shields = this.data.items.filter(item => item.type == "shield" && item.data.data.equipped) for (let sh of shields) { rollData.armorsList.push({ label: `Shield ${sh.name}`, type: "shield", applied: false, value: sh.data.data.level }) } } } addWeapons(rollData, statKey) { let weapons = this.getWeapons() for (let weapon of weapons) { if (weapon.data.equipped && weapon.data.statistic == statKey) { rollData.weaponsList.push({ label: `Attack ${weapon.name}`, type: "attack", applied: false, weapon: weapon, value: 0, damageDice: PegasusUtility.getDiceFromLevel(0) }) } if (weapon.data.equipped && weapon.data.enhanced && weapon.data.enhancedstat == statKey) { rollData.weaponsList.push({ label: `Enhanced Attack ${weapon.name}`, type: "enhanced", applied: false, weapon: weapon, value: weapon.data.enhancedlevel, damageDice: PegasusUtility.getDiceFromLevel(weapon.data.enhancedlevel) }) } if (weapon.data.equipped && weapon.data.damagestatistic == statKey) { rollData.weaponsList.push({ label: `Damage ${weapon.name}`, type: "damage", applied: false, weapon: weapon, value: weapon.data.damage, damageDice: PegasusUtility.getDiceFromLevel(weapon.data.damage) }) } } } addEquipments(rollData, statKey) { let equipments = this.getEquipmentsOnly() for (let equip of equipments) { if (equip.data.equipped && equip.data.stataffected == statKey) { rollData.equipmentsList.push({ label: `Item ${equip.name}`, type: "item", applied: false, equip: equip, value: equip.data.level }) } } } /* -------------------------------------------- */ getCommonRollData(statKey = undefined, useShield = false, isInit = false) { let rollData = PegasusUtility.getBasicRollData(isInit) rollData.alias = this.name rollData.actorImg = this.img rollData.actorId = this.id rollData.img = this.img rollData.traumaState = this.getTraumaState() rollData.levelRemaining = this.getLevelRemaining() rollData.activePerks = duplicate(this.getActivePerks()) rollData.diceList = PegasusUtility.getDiceList() rollData.dicePool = [] if (statKey) { rollData.statKey = statKey rollData.stat = this.getStat(statKey) rollData.statDicesLevel = rollData.stat.value rollData.statMod = rollData.stat.mod rollData.specList = this.getRelevantSpec(statKey) rollData.selectedSpec = "0" if (statKey.toLowerCase() == "mr") { rollData.img = "systems/fvtt-pegasus-rpg/images/icons/MR.webp" } else { rollData.img = `systems/fvtt-pegasus-rpg/images/icons/${rollData.stat.abbrev}.webp` } let diceKey = PegasusUtility.getDiceFromLevel(rollData.stat.value) let diceList = diceKey.split(" ") let mod = rollData.stat.mod for (let myDice of diceList) { myDice = myDice.trim() let newDice = { name: "stat", key: myDice, level: PegasusUtility.getLevelFromDice(myDice), mod: mod, img: `systems/fvtt-pegasus-rpg/images/dice/${myDice}.webp` } rollData.dicePool.push(newDice) mod = 0 // Only first dice has modifier } } this.addEffects(rollData, isInit) this.addArmorsShields(rollData, statKey, useShield) this.addWeapons(rollData, statKey, useShield) this.addEquipments(rollData, statKey) console.log("ROLLDATA", rollData) return rollData } /* -------------------------------------------- */ getLevelRemainingList() { let options = [] for (let i = 0; i <= this.data.data.biodata.maxlevelremaining; i++) { options.push(``) } return options.join("\n") } /* -------------------------------------------- */ getMaxLevelRemainingList() { let options = [] for (let i = 0; i <= 12; i++) { options.push(``) } return options.join("\n") } /* -------------------------------------------- */ async startRoll(rollData) { this.syncRoll(rollData); //console.log("ROLL DATA", rollData) let rollDialog = await PegasusRollDialog.create(this, rollData) console.log(rollDialog) rollDialog.render(true); } /* -------------------------------------------- */ powerDmgRoll(itemId) { let power = this.data.items.get(itemId) if (power) { power = duplicate(power) this.rollPool(power.data.dmgstatistic, false, "power-dmg") } } /* -------------------------------------------- */ rollPool(statKey, useShield = false, subKey = "none") { let stat = this.getStat(statKey) if (stat) { let rollData = this.getCommonRollData(statKey, useShield) rollData.mode = "stat" rollData.subKey = subKey let def = stat.label if (subKey) { def = __subkey2title[subKey] } rollData.title = `Roll : ${def} ` rollData.img = "icons/dice/d12black.svg" if (subKey == "melee-dmg" || subKey == "ranged-dmg" || subKey == "power-dmg") { rollData.isDamage = true } this.startRoll(rollData) } else { ui.notifications.warn("Statistic not found !"); } } /* -------------------------------------------- */ rollUnarmedAttack() { let stat = this.getStat('com') if (stat) { let rollData = this.getCommonRollData(statKey) rollData.mode = "stat" rollData.title = `Unarmed Attack`; rollData.damages = this.getStat('str'); this.startRoll(rollData); } else { ui.notifications.warn("Statistic not found !"); } } /*-------------------------------------------- */ rollStat(statKey) { let stat = this.getStat(statKey); if (stat) { let rollData = this.getCommonRollData(statKey) rollData.mode = "stat" rollData.title = `Stat ${stat.label}`; this.startRoll(rollData) } else { ui.notifications.warn("Statistic not found !"); } } /* -------------------------------------------- */ async rollSpec(specId) { let spec = this.getOneSpec(specId) if (spec) { let rollData = this.getCommonRollData(spec.data.statistic) rollData.mode = "spec" rollData.title = `Spec. : ${spec.name} ` rollData.specList = [spec] rollData.selectedSpec = spec._id rollData.specName = spec.name rollData.img = spec.img rollData.specDicesLevel = spec.data.level PegasusUtility.updateSpecDicePool(rollData) this.startRoll(rollData) } else { ui.notifications.warn("Specialisation not found !"); } } /* -------------------------------------------- */ async rollMR(isInit = false, combatId = 0, combatantId = 0) { let mr = duplicate(this.data.data.mr) if (mr) { mr.dice = PegasusUtility.getDiceFromLevel(mr.value); let rollData = this.getCommonRollData("mr", false, isInit) rollData.mode = "MR" rollData.img = "systems/fvtt-pegasus-rpg/images/icons/MR.webp" rollData.isInit = isInit rollData.combatId = combatId rollData.combatantId = combatantId console.log("MR ROLL", rollData) if (isInit) { rollData.title = "MR / Initiative" } this.startRoll(rollData); } else { ui.notifications.warn("MR not found !"); } } /* -------------------------------------------- */ async rollArmor(armorId) { let armor = this.data.items.get(armorId) if (armor) { let rollData = this.getCommonRollData(armor.data.statistic) armor = duplicate(armor); this.checkAndPrepareEquipment(armor); rollData.mode = "armor" rollData.armor = armor rollData.title = `Armor : ${armor.name}` rollData.isResistance = true; rollData.img = armor.img rollData.damageDiceLevel = armor.data.resistance this.startRoll(rollData); } else { ui.notifications.warn("Armor not found !", weaponId); } } /* -------------------------------------------- */ async rollPower(powerId) { let power = this.data.items.get(powerId) if (power) { power = duplicate(power) let rollData = this.getCommonRollData(power.data.statistic) rollData.mode = "power" rollData.power = power rollData.title = `Power : ${power.name}` rollData.img = power.img this.startRoll(rollData); } else { ui.notifications.warn("Power not found !", powerId); } } }