diff --git a/modules/crucible-actor.js b/modules/crucible-actor.js index 9fd51df..2cb578d 100644 --- a/modules/crucible-actor.js +++ b/modules/crucible-actor.js @@ -556,7 +556,16 @@ export class CrucibleActor extends Actor { rollData.forceRollDisadvantage = this.isForcedRollDisadvantage() rollData.noAdvantage = this.isNoAdvantage() if ( rollData.defenderTokenId) { - let defender = game.canvas.tokens.get(rollData.defenderTokenId).actor + let defenderToken = game.canvas.tokens.get(rollData.defenderTokenId) + let defender = defenderToken.actor + + // Distance management + if ( this.token) { + const ray = new Ray(this.token.object.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.") + } if (defender ) { rollData.forceAdvantage = defender.isAttackerAdvantage() rollData.advantageFromTarget = true @@ -625,6 +634,10 @@ export class CrucibleActor extends Actor { if ( !rollData.forceDisadvantage) { // This is an attack, check if disadvantaged rollData.forceDisadvantage = this.isAttackDisadvantage() } + if (rollData.weapon.system.isranged && rollData.tokensDistance > CrucibleUtility.getWeaponMaxRange(rollData.weapon) ) { + ui.notifications.warn(`Your target is out of range of your weapon (max: ${CrucibleUtility.getWeaponMaxRange(rollData.weapon)} - current : ${rollData.tokensDistance})` ) + return + } this.startRoll(rollData) } else { ui.notifications.warn("Unable to find the relevant skill for weapon " + weapon.name) @@ -663,6 +676,23 @@ export class CrucibleActor extends Actor { } } + /* -------------------------------------------- */ + rollDefenseRanged(attackRollData) { + let rollData = this.getCommonRollData() + rollData.defenderTokenId = undefined // Cleanup + rollData.mode = "rangeddefense" + rollData.attackRollData = duplicate(attackRollData) + rollData.sizeDice = CrucibleUtility.getSizeDice( this.system.biodata.size ) + rollData.effectiveRange = CrucibleUtility.getWeaponRange(attackRollData.weapon) + rollData.tokensDistance = attackRollData.tokensDistance // QoL copy + rollData.distanceBonusDice = Math.max(0, Math.floor((rollData.tokensDistance - rollData.effectiveRange) + 0.5)) + rollData.hasCover = "none" + rollData.situational = "none" + rollData.useshield = false + rollData.shield = this.getEquippedShield() + this.startRoll(rollData) + } + /* -------------------------------------------- */ rollShieldDie() { let shield = this.getEquippedShield() diff --git a/modules/crucible-roll-dialog.js b/modules/crucible-roll-dialog.js index 4919812..34025a6 100644 --- a/modules/crucible-roll-dialog.js +++ b/modules/crucible-roll-dialog.js @@ -70,6 +70,12 @@ export class CrucibleRollDialog extends Dialog { html.find('#useshield').change((event) => { this.rollData.useshield = event.currentTarget.checked }) + html.find('#hasCover').change((event) => { + this.rollData.hasCover = event.currentTarget.value + }) + html.find('#situational').change((event) => { + this.rollData.situational = event.currentTarget.value + }) } } \ No newline at end of file diff --git a/modules/crucible-utility.js b/modules/crucible-utility.js index 9a51535..8b70f62 100644 --- a/modules/crucible-utility.js +++ b/modules/crucible-utility.js @@ -10,6 +10,7 @@ const __color2RollTable = { blue: "Blue Armor Die", black: "Black Armor Die", green: "Green Armor Die", purple: "Purple Armor Die", white: "White Armor Die", red: "Red Armor Die", blackgreen: "Black & Green Armor Dice" } +const __size2Dice = [ { nb: 0, dice: "d0" }, { nb: 5, dice: "d8" }, { nb: 3, dice: "d8" }, { nb: 2, dice: "d8" }, { nb: 1, dice: "d8" }, { nb: 1, dice: "d6" }, { nb: 1, noAddFirst: true, dice: "d6" }] /* -------------------------------------------- */ export class CrucibleUtility { @@ -56,7 +57,7 @@ export class CrucibleUtility { if (typeof text !== 'string') return text return text.charAt(0).toUpperCase() + text.slice(1) } - + /*-------------------------------------------- */ static getSkills() { return duplicate(this.skills) @@ -150,6 +151,20 @@ export class CrucibleUtility { } return false } + static getWeaponRange(weapon) { + if (weapon && weapon.system.isranged) { + let rangeValue = weapon.system.range.replace(/[^0-9]/g, '') + return Number(rangeValue) + } + return false + } + static getWeaponMaxRange(weapon) { + if (weapon && weapon.system.isranged) { + let rangeValue = weapon.system.maxrange.replace(/[^0-9]/g, '') + return Number(rangeValue) + } + return false + } /* -------------------------------------------- */ static async getRollTableFromDiceColor(diceColor, displayChat = true) { @@ -163,6 +178,10 @@ export class CrucibleUtility { return draw.results.length > 0 ? draw.results[0] : undefined } } + /* -------------------------------------------- */ + static getSizeDice(sizeValue) { + return __size2Dice[sizeValue] + } /* -------------------------------------------- */ static async getCritical(level, weapon) { @@ -191,6 +210,15 @@ export class CrucibleUtility { actor.rollDefenseMelee(rollData) } }) + html.on("click", '.roll-defense-ranged', event => { + let rollId = $(event.currentTarget).data("roll-id") + let rollData = CrucibleUtility.getRollData(rollId) + let defender = game.canvas.tokens.get(rollData.defenderTokenId).actor + if (defender && (game.user.isGM || defender.isOwner)) { + defender.rollDefenseRanged(rollData) + } + }) + } /* -------------------------------------------- */ @@ -290,12 +318,12 @@ export class CrucibleUtility { /* -------------------------------------------- */ static async displayDefenseMessage(rollData) { - if (rollData.mode == "weapon" && rollData.defenderTokenId) { + if (rollData.mode == "weapon" && rollData.defenderTokenId) { let defender = game.canvas.tokens.get(rollData.defenderTokenId).actor if (game.user.isGM || (game.user.character && game.user.character.id == defender.id)) { rollData.defender = defender rollData.defenderWeapons = defender.getEquippedWeapons() - rollData.isRollTarget = rollData.weapon?.system.isranged + rollData.isRangedAttack = rollData.weapon?.system.isranged this.createChatWithRollMode(defender.name, { name: defender.name, alias: defender.name, @@ -373,7 +401,7 @@ export class CrucibleUtility { } if (result.critical_1 || result.critical_2) { let isDeadly = CrucibleUtility.isWeaponDeadly(rollData.attackRollData.weapon) - result.critical = await this.getCritical((result.critical_1) ? "I" : "II", rollData.attackRollData.weapon ) + result.critical = await this.getCritical((result.critical_1) ? "I" : "II", rollData.attackRollData.weapon) result.criticalText = result.critical.text } this.createChatWithRollMode(rollData.alias, { @@ -480,12 +508,26 @@ export class CrucibleUtility { skill.system.skilldice = __skillLevel2Dice[skill.system.level] } + /* -------------------------------------------- */ + static getDiceFromCover(cover) { + if (cover == "cover50") return 1 + return 0 + } + /* -------------------------------------------- */ + static getDiceFromSituational(cover) { + if (cover == "prone") return 1 + if (cover == "dodge") return 1 + if (cover == "moving") return 1 + if (cover == "engaged") return 1 + return 0 + } + /* -------------------------------------------- */ static async rollCrucible(rollData) { let actor = game.actors.get(rollData.actorId) - // ability/save => 0 + // ability/save/size => 0 let diceFormula let startFormula = "0d6cs>=5" if (rollData.ability) { @@ -494,6 +536,10 @@ export class CrucibleUtility { if (rollData.save) { startFormula = String(rollData.save.value) + "d6cs>=5" } + if (rollData.sizeDice) { + let nb = rollData.sizeDice.nb + rollData.distanceBonusDice + this.getDiceFromCover(rollData.hasCover) + this.getDiceFromSituational(rollData.situational) + startFormula = String(nb) + String(rollData.sizeDice.dice) + "cs>=5" + } diceFormula = startFormula // skill => 2 @@ -582,16 +628,16 @@ export class CrucibleUtility { rollData.roll = myRoll rollData.nbSuccess = myRoll.total - if ( rollData.rollAdvantage == "none" && rollData.forceRollAdvantage) { + if (rollData.rollAdvantage == "none" && rollData.forceRollAdvantage) { rollData.rollAdvantage = "roll-advantage" - } - if ( rollData.rollAdvantage == "none" && rollData.forceRollDisadvantage) { + } + if (rollData.rollAdvantage == "none" && rollData.forceRollDisadvantage) { rollData.rollAdvantage = "roll-disadvantage" - } - if (rollData.rollAdvantage != "none" ) { + } + if (rollData.rollAdvantage != "none") { rollData.rollOrder = 1 - rollData.rollType = (rollData.rollAdvantage == "roll-advantage") ? "Advantage": "Disadvantage" + rollData.rollType = (rollData.rollAdvantage == "roll-advantage") ? "Advantage" : "Disadvantage" this.createChatWithRollMode(rollData.alias, { content: await renderTemplate(`systems/fvtt-crucible-rpg/templates/chat-generic-result.html`, rollData) }) @@ -599,7 +645,7 @@ export class CrucibleUtility { rollData.rollOrder = 2 let myRoll2 = new Roll(diceFormula).roll({ async: false }) await this.showDiceSoNice(myRoll2, game.settings.get("core", "rollMode")) - + rollData.roll = myRoll2 // Tmp switch to display the proper results rollData.nbSuccess = myRoll2.total this.createChatWithRollMode(rollData.alias, { diff --git a/system.json b/system.json index 868b748..ea9a66e 100644 --- a/system.json +++ b/system.json @@ -199,7 +199,7 @@ "styles": [ "styles/simple.css" ], - "version": "10.0.3", + "version": "10.0.4", "compatibility": { "minimum": "10", "verified": "10.278", @@ -207,7 +207,7 @@ }, "title": "Crucible RPG", "manifest": "https://www.uberwald.me/gitea/public/fvtt-crucible-rpg/raw/master/system.json", - "download": "https://www.uberwald.me/gitea/public/fvtt-crucible-rpg/archive/fvtt-crucible-rpg-v10.0.3.zip", + "download": "https://www.uberwald.me/gitea/public/fvtt-crucible-rpg/archive/fvtt-crucible-rpg-v10.0.4.zip", "url": "https://www.uberwald.me/gitea/public/fvtt-crucible-rpg", "background": "images/ui/crucible_welcome_page.webp", "id": "fvtt-crucible-rpg" diff --git a/templates/chat-generic-result.html b/templates/chat-generic-result.html index c209cf9..e6633de 100644 --- a/templates/chat-generic-result.html +++ b/templates/chat-generic-result.html @@ -36,6 +36,14 @@ {{/if}} + {{#if sizeDice}} +
  • Size/Range/Cover/Situational dices + ({{#each roll.terms.0.results as |die idx|}} + {{die.result}}  + {{/each}}) +
  • + {{/if}} + {{#if ability}}
  • Ability : {{ability.label}} - {{ability.value}}d6 ({{#each roll.terms.0.results as |die idx|}} diff --git a/templates/chat-request-defense.html b/templates/chat-request-defense.html index 540f09a..f5bda4d 100644 --- a/templates/chat-request-defense.html +++ b/templates/chat-request-defense.html @@ -18,17 +18,16 @@
    - {{#if isRollTarget}} + {{#if isRangedAttack}}
    {{defender.name}} is under Ranged attack. He must roll a Target Roll to defend himself.
    {{else}}
    {{defender.name}} is under Melee attack. He must roll a Defense Roll to defend himself.
    {{/if}}