RnK with some bugs

This commit is contained in:
Vlyan
2021-02-01 16:27:57 +01:00
parent 30f71e31cd
commit 68577737fc
14 changed files with 513 additions and 204 deletions

View File

@@ -72,13 +72,7 @@ export class L5rBaseDie extends DiceTerm {
this._evaluateModifiers();
// Combine all results
this.l5r5e = { success: 0, explosive: 0, opportunity: 0, strife: 0 };
this.results.forEach((term) => {
const face = this.constructor.FACES[term.result];
["success", "explosive", "opportunity", "strife"].forEach((props) => {
this.l5r5e[props] += parseInt(face[props]);
});
});
this.l5rSummary();
// Return the evaluated term
this._evaluated = true;
@@ -87,24 +81,33 @@ export class L5rBaseDie extends DiceTerm {
return this;
}
/**
* Summarise the total of success, strife... for L5R dices for the current Die
*/
l5rSummary() {
this.l5r5e = { success: 0, explosive: 0, opportunity: 0, strife: 0 };
this.results.forEach((term) => {
const face = this.constructor.FACES[term.result];
["success", "explosive", "opportunity", "strife"].forEach((props) => {
this.l5r5e[props] += parseInt(face[props]);
});
});
}
/**
* Roll the DiceTerm by mapping a random uniform draw against the faces of the dice term
* @override
*/
roll(options) {
const roll = super.roll(options);
//roll.l5r5e = this.l5r5e;
return roll;
}
/** @override */
static fromData(data) {
const roll = super.fromData(data);
roll.l5r5e = data.l5r5e;
return roll;
}
@@ -114,9 +117,7 @@ export class L5rBaseDie extends DiceTerm {
*/
toJSON() {
const json = super.toJSON();
json.l5r5e = this.l5r5e;
return json;
}
}

View File

@@ -32,7 +32,7 @@ export class RollnKeepDialog extends FormApplication {
*/
object = {
currentStep: 0,
submitDisabled: true,
submitDisabled: false,
swapDiceFaces: {
rings: [],
skills: [],
@@ -52,6 +52,7 @@ export class RollnKeepDialog extends FormApplication {
title: game.i18n.localize("l5r5e.roll_n_keep.title"),
width: 660,
height: 454,
closeOnSubmit: false,
});
}
@@ -62,21 +63,23 @@ export class RollnKeepDialog extends FormApplication {
return `l5r5e-roll-n-keep-dialog-${this.message._id}`;
}
set message(msg) {
this.message = msg instanceof ChatMessage ? duplicate(msg) : null;
}
/**
* Create the Roll n Keep dialog
* @param {number} messageId
* @param {FormApplicationOptions} options
*/
constructor(messageId, options = {}) {
console.clear(); // TODO TMP
super({}, options);
this.message = game.messages.get(messageId);
this.options.editable = this.message?._roll.l5r5e.actor.owner || false;
this._initializeDiceFaces();
this._initialize();
this.options.editable =
this.message?.isAuthor || this.message?._roll.l5r5e.actor?.owner || this.message?.owner || false;
console.log(this.object); // TODO TMP
this._initializeDiceFaces();
this._initializeHistory();
}
/**
@@ -86,7 +89,7 @@ export class RollnKeepDialog extends FormApplication {
if (!this.message) {
return;
}
this._initialize();
this._initializeHistory();
this.render(false);
}
@@ -105,10 +108,10 @@ export class RollnKeepDialog extends FormApplication {
}
/**
* Initialize the dialog with the message
* Initialize the dice history list
* @private
*/
_initialize() {
_initializeHistory() {
if (!this.message) {
return;
}
@@ -118,12 +121,18 @@ export class RollnKeepDialog extends FormApplication {
// Already history
if (Array.isArray(this.roll.l5r5e.history)) {
this.object.currentStep = this.roll.l5r5e.history.length;
this.object.dicesList = this.roll.l5r5e.history;
let currentStep = this.roll.l5r5e.history.length - 1;
if (!this._haveChoice(currentStep)) {
currentStep += 1;
}
this.object.currentStep = currentStep;
return;
}
// New
this.object.dicesList = [[]];
this.roll.terms.forEach((term) => {
if (typeof term !== "object") {
return;
@@ -132,7 +141,6 @@ export class RollnKeepDialog extends FormApplication {
this.object.dicesList[0].push({
type: term.constructor.name,
face: res.result,
img: term.constructor.getResultSrc(res.result),
choice: RollnKeepDialog.CHOICES.nothing,
});
});
@@ -145,14 +153,10 @@ export class RollnKeepDialog extends FormApplication {
*/
_initializeDiceFaces() {
// All faces are unique for rings
this.object.swapDiceFaces.rings = Object.keys(game.l5r5e.RingDie.FACES).map((id) => {
return { id, img: game.l5r5e.RingDie.getResultSrc(id) };
});
this.object.swapDiceFaces.rings = Object.keys(game.l5r5e.RingDie.FACES);
// Only unique for Skills
this.object.swapDiceFaces.skills = [1, 3, 6, 8, 10, 11, 12].map((id) => {
return { id, img: game.l5r5e.AbilityDie.getResultSrc(id) };
});
this.object.swapDiceFaces.skills = [1, 3, 6, 8, 10, 11, 12];
}
/**
@@ -170,6 +174,15 @@ export class RollnKeepDialog extends FormApplication {
];
}
/**
* Define whether a user is able to begin a dragstart workflow for a given drag selector
* @param selector The candidate HTML selector for dragging
* @return Can the current user drag this selector?
*/
_canDragStart(selector) {
return this.options.editable;
}
/**
* Callback actions which occur at the beginning of a drag start workflow.
* @param {DragEvent} event The originating DragEvent
@@ -192,8 +205,12 @@ export class RollnKeepDialog extends FormApplication {
*/
getData(options = null) {
// Check only on 1st step
const kept = this.object.currentStep === 0 ? this._getKeepCount() : 1;
this.object.submitDisabled = kept < 1 || kept > this.roll.l5r5e.summary.ringsUsed;
if (this.object.currentStep === 0) {
const kept = this._getKeepCount();
this.object.submitDisabled = kept < 1 || kept > this.roll.l5r5e.summary.ringsUsed;
} else if (!this.object.dicesList[this.object.currentStep]) {
this.options.editable = false;
}
return {
...super.getData(options),
@@ -263,39 +280,16 @@ export class RollnKeepDialog extends FormApplication {
}
current.choice = type;
current.img = game.l5r5e[current.type].getResultSrc(current.newFace ?? current.face);
// Little time saving : on 1st step, if we reach the max kept dices, discard all dices without a choice
if (this.object.currentStep === 0 && this._getKeepCount() === this.roll.l5r5e.summary.ringsUsed) {
this._discardDiceWithoutChoice();
}
this.render(false);
return false;
}
/**
* Roll a new die avec return the result
* @private
*/
async _newRoll(dieType, actionType) {
const roll = await new game.l5r5e.RollL5r5e(dieType === "RingDie" ? "1dr" : "1ds");
roll.actor = this.message.roll.l5r5e.actor;
roll.l5r5e.stance = this.message.roll.l5r5e.stance;
// roll.l5r5e.skillId = this.message.roll.l5r5e.skillId;
// roll.l5r5e.skillCatId = this.message.roll.l5r5e.skillCatId;
await roll.roll();
await roll.toMessage({
flavor: game.i18n.localize(`l5r5e.roll_n_keep.${actionType}_chat`),
});
const dice = roll.terms[0];
const result = dice.results[0].result;
return {
type: dieType,
face: result,
img: dice.constructor.getResultSrc(result),
choice: RollnKeepDialog.CHOICES.nothing,
};
}
/**
* Return the current number of dices kept
* @private
@@ -321,6 +315,255 @@ export class RollnKeepDialog extends FormApplication {
}, 0);
}
/**
* Return true if the player can make a choice for the current step
* @private
*/
_haveChoice(currentStep) {
return (
this.object.dicesList[currentStep] &&
this.object.dicesList[currentStep].some((e) => !!e && e.choice === RollnKeepDialog.CHOICES.nothing)
);
}
/**
* Discard all dices without a choice for the current step
* @private
*/
_discardDiceWithoutChoice() {
this.object.dicesList[this.object.currentStep]
.filter((e) => !!e)
.map((e) => {
if (e.choice === RollnKeepDialog.CHOICES.nothing) {
e.choice = RollnKeepDialog.CHOICES.discard;
}
return e;
});
}
/**
* Apply all choices to build the next step
* @returns {Promise<void>}
* @private
*/
async _applyChoices() {
const nextStep = this.object.currentStep + 1;
// Foreach kept dices, apply choices
const newRolls = {};
this.object.dicesList[this.object.currentStep].forEach((die, idx) => {
if (!die) {
return;
}
switch (die.choice) {
case RollnKeepDialog.CHOICES.keep:
// Exploding dice : add a new dice in the next step
if (game.l5r5e[die.type].FACES[die.face].explosive) {
if (!newRolls[die.type]) {
newRolls[die.type] = 0;
}
newRolls[die.type] += 1;
}
break;
case RollnKeepDialog.CHOICES.reroll:
// Reroll : add a new dice in the next step
if (!newRolls[die.type]) {
newRolls[die.type] = 0;
}
newRolls[die.type] += 1;
break;
case RollnKeepDialog.CHOICES.swap:
// FaceSwap : add a new dice with selected face in next step
if (!this.object.dicesList[nextStep]) {
this.object.dicesList[nextStep] = Array(this.object.dicesList[0].length).fill(null);
}
this.object.dicesList[nextStep][idx] = {
type: this.object.dicesList[this.object.currentStep][idx].type,
face: this.object.dicesList[this.object.currentStep][idx].newFace,
choice: RollnKeepDialog.CHOICES.keep,
};
delete this.object.dicesList[this.object.currentStep][idx].newFace;
break;
}
});
// If new rolls, roll and add them
if (Object.keys(newRolls).length > 0) {
const newRollsResults = await this._newRoll(newRolls);
if (!this.object.dicesList[nextStep]) {
this.object.dicesList[nextStep] = Array(this.object.dicesList[0].length).fill(null);
}
this.object.dicesList[this.object.currentStep].forEach((die, idx) => {
if (!die) {
return;
}
if (
die.choice === RollnKeepDialog.CHOICES.reroll ||
(die.choice === RollnKeepDialog.CHOICES.keep && game.l5r5e[die.type].FACES[die.face].explosive)
) {
this.object.dicesList[nextStep][idx] = newRollsResults[die.type].shift();
}
});
}
}
/**
* Transform a array (of int or object) into a formula ring/skill
* @param rolls
* @returns {string}
* @private
*/
_arrayToFormula(rolls) {
const formula = [];
if (rolls["RingDie"]) {
const rings = Array.isArray(rolls["RingDie"]) ? rolls["RingDie"].length : rolls["RingDie"];
formula.push(rings + "dr");
}
if (rolls["AbilityDie"]) {
const skills = Array.isArray(rolls["AbilityDie"]) ? rolls["AbilityDie"].length : rolls["AbilityDie"];
formula.push(skills + "ds");
}
if (formula.length < 1) {
return "";
}
return formula.join("+");
}
/**
* Roll all new dice at once (better performance) and return the result
* @private
*/
async _newRoll(newRolls) {
const out = {
RingDie: [],
AbilityDie: [],
};
const roll = await new game.l5r5e.RollL5r5e(this._arrayToFormula(newRolls));
roll.l5r5e = {
...this.message.roll.l5r5e,
summary: roll.l5r5e.summary,
};
await roll.roll();
// Show DsN dice for the new roll
if (game.dice3d !== undefined) {
game.dice3d.showForRoll(roll, game.user, true);
}
roll.terms.forEach((term) => {
if (typeof term !== "object") {
return;
}
term.results.forEach((res) => {
out[term.constructor.name].push({
type: term.constructor.name,
face: res.result,
choice: RollnKeepDialog.CHOICES.nothing,
});
});
});
return out;
}
/**
* Rebuild the message roll
* @private
*/
async _rebuildRoll() {
// Get all kept dices + new (choice null)
const diceList = this.object.dicesList.reduce((acc, step) => {
step.forEach((die) => {
if (!!die && die.choice !== RollnKeepDialog.CHOICES.discard) {
if (!acc[die.type]) {
acc[die.type] = [];
}
acc[die.type].push(die);
}
});
return acc;
}, {});
// Re create a new roll
const roll = await new game.l5r5e.RollL5r5e(this._arrayToFormula(diceList));
roll.l5r5e = {
...this.message.roll.l5r5e,
summary: roll.l5r5e.summary,
};
// Fill the data
roll.evaluate();
// Modify results
roll.terms.map((term) => {
if (term instanceof game.l5r5e.L5rBaseDie) {
term.results.map((res) => {
const die = diceList[term.constructor.name].shift();
res.result = die.face;
return res;
});
term.l5rSummary();
}
return term;
});
// Recompute summary
roll.l5rSummary();
// Add roll & history to message
this.message._roll = roll;
this.message._roll.l5r5e.history = this.object.dicesList;
}
/**
* Send the new roll in chat and delete the old message
* @returns {Promise<void>}
* @private
*/
async _toChatMessage() {
// Keep old Ids
const appId = this.id;
const msgId = this.message._id;
if (this.message._roll.l5r5e.isInitiativeRoll) {
await this.message._roll.l5r5e.actor.rollInitiative({
rerollInitiative: true,
initiativeOptions: {
messageOptions: {
rnkRoll: this.message._roll,
},
},
});
// Adhesive tape to get the messageId :/
this.message = this.message._roll.l5r5e.actor.rnkMessage;
delete this.message._roll.l5r5e.actor.rnkMessage;
} else {
// Send it to chat, switch to new message
this.message = await this.message._roll.toMessage();
}
// Refresh viewers
game.l5r5e.sockets.updateMessageIdAndRefresh(appId, this.message._id);
// Delete old chat message related to this series
if (game.settings.get("l5r5e", "rnk.deleteOldMessage")) {
if (game.user.isGM) {
const message = game.messages.get(msgId);
if (message) {
message.delete();
}
} else {
game.l5r5e.sockets.deleteChatMessage(msgId);
}
}
}
/**
* This method is called upon form submission after form data is validated
* @param event The initial triggering submission event
@@ -334,74 +577,26 @@ export class RollnKeepDialog extends FormApplication {
return;
}
console.log("**** _updateObject");
// Discard all dices without a choice for the current step
this._discardDiceWithoutChoice();
console.log(this.object.dicesList);
// Apply all choices to build the next step
await this._applyChoices();
// Actions p 26 : change, ignore/discard, reroll, reserve, change face
// let addNewRoll = false;
// switch (type) {
// case RollnKeepDialog.CHOICES.keep:
// // current.explosive = term.constructor.FACES[current.face / newFace].explosive
// if (current.explosive) {
// addNewRoll = true;
// }
// break;
//
// case RollnKeepDialog.CHOICES.reroll:
// addNewRoll = true;
// break;
// }
//
// // New roll
// if (addNewRoll) {
// if (!this.object.dicesList[data.step + 1]) {
// this.object.dicesList[data.step + 1] = Array(this.object.dicesList[0].length).fill(null);
// }
// this.object.dicesList[data.step + 1][data.die] = await this._newRoll(current.type, type);
// }
// *** Below this the current step become the next step ***
this.object.currentStep += 1;
// if (isInitiativeRoll) {
// // Initiative roll
// this._actor.rollInitiative({
// initiativeOptions: {
// formula: formula.join("+"),
// // updateTurn: true,
// messageOptions: {
// skillId: this.object.skill.id,
// difficulty: this.object.difficulty.value,
// difficultyHidden: this.object.difficulty.hidden,
// useVoidPoint: this.object.useVoidPoint,
// rerollInitiative: true,
// },
// },
// });
// } else {}
// Rebuild the roll
await this._rebuildRoll();
// Notify the change to other players
// game.l5r5e.sockets.refreshAppId(this.id);
// Send the new roll in chat and delete the old message
await this._toChatMessage();
// this.message._roll.l5r5e.history = {test: "yahooo"};
// await message.update({
// data: {
// roll: roll
// }
// });
// message.render(false);
// console.log(roll.toJSON(), this.message);
// ui.chat.updateMessage(message);
// ui.chat.postOne(message, false);
// if (game.user.isGM) {
// message.delete();
// } else {
// game.l5r5e.sockets.deleteChatMessage(messageId);
// }
// return this.close();
// If a next step exist, rerender, else close
if (this.object.dicesList[this.object.currentStep]) {
return this.render();
}
return this.close();
}
/**
@@ -411,6 +606,7 @@ export class RollnKeepDialog extends FormApplication {
*/
static async onChatAction(event) {
event.preventDefault();
event.stopPropagation();
// Extract card data
const button = $(event.currentTarget);

View File

@@ -21,7 +21,7 @@ export class RollL5r5e extends Roll {
difficulty: 2,
difficultyHidden: false,
voidPointUsed: false,
ringsUsed: 0,
ringsUsed: null,
totalSuccess: 0,
totalBonus: 0,
success: 0,
@@ -30,6 +30,7 @@ export class RollL5r5e extends Roll {
strife: 0,
},
history: null,
isInitiativeRoll: false,
};
// Parse flavor for stance and skillId
@@ -68,24 +69,10 @@ export class RollL5r5e extends Roll {
// Roll
super.evaluate({ minimize, maximize });
// Current terms - L5R Summary
this.terms.forEach((term) => this._l5rSummary(term));
// Check inner L5R rolls - L5R Summary
this._dice.forEach((term) => this._l5rSummary(term));
// Store final outputs
this._rolled = true;
this.l5r5e.dicesTypes.std = this.dice.some(
(term) => term instanceof DiceTerm && !(term instanceof game.l5r5e.L5rBaseDie)
); // ignore math symbols
this.l5r5e.dicesTypes.l5r = this.dice.some((term) => term instanceof game.l5r5e.L5rBaseDie);
this.l5r5e.summary.totalBonus = Math.max(0, this.l5r5e.summary.totalSuccess - this.l5r5e.summary.difficulty);
this.l5r5e.summary.ringsUsed = this.dice.reduce(
(acc, term) => (term instanceof game.l5r5e.RingDie ? acc + term.number : acc),
0
);
// Compute summary
this.l5rSummary();
return this;
}
@@ -93,10 +80,43 @@ export class RollL5r5e extends Roll {
/**
* Summarise the total of success, strife... for L5R dices for the current roll
*
* @private
*/
l5rSummary() {
const summary = this.l5r5e.summary;
// Reset totals
summary.success = 0;
summary.explosive = 0;
summary.opportunity = 0;
summary.strife = 0;
summary.totalSuccess = 0;
// Current terms - L5R Summary
this.terms.forEach((term) => this._l5rTermSummary(term));
// Check inner L5R rolls - L5R Summary
this._dice.forEach((term) => this._l5rTermSummary(term));
// Store final outputs
this.l5r5e.dicesTypes.std = this.dice.some(
(term) => term instanceof DiceTerm && !(term instanceof game.l5r5e.L5rBaseDie)
); // ignore math symbols
this.l5r5e.dicesTypes.l5r = this.dice.some((term) => term instanceof game.l5r5e.L5rBaseDie);
summary.totalBonus = Math.max(0, summary.totalSuccess - summary.difficulty);
summary.ringsUsed = this.dice.reduce(
(acc, term) => (term instanceof game.l5r5e.RingDie ? acc + term.number : acc),
0
);
}
/**
* Summarise the total of success, strife... for L5R dices for the current term
*
* @param term
* @private
*/
_l5rSummary(term) {
_l5rTermSummary(term) {
if (!(term instanceof game.l5r5e.L5rBaseDie)) {
return;
}