diff --git a/modules/pegasus-actor.js b/modules/pegasus-actor.js index 4f088b4..4931db2 100644 --- a/modules/pegasus-actor.js +++ b/modules/pegasus-actor.js @@ -237,6 +237,95 @@ export class PegasusActor extends Actor { let role = this.items.filter(item => item.type == 'role') return role[0] ?? []; } + /* -------------------------------------------- */ + getRoleLevel() { + let role = this.items.find(item => item.type == 'role') + if (role ) { + console.log("Role", role) + return role.system.rolelevel + } + return 0 + } + + /* -------------------------------------------- */ + isTactician() { + let role = this.items.find(item => item.type == 'role') + return role && role.system.perksrole == "tactician" + } + hasTacticianBonus() { + let effect = this.items.find( item => item.name.toLowerCase().includes("tactician bonus dice") ) + return effect + } + async addTacticianEffect(name, level) { + let effect = duplicate(__bonusEffect) + effect.name = `${name} Tactician Bonus Dice` + effect.system.effectlevel = level + effect.system.stataffected = "all" + effect.system.bonusdice = true + await this.createEmbeddedDocuments('Item', [effect]) + ChatMessage.create({ content: `Tactician Bonus Dice has been added to ${this.name} (${level})` }) + } + async removeTacticianEffect() { + let effect = this.items.find( item => item.name.toLowerCase().includes("tactician bonus dice") ) + if (effect) { + await this.deleteEmbeddedDocuments('Item', [effect.id]) + ChatMessage.create({ content: `Tactician Bonus Dice has been removed to ${this.name}` }) + } + } + + /* -------------------------------------------- */ + isEnhancer() { + let role = this.items.find(item => item.type == 'role') + return role && role.system.perksrole == "enhancer" + } + hasEnhancerBonus() { + let effect = this.items.find( item => item.name.toLowerCase().includes("enhancer bonus dice") ) + return effect + } + async addEnhancerEffect( name, level) { + let effect = duplicate(__bonusEffect) + effect.name = `${name} Enhancer Bonus Dice ALL` + effect.system.effectlevel = level + effect.system.stataffected = "all" + effect.system.bonusdice = true + await this.createEmbeddedDocuments('Item', [effect]) + ChatMessage.create({ content: `Enhancer Bonus Dice has been added to ${this.name} (${level})` }) + } + async removeEnhancerEffect() { + let effect = this.items.find( item => item.name.toLowerCase().includes("enhancer bonus dice") ) + if (effect) { + await this.deleteEmbeddedDocuments('Item', [effect.id]) + ChatMessage.create({ content: `Enhancer Bonus Dice has been removed to ${this.name}` }) + } + } + + /* -------------------------------------------- */ + isAgitator() { + let role = this.items.find(item => item.type == 'role') + return role && role.system.perksrole == "agitator" + } + hasAgitatorHindrance() { + let effect = this.items.find( item => item.name.toLowerCase().includes("hindered by agitator") ) + return effect + } + async addAgitatorHindrance(name, level) { + let effect = duplicate(__bonusEffect) + effect.name = `Hindered by Agitator ${name}` + effect.system.effectlevel = level + effect.system.stataffected = "all" + effect.system.genre = "negative" + effect.system.hindrance = true + await this.createEmbeddedDocuments('Item', [effect]) + ChatMessage.create({ content: `Agitator Hindrance has been added to ${this.name} (${level})` }) + } + async removeAgitatorHindrance() { + let effect = this.items.find( item => item.name.toLowerCase().includes("hindered by agitator") ) + if (effect) { + await this.deleteEmbeddedDocuments('Item', [effect.id]) + ChatMessage.create({ content: `Agitator Hindrance has been removed to ${this.name}` }) + } + } + /* -------------------------------------------- */ checkAndPrepareEquipment(item) { if (item.system.resistance) { @@ -379,13 +468,13 @@ export class PegasusActor extends Actor { /* -------------------------------------------- */ updateSize() { let sizeBonus = 0 - for(let effect of this.items) { - if (effect.type == "effect" && effect.system.effectlevel > 0 && effect.system.affectsize) { + for (let effect of this.items) { + if (effect.type == "effect" && effect.system.effectlevel > 0 && effect.system.affectsize) { sizeBonus += effect.system.effectlevel } } if (sizeBonus != this.system.biodata.sizebonus) { - this.update( {'system.biodata.sizebonus': sizeBonus}) + this.update({ 'system.biodata.sizebonus': sizeBonus }) } } @@ -1353,7 +1442,7 @@ export class PegasusActor extends Actor { /* -------------------------------------------- */ parseStatEffects() { - let effects = this.items.filter(effect => effect.type == "effect" && effect.system.genre == "positive" && effect.system.statdice ) + let effects = this.items.filter(effect => effect.type == "effect" && effect.system.genre == "positive" && effect.system.statdice) for (let statKey in this.system.statistics) { let stat = duplicate(this.system.statistics[statKey]) let bonus = 0 @@ -1362,40 +1451,40 @@ export class PegasusActor extends Actor { bonus += Number(effect.system.effectlevel) } } - if ( bonus != stat.bonuseffect) { + if (bonus != stat.bonuseffect) { stat.bonuseffect = bonus - this.update( { [`system.statistics.${statKey}`]: stat} ) + this.update({ [`system.statistics.${statKey}`]: stat }) } } } /* -------------------------------------------- */ parseStatusEffects() { - let effects = this.items.filter(effect => effect.type == "effect" && effect.system.affectstatus && (Number(effect.system.effectlevel) > 0) ) + let effects = this.items.filter(effect => effect.type == "effect" && effect.system.affectstatus && (Number(effect.system.effectlevel) > 0)) for (let statusKey in this.system.secondary) { let status = duplicate(this.system.secondary[statusKey]) let bonus = 0 - for (let effect of effects) { - if ( effect.system.affectedstatus && effect.system.affectedstatus == statusKey) { + for (let effect of effects) { + if (effect.system.affectedstatus && effect.system.affectedstatus == statusKey) { bonus += Number(effect.system.effectlevel) } } - if ( bonus != status.bonus) { + if (bonus != status.bonus) { status.bonus = bonus - this.update( { [`system.secondary.${statusKey}`]: status} ) + this.update({ [`system.secondary.${statusKey}`]: status }) } } let nrg = duplicate(this.system.nrg) let bonus = 0 - for (let effect of effects) { - if ( effect.system.affectedstatus && effect.system.affectedstatus == "nrg") { + for (let effect of effects) { + if (effect.system.affectedstatus && effect.system.affectedstatus == "nrg") { bonus += Number(effect.system.effectlevel) } } - if ( bonus != nrg.mod) { + if (bonus != nrg.mod) { nrg.mod = bonus - this.update( { [`system.nrg`]: nrg} ) + this.update({ [`system.nrg`]: nrg }) } } @@ -1635,7 +1724,7 @@ export class PegasusActor extends Actor { let effects = this.items.filter(item => item.type == 'effect') for (let effect of effects) { effect = duplicate(effect) - if (!effect.system.hindrance && !effect.system.statdice + if (!effect.system.hindrance && !effect.system.statdice && (effect.system.stataffected != "notapplicable" || effect.system.specaffected.length > 0) && effect.system.stataffected != "special" && effect.system.stataffected != "powerroll" @@ -1728,7 +1817,7 @@ export class PegasusActor extends Actor { if (statKey) { rollData.statKey = statKey rollData.stat = this.getStat(statKey) - if ( rollData.stat.value != undefined ) { + if (rollData.stat.value != undefined) { rollData.stat.level = rollData.stat.value // Normalize } rollData.statDicesLevel = rollData.stat.level + rollData.stat.bonuseffect @@ -1790,30 +1879,30 @@ export class PegasusActor extends Actor { } /* -------------------------------------------- */ - processSizeBonus( rollData) { - if ( rollData.defenderTokenId) { - let diffSize = rollData.defenderSize - this.system.biodata.sizenum+this.system.biodata.sizebonus + processSizeBonus(rollData) { + if (rollData.defenderTokenId) { + let diffSize = rollData.defenderSize - this.system.biodata.sizenum + this.system.biodata.sizebonus //console.log("Diffsize", diffSize) - if( rollData.subKey == "melee-atk" || rollData.subKey == "ranged-atk") { - if ( diffSize > 0) { + if (rollData.subKey == "melee-atk" || rollData.subKey == "ranged-atk") { + if (diffSize > 0) { rollData.effectsList.push({ label: "Size Bonus", type: "effect", applied: false, isdynamic: true, value: diffSize }) } - } - if( rollData.subKey == "dmg-res" ) { - if ( diffSize < 0) { + } + if (rollData.subKey == "dmg-res") { + if (diffSize < 0) { rollData.effectsList.push({ label: "Size Bonus", type: "effect", applied: false, isdynamic: true, value: Math.abs(diffSize) }) } } - if( rollData.subKey == "defence" ) { - if ( diffSize > 0) { + if (rollData.subKey == "defence") { + if (diffSize > 0) { rollData.effectsList.push({ label: "Size Bonus", type: "effect", applied: false, isdynamic: true, value: Math.abs(diffSize) }) } } - if( rollData.subKey == "melee-dmg" || rollData.subKey == "ranged-dmg" || rollData.subKey == "power-dmg") { - if ( diffSize < 0) { + if (rollData.subKey == "melee-dmg" || rollData.subKey == "ranged-dmg" || rollData.subKey == "power-dmg") { + if (diffSize < 0) { rollData.effectsList.push({ label: "Size Bonus", type: "effect", applied: false, isdynamic: true, value: Math.abs(diffSize) }) } - } + } } } @@ -1922,8 +2011,8 @@ export class PegasusActor extends Actor { /* -------------------------------------------- */ async rollMR(isInit = false, combatId = 0, combatantId = 0) { - - let mr = duplicate((this.type =="vehicle") ? this.system.statistics.mr : this.system.mr) + + let mr = duplicate((this.type == "vehicle") ? this.system.statistics.mr : this.system.mr) if (mr) { mr.dice = PegasusUtility.getDiceFromLevel(mr.value); diff --git a/modules/pegasus-main.js b/modules/pegasus-main.js index 3ee15df..de6fffb 100644 --- a/modules/pegasus-main.js +++ b/modules/pegasus-main.js @@ -57,8 +57,9 @@ Hooks.once("init", async function () { CONFIG.Combat.documentClass = PegasusCombat CONFIG.Actor.documentClass = PegasusActor CONFIG.Item.documentClass = PegasusItem - //CONFIG.Token.objectClass = PegasusToken - game.system.pegasus = { }; + game.system.pegasus = { + utility: PegasusUtility + } /* -------------------------------------------- */ // Register sheet application classes @@ -69,8 +70,9 @@ Hooks.once("init", async function () { Items.unregisterSheet("core", ItemSheet); Items.registerSheet("fvtt-pegasus", PegasusItemSheet, { makeDefault: true }); - PegasusUtility.init(); - + PegasusUtility.init() + + }); /* -------------------------------------------- */ diff --git a/modules/pegasus-utility.js b/modules/pegasus-utility.js index 30d37ad..e7e3a34 100644 --- a/modules/pegasus-utility.js +++ b/modules/pegasus-utility.js @@ -23,7 +23,6 @@ export class PegasusUtility { Hooks.on('renderChatLog', (log, html, data) => PegasusUtility.chatListeners(html)) Hooks.on('targetToken', (user, token, flag) => PegasusUtility.targetToken(user, token, flag)) Hooks.on('renderSidebarTab', (app, html, data) => PegasusUtility.addDiceRollButton(app, html, data)) - Hooks.on("getCombatTrackerEntryContext", (html, options) => { PegasusUtility.pushInitiativeOptions(html, options); }); @@ -35,9 +34,10 @@ export class PegasusUtility { this.defenderStore = {} this.diceList = []; this.diceFoundryList = []; - this.optionsDiceList = ""; - this.buildDiceLists(); - PegasusCommands.init(); + this.optionsDiceList = "" + this.lastRoleEffectProcess = Date.now() + this.buildDiceLists() + PegasusCommands.init() Handlebars.registerHelper('count', function (list) { return (list) ? list.length : 0; @@ -269,6 +269,10 @@ export class PegasusUtility { static async ready() { const specs = await PegasusUtility.loadCompendium("fvtt-pegasus-rpg.specialisations"); this.specs = specs.map(i => i.toObject()); + + if (game.user.isGM) { + Hooks.on('sightRefresh', (app, html, data) => PegasusUtility.refreshSightForEffect(app, html, data)) + } } /* -------------------------------------------- */ @@ -1062,4 +1066,206 @@ export class PegasusUtility { d.render(true); } + /* -------------------------------------------- */ + static glowToken(token) { + let params = + [{ + filterType: "glow", + filterId: "superSpookyGlow", + outerStrength: 15, + innerStrength: 0, + color: 0x6AAB8E, + quality: 0.5, + padding: 40, + autoDestroy: true, + animated: + { + color: + { + active: true, + loopDuration: 3000, + loops: 2, + animType: "colorOscillation", + val1: 0x6AAB8E, + val2: 0x66FF33 + } + } + }] + TokenMagic.addUpdateFilters(token, params) + } + + /* -------------------------------------------- */ + static async getRelevantTokens() { + if (!_token) { return } + let tokens = canvas.tokens.placeables.filter(token => token.document.disposition == 1) + for (let token of tokens) { + console.log("Parsing tokens", token.name) + let dist = canvas.grid.measureDistances( + [{ ray: new Ray(_token.center, token.center) }], { gridSpaces: false }) + if (dist && dist[0] && dist[0] > 0) { + console.log(" Friendly Tokens at : ", token.name, dist / canvas.grid.grid.options.dimensions.distance) + } + let visible = canvas.effects.visibility.testVisibility(token.center, { object: _token }) + if (visible && dist[0] > 0) { + this.glowToken(token) + } + console.log(" Visible!", visible) + } + } + + /* -------------------------------------------- */ + static async processTactician(friends) { + // Tactician management + let toApply = {} + let tacticianTokens = canvas.tokens.placeables.filter(token => token.actor.isTactician()) + for (let token of tacticianTokens) { + token.refresh() + for (let friend of friends) { + if (friend.actor.id != token.actor.id) { + let existing = toApply[friend.actor.id] || { actor: friend.actor, add: false, level: 0, names: [] } + let visible = canvas.effects.visibility.testVisibility(friend.center, { object: token }) + console.log("parse visible TACTICIAN : ", visible, token.name, friend.name) + if (visible) { + existing.add = true + existing.level += token.actor.getRoleLevel() + existing.names.push(token.actor.name) + } + toApply[friend.actor.id] = existing + } + } + } + for (let id in toApply) { + let applyDef = toApply[id] + let hasBonus = applyDef.actor.hasTacticianBonus() + if (applyDef.add) { + if (!hasBonus) { + applyDef.actor.addTacticianEffect(applyDef.names.toString(), applyDef.level) + } else if (applyDef.level != hasBonus.system.effectlevel) { + await applyDef.actor.removeTacticianEffect() + applyDef.actor.addTacticianEffect(applyDef.names.toString(), applyDef.level) + } + } else if (hasBonus) { + applyDef.actor.removeTacticianEffect() + } + } + } + + /* -------------------------------------------- */ + static async processEnhancer(friends) { + // Enhancer management + let toApply = {} + let enhancerTokens = canvas.tokens.placeables.filter(token => token.actor.isEnhancer()) + for (let token of enhancerTokens) { + token.refresh() + for (let friend of friends) { + if (friend.actor.id != token.actor.id) { + let existing = toApply[friend.actor.id] || { actor: friend.actor, add: false, level: 0, names: [] } + let visible = canvas.effects.visibility.testVisibility(friend.center, { object: token }) + console.log("parse visible ENHANCER: ", visible, token.name, friend.name) + if (visible) { + let dist = canvas.grid.measureDistances([{ ray: new Ray(token.center, friend.center) }], { gridSpaces: false }) + if (dist && dist[0] && (dist[0] / canvas.grid.grid.options.dimensions.distance) <= 5) { + existing.add = true + existing.level += token.actor.getRoleLevel() + existing.names.push(token.actor.name) + } + } + toApply[friend.actor.id] = existing + } + } + } + for (let id in toApply) { + let applyDef = toApply[id] + let hasBonus = applyDef.actor.hasEnhancerBonus() + if (applyDef.add) { + if (!hasBonus) { + applyDef.actor.addEnhancerEffect(applyDef.names.toString(), applyDef.level) + } else if (applyDef.level != hasBonus.system.effectlevel) { + await applyDef.actor.removeEnhancerEffect() + applyDef.actor.addEnhancerEffect(applyDef.names.toString(), applyDef.level) + } + } else if (hasBonus) { + applyDef.actor.removeEnhancerEffect() + } + } + } + + /* -------------------------------------------- */ + static async processAgitator(ennemies) { + // Agitator management + let toApply = {} + let agitatorTokens = canvas.tokens.placeables.filter(token => token.actor.isAgitator()) + for (let token of agitatorTokens) { + token.refresh() + for (let ennemy of ennemies) { + if (ennemy.actor.id != token.actor.id) { + let existing = toApply[ennemy.actor.id] || { actor: ennemy.actor, add: false, level: 0, names: [] } + let visible = canvas.effects.visibility.testVisibility(ennemy.center, { object: token }) + if (visible) { + let dist = canvas.grid.measureDistances([{ ray: new Ray(token.center, ennemy.center) }], { gridSpaces: false }) + if (dist && dist[0] && (dist[0] / canvas.grid.grid.options.dimensions.distance) <= 5) { + existing.add = true + existing.level += token.actor.getRoleLevel() + existing.names.push(token.actor.name) + } + } + toApply[ennemy.actor.id] = existing + } + } + } + for (let id in toApply) { + let applyDef = toApply[id] + let hasHindrance = applyDef.actor.hasAgitatorHindrance() + if (applyDef.add) { + if (!hasHindrance) { + applyDef.actor.addAgitatorHindrance(applyDef.names.toString(), applyDef.level) + } else if (applyDef.level != hasHindrance.system.effectlevel) { + await applyDef.actor.removeAgitatorHindrance() + applyDef.actor.addAgitatorHindrance(applyDef.names.toString(), applyDef.level) + } + } else if (hasHindrance) { + applyDef.actor.removeAgitatorHindrance() + } + } + } + + /* -------------------------------------------- */ + static async processRoleEffects() { + + // Small optimization + let now = Date.now() + if ( now - this.lastRoleEffectProcess < 300 ) { + return // Save some processing + } + this.lastRoleEffectProcess = now + console.log("=========================+> Searching/Processing roles effects") + + let friends = canvas.tokens.placeables.filter(token => token.actor.type == "character" && token.document.disposition == 1) + let ennemies = canvas.tokens.placeables.filter(token => token.actor.type == "character" && token.document.disposition == -1) + + await this.processTactician(friends) + await this.processEnhancer(friends) + await this.processAgitator(ennemies) + + // Cleanup if disposition has changed + let allTokens = canvas.tokens.placeables.filter(token => token.actor.type == "character") + for (let token of allTokens) { + if (token.document.disposition != -1 && token.actor.hasAgitatorHindrance()) { + token.actor.removeAgitatorHindrance() + } + if (token.document.disposition != 1 && token.actor.hasTacticianBonus()) { + token.actor.removeTacticianEffect() + } + if (token.document.disposition != 1 && token.actor.hasEnhancerBonus()) { + token.actor.removeEnhancerEffect() + } + } + + } + + /* -------------------------------------------- */ + static async refreshSightForEffect() { + setTimeout(500, this.processRoleEffects()) + } + } \ No newline at end of file diff --git a/system.json b/system.json index a94ae06..03cef06 100644 --- a/system.json +++ b/system.json @@ -253,7 +253,7 @@ ], "title": "Pegasus RPG", "url": "https://www.uberwald.me/data/files/fvtt-pegasus-rpg", - "version": "10.0.28", - "download": "https://www.uberwald.me/gitea/uberwald/fvtt-pegasus-rpg/archive/fvtt-pegasus-rpg-v10.0.28.zip", + "version": "10.0.29", + "download": "https://www.uberwald.me/gitea/uberwald/fvtt-pegasus-rpg/archive/fvtt-pegasus-rpg-v10.0.29.zip", "background": "systems/fvtt-pegasus-rpg/images/ui/pegasus_welcome_page.webp" } \ No newline at end of file