diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9fdcbca..1000e8f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -77,3 +77,22 @@ jobs: asset_content_type: application/gzip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Commit and Push Updated module.json to deploy branch + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git add module.json + git commit -m "Update version in module.json" + git push origin deploy + + - name: Create Pull Request to push new version back to master + uses: repo-sync/pull-request@v2 + with: + source_branch: "deploy" + destination_branch: "master" + pr_title: "Deploy to Master" + pr_body: "Auto PR to update version number." + pr_label: "automerge" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/Old Module.txt b/Old Module.txt index 9489a0c..ab1e766 100644 --- a/Old Module.txt +++ b/Old Module.txt @@ -1,7 +1,6 @@ This file is just holding old module.json information for later. "dnd5e", "dnd5eJP", - "pf1", "pf2e", "D35E", "sw5e", @@ -26,14 +25,7 @@ This file is just holding old module.json information for later. "id": "dnd5eJP", "type": "system" }, - { - "id": "pf1", - "type": "system", - "manifest": "https://gitlab.com/foundryvtt_pathfinder1e/foundryvtt-pathfinder1/-/releases/v0.82.1/downloads/system.json", - "compatibility": { - "verified": "0.82.1" - } - }, + , { "id": "pf2e", "type": "system", diff --git a/css/pf1.css b/css/pf1.css new file mode 100644 index 0000000..f41577f --- /dev/null +++ b/css/pf1.css @@ -0,0 +1,7 @@ +/* WARNING: When mentioning any changes, all CSS must be prefixed with .game.system-pf1 in this file. If you need to edit Ceus-wide CSS, please edit ceus.css. */ +/* Pathfinder 1 Specific CSS */ +.game.system-pf1 .ceus.ceus-result header, +.game.system-pf1 .ceus.ceus-result .result-body .result-total, +.game.system-pf1 .ceus.ceus-result .result-body .result-breakdown { + background-color: rgba(255,255,255,0.3); +} \ No newline at end of file diff --git a/module.json b/module.json index 7de78b3..94b1e19 100644 --- a/module.json +++ b/module.json @@ -3,7 +3,7 @@ "name": "Ceus", "title": "Ceus - Ask For Rolls", "description": "Want your players to roll in a specific way? Ceus asks players for specific rolls, preventing them from spending time looking for rolls.", - "version": "0.80", + "version": "0.81", "author": "KaKaRoTo, iotech, Limping Ninja, Rughalt, Calego, VTT Lair, Imper1um", "authors": [{ "name": "Imper1um", @@ -14,7 +14,8 @@ ], "scripts": [], "styles": [ - "/css/ceus.css" + "/css/ceus.css", + "/css/pf1.css" ], "minimumCoreVersion": "11", "compatibleCoreVersion": "11", @@ -29,10 +30,19 @@ } ], "systems": [ + "pf1", "sf1e" ], "relationships": { "systems": [ + { + "id": "pf1", + "type": "system", + "manifest": "https://gitlab.com/foundryvtt_pathfinder1e/foundryvtt-pathfinder1/-/releases/v0.82.1/downloads/system.json", + "compatibility": { + "verified": "0.82.1" + } + }, { "id": "sfrpg", "type": "system", @@ -46,7 +56,7 @@ "socket": true, "url": "https://github.com/Imper1um/foundry-ceus/", "manifest": "https://github.com/Imper1um/foundry-ceus/releases/latest/download/module.json", - "download": "https://github.com/Imper1um/foundry-ceus/releases/download/v3.00/module.zip", + "download": "https://github.com/Imper1um/foundry-ceus/releases/download/0.81/module.zip", "changelog": "https://github.com/Imper1um/foundry-ceus/releases", "bugs": "https://github.com/Imper1um/foundry-ceus/issues" } \ No newline at end of file diff --git a/src/ceus.js b/src/ceus.js index 5018943..b33d55e 100644 --- a/src/ceus.js +++ b/src/ceus.js @@ -5,7 +5,6 @@ import { ceus_SocketEngine } from "./ceus_SocketEngine.js"; import { ceus_SettingsEngine } from "./ceus_SettingsEngine.js"; import { ceus_ResultsWindow } from "./ceus_ResultsWindow.js"; import { ceus_LogEngine } from "./ceus_LogEngine.js"; -import { CeusRequestor } from "./requestor.js"; export class Ceus { @@ -34,7 +33,7 @@ export class Ceus { } async onChatMessage(app, html, data) { - Ceus.log.Trace("onChatMessage", {app, html, data}); + if (Ceus.current && Ceus.current.settingsEngine) { Ceus.log.Trace("onChatMessage", {app, html, data}); } //Avoids conflicts when starting up. if (game.user.isGM) { return; } if (html.hasClass("sensitive") && html.hasClass("ceus")) { html.find('.result-total').text('???'); @@ -87,31 +86,10 @@ export class Ceus { } } - onThemeChange(enabled) { - $(".ceus.ceus-requestor,.ceus.ceus-roller").toggleClass("ceus-parchment", enabled); - if (!this.requestor) { return; } - if (enabled) { - this.requestor.options.classes.push("ceus-parchment"); - } else { - this.requestor.options.classes = this.requestor.options.classes.filter(c => c !== "ceus-parchment"); - } - // Resize to fit the new theme - if (this.requestor.element.length) { - this.requestor.setPosition({ width: "auto", height: "auto" }); - } - } - requestRoll() { - if (this.providerEngine.currentRollProvider.rollProviderType() === 'legacy') { - if (this.requestor === undefined) { - this.requestor = new CeusRequestor(); - } - this.requestor.render(true); - } else if (this.providerEngine.currentRollProvider.rollProviderType() === 'refactor') { - const requestwindow = new ceus_RequestWindow(); - this.requestWindows.push(requestwindow); - requestwindow.render(true); - } + const requestwindow = new ceus_RequestWindow(); + this.requestWindows.push(requestwindow); + requestwindow.render(true); } async registerHandlebarsHelpers() { @@ -212,80 +190,4 @@ export class Ceus { static requestRoll(data) { if (!game.user.isGM) { return; } } - - /*async onReady() { - this.providerEngine.onReady(); - this.socketEngine.onReady(); - - - - if (game.settings.get('ceus', 'deselectOnRequestorRender')) { - Hooks.on("renderCeusRequestor", () => { - canvas.tokens.releaseAll(); - }); - } - - - Hooks.on('renderChatMessage', this.hideBlind); - }*/ - - /* - results() { - if (this.results === undefined) { - this.results = new Array(); - } - return this.results; - } - addResults(newResults) { - const r = results(); - r.push(newResults); - this.results = r; - } - getResults(id) { - return results.find(r => r.id == id); - } - - - - - - async hideBlind(app, html, msg) { - if (msg.message.flags && msg.message.flags.ceus) { - if (msg.message.flags.ceus.blind && !game.user.isGM) { - msg.content = '

??

'; - - let idx = html[0].innerHTML.indexOf('
'); - html[0].innerHTML = html[0].innerHTML.substring(0, idx); - html[0].innerHTML += `
${msg.content}
`; - } - } - } - - fromUuid(uuid) { - let parts = uuid.split("."); - let doc; - - if (parts.length === 1) return game.actors.get(uuid); - // Compendium Documents - if (parts[0] === "Compendium") { - return undefined; - } - - // World Documents - else { - const [docName, docId] = parts.slice(0, 2); - parts = parts.slice(2); - const collection = CONFIG[docName].collection.instance; - doc = collection.get(docId); - } - - // Embedded Documents - while (parts.length > 1) { - const [embeddedName, embeddedId] = parts.slice(0, 2); - doc = doc.getEmbeddedDocument(embeddedName, embeddedId); - parts = parts.slice(2); - } - if (doc.actor) doc = doc.actor; - return doc || undefined; - } */ } \ No newline at end of file diff --git a/src/ceus_LogEngine.js b/src/ceus_LogEngine.js index df752ca..b53abc9 100644 --- a/src/ceus_LogEngine.js +++ b/src/ceus_LogEngine.js @@ -22,6 +22,7 @@ export class ceus_LogEngine { } Error(module, message, error = null, system = null) { + if (!Ceus.current.settingsEngine || !Ceus.current.settingsEngine.isInitialized) { return; } if (system == null) { system = this.system; } if (typeof message !== "string") { message = JSON.stringify(message); } const logLevel = Ceus.current.settingsEngine.LogLevel; @@ -41,6 +42,7 @@ export class ceus_LogEngine { } Warn(module, message, error = null, system = null) { + if (!Ceus.current.settingsEngine || !Ceus.current.settingsEngine.isInitialized) { return; } if (system == null) { system = this.system; } if (typeof message !== "string") { message = JSON.stringify(message); } const logLevel = Ceus.current.settingsEngine.LogLevel; @@ -60,6 +62,7 @@ export class ceus_LogEngine { } Info(module, message, error = null, system = null) { + if (!Ceus.current.settingsEngine || !Ceus.current.settingsEngine.isInitialized) { return; } if (system == null) { system = this.system; } if (typeof message !== "string") { message = JSON.stringify(message); } const logLevel = Ceus.current.settingsEngine.LogLevel; @@ -79,6 +82,7 @@ export class ceus_LogEngine { } Debug(module, message, error = null, system = null) { + if (!Ceus.current.settingsEngine || !Ceus.current.settingsEngine.isInitialized) { return; } if (system == null) { system = this.system; } if (typeof message !== "string") { message = JSON.stringify(message); } const logLevel = Ceus.current.settingsEngine.LogLevel; @@ -98,6 +102,7 @@ export class ceus_LogEngine { } Trace(module, message, error = null, system = null) { + if (!Ceus.current.settingsEngine || !Ceus.current.settingsEngine.isInitialized) { return; } if (system == null) { system = this.system; } if (typeof message !== "string") { message = JSON.stringify(message); } const logLevel = Ceus.current.settingsEngine.LogLevel; diff --git a/src/ceus_ProviderEngine.js b/src/ceus_ProviderEngine.js index 8ef1b38..d0f359b 100644 --- a/src/ceus_ProviderEngine.js +++ b/src/ceus_ProviderEngine.js @@ -1,3 +1,5 @@ +import { ceus_RollProvider_pf1 } from "./ceus_RollProvider_pf1.js"; +import { ceus_RollProvider_sf1e } from "./ceus_RollProvider_sf1e.js"; /*import { ceus_RollProvider_pf2e } from "./ceus_RollProvider_pf2e.js"; import { ceus_RollProvider_cof } from "./ceus_RollProvider_cof.js"; import { ceus_RollProvider_coc } from "./ceus_RollProvider_coc.js"; @@ -8,13 +10,13 @@ import { ceus_RollProvider_dnd5eJP } from "./ceus_RollProvider_dnd5eJP.js"; import { ceus_RollProvider_dnd35 } from "./ceus_RollProvider_dnd35.js"; import { ceus_RollProvider_ffd20 } from "./ceus_RollProvider_ffd20.js"; import { ceus_RollProvider_ose } from "./ceus_RollProvider_ose.js"; -import { ceus_RollProvider_pf1 } from "./ceus_RollProvider_pf1.js";*/ -import { ceus_RollProvider_sf1e } from "./ceus_RollProvider_sf1e.js"; -//import { ceus_RollProvider_sw5e } from "./ceus_RollProvider_sw5e.js"; +import { ceus_RollProvider_sw5e } from "./ceus_RollProvider_sw5e.js";*/ export class ceus_ProviderEngine { constructor() { this.externalRollProviders = [ + new ceus_RollProvider_pf1(), + new ceus_RollProvider_sf1e() /*new ceus_RollProvider_cd(), new ceus_RollProvider_coc(), new ceus_RollProvider_cof(), @@ -24,9 +26,7 @@ export class ceus_ProviderEngine { new ceus_RollProvider_dnd35(), new ceus_RollProvider_ffd20(), new ceus_RollProvider_ose(), - new ceus_RollProvider_pf1(), - new ceus_RollProvider_pf2e(),*/ - new ceus_RollProvider_sf1e()/*, + new ceus_RollProvider_pf2e(), new ceus_RollProvider_sw5e()*/ ]; } diff --git a/src/ceus_RollProvider_pf1.js b/src/ceus_RollProvider_pf1.js index 0addbc2..7299876 100644 --- a/src/ceus_RollProvider_pf1.js +++ b/src/ceus_RollProvider_pf1.js @@ -1,50 +1,420 @@ -import { ceus_RollProvider } from "./ceus_RollProvider.js"; +import { ceus_RefactorRollProvider } from "./ceus_RefactorRollProvider.js"; +import { CeusRoller } from "./roller.js"; +import { Ceus } from "./ceus.js"; +import { ceus_Result } from "./ceus_Result.js"; +import { ceus_LogEngine } from "./ceus_LogEngine.js"; -export class ceus_RollProvider_pf1 extends ceus_RollProvider { +/** + * RollProvider for Pathfinder (1st Edition) (pf1) + * + * systemIdentifier: pf1 + * trained: enabled + * results: enabled + * advantage/disadvantage: disabled + * DC: enabled + * rollMode: enabled + */ +export class ceus_RollProvider_pf1 extends ceus_RefactorRollProvider { + constructor() { + super(); + } + + static get log() { + if (!ceus_RollProvider_pf1._log) { + ceus_RollProvider_pf1._log = new ceus_LogEngine("pf1"); + } + return ceus_RollProvider_pf1._log; + } systemIdentifiers() { return 'pf1'; } - abilities() { - return CONFIG.PF1.abilities; + trainedOptions() { + return [ "HideUntrained", "PreventUntrained", "AllowUntrained" ]; } - - abilityAbbreviations() { - return CONFIG.PF1.abilitiesShort; + + rollTrainedOptions(rollType, id) { + switch (rollType) { + case CeusRoller.rollTypes().INITIATIVE: + case CeusRoller.rollTypes().ABILITY: + case CeusRoller.rollTypes().PERCEPTION: + case CeusRoller.rollTypes().SAVE: + return [ "AllowUntrained" ]; + } + + return this.trainedOptions(); } - - abilityRollMethod() { - return 'rollAbility'; + + isActorTrained(actor, rollType, id) { + if (rollType != CeusRoller.rollTypes().SKILL) { + return true; + } + var skill = actor.system.skills[id]; + if (!skill) { + const availableSkillRoll = this.getAvailableSkillRolls().find(s => s.id === id); + if (availableSkillRoll) { + skill = actor.system.skills[availableSkillRoll.skillId]; + } + } + if (!skill) { return false; } + return skill.rank > 0; } - - advantageRollEvent() { - return new ceus_RollEvent(false, true, false); + + getActorRollBonus(actor, rollType, id) { + switch (rollType) { + case CeusRoller.rollTypes().SKILL: + var skill = actor.system.skills[id]; + if (!skill) { + const availableSkillRoll = this.getAvailableSkillRolls().find(s => s.id === id); + if (availableSkillRoll) { + skill = actor.system.skills[availableSkillRoll.skillId]; + } + } + if (!skill) { return null; } + return skill.mod; + case CeusRoller.rollTypes().INITIATIVE: + return actor.system.attributes.init.value; + case CeusRoller.rollTypes().PERCEPTION: + return actor.system.skills.per.mod; + case CeusRoller.rollTypes().ABILITY: + var ability = actor.system.abilities[id]; + if (!ability) { + const availableAbilityRoll = this.getAvailableAbilityRolls().find(a => a.id === id); + if (availableAbilityRoll) { + ability = actor.system.abilities[availableAbilityRoll.abilityId]; + } + } + if (!ability) { return null; } + return ability.mod; + case CeusRoller.rollTypes().SAVE: + var save = actor.system.attributes.savingThrows[id]; + if (!save) { + const availableSaveRoll = this.getAvailableSaveRolls().find(a => a.id === id); + if (availableSaveRoll) { + save = actor.system.attributes.savingThrows[availableSaveRoll.saveId]; + } + } + if (!save) { return null; } + return save.value; + } + return null; } - - disadvantageRollEvent() { - return new ceus_RollEvent(false, false, true); + + isPlayer(actor) { + return actor.type === "character"; } - - normalRollEvent() { - return new ceus_RollEvent(false, false, false); + + canActorSeeRoll(actor, rollType, id, trainedOption) { + var train = this.isActorTrained(actor, rollType, id); + switch (trainedOption) { + case "HideUntrained": + return train; + } + return true; } - - saveRollMethod() { - return 'rollSavingThrow'; + + canActorRoll(actor, rollType, id, trainedOption) { + var train = this.isActorTrained(actor, rollType, id); + switch (trainedOption) { + case "HideUntrained": + case "PreventUntrained": + return train; + } + return true; } - - saves() { - return CONFIG.PF1.savingThrows; + + getInitiativeContexts() { + const initiativeContexts = new Array(); + for (const combat of game.combats) { + initiativeContexts.push({"id":combat._id, "name":`${combat.combatants.size} on ${combat.scene.name}`}); + } + ceus_RollProvider_pf1.log.Trace("getInitiativeContexts", initiativeContexts); + return initiativeContexts; } - - skills() { - return CONFIG.PF1.skills; + + getAvailableRolls() { + return [ + { + id: "Special", + name: "PF1.WeaponPropSpecial", + type: "category", + rolls: [ + { id: "Initiative", name: "PF1.Initiative", type: "roll", rollType: CeusRoller.rollTypes().INITIATIVE, method: this.rollInitiative, contexts: this.getInitiativeContexts, canCritSuccess: false, canCritFail: false }, + { id: "Perception", name: "PF1.SkillPer", type: "roll", rollType:CeusRoller.rollTypes().PERCEPTION, method: this.rollPerception, canCritSuccess: false, canCritFail: false } + ] + }, + { + id: "Abilities", + name: "PF1.Ability", + type: "category", + rolls: this.getAvailableAbilityRolls() + }, + { + id: "Saves", + name: "PF1.Save", + type: "category", + rolls: this.getAvailableSaveRolls() + }, + { + id: "Skills", + name: "PF1.Skills", + type: "category", + rolls: this.getAvailableSkillRolls() + }, + ]; } - - skillRollMethod() { - return 'rollSkill'; + + getAvailableAbilityRolls() { + const abilities = CONFIG.PF1.abilities; + return Object.keys(abilities).map(key => { + const capitalizedKey = key.charAt(0).toUpperCase() + key.slice(1); + return { + id: `Ability${capitalizedKey}`, + name: `PF1.Ability${capitalizedKey}`, + type: "roll", + rollType: CeusRoller.rollTypes().ABILITY, + method: this.rollAbility, + abilityId: key, + canCritSuccess: true, + canCritFail: true + }; + }); } - - specialRolls() { - return {'initiative': true, 'deathsave': false, 'perception': false}; + + getAvailableSaveRolls() { + const saves = CONFIG.PF1.savingThrows; + return Object.keys(saves).map(key => { + const saveName = saves[key]; + const capitalizedKey = key.charAt(0).toUpperCase() + key.slice(1); + return { + id: `${saveName}Save`, + name: `PF1.SavingThrow${capitalizedKey}`, + type: "roll", + rollType: CeusRoller.rollTypes().SAVE, + method: this.rollSave, + saveId: key, + canCritSuccess: true, + canCritFail: true + }; + }); + } + + getAvailableSkillRolls() { + const abilities = CONFIG.PF1.skills; + return Object.keys(abilities).map(key => { + const capitalizedKey = (key.charAt(0).toUpperCase() + key.slice(1)).substring(0, 3); + var translatedKey = capitalizedKey; + if (capitalizedKey.startsWith("K")) { + //Knowledge skills are different. The second letter is capitalized. + translatedKey = key.charAt(0).toUpperCase() + key.charAt(1).toUpperCase() + key.slice(2); + } else if (capitalizedKey == "Umd") { + //UMD is UMD. + translatedKey = "UMD"; + } + + return { + id: `Skill${capitalizedKey}`, + name: `PF1.Skill${translatedKey}`, + type: "roll", + rollType: CeusRoller.rollTypes().SKILL, + method: this.rollSkill, + skillId: key, + canCritSuccess: false, + canCritFail: translatedKey == "UMD" ? true : false //UMD can Crit Fail + }; + }); + } + + baseRollOptions() { + return { + chatMessage: false + }; + } + + async rollSkill(requestOptions, actor, requestItem) { + ceus_RollProvider_pf1.log.Trace("rollSkill", {requestOptions, actor, requestItem}); + const rp = Ceus.current.providerEngine.currentRollProvider; + const skillRoll = rp.getAvailableRolls().find(r => r.id === "Skills").rolls.find(r => r.id === requestItem.rollId); + const skillId = skillRoll.skillId; + const skill = actor.system.skills[skillRoll.skillId]; + const rollOptions = rp.baseRollOptions(); + var completeRoll; + switch (requestItem.trainedOption) { + case "HideUntrained": + if (skill.isTrainedOnly && skill.ranks < 1) { + return new ceus_Result( + requestOptions.requestId, + actor._id, + requestItem.id, + game.data.userId, + null, + false, + null, + null + ); + } + completeRoll = await actor.rollSkill(skillId, rollOptions); + break; + case "PreventUntrained": + if (skill.isTrainedOnly && skill.ranks < 1) { + completeRoll = await actor.rollSkill(skillId, rollOptions); + } else { + completeRoll = await actor.rollSkill(skillId, rollOptions); + } + break; + default: + completeRoll = await actor.rollSkill(skillId, rollOptions); + break; + + } + return rp.buildResult(requestOptions, actor, requestItem, completeRoll); + } + + async rollPerception(requestOptions, actor, requestItem) { + ceus_RollProvider_pf1.log.Trace("rollPerception", {requestOptions, actor, requestItem}); + const rp = Ceus.current.providerEngine.currentRollProvider; + const skill = actor.system.skills["per"]; + const skillId = "per"; + const completeRoll = await actor.rollSkill(skillId, skill, rp.baseRollOptions()); + return rp.buildResult(requestOptions, actor, requestItem, completeRoll); + } + + async rollSave(requestOptions, actor, requestItem) { + ceus_RollProvider_pf1.log.Trace("rollSave", {requestOptions, actor, requestItem}); + const rp = Ceus.current.providerEngine.currentRollProvider; + const saveRoll = rp.getAvailableRolls().find(r => r.id === "Saves").rolls.find(r => r.id === requestItem.rollId); + const completeRoll = await actor.rollSavingThrow(saveRoll.saveId, rp.baseRollOptions()); + return rp.buildResult(requestOptions, actor, requestItem, completeRoll); + } + + async rollAbility(requestOptions, actor, requestItem) { + ceus_RollProvider_pf1.log.Trace("rollAbility", {requestOptions, actor, requestItem}); + const rp = Ceus.current.providerEngine.currentRollProvider; + const abilityRoll = rp.getAvailableRolls().find(r => r.id === "Abilities").rolls.find(r => r.id === requestItem.rollId); + const completeRoll = await actor.rollAbilityTest(abilityRoll.abilityId, rp.baseRollOptions()); + return rp.buildResult(requestOptions, actor, requestItem, completeRoll); + } + + async rollInitiative(requestOptions, actor, requestItem) { + ceus_RollProvider_pf1.log.Trace("rollInitiative", {requestOptions, actor, requestItem}); + const rp = Ceus.current.providerEngine.currentRollProvider; + const combat = game.combats.get(requestOptions.contextId); + if (!combat) { return; } + var rollOptions = {formula: null, updateTurn: true, messageOptions: {}, requireMode: null, requireRollState: null}; + switch (requestOptions.rollPrivacy) { + case "public": + rollOptions.rollMode = "publicroll"; + break; + case "blind": + rollOptions.rollMode = "blindroll"; + break; + case "gm": + rollOptions.rollMode = "gmroll"; + break; + case "self": + rollOptions.rollMode = "selfroll"; + break; + } + //Roll Privacy can't be enforced for Initiative. + const newCombat = await combat.rollInitiative(actor._id, rollOptions); + const newTurn = newCombat.turns.find(t => t.actorId === actor._id); + if (!newTurn) { return; } + const result = new ceus_Result( + requestOptions.id, + requestOptions.resultId, + actor._id, + requestItem.id, + game.data.userId, + newTurn.resource, + true, + true, + null); + return result; + } + + buildResult(requestOptions, actor, requestItem, roll) { + ceus_RollProvider_pf1.log.Trace("buildResult", {requestOptions, actor, requestItem, roll}); + let rollType; + if (roll.button === "advantage") { + rollType = "advantage"; + } else if (roll.button === "disadvantage") { + rollType = "disadvantage"; + } else { + rollType = "normal"; + } + var isCritSuccess = false; + var isCritFail = false; + const finalRoll = typeof roll.rolls[0] === 'string' ? JSON.parse(roll.rolls[0]) : roll.rolls[0]; //I don't know why it does this. + for (const term of finalRoll.terms) { + if (!term.faces || term.faces !== 20) { continue; } + if (term.results[0].result === 20 && requestItem.canCritSuccess) { isCritSuccess = true; } + if (term.results[0].result === 1 && requestItem.canCritFail) { isCritFail = true; } + } + var isPass = null; + if (requestItem.dc) { + isPass = requestItem.dc <= finalRoll.total; + if (isCritSuccess) { isPass = true; } + if (isCritFail) { isPass = false; } + } + + return new ceus_Result( + requestOptions.id, + requestOptions.resultId, + actor._id, + requestItem.rollId, + game.data.userId, + finalRoll.total, + true, + isPass, + rollType, + finalRoll.formula, + isCritFail, + isCritSuccess + ); + } + + resultsEnabled() { + return true; + } + + canActionAdvantage(rollType, id) { + return false; //Advantage/Disadvantage can't base be passed into the Pathfinder 1e roll system. You can't force it (at least through PF1) + } + + canActionDisadvantage(rollType, id) { + return false; + } + + permitAdvantageDisadvantage() { + return false; + } + needsContext(requestOptions) { + if (requestOptions.requestItems.some(ri => ri.id == "Initiative")) { + return true; + } + return false; + } + getContextList(requestOptions) { + if (requestOptions.requestItems.some(ri => ri.id == "Initiative")) { + return this.getInitiativeContexts(); + } + return null; + } + permitDC() { + return true; + } + allowDC(rollType, id) { + if (rollType == CeusRoller.rollTypes().INITIATIVE) { + return false; + } + return true; + } + permitSetRollPrivacy() { + return true; + } + permitRequireRollPrivacy() { + return true; + } + displayRequiredByMod() { + return true; } } \ No newline at end of file diff --git a/src/ceus_RollProvider_sf1e.js b/src/ceus_RollProvider_sf1e.js index 6d35fff..eb201bd 100644 --- a/src/ceus_RollProvider_sf1e.js +++ b/src/ceus_RollProvider_sf1e.js @@ -212,7 +212,7 @@ export class ceus_RollProvider_sf1e extends ceus_RefactorRollProvider { const rp = Ceus.current.providerEngine.currentRollProvider; const saveRoll = rp.getAvailableRolls().find(r => r.id === "Saves").rolls.find(r => r.id === requestItem.rollId); const completeRoll = await actor.rollSave(saveRoll.saveId, rp.baseRollOptions()); - return this.buildResult(requestOptions, actor, requestItem, completeRoll); + return rp.buildResult(requestOptions, actor, requestItem, completeRoll); } async rollAbility(requestOptions, actor, requestItem) { diff --git a/src/ceus_SettingsEngine.js b/src/ceus_SettingsEngine.js index a9bbfdb..4478d9b 100644 --- a/src/ceus_SettingsEngine.js +++ b/src/ceus_SettingsEngine.js @@ -50,11 +50,13 @@ export class ceus_SettingsEngine { config: setting.config, type: setting.type, default: setting.default, - onChange: setting.onChange + onChange: setting.onChange, + choices: setting.choices }); } console.log("Ceus | Game settings ready."); + this.isInitialized = true; } get LogLevel() { diff --git a/src/ceus_SocketEngine.js b/src/ceus_SocketEngine.js index e14c992..d3b344e 100644 --- a/src/ceus_SocketEngine.js +++ b/src/ceus_SocketEngine.js @@ -99,22 +99,28 @@ export class ceus_SocketEngine { async pushRefactorRequest(request) { ceus_SocketEngine.log.Trace("pushRefactorRequest", request); - await game.socket.emit('module.ceus', {type: 'refactor', request}); + const data = {type: 'refactor', request}; + await game.socket.emit('module.ceus', data); + await this.onRefactorRequest(data.request); ui.notifications.info(game.i18n.localize("Ceus.Requestor.Sent")); } async pushCancelResponse(request, userid) { ceus_SocketEngine.log.Trace("pushCancelResponse", {request, userid}); - await game.socket.emit('module.ceus', {type: 'cancel', request: { + const data = {type: 'cancel', request: { userid, request - }}); + }}; + await game.socket.emit('module.ceus', data); + await this.onRequestCancel(data.request); } async pushCompleteResponse(request, userid, response) { ceus_SocketEngine.log.Trace("onRefactorRequest", {request,userid,response}); - await game.socket.emit('module.ceus', {type: 'complete', request: { + const data = {type: 'complete', request: { userid, request, response - }}); + }}; + await game.socket.emit('module.ceus', data); + await this.onRequestComplete(data.request); } } \ No newline at end of file diff --git a/src/ceus_RollProvider.js b/src/old code/ceus_RollProvider.js similarity index 100% rename from src/ceus_RollProvider.js rename to src/old code/ceus_RollProvider.js diff --git a/src/ceus_RollProvider_cd.js b/src/old code/ceus_RollProvider_cd.js similarity index 100% rename from src/ceus_RollProvider_cd.js rename to src/old code/ceus_RollProvider_cd.js diff --git a/src/ceus_RollProvider_coc.js b/src/old code/ceus_RollProvider_coc.js similarity index 100% rename from src/ceus_RollProvider_coc.js rename to src/old code/ceus_RollProvider_coc.js diff --git a/src/ceus_RollProvider_cof.js b/src/old code/ceus_RollProvider_cof.js similarity index 100% rename from src/ceus_RollProvider_cof.js rename to src/old code/ceus_RollProvider_cof.js diff --git a/src/ceus_RollProvider_degenesis.js b/src/old code/ceus_RollProvider_degenesis.js similarity index 100% rename from src/ceus_RollProvider_degenesis.js rename to src/old code/ceus_RollProvider_degenesis.js diff --git a/src/ceus_RollProvider_demonlord.js b/src/old code/ceus_RollProvider_demonlord.js similarity index 100% rename from src/ceus_RollProvider_demonlord.js rename to src/old code/ceus_RollProvider_demonlord.js diff --git a/src/ceus_RollProvider_dnd35.js b/src/old code/ceus_RollProvider_dnd35.js similarity index 100% rename from src/ceus_RollProvider_dnd35.js rename to src/old code/ceus_RollProvider_dnd35.js diff --git a/src/ceus_RollProvider_dnd5e.js b/src/old code/ceus_RollProvider_dnd5e.js similarity index 100% rename from src/ceus_RollProvider_dnd5e.js rename to src/old code/ceus_RollProvider_dnd5e.js diff --git a/src/ceus_RollProvider_dnd5eJP.js b/src/old code/ceus_RollProvider_dnd5eJP.js similarity index 100% rename from src/ceus_RollProvider_dnd5eJP.js rename to src/old code/ceus_RollProvider_dnd5eJP.js diff --git a/src/ceus_RollProvider_ffd20.js b/src/old code/ceus_RollProvider_ffd20.js similarity index 100% rename from src/ceus_RollProvider_ffd20.js rename to src/old code/ceus_RollProvider_ffd20.js diff --git a/src/ceus_RollProvider_ose.js b/src/old code/ceus_RollProvider_ose.js similarity index 100% rename from src/ceus_RollProvider_ose.js rename to src/old code/ceus_RollProvider_ose.js diff --git a/src/ceus_RollProvider_pf2e.js b/src/old code/ceus_RollProvider_pf2e.js similarity index 100% rename from src/ceus_RollProvider_pf2e.js rename to src/old code/ceus_RollProvider_pf2e.js diff --git a/src/ceus_RollProvider_sw5e.js b/src/old code/ceus_RollProvider_sw5e.js similarity index 100% rename from src/ceus_RollProvider_sw5e.js rename to src/old code/ceus_RollProvider_sw5e.js diff --git a/src/requestor.js b/src/old code/requestor.js similarity index 100% rename from src/requestor.js rename to src/old code/requestor.js diff --git a/src/old code/roller.js b/src/old code/roller.js new file mode 100644 index 0000000..a1a9236 --- /dev/null +++ b/src/old code/roller.js @@ -0,0 +1,584 @@ +import { Ceus } from "./ceus.js"; + +export class CeusRoller extends Application { + + constructor(actors, data) { + super(); + this.actors = actors; + this.data = data; + this.abilities = data.abilities; + this.saves = data.saves; + this.skills = data.skills; + this.advantage = data.advantage; + this.mode = data.mode; + this.message = data.message; + this.tables = data.tables; + this.chooseOne = data.chooseOne ?? false; + this.dc = data.dc; + + if (data.title) { + this.options.title = data.title; + } + + this.hasMidi = game.modules.get("midi-qol")?.active; + this.midiUseNewRoller = isNewerVersion(game.modules.get("midi-qol")?.version, "10.0.26"); + + Handlebars.registerHelper('canFailAbilityChecks', function (name, ability) { + if (Ceus.currentRollProvider.canFailChecks()) { + return `
` + + `` + + `
` + + `${Ceus.d20Svg}` + + `
` + + `
`; + } else { + return ''; + } + }); + + Handlebars.registerHelper('canFailSaveChecks', function (name, ability) { + if (Ceus.currentRollProvider.canFailChecks()) { + return `
` + + `` + + `
` + + `${Ceus.d20Svg}` + + `
` + + `
`; + } else { + return ''; + } + }); + + Handlebars.registerHelper('canFailSkillChecks', function (name, skill) { + if (Ceus.currentRollProvider.canFailChecks()) { + return `
` + + `` + + `
` + + `${Ceus.d20Svg}` + + `
` + + `
`; + } else { + return ''; + } + }); + } + + static get defaultOptions() { + const options = super.defaultOptions; + options.title = game.i18n.localize("Ceus.Title"); + options.template = "modules/ceus/templates/roller.html"; + options.popOut = true; + options.width = 400; + options.height = "auto"; + options.classes = ["ceus", "ceus-roller"]; + if (game.settings.get('ceus', 'enableParchmentTheme')) { + options.classes.push('ceus-parchment'); + } + return options; + } + + static requestAbilityChecks(actor, abilities, options={}) { + if (!actor || !abilities) return; + if (typeof(abilities) === "string") abilities = [abilities]; + const data = mergeObject(options, { + abilities: [], + saves: [], + skills: [] + }, {inplace: false}); + data.abilities = abilities; + new CeusRoller([actor], data).render(true); + } + static requestSkillChecks(actor, skills, options={}) { + if (!actor || !skills) return; + if (typeof(skills) === "string") skills = [skills]; + const data = mergeObject(options, { + abilities: [], + saves: [], + skills: [] + }, {inplace: false}); + data.skills = skills; + new CeusRoller([actor], data).render(true); + } + static requestSavingThrows(actor, saves, options={}) { + if (!actor || !saves) return; + if (typeof(saves) === "string") saves = [saves]; + const data = mergeObject(options, { + abilities: [], + saves: [], + skills: [] + }, {inplace: false}); + data.saves = saves; + new CeusRoller([actor], data).render(true); + } + static rollTypes() { + return { + ABILITY: "ability", + SAVE: "save", + SKILL: "skill", + PERCEPTION: "perception", + INITIATIVE: "initiative", + DEATHSAVE: "deathsave", + DICE: "dice", + CUSTOM: "custom" + }; + } + + async getData() { + let note = "" + if (this.advantage == 1) + note = game.i18n.localize("Ceus.AdvantageNote"); + else if (this.advantage == -1) + note = game.i18n.localize("Ceus.DisadvantageNote"); + + let abilities = {} + let saves = {} + let skills = {} + this.abilities.forEach(a => abilities[a] = Ceus.currentRollProvider.abilities()[a]) + this.saves.forEach(a => saves[a] = Ceus.currentRollProvider.saves()[a]) + this.skills + .sort((a, b) => { + const skillA = (Ceus.currentRollProvider.skills()[a]?.label) ? Ceus.currentRollProvider.skills()[a].label : Ceus.currentRollProvider.skills()[a]; + const skillB = (Ceus.currentRollProvider.skills()[b]?.label) ? Ceus.currentRollProvider.skills()[b].label : Ceus.currentRollProvider.skills()[b]; + game.i18n.localize(skillA).localeCompare(skillB) + }) + .forEach(s => { + const skill = (Ceus.currentRollProvider.skills()[s]?.label) ? Ceus.currentRollProvider.skills()[s].label : Ceus.currentRollProvider.skills()[s]; + skills[s] = skill; + }); + + const data = { + actors: this.actors, + abilities: abilities, + saves: saves, + skills: skills, + note: note, + message: this.message, + customFormula: this.data.formula || false, + deathsave: this.data.deathsave, + initiative: this.data.initiative, + perception: this.data.perception, + tables: this.tables, + chooseOne: this.chooseOne, + }; + + return data; + } + + activateListeners(html) { + super.activateListeners(html); + this.element.find(".ceus-ability-check").click(this._onAbilityCheck.bind(this)) + this.element.find(".ceus-ability-save").click(this._onAbilitySave.bind(this)) + this.element.find(".ceus-skill-check").click(this._onSkillCheck.bind(this)) + this.element.find(".ceus-custom-formula").click(this._onCustomFormula.bind(this)) + this.element.find(".ceus-roll-table").click(this._onRollTable.bind(this)); + var specialRolls = Ceus.currentRollProvider.specialRolls(); + if(specialRolls['initiative']) { + this.element.find(".ceus-initiative").click(this._onInitiative.bind(this)) + } + if(specialRolls['deathsave']) { + this.element.find(".ceus-death-save").click(this._onDeathSave.bind(this)) + } + if(specialRolls['perception']) { + this.element.find(".ceus-perception").click(this._onPerception.bind(this)) + } + + this.element.find(".enable-ceus-ability-check-fail").click(this._onToggleFailAbilityRoll.bind(this)); + this.element.find(".ceus-ability-check-fail").click(this._onFailAbilityCheck.bind(this)); + + this.element.find(".enable-ceus-ability-save-fail").click(this._onToggleFailSaveRoll.bind(this)); + this.element.find(".ceus-ability-save-fail").click(this._onFailAbilitySave.bind(this)); + + this.element.find(".enable-ceus-skill-check-fail").click(this._onToggleFailSkillRoll.bind(this)); + this.element.find(".ceus-skill-check-fail").click(this._onFailSkillCheck.bind(this)); + } + + _checkClose() { + if (this.element.find("button").filter((i, e) => !e.disabled).length === 0 || this.chooseOne) { + this.close(); + } + } + + _disableButtons(event) { + event.currentTarget.disabled = true; + + if (Ceus.canFailChecks) { + const buttonSelector = `${event.currentTarget.className}`; + let oppositeSelector = ""; + let dataSelector = ""; + + if ( + event.currentTarget.className.indexOf('ability-check') > 0 || + event.currentTarget.className.indexOf('ability-save') > 0 + ) { + dataSelector = `[data-ability *= '${event?.currentTarget?.dataset?.ability}']`; + } else { + dataSelector = `[data-skill *= '${event?.currentTarget?.dataset?.skill}']`; + } + + if (event.currentTarget.className.indexOf('fail') > 0) { + oppositeSelector = event.currentTarget.className.substring(0, event.currentTarget.className.indexOf('fail') - 1); + } else { + oppositeSelector = `${event.currentTarget.className}-fail`; + } + + const enableButton = document.querySelector(`.enable-${buttonSelector}${dataSelector}`); + if (enableButton) { + enableButton.disabled = true; + enableButton.classList.add('disabled-button'); + } + + const oppositeButton = document.querySelector(`.${oppositeSelector}${dataSelector}`); + if (oppositeButton) oppositeButton.disabled = true; + } + } + + _getRollOptions(event, failRoll) { + let options; + switch(this.advantage) { + case -1: + options = {... Ceus.currentRollProvider.disadvantageRollEvent() }; + break; + case 0: + options = {... Ceus.currentRollProvider.normalRollEvent() }; + break; + case 1: + options = {... Ceus.currentRollProvider.advantageRollEvent() }; + break; + case 2: + options = { event: event }; + break; + } + + if (failRoll) { + options["parts"] = [-100]; + } + + return options; + } + + async _makeRoll(event, rollMethod, rolledType, failRoll, ...args) { + let options = this._getRollOptions(event, failRoll); + + // save the current roll mode to reset it after this roll + const rollMode = game.settings.get("core", "rollMode"); + game.settings.set("core", "rollMode", this.mode || CONST.DICE_ROLL_MODES); + + for (let actor of this.actors) { + Hooks.once("preCreateChatMessage", this._tagMessage.bind(this)); + + if (Ceus.currentRollProvider.handleCustomRoll(actor, event, rollMethod, rolledType, failRoll, this.dc, args)) { + continue; + } + + await actor[rollMethod].call(actor, ...args, options); + } + + game.settings.set("core", "rollMode", rollMode); + + this._disableButtons(event); + this._checkClose(); + } + + _makePF2EInitiativeRoll(event) { + // save the current roll mode to reset it after this roll + const rollMode = game.settings.get("core", "rollMode"); + game.settings.set("core", "rollMode", this.mode || CONST.DICE_ROLL_MODES); + + for (let actor of this.actors) { + const initiative = actor.data.data.attributes.initiative; + const rollNames = ['all', 'initiative']; + if (initiative.ability === 'perception') { + rollNames.push('wis-based'); + rollNames.push('perception'); + } else { + const skill = actor.data.data.skills[initiative.ability]; + rollNames.push(`${skill.ability}-based`); + rollNames.push(skill.name); + } + const options = actor.getRollOptions(rollNames); + initiative.roll({ event, options }); + } + + game.settings.set("core", "rollMode", rollMode); + + event.currentTarget.disabled = true; + this._checkClose(); + } + + _tagMessage(candidate, data, options) { + candidate.updateSource({"flags.ceus": {"message": this.data.message, "data": this.data.attach, "blind": candidate.blind}}); + } + + async _makeDiceRoll(event, formula, defaultMessage = null) { + if (formula.startsWith("1d20")) { + if (this.advantage === 1) + formula = formula.replace("1d20", "2d20kh1") + else if (this.advantage === -1) + formula = formula.replace("1d20", "2d20kl1") + } + + const messageFlag = {"message": this.data.message, "data": this.data.attach}; + + const rollMessages = []; + const rollMessagePromises = this.actors.map(async (actor) => { + const speaker = ChatMessage.getSpeaker({actor: actor}); + + const rollData = actor.getRollData(); + const roll = new Roll(formula, rollData); + const rollMessageData = await roll.toMessage( + {"flags.ceus": messageFlag}, + {rollMode: this.mode, create: false}, + ); + + rollMessages.push( + mergeObject( + rollMessageData, + { + speaker: { + alias: speaker.alias, + scene: speaker.scene, + token: speaker.token, + actor: speaker.actor, + }, + flavor: this.message || defaultMessage, + rollMode: this.mode, + }, + ), + ); + }) + + await Promise.allSettled(rollMessagePromises); + await ChatMessage.create(rollMessages, {rollMode: this.mode}); + + event.currentTarget.disabled = true; + this._checkClose(); + } + + _drawTable(event, table) { + const icons = { + Actor: 'fas fa-user', + Item: 'fas fa-suitcase', + Scene: 'fas fa-map', + JournalEntry: 'fas fa-book-open', + Macro: 'fas fa-terminal', + Playlist: '', + Compendium: 'fas fa-atlas', + } + + let chatMessages = []; + let count = 0; + const rollTable = game.tables.getName(table); + + if (rollTable) { + for (let actor of this.actors) { + rollTable.draw({ displayChat: false }).then((res) => { + count++; + const rollResults = res.results; + + const nr = rollResults.length > 1 ? `${rollResults.length} results` : "a result"; + let content = ""; + + for (const rollResult of rollResults) { + const result = rollResult; + + if (!result.documentCollection) { + content += `

${result.text}

`; + } else if (['Actor', 'Item', 'Scene', 'JournalEntry', 'Macro'].includes(result.documentCollection)) { + content += `

+ ${result.text}

`; + } else if (result.documentCollection === 'Playlist') { + content += `

@${result.documentCollection}[${result.documentId}]{${result.text}}

`; + } else if (result.documentCollection) { // if not specific collection, then is compendium + content += `

+ ${result.text}

`; + } + } + let chatData = { + user: game.user.id, + speaker: ChatMessage.getSpeaker({actor}), + flavor: `Draws ${nr} from the ${table} table.`, + content: content, + type: CONST.CHAT_MESSAGE_TYPES.OTHER, + }; + + if ( ["gmroll", "blindroll"].includes(this.mode) ) { + chatData.whisper = ChatMessage.getWhisperRecipients("GM"); + } + if ( this.mode === "selfroll" ) chatData.whisper = [game.user.id]; + if ( this.mode === "blindroll" ) chatData.blind = true; + + setProperty(chatData, "flags.ceus", {"message": this.data.message, "data": this.data.attach, "blind": chatData.blind}); + + chatMessages.push(chatData); + + if (count === this.actors.length) { + ChatMessage.create(chatMessages, {}); + + event.currentTarget.disabled = true; + this._checkClose(); + } + }); + } + } + } + + _onAbilityCheck(event) { + event.preventDefault(); + const ability = event.currentTarget.dataset.ability; + + // until patching has been removed + if (!this.hasMidi || this.midiUseNewRoller) { + this._makeRoll(event, Ceus.currentRollProvider.abilityRollMethod(), CeusRoller.rollTypes().ABILITY, false, ability); + } else { + this._makeRoll(event, Ceus.currentRollProvider.abilityRollMethod(), CeusRoller.rollTypes().ABILITY, ability); + } + } + + _onFailAbilityCheck(event) { + event.preventDefault(); + const ability = event.currentTarget.dataset.ability; + + // until patching has been removed + if (!this.hasMidi || this.midiUseNewRoller) { + this._makeRoll(event, Ceus.currentRollProvider.abilityRollMethod(), CeusRoller.rollTypes().ABILITY, true, ability); + } else { + this._makeRoll(event, Ceus.currentRollProvider.abilityRollMethod(), CeusRoller.rollTypes().ABILITY, ability); + } + } + + _onAbilitySave(event) { + event.preventDefault(); + const saves = event.currentTarget.dataset.ability; + + // until patching has been removed + if (!this.hasMidi || this.midiUseNewRoller) { + this._makeRoll(event, Ceus.currentRollProvider.saveRollMethod(), CeusRoller.rollTypes().SAVE, false, saves); + } else { + this._makeRoll(event, Ceus.currentRollProvider.saveRollMethod(), CeusRoller.rollTypes().SAVE, saves); + } + } + + _onFailAbilitySave(event) { + event.preventDefault(); + const saves = event.currentTarget.dataset.ability; + + // until patching has been removed + if (!this.hasMidi || this.midiUseNewRoller) { + this._makeRoll(event, Ceus.currentRollProvider.saveRollMethod(), CeusRoller.rollTypes().SAVE, true, saves); + } else { + this._makeRoll(event, Ceus.currentRollProvider.saveRollMethod(), CeusRoller.rollTypes().SAVE, saves); + } + } + + _onSkillCheck(event) { + event.preventDefault(); + const skill = event.currentTarget.dataset.skill; + + // until patching has been removed + if (!this.hasMidi || this.midiUseNewRoller) { + this._makeRoll(event, Ceus.currentRollProvider.skillRollMethod(), CeusRoller.rollTypes().SKILL, false, skill); + } else { + this._makeRoll(event, Ceus.currentRollProvider.skillRollMethod(), CeusRoller.rollTypes().SKILL, skill); + } + } + + _onFailSkillCheck(event) { + event.preventDefault(); + const skill = event.currentTarget.dataset.skill; + + // until patching has been removed + if (!this.hasMidi || this.midiUseNewRoller) { + this._makeRoll(event, Ceus.currentRollProvider.skillRollMethod(), CeusRoller.rollTypes().SKILL, true, skill); + } else { + this._makeRoll(event, Ceus.currentRollProvider.skillRollMethod(), CeusRoller.rollTypes().SKILL, skill); + } + } + + async _onCustomFormula(event) { + event.preventDefault(); + await this._makeDiceRoll(event, this.data.formula); + } + + _onInitiative(event) { + event.preventDefault(); + + //Custom Event Handling for Initiative Rolls (if needed) + var initRollHandling = Ceus.currentRollProvider.handleInitiativeRoll(event, this.mode, this.actors); + if (initRollHandling && initRollHandling.isHandled) { + if (initRollHandling.checkClose) { + this._checkClose(); + } + return; + } + + if (this.data.initiative) { + for (let actor of this.actors) { + actor.rollInitiative(); + } + event.currentTarget.disabled = true; + this._checkClose(); + } else { + const initiative = CONFIG.Combat.initiative.formula || game.system.data.initiative; + this._makeDiceRoll(event, initiative, game.i18n.localize("Ceus.InitiativeRollMessage")); + } + } + + _onDeathSave(event) { + event.preventDefault(); + + var deathSaveHandling = Ceus.currentRollProvider.handleDeathSave(this.actors, event); + if (deathSaveHandling && deathSaveHandling.isHandled) { + if (deathSaveHandling.checkClose) { + this._checkClose(); + } + return; + } + this._makeDiceRoll(event, "1d20", game.i18n.localize("Ceus.DeathSaveRollMessage")); + } + + _onPerception(event) { + event.preventDefault(); + this._makeDiceRoll(event, `1d20 + @attributes.perception.totalModifier`, game.i18n.localize("Ceus.PerceptionRollMessage")); + } + + _onRollTable(event) { + event.preventDefault(); + const table = event.currentTarget.dataset.table; + this._drawTable(event, table); + } + + _onToggleFailAbilityRoll(event) { + event.preventDefault(); + if (event.currentTarget.classList.contains('disabled-button')) return; + + const failButton = document.querySelector(`.ceus-ability-check-fail[data-ability *= '${event?.currentTarget?.dataset?.ability}']`); + if (failButton) failButton.disabled = !failButton.disabled; + + const normalButton = document.querySelector(`.ceus-ability-check[data-ability *= '${event?.currentTarget?.dataset?.ability}']`); + if (normalButton) normalButton.disabled = !normalButton.disabled; + } + + _onToggleFailSaveRoll(event) { + event.preventDefault(); + if (event.currentTarget.classList.contains('disabled-button')) return; + + const failButton = document.querySelector(`.ceus-ability-save-fail[data-ability *= '${event?.currentTarget?.dataset?.ability}']`); + if (failButton) failButton.disabled = !failButton.disabled; + + const normalButton = document.querySelector(`.ceus-ability-save[data-ability *= '${event?.currentTarget?.dataset?.ability}']`); + if (normalButton) normalButton.disabled = !normalButton.disabled; + } + + _onToggleFailSkillRoll(event) { + event.preventDefault(); + if (event.currentTarget.classList.contains('disabled-button')) return; + + const failButton = document.querySelector(`.ceus-skill-check-fail[data-skill *= '${event?.currentTarget?.dataset?.skill}']`); + if (failButton) failButton.disabled = !failButton.disabled; + + const normalButton = document.querySelector(`.ceus-skill-check[data-ability *= '${event?.currentTarget?.dataset?.ability}']`); + if (normalButton) normalButton.disabled = !normalButton.disabled; + } +} + +console.log("Ceus | roller.js loaded"); \ No newline at end of file diff --git a/src/roller.js b/src/roller.js index a1a9236..5c11266 100644 --- a/src/roller.js +++ b/src/roller.js @@ -1,115 +1,6 @@ import { Ceus } from "./ceus.js"; -export class CeusRoller extends Application { - - constructor(actors, data) { - super(); - this.actors = actors; - this.data = data; - this.abilities = data.abilities; - this.saves = data.saves; - this.skills = data.skills; - this.advantage = data.advantage; - this.mode = data.mode; - this.message = data.message; - this.tables = data.tables; - this.chooseOne = data.chooseOne ?? false; - this.dc = data.dc; - - if (data.title) { - this.options.title = data.title; - } - - this.hasMidi = game.modules.get("midi-qol")?.active; - this.midiUseNewRoller = isNewerVersion(game.modules.get("midi-qol")?.version, "10.0.26"); - - Handlebars.registerHelper('canFailAbilityChecks', function (name, ability) { - if (Ceus.currentRollProvider.canFailChecks()) { - return `
` + - `` + - `
` + - `${Ceus.d20Svg}` + - `
` + - `
`; - } else { - return ''; - } - }); - - Handlebars.registerHelper('canFailSaveChecks', function (name, ability) { - if (Ceus.currentRollProvider.canFailChecks()) { - return `
` + - `` + - `
` + - `${Ceus.d20Svg}` + - `
` + - `
`; - } else { - return ''; - } - }); - - Handlebars.registerHelper('canFailSkillChecks', function (name, skill) { - if (Ceus.currentRollProvider.canFailChecks()) { - return `
` + - `` + - `
` + - `${Ceus.d20Svg}` + - `
` + - `
`; - } else { - return ''; - } - }); - } - - static get defaultOptions() { - const options = super.defaultOptions; - options.title = game.i18n.localize("Ceus.Title"); - options.template = "modules/ceus/templates/roller.html"; - options.popOut = true; - options.width = 400; - options.height = "auto"; - options.classes = ["ceus", "ceus-roller"]; - if (game.settings.get('ceus', 'enableParchmentTheme')) { - options.classes.push('ceus-parchment'); - } - return options; - } - - static requestAbilityChecks(actor, abilities, options={}) { - if (!actor || !abilities) return; - if (typeof(abilities) === "string") abilities = [abilities]; - const data = mergeObject(options, { - abilities: [], - saves: [], - skills: [] - }, {inplace: false}); - data.abilities = abilities; - new CeusRoller([actor], data).render(true); - } - static requestSkillChecks(actor, skills, options={}) { - if (!actor || !skills) return; - if (typeof(skills) === "string") skills = [skills]; - const data = mergeObject(options, { - abilities: [], - saves: [], - skills: [] - }, {inplace: false}); - data.skills = skills; - new CeusRoller([actor], data).render(true); - } - static requestSavingThrows(actor, saves, options={}) { - if (!actor || !saves) return; - if (typeof(saves) === "string") saves = [saves]; - const data = mergeObject(options, { - abilities: [], - saves: [], - skills: [] - }, {inplace: false}); - data.saves = saves; - new CeusRoller([actor], data).render(true); - } +export class CeusRoller { static rollTypes() { return { ABILITY: "ability", @@ -122,463 +13,4 @@ export class CeusRoller extends Application { CUSTOM: "custom" }; } - - async getData() { - let note = "" - if (this.advantage == 1) - note = game.i18n.localize("Ceus.AdvantageNote"); - else if (this.advantage == -1) - note = game.i18n.localize("Ceus.DisadvantageNote"); - - let abilities = {} - let saves = {} - let skills = {} - this.abilities.forEach(a => abilities[a] = Ceus.currentRollProvider.abilities()[a]) - this.saves.forEach(a => saves[a] = Ceus.currentRollProvider.saves()[a]) - this.skills - .sort((a, b) => { - const skillA = (Ceus.currentRollProvider.skills()[a]?.label) ? Ceus.currentRollProvider.skills()[a].label : Ceus.currentRollProvider.skills()[a]; - const skillB = (Ceus.currentRollProvider.skills()[b]?.label) ? Ceus.currentRollProvider.skills()[b].label : Ceus.currentRollProvider.skills()[b]; - game.i18n.localize(skillA).localeCompare(skillB) - }) - .forEach(s => { - const skill = (Ceus.currentRollProvider.skills()[s]?.label) ? Ceus.currentRollProvider.skills()[s].label : Ceus.currentRollProvider.skills()[s]; - skills[s] = skill; - }); - - const data = { - actors: this.actors, - abilities: abilities, - saves: saves, - skills: skills, - note: note, - message: this.message, - customFormula: this.data.formula || false, - deathsave: this.data.deathsave, - initiative: this.data.initiative, - perception: this.data.perception, - tables: this.tables, - chooseOne: this.chooseOne, - }; - - return data; - } - - activateListeners(html) { - super.activateListeners(html); - this.element.find(".ceus-ability-check").click(this._onAbilityCheck.bind(this)) - this.element.find(".ceus-ability-save").click(this._onAbilitySave.bind(this)) - this.element.find(".ceus-skill-check").click(this._onSkillCheck.bind(this)) - this.element.find(".ceus-custom-formula").click(this._onCustomFormula.bind(this)) - this.element.find(".ceus-roll-table").click(this._onRollTable.bind(this)); - var specialRolls = Ceus.currentRollProvider.specialRolls(); - if(specialRolls['initiative']) { - this.element.find(".ceus-initiative").click(this._onInitiative.bind(this)) - } - if(specialRolls['deathsave']) { - this.element.find(".ceus-death-save").click(this._onDeathSave.bind(this)) - } - if(specialRolls['perception']) { - this.element.find(".ceus-perception").click(this._onPerception.bind(this)) - } - - this.element.find(".enable-ceus-ability-check-fail").click(this._onToggleFailAbilityRoll.bind(this)); - this.element.find(".ceus-ability-check-fail").click(this._onFailAbilityCheck.bind(this)); - - this.element.find(".enable-ceus-ability-save-fail").click(this._onToggleFailSaveRoll.bind(this)); - this.element.find(".ceus-ability-save-fail").click(this._onFailAbilitySave.bind(this)); - - this.element.find(".enable-ceus-skill-check-fail").click(this._onToggleFailSkillRoll.bind(this)); - this.element.find(".ceus-skill-check-fail").click(this._onFailSkillCheck.bind(this)); - } - - _checkClose() { - if (this.element.find("button").filter((i, e) => !e.disabled).length === 0 || this.chooseOne) { - this.close(); - } - } - - _disableButtons(event) { - event.currentTarget.disabled = true; - - if (Ceus.canFailChecks) { - const buttonSelector = `${event.currentTarget.className}`; - let oppositeSelector = ""; - let dataSelector = ""; - - if ( - event.currentTarget.className.indexOf('ability-check') > 0 || - event.currentTarget.className.indexOf('ability-save') > 0 - ) { - dataSelector = `[data-ability *= '${event?.currentTarget?.dataset?.ability}']`; - } else { - dataSelector = `[data-skill *= '${event?.currentTarget?.dataset?.skill}']`; - } - - if (event.currentTarget.className.indexOf('fail') > 0) { - oppositeSelector = event.currentTarget.className.substring(0, event.currentTarget.className.indexOf('fail') - 1); - } else { - oppositeSelector = `${event.currentTarget.className}-fail`; - } - - const enableButton = document.querySelector(`.enable-${buttonSelector}${dataSelector}`); - if (enableButton) { - enableButton.disabled = true; - enableButton.classList.add('disabled-button'); - } - - const oppositeButton = document.querySelector(`.${oppositeSelector}${dataSelector}`); - if (oppositeButton) oppositeButton.disabled = true; - } - } - - _getRollOptions(event, failRoll) { - let options; - switch(this.advantage) { - case -1: - options = {... Ceus.currentRollProvider.disadvantageRollEvent() }; - break; - case 0: - options = {... Ceus.currentRollProvider.normalRollEvent() }; - break; - case 1: - options = {... Ceus.currentRollProvider.advantageRollEvent() }; - break; - case 2: - options = { event: event }; - break; - } - - if (failRoll) { - options["parts"] = [-100]; - } - - return options; - } - - async _makeRoll(event, rollMethod, rolledType, failRoll, ...args) { - let options = this._getRollOptions(event, failRoll); - - // save the current roll mode to reset it after this roll - const rollMode = game.settings.get("core", "rollMode"); - game.settings.set("core", "rollMode", this.mode || CONST.DICE_ROLL_MODES); - - for (let actor of this.actors) { - Hooks.once("preCreateChatMessage", this._tagMessage.bind(this)); - - if (Ceus.currentRollProvider.handleCustomRoll(actor, event, rollMethod, rolledType, failRoll, this.dc, args)) { - continue; - } - - await actor[rollMethod].call(actor, ...args, options); - } - - game.settings.set("core", "rollMode", rollMode); - - this._disableButtons(event); - this._checkClose(); - } - - _makePF2EInitiativeRoll(event) { - // save the current roll mode to reset it after this roll - const rollMode = game.settings.get("core", "rollMode"); - game.settings.set("core", "rollMode", this.mode || CONST.DICE_ROLL_MODES); - - for (let actor of this.actors) { - const initiative = actor.data.data.attributes.initiative; - const rollNames = ['all', 'initiative']; - if (initiative.ability === 'perception') { - rollNames.push('wis-based'); - rollNames.push('perception'); - } else { - const skill = actor.data.data.skills[initiative.ability]; - rollNames.push(`${skill.ability}-based`); - rollNames.push(skill.name); - } - const options = actor.getRollOptions(rollNames); - initiative.roll({ event, options }); - } - - game.settings.set("core", "rollMode", rollMode); - - event.currentTarget.disabled = true; - this._checkClose(); - } - - _tagMessage(candidate, data, options) { - candidate.updateSource({"flags.ceus": {"message": this.data.message, "data": this.data.attach, "blind": candidate.blind}}); - } - - async _makeDiceRoll(event, formula, defaultMessage = null) { - if (formula.startsWith("1d20")) { - if (this.advantage === 1) - formula = formula.replace("1d20", "2d20kh1") - else if (this.advantage === -1) - formula = formula.replace("1d20", "2d20kl1") - } - - const messageFlag = {"message": this.data.message, "data": this.data.attach}; - - const rollMessages = []; - const rollMessagePromises = this.actors.map(async (actor) => { - const speaker = ChatMessage.getSpeaker({actor: actor}); - - const rollData = actor.getRollData(); - const roll = new Roll(formula, rollData); - const rollMessageData = await roll.toMessage( - {"flags.ceus": messageFlag}, - {rollMode: this.mode, create: false}, - ); - - rollMessages.push( - mergeObject( - rollMessageData, - { - speaker: { - alias: speaker.alias, - scene: speaker.scene, - token: speaker.token, - actor: speaker.actor, - }, - flavor: this.message || defaultMessage, - rollMode: this.mode, - }, - ), - ); - }) - - await Promise.allSettled(rollMessagePromises); - await ChatMessage.create(rollMessages, {rollMode: this.mode}); - - event.currentTarget.disabled = true; - this._checkClose(); - } - - _drawTable(event, table) { - const icons = { - Actor: 'fas fa-user', - Item: 'fas fa-suitcase', - Scene: 'fas fa-map', - JournalEntry: 'fas fa-book-open', - Macro: 'fas fa-terminal', - Playlist: '', - Compendium: 'fas fa-atlas', - } - - let chatMessages = []; - let count = 0; - const rollTable = game.tables.getName(table); - - if (rollTable) { - for (let actor of this.actors) { - rollTable.draw({ displayChat: false }).then((res) => { - count++; - const rollResults = res.results; - - const nr = rollResults.length > 1 ? `${rollResults.length} results` : "a result"; - let content = ""; - - for (const rollResult of rollResults) { - const result = rollResult; - - if (!result.documentCollection) { - content += `

${result.text}

`; - } else if (['Actor', 'Item', 'Scene', 'JournalEntry', 'Macro'].includes(result.documentCollection)) { - content += `

- ${result.text}

`; - } else if (result.documentCollection === 'Playlist') { - content += `

@${result.documentCollection}[${result.documentId}]{${result.text}}

`; - } else if (result.documentCollection) { // if not specific collection, then is compendium - content += `

- ${result.text}

`; - } - } - let chatData = { - user: game.user.id, - speaker: ChatMessage.getSpeaker({actor}), - flavor: `Draws ${nr} from the ${table} table.`, - content: content, - type: CONST.CHAT_MESSAGE_TYPES.OTHER, - }; - - if ( ["gmroll", "blindroll"].includes(this.mode) ) { - chatData.whisper = ChatMessage.getWhisperRecipients("GM"); - } - if ( this.mode === "selfroll" ) chatData.whisper = [game.user.id]; - if ( this.mode === "blindroll" ) chatData.blind = true; - - setProperty(chatData, "flags.ceus", {"message": this.data.message, "data": this.data.attach, "blind": chatData.blind}); - - chatMessages.push(chatData); - - if (count === this.actors.length) { - ChatMessage.create(chatMessages, {}); - - event.currentTarget.disabled = true; - this._checkClose(); - } - }); - } - } - } - - _onAbilityCheck(event) { - event.preventDefault(); - const ability = event.currentTarget.dataset.ability; - - // until patching has been removed - if (!this.hasMidi || this.midiUseNewRoller) { - this._makeRoll(event, Ceus.currentRollProvider.abilityRollMethod(), CeusRoller.rollTypes().ABILITY, false, ability); - } else { - this._makeRoll(event, Ceus.currentRollProvider.abilityRollMethod(), CeusRoller.rollTypes().ABILITY, ability); - } - } - - _onFailAbilityCheck(event) { - event.preventDefault(); - const ability = event.currentTarget.dataset.ability; - - // until patching has been removed - if (!this.hasMidi || this.midiUseNewRoller) { - this._makeRoll(event, Ceus.currentRollProvider.abilityRollMethod(), CeusRoller.rollTypes().ABILITY, true, ability); - } else { - this._makeRoll(event, Ceus.currentRollProvider.abilityRollMethod(), CeusRoller.rollTypes().ABILITY, ability); - } - } - - _onAbilitySave(event) { - event.preventDefault(); - const saves = event.currentTarget.dataset.ability; - - // until patching has been removed - if (!this.hasMidi || this.midiUseNewRoller) { - this._makeRoll(event, Ceus.currentRollProvider.saveRollMethod(), CeusRoller.rollTypes().SAVE, false, saves); - } else { - this._makeRoll(event, Ceus.currentRollProvider.saveRollMethod(), CeusRoller.rollTypes().SAVE, saves); - } - } - - _onFailAbilitySave(event) { - event.preventDefault(); - const saves = event.currentTarget.dataset.ability; - - // until patching has been removed - if (!this.hasMidi || this.midiUseNewRoller) { - this._makeRoll(event, Ceus.currentRollProvider.saveRollMethod(), CeusRoller.rollTypes().SAVE, true, saves); - } else { - this._makeRoll(event, Ceus.currentRollProvider.saveRollMethod(), CeusRoller.rollTypes().SAVE, saves); - } - } - - _onSkillCheck(event) { - event.preventDefault(); - const skill = event.currentTarget.dataset.skill; - - // until patching has been removed - if (!this.hasMidi || this.midiUseNewRoller) { - this._makeRoll(event, Ceus.currentRollProvider.skillRollMethod(), CeusRoller.rollTypes().SKILL, false, skill); - } else { - this._makeRoll(event, Ceus.currentRollProvider.skillRollMethod(), CeusRoller.rollTypes().SKILL, skill); - } - } - - _onFailSkillCheck(event) { - event.preventDefault(); - const skill = event.currentTarget.dataset.skill; - - // until patching has been removed - if (!this.hasMidi || this.midiUseNewRoller) { - this._makeRoll(event, Ceus.currentRollProvider.skillRollMethod(), CeusRoller.rollTypes().SKILL, true, skill); - } else { - this._makeRoll(event, Ceus.currentRollProvider.skillRollMethod(), CeusRoller.rollTypes().SKILL, skill); - } - } - - async _onCustomFormula(event) { - event.preventDefault(); - await this._makeDiceRoll(event, this.data.formula); - } - - _onInitiative(event) { - event.preventDefault(); - - //Custom Event Handling for Initiative Rolls (if needed) - var initRollHandling = Ceus.currentRollProvider.handleInitiativeRoll(event, this.mode, this.actors); - if (initRollHandling && initRollHandling.isHandled) { - if (initRollHandling.checkClose) { - this._checkClose(); - } - return; - } - - if (this.data.initiative) { - for (let actor of this.actors) { - actor.rollInitiative(); - } - event.currentTarget.disabled = true; - this._checkClose(); - } else { - const initiative = CONFIG.Combat.initiative.formula || game.system.data.initiative; - this._makeDiceRoll(event, initiative, game.i18n.localize("Ceus.InitiativeRollMessage")); - } - } - - _onDeathSave(event) { - event.preventDefault(); - - var deathSaveHandling = Ceus.currentRollProvider.handleDeathSave(this.actors, event); - if (deathSaveHandling && deathSaveHandling.isHandled) { - if (deathSaveHandling.checkClose) { - this._checkClose(); - } - return; - } - this._makeDiceRoll(event, "1d20", game.i18n.localize("Ceus.DeathSaveRollMessage")); - } - - _onPerception(event) { - event.preventDefault(); - this._makeDiceRoll(event, `1d20 + @attributes.perception.totalModifier`, game.i18n.localize("Ceus.PerceptionRollMessage")); - } - - _onRollTable(event) { - event.preventDefault(); - const table = event.currentTarget.dataset.table; - this._drawTable(event, table); - } - - _onToggleFailAbilityRoll(event) { - event.preventDefault(); - if (event.currentTarget.classList.contains('disabled-button')) return; - - const failButton = document.querySelector(`.ceus-ability-check-fail[data-ability *= '${event?.currentTarget?.dataset?.ability}']`); - if (failButton) failButton.disabled = !failButton.disabled; - - const normalButton = document.querySelector(`.ceus-ability-check[data-ability *= '${event?.currentTarget?.dataset?.ability}']`); - if (normalButton) normalButton.disabled = !normalButton.disabled; - } - - _onToggleFailSaveRoll(event) { - event.preventDefault(); - if (event.currentTarget.classList.contains('disabled-button')) return; - - const failButton = document.querySelector(`.ceus-ability-save-fail[data-ability *= '${event?.currentTarget?.dataset?.ability}']`); - if (failButton) failButton.disabled = !failButton.disabled; - - const normalButton = document.querySelector(`.ceus-ability-save[data-ability *= '${event?.currentTarget?.dataset?.ability}']`); - if (normalButton) normalButton.disabled = !normalButton.disabled; - } - - _onToggleFailSkillRoll(event) { - event.preventDefault(); - if (event.currentTarget.classList.contains('disabled-button')) return; - - const failButton = document.querySelector(`.ceus-skill-check-fail[data-skill *= '${event?.currentTarget?.dataset?.skill}']`); - if (failButton) failButton.disabled = !failButton.disabled; - - const normalButton = document.querySelector(`.ceus-skill-check[data-ability *= '${event?.currentTarget?.dataset?.ability}']`); - if (normalButton) normalButton.disabled = !normalButton.disabled; - } } - -console.log("Ceus | roller.js loaded"); \ No newline at end of file