ENH: Attack rolls !

master
U~man 2020-07-05 22:13:27 +02:00
parent e1319b6215
commit 7106f138d9
6 changed files with 219 additions and 104 deletions

View File

@ -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": "<b>Hits AC {result}!</b> ({bonus})",
"OSE.messages.AttackFailure": "<b>Attack fails</b> ({bonus})",
"OSE.messages.InflictsDamage": "Inflicts damage!"
}

View File

@ -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;
}
}
}

View File

@ -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 = `<div class='roll-result'><b>Hits AC ${Math.clamped(
thac - roll.total,
-3,
9
)}</b> (${thac})</div>`;
}
} 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: '<i class="fas fa-dice-d20"></i>',
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: '<i class="fas fa-times"></i>',
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;

View File

@ -156,12 +156,12 @@
{{#if config.ascendingAC}}
<div class="attribute-value"
title="{{localize 'OSE.AB'}}({{data.thac0.bba}}) + {{localize 'OSE.scores.str.long'}}({{data.scores.str.mod}}) + {{localize 'OSE.Modifier'}}({{data.thac0.mod.melee}})">
{{add data.thac0.mod.melee (add data.scores.str.mod data.thac0.bba)}}
{{data.thac0.melee}}
</div>
{{else}}
<div class="attribute-value"
title="{{localize 'OSE.Thac0'}}({{data.thac0.value}}) - {{localize 'OSE.scores.str.long'}}({{data.scores.str.mod}}) - {{localize 'OSE.Modifier'}}({{data.thac0.mod.melee}})">
{{subtract data.thac0.mod.melee (subtract data.scores.str.mod data.thac0.value)}}
{{data.thac0.melee}}
</div>
{{/if}}
</div>
@ -196,12 +196,12 @@
{{#if config.ascendingAC}}
<div class="attribute-value"
title="{{localize 'OSE.AB'}}({{data.thac0.bba}}) + {{localize 'OSE.scores.dex.long'}}({{data.scores.dex.mod}}) + {{localize 'OSE.Modifier'}}({{data.thac0.mod.missile}})">
{{add data.thac0.mod.missile (add data.scores.dex.mod data.thac0.bba)}}
{{data.thac0.missile}}
</div>
{{else}}
<div class="attribute-value"
title="{{localize 'OSE.Thac0'}}({{data.thac0.value}}) - {{localize 'OSE.scores.dex.long'}}({{data.scores.dex.mod}}) - {{localize 'OSE.Modifier'}}({{data.thac0.mod.missile}})">
{{subtract data.thac0.mod.missile (subtract data.scores.dex.mod data.thac0.value)}}
{{data.thac0.missile}}
</div>
{{/if}}
</div>

View File

@ -1,9 +1,15 @@
<section class="ose chat-message">
<div class="ose chat-block">
<h2 class="chat-title">{{title}}</h2>
{{#if result.details}}<div class="chat-details">{{{result.details}}}</div>{{/if}}
{{#if result.isFailure}}<div class='roll-result roll-fail'><b>{{localize 'OSE.Failure'}}</b> ({{result.target}})</div>{{/if}}
{{#if result.isSuccess}}<div class='roll-result roll-success'><b>{{localize 'OSE.Success'}}</b> ({{result.target}})</div>{{/if}}
<div class="chat-details">
<div class="roll-result">{{{result.details}}}</div>
</div>
{{#if rollOSE}}<div>{{{rollOSE}}}</div>{{/if}}
{{#if result.isSuccess}}
<div class="chat-details">
<div class="roll-result"><b>{{localize 'OSE.messages.InflictsDamage'}}</b></div>
</div>
<div>{{{rollDamage}}}</div>
{{/if}}
</div>
</section>

View File

@ -0,0 +1,11 @@
<section class="ose chat-message">
<div class="ose chat-block">
<h2 class="chat-title">{{title}}</h2>
{{#if result.details}}<div class="chat-details">{{{result.details}}}</div>{{/if}}
{{#if result.isFailure}}<div class='roll-result roll-fail'><b>{{localize 'OSE.Failure'}}</b> ({{result.target}})
</div>{{/if}}
{{#if result.isSuccess}}<div class='roll-result roll-success'><b>{{localize 'OSE.Success'}}</b>
({{result.target}})</div>{{/if}}
{{#if rollOSE}}<div>{{{rollOSE}}}</div>{{/if}}
</div>
</section>