314 lines
8.6 KiB
JavaScript
314 lines
8.6 KiB
JavaScript
export class OseDice {
|
|
static digestResult(data, roll) {
|
|
let result = {
|
|
isSuccess: false,
|
|
isFailure: false,
|
|
target: data.roll.target,
|
|
total: roll.total,
|
|
};
|
|
|
|
let die = roll.parts[0].total;
|
|
if (data.roll.type == "above") {
|
|
// SAVING THROWS
|
|
if (roll.total >= result.target) {
|
|
result.isSuccess = true;
|
|
} else {
|
|
result.isFailure = true;
|
|
}
|
|
} else if (data.roll.type == "below") {
|
|
// MORALE, EXPLORATION
|
|
if (roll.total <= result.target) {
|
|
result.isSuccess = true;
|
|
} else {
|
|
result.isFailure = true;
|
|
}
|
|
} else if (data.roll.type == "check") {
|
|
// SCORE CHECKS (1s and 20s)
|
|
if (die == 1 || (roll.total <= result.target && die < 20)) {
|
|
result.isSuccess = true;
|
|
} else {
|
|
result.isFailure = true;
|
|
}
|
|
} else if (data.roll.type == "table") {
|
|
// Reaction
|
|
let table = data.roll.table;
|
|
let output = "";
|
|
for (let i = 0; i <= roll.total; i++) {
|
|
if (table[i]) {
|
|
output = table[i];
|
|
}
|
|
}
|
|
result.details = output;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static async sendRoll({
|
|
parts = [],
|
|
data = {},
|
|
title = null,
|
|
flavor = null,
|
|
speaker = null,
|
|
form = null,
|
|
} = {}) {
|
|
const template = "systems/ose/templates/chat/roll-result.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 && form.bonus.value) {
|
|
parts.push(form.bonus.value);
|
|
}
|
|
|
|
const roll = new Roll(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;
|
|
|
|
// Force blind roll (ability formulas)
|
|
if (data.blindroll) {
|
|
rollMode = "blindroll";
|
|
}
|
|
|
|
if (["gmroll", "blindroll"].includes(rollMode))
|
|
chatData["whisper"] = ChatMessage.getWhisperRecipients("GM");
|
|
if (rollMode === "selfroll") chatData["whisper"] = [game.user._id];
|
|
if (rollMode === "blindroll") chatData["blind"] = true;
|
|
|
|
templateData.result = OseDice.digestResult(data, roll);
|
|
|
|
return new Promise((resolve) => {
|
|
roll.render().then((r) => {
|
|
templateData.rollOSE = r;
|
|
renderTemplate(template, templateData).then((content) => {
|
|
chatData.content = content;
|
|
// Dice So Nice
|
|
if (game.dice3d) {
|
|
game.dice3d
|
|
.showForRoll(
|
|
roll,
|
|
game.user,
|
|
true,
|
|
chatData.whisper,
|
|
chatData.blind
|
|
)
|
|
.then((displayed) => {
|
|
ChatMessage.create(chatData);
|
|
resolve(roll);
|
|
});
|
|
} else {
|
|
chatData.sound = CONFIG.sounds.dice;
|
|
ChatMessage.create(chatData);
|
|
resolve(roll);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
static digestAttackResult(data, roll) {
|
|
let result = {
|
|
isSuccess: false,
|
|
isFailure: false,
|
|
target: "",
|
|
total: roll.total,
|
|
};
|
|
result.target = data.roll.thac0;
|
|
|
|
const targetAc = data.roll.target ? data.roll.target.actor.data.data.ac.value : 9;
|
|
const targetAac = data.roll.target ? data.roll.target.actor.data.data.aac.value : 0;
|
|
result.victim = data.roll.target ? data.roll.target.actor.name : null;
|
|
|
|
if (game.settings.get("ose", "ascendingAC")) {
|
|
if (roll.total < targetAac) {
|
|
result.details = game.i18n.format("OSE.messages.AttackFailure", {
|
|
bonus: result.target,
|
|
});
|
|
return result;
|
|
}
|
|
result.details = game.i18n.format("OSE.messages.AttackAscendingSuccess", {
|
|
result: roll.total,
|
|
});
|
|
result.isSuccess = true;
|
|
} else {
|
|
// B/X Historic THAC0 Calculation
|
|
if (result.target - roll.total > targetAc) {
|
|
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,
|
|
config: CONFIG.OSE,
|
|
};
|
|
|
|
// Optionally include a situational bonus
|
|
if (form !== null && form.bonus.value) parts.push(form.bonus.value);
|
|
|
|
const roll = new Roll(parts.join("+"), data).roll();
|
|
const dmgRoll = new Roll(data.roll.dmg.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;
|
|
|
|
if (["gmroll", "blindroll"].includes(rollMode))
|
|
chatData["whisper"] = ChatMessage.getWhisperRecipients("GM");
|
|
if (rollMode === "selfroll") chatData["whisper"] = [game.user._id];
|
|
if (rollMode === "blindroll") chatData["blind"] = true;
|
|
|
|
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) {
|
|
templateData.result.dmg = dmgRoll.total;
|
|
game.dice3d
|
|
.showForRoll(
|
|
dmgRoll,
|
|
game.user,
|
|
true,
|
|
chatData.whisper,
|
|
chatData.blind
|
|
)
|
|
.then(() => {
|
|
ChatMessage.create(chatData);
|
|
resolve(roll);
|
|
});
|
|
} else {
|
|
ChatMessage.create(chatData);
|
|
resolve(roll);
|
|
}
|
|
});
|
|
} else {
|
|
chatData.sound = CONFIG.sounds.dice;
|
|
ChatMessage.create(chatData);
|
|
resolve(roll);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
static async Roll({
|
|
parts = [],
|
|
data = {},
|
|
skipDialog = false,
|
|
speaker = null,
|
|
flavor = null,
|
|
title = null,
|
|
} = {}) {
|
|
let rolled = false;
|
|
const template = "systems/ose/templates/chat/roll-dialog.html";
|
|
let dialogData = {
|
|
formula: parts.join(" "),
|
|
data: data,
|
|
rollMode: game.settings.get("core", "rollMode"),
|
|
rollModes: CONFIG.Dice.rollModes,
|
|
};
|
|
|
|
let rollData = {
|
|
parts: parts,
|
|
data: data,
|
|
title: title,
|
|
flavor: flavor,
|
|
speaker: speaker,
|
|
};
|
|
if (skipDialog) {
|
|
return ["melee", "missile", "attack"].includes(data.roll.type)
|
|
? 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) => {
|
|
rolled = true;
|
|
rollData.form = html[0].children[0];
|
|
roll = ["melee", "missile", "attack"].includes(data.roll.type)
|
|
? OseDice.sendAttackRoll(rollData)
|
|
: OseDice.sendRoll(rollData);
|
|
},
|
|
},
|
|
cancel: {
|
|
icon: '<i class="fas fa-times"></i>',
|
|
label: game.i18n.localize("OSE.Cancel"),
|
|
callback: (html) => {},
|
|
},
|
|
};
|
|
|
|
const html = await renderTemplate(template, dialogData);
|
|
let roll;
|
|
|
|
//Create Dialog window
|
|
return new Promise((resolve) => {
|
|
new Dialog({
|
|
title: title,
|
|
content: html,
|
|
buttons: buttons,
|
|
default: "ok",
|
|
close: () => {
|
|
resolve(rolled ? roll : false);
|
|
},
|
|
}).render(true);
|
|
});
|
|
}
|
|
}
|