ENH: Attack rolls !
parent
e1319b6215
commit
7106f138d9
|
@ -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!"
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
Loading…
Reference in New Issue