diff --git a/src/lang/en.json b/src/lang/en.json index 926d824..d3de31d 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -167,5 +167,8 @@ "OSE.exploration.ft.long": "Find Room Trap", "OSE.exploration.ft.short": "Find Trap", - "OSE.messages.getExperience": "{name} gained {value} experience points!" + "OSE.messages.GetExperience": "{name} gained {value} experience points!", + "OSE.messages.AttackSuccess": "Hits AC {result}! ({bonus})", + "OSE.messages.AttackFailure": "Attack fails ({bonus})", + "OSE.messages.InflictsDamage": "Inflicts damage!" } \ No newline at end of file diff --git a/src/module/actor/entity.js b/src/module/actor/entity.js index 7b2d751..775bd2b 100644 --- a/src/module/actor/entity.js +++ b/src/module/actor/entity.js @@ -11,6 +11,7 @@ export class OseActor extends Actor { // Compute modifiers from actor scores this.computeModifiers(); + this.computeAttack(); // Determine Initiative if (game.settings.get("ose", "individualInit")) { @@ -37,7 +38,7 @@ export class OseActor extends Actor { }).then(() => { const speaker = ChatMessage.getSpeaker({ actor: this }); ChatMessage.create({ - content: game.i18n.format("OSE.messages.getExperience", { + content: game.i18n.format("OSE.messages.GetExperience", { name: this.name, value: modified, }), @@ -51,7 +52,6 @@ export class OseActor extends Actor { rollHP(options = {}) { let roll = new Roll(this.data.data.hp.hd).roll(); - console.log(roll); return this.update({ data: { hp: { @@ -177,8 +177,8 @@ export class OseActor extends Actor { ...this.data, ...{ rollData: { - type: "Exploration", - stat: expl, + type: "Below", + target: this.data.data.exploration[expl], }, }, }; @@ -236,22 +236,40 @@ export class OseActor extends Actor { } rollAttack(attData, options = {}) { - const rollParts = ["1d20"]; const data = this.data.data; - if (attData.type == "missile") { - rollParts.push( - data.scores.dex.mod.toString(), - data.thac0.mod.missile.toString() - ); - } else if (attData.type == "melee") { - rollParts.push( - data.scores.str.mod.toString(), - data.thac0.mod.melee.toString() - ); + const rollParts = ["1d20"]; + const dmgParts = []; + + if (!attData.dmg || !game.settings.get("ose", "variableWeaponDamage")) { + dmgParts.push("1d6"); + } else { + dmgParts.push(attData.dmg); } - if (game.settings.get("ose", "ascendingAC")) { - rollParts.push(this.data.data.thac0.bba.toString()); + + + let ascending = game.settings.get("ose", "ascendingAC"); + if (ascending) { + rollParts.push(data.thac0.bba.toString()); + if (attData.type == "missile") { + rollParts.push( + data.scores.dex.mod.toString(), + data.thac0.mod.missile.toString() + ); + } else if (attData.type == "melee") { + rollParts.push( + data.scores.str.mod.toString(), + data.thac0.mod.melee.toString() + ); + } + } + + let thac0 = 0; + if (attData.type == "melee") { + dmgParts.push(data.scores.str.mod); + thac0 = data.thac0.melee; + } else if (attData.type == "missile") { + thac0 = data.thac0.missile; } const rollData = { @@ -259,12 +277,15 @@ export class OseActor extends Actor { ...{ rollData: { type: "Attack", - stat: attData.type, - scores: data.scores, + thac0: thac0, + weapon: { + parts: dmgParts, + }, }, }, }; let skip = options.event && options.event.ctrlKey; + // Roll and return return OseDice.Roll({ event: options.event, @@ -274,8 +295,6 @@ export class OseActor extends Actor { speaker: ChatMessage.getSpeaker({ actor: this }), flavor: `${attData.label} - ${game.i18n.localize("OSE.Attack")}`, title: `${attData.label} - ${game.i18n.localize("OSE.Attack")}`, - }).then(() => { - this.rollDamage(attData, {}); }); } @@ -334,4 +353,21 @@ export class OseActor extends Actor { data.scores.dex.init = OseActor._cappedMod(this.data.data.scores.dex.value); data.scores.cha.npc = OseActor._cappedMod(this.data.data.scores.cha.value); } + + computeAttack() { + const data = this.data.data; + let ascending = game.settings.get("ose", "ascendingAC"); + data.thac0.missile = ascending ? data.thac0.bba : data.thac0.value; + data.thac0.melee = ascending ? data.thac0.bba : data.thac0.value; + if (this.data.type != "character") { + return; + } + if (ascending) { + data.thac0.missile += data.scores.dex.mod + data.thac0.mod.missile; + data.thac0.melee += data.scores.str.mod + data.thac0.mod.melee; + } else { + data.thac0.missile -= data.scores.dex.mod - data.thac0.mod.missile; + data.thac0.melee -= data.scores.str.mod - data.thac0.mod.melee; + } + } } diff --git a/src/module/dice.js b/src/module/dice.js index 24851a9..e6d7166 100644 --- a/src/module/dice.js +++ b/src/module/dice.js @@ -3,75 +3,27 @@ export class OseDice { let result = { isSuccess: false, isFailure: false, - target: "", + target: data.rollData.target, }; - // ATTACKS + let die = roll.parts[0].total; - if (data.rollData.type == "Attack") { - if (game.settings.get("ose", "ascendingAC")) { - let bba = data.data.thac0.bba; - if (data.rollData.stat == "melee") { - bba += data.data.thac0.mod.melee + data.rollData.scores.str.mod; - } else if (data.rollData.stat == "missile") { - bba += data.data.thac0.mod.missile + data.rollData.scores.dex.mod; - } - result.target = bba; - if (die == 1) { - result.isFailure = true; - return result; - } - result.isSuccess = true; - } else { - // B/X Historic THAC0 Calculation - let thac = data.data.thac0.value; - if (data.rollData.stat == "melee") { - thac -= data.data.thac0.mod.melee + data.rollData.scores.str.mod; - } else if (data.rollData.stat == "missile") { - thac -= data.data.thac0.mod.missile + data.rollData.scores.dex.mod; - } - result.target = thac; - if (thac - roll.total > 9) { - result.isFailure = true; - return result; - } - result.details = `
Hits AC ${Math.clamped( - thac - roll.total, - -3, - 9 - )} (${thac})
`; - } - } else if (data.rollData.type == "Above") { + if (data.rollData.type == "Above") { // SAVING THROWS - let sv = data.rollData.target; - result.target = sv; - if (roll.total >= sv) { + if (roll.total >= result.target) { result.isSuccess = true; } else { result.isFailure = true; } } else if (data.rollData.type == "Below") { - // Morale - let m = data.rollData.target; - result.target = m; - if (roll.total <= m) { + // MORALE, EXPLORATION + if (roll.total <= result.target) { result.isSuccess = true; } else { result.isFailure = true; } } else if (data.rollData.type == "Check") { - // SCORE CHECKS - let sc = data.rollData.target; - result.target = sc; - if (die == 1 || (roll.total <= sc && die < 20)) { - result.isSuccess = true; - } else { - result.isFailure = true; - } - } else if (data.rollData.type == "Exploration") { - // EXPLORATION CHECKS - let sc = data.data.exploration[data.rollData.stat]; - result.target = sc; - if (roll.total <= sc) { + // SCORE CHECKS (1s and 20s) + if (die == 1 || (roll.total <= result.target && die < 20)) { result.isSuccess = true; } else { result.isFailure = true; @@ -88,7 +40,7 @@ export class OseDice { speaker = null, form = null, } = {}) { - const template = "systems/ose/templates/chat/roll-attack.html"; + const template = "systems/ose/templates/chat/roll-result.html"; let chatData = { user: game.user._id, @@ -142,6 +94,110 @@ export class OseDice { }); } + static digestAttackResult(data, roll) { + let result = { + isSuccess: false, + isFailure: false, + target: "", + }; + result.target = data.rollData.thac0; + if (game.settings.get("ose", "ascendingAC")) { + result.details = game.i18n.format('OSE.messages.AttackSuccess', {result: roll.total, bonus: result.target}); + result.isSuccess = true; + } else { + // B/X Historic THAC0 Calculation + if (result.target - roll.total > 9) { + result.details = game.i18n.format('OSE.messages.AttackFailure', {bonus: result.target}); + return result; + } + result.isSuccess = true; + let value = Math.clamped(result.target - roll.total, -3, 9); + result.details = game.i18n.format('OSE.messages.AttackSuccess', {result: value, bonus: result.target}); + } + return result; + } + + static async sendAttackRoll({ + parts = [], + data = {}, + title = null, + flavor = null, + speaker = null, + form = null, + } = {}) { + const template = "systems/ose/templates/chat/roll-attack.html"; + + let chatData = { + user: game.user._id, + speaker: speaker, + }; + + let templateData = { + title: title, + flavor: flavor, + data: data, + }; + + // Optionally include a situational bonus + if (form !== null) data["bonus"] = form.bonus.value; + if (data["bonus"]) parts.push(data["bonus"]); + + const roll = new Roll(parts.join("+"), data).roll(); + const dmgRoll = new Roll(data.rollData.weapon.parts.join("+"), data).roll(); + + // Convert the roll to a chat message and return the roll + let rollMode = game.settings.get("core", "rollMode"); + rollMode = form ? form.rollMode.value : rollMode; + + templateData.result = OseDice.digestAttackResult(data, roll); + + return new Promise((resolve) => { + roll.render().then((r) => { + templateData.rollOSE = r; + dmgRoll.render().then((dr) => { + templateData.rollDamage = dr; + renderTemplate(template, templateData).then((content) => { + chatData.content = content; + // 2 Step Dice So Nice + if (game.dice3d) { + game.dice3d + .showForRoll( + roll, + game.user, + true, + chatData.whisper, + chatData.blind + ) + .then(() => { + if (templateData.result.isSuccess) { + game.dice3d + .showForRoll( + dmgRoll, + game.user, + true, + chatData.whisper, + chatData.blind + ) + .then(() => { + ChatMessage.create(chatData); + resolve(); + }); + } else { + ChatMessage.create(chatData); + resolve(); + } + }); + } else { + chatData.sound = CONFIG.sounds.dice; + ChatMessage.create(chatData); + resolve(); + } + }); + }); + }); + }); + } + static async Roll({ parts = [], data = {}, @@ -164,37 +220,40 @@ export class OseDice { rollModes: CONFIG.Dice.rollModes, }; + let rollData = { + parts: parts, + data: data, + title: title, + flavor: flavor, + speaker: speaker, + }; + + if (skipDialog) { + return data.rollData.type === "Attack" + ? OseDice.sendAttackRoll(rollData) + : OseDice.sendRoll(rollData); + } + let buttons = { ok: { label: game.i18n.localize("OSE.Roll"), icon: '', callback: (html) => { - roll = OseDice.sendRoll({ - parts: parts, - data: data, - title: title, - flavor: flavor, - speaker: speaker, - form: html[0].children[0], - }); + rolled = true; + rollData.form = html[0].children[0]; + roll = + data.rollData.type === "Attack" + ? OseDice.sendAttackRoll(rollData) + : OseDice.sendRoll(rollData); }, }, cancel: { icon: '', label: game.i18n.localize("OSE.Cancel"), + callback: (html) => {}, }, }; - if (skipDialog) { - return OseDice.sendRoll({ - parts, - data, - title, - flavor, - speaker, - }); - } - const html = await renderTemplate(template, dialogData); let roll; diff --git a/src/templates/actors/partials/character-attributes-tab.html b/src/templates/actors/partials/character-attributes-tab.html index 933bbbc..88064cc 100644 --- a/src/templates/actors/partials/character-attributes-tab.html +++ b/src/templates/actors/partials/character-attributes-tab.html @@ -156,12 +156,12 @@ {{#if config.ascendingAC}}
- {{add data.thac0.mod.melee (add data.scores.str.mod data.thac0.bba)}} + {{data.thac0.melee}}
{{else}}
- {{subtract data.thac0.mod.melee (subtract data.scores.str.mod data.thac0.value)}} + {{data.thac0.melee}}
{{/if}} @@ -196,12 +196,12 @@ {{#if config.ascendingAC}}
- {{add data.thac0.mod.missile (add data.scores.dex.mod data.thac0.bba)}} + {{data.thac0.missile}}
{{else}}
- {{subtract data.thac0.mod.missile (subtract data.scores.dex.mod data.thac0.value)}} + {{data.thac0.missile}}
{{/if}} diff --git a/src/templates/chat/roll-attack.html b/src/templates/chat/roll-attack.html index 96bcd4a..c3eaca3 100644 --- a/src/templates/chat/roll-attack.html +++ b/src/templates/chat/roll-attack.html @@ -1,9 +1,15 @@

{{title}}

- {{#if result.details}}
{{{result.details}}}
{{/if}} - {{#if result.isFailure}}
{{localize 'OSE.Failure'}} ({{result.target}})
{{/if}} - {{#if result.isSuccess}}
{{localize 'OSE.Success'}} ({{result.target}})
{{/if}} +
+
{{{result.details}}}
+
{{#if rollOSE}}
{{{rollOSE}}}
{{/if}} + {{#if result.isSuccess}} +
+
{{localize 'OSE.messages.InflictsDamage'}}
+
+
{{{rollDamage}}}
+ {{/if}}
\ No newline at end of file diff --git a/src/templates/chat/roll-result.html b/src/templates/chat/roll-result.html new file mode 100644 index 0000000..f452bd2 --- /dev/null +++ b/src/templates/chat/roll-result.html @@ -0,0 +1,11 @@ +
+
+

{{title}}

+ {{#if result.details}}
{{{result.details}}}
{{/if}} + {{#if result.isFailure}}
{{localize 'OSE.Failure'}} ({{result.target}}) +
{{/if}} + {{#if result.isSuccess}}
{{localize 'OSE.Success'}} + ({{result.target}})
{{/if}} + {{#if rollOSE}}
{{{rollOSE}}}
{{/if}} +
+
\ No newline at end of file