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}}
+
{{#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