/* -------------------------------------------- */ 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; } /* -------------------------------------------- */ getMoneys() { let comp = this.data.items.filter(item => item.type == 'money'); 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( combatId, combatantId) { if (this.type == 'character') { this.rollMR(true, combatId, combatantId) } console.log("Init required !!!!") return -1; } /* -------------------------------------------- */ getSubActors() { let subActors = []; for (let id of this.data.data.subactors) { subActors.push(duplicate(game.actors.get(id))); } return subActors; } /* -------------------------------------------- */ async addSubActor(subActorId) { let subActors = duplicate(this.data.data.subactors); subActors.push(subActorId); await this.update({ 'data.subactors': subActors }); } /* -------------------------------------------- */ async delSubActor(subActorId) { let newArray = []; for (let id of this.data.data.subactors) { if (id != subActorId) { newArray.push(id); } } await this.update({ 'data.subactors': newArray }); } /* -------------------------------------------- */ syncRoll(rollData) { let linkedRollId = PegasusUtility.getDefenseState(this.id); if (linkedRollId) { rollData.linkedRollId = linkedRollId; } this.lastRollId = rollData.rollId; PegasusUtility.saveRollData(rollData); } /* -------------------------------------------- */ getStat(statKey) { let stat if (statKey == 'mr') { stat = duplicate(this.data.data.mr); } else { stat = duplicate(this.data.data.statistics[statKey]); } stat.dice = PegasusUtility.getDiceFromLevel(stat.value); return stat; } /* -------------------------------------------- */ getOneSpec(specId) { let spec = this.data.items.find(item => item.type == 'specialisation' && item.id == specId); if (spec) { spec = duplicate(spec); spec.data.dice = PegasusUtility.getDiceFromLevel(spec.data.level); } return spec; } /* -------------------------------------------- */ updatePerkRounds(itemId, roundValue) { let item = this.items.get(itemId) if (item) { this.updateEmbeddedDocuments('Item', [{ _id: item.id, 'data.roundcount': roundValue }]); } } /* -------------------------------------------- */ 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() { if (this.isOwner || game.user.isGM) { let updates = {} let phyDiceValue = PegasusUtility.getDiceValue(this.data.data.statistics.phy.value) + this.data.data.secondary.health.bonus + this.data.data.statistics.phy.mod; if (phyDiceValue != this.data.data.secondary.health.max) { updates['data.secondary.health.max'] = phyDiceValue 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 } nrgValue = PegasusUtility.getDiceValue(this.data.data.statistics.foc.value) + this.data.data.statistics.foc.mod; if (nrgValue != this.data.data.combat.stunthreshold) { updates['data.combat.stunthreshold'] = nrgValue } let momentum = this.data.data.statistics.foc.value + this.data.data.statistics.foc.mod if (momentum != this.data.data.momentum.max) { updates['data.momentum.value'] = 0 updates['data.momentum.max'] = momentum } let mrLevel = (this.data.data.statistics.agi.value + this.data.data.statistics.str.value) - this.data.data.statistics.phy.value mrLevel = (mrLevel < 1) ? 1 : mrLevel; if (mrLevel != this.data.data.mr.value) { updates['data.mr.value'] = mrLevel } let 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]); } } /* -------------------------------------------- */ async incDecQuantity(objetId, incDec = 0) { let objetQ = this.data.items.get(objetId) if (objetQ) { let newQ = objetQ.data.data.quantity + incDec; const updated = await this.updateEmbeddedDocuments('Item', [{ _id: objetQ.id, 'data.quantity': newQ }]); // pdates one EmbeddedEntity } } /* -------------------------------------------- */ 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) } /* -------------------------------------------- */ getShieldValue() { let shields = this.data.items.filter(item => item.type == "shield" && item.data.data.equipped) let def = 0 for (let sh of shields) { def += sh.data.data.level } return def } /* -------------------------------------------- */ addHindrancesList( effectsList ) { if (this.data.data.combat.stunlevel > 0) { effectsList.push( { label: "Stun Hindrance", type: "hindrance", applied: false, value: this.data.data.combat.stunlevel } ) } let effects = this.data.items.filter( item => item.type == 'effect' ) for( let effect of effects) { effect = duplicate(effect) if (effect.data.hindrance) { effectsList.push( { label: effect.name, type: "effect", applied: false, effect: effect, value: effect.data.effectlevel } ) } } } /* -------------------------------------------- */ /* ROLL SECTION /* -------------------------------------------- */ /* -------------------------------------------- */ addEffects( rollData) { let effects = this.data.items.filter( item => item.type == 'effect' ) for( let effect of effects) { effect = duplicate(effect) if ( !effect.data.hindrance && effect.data.stataffected != "notapplicable" && effect.data.stataffected != "special") { rollData.effectsList.push( { label: effect.name, type: "effect", applied: false, effect: effect, value: effect.data.effectlevel } ) } } } /* -------------------------------------------- */ addArmorsShields( rollData, useShield = false) { let armors = this.getArmors() let armorLevel = 0 for (let armor of armors) { armorLevel += armor.data.resistance } rollData.armorsList.push( {label: 'Total armor level', type: "other", applied: false, value: armorLevel } ) if (useShield ) { rollData.armorsList.push( {label: 'Shield level', type: "other", applied: false, value: this.getShieldValue() } ) } } /* -------------------------------------------- */ getCommonRollData(statKey = undefined, useShield = false) { let rollData = PegasusUtility.getBasicRollData() rollData.alias = this.name rollData.actorImg = this.img rollData.actorId = this.id rollData.img = this.img rollData.activePerks = duplicate(this.getActivePerks()) if ( statKey) { rollData.statKey = statKey rollData.stat = this.getStat(statKey) rollData.statDicesLevel = rollData.stat.value rollData.statMod = rollData.stat.mod rollData.specList = this.getRelevantSpec(statKey) rollData.selectedSpec = "0" } this.addEffects( rollData) this.addArmorsShields(rollData, useShield) return rollData } /* -------------------------------------------- */ async startRoll(rollData) { this.syncRoll(rollData); console.log("ROLL DATA", rollData) let rollDialog = await PegasusRollDialog.create(this, rollData); console.log(rollDialog); rollDialog.render(true); } /* -------------------------------------------- */ rollPool(statKey, useShield = false) { let stat = this.getStat(statKey); if (stat) { let rollData = this.getCommonRollData(statKey, useShield) rollData.mode = "stat" this.startRoll(rollData) } else { ui.notifications.warn("Statistic not found !"); } } /* -------------------------------------------- */ rollUnarmedAttack() { let stat = this.getStat('com'); if (stat) { let rollData = this.getCommonRollData(statKey) rollData.mode = "stat" rollData.title = `Unarmed Attack`; rollData.damages = this.getStat('str'); this.startRoll(rollData); } else { ui.notifications.warn("Statistic not found !"); } } /*-------------------------------------------- */ rollStat(statKey) { let stat = this.getStat(statKey); if (stat) { let rollData = this.getCommonRollData(statKey) rollData.mode = "stat" rollData.title = `Stat ${stat.label}`; this.startRoll(rollData) } else { ui.notifications.warn("Statistic not found !"); } } /* -------------------------------------------- */ async rollSpec(specId) { let spec = this.getOneSpec(specId) if (spec) { let rollData = this.getCommonRollData(spec.data.statistic) rollData.mode = "spec" rollData.title = `Spec. : ${spec.name} ` rollData.specList = [ spec ] rollData.selectedSpec = spec._id rollData.specDicesLevel = spec.data.level this.startRoll(rollData) } else { ui.notifications.warn("Specialisation not found !"); } } /* -------------------------------------------- */ async rollMR( isInit = false, combatId = 0, combatantId = 0) { let mr = duplicate(this.data.data.mr) if (mr) { mr.dice = PegasusUtility.getDiceFromLevel(mr.value); let rollData = this.getCommonRollData("mr") rollData.mode = "MR" rollData.isInit = isInit rollData.combatId = combatId rollData.combatantId = combatantId this.startRoll(rollData); } else { ui.notifications.warn("MR not found !"); } } /* -------------------------------------------- */ async rollArmor(armorId) { let armor = this.data.items.get(armorId) if (armor) { let rollData = this.getCommonRollData(armor.data.statistic) armor = duplicate(armor); this.checkAndPrepareArmor(armor); rollData.mode = "armor" rollData.armor = armor rollData.title = `Armor : ${armor.name}` rollData.isResistance = true; rollData.otherDicesLevel = armor.data.resistance 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) { weapon = duplicate(weapon) this.checkAndPrepareWeapon(weapon) let rollData = this.getCommonRollData(weapon.data.statistic) rollData.mode = "weapon" rollData.weapon = weapon rollData.title = `Weapon : ${weapon.name}` if (damage) { rollData.stat = this.getStat(weapon.data.damagestatistic) rollData.isDamage = true; rollData.otherDicesLevel = weapon.data.damage } this.startRoll(rollData); } else { ui.notifications.warn("Weapon not found !", weaponId); } } /* -------------------------------------------- */ async rollPower(powerId) { let power = this.data.items.get(powerId) if (power) { power = duplicate(power) let rollData = this.getCommonRollData(power.data.statistic) rollData.mode = "power" rollData.power = power rollData.title = `Power : ${power.name}` this.startRoll(rollData); } else { ui.notifications.warn("Power not found !", powerId); } } }