/* -------------------------------------------- */ 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", "defence": "Defence", "dmg-res": "Damage Resistance", "power-dmg": "Power Damage" } const __statBuild = [ { modules: ["vehiclehull"], field: "hr", itemfield: "hr" }, { modules: ["vehiclehull", "vehiclemodule"], field: "hr", itemfield: "size", subfield: "size" }, //{ modules: ["vehiclehull"], field: "pc", itemfield: "vms", subfield: "avgnrg" }, //{ modules: ["powercoremodule"], field: "pc", itemfield: "nrg", subfield: "avgnrg" }, { modules: ["vehiclehull", "mobilitymodule"], itemfield: "man", field: "man", additionnal1: "turningarc45" }, { modules: ["powercoremodule"], field: "pc", itemfield: "pc" }, { modules: ["mobilitymodule"], field: "mr", itemfield: "mr" }, { modules: ["propulsionmodule"], field: "ad", itemfield: "ad" }, { modules: ["combatmodule"], field: "fc", itemfield: "fc" }, ] const __LocationsArmour = ["front", "rear", "bottom", "left", "right", "bottom"] const __isVehicleUnique = { vehiclehull: 1, powercoremodule: 1, mobilitymodule: 1, propulsionmodule: 1, combatmodule: 1 } const __speed2Num = { fullstop: 0, crawling: 1, slow: 2, average: 3, fast: 4, extfast: 5 } const __num2speed = ["fullstop", "crawling", "slow", "average", "fast", "extfast"] const __isVehicle = { vehiclehull: 1, powercoremodule: 1, mobilitymodule: 1, combatmodule: 1, propulsionmodule: 1, vehiclemodule: 1, vehicleweaponmodule: 1, effect: 1, cargo: 1 } const __isVehicleCargo = { cargo: 1 } const __bonusEffect = { name: "Crawling MAN Bonus", type: "effect", img: "systems/fvtt-pegasus-rpg/images/icons/icon_effect.webp", system: { type: "physical", genre: "positive", effectlevel: 3, reducedicevalue: false, stataffected: "man", specaffected: [], statdice: false, bonusdice: true, weapondamage: false, hindrance: false, resistedby: "notapplicable", recoveryroll: false, recoveryrollstat: "", recoveryrollspec: [], effectstatlevel: false, effectstat: "", oneuse: false, ignorehealthpenalty: false, isthispossible: "", mentaldisruption: false, physicaldisruption: false, mentalimmunity: false, physicalimmunity: false, nobonusdice: false, noperksallowed: false, description: "", otherdice: false } } /* -------------------------------------------- */ /** * 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' || this.type == 'npc') { } return super.create(data, options); } /* -------------------------------------------- */ prepareBaseData() { } /* -------------------------------------------- */ async prepareData() { super.prepareData(); } /* -------------------------------------------- */ prepareDerivedData() { if (this.system.secondary.stealthhealth) { this.update({ "system.secondary": { "-=stealthhealth": null } }) } if (this.system.secondary.socialhealth) { this.update({ "system.secondary": { "-=socialhealth": null } }) } if (!this.traumaState) { this.traumaState = "none" } if (this.type == 'character' || this.type == 'npc') { this.computeNRGHealth(); this.system.encCapacity = this.getEncumbranceCapacity() this.buildContainerTree() this.updatePPP() this.updateSize() } if (this.type == 'vehicle') { this.computeVehicleStats() } super.prepareDerivedData() } /* -------------------------------------------- */ _preUpdate(changed, options, user) { super._preUpdate(changed, options, user); } /* -------------------------------------------- */ getMT() { let modifier = 0 for (let effect of this.items) { if (effect.type == "effect" && effect.system.affectstatus && effect.system.affectedstatus == "mt") { if (effect.system.genre == "positive") { modifier += effect.system.effectlevel } if (effect.system.genre == "negative") { modifier -= effect.system.effectlevel } } if (effect.type == "ability" && effect.system.statusaffected && effect.system.statusaffected == "mt") { modifier += effect.system.statusmodifier } } return PegasusUtility.getDiceValue(this.system.statistics.mnd.value) + this.system.statistics.mnd.mod + PegasusUtility.getDiceValue(this.system.statistics.mnd.bonuseffect) + modifier } /* -------------------------------------------- */ getKBV() { let modifier = 0 for (let effect of this.items) { if (effect.type == "effect" && effect.system.affectstatus && effect.system.affectedstatus == "kbv") { if (effect.system.genre == "positive") { modifier += effect.system.effectlevel } if (effect.system.genre == "negative") { modifier -= effect.system.effectlevel } } if (effect.type == "ability" && effect.system.statusaffected && effect.system.statusaffected == "kbv") { modifier += effect.system.statusmodifier } } return this.system.statistics.phy.value + this.system.statistics.phy.mod + this.system.statistics.phy.bonuseffect + modifier } /* -------------------------------------------- */ getEncumbranceCapacity() { return this.system.statistics.str.value * 25 } /* -------------------------------------------- */ getActivePerks() { let perks = this.items.filter(item => item.type == 'perk' && item.system.active); return perks; } /* -------------------------------------------- */ getAbilities() { let ab = this.items.filter(item => item.type == 'ability'); return ab; } /* -------------------------------------------- */ getPerks() { let comp = duplicate(this.items.filter(item => item.type == 'perk') || []) for (let perk of comp) { if (perk.system.features.range.flag) { perk.rangeText = PegasusUtility.getRangeText(perk.system.features.range.value) } } return comp; } /* -------------------------------------------- */ getEffects() { let comp = this.items.filter(item => item.type == 'effect'); return comp; } /* -------------------------------------------- */ getCombatModules() { let comp = this.items.filter(item => item.type == 'combatmodule'); return comp; } getCargos() { let comp = this.items.filter(item => item.type == 'cargo'); return comp; } getVehicleHull() { let comp = this.items.filter(item => item.type == 'vehiclehull'); return comp; } getPowercoreModules() { let comp = this.items.filter(item => item.type == 'powercoremodule'); return comp; } getMobilityModules() { let comp = this.items.filter(item => item.type == 'mobilitymodule'); return comp; } getPropulsionModules() { let comp = this.items.filter(item => item.type == 'propulsionmodule'); return comp; } getVehicleModules() { let comp = this.items.filter(item => item.type == 'vehiclemodule'); return comp; } getVehicleWeaponModules(activated = false) { let comp = [] if (activated) { comp = this.items.filter(item => item.type == 'vehicleweaponmodule' && item.system.activated) } else { comp = this.items.filter(item => item.type == 'vehicleweaponmodule') } return comp; } /* -------------------------------------------- */ getPowers() { let comp = this.items.filter(item => item.type == 'power'); return comp; } /* -------------------------------------------- */ getMoneys() { let comp = this.items.filter(item => item.type == 'money'); return comp; } /* -------------------------------------------- */ getVirtues() { let comp = this.items.filter(item => item.type == 'virtue'); return comp; } /* -------------------------------------------- */ getVices() { let comp = this.items.filter(item => item.type == 'vice'); return comp; } /* -------------------------------------------- */ getArmors() { let comp = duplicate(this.items.filter(item => item.type == 'armor') || []); return comp; } /* -------------------------------------------- */ getShields() { let comp = this.items.filter(item => item.type == 'shield') return comp; } getRace() { let race = this.items.filter(item => item.type == 'race') return race[0] ?? []; } getRole() { let role = this.items.find(item => item.type == 'role') return role; } /* -------------------------------------------- */ getRoleLevel() { let role = this.items.find(item => item.type == 'role') if (role) { //console.log("Role", role) return role.system.rolelevel } return 0 } /* -------------------------------------------- */ isTactician() { let role = this.items.find(item => item.type == 'role') return role && role.system.perksrole == "tactician" } hasTacticianBonus() { let effect = this.items.find(item => item.name.toLowerCase().includes("tactician bonus dice")) return effect } async addTacticianEffect(name, level) { let effect = duplicate(__bonusEffect) effect.name = `${name} Tactician Bonus Dice` effect.system.effectlevel = level effect.system.stataffected = "mr" effect.system.bonusdice = true await this.createEmbeddedDocuments('Item', [effect]) ChatMessage.create({ content: `Tactician Bonus Dice has been added to ${this.name} (${level})`, whisper: ChatMessage.getWhisperRecipients('GM') }) } async removeTacticianEffect() { let effect = this.items.find(item => item.name.toLowerCase().includes("tactician bonus dice")) if (effect) { await this.deleteEmbeddedDocuments('Item', [effect.id]) ChatMessage.create({ content: `Tactician Bonus Dice has been removed to ${this.name}`, whisper: ChatMessage.getWhisperRecipients('GM') }) } } /* -------------------------------------------- */ getStatus(statusKey) { if (statusKey == "nrg") { return duplicate(this.system.nrg) } return duplicate(this.system.secondary[statusKey]) } /* -------------------------------------------- */ async addStatusBonus(statusKey, value) { let status = this.getStatus(statusKey) let effect = duplicate(__bonusEffect) effect.name = `${status.label} Creation Bonus` effect.system.affectstatus = true effect.system.affectedstatus = statusKey effect.system.effectlevel = value effect.system.bonusdice = false effect.system.locked = true await this.createEmbeddedDocuments('Item', [effect]) } /* -------------------------------------------- */ isEnhancer() { let role = this.items.find(item => item.type == 'role') return role && role.system.perksrole == "enhancer" } hasEnhancerBonus() { let effect = this.items.find(item => item.name.toLowerCase().includes("enhancer bonus dice")) return effect } async addEnhancerEffect(name, level) { let effect = duplicate(__bonusEffect) effect.name = `${name} Enhancer Bonus Dice ALL` effect.system.effectlevel = level effect.system.stataffected = "all" effect.system.bonusdice = true await this.createEmbeddedDocuments('Item', [effect]) ChatMessage.create({ content: `Enhancer Bonus Dice has been added to ${this.name} (${level})`, whisper: ChatMessage.getWhisperRecipients('GM') }) } async removeEnhancerEffect() { let effect = this.items.find(item => item.name.toLowerCase().includes("enhancer bonus dice")) if (effect) { await this.deleteEmbeddedDocuments('Item', [effect.id]) ChatMessage.create({ content: `Enhancer Bonus Dice has been removed to ${this.name}`, whisper: ChatMessage.getWhisperRecipients('GM') }) } } /* -------------------------------------------- */ isAgitator() { let role = this.items.find(item => item.type == 'role') return role && role.system.perksrole == "agitator" } hasAgitatorHindrance() { let effect = this.items.find(item => item.name.toLowerCase().includes("hindered by agitator")) return effect } async addAgitatorHindrance(name, level) { let effect = duplicate(__bonusEffect) effect.name = `Hindered by Agitator ${name}` effect.system.effectlevel = level effect.system.stataffected = "all" effect.system.genre = "negative" effect.system.hindrance = true await this.createEmbeddedDocuments('Item', [effect]) ChatMessage.create({ content: `Agitator Hindrance has been added to ${this.name} (${level})`, whisper: ChatMessage.getWhisperRecipients('GM') }) } async removeAgitatorHindrance() { let effect = this.items.find(item => item.name.toLowerCase().includes("hindered by agitator")) if (effect) { await this.deleteEmbeddedDocuments('Item', [effect.id]) ChatMessage.create({ content: `Agitator Hindrance has been removed to ${this.name}`, whisper: ChatMessage.getWhisperRecipients('GM') }) } } /* -------------------------------------------- */ checkAndPrepareEquipment(item) { if (item.system.resistance) { item.system.resistanceDice = PegasusUtility.getDiceFromLevel(item.system.resistance) } if (item.system.idr && Number(item.system.idr) > 0) { item.system.idrDice = PegasusUtility.getDiceFromLevel(item.system.idr) } if (item.system.damage) { item.system.damageDice = PegasusUtility.getDiceFromLevel(item.system.damage) } if (item.system.level) { item.system.levelDice = PegasusUtility.getDiceFromLevel(item.system.level) } } /* -------------------------------------------- */ checkAndPrepareEquipments(listItem) { for (let item of listItem) { this.checkAndPrepareEquipment(item) } return listItem } /* -------------------------------------------- */ getWeapons() { let comp = duplicate(this.items.filter(item => item.type == 'weapon') || []); return comp; } /* -------------------------------------------- */ getItemById(id) { let item = this.items.find(item => item.id == id); if (item) { item = duplicate(item) if (item.type == 'specialisation') { item.system.dice = PegasusUtility.getDiceFromLevel(item.system.level); } } return item; } /* -------------------------------------------- */ getSpecs() { let comp = duplicate(this.items.filter(item => item.type == 'specialisation') || []); for (let c of comp) { c.system.dice = PegasusUtility.getDiceFromLevel(c.system.level); } return comp; } /* -------------------------------------------- */ async manageWorstFear(flag) { if (flag) { let effect = await PegasusUtility.getEffectFromCompendium("Worst Fear") effect.system.worstfear = true this.createEmbeddedDocuments('Item', [effect]) } else { let effect = this.items.find(item => item.type == "effect" && item.system.worstfear) if (effect) { this.deleteEmbeddedDocuments('Item', [effect.id]) } } } /* -------------------------------------------- */ async manageDesires(flag) { if (flag) { let effect = await PegasusUtility.getEffectFromCompendium("Desire") //console.log("EFFECT", effect) effect.system.desires = true this.createEmbeddedDocuments('Item', [effect]) } else { let effect = this.items.find(item => item.type == "effect" && item.system.desires) if (effect) { this.deleteEmbeddedDocuments('Item', [effect.id]) } } } /* -------------------------------------------- */ getRelevantSpec(statKey) { let comp = duplicate(this.items.filter(item => item.type == 'specialisation' && item.system.statistic == statKey) || []); for (let c of comp) { c.system.dice = PegasusUtility.getDiceFromLevel(c.system.level); } return comp; } /* -------------------------------------------- */ async activatePerk(perkId) { let item = this.items.find(item => item.id == perkId); if (item && item.system) { let update = { _id: item.id, "system.active": !item.system.active }; await this.updateEmbeddedDocuments('Item', [update]); // Updates one EmbeddedEntity } } /* -------------------------------------------- */ async activateViceOrVirtue(itemId) { let item = this.items.find(item => item.id == itemId) if (item && item.system) { let nrg = duplicate(this.system.nrg) if (!item.system.activated) { // Current value let effects = [] for (let effect of item.system.effectsgained) { effect.system.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.items) { if (item.type == 'effect' && item.system.powerId == itemId) { toRem.push(item.id) } } if (toRem.length) { await this.deleteEmbeddedDocuments('Item', toRem) } } let update = { _id: item.id, "system.activated": !item.system.activated } await this.updateEmbeddedDocuments('Item', [update]) // Updates one EmbeddedEntity } } /* -------------------------------------------- */ setHandInformation(info) { this.update({ 'system.biodata.preferredhand': info }) } /* -------------------------------------------- */ increaseRoleAbility() { let role = this.getRole() let level = role.system.rolelevel + 1 this.updateEmbeddedDocuments('Item', [{ _id: role.id, 'system.rolelevel': level }]) } /* -------------------------------------------- */ setBonusInformation(info) { this.update({ 'system.biodata.bonusselection': info }) } /* -------------------------------------------- */ addCDP(value) { let cdp = this.system.biodata.cdp cdp += value this.update({ 'system.biodata.cdp': cdp }) } /* -------------------------------------------- */ addPPP(value) { let ppp = duplicate(this.system.ppp) console.log("PPP", ppp) ppp.availablePPP += Number(value) this.update({ 'system.ppp': ppp }) } /* -------------------------------------------- */ updatePPP() { let ppp = 0 for (let power of this.items) { if (power.type == "power") { ppp += Number(power.system.powerlevelcost) } } if (ppp != this.system.ppp.spentPPP) { this.update({ 'system.ppp.spentPPP': ppp }) } } /* -------------------------------------------- */ updateSize() { let sizeBonus = 0 for (let effect of this.items) { if (effect.type == "effect" && effect.system.effectlevel > 0 && effect.system.affectsize) { sizeBonus += effect.system.effectlevel } } if (sizeBonus != this.system.biodata.sizebonus) { this.update({ 'system.biodata.sizebonus': sizeBonus }) } } /* -------------------------------------------- */ async activatePower(itemId) { let item = this.items.find(item => item.id == itemId) if (item && item.system) { let nrg = duplicate(this.system.nrg) if (!item.system.activated) { // Current value if (item.system.costspent > nrg.value || item.system.costspent > nrg.max) { return ui.notifications.warn("Not enough NRG to activate the Power " + item.name) } nrg.activated += item.system.costspent nrg.value -= item.system.costspent nrg.max -= item.system.costspent await this.update({ 'system.nrg': nrg }) let effects = [] for (let effect of item.system.effectsgained) { effect.system.powerId = itemId // Link to the perk, in order to dynamically remove them effects.push(effect) } if (effects.length) { await this.createEmbeddedDocuments('Item', effects) } if (item.system.activatedtext.length > 0) { ChatMessage.create({ content: `Power ${item.name} activated : ${item.system.activatedtext}` }) } } else { nrg.activated -= item.system.costspent nrg.max += item.system.costspent await this.update({ 'system.nrg': nrg }) let toRem = [] for (let item of this.items) { if (item.type == 'effect' && item.system.powerId == itemId) { toRem.push(item.id) } } if (toRem.length) { await this.deleteEmbeddedDocuments('Item', toRem) } if (item.system.deactivatedtext.length > 0) { ChatMessage.create({ content: `Power ${item.name} deactivated : ${item.system.deactivatedtext}` }) } } let update = { _id: item.id, "system.activated": !item.system.activated } await this.updateEmbeddedDocuments('Item', [update]) // Updates one EmbeddedEntity } } /* -------------------------------------------- */ async equipItem(itemId) { let item = this.items.find(item => item.id == itemId); if (item && item.system) { let update = { _id: item.id, "system.equipped": !item.system.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.items.filter(item => item.type == 'shield' || item.type == 'armor' || item.type == "weapon" || item.type == "equipment"); } /* ------------------------------------------- */ getEquipmentsOnly() { return duplicate(this.items.filter(item => item.type == "equipment") || []) } /* ------------------------------------------- */ computeThreatLevel() { let tl = 0 for (let key of statThreatLevel) { // Init with concerned stats tl += PegasusUtility.getDiceValue(this.system.statistics[key].value) } let powers = duplicate(this.getPowers() || []) if (powers.length > 0) { // Then add some mental ones of powers tl += PegasusUtility.getDiceValue(this.system.statistics.foc.value) tl += PegasusUtility.getDiceValue(this.system.statistics.mnd.value) } tl += PegasusUtility.getDiceValue(this.system.mr.value) let specThreat = this.items.filter(it => it.type == "specialisation" && it.system.isthreatlevel) || [] for (let spec of specThreat) { tl += PegasusUtility.getDiceValue(spec.system.level) } tl += this.system.nrg.absolutemax /* NO MORE USED + this.system.secondary.health.max + this.system.secondary.delirium.max*/ tl += this.getPerks().length * 5 let weapons = this.getWeapons() for (let weapon of weapons) { tl += PegasusUtility.getDiceValue(weapon.system.damage) } let armors = this.getArmors() for (let armor of armors) { tl += PegasusUtility.getDiceValue(armor.system.resistance) } let shields = duplicate(this.getShields()) for (let shield of shields) { tl += PegasusUtility.getDiceValue(shield.system.level) } let abilities = duplicate(this.getAbilities()) for (let ability of abilities) { tl += ability.system.threatlevel } let equipments = this.getEquipmentsOnly() for (let equip of equipments) { tl += equip.system.threatlevel } if (tl != this.system.biodata.threatlevel) { this.update({ 'system.biodata.threatlevel': tl }) } } /* ------------------------------------------- */ async buildContainerTree() { let equipments = duplicate(this.items.filter(item => item.type == "equipment") || []) for (let equip1 of equipments) { if (equip1.system.iscontainer) { equip1.system.contents = [] equip1.system.contentsEnc = 0 for (let equip2 of equipments) { if (equip1._id != equip2._id && equip2.system.containerid == equip1._id) { equip1.system.contents.push(equip2) let q = equip2.system.quantity ?? 1 equip1.system.contentsEnc += q * equip2.system.weight } } } } // Compute whole enc let enc = 0 for (let item of equipments) { item.system.idrDice = PegasusUtility.getDiceFromLevel(Number(item.system.idr)) if (item.system.equipped) { if (item.system.iscontainer) { enc += item.system.contentsEnc } else if (item.system.containerid == "") { let q = item.system.quantity ?? 1 enc += q * item.system.weight } } } for (let item of this.items) { // Process items/shields/armors if ((item.type == "weapon" || item.type == "shield" || item.type == "armor") && item.system.equipped) { let q = item.system.quantity ?? 1 enc += q * item.system.weight } } // Store local values this.encCurrent = enc this.containersTree = equipments.filter(item => item.system.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.items.find(item => item.type == "effect" && item.system.slow) if (overCapacity >= 4) { if (!effect) { effect = await PegasusUtility.getEffectFromCompendium("Slowed") effect.system.slow = true this.createEmbeddedDocuments('Item', [effect]) } } else { if (effect) { this.deleteEmbeddedDocuments('Item', [effect.id]) } } } /* -------------------------------------------- */ modifyStun(incDec) { if ( incDec < 0 && (this.system.secondary.confidence.status == "anxious" || this.system.secondary.confidence.status == "lostface") ) { ui.notifications.warn("Unable to recover STUN because of Confidence status : " + this.system.secondary.confidence.status) return } let myself = this let combat = duplicate(myself.system.combat) combat.stunlevel += incDec if (combat.stunlevel >= 0) { myself.update({ 'system.combat': combat }) let chatData = { user: game.user.id, rollMode: game.settings.get("core", "rollMode"), whisper: [game.user.id].concat(ChatMessage.getWhisperRecipients('GM')) } this.createEmbeddedDocuments("ActiveEffect", [ { label: 'EFFECT.StatusDaze', icon: 'icons/svg/daze.svg', flags: { core: { statusId: 'daze' } } } ]) if (incDec > 0) { chatData.content = `
${this.name} suffered a Stun level.${this.name} recovered a Stun level. 0) { ChatMessage.create({ content: `${this.name} Stun threshold has been exceeded.` }) } /* NO MORE AUTOMATION HERE if (incDec > 0 && stunAbove > 0) { let delirium = duplicate(myself.system.secondary.delirium) delirium.value -= incDec myself.update({ 'system.secondary.delirium': delirium }) }*/ } /* -------------------------------------------- */ modifyMomentum(incDec) { if ( this.system.stun.value > 0 ) { ui.notifications.warn("Unable to gain/use Momentum while stunned") return } let momentum = duplicate(this.system.momentum) momentum.value += incDec this.update({ 'system.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.system.label == label); } /* -------------------------------------------- */ getEffectById(id) { return this.getActiveEffects().find(it => it.id == id); } /* -------------------------------------------- */ getAttribute(attrKey) { return this.system.attributes[attrKey]; } /* -------------------------------------------- */ async addObjectToContainer(object, containerId) { let container = this.items.find(item => item.id == containerId && item.system.iscontainer) console.log("Adding container: ", container, object) if (container) { if (object.system?.iscontainer) { ui.notifications.warn("Only 1 level of container allowed") return } let alreadyInside = this.items.filter(item => item.system.containerid && item.system.containerid == containerId); if (alreadyInside.length >= container.system.containercapacity) { ui.notifications.warn("Container is already full !") return } else { this.updateEmbeddedDocuments("Item", [{ _id: object.id, 'system.containerid': containerId }]) } } else if (object && object.system.containerid) { // remove from container this.updateEmbeddedDocuments("Item", [{ _id: object.id, 'system.containerid': "" }]) } } /* -------------------------------------------- */ checkVirtue(virtue) { let vices = this.getVices() for (let vice of vices) { let nonVirtues = vice.system.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.system.unavailablevice for (let blockedVice of nonVices) { if (blockedVice.name.toLowerCase() == vice.name.toLowerCase()) { return false } } } return true } /* -------------------------------------------- */ async preprocessItem(event, item, onDrop = false) { console.log("Pre-process", item) if (!item) { return } if (item.type != "effect" && __isVehicle[item.type]) { ui.notifications.warn("You can't drop Vehicles item over a character sheet.") return } // Pre-filter effects if (item.type == 'effect') { if (this.checkMentalDisruption() && item.system.type == "mental" && item.system.genre == "positive") { ChatMessage.create({ content: "Effects of this type cannot be applied while Disruption is applied, Use a Soft Action to remove Disruption" }) return } if (this.checkPhysicalDisruption() && item.system.type == "physical" && item.system.genre == "positive") { ChatMessage.create({ content: "Effects of this type cannot be applied while Disruption is applied, Use a Soft Action to remove Disruption" }) return } if (this.checkMentalImmunity() && item.system.type == "mental" && item.system.genre == "negative") { ChatMessage.create({ content: "Effects of this type cannot be applied while Immunity is applied" }) return } if (this.checkPhysicalImmunity() && item.system.type == "physical" && item.system.genre == "negative") { ChatMessage.create({ content: "Effects of this type cannot be applied while Immunity is applied" }) return } if (item.system.droptext && item.system.droptext.length > 0) { ChatMessage.create({ content: `Effect ${item.name} message : ${item.system.droptext}` }) } } if (item.type == "race") { this.applyRace(item) } else if (item.type == "role") { this.applyRole(item) } else if (item.type == "ability") { this.applyAbility(item, [], true) if (!onDrop) { await this.createEmbeddedDocuments('Item', [item]) } } else { if (!onDrop) { await this.createEmbeddedDocuments('Item', [item]) } } // Check virtue/vice validity if (item.type == "virtue") { if (!this.checkVirtue(item)) { ui.notifications.info("Virtue is not allowed due to Vice.") return false } } if (item.type == "vice") { if (!this.checkVice(item)) { ui.notifications.info("Vice is not allowed due to Virtue.") return false } } if (item.type == "power" && item.system.purchasedtext.length > 0) { ChatMessage.create({ content: `Power ${item.name} puchased : ${item.system.purchasedtext}` }) } let dropID = $(event.target).parents(".item").attr("data-item-id") // Only relevant if container drop this.addObjectToContainer(item, dropID) return true } /* -------------------------------------------- */ async equipGear(equipmentId) { let item = this.items.find(item => item.id == equipmentId); if (item && item.system) { let update = { _id: item.id, "system.equipped": !item.system.equipped }; await this.updateEmbeddedDocuments('Item', [update]); // Updates one EmbeddedEntity } } /* -------------------------------------------- */ getInitiativeScore(combatId, combatantId) { if (this.type == 'character' || this.type == 'npc') { this.rollMR(true, combatId, combatantId) } if (this.type == 'vehicle') { this.rollMR(true, combatId, combatantId) } console.log("Init required !!!!") return -1; } /* -------------------------------------------- */ getSubActors() { let subActors = []; if (this.system.subactors) { for (let id of this.system.subactors) { subActors.push(duplicate(game.actors.get(id))) } } return subActors; } /* -------------------------------------------- */ async addSubActor(subActorId) { let subActors = duplicate(this.system.subactors || []); subActors.push(subActorId); await this.update({ 'system.subactors': subActors }); } /* -------------------------------------------- */ async delSubActor(subActorId) { let newArray = []; for (let id of this.system.subactors) { if (id != subActorId) { newArray.push(id); } } await this.update({ 'system.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 ((this.type == "character" || this.type == 'npc') && statKey == 'mr') { stat = duplicate(this.system.mr) } else { stat = duplicate(this.system.statistics[statKey]) } if (stat.currentlevel) { stat.dice = PegasusUtility.getDiceFromLevel(stat.currentlevel) } else { stat.dice = PegasusUtility.getDiceFromLevel(stat.value || stat.level) } return stat } /* -------------------------------------------- */ getOneSpec(specId) { let spec = this.items.find(item => item.type == 'specialisation' && item.id == specId) if (spec) { spec = duplicate(spec); spec.system.dice = PegasusUtility.getDiceFromLevel(spec.system.level); } return spec; } /* -------------------------------------------- */ specPowerActivate(specId) { let spec = this.getOneSpec(specId) if (spec) { let powers = [] for (let power of spec.system.powers) { if (power.data) { power.system = power.data } power.system.specId = specId powers.push(power) } if (powers.length > 0) { this.createEmbeddedDocuments('Item', powers) } this.updateEmbeddedDocuments('Item', [{ _id: specId, 'system.powersactivated': true }]) } } /* -------------------------------------------- */ specPowerDeactivate(specId) { let toRem = [] for (let power of this.items) { if (power.type == "power" && power.system.specId && power.system.specId == specId) { toRem.push(power.id) } } if (toRem.length > 0) { this.deleteEmbeddedDocuments('Item', toRem) } this.updateEmbeddedDocuments('Item', [{ _id: specId, 'system.powersactivated': false }]) } /* -------------------------------------------- */ equipActivate(itemId) { let item = this.items.get(itemId) if (item) { let effects = [] for (let effect of item.system.effects) { effect.system.itemId = itemId // Keep link effects.push(effect) } if (effects.length > 0) { this.createEmbeddedDocuments('Item', effects) } this.updateEmbeddedDocuments('Item', [{ _id: itemId, 'system.activated': true }]) } } /* -------------------------------------------- */ equipDeactivate(itemId) { let toRem = [] for (let item of this.items) { if (item.system.itemId && item.system.itemId == itemId) { toRem.push(item.id) } } if (toRem.length > 0) { this.deleteEmbeddedDocuments('Item', toRem) } this.updateEmbeddedDocuments('Item', [{ _id: itemId, 'system.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.system.isweaver) { for (let spec of this.items) { if (spec.type == 'specialisation' && spec.system.ispowergroup) { this.specPowerDeactivate(spec.id) } } } } /* -------------------------------------------- */ enableWeaverPerk(perk) { if (perk.system.isweaver) { for (let spec of this.items) { if (spec.type == 'specialisation' && spec.system.ispowergroup) { this.specPowerActivate(spec.id) } } } } /* -------------------------------------------- */ async cleanPerkEffects(itemId) { let effects = [] for (let item of this.items) { if (item.type == "effect" && item.system.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 = "system.used" + index await this.updateEmbeddedDocuments('Item', [{ _id: itemId, [`${key}`]: checked }]) item = this.items.get(itemId) // Refresh if (item.system.nbuse == "next1action" && item.system.used1) { this.cleanPerkEffects(itemId) } if (item.system.nbuse == "next2action" && item.system.used1 && item.system.used2) { this.cleanPerkEffects(itemId) } if (item.system.nbuse == "next3action" && item.system.used1 && item.system.used2 && item.system.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, 'system.costspent': value }]) } } /* -------------------------------------------- */ async cleanupPerksIfTrauma() { if (this.getTraumaState() == "severetrauma") { for (let perk of this.items) { if (perk.type == "perk") { this.cleanPerkEffects(perk.id) this.updateEmbeddedDocuments('Item', [{ _id: perk.id, 'system.status': "ready", 'system.used1': false, 'system.used2': false, 'system.used3': false }]) ChatMessage.create({ content: `Perk ${perk.name} has been deactivated due to Severe Trauma state !` }) } } } } /* -------------------------------------------- */ incDecNRG(value) { if (this.type == "character" || this.type == 'npc') { let nrg = duplicate(this.system.nrg) nrg.value += value if (nrg.value >= 0 && nrg.value <= nrg.max) { this.update({ 'system.nrg': nrg }) } } else { let pc = duplicate(this.system.statistics.pc) pc.curnrg += value if (pc.curnrg >= 0 && pc.curnrg <= pc.maxnrg) { this.update({ 'system.statistics.pc': pc }) } } } /* -------------------------------------------- */ async updatePerkStatus(itemId, status) { let item = this.items.get(itemId) if (item) { if (item.system.status == status) return;// Ensure we are really changing the status if (this.checkNoPerksAllowed()) { await this.updateEmbeddedDocuments('Item', [{ _id: item.id, 'system.status': "ready" }]) ChatMessage.create({ content: "No perks activation allowed due to effect !" }) return } // 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.system.features.nrgcost.value) && (this.system.nrg.max >= item.system.features.nrgcost.value)) { let nrg = duplicate(this.system.nrg) nrg.activated += item.system.features.nrgcost.value nrg.value -= item.system.features.nrgcost.value nrg.max -= item.system.features.nrgcost.value await this.update({ 'system.nrg': nrg }) } else { updateOK = false ui.notifications.warn("Not enough NRG to activate the Perk " + item.name) } } /* NO MORE USED if (item.system.features.bonushealth.flag) { let health = duplicate(this.system.secondary.health) health.value += Number(item.system.features.bonushealth.value) || 0 health.max += Number(item.system.features.bonushealth.value) || 0 await this.update({ 'system.secondary.health': health }) } if (item.system.features.bonusdelirium.flag) { let delirium = duplicate(this.system.secondary.delirium) delirium.value += Number(item.system.features.bonusdelirium.value) || 0 delirium.max += Number(item.system.features.bonusdelirium.value) || 0 await this.update({ 'system.secondary.delirium': delirium }) }*/ if (item.system.features.bonusnrg.flag) { let nrg = duplicate(this.system.nrg) nrg.value += Number(item.system.features.bonusnrg.value) || 0 nrg.max += Number(item.system.features.bonusnrg.value) || 0 await this.update({ 'system.nrg': nrg }) } PegasusUtility.createChatWithRollMode(item.name, { content: await renderTemplate(`systems/fvtt-pegasus-rpg/templates/chat-perk-activated.html`, { name: this.name, perk: duplicate(item) }) }) this.enableWeaverPerk(item) } if (updateOK) { await this.updateEmbeddedDocuments('Item', [{ _id: item.id, 'system.status': status }]) } } } /* -------------------------------------------- */ async deleteAllItemsByType(itemType) { let items = this.items.filter(item => item.type == itemType).map(item => item.id) console.log("Dele....", items) await this.deleteEmbeddedDocuments('Item', items); } /* -------------------------------------------- */ async addItemWithoutDuplicate(newItem) { let item = this.items.find(item => item.type == newItem.type && item.name.toLowerCase() == newItem.name.toLowerCase()) if (!item) { await this.createEmbeddedDocuments('Item', [newItem]); } } /* -------------------------------------------- */ getTraumaState() { this.traumaState = "none" if (this.type == "character" || this.type == 'npc') { if (this.system.secondary.delirium.status == "trauma") { this.traumaState = "trauma" } if (this.system.secondary.delirium.status == "severetrauma" || this.system.secondary.delirium.status == "defeated") { this.traumaState = "severetrauma" } } return this.traumaState } /* -------------------------------------------- */ getLevelRemaining() { return this.system.biodata?.currentlevelremaining || 0 } /* -------------------------------------------- */ modifyHeroLevelRemaining(incDec) { let biodata = duplicate(this.system.biodata) biodata.currentlevelremaining = Math.max(biodata.currentlevelremaining + incDec, 0) this.update({ "system.biodata": biodata }) ChatMessage.create({ content: `${this.name} has used a Hero Level to reroll !` }) return biodata.currentlevelremaining } /* -------------------------------------------- */ checkIgnoreHealth() { for (let effect of this.items) { if (effect.type == "effect" && effect.system.ignorehealthpenalty) { return true } } return false } /* -------------------------------------------- */ checkMentalDisruption() { for (let effect of this.items) { if (effect.type == "effect" && effect.system.mentaldisruption) { return true } } return false } /* -------------------------------------------- */ checkPhysicalDisruption() { for (let effect of this.items) { if (effect.type == "effect" && effect.system.physicaldisruption) { return true } } return false } /* -------------------------------------------- */ checkMentalImmunity() { for (let effect of this.items) { if (effect.type == "effect" && effect.system.mentalimmunity) { return true } } return false } /* -------------------------------------------- */ checkPhysicalImmunity() { for (let effect of this.items) { if (effect.type == "effect" && effect.system.physicalimmunity) { return true } } return false } /* -------------------------------------------- */ checkNoBonusDice() { for (let effect of this.items) { if (effect.type == "effect" && effect.system.nobonusdice) { return true } } return false } /* -------------------------------------------- */ checkNoPerksAllowed() { for (let effect of this.items) { if (effect.type == "effect" && effect.system.noperksallowed) { return true } } return false } /* -------------------------------------------- */ checkIfPossible() { for (let effect of this.items) { if (effect.type == "effect" && effect.system.isthispossible.length > 0) { ChatMessage.create({ content: effect.system.isthispossible }) } } } /* -------------------------------------------- */ async computeNRGHealth() { if (this.isOwner || game.user.isGM) { let updates = {} let nrgValue = PegasusUtility.getDiceValue(this.system.statistics.foc.value) + this.system.nrg.mod + this.system.statistics.foc.mod + PegasusUtility.getDiceValue(this.system.statistics.foc.bonuseffect) if (nrgValue != this.system.nrg.absolutemax) { updates['system.nrg.absolutemax'] = nrgValue } if (this.computeValue) { updates['system.nrg.max'] = nrgValue updates['system.nrg.value'] = nrgValue } let stunth = PegasusUtility.getDiceValue(this.system.statistics.phy.value) + PegasusUtility.getDiceValue(this.system.statistics.mnd.value) + PegasusUtility.getDiceValue(this.system.statistics.foc.value) + this.system.statistics.mnd.mod + this.system.statistics.phy.mod + this.system.statistics.foc.mod if (stunth != this.system.combat.stunthreshold) { updates['system.combat.stunthreshold'] = stunth } let momentum = this.system.statistics.foc.value + this.system.statistics.foc.mod if (momentum != this.system.momentum.max) { updates['system.momentum.value'] = 0 updates['system.momentum.max'] = momentum } let mrLevel = (this.system.statistics.agi.value + this.system.statistics.str.value) - this.system.statistics.phy.value mrLevel = (mrLevel < 1) ? 1 : mrLevel; if (mrLevel != this.system.mr.value) { updates['system.mr.value'] = mrLevel } let moralitythreshold = - (Number(PegasusUtility.getDiceValue(this.system.statistics.foc.value)) + Number(this.system.statistics.foc.mod)) if (moralitythreshold != this.system.biodata.moralitythreshold) { updates['system.biodata.moralitythreshold'] = moralitythreshold } if (!this.isToken) { if (this.warnMorality != this.system.biodata.morality && this.system.biodata.morality <= 0) { ChatMessage.create({ content: "WARNING: Your character is dangerously close to becoming corrupted and defeated. Start on a path of redemption!" }) ChatMessage.create({ content: "This character can no longer spend CDP until their Morality has reached 1 or higher" }) } if (this.warnMorality != this.system.biodata.morality) { this.warnMorality = this.system.biodata.morality } } let race = this.getRace() if (race?.name && (race.name != this.system.biodata.racename)) { updates['system.biodata.racename'] = race.name } let role = this.getRole() if (role?.name && (role.name != this.system.biodata.rolename)) { updates['system.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.system.combat.hindrancedice if (!this.checkIgnoreHealth()) { if (this.system.secondary.health.status == "wounded") { hindrance += 1 } if (this.system.secondary.health.status == "severlywounded" || this.system.secondary.health.status == "defeated") { hindrance += 3 } /* Manage confidence */ if (this.system.secondary.confidence.status == "shaken" || this.system.secondary.confidence.status == "anxious" || this.system.secondary.confidence.status == "lostface") { if (!this.items.find(it => it.name.toLowerCase() == "fear" && it.type == "effect")) { let effect = await PegasusUtility.getEffectFromCompendium("Fear") this.createEmbeddedDocuments('Item', [effect]) } } /* Manage flag state for status */ this.defeatedDisplayed = this.defeatedDisplayed && this.system.secondary.health.status != "defeated" this.deliriumDisplayed = this.deliriumDisplayed && this.system.secondary.delirium.status != "defeated" this.concealmentDisplayed = this.concealmentDisplayed && this.system.secondary.concealment.status != "defeated" this.confidenceDisplayed = this.confidenceDisplayed && this.system.secondary.confidence.status != "defeated" /* Then display relevant messages */ if (!this.defeatedDisplayed && this.system.secondary.health.status == "defeated") { ChatMessage.create({ content: `DEFEATED : ${this.name} must make a Death Save!` }) this.defeatedDisplayed = true } if (!this.deliriumDisplayed && this.system.secondary.delirium.status == "defeated") { ChatMessage.create({ content: `DEFEATED : ${this.name} must make a Madness Check!` }) this.deliriumDisplayed = true } if (!this.concealmentDisplayed && this.system.secondary.concealment.status == "located") { ChatMessage.create({ content: `${this.name} has been discovered! You can not longer hide and either must fight/surrender or make a run for it` }) this.concealmentDisplayed = true } if (!this.confidenceDisplayed && this.system.secondary.confidence.status == "lostface") { ChatMessage.create({ content: `${this.name} have Lost Face! You can not longer make any Social Rolls, all social rolls against your character is considered an automatic success until healed!` }) this.confidenceDisplayed = true } } this.system.combat.hindrancedice = hindrance this.getTraumaState() this.cleanupPerksIfTrauma() this.parseStatEffects() this.parseDamageValues() await this.parseStatusEffects() } } /* -------------------------------------------- */ getArmorResistanceBonus() { let bonus = 0 for (let a of armors) { bonus += Number(a.system.resistance) } return bonus } /* -------------------------------------------- */ parseDamageValues() { if (this.system.biodata.noautobonus) { // If we are in "no-bonus mode return } let updates = [] let role = this.getRole() // Get the role for optionnal bonuses let roleBonus = 0 /* Get MDL bonus */ let meleeBonus = 0 let effects = this.items.filter(effect => effect.type == "effect" && effect.system.affectstatus && effect.system.affectstatus == "mdl" && (Number(effect.system.effectlevel) > 0)) for (let e of effects) { meleeBonus += Number(e.system.effectlevel) } let weaponsMelee = this.items.filter(it => it.type == "weapon" && it.system.damagestatistic.toLowerCase() == "str") for (let w of weaponsMelee) { let damage = Number(w.system.damage) + this.system.biodata.sizenum + this.system.biodata.sizebonus + this.system.statistics.str.value + this.system.statistics.str.bonuseffect + meleeBonus if (damage != w.system.mdl) { updates.push({ _id: w.id, "system.mdl": damage }) } } let rangedBonus = 0 effects = this.items.filter(effect => effect.type == "effect" && effect.system.affectstatus && effect.system.affectstatus == "rdl" && (Number(effect.system.effectlevel) > 0)) for (let e of effects) { rangedBonus += Number(e.system.effectlevel) } if (role?.name?.toLowerCase() == "ranged") { // Add ranged bonus to ADRL roleBonus = this.getRoleLevel() } let weaponsRanged = this.items.filter(it => it.type == "weapon" && it.system.damagestatistic.toLowerCase() == "pre") for (let w of weaponsRanged) { let damage = roleBonus + Number(w.system.damage) + rangedBonus if (damage != w.system.rdl) { updates.push({ _id: w.id, "system.rdl": damage }) } } let armorBonus = 0 effects = this.items.filter(effect => effect.type == "effect" && effect.system.affectstatus && effect.system.affectstatus == " adrl" && (Number(effect.system.effectlevel) > 0)) for (let e of effects) { armorBonus += Number(e.system.effectlevel) } roleBonus = 0 if (role?.name?.toLowerCase() == "defender") { // Add defender bonus to ADRL roleBonus = this.getRoleLevel() } let armors = this.items.filter(it => it.type == "armor") for (let a of armors) { let adrl = roleBonus + this.system.statistics.phy.value + this.system.statistics.phy.bonuseffect + this.system.biodata.sizenum + this.system.biodata.sizebonus + a.system.resistance + armorBonus if (adrl != a.system.adrl) { updates.push({ _id: a.id, "system.adrl": adrl }) } } if (updates.length > 0) { this.updateEmbeddedDocuments('Item', updates) } } /* -------------------------------------------- */ parseStatEffects() { if (this.system.biodata.noautobonus) { // If we are in "no-bonus mode return } let effectsPlus = this.items.filter(effect => effect.type == "effect" && effect.system.genre == "positive" && effect.system.statdice) let effectsMinus = this.items.filter(effect => effect.type == "effect" && effect.system.genre == "negative" && effect.system.reducedicevalue) for (let statKey in this.system.statistics) { let stat = duplicate(this.system.statistics[statKey]) let bonus = 0 for (let effect of effectsPlus) { if (effect.system.stataffected == statKey) { bonus += Number(effect.system.effectlevel) } } for (let effect of effectsMinus) { if (effect.system.stataffected == statKey) { bonus -= Number(effect.system.effectlevel) } } if (bonus != stat.bonuseffect) { stat.bonuseffect = bonus if (stat.bonuseffect + stat.value < 1) { stat.value = 1 stat.bonuseffect = 0 } this.update({ [`system.statistics.${statKey}`]: stat }) } } } /* -------------------------------------------- */ async parseStatusEffects() { if (this.system.biodata.noautobonus) { // If we are in "no-bonus mode return } let effects = this.items.filter(effect => effect.type == "effect" && effect.system.affectstatus && (Number(effect.system.effectlevel) > 0)) let abilities = this.items.filter(ability => ability.type == "ability" && ability.system.statusaffected != "notapplicable") for (let statusKey in this.system.secondary) { let status = duplicate(this.system.secondary[statusKey]) let bonus = 0 for (let effect of effects) { if (effect.system.affectedstatus && effect.system.affectedstatus == statusKey) { bonus += Number(effect.system.effectlevel) } } for (let ability of abilities) { if (ability.system.statusaffected && ability.system.statusaffected == statusKey) { bonus += Number(effect.system.statusmodifier) } } if (bonus != status.bonus) { status.bonus = bonus await this.update({ [`system.secondary.${statusKey}`]: status }) } } let nrg = duplicate(this.system.nrg) let bonus = 0 for (let effect of effects) { if (effect.system.affectedstatus && effect.system.affectedstatus == "nrg") { bonus += Number(effect.system.effectlevel) } } for (let ability of abilities) { if (ability.system.statusaffected && ability.system.statusaffected == "nrg") { bonus += Number(ability.system.statusmodifier) } } if (bonus != nrg.mod) { nrg.mod = bonus await this.update({ [`system.nrg`]: nrg }) } } /* -------------------------------------------- */ async modStat(key, inc = 1) { let stat = duplicate(this.system.statistics[key]) stat.mod += parseInt(inc) await this.update({ [`system.statistics.${key}`]: stat }) } /* -------------------------------------------- */ async valueStat(key, inc = 1) { key = key.toLowerCase() let stat = duplicate(this.system.statistics[key]) stat.value += parseInt(inc) await this.update({ [`system.statistics.${key}`]: stat }) } /* -------------------------------------------- */ async modStatus(key, inc = 1) { if (key == "nrg") { let nrg = duplicate(this.system.nrg) nrg.mod += parseInt(inc) await this.update({ [`system.nrg`]: nrg }) } else { let status = duplicate(this.system.secondary[key]) status.bonus += parseInt(inc) await this.update({ [`system.secondary.${key}`]: status }) } } /* -------------------------------------------- */ async addIncSpec(spec, inc = 1) { console.log("Using spec : ", spec, inc) let specExist = this.items.find(item => item.type == 'specialisation' && item.name.toLowerCase() == spec.name.toLowerCase()) if (specExist) { specExist = duplicate(specExist) specExist.system.level += inc; let update = { _id: specExist._id, "system.level": specExist.system.level }; await this.updateEmbeddedDocuments('Item', [update]); } else { spec.system.level = inc; await this.createEmbeddedDocuments('Item', [spec]); } } /* -------------------------------------------- */ async addIncPerk(perk, inc = 1) { console.log("Using perk : ", perk, inc) let perkExist = this.items.find(item => item.type == 'perk' && item.name.toLowerCase() == perk.name.toLowerCase()) if (perkExist) { perkExist = duplicate(perkExist) perkExist.system.level += inc; let update = { _id: perkExist._id, "system.level": perkExist.system.level }; await this.updateEmbeddedDocuments('Item', [update]); } else { perk.system.level = inc; await this.createEmbeddedDocuments('Item', [perk]); } } /* -------------------------------------------- */ async incDecQuantity(objetId, incDec = 0) { let objetQ = this.items.get(objetId) if (objetQ) { let newQ = objetQ.system.quantity + incDec if (newQ >= 0) { await this.updateEmbeddedDocuments('Item', [{ _id: objetQ.id, 'system.quantity': newQ }]) // pdates one EmbeddedEntity } } } /* -------------------------------------------- */ async incDecAmmo(objetId, incDec = 0) { let objetQ = this.items.get(objetId) if (objetQ) { let newQ = objetQ.system.ammocurrent + incDec; if (newQ >= 0 && newQ <= objetQ.system.ammomax) { await this.updateEmbeddedDocuments('Item', [{ _id: objetQ.id, 'system.ammocurrent': newQ }]); // pdates one EmbeddedEntity } } } /* -------------------------------------------- */ async applyAbility(ability, updates = {}, directUpdate = false) { // manage stat bonus if (!ability.system) { ability.system = ability.data } if (ability.system.affectedstat != "notapplicable") { if (ability.system.affectedstat == "mr") { let stat = duplicate(this.system.mr) stat.mod += Number(ability.system.statmodifier) updates[`system.mr`] = stat } else { let stat = duplicate(this.system.statistics[ability.system.affectedstat]) stat.mod += Number(ability.system.statmodifier) updates[`system.statistics.${ability.system.affectedstat}`] = stat } } // manage status bonus if (ability.system.statusaffected != "notapplicable") { if (ability.system.statusaffected == 'nrg') { let nrg = duplicate(this.system.nrg) nrg.mod += Number(ability.system.statusmodifier) updates[`system.nrg`] = nrg } /* NO MORE USED if (ability.system.statusaffected == 'health') { let health = duplicate(this.system.secondary.health) health.bonus += Number(ability.system.statusmodifier) updates[`system.secondary.health`] = health } if (ability.system.statusaffected == 'delirium') { let delirium = duplicate(this.system.secondary.delirium) delirium.bonus += Number(ability.system.statusmodifier) updates[`system.secondary.delirium`] = delirium } if (ability.system.statusaffected == 'socialhealth') { let socialhealth = duplicate(this.system.secondary.socialhealth) socialhealth.bonus += Number(ability.system.statusmodifier) updates[`system.secondary.socialhealth`] = delirium } if (ability.system.statusaffected == 'stealthhealth') { let stealthhealth = duplicate(this.system.secondary.stealthhealth) stealthhealth.bonus += Number(ability.system.statusmodifier) updates[`system.secondary.stealthhealth`] = delirium }*/ } if (directUpdate) { await this.update(updates) } let newItems = [] if (ability.system.effectsgained) { for (let effect of ability.system.effectsgained) { if (!effect.system) effect.system = effect.data newItems.push(effect); } } if (ability.system.powersgained) { for (let power of ability.system.powersgained) { if (!power.system) power.system = power.data newItems.push(power); } } if (ability.system.specialisations) { for (let spec of ability.system.specialisations) { if (!spec.system) spec.system = spec.data newItems.push(spec); } } if (ability.system.attackgained) { for (let weapon of ability.system.attackgained) { if (!weapon.system) weapon.system = weapon.data newItems.push(weapon); } } if (ability.system.armorgained) { for (let armor of ability.system.armorgained) { if (!armor.system) armor.system = armor.data newItems.push(armor); } } console.log("Ability : adding", newItems) await this.createEmbeddedDocuments('Item', newItems) } /* -------------------------------------------- */ async applyRace(race) { let updates = { 'system.biodata.racename': race.name } updates['system.biodata.sizenum'] = race.system.size let newItems = [] await this.deleteAllItemsByType("race") newItems.push(race) for (let ability of race.system.abilities) { if (!ability.system) ability.system = ability.data newItems.push(ability) this.applyAbility(ability, updates) } if (race.system.perksgained) { for (let power of race.system.perks) { if (!power.system) power.system = power.data 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.system.statistics[statKey]) stat.value += 1; updates[`system.statistics.${statKey}`] = stat } /* -------------------------------------------- */ async applyRole(role) { console.log("ROLE", role) let updates = { 'system.biodata.rolename': role.name } let newItems = [] await this.deleteAllItemsByType('role') newItems.push(role) this.getIncreaseStatValue(updates, role.system.statincrease1) this.getIncreaseStatValue(updates, role.system.statincrease2) if (role.system.specialability.length > 0) { //console.log("Adding ability", role.system.specialability) newItems = newItems.concat(duplicate(role.system.specialability)) // Add new ability this.applyAbility(role.system.specialability[0], newItems) } await this.update(updates) await this.createEmbeddedDocuments('Item', newItems) } /* -------------------------------------------- */ computeCurrentHindrances(statKey) { let hindrancesDices = 0 if (this.type == "character" || this.type == 'npc') { hindrancesDices += this.system.combat.hindrancedice let overCapacity = Math.floor(this.encCurrent / this.getEncumbranceCapacity()) if (overCapacity > 0) { hindrancesDices += overCapacity } let effects = this.items.filter(item => item.type == 'effect') for (let effect of effects) { if (effect.system.hindrance) { hindrancesDices += effect.system.effectlevel } } if (statKey.toLowerCase() == "stl" && this.system.secondary.concealment.status == "exposed") { hindrancesDices += 1 } if (statKey.toLowerCase() == "stl" && (this.system.secondary.concealment.status == "detected" || this.system.secondary.concealment.status == "located")) { hindrancesDices += 3 } } if (this.type == "vehicle") { if (this.isVehicleCrawling()) { hindrancesDices += 3 } if (this.isVehicleSlow()) { hindrancesDices += 1 } if (this.isVehicleAverage()) { hindrancesDices += 1 } if (this.isVehicleFast()) { hindrancesDices += 3 } if (this.isVehicleExFast()) { hindrancesDices += 5 } } return hindrancesDices } /* -------------------------------------------- */ addHindrancesList(effectsList) { if (this.type == "character" || this.type == 'npc') { if (this.system.combat.stunlevel > 0) { effectsList.push({ label: "Stun Hindrance", type: "hindrance", foreign: true, actorId: this.id, applied: false, value: 2 }) } if (this.system.combat.hindrancedice > 0) { effectsList.push({ label: "Wounds Hindrance", type: "hindrance", foreign: true, actorId: this.id, applied: false, value: this.system.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 }) } /* Remove as per ticket #159if (this.system.biodata.morality <= 0) { effectsList.push({ label: "Morality Hindrance", type: "hindrance", foreign: true, actorId: this.id, applied: false, value: 3 }) }*/ let effects = this.items.filter(item => item.type == 'effect') for (let effect of effects) { effect = duplicate(effect) if (effect.system.hindrance) { effectsList.push({ label: effect.name, type: "effect", foreign: true, actorId: this.id, applied: false, effect: effect, value: effect.system.effectlevel }) } } } if (this.type == "vehicle") { if (this.system.stun.value > 0) { effectsList.push({ label: "Stun Hindrance", type: "hindrance", foreign: true, actorId: this.id, applied: false, value: 2 }) } if (this.isVehicleCrawling()) { effectsList.push({ label: "Crawling Hindrance", type: "hindrance", foreign: true, actorId: this.id, applied: false, value: 3 }) } if (this.isVehicleSlow()) { effectsList.push({ label: "Slow Hindrance", type: "hindrance", foreign: true, actorId: this.id, applied: false, value: 1 }) } if (this.isVehicleAverage()) { effectsList.push({ label: "Average Hindrance", type: "hindrance", foreign: true, actorId: this.id, applied: false, value: 1 }) } if (this.isVehicleFast()) { effectsList.push({ label: "Fast Hindrance", type: "hindrance", foreign: true, actorId: this.id, applied: false, value: 3 }) } if (this.isVehicleExFast()) { effectsList.push({ label: "Ext. Fast Hindrance", type: "hindrance", foreign: true, actorId: this.id, applied: false, value: 5 }) } } } /* -------------------------------------------- */ /* ROLL SECTION /* -------------------------------------------- */ pushEffect(rollData, effect) { if ((this.getTraumaState() == "none" && !this.checkNoBonusDice()) || !effect.system.bonusdice) { rollData.effectsList.push({ label: effect.name, type: "effect", applied: false, effect: effect, value: effect.system.effectlevel }) } } /* -------------------------------------------- */ addEffects(rollData, isInit = false, isPower = false, isPowerDmg = false) { let effects = this.items.filter(item => item.type == 'effect') for (let effect of effects) { effect = duplicate(effect) if (!effect.system.hindrance && !effect.system.statdice && !effect.system.reducedicevalue && (effect.system.stataffected != "notapplicable" || effect.system.specaffected.length > 0) && effect.system.stataffected != "special" && effect.system.stataffected != "powerroll" && effect.system.stataffected != "powerdmgroll") { if (effect.system.effectstatlevel) { effect.system.effectlevel = this.system.statistics[effect.system.effectstat].value } this.pushEffect(rollData, effect) } if (isPower && effect.system.stataffected == "powerroll") { this.pushEffect(rollData, effect) } if (isPowerDmg && effect.system.stataffected == "powerdmgroll") { this.pushEffect(rollData, effect) } } } /* -------------------------------------------- */ addRoleBonus(rollData, statKey, subKey) { let role = this.getRole() if (role && role.name.toLowerCase() == "ranged" && subKey == "ranged-dmg") { rollData.effectsList.push({ label: "Ranged Role Bonus", type: "effect", applied: true, isdynamic: true, value: this.getRoleLevel() }) rollData.dicePool = rollData.dicePool.concat(PegasusUtility.buildDicePool("effect-bonus-dice", this.getRoleLevel(), 0, "Ranged Role Bonus")) } if (role && role.name.toLowerCase() == "defender" && (subKey == "defence" || subKey == "dmg-res")) { rollData.effectsList.push({ label: "Defender Role Bonus", type: "effect", applied: true, isdynamic: true, value: this.getRoleLevel() }) rollData.dicePool = rollData.dicePool.concat(PegasusUtility.buildDicePool("effect-bonus-dice", this.getRoleLevel(), 0, "Defender Role Bonus")) } if (role && role.name.toLowerCase() == "scrapper" && statKey == "com") { rollData.effectsList.push({ label: "Scrapper Role Bonus", type: "effect", applied: true, isdynamic: true, value: this.getRoleLevel() }) rollData.dicePool = rollData.dicePool.concat(PegasusUtility.buildDicePool("effect-bonus-dice", this.getRoleLevel(), 0, "Scrapper Role Bonus")) } } /* -------------------------------------------- */ addArmorsShields(rollData, statKey = "none", useShield = false, subKey = undefined) { if (statKey == 'phy' && subKey == "dmg-res") { let armors = this.getArmors() for (let armor of armors) { rollData.armorsList.push({ label: `Armor ${armor.name}`, type: "armor", applied: false, value: armor.system.resistance, adrl: armor.system.adrl }) } } if (useShield) { let shields = this.items.filter(item => item.type == "shield" && item.system.equipped) for (let sh of shields) { rollData.armorsList.push({ label: `Shield ${sh.name}`, type: "shield", applied: false, value: sh.system.level }) } } } addWeapons(rollData, statKey) { let weapons = this.getWeapons() for (let weapon of weapons) { if (weapon.system.equipped && weapon.system.statistic == statKey) { rollData.weaponsList.push({ label: `Attack ${weapon.name}`, type: "attack", applied: false, weapon: weapon, value: 0, damageDice: PegasusUtility.getDiceFromLevel(0) }) } if (weapon.system.equipped && weapon.system.canbethrown && statKey == "agi") { rollData.weaponsList.push({ label: `Attack ${weapon.name}`, type: "attack", applied: false, weapon: weapon, value: 0, damageDice: PegasusUtility.getDiceFromLevel(0) }) } if (weapon.system.equipped && weapon.system.enhanced && weapon.system.enhancedstat == statKey) { rollData.weaponsList.push({ label: `Enhanced Attack ${weapon.name}`, type: "enhanced", applied: false, weapon: weapon, value: weapon.system.enhancedlevel, damageDice: PegasusUtility.getDiceFromLevel(weapon.system.enhancedlevel) }) } if (weapon.system.equipped && weapon.system.damagestatistic == statKey) { rollData.weaponsList.push({ label: `Damage ${weapon.name}`, type: "damage", applied: false, weapon: weapon, value: weapon.system.damage, damageDice: PegasusUtility.getDiceFromLevel(weapon.system.damage) }) } } } addEquipments(rollData, statKey) { let equipments = this.getEquipmentsOnly() for (let equip of equipments) { if (equip.system.equipped && equip.system.stataffected == statKey) { rollData.equipmentsList.push({ label: `Item ${equip.name}`, type: "item", applied: false, equip: equip, value: equip.system.level }) } } } addVehicleWeapons(rollData, vehicle) { if (vehicle) { let modules = vehicle.items.filter(item => item.type == "vehicleweaponmodule" && item.system.activated) if (modules && modules.length > 0) { for (let module of modules) { rollData.vehicleWeapons.push({ label: `Weapon ${module.name}`, type: "item", applied: false, weapon: module, value: module.system.damagedicevalue }) } } } } /* -------------------------------------------- */ processVehicleTargetMessage(rollData) { if (rollData.defenderTokenId) { let vehicle = game.canvas.tokens.get(rollData.defenderTokenId).actor if (vehicle.type == "vehicle") { if (rollData.subKey == "defence" && vehicle.system.statistics.ad.currentspeed == "crawling") { ChatMessage.create({ content: `${vehicle.name} is moving at Crawling speed : add a D8 to your Attack Dice Pool against this Vehicle` }) } if (rollData.subKey == "defence" && vehicle.system.statistics.ad.currentspeed == "slow") { ChatMessage.create({ content: `${vehicle.name} is moving at Slow speed : add a D4 to your Attack Dice Pool against this Vehicle` }) } } if (this.type == "vehicle" && rollData.statKey == "man" && this.system.statistics.ad.currentspeed == "fast") { ChatMessage.create({ content: `${this.name} is moving at Fast speed : anyone using the vehicles MAN Dice Pool suffers a D8 Hindrance which is added to the Difficulty Dice Pool against this Vehicle` }) } if (this.type == "vehicle" && rollData.statKey == "man" && this.system.statistics.ad.currentspeed == "extfast") { ChatMessage.create({ content: `${this.name} is moving at Extremely Fast speed : anyone using the vehicles MAN Dice Pool suffers a D12 Hindrance which is added to the Difficulty Dice Pool against this Vehicle` }) } if (rollData.subKey == "defence" && rollData.vehicle && rollData.vehicle.system.statistics.ad.currentspeed == "crawling") { ChatMessage.create({ content: `${this.name} is moving at Crawling speed : add a D8 to your Attack Dice Pool against this Vehicle` }) } if (rollData.subKey == "defence" && rollData.vehicle && rollData.vehicle.system.statistics.ad.currentspeed == "slow") { ChatMessage.create({ content: `${this.name} is moving at Slow speed : add a D4 to your Attack Dice Pool against this Vehicle` }) } } } /* -------------------------------------------- */ getCommonRollData(statKey = undefined, useShield = false, isInit = false, isPower = false, subKey = "", vehicle = undefined) { let rollData = PegasusUtility.getBasicRollData(isInit) rollData.alias = this.name rollData.actorImg = this.img rollData.actorType = this.type rollData.tokenId = this.token?.id rollData.actorId = (this.token) ? this.token.actor.id : this.id rollData.img = this.img rollData.traumaState = this.getTraumaState() rollData.levelRemaining = this.getLevelRemaining() rollData.activePerks = duplicate(this.getActivePerks()) rollData.diceList = PegasusUtility.getDiceList() rollData.noBonusDice = this.checkNoBonusDice() rollData.dicePool = [] rollData.subKey = subKey if (subKey == "melee-dmg" || subKey == "ranged-dmg" || subKey == "power-dmg") { rollData.isDamage = true } if (statKey) { rollData.statKey = statKey rollData.stat = this.getStat(statKey) if (rollData.stat.value != undefined) { rollData.stat.level = rollData.stat.value // Normalize rollData.statDicesLevel = rollData.stat.level + rollData.stat.bonuseffect } if (rollData.stat.currentlevel) { rollData.stat.level = rollData.stat.currentlevel rollData.statDicesLevel = rollData.stat.currentlevel } rollData.statMod = rollData.stat.mod if (vehicle) { rollData.vehicle = duplicate(vehicle) if (subKey == "melee-dmg") { if (vehicle.isVehicleFullStop()) { ui.notifications.warn("MR not added to Melee Damage due to Full Stop.") } else { rollData.statVehicle = vehicle.system.statistics.mr rollData.vehicleKey = "mr" } this.addVehicleWeapons(rollData, vehicle) } if (subKey == "ranged-atk") { rollData.statVehicle = vehicle.system.statistics.fc rollData.vehicleKey = "fc" } if (subKey == "ranged-dmg") { this.addVehicleWeapons(rollData, vehicle) } if (subKey == "defence") { if (vehicle.isVehicleFullStop()) { ui.notifications.warn("MAN not added to Defense due to Full Stop.") } else { rollData.statVehicle = vehicle.system.statistics.man rollData.vehicleKey = "man" } vehicle.addVehicleShields(rollData) } vehicle.addEffects(rollData, false, false, false) } rollData.specList = this.getRelevantSpec(statKey) rollData.selectedSpec = "0" if (statKey.toLowerCase() == "mr") { rollData.img = "systems/fvtt-pegasus-rpg/images/icons/MR.webp" } else { let abbrev = rollData.stat.abbrev.toUpperCase() rollData.img = `systems/fvtt-pegasus-rpg/images/icons/${abbrev}.webp` } rollData.dicePool = rollData.dicePool.concat(PegasusUtility.buildDicePool("stat", rollData.statDicesLevel, rollData.stat.mod)) if (rollData.statVehicle) { rollData.dicePool = rollData.dicePool.concat(PegasusUtility.buildDicePool("statvehicle", rollData.statVehicle.currentlevel, 0)) } } if (statKey == "mr") { if (this.type == "character" || this.type == 'npc') { rollData.mrVehicle = PegasusUtility.checkIsVehicleCrew(this.id) if (rollData.mrVehicle) { rollData.effectsList.push({ label: `Vehicle ${rollData.mrVehicle.name} MR Bonus`, type: "effect", applied: false, isdynamic: true, value: rollData.mrVehicle.system.statistics.mr.currentlevel }) } } if (this.type == "vehicle") { for (let member of this.system.crew) { let actor = game.actors.get(member.id) let specList = actor.getRelevantSpec("mr") rollData.effectsList.push({ label: `Crew ${actor.name} MR Bonus`, type: "effect", applied: false, isdynamic: true, value: actor.system.mr.value + actor.system.mr.bonuseffect, specList: specList }) } } } rollData.hindranceDices = this.computeCurrentHindrances(statKey) this.processSizeBonus(rollData) this.addEffects(rollData, isInit, isPower, subKey == "power-dmg") this.addArmorsShields(rollData, statKey, useShield, subKey) this.addWeapons(rollData, statKey, useShield) this.addEquipments(rollData, statKey) this.addRoleBonus(rollData, statKey, subKey) this.processVehicleTargetMessage(rollData) console.log("ROLLDATA", rollData) return rollData } /* -------------------------------------------- */ processSizeBonus(rollData) { if (rollData.defenderTokenId) { let diffSize = 0 if (this.type == "character" || this.type == 'npc') { this.system.biodata.sizenum = this.system.biodata?.sizenum ?? 0 this.system.biodata.sizebonus = this.system.biodata?.sizebonus ?? 0 diffSize = rollData.defenderSize - this.system.biodata.sizenum + this.system.biodata.sizebonus } else { diffSize = rollData.defenderSize - this.system.statistics.hr.size } //console.log("Diffsize", diffSize) if (rollData.subKey == "melee-atk" || rollData.subKey == "ranged-atk") { if (diffSize > 0) { rollData.effectsList.push({ label: "Size Bonus", type: "effect", applied: false, isdynamic: true, value: diffSize }) } } if (rollData.subKey == "dmg-res") { if (diffSize < 0) { rollData.effectsList.push({ label: "Size Bonus", type: "effect", applied: false, isdynamic: true, value: Math.abs(diffSize) }) } } if (rollData.subKey == "defence") { if (diffSize > 0) { rollData.effectsList.push({ label: "Size Bonus", type: "effect", applied: false, isdynamic: true, value: Math.abs(diffSize) }) } } if (rollData.subKey == "melee-dmg" || rollData.subKey == "ranged-dmg" || rollData.subKey == "power-dmg") { if (diffSize < 0) { rollData.effectsList.push({ label: "Size Bonus", type: "effect", applied: false, isdynamic: true, value: Math.abs(diffSize) }) } } } } /* -------------------------------------------- */ getExtraTICsFromEffect() { let effects = this.items.filter(it => it.type == "effect" && Number(it.system.extratics)>0) let nbTics = 0 for (let e of effects) { nbTics += Number(it.system.extratics) } return nbTics } /* -------------------------------------------- */ getLevelRemainingList() { let options = [] for (let i = 0; i <= this.system.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.items.get(itemId) if (power) { power = duplicate(power) this.rollPool(power.system.dmgstatistic, false, "power-dmg") } } /* -------------------------------------------- */ rollPool(statKey, useShield = false, subKey = "none", vehicle = undefined) { let stat = this.getStat(statKey) if (stat) { let rollData = this.getCommonRollData(statKey, useShield, false, false, subKey, vehicle) 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" 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.system.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.system.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.type == "vehicle") ? this.system.statistics.mr : this.system.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" rollData.nbTIC = ((this.type == "character") ? 2 : 1) + this.getExtraTICsFromEffect() rollData.TICs = [] for(let i=0; i effect.system.isspeed != undefined) if (effect) { await this.deleteEmbeddedDocuments("Item", [effect.id]) } if (speed == "fullstop") { this.update({ 'system.secondary.moverange': "nomovement" }) } if (speed == "crawling") { await this.update({ 'system.secondary.moverange': "threatzone" }) await this.manageVehicleSpeedBonus("crawling", "Crawling MAN Bonus", "man", 3) } if (speed == "slow") { await this.update({ 'system.secondary.moverange': "close" }) await this.manageVehicleSpeedBonus("slow", "Slow MAN Bonus", "man", 1) } if (speed == "average") { await this.update({ 'system.secondary.moverange': "medium" }) await this.manageVehicleSpeedBonus("average", "Avoid attack Bonus", "all", 1) } if (speed == "fast") { await this.update({ 'system.secondary.moverange': "long" }) await this.manageVehicleSpeedBonus("fast", "Avoid attack Bonus", "all", 3) } if (speed == "extfast") { await this.update({ 'system.secondary.moverange': "extreme" }) await this.manageVehicleSpeedBonus("extfast", "Avoid attack Bonus", "all", 5) } } /* -------------------------------------------- */ modifyVehicleStun(incDec) { let stun = this.system.stun.value + incDec this.update({ 'system.stun.value': stun }) } /* -------------------------------------------- */ addTopSpeedBonus(topspeed, bonus) { let num = __speed2Num[topspeed] + Number(bonus) num = Math.max(0, num) num = Math.min(num, __num2speed.length - 1) return __num2speed[num] } /* -------------------------------------------- */ async manageVehicleSpeedBonus(speed, name, stat, level) { let effect = duplicate(__bonusEffect) effect.id = randomID(16) effect.name = name effect.system.stataffected = stat effect.system.effectlevel = level effect.system.isspeed = speed await this.createEmbeddedDocuments("Item", [effect]) } /* -------------------------------------------- */ processVehicleStatEffects() { let effectsPlus = this.items.filter(effect => effect.type == "effect" && effect.system.genre == "positive" && effect.system.statdice) let effectsMinus = this.items.filter(effect => effect.type == "effect" && effect.system.genre == "negative" && effect.system.reducedicevalue) for (let statKey in this.system.statistics) { let stat = duplicate(this.system.statistics[statKey]) let bonus = 0 for (let effect of effectsPlus) { if (effect.system.stataffected == statKey) { bonus += Number(effect.system.effectlevel) } } for (let effect of effectsMinus) { if (effect.system.stataffected == statKey) { bonus -= Number(effect.system.effectlevel) } } if (bonus != stat.bonuseffect) { stat.bonuseffect = bonus if (stat.bonuseffect + stat.level < 1) { stat.level = 1 stat.bonuseffect = 0 } if (stat.currentlevel > stat.bonuseffect + stat.level) { stat.currentlevel = stat.bonuseffect + stat.level } this.update({ [`system.statistics.${statKey}`]: stat }) } } } /* -------------------------------------------- */ async computeVehicleStats() { if (this.type == "vehicle") { for (let statDef of __statBuild) { let sum = 0 let list = [] for (let moduleType of statDef.modules) { list = list.concat(this.items.filter(item => item.type == moduleType)) } if (list && list.length > 0) { sum = list.reduce((value, item2) => value + Number(item2.system[statDef.itemfield]), 0) } //console.log("Processing", statDef.field, this.system.statistics[statDef.field].level, list, sum) if (statDef.subfield) { if (sum != Number(this.system.statistics[statDef.field][statDef.subfield])) { //console.log("Update", statDef.field, statDef.subfield, sum, this.system.statistics[statDef.field][statDef.subfield]) this.update({ [`system.statistics.${statDef.field}.${statDef.subfield}`]: sum }) } } else { if (sum != Number(this.system.statistics[statDef.field].level)) { this.update({ [`system.statistics.${statDef.field}.level`]: sum, [`system.statistics.${statDef.field}.currentlevel`]: sum }) if (statDef.additionnal1) { if (sum != Number(this.system.statistics[statDef.field][statDef.additionnal1])) { this.update({ [`system.statistics.${statDef.field}.${statDef.additionnal1}`]: sum }) } } if (statDef.additionnal2) { if (sum != Number(this.system.statistics[statDef.field][statDef.additionnal2])) { this.update({ [`system.statistics.${statDef.field}.${statDef.additionnal2}`]: sum }) } } } } } // Top speed management let mobility = this.items.find(item => item.type == "mobilitymodule") let arcs = duplicate(this.system.arcs) if (mobility) { let propulsion = this.items.find(item => item.type == "propulsionmodule") let bonus = (propulsion) ? propulsion.system.topspeed : 0 arcs.frontarc.topspeed = this.addTopSpeedBonus(mobility.system.ts_f, bonus) arcs.rightarc.topspeed = mobility.system.ts_s arcs.leftarc.topspeed = mobility.system.ts_s arcs.toparc.topspeed = mobility.system.ts_s arcs.bottomarc.topspeed = mobility.system.ts_s arcs.reararc.topspeed = mobility.system.ts_r } else { arcs.frontarc.topspeed = "fullstop" arcs.rightarc.topspeed = "fullstop" arcs.leftarc.topspeed = "fullstop" arcs.toparc.topspeed = "fullstop" arcs.bottomarc.topspeed = "fullstop" arcs.reararc.topspeed = "fullstop" } for (let key in this.system.arcs) { if (this.system.arcs[key].topspeed != arcs[key].topspeed) { this.update({ 'system.arcs': arcs }) } } // VMS management let hull = this.items.find(item => item.type == "vehiclehull") let modules = duplicate(this.system.modules) if (hull) { modules.totalvms = Number(hull.system.vms) } else { modules.totalvms = 0 } let spaceList = this.items.filter(item => item.type == "vehiclemodule") || [] spaceList = spaceList.concat(this.items.filter(item => item.type == "vehicleweaponmodule") || []) let space = 0 if (spaceList && spaceList.length > 0) { space = spaceList.reduce((value, item2) => value + Number(item2.system.space), 0) } modules.vmsused = space if (modules.totalvms != this.system.modules.totalvms || modules.usedvms != this.system.modules.vmsused) { this.update({ 'system.modules': modules }) } if (modules.vmsused > modules.totalvms) { ui.notifications.warn("Warning! No more space available in cargo !!") } // Destroyed if (this.system.statistics.hr.currentlevel == 0) { ChatMessage.create({ content: `The vehicle ${this.name} has been destroyed !` }) } this.processVehicleArmorShields() this.processVehicleStatEffects() } } /* -------------------------------------------- */ getTotalCost() { let sumCost = 0 for (let item of this.items) { if (__isVehicle[item.type]) { if (item.system.cost) { sumCost += Number(item.system.cost) } } } return sumCost } /* -------------------------------------------- */ async preprocessItemVehicle(event, item, onDrop = false) { if (item.type != "effect" && !__isVehicle[item.type]) { ui.notifications.warn("You can't drop Character items over a vehicle sheet.") return } if (item.type == "effect" && item.system.droptext && item.system.droptext.length > 0) { ChatMessage.create({ content: `Effect ${item.name} message : ${item.system.droptext}` }) } //console.log(">>>>> item", item.type, __isVehicleUnique[item.type]) if (__isVehicleUnique[item.type]) { let toDelList = [] for (let toDel of this.items) { if (toDel.type == item.type) { toDelList.push(toDel.id) } } //console.log("TODEL : ", toDelList) if (toDelList.length > 0) { await this.deleteEmbeddedDocuments('Item', toDelList) } } // Check size if (item.type == "vehiclemodule" || item.type == "vehicleweaponmodule") { item.system.space = item.system?.space || 0 if (this.system.modules.usedvms + Number(item.system.space) > this.system.modules.totalvms) { ChatMessage.create({ content: `No more room available to host module ${item.name}. Module is not added to the vehicle.` }) return false } } // NRG max management if (item.type == "powercoremodule") { if (this.system.statistics.pc.maxnrg != item.system.nrg) { this.update({ 'system.statistics.pc.maxnrg': item.system.nrg }) } } // Cargo management if (__isVehicleCargo[item.type]) { let capacity = this.getCurrentCargoCapacity() if (item.type == "cargo") { capacity += Number(item.system.capacity) } else { let q = item.system.quantity || 1 capacity += Number(q) * Number(item.system.weight) } console.log("capa", capacity, this.system.cargo.cargocapacity) if (capacity > this.system.cargo.cargocapacity) { ui.notifications.warn("Your cargo capacity is already full, unable to add this content : " + item.name) return false } } return true } /* -------------------------------------------- */ getCrewList() { let crew = [] for (let actorDef of this.system.crew) { let actor = game.actors.get(actorDef.id) if (actor) { crew.push({ name: actor.name, img: actor.img, id: actor.id }) } } return crew } addCrew(actorId) { if (this.system.crew.length >= this.system.crewmax) { ui.notifications.warn("Vehicle crew is already full.") return } let crewList = duplicate(this.system.crew.filter(actorDef => actorDef.id != actorId) || []) crewList.push({ id: actorId }) this.update({ 'system.crew': crewList }) } delCrew(actorId) { let crewList = duplicate(this.system.crew.filter(actorDef => actorDef.id != actorId) || []) this.update({ 'system.crew': crewList }) } inCrew(actorId) { return this.system.crew.find(member => member.id == actorId) } /* -------------------------------------------- */ isVehicleFullStop() { return this.system.statistics.ad.currentspeed == "fullstop" } isVehicleCrawling() { return this.system.statistics.ad.currentspeed == "crawling" } isVehicleSlow() { return this.system.statistics.ad.currentspeed == "slow" } isVehicleAverage() { return this.system.statistics.ad.currentspeed == "average" } isVehicleFast() { return this.system.statistics.ad.currentspeed == "fast" } isVehicleExFast() { return this.system.statistics.ad.currentspeed == "extfast" } /* -------------------------------------------- */ isValidActor() { // Find relevant actor let actor for (let actorDef of this.system.crew) { let actorTest = game.actors.get(actorDef.id) if (actorTest.testUserPermission(game.user, "OWNER")) { return actorTest } } if (!actor) { ui.notifications.warn("You do no own any actors in the crew of this vehicle.") return } } /* -------------------------------------------- */ rollPoolFromVehicle(statKey, useShield = false, subKey = "none") { let actor = this.isValidActor() if (actor) { actor.rollPool(statKey, useShield, subKey, this) } } /* -------------------------------------------- */ addVehicleShields(rollData) { for (let arcKey in this.system.arcs) { let arc = this.system.arcs[arcKey] if (arc.shieldlevel > 0) { rollData.vehicleShieldList.push({ label: `${arc.label} Shield`, type: "vehicleshield", applied: false, value: arc.shieldlevel }) } } } /* -------------------------------------------- */ addVehicleArmors(rollData) { for (let arcKey in this.system.arcs) { let arc = this.system.arcs[arcKey] if (arc.armourlevel > 0) { rollData.vehicleShieldList.push({ label: `${arc.label} Armor`, type: "vehicleshield", applied: false, value: arc.armourlevel }) } } } /* -------------------------------------------- */ rollVehicleDamageResistance() { let actor = this.isValidActor() if (actor) { let stat = this.getStat("hr") let rollData = this.getCommonRollData("hr") rollData.vehicle = duplicate(this) rollData.isVehicleStun = true rollData.mode = "stat" rollData.title = `Stat ${stat.label}` this.addVehicleArmors(rollData) this.startRoll(rollData) } } /* -------------------------------------------- */ async addVehicleModuleEffects(mod) { let effects = [] for (let effect of mod.system.effects) { if (!effect.system) { effect.system = effect.data } effect.system.effectId = mod.id // Link to the perk, in order to dynamically remove them effects.push(effect) } if (effects.length) { await this.createEmbeddedDocuments('Item', effects) } } /* -------------------------------------------- */ async removeVehicleModuleEffects(mod) { let toRem = [] for (let item of this.items) { if (item.type == 'effect' && item.system.effectId == mod.id) { toRem.push(item.id) } } console.log("TODEL", toRem) if (toRem.length) { await this.deleteEmbeddedDocuments('Item', toRem) } } /* -------------------------------------------- */ async activateVehicleModule(itemId) { let mod = this.items.get(itemId) if (mod) { if (mod.system.nrg && mod.system.nrg > 0) { let pc = duplicate(this.system.statistics.pc) if (!mod.system.activated) { // Previous state was non activated -> activated now if (mod.system.nrg > pc.curnrg) { ChatMessage.create({ content: `The Vehicle ${this.name} does not have enough Energy to Activate this module at this time.` }) return } await this.updateEmbeddedDocuments('Item', [{ _id: mod.id, 'system.activated': !mod.system.activated }]) pc.actnrg += Number(mod.system.nrg) pc.maxnrg -= Number(mod.system.nrg) pc.curnrg -= Number(mod.system.nrg) this.update({ 'system.statistics.pc': pc }) this.addVehicleModuleEffects(mod) } else { // Now deactivated pc.actnrg -= Number(mod.system.nrg) pc.maxnrg += Number(mod.system.nrg) this.update({ 'system.statistics.pc': pc }) await this.updateEmbeddedDocuments('Item', [{ _id: mod.id, 'system.activated': !mod.system.activated }]) this.removeVehicleModuleEffects(mod) } } else { if (mod.system.activated) { this.removeVehicleModuleEffects(mod) } else { this.addVehicleModuleEffects(mod) } await this.updateEmbeddedDocuments('Item', [{ _id: mod.id, 'system.activated': !mod.system.activated }]) } } this.processVehicleArmorShields() } /* -------------------------------------------- */ processVehicleArmorShields() { // Shield management let shieldsList = this.items.filter(item => item.type == "vehiclemodule" && item.system.category == "shield" && item.system.activated) || [] let level = 0 for (let armour of shieldsList) { if (armour.system.shielddicevalue > 0) { level += Number(armour.system.shielddicevalue) } } let shift = 500 for (let loc of __LocationsArmour) { let arcKey = loc + "arc" let arc = duplicate(this.system.arcs[arcKey]) if (level != Number(arc.maxshieldlevel)) { let lvChanged = level - arc.maxshieldlevel arc.maxshieldlevel = level arc.shieldlevel += lvChanged if (arc.shieldlevel <= 0 || arc.shieldlevel > level) { arc.shieldlevel = level } setTimeout(shift, this.update({ [`system.arcs.${arcKey}`]: arc })) shift += 200 } } // Armour management let armorsList = this.items.filter(item => item.type == "vehiclemodule" && item.system.category == "armor" && item.system.activated) || [] for (let loc of __LocationsArmour) { let arcKey = loc + "arc" let arc = duplicate(this.system.arcs[arcKey]) let level = 0 for (let armour of armorsList) { if (armour.system.location == loc && armour.system.armourdicevalue > 0) { level += Number(armour.system.armourdicevalue) } } if (level != Number(arc.maxarmourlevel)) { let lvChanged = level - arc.maxarmourlevel arc.maxarmourlevel = level arc.armourlevel += lvChanged if (arc.armourlevel <= 0 || arc.armourlevel > arc.maxarmourlevel) { arc.armourlevel = level } setTimeout(500, this.update({ [`system.arcs.${arcKey}`]: arc })) } } } /* -------------------------------------------- */ updateMaxNrg(currentLevel) { this.update({ 'system.statistics.pc.maxnrg': Number(PegasusUtility.getDiceValue(currentLevel)) }) } /* -------------------------------------------- */ setTurningArc(currentLevel) { this.update({ 'system.statistics.man.turningarc45': Number(currentLevel) }) } /* -------------------------------------------- */ getCurrentCargoCapacity() { let capacity = 0 for (let cargo of this.items) { if (cargo.type == "equipment" || cargo.type == "weapon" || cargo.type == "armor" || cargo.type == "money" || cargo.type == "shield") { let q = cargo.system.quantity || 1 capacity += Number(q) * Number(cargo.system.weight) } if (cargo.type == "cargo") { capacity += Number(cargo.system.capacity) } } return capacity } }