/* -------------------------------------------- */ 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 = 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]?? []; } /* -------------------------------------------- */ checkAndPrepareWeapon(item) { let types=[]; let specs=[]; let stats=[]; item.data.specs = specs; item.data.stats = stats; item.data.typeText = types.join('/'); } /* -------------------------------------------- */ 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) { console.log("Search", 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 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 }] ); } } /* -------------------------------------------- */ async rollMR() { let mr = duplicate( this.data.data.mr) ; if (mr) { mr.dice = PegasusUtility.getDiceFromLevel(mr.value); let rollData = { rollId:randomID(16), mode: "MR", alias: this.name, actorImg: this.img, actorId: this.id, img: this.img, rollMode: game.settings.get("core", "rollMode"), title: `${mr.label} `, stat: mr, activePerks: duplicate(this.getActivePerks()), optionsDiceList: PegasusUtility.getOptionsDiceList(), bonusDicesLevel: 0, hindranceDicesLevel: 0, otherDicesLevel: 0, } this.syncRoll( rollData); let rollDialog = await PegasusRollDialog.create( this, rollData); console.log(rollDialog); rollDialog.render( true ); } else { ui.notifications.warn("MR not found !"); } } /* -------------------------------------------- */ 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.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 !"); } } /* -------------------------------------------- */ updateWithTarget( rollData) { let objectDefender let target = WotGUtility.getTarget(); if ( !target) { ui.notifications.warn("You are using a Weapon without a Target."); } else { let defenderActor = game.actors.get(target.data.actorId); objectDefender = WotGUtility.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 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]); } } /* -------------------------------------------- */ computeNRGHealth( ) { let phyDiceValue = PegasusUtility.getDiceValue(this.data.data.statistics.phy.value); if ( phyDiceValue!=this.data.data.secondary.health.max) { this.update( {'data.secondary.health.max': phyDiceValue, 'data.secondary.health.value': phyDiceValue} ) } let mndDiceValue = PegasusUtility.getDiceValue(this.data.data.statistics.mnd.value); if ( mndDiceValue!=this.data.data.secondary.delirium.max) { this.update( {'data.secondary.delirium.max': mndDiceValue, 'data.secondary.delirium.value': mndDiceValue} ) } let stlDiceValue = PegasusUtility.getDiceValue(this.data.data.statistics.stl.value); if ( stlDiceValue != this.data.data.secondary.stealthhealth.max) { this.update( {'data.secondary.stealthhealth.max': stlDiceValue, 'data.secondary.stealthhealth.value': stlDiceValue} ) } let socDiceValue = PegasusUtility.getDiceValue(this.data.data.statistics.soc.value); if ( socDiceValue!=this.data.data.secondary.socialhealth.max) { this.update( {'data.secondary.socialhealth.max': socDiceValue, 'data.secondary.socialhealth.value': socDiceValue} ) } let nrgValue = PegasusUtility.getDiceValue(this.data.data.statistics.foc.value); if ( nrgValue!= this.data.data.nrg.max) { this.update( {'data.nrg.max': nrgValue, 'data.nrg.value': nrgValue} ) } if ( nrgValue!= this.data.data.combat.stunthreshold) { this.update( {'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) { this.update( {'data.mr.value': mrLevel } ); } } /* -------------------------------------------- */ 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.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.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 !"); } } /* -------------------------------------------- */ async rollWeapon( weaponId ) { let weapon = this.data.items.find( item => item.id == weaponId); console.log("WEAPON :", weaponId, weapon ); if ( weapon ) { weapon = duplicate(weapon); this.checkAndPrepareWeapon( weapon ); let rollData = { mode: 'weapon', actorType: this.type, alias: this.name, actorId: this.id, img: weapon.img, rollMode: game.settings.get("core", "rollMode"), title: "Attack : " + weapon.name, weapon: weapon, 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("WEAPON ROLL", rollData); rollDialog.render( true ); } else { ui.notifications.warn("Weapon not found !", weaponId); } } }