foundryacks/src/module/dice.js

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