/* -------------------------------------------- */ import { Hero6Utility } from "./hero6-utility.js"; import { Hero6RollDialog } from "./hero6-roll-dialog.js"; import { Hero6LiftDice } from "./hero6-lift-dice.js"; /* -------------------------------------------- */ const __speed2Segments = [ [0], [7], [6, 12], [4, 8, 12], [3, 6, 9, 12], [3, 5, 8, 10, 12], [2, 4, 6, 8, 10, 12], [2, 4, 6, 7, 9, 11, 12], [2, 3, 5, 6, 8, 9, 11, 12], [2, 3, 4, 6, 7, 8, 10, 11, 12], [2, 3, 4, 5, 6, 8, 9, 10, 11, 12], [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]] /* -------------------------------------------- */ /* -------------------------------------------- */ /** * Extend the base Actor entity by defining a custom roll data structure which is ideal for the Simple system. * @extends {Actor} */ export class Hero6Actor 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 Hero6Utility.loadCompendium("fvtt-hero-system-6.skills"); //data.items = skills.map(i => i.toObject()) } if (data.type == 'npc') { } return super.create(data, options); } /* -------------------------------------------- */ prepareBaseData() { } /* -------------------------------------------- */ async prepareData() { super.prepareData(); } /* -------------------------------------------- */ computeDerivatedData() { if (this.type == "character") { let newSTREND = this.computeSTREND() if (newSTREND != this.system.characteristics.str.strend) { this.update({ 'system.characteristics.str.strend': newSTREND }) } } } computeDicesValue() { this.system.biodata.presenceattack = Hero6Utility.getDerivatedDiceFormulas(this.system.characteristics.pre.value) this.system.characteristics.str.strdice = Hero6LiftDice.getLiftDice(this.system.characteristics.str.value) this.system.characteristics.str.lift = Hero6LiftDice.getLift(this.system.characteristics.str.value) } /* -------------------------------------------- */ prepareDerivedData() { if (this.type == 'character' || game.user.isGM) { this.system.encCapacity = this.getEncumbranceCapacity() this.buildContainerTree() this.computeDerivatedData() this.computeDicesValue() } super.prepareDerivedData(); } /* -------------------------------------------- */ _preUpdate(changed, options, user) { super._preUpdate(changed, options, user); } /* -------------------------------------------- */ getEncumbranceCapacity() { let numLift = this.system.characteristics.str.lift.match(/\d*\s/g) if (numLift && numLift[0] && Number(numLift[0])) { return numLift[0] / 2 } } /* -------------------------------------------- */ getMoneys() { let comp = this.items.filter(item => item.type == 'currency'); Hero6Utility.sortArrayObjectsByName(comp) return comp; } getEquippedWeapons() { let comp = duplicate(this.items.filter(item => item.type == 'weapon' && item.system.equipped) || []); Hero6Utility.sortArrayObjectsByName(comp) return comp; } /* -------------------------------------------- */ getArmors() { let comp = duplicate(this.items.filter(item => item.type == 'armor') || []); Hero6Utility.sortArrayObjectsByName(comp) return comp; } getEquippedArmor() { let comp = this.items.find(item => item.type == 'armor' && item.system.equipped) if (comp) { return duplicate(comp) } return undefined } /* -------------------------------------------- */ getShields() { let comp = duplicate(this.items.filter(item => item.type == 'shield') || []); Hero6Utility.sortArrayObjectsByName(comp) return comp; } getEquippedShield() { let comp = this.items.find(item => item.type == 'shield' && item.system.equipped) if (comp) { return duplicate(comp) } return undefined } /* -------------------------------------------- */ getRace() { let race = this.items.filter(item => item.type == 'race') return race[0] ?? []; } /* -------------------------------------------- */ checkAndPrepareEquipment(item) { } /* -------------------------------------------- */ checkAndPrepareEquipments(listItem) { for (let item of listItem) { this.checkAndPrepareEquipment(item) } return listItem } /* -------------------------------------------- */ getItemById(id) { let item = this.items.find(item => item.id == id); if (item) { item = duplicate(item) } return item; } /* -------------------------------------------- */ prepareSkill(skill) { skill.roll = 0 skill.charac = "N/A" skill.system.skilltype = skill.system.skilltype.toLowerCase() if (skill.system.skillfamiliarity) { skill.roll = 8; } else if (skill.system.skillprofiency) { skill.roll = 10; } else if (skill.system.skilltype == "agility") { skill.charac = "dex" let charac = duplicate(this.system.characteristics.dex) this.prepareCharacValues(charac) skill.roll = charac.roll } else if (skill.system.skilltype == "interaction") { skill.charac = "pre" let charac = duplicate(this.system.characteristics.pre) this.prepareCharacValues(charac) skill.roll = charac.roll } else if (skill.system.skilltype == "intellect") { skill.charac = "int" let charac = duplicate(this.system.characteristics.int) this.prepareCharacValues(charac) skill.roll = charac.roll } else if (skill.system.skilltype == "background") { skill.roll = 11 } else if (skill.system.skilltype == "custom") { if (skill.system.characteristic == "manual") { skill.roll = skill.system.base } else { skill.charac = (skill.system.characteristic == "") ? "str" : skill.system.characteristic let charac = duplicate(this.system.characteristics[skill.system.characteristic]) this.prepareCharacValues(charac) skill.roll = charac.roll } } console.log("SILL", skill) if (skill.system.levels > 0) { skill.roll += skill.system.levels } } /* -------------------------------------------- */ getSkills() { let comp = duplicate(this.items.filter(item => item.type == 'skill') || []) for (let skill of comp) { this.prepareSkill(skill) } Hero6Utility.sortArrayObjectsByName(comp) return comp } getPerks() { let comp = duplicate(this.items.filter(item => item.type == 'perk') || []) Hero6Utility.sortArrayObjectsByName(comp) return comp } async getPowers() { let comp = duplicate(this.items.filter(item => item.type == 'power') || []) for (let c of comp) { c.enrichDescription = c.name + "
" + await TextEditor.enrichHTML(c.system.description, { async: true }) c.enrichNotes = c.name + "
" + await TextEditor.enrichHTML(c.system.notes, { async: true }) } Hero6Utility.sortArrayObjectsByName(comp) return comp } getTalents() { let comp = duplicate(this.items.filter(item => item.type == 'talent') || []) Hero6Utility.sortArrayObjectsByName(comp) return comp } getComplications() { let comp = duplicate(this.items.filter(item => item.type == 'complication') || []) Hero6Utility.sortArrayObjectsByName(comp) return comp } /* -------------------------------------------- */ async equipItem(itemId) { let item = this.items.find(item => item.id == itemId) if (item && item.system) { if (item.type == "armor") { let armor = this.items.find(item => item.id != itemId && item.type == "armor" && item.system.equipped) if (armor) { ui.notifications.warn("You already have an armor equipped!") return } } if (item.type == "shield") { let shield = this.items.find(item => item.id != itemId && item.type == "shield" && item.system.equipped) if (shield) { ui.notifications.warn("You already have a shield equipped!") return } } 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; } getManeuvers() { let maneuvers = { general: this.items.filter(item => item.type == "maneuver" && item.system.maneuvertype == "general"), offensive: this.items.filter(item => item.type == "maneuver" && item.system.maneuvertype == "offensive"), defensive: this.items.filter(item => item.type == "maneuver" && item.system.maneuvertype == "defensive") } Hero6Utility.sortArrayObjectsByName(maneuvers.general) Hero6Utility.sortArrayObjectsByName(maneuvers.offensive) Hero6Utility.sortArrayObjectsByName(maneuvers.defensive) return maneuvers } getNonStockManeuvers() { let maneuvers = this.items.filter(item => item.type == "maneuver" && !item.system.isstock) Hero6Utility.sortArrayObjectsByName(maneuvers) return maneuvers } getEquipments() { let list = this.items.filter(item => item.type == "equipment" && item.system.subtype == "equipment"); Hero6Utility.sortArrayObjectsByName(list) return list } getWeapons() { let list = this.items.filter(item => item.type == "equipment" && item.system.subtype == "weapon"); Hero6Utility.sortArrayObjectsByName(list) return list } getArmors() { let list = this.items.filter(item => item.type == "equipment" && item.system.subtype == "armor"); Hero6Utility.sortArrayObjectsByName(list) return list } getShields() { let list = this.items.filter(item => item.type == "equipment" && item.system.subtype == "shield"); Hero6Utility.sortArrayObjectsByName(list) return list } getEquipmentsMoneys() { let list = duplicate(this.items.filter(item => item.type == "equipment" && (item.system.subtype == "equipment" || item.system.subtype == "money")) || []) Hero6Utility.sortArrayObjectsByName(list) return list } getEquipmentsOnly() { let list = duplicate(this.items.filter(item => item.type == "equipment" && item.system.subtype == "equipment") || []) Hero6Utility.sortArrayObjectsByName(list) return list } /* ------------------------------------------- */ buildContainerTree() { let equipments = duplicate(this.items.filter(item => item.type == "equipment") || []); let enc = 0 let value = 0 for (let equip1 of equipments) { if (Number(equip1.system.weight) && Number(equip1.system.quantity)) { enc += equip1.system.weight * equip1.system.quantity } if (Number(equip1.system.value) && Number(equip1.system.quantity)) { value += equip1.system.value * equip1.system.quantity } } // Store local values this.encCurrent = enc this.totalValue = value } /* -------------------------------------------- */ async incDecHP(formula) { let dmgRoll = new Roll(formula + "[dark-starsorange]").roll({ async: false }) await Hero6Utility.showDiceSoNice(dmgRoll, game.settings.get("core", "rollMode")) let hp = duplicate(this.system.secondary.hp) hp.value = Number(hp.value) + Number(dmgRoll.total) this.update({ 'system.secondary.hp': hp }) return Number(dmgRoll.total) } /* -------------------------------------------- */ async addObjectToContainer(itemId, containerId) { let container = this.items.find(item => item.id == containerId && item.system.iscontainer) let object = this.items.find(item => item.id == itemId) 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 { await this.updateEmbeddedDocuments("Item", [{ _id: object.id, 'system.containerid': containerId }]) } } else if (object?.system?.containerid) { // remove from container console.log("Removeing: ", object) await this.updateEmbeddedDocuments("Item", [{ _id: object.id, 'system.containerid': "" }]); } } /* -------------------------------------------- */ async preprocessItem(event, item, onDrop = false) { let dropID = $(event.target).parents(".item").attr("data-item-id") // Only relevant if container drop let objectID = item.id || item._id this.addObjectToContainer(objectID, dropID) return true } /* -------------------------------------------- */ async equipGear(equipmentId) { let item = this.items.find(item => item.id == equipmentId); if (item?.system) { let update = { _id: item.id, "system.equipped": !item.system.equipped }; await this.updateEmbeddedDocuments('Item', [update]); // Updates one EmbeddedEntity } } /* -------------------------------------------- */ async cleanCombat() { await this.setFlag("world", "hold-action", false) await this.setFlag("world", "abort-action", { state: false, count: 0 } ) } async holdAction() { await this.disableAbortAction() if (this.getFlag("world", "hold-action")) { await this.setFlag("world", "hold-action", false) //game.combat.holdAction(this.id, false) game.combat.forceHold(this, false) return false } else { await this.setFlag("world", "hold-action", true) //game.combat.holdAction(this.id, false) game.combat.forceHold(this, true) return true } } async disableHoldAction() { await this.setFlag("world", "hold-action", false) } async disableAbortAction() { await this.setFlag("world", "abort-action", { state: false, count: 0 }) } async abortAction() { await this.disableHoldAction() let abort = this.getFlag("world", "abort-action") if (abort.state) { await this.setFlag("world", "abort-action", { state: false, count: 0 }) game.combat.forceAbort(this, false) //game.combat.abortAction(this.id, false) } else { await this.setFlag("world", "abort-action", { state: true, count: 0 }) game.combat.forceAbort(this, true) //game.combat.abortAction(this.id, true) } } async incAbortActionCount() { let abort = this.getFlag("world", "abort-action") if (abort.state) { abort.count++ await this.setFlag("world", "abort-action", abort) if (abort.count == 2) { return true } } return false } getHoldAction() { return this.getFlag("world", "hold-action") } getAbortAction() { let abort = this.getFlag("world", "abort-action") return abort?.state || false } /* -------------------------------------------- */ hasPhase(segmentNumber) { let index = Math.min(Math.max(this.system.characteristics.spd.value, 1), 12) // Security bounds let phases = __speed2Segments[index] console.log("index", segmentNumber, index, phases, phases.includes(segmentNumber), __speed2Segments) return phases.includes(segmentNumber) } /* -------------------------------------------- */ getSegments() { let index = Math.min(Math.max(this.system.characteristics.spd.value, 1), 12) // Security bounds console.log("INDEX", index, __speed2Segments[index]) return __speed2Segments[index] } getPhasesString() { let index = Math.min(Math.max(this.system.characteristics.spd.value, 1), 12) // Security bounds return __speed2Segments[index].toString() } /* -------------------------------------------- */ getBaseInit(turn) { if ( turn != this.turn) { let r = new Roll("1d6").roll({ async: false }) this.currentInit = this.system.characteristics.dex.value + (r.total / 10) this.turn = turn } return this.currentInit } /* -------------------------------------------- */ getSubActors() { let 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 }); } /* -------------------------------------------- */ prepareCharacValues(charac) { charac.total = charac.value charac.roll = 9 + Math.round((charac.value) / 5) } prepareCharac() { let characs = duplicate(this.system.characteristics) for (let key in characs) { let ch = characs[key] this.prepareCharacValues(ch) if (key == "str") { ch.lift = Hero6LiftDice.getLift(ch.value) ch.liftDice = Hero6LiftDice.getLiftDice(ch.value) } if (key == "spd") { ch.phasesString = this.getPhasesString() } if (key =="pre") { ch.presenceattack = duplicate(this.system.biodata.presenceattack) } } return characs } /* -------------------------------------------- */ getOneSkill(skillId) { let skill = this.items.find(item => item.type == 'skill' && item.id == skillId) if (skill) { skill = duplicate(skill); } return skill; } /* -------------------------------------------- */ computeSTREND() { let newSTREND = 0 switch (this.system.characteristics.str.strendmode) { case "str20": newSTREND = Math.floor(this.system.characteristics.str.value / 20) break; case "str10": newSTREND = Math.floor(this.system.characteristics.str.value / 10) break; case "str5": newSTREND = Math.floor(this.system.characteristics.str.value / 5) break; } return newSTREND } /* -------------------------------------------- */ async deleteAllItemsByType(itemType) { let items = this.items.filter(item => item.type == itemType); 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]); } } /* -------------------------------------------- */ async incDecQuantity(objetId, incDec = 0) { let objetQ = this.items.get(objetId) if (objetQ) { let newQ = objetQ.system.quantity + incDec if (newQ >= 0) { const updated = 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) { const updated = await this.updateEmbeddedDocuments('Item', [{ _id: objetQ.id, 'system.ammocurrent': newQ }]); // pdates one EmbeddedEntity } } } /* -------------------------------------------- */ getCommonRollData(chKey = undefined) { let rollData = Hero6Utility.getBasicRollData() rollData.alias = this.name rollData.actorImg = this.img rollData.actorId = this.id rollData.img = this.img if (chKey) { rollData.charac = duplicate(this.system.characteristics[chKey]) this.prepareCharacValues(rollData.charac) } if (rollData.defenderTokenId) { let defenderToken = game.canvas.tokens.get(rollData.defenderTokenId) let defender = defenderToken.actor // Distance management let token = this.token if (!token) { let tokens = this.getActiveTokens() token = tokens[0] } if (token) { const ray = new Ray(token.object?.center || token.center, defenderToken.center) rollData.tokensDistance = canvas.grid.measureDistances([{ ray }], { gridSpaces: false })[0] / canvas.grid.grid.options.dimensions.distance } else { ui.notifications.info("No token connected to this actor, unable to compute distance.") return } if (defender) { rollData.forceAdvantage = defender.isAttackerAdvantage() rollData.advantageFromTarget = true } } console.log("ROLLDATA", rollData) return rollData } /* -------------------------------------------- */ rollPerception() { let rollData = this.getCommonRollData("int") rollData.isPerception = true rollData.charac.roll = Number(rollData.charac.perceptionroll) rollData.mode = "perception" if (rollData.target) { ui.notifications.warn("You are targetting a token with a skill : please use a Weapon instead.") return } this.startRoll(rollData) } /* -------------------------------------------- */ rollCharac(chKey) { let rollData = this.getCommonRollData(chKey) rollData.mode = "charac" if (rollData.target) { ui.notifications.warn("You are targetting a token with a skill : please use a Weapon instead.") return } this.startRoll(rollData) } /* -------------------------------------------- */ rollItem(itemId) { let item = this.items.get(itemId) let rollData = this.getCommonRollData() rollData.mode = "item" rollData.item = duplicate(item) if (item.type == "skill") { this.prepareSkill(rollData.item) } this.startRoll(rollData) } /* -------------------------------------------- */ async rollDamage(itemId) { let item = this.items.get(itemId) let rollData = this.getCommonRollData() rollData.mode = "damage" rollData.item = duplicate(item) rollData.title = item.name rollData.diceFormula = Hero6Utility.convertRollHeroSyntax(item.system.damage) let myRoll = new Roll(rollData.diceFormula).roll({ async: false }) await Hero6Utility.showDiceSoNice(myRoll, game.settings.get("core", "rollMode")) rollData.roll = myRoll rollData.result = myRoll.total rollData.bodyValue = Hero6Utility.computeBodyValue(myRoll) let msgFlavor = await renderTemplate(`systems/fvtt-hero-system-6/templates/chat/chat-damage-result.hbs`, rollData) let msg = await rollData.roll.toMessage({ user: game.user.id, rollMode: game.settings.get("core", "rollMode"), flavor: msgFlavor }) rollData.roll = duplicate(rollData.roll) // Convert to object msg.setFlag("world", "rolldata", rollData) console.log("Rolldata result", rollData) } /* -------------------------------------------- */ async rollLiftDice() { let rollData = this.getCommonRollData() rollData.mode = "lift-dice" rollData.diceFormula = Hero6Utility.convertRollHeroSyntax(Hero6LiftDice.getLiftDice(this.system.characteristics.str.value)) let myRoll = new Roll(rollData.diceFormula).roll({ async: false }) await Hero6Utility.showDiceSoNice(myRoll, game.settings.get("core", "rollMode")) rollData.bodyValue = Hero6Utility.computeBodyValue(myRoll) rollData.result = myRoll.total rollData.roll = duplicate(myRoll) let msgFlavor = await renderTemplate(`systems/fvtt-hero-system-6/templates/chat/chat-lift-dice-result.hbs`, rollData) let msg = await myRoll.toMessage({ user: game.user.id, rollMode: game.settings.get("core", "rollMode"), flavor: msgFlavor }) msg.setFlag("world", "rolldata", rollData) console.log("Rolldata result", rollData) } /* -------------------------------------------- */ rollSkill(skillId) { let skill = this.items.get(skillId) if (skill) { if (skill.system.islore && skill.system.level == 0) { ui.notifications.warn("You can't use Lore Skills with a SL of 0.") return } skill = duplicate(skill) Hero6Utility.updateSkill(skill) let abilityKey = skill.system.ability let rollData = this.getCommonRollData(abilityKey) rollData.mode = "skill" rollData.skill = skill rollData.img = skill.img if (rollData.target) { ui.notifications.warn("You are targetting a token with a skill : please use a Weapon instead.") return } this.startRoll(rollData) } } /* -------------------------------------------- */ rollWeapon(weaponId) { let weapon = this.items.get(weaponId) if (weapon) { weapon = duplicate(weapon) let skill = this.items.find(item => item.name.toLowerCase() == weapon.system.skill.toLowerCase()) if (skill) { skill = duplicate(skill) Hero6Utility.updateSkill(skill) let abilityKey = skill.system.ability let rollData = this.getCommonRollData(abilityKey) rollData.mode = "weapon" rollData.skill = skill rollData.weapon = weapon rollData.img = weapon.img if (!rollData.forceDisadvantage) { // This is an attack, check if disadvantaged rollData.forceDisadvantage = this.isAttackDisadvantage() } /*if (rollData.weapon.system.isranged && rollData.tokensDistance > Hero6Utility.getWeaponMaxRange(rollData.weapon) ) { ui.notifications.warn(`Your target is out of range of your weapon (max: ${Hero6Utility.getWeaponMaxRange(rollData.weapon)} - current : ${rollData.tokensDistance})` ) return }*/ this.startRoll(rollData) } else { ui.notifications.warn("Unable to find the relevant skill for weapon " + weapon.name) } } } /* -------------------------------------------- */ async startRoll(rollData) { let rollDialog = await Hero6RollDialog.create(this, rollData) rollDialog.render(true) } }