/* -------------------------------------------- */ import { PegasusUtility } from "./pegasus-utility.js"; import { PegasusRollDialog } from "./pegasus-roll-dialog.js"; /* -------------------------------------------- */ const coverBonusTable = { "nocover": 0, "lightcover": 2, "heavycover": 4, "entrenchedcover": 6 }; /* -------------------------------------------- */ /* -------------------------------------------- */ /** * 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') { const skills = await PegasusUtility.loadCompendium("fvtt-weapons-of-the-gods.skills"); data.items = skills.map(i => i.toObject()); } if (data.type == 'npc') { } return super.create(data, options); } /* -------------------------------------------- */ prepareBaseData() { } /* -------------------------------------------- */ async prepareData() { super.prepareData(); } /* -------------------------------------------- */ prepareDerivedData() { if (this.type == 'character') { let h = 0; let updates = []; for (let key in this.data.data.statistics) { let attr = this.data.data.statistics[key]; } /*if ( h != this.data.data.secondary.health.max) { this.data.data.secondary.health.max = h; updates.push( {'data.secondary.health.max': h} ); }*/ if (updates.length > 0) { this.update(updates); } this.computeNRGHealth(); } super.prepareDerivedData(); } /* -------------------------------------------- */ _preUpdate(changed, options, user) { super._preUpdate(changed, options, user); } /* -------------------------------------------- */ getActivePerks() { let perks = this.data.items.filter(item => item.type == 'perk' && item.data.data.active); return perks; } /* -------------------------------------------- */ getAbilities() { let ab = this.data.items.filter(item => item.type == 'ability'); return ab; } /* -------------------------------------------- */ getPerks() { let comp = this.data.items.filter(item => item.type == 'perk'); return comp; } /* -------------------------------------------- */ getEffects() { let comp = this.data.items.filter(item => item.type == 'effect'); return comp; } /* -------------------------------------------- */ getPowers() { let comp = this.data.items.filter(item => item.type == 'power'); return comp; } /* -------------------------------------------- */ getArmors() { let comp = duplicate(this.data.items.filter(item => item.type == 'armor') || []); return comp; } /* -------------------------------------------- */ getShields() { let comp = this.data.items.filter(item => item.type == 'shield'); return comp; } getRace() { let race = this.data.items.filter(item => item.type == 'race'); return race[0] ?? []; } getRole() { let role = this.data.items.filter(item => item.type == 'role'); return role[0] ?? []; } /* -------------------------------------------- */ checkAndPrepareArmor(armor) { armor.data.resistanceDice = PegasusUtility.getDiceFromLevel(armor.data.resistance); } /* -------------------------------------------- */ checkAndPrepareArmors(armors) { for (let item of armors) { this.checkAndPrepareArmor(item); } return armors; } /* -------------------------------------------- */ checkAndPrepareWeapon(weapon) { weapon.data.damageDice = PegasusUtility.getDiceFromLevel(weapon.data.damage); } /* -------------------------------------------- */ checkAndPrepareWeapons(weapons) { for (let item of weapons) { this.checkAndPrepareWeapon(item); } return weapons; } /* -------------------------------------------- */ getWeapons() { let comp = duplicate(this.data.items.filter(item => item.type == 'weapon') || []); return comp; } /* -------------------------------------------- */ getItemById(id) { let item = this.data.items.find(item => item.id == id); if (item) { item = duplicate(item) if (item.type == 'specialisation') { item.data.dice = PegasusUtility.getDiceFromLevel(item.data.level); } } return item; } /* -------------------------------------------- */ getSpecs() { let comp = duplicate(this.data.items.filter(item => item.type == 'specialisation') || []); for (let c of comp) { c.data.dice = PegasusUtility.getDiceFromLevel(c.data.level); } return comp; } /* -------------------------------------------- */ getRelevantSpec(statKey) { let comp = duplicate(this.data.items.filter(item => item.type == 'specialisation' && item.data.data.statistic == statKey) || []); for (let c of comp) { c.data.dice = PegasusUtility.getDiceFromLevel(c.data.level); } return comp; } /* -------------------------------------------- */ async activatePerk(perkId) { let item = this.data.items.find(item => item.id == perkId); if (item && item.data.data) { let update = { _id: item.id, "data.active": !item.data.data.active }; await this.updateEmbeddedDocuments('Item', [update]); // Updates one EmbeddedEntity } } /* -------------------------------------------- */ async activatePower(itemId) { let item = this.data.items.find(item => item.id == itemId); if (item && item.data.data) { let update = { _id: item.id, "data.activated": !item.data.data.activated }; await this.updateEmbeddedDocuments('Item', [update]); // Updates one EmbeddedEntity } } /* -------------------------------------------- */ async equipItem(itemId) { let item = this.data.items.find(item => item.id == itemId); if (item && item.data.data) { let update = { _id: item.id, "data.equipped": !item.data.data.equipped }; await this.updateEmbeddedDocuments('Item', [update]); // Updates one EmbeddedEntity } } /* -------------------------------------------- */ compareName(a, b) { if (a.name < b.name) { return -1; } if (a.name > b.name) { return 1; } return 0; } /* ------------------------------------------- */ getEquipments() { return this.data.items.filter(item => item.type == 'shield' || item.type == 'armor' || item.type == "weapon" || item.type == "equipment"); } /* -------------------------------------------- */ getActiveEffects(matching = it => true) { let array = Array.from(this.getEmbeddedCollection("ActiveEffect").values()); return Array.from(this.getEmbeddedCollection("ActiveEffect").values()).filter(it => matching(it)); } /* -------------------------------------------- */ getEffectByLabel(label) { return this.getActiveEffects().find(it => it.data.label == label); } /* -------------------------------------------- */ getEffectById(id) { return this.getActiveEffects().find(it => it.id == id); } /* -------------------------------------------- */ getAttribute(attrKey) { return this.data.data.attributes[attrKey]; } /* -------------------------------------------- */ async equipGear(equipmentId) { let item = this.data.items.find(item => item.id == equipmentId); if (item && item.data.data) { let update = { _id: item.id, "data.equipped": !item.data.data.equipped }; await this.updateEmbeddedDocuments('Item', [update]); // Updates one EmbeddedEntity } } /* -------------------------------------------- */ getInitiativeScore() { if (this.type == 'character') { // TODO } return 0.0; } /* -------------------------------------------- */ getArmorModifier() { let armors = this.getArmors(); let modifier = 0; for (let armor of armors) { if (armor.data.data.equipped) { if (armor.data.data.type == 'light') modifier += 5; if (armor.data.data.type == 'medium') modifier += 10; if (armor.data.data.type == 'heavy') modifier += 15; } } return modifier; } /* -------------------------------------------- */ async applyDamageLoss(damage) { let chatData = { user: game.user.id, alias: this.name, rollMode: game.settings.get("core", "rollMode"), whisper: [game.user.id].concat(ChatMessage.getWhisperRecipients('GM')), }; //console.log("Apply damage chat", chatData ); if (damage > 0) { let health = duplicate(this.data.data.secondary.health); health.value -= damage; if (health.value < 0) health.value = 0; this.update({ "data.secondary.health.value": health.value }); chatData.content = `${this.name} looses ${damage} health. New health value is : ${health.value}`; } else { chatData.content = `No health loss for ${this.name} !`; } await ChatMessage.create(chatData); } /* -------------------------------------------- */ processNoDefense(attackRollData) { let defenseRollData = { mode: "nodefense", finalScore: 0, defenderName: this.name, attackerName: attackRollData.alias, armorModifier: this.getArmorModifier(), actorId: this.id, alias: this.name, result: 0, } this.syncRoll(defenseRollData); this.processDefenseResult(defenseRollData, attackRollData); } /* -------------------------------------------- */ async processApplyDamage(defenseRollData, attackRollData) { // Processed by the defender actor if (attackRollData && attackRollData) { let result = attackRollData.finalScore; defenseRollData.damageDices = WotGUtility.getDamageDice(result); defenseRollData.damageRoll = await this.rollDamage(defenseRollData); chatData.damages = true; chatData.damageDices = defenseRollData.damageDices; WotGUtility.createChatWithRollMode(this.name, { content: await renderTemplate(`systems/fvtt-weapons-of-the-gods/templates/chat-opposed-damages.html`, chatData) }); } } /* -------------------------------------------- */ async processDefenseResult(defenseRollData, attackRollData) { // Processed by the defenser if (defenseRollData && attackRollData) { let result = attackRollData.finalScore - defenseRollData.finalScore; defenseRollData.defenderName = this.name, defenseRollData.attackerName = attackRollData.alias defenseRollData.result = result defenseRollData.damages = false defenseRollData.damageDices = 0; if (result > 0) { defenseRollData.damageDices = WotGUtility.getDamageDice(result); defenseRollData.damageRoll = await this.rollDamage(defenseRollData, attackRollData); defenseRollData.damages = true; defenseRollData.finalDamage = defenseRollData.damageRoll.total; WotGUtility.updateRollData(defenseRollData); console.log("DAMAGE ROLL OBJECT", defenseRollData); WotGUtility.createChatWithRollMode(this.name, { content: await renderTemplate(`systems/fvtt-weapons-of-the-gods/templates/chat-opposed-damage.html`, defenseRollData) }); } else { WotGUtility.updateRollData(defenseRollData); WotGUtility.createChatWithRollMode(this.name, { content: await renderTemplate(`systems/fvtt-weapons-of-the-gods/templates/chat-opposed-fail.html`, defenseRollData) }); } } } /* -------------------------------------------- */ async rollDamage(defenseRollData, attackRollData) { let weaponDamage = 0; if (attackRollData.weapon?.data?.damage) { weaponDamage = Number(attackRollData.weapon.data.damage); } let formula = defenseRollData.damageDices + "d10+" + defenseRollData.armorModifier + "+" + weaponDamage; console.log("ROLL : ", formula); let myRoll = new Roll(formula).roll({ async: false }); await WotGUtility.showDiceSoNice(myRoll, game.settings.get("core", "rollMode")); return myRoll; } /* -------------------------------------------- */ getSubActors() { let subActors = []; for (let id of this.data.data.subactors) { subActors.push(duplicate(game.actors.get(id))); } return subActors; } /* -------------------------------------------- */ async addSubActor(subActorId) { let subActors = duplicate(this.data.data.subactors); subActors.push(subActorId); await this.update({ 'data.subactors': subActors }); } /* -------------------------------------------- */ async delSubActor(subActorId) { let newArray = []; for (let id of this.data.data.subactors) { if (id != subActorId) { newArray.push(id); } } await this.update({ 'data.subactors': newArray }); } /* -------------------------------------------- */ setDefenseMode(rollData) { console.log("DEFENSE MODE IS SET FOR", this.name); this.data.defenseRollData = rollData; this.data.defenseDefenderId = rollData.defenderId; this.data.defenseAttackerId = rollData.attackerId; } /* -------------------------------------------- */ clearDefenseMode() { this.data.defenseDefenderId = undefined; this.data.defenseAttackerId = undefined; this.data.defenseRollData = undefined; } /* -------------------------------------------- */ syncRoll(rollData) { let linkedRollId = PegasusUtility.getDefenseState(this.id); if (linkedRollId) { rollData.linkedRollId = linkedRollId; } this.lastRollId = rollData.rollId; PegasusUtility.saveRollData(rollData); } /* -------------------------------------------- */ getStat(statKey) { let stat if (statKey == 'mr') { stat = duplicate(this.data.data.mr); } else { stat = duplicate(this.data.data.statistics[statKey]); } stat.dice = PegasusUtility.getDiceFromLevel(stat.value); return stat; } /* -------------------------------------------- */ getOneSpec(specId) { let spec = this.data.items.find(item => item.type == 'specialisation' && item.id == specId); if (spec) { spec = duplicate(spec); spec.data.dice = PegasusUtility.getDiceFromLevel(spec.data.level); } return spec; } /* -------------------------------------------- */ updatePerkRounds(itemId, roundValue) { let item = this.items.get(itemId) if (item) { this.updateEmbeddedDocuments('Item', [{ _id: item.id, 'data.roundcount': roundValue }]); } } /* -------------------------------------------- */ getCommonRollData() { let rollData = { rollId: randomID(16), alias: this.name, actorImg: this.img, actorId: this.id, img: this.img, rollMode: game.settings.get("core", "rollMode"), activePerks: duplicate(this.getActivePerks()), optionsDiceList: PegasusUtility.getOptionsDiceList(), bonusDicesLevel: 0, hindranceDicesLevel: 0, otherDicesLevel: 0, } return rollData } /* -------------------------------------------- */ async startRoll(rollData) { this.syncRoll(rollData); let rollDialog = await PegasusRollDialog.create(this, rollData); console.log(rollDialog); rollDialog.render(true); } /* -------------------------------------------- */ rollPool(statKey, useSPec) { let stat = this.getStat(statKey); if (stat) { let rollData = this.getCommonRollData() rollData.mode = "stat" rollData.specList = this.getRelevantSpec(statKey) rollData.selectedSpec = "0" rollData.stat = stat; this.startRoll(rollData); } else { ui.notifications.warn("Statistic not found !"); } } /* -------------------------------------------- */ rollUnarmedAttack() { let stat = this.getStat('com'); if (stat) { let rollData = this.getCommonRollData() rollData.mode = "stat" rollData.title = `Unarmed Attack`; rollData.stat = stat; 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() rollData.specList = this.getRelevantSpec(statKey) rollData.mode = "stat" rollData.title = `Stat ${stat.label}`; rollData.stat = stat; this.startRoll(rollData); } else { ui.notifications.warn("Statistic not found !"); } } /* -------------------------------------------- */ async rollSpec(specId) { let spec = this.getOneSpec(specId) if (spec) { let rollData = this.getCommonRollData() rollData.mode = "spec" rollData.title = `Spec. : ${spec.name} `, rollData.stat = this.getStat(spec.data.statistic) rollData.spec = spec this.startRoll(rollData); } else { ui.notifications.warn("Specialisation not found !"); } } /* -------------------------------------------- */ async rollMR() { let mr = duplicate(this.data.data.mr); if (mr) { mr.dice = PegasusUtility.getDiceFromLevel(mr.value); let rollData = this.getCommonRollData() rollData.mode = "MR" rollData.stat = mr rollData.activePerks = duplicate(this.getActivePerks()), rollData.specList = this.getRelevantSpec('mr'), this.startRoll(rollData); } else { ui.notifications.warn("MR not found !"); } } /* -------------------------------------------- */ async deleteAllItemsByType(itemType) { let items = this.data.items.filter(item => item.type == itemType); await this.deleteEmbeddedDocuments('Item', items); } /* -------------------------------------------- */ async addItemWithoutDuplicate(newItem) { let item = this.data.items.find(item => item.type == newItem.type && item.name.toLowerCase() == newItem.name.toLowerCase()) if (!item) { await this.createEmbeddedDocuments('Item', [newItem]); } } /* -------------------------------------------- */ async computeNRGHealth() { let updates = {} let phyDiceValue = PegasusUtility.getDiceValue(this.data.data.statistics.phy.value) + this.data.data.secondary.health.bonus + this.data.data.statistics.phy.mod; if (phyDiceValue != this.data.data.secondary.health.max) { updates['data.secondary.health.max'] = phyDiceValue updates['data.secondary.health.value'] = phyDiceValue } let mndDiceValue = PegasusUtility.getDiceValue(this.data.data.statistics.mnd.value) + this.data.data.secondary.delirium.bonus + this.data.data.statistics.mnd.mod; if (mndDiceValue != this.data.data.secondary.delirium.max) { updates['data.secondary.delirium.max'] = mndDiceValue updates['data.secondary.delirium.value'] = mndDiceValue } let stlDiceValue = PegasusUtility.getDiceValue(this.data.data.statistics.stl.value) + this.data.data.secondary.stealthhealth.bonus + this.data.data.statistics.stl.mod; if (stlDiceValue != this.data.data.secondary.stealthhealth.max) { updates['data.secondary.stealthhealth.max'] = stlDiceValue updates['data.secondary.stealthhealth.value'] = stlDiceValue } let socDiceValue = PegasusUtility.getDiceValue(this.data.data.statistics.soc.value) + this.data.data.secondary.socialhealth.bonus + this.data.data.statistics.soc.mod; if (socDiceValue != this.data.data.secondary.socialhealth.max) { updates['data.secondary.socialhealth.max'] = socDiceValue updates['data.secondary.socialhealth.value'] = socDiceValue } let nrgValue = PegasusUtility.getDiceValue(this.data.data.statistics.foc.value) + this.data.data.nrg.mod + this.data.data.statistics.foc.mod; if (nrgValue != this.data.data.nrg.max) { updates['data.nrg.max'] = nrgValue updates['data.nrg.value'] = nrgValue } if (nrgValue != this.data.data.combat.stunthreshold) { updates['data.combat.stunthreshold'] = nrgValue } let mrLevel = (this.data.data.statistics.agi.value + this.data.data.statistics.str.value) - this.data.data.statistics.phy.value mrLevel = (mrLevel < 1) ? 1 : mrLevel; if (mrLevel != this.data.data.mr.value) { updates['data.mr.value'] = mrLevel } let race = this.getRace() if ( race && race.name && (race.name != this.data.data.biodata.racename) ) { updates['data.biodata.racename'] = race.name } let role = this.getRole() if ( role && role.name && (role.name != this.data.data.biodata.rolename)) { updates['data.biodata.rolename'] = role.name } //console.log("UPD", updates, this.data.data.biodata) await this.update(updates) } /* -------------------------------------------- */ async modStat(key, inc = 1) { let stat = duplicate(this.data.data.statistics[key]) stat.mod += parseInt(inc) await this.update({ [`data.statistics.${key}`]: stat }) } /* -------------------------------------------- */ async valueStat(key, inc = 1) { key = key.toLowerCase() let stat = duplicate(this.data.data.statistics[key]) stat.value += parseInt(inc) await this.update({ [`data.statistics.${key}`]: stat }) } /* -------------------------------------------- */ async addIncSpec(spec, inc = 1) { console.log("Using spec : ", spec, inc) let specExist = this.data.items.find(item => item.type == 'specialisation' && item.name.toLowerCase() == spec.name.toLowerCase()) if (specExist) { specExist = duplicate(specExist) specExist.data.level += inc; let update = { _id: specExist._id, "data.level": specExist.data.level }; await this.updateEmbeddedDocuments('Item', [update]); } else { spec.data.level += inc; await this.createEmbeddedDocuments('Item', [spec]); } } /* -------------------------------------------- */ applyAbility(ability, updates = []) { if (ability.data.affectedstat != "notapplicable") { let stat = duplicate(this.data.data.statistics[ability.data.affectedstat]) stat.value += parseInt(ability.data.statlevelincrease) stat.mod += parseInt(ability.data.statmodifier) updates[`data.statistics.${ability.data.affectedstat}`] = stat } } /* -------------------------------------------- */ async applyRace(race) { let updates = { 'data.biodata.racename': race.name } let newItems = [] await this.deleteAllItemsByType('race') newItems.push(race); for (let ability of race.data.abilities) { newItems.push(ability); this.applyAbility(ability, updates) } if (race.data.powersgained) { for (let power of race.data.powersgained) { newItems.push(power); } } if (race.data.specialisations) { for (let spec of race.data.specialisations) { newItems.push(spec); } } if (race.data.attackgained) { for (let weapon of race.data.attackgained) { newItems.push(weapon); } } if (race.data.armorgained) { for (let armor of race.data.armorgained) { newItems.push(armor); } } await this.update(updates) await this.createEmbeddedDocuments('Item', newItems) console.log("Updates", updates, newItems) console.log("Updated actor", this) } /* -------------------------------------------- */ getIncreaseStatValue(updates, statKey) { let stat = duplicate(this.data.data.statistics[statKey]) stat.value += 1; updates[`data.statistics.${statKey}`] = stat } /* -------------------------------------------- */ async applyRole(role) { console.log("ROLE", role) let updates = { 'data.biodata.rolename': role.name } let newItems = [] await this.deleteAllItemsByType('role') newItems.push(role); this.getIncreaseStatValue(updates, role.data.statincrease1) this.getIncreaseStatValue(updates, role.data.statincrease2) //newItems = newItems.concat(duplicate(role.data.specialisationsplus1)) newItems = newItems.concat(duplicate(role.data.specialperk)) await this.update(updates) await this.createEmbeddedDocuments('Item', newItems) } /* -------------------------------------------- */ async rollPower(powId) { let power = this.data.items.find(item => item.type == 'power' && item.id == powId); if (power) { let rollData = { mode: "power", alias: this.name, actorImg: this.img, actorId: this.id, img: power.img, rollMode: game.settings.get("core", "rollMode"), title: `Power ${power.name} `, power: duplicate(power), activePerks: duplicate(this.getActivePerks()), optionsDiceList: PegasusUtility.getOptionsDiceList(), bonusDicesLevel: 0, hindranceDicesLevel: 0, otherDicesLevel: 0, } this.updateWithTarget(rollData); this.syncRoll(rollData); let rollDialog = await PegasusRollDialog.create(this, rollData); console.log(rollDialog); rollDialog.render(true); } else { ui.notifications.warn("Technique not found !"); } } /* -------------------------------------------- */ updateWithTarget(rollData) { let objectDefender let target = PegasusUtility.getTarget(); if (!target) { ui.notifications.warn("You are using a Weapon without a Target."); } else { let defenderActor = game.actors.get(target.data.actorId); objectDefender = PegasusUtility.data(defenderActor); objectDefender = mergeObject(objectDefender, target.data.actorData); rollData.defender = objectDefender; rollData.attackerId = this.id; rollData.defenderId = objectDefender._id; //console.log("ROLLDATA DEFENDER !!!", rollData); } } /* -------------------------------------------- */ async rollArmor(armorId) { let armor = this.data.items.get(armorId) if (armor) { let rollData = this.getCommonRollData() armor = duplicate(armor); this.checkAndPrepareArmor(armor); rollData.mode = "armor" rollData.img = armor.img rollData.armor = armor rollData.title = `Armor : ${armor.name}` rollData.stat = this.getStat(armor.data.statistic) rollData.specList = this.getRelevantSpec(armor.data.statistic) rollData.activePerks = duplicate(this.getActivePerks()) rollData.isResistance = true; rollData.otherDicesLevel = armor.data.resistance //this.updateWithTarget(rollData); this.startRoll(rollData); } else { ui.notifications.warn("Armor not found !", weaponId); } } /* -------------------------------------------- */ async rollWeapon(weaponId, damage = false) { let weapon = this.data.items.get(weaponId) if (weapon) { let rollData = this.getCommonRollData() weapon = duplicate(weapon); this.checkAndPrepareWeapon(weapon); rollData.mode = "weapon" rollData.img = weapon.img rollData.weapon = weapon rollData.title = `Weapon : ${weapon.name}` rollData.specList = this.getRelevantSpec(weapon.data.statistic) rollData.activePerks = duplicate(this.getActivePerks()) if (damage) { rollData.stat = this.getStat(weapon.data.damagestatistic) rollData.isDamage = true; rollData.otherDicesLevel = weapon.data.damage } else { rollData.stat = this.getStat(weapon.data.statistic) } //this.updateWithTarget(rollData); this.startRoll(rollData); } else { ui.notifications.warn("Weapon not found !", weaponId); } } /* -------------------------------------------- */ async rollPower(powerId) { let power = this.data.items.get(powerId) if (power) { let rollData = this.getCommonRollData() power = duplicate(power); rollData.mode = "power" rollData.img = power.img rollData.power = power rollData.title = `Power : ${power.name}` rollData.stat = this.getStat(power.data.statistic) rollData.specList = this.getRelevantSpec(power.data.statistic) rollData.activePerks = duplicate(this.getActivePerks()) this.startRoll(rollData); } else { ui.notifications.warn("Power not found !", powerId); } } }