Initiative rework, with some broken css
This commit is contained in:
@@ -96,6 +96,12 @@ export class ActorL5r5e extends Actor {
|
||||
if (data.void_points.value > data.void_points.max) {
|
||||
data.void_points.value = data.void_points.max;
|
||||
}
|
||||
|
||||
// *** Migration stuff ***
|
||||
// TODO remove in patch 1.1+
|
||||
if (data.prepared === undefined) {
|
||||
data.prepared = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,6 +175,7 @@ export class BaseSheetL5r5e extends ActorSheet {
|
||||
new game.l5r5e.DicePickerDialog({
|
||||
skillId: li.data("skill") || null,
|
||||
skillCatId: li.data("skillcat") || null,
|
||||
isInitiativeRoll: li.data("initiative") || false,
|
||||
actor: this.actor,
|
||||
}).render(true);
|
||||
});
|
||||
@@ -186,6 +187,16 @@ export class BaseSheetL5r5e extends ActorSheet {
|
||||
event.target.select();
|
||||
});
|
||||
|
||||
// Prepared (Initiative)
|
||||
html.find(".prepared-control").on("click", (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const preparedId = $(event.currentTarget).data("id");
|
||||
if (["adversary", "character"].includes(preparedId)) {
|
||||
this._switchPrepared();
|
||||
}
|
||||
});
|
||||
|
||||
// *** Items : add, edit, delete ***
|
||||
html.find(".item-add").on("click", (event) => {
|
||||
event.preventDefault();
|
||||
@@ -204,6 +215,20 @@ export class BaseSheetL5r5e extends ActorSheet {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch the state "prepared" (initiative)
|
||||
* @private
|
||||
*/
|
||||
_switchPrepared() {
|
||||
this.actor.data.data.prepared = !this.actor.data.data.prepared;
|
||||
this.actor.update({
|
||||
data: {
|
||||
prepared: this.actor.data.data.prepared,
|
||||
},
|
||||
});
|
||||
this.render(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a generic item with sub type
|
||||
* @private
|
||||
|
||||
@@ -21,7 +21,7 @@ export class NpcSheetL5r5e extends BaseSheetL5r5e {
|
||||
|
||||
sheetData.data.types = NpcSheetL5r5e.types.map((e) => ({
|
||||
id: e,
|
||||
label: game.i18n.localize("l5r5e.npc.types." + e),
|
||||
label: game.i18n.localize("l5r5e.character_types." + e),
|
||||
}));
|
||||
|
||||
return sheetData;
|
||||
|
||||
@@ -22,57 +22,90 @@ export class CombatL5r5e extends Combat {
|
||||
ids = [ids];
|
||||
}
|
||||
|
||||
// Make combatants array
|
||||
const combatants = [];
|
||||
ids.forEach((combatantId) => {
|
||||
const combatant = game.combat.combatants.find((c) => c._id === combatantId);
|
||||
if (combatant && combatant.actor) {
|
||||
combatants.push(combatant);
|
||||
}
|
||||
});
|
||||
// Get global modifiers
|
||||
const cfg = {
|
||||
difficulty: game.settings.get("l5r5e", "initiative.difficulty.value"),
|
||||
difficultyHidden: game.settings.get("l5r5e", "initiative.difficulty.hidden"),
|
||||
prepared: {
|
||||
character: game.settings.get("l5r5e", "initiative.prepared.character"),
|
||||
adversary: game.settings.get("l5r5e", "initiative.prepared.adversary"),
|
||||
minion: game.settings.get("l5r5e", "initiative.prepared.minion"),
|
||||
},
|
||||
};
|
||||
|
||||
// Get modifiers
|
||||
const difficulty = game.settings.get("l5r5e", "initiative.difficulty.value");
|
||||
const difficultyHidden = game.settings.get("l5r5e", "initiative.difficulty.hidden");
|
||||
const skillId = CONFIG.l5r5e.initiativeSkills[game.settings.get("l5r5e", "initiative.encounter")];
|
||||
// SkillId from DicePicker or global
|
||||
const skillId = messageOptions.skillId
|
||||
? messageOptions.skillId
|
||||
: CONFIG.l5r5e.initiativeSkills[game.settings.get("l5r5e", "initiative.encounter")];
|
||||
const skillCat = CONFIG.l5r5e.skills.get(skillId);
|
||||
|
||||
// Get score for each combatant
|
||||
const updatedCombatants = [];
|
||||
combatants.forEach((combatant) => {
|
||||
ids.forEach((combatantId) => {
|
||||
const combatant = game.combat.combatants.find((c) => c._id === combatantId);
|
||||
if (!combatant || !combatant.actor) {
|
||||
return;
|
||||
}
|
||||
|
||||
// shortcut to data
|
||||
const data = combatant.actor.data.data;
|
||||
|
||||
// A character’s initiative value is based on their state of preparedness when the conflict began.
|
||||
// If the character was ready for the conflict, their base initiative value is their focus attribute.
|
||||
// If the character was unprepared (such as when surprised), their base initiative value is their vigilance attribute.
|
||||
const isPrepared = true; // TODO in actor ? (pc and npc)
|
||||
let initiative = isPrepared ? data.focus : data.vigilance;
|
||||
let initiative = 0;
|
||||
|
||||
// PC and Adversary
|
||||
// (minion NPCs can generate initiative value without a check, using their focus or vigilance attribute)
|
||||
if (combatant.actor.data.type !== "npc" || combatant.actor.data.data.type === "minion") {
|
||||
const formula = [`${data.rings[data.stance]}dr`];
|
||||
const skillValue =
|
||||
combatant.actor.data.type === "npc" ? data.skills[skillCat] : data.skills[skillCat][skillId];
|
||||
if (skillValue > 0) {
|
||||
formula.push(`${skillValue}ds`);
|
||||
if (combatant.actor.data.type === "npc" && combatant.actor.data.data.type === "minion") {
|
||||
// Minion NPCs can generate initiative value without a check, using their focus or vigilance attribute
|
||||
initiative = cfg.prepared.minion ? data.focus : data.vigilance;
|
||||
} else {
|
||||
// PC and Adversary
|
||||
const isPc = combatant.actor.data.type === "character";
|
||||
|
||||
// prepared is a boolean or if null we get the info in the actor sheet
|
||||
let isPrepared = isPc ? cfg.prepared.character : cfg.prepared.adversary;
|
||||
if (isPrepared === "null") {
|
||||
isPrepared = data.prepared;
|
||||
}
|
||||
initiative = isPrepared ? data.focus : data.vigilance;
|
||||
|
||||
// Roll formula
|
||||
if (!formula) {
|
||||
const createFormula = [`${data.rings[data.stance]}dr`];
|
||||
const skillValue = isPc ? data.skills[skillCat][skillId] : data.skills[skillCat];
|
||||
if (skillValue > 0) {
|
||||
createFormula.push(`${skillValue}ds`);
|
||||
}
|
||||
formula = createFormula.join("+");
|
||||
}
|
||||
|
||||
const roll = new game.l5r5e.RollL5r5e(formula.join("+"));
|
||||
|
||||
const roll = new game.l5r5e.RollL5r5e(formula);
|
||||
roll.actor = combatant.actor;
|
||||
roll.l5r5e.stance = data.stance;
|
||||
roll.l5r5e.skillId = skillId;
|
||||
roll.l5r5e.summary.difficulty = difficulty;
|
||||
roll.l5r5e.summary.difficultyHidden = difficultyHidden;
|
||||
roll.l5r5e.skillCatId = skillCat;
|
||||
roll.l5r5e.summary.difficulty =
|
||||
messageOptions.difficulty !== undefined ? messageOptions.difficulty : cfg.difficulty;
|
||||
roll.l5r5e.summary.difficultyHidden =
|
||||
messageOptions.difficultyHidden !== undefined
|
||||
? messageOptions.difficultyHidden
|
||||
: cfg.difficultyHidden;
|
||||
roll.l5r5e.summary.voidPointUsed = !!messageOptions.useVoidPoint;
|
||||
|
||||
roll.roll();
|
||||
roll.toMessage({ flavor: game.i18n.localize("l5r5e.chatdices.initiative_roll") });
|
||||
roll.toMessage({
|
||||
flavor:
|
||||
game.i18n.localize("l5r5e.chatdices.initiative_roll") +
|
||||
" (" +
|
||||
game.i18n.localize(`l5r5e.conflict.initiative.prepared_${isPrepared}`) +
|
||||
")",
|
||||
});
|
||||
|
||||
// if the character succeeded on their Initiative check, they add 1 to their base initiative value,
|
||||
// plus an additional amount equal to their bonus successes.
|
||||
if (roll.l5r5e.summary.success >= difficulty) {
|
||||
initiative = initiative + 1 + Math.max(roll.l5r5e.summary.success - difficulty, 0);
|
||||
if (roll.l5r5e.summary.success >= roll.l5r5e.summary.difficulty) {
|
||||
initiative =
|
||||
initiative + 1 + Math.max(roll.l5r5e.summary.success - roll.l5r5e.summary.difficulty, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
export class DicePickerDialog extends FormApplication {
|
||||
/**
|
||||
* Current Actor
|
||||
* @type {Actor}
|
||||
* @private
|
||||
*/
|
||||
_actor = null;
|
||||
|
||||
@@ -29,6 +31,7 @@ export class DicePickerDialog extends FormApplication {
|
||||
add_void_point: true,
|
||||
},
|
||||
useVoidPoint: false,
|
||||
isInitiativeRoll: false,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -84,8 +87,9 @@ export class DicePickerDialog extends FormApplication {
|
||||
* skillCatId string (artisan)
|
||||
* difficulty number (0-9)
|
||||
* difficultyHidden boolean
|
||||
* isInitiativeRoll boolean
|
||||
*
|
||||
* @param options actor, actorId, ringId, actorName, skillId, skillCatId, difficulty, difficultyHidden
|
||||
* @param options actor, actorId, ringId, actorName, skillId, skillCatId, difficulty, difficultyHidden, isInitiativeRoll
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
super({}, options);
|
||||
@@ -104,33 +108,38 @@ export class DicePickerDialog extends FormApplication {
|
||||
});
|
||||
|
||||
// Ring
|
||||
if (options?.ringId) {
|
||||
if (options.ringId) {
|
||||
this.ringId = options.ringId;
|
||||
}
|
||||
|
||||
// Skill / SkillCategory
|
||||
if (options?.skillId) {
|
||||
if (options.skillId) {
|
||||
this.skillId = options.skillId;
|
||||
}
|
||||
|
||||
// SkillCategory skillCatId
|
||||
if (options?.skillCatId) {
|
||||
if (options.skillCatId) {
|
||||
this.skillCatId = options.skillCatId;
|
||||
}
|
||||
|
||||
// Difficulty
|
||||
if (options?.difficulty) {
|
||||
if (options.difficulty) {
|
||||
this.difficulty = options.difficulty;
|
||||
} else {
|
||||
this.difficulty = game.settings.get("l5r5e", "initiative.difficulty.value");
|
||||
}
|
||||
|
||||
// difficultyHidden
|
||||
if (options?.difficultyHidden) {
|
||||
// DifficultyHidden
|
||||
if (options.difficultyHidden) {
|
||||
this.difficultyHidden = options.difficultyHidden;
|
||||
} else {
|
||||
this.difficultyHidden = game.settings.get("l5r5e", "initiative.difficulty.hidden");
|
||||
}
|
||||
|
||||
// InitiativeRoll
|
||||
if (options.isInitiativeRoll) {
|
||||
this.object.isInitiativeRoll = !!options.isInitiativeRoll;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -355,12 +364,13 @@ export class DicePickerDialog extends FormApplication {
|
||||
return false;
|
||||
}
|
||||
|
||||
let formula = [];
|
||||
if (this.object.ring.value > 0) {
|
||||
formula.push(`${this.object.ring.value}dr`);
|
||||
}
|
||||
if (this.object.skill.value > 0) {
|
||||
formula.push(`${this.object.skill.value}ds`);
|
||||
// If initiative roll, check if player already have
|
||||
if (this.object.isInitiativeRoll) {
|
||||
const combatant = game.combat.combatants.find((c) => c.actor._id === this._actor._id && c.initiative > 0);
|
||||
if (combatant) {
|
||||
ui.notifications.error(game.i18n.localize("l5r5e.conflict.initiative.already_set"));
|
||||
return this.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Update Actor
|
||||
@@ -381,24 +391,50 @@ export class DicePickerDialog extends FormApplication {
|
||||
}
|
||||
|
||||
// Update actor
|
||||
this._actor.update({
|
||||
await this._actor.update({
|
||||
data: diffObject(this._actor.data.data, actorData),
|
||||
});
|
||||
}
|
||||
|
||||
// Let's roll !
|
||||
const roll = await new game.l5r5e.RollL5r5e(formula.join("+"));
|
||||
// Build the formula
|
||||
let formula = [];
|
||||
if (this.object.ring.value > 0) {
|
||||
formula.push(`${this.object.ring.value}dr`);
|
||||
}
|
||||
if (this.object.skill.value > 0) {
|
||||
formula.push(`${this.object.skill.value}ds`);
|
||||
}
|
||||
|
||||
roll.actor = this._actor;
|
||||
roll.l5r5e.stance = this.object.ring.id;
|
||||
roll.l5r5e.skillId = this.object.skill.id;
|
||||
roll.l5r5e.skillCatId = this.object.skill.cat;
|
||||
roll.l5r5e.summary.difficulty = this.object.difficulty.value;
|
||||
roll.l5r5e.summary.difficultyHidden = this.object.difficulty.hidden;
|
||||
roll.l5r5e.summary.voidPointUsed = this.object.useVoidPoint;
|
||||
if (this.object.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,
|
||||
},
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// Regular roll, so let's roll !
|
||||
const roll = await new game.l5r5e.RollL5r5e(formula.join("+"));
|
||||
|
||||
roll.actor = this._actor;
|
||||
roll.l5r5e.stance = this.object.ring.id;
|
||||
roll.l5r5e.skillId = this.object.skill.id;
|
||||
roll.l5r5e.skillCatId = this.object.skill.cat;
|
||||
roll.l5r5e.summary.difficulty = this.object.difficulty.value;
|
||||
roll.l5r5e.summary.difficultyHidden = this.object.difficulty.hidden;
|
||||
roll.l5r5e.summary.voidPointUsed = this.object.useVoidPoint;
|
||||
|
||||
await roll.roll();
|
||||
await roll.toMessage();
|
||||
}
|
||||
|
||||
await roll.roll();
|
||||
await roll.toMessage();
|
||||
return this.close();
|
||||
}
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ export class GmToolsDialog extends FormApplication {
|
||||
break;
|
||||
case 3:
|
||||
// right clic - minus 1
|
||||
this.object.difficulty = Math.max(1, this.object.difficulty - 1);
|
||||
this.object.difficulty = Math.max(0, this.object.difficulty - 1);
|
||||
break;
|
||||
}
|
||||
game.settings.set("l5r5e", "initiative.difficulty.value", this.object.difficulty).then(() => this.submit());
|
||||
|
||||
@@ -61,63 +61,80 @@ export default class HooksL5r5e {
|
||||
* Combat tracker
|
||||
*/
|
||||
static async renderCombatTracker(app, html, data) {
|
||||
// TODO do this in partial
|
||||
let bar = "";
|
||||
// Display Combat bar (only for GMs)
|
||||
await this._gmCombatBar(app, html, data);
|
||||
}
|
||||
|
||||
// *** Encounter Type ***
|
||||
const encounterIcons = {
|
||||
intrigue: "i_courtier",
|
||||
duel: "fas fa-tint", // fa-tint / fa-blind
|
||||
skirmish: "i_bushi",
|
||||
mass_battle: "fa fa-users",
|
||||
};
|
||||
const encounterType = game.settings.get("l5r5e", "initiative.encounter");
|
||||
Object.entries(CONFIG.l5r5e.initiativeSkills).forEach(([id, skill]) => {
|
||||
bar =
|
||||
bar +
|
||||
`<a class="encounter encounter-control" data-id="${id}">` +
|
||||
`<i class="${encounterIcons[id]}${id === encounterType ? " active" : ""}" title="${game.i18n.localize(
|
||||
"l5r5e.conflict.initiative." + id
|
||||
)}"></i>` +
|
||||
`</a>`;
|
||||
});
|
||||
|
||||
// *** Prepared ***
|
||||
// TODO
|
||||
// const encounterType = game.settings.get("l5r5e", "initiative.prepared");
|
||||
bar =
|
||||
bar +
|
||||
`<a class="encounter prepared-control" data-id="tmp">` +
|
||||
`<i class="fa fa-low-vision" title="npc prepared or not (WIP)"></i>` +
|
||||
`</a>`;
|
||||
|
||||
const elmt = html.find("#l5r5e_encounter");
|
||||
if (elmt.length > 0) {
|
||||
elmt.html(bar);
|
||||
} else {
|
||||
html.find("#combat-round").append(`<nav class="encounters flexrow" id="l5r5e_encounter">${bar}</nav>`);
|
||||
/**
|
||||
* Display a GM bar for Combat/Initiative
|
||||
* @private
|
||||
*/
|
||||
static async _gmCombatBar(app, html, data) {
|
||||
// Only for GMs
|
||||
if (!game.user.isGM) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Buttons Listener
|
||||
// *** Conf ***
|
||||
const encounterTypeList = Object.keys(CONFIG.l5r5e.initiativeSkills);
|
||||
const prepared = {
|
||||
character: game.settings.get("l5r5e", "initiative.prepared.character"),
|
||||
adversary: game.settings.get("l5r5e", "initiative.prepared.adversary"),
|
||||
minion: game.settings.get("l5r5e", "initiative.prepared.minion"),
|
||||
};
|
||||
|
||||
// *** Template ***
|
||||
const tpl = await renderTemplate(`${CONFIG.l5r5e.paths.templates}gm/combat-tracker-bar.html`, {
|
||||
encounterType: game.settings.get("l5r5e", "initiative.encounter"),
|
||||
encounterTypeList,
|
||||
prepared,
|
||||
});
|
||||
|
||||
// Add/replace in bar
|
||||
const elmt = html.find("#l5r5e_gm_combat_tracker_bar");
|
||||
if (elmt.length > 0) {
|
||||
elmt.replaceWith(tpl);
|
||||
} else {
|
||||
html.find("#combat-round").append(tpl);
|
||||
}
|
||||
|
||||
// Buttons Listeners
|
||||
// TODO event for multiple GM
|
||||
html.find(".encounter-control").on("click", (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const encounter = $(event.currentTarget).data("id");
|
||||
if (!encounterTypeList.includes(encounter)) {
|
||||
return;
|
||||
}
|
||||
game.settings
|
||||
.set("l5r5e", "initiative.encounter", encounter)
|
||||
.then(() => HooksL5r5e.renderCombatTracker(app, html, data));
|
||||
.then(() => HooksL5r5e._gmCombatBar(app, html, data));
|
||||
});
|
||||
|
||||
// html.find(".prepared-control").on("click", (event) => {
|
||||
// event.preventDefault();
|
||||
// event.stopPropagation();
|
||||
// let prepared = $(event.currentTarget).data('id');
|
||||
// // if same, unset it
|
||||
// if (prepared === encounterType) {
|
||||
// prepared = "";
|
||||
// }
|
||||
// game.settings.set("l5r5e", "initiative.prepared", prepared).then(() => HooksL5r5e.renderCombatTracker(app, html, data));
|
||||
// });
|
||||
html.find(".prepared-control").on("click", (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
let preparedId = $(event.currentTarget).data("id");
|
||||
if (!Object.hasOwnProperty.call(prepared, preparedId)) {
|
||||
return;
|
||||
}
|
||||
let value = prepared[preparedId];
|
||||
switch (value) {
|
||||
case "false":
|
||||
value = "true";
|
||||
break;
|
||||
case "true":
|
||||
value = preparedId === "minion" ? "false" : "null";
|
||||
break;
|
||||
case "null":
|
||||
value = "false";
|
||||
break;
|
||||
}
|
||||
game.settings
|
||||
.set("l5r5e", `initiative.prepared.${preparedId}`, value)
|
||||
.then(() => HooksL5r5e._gmCombatBar(app, html, data));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,6 +21,7 @@ export const PreloadTemplates = async function () {
|
||||
"systems/l5r5e/templates/actors/npc/social.html",
|
||||
"systems/l5r5e/templates/actors/npc/rings.html",
|
||||
"systems/l5r5e/templates/actors/npc/attributes.html",
|
||||
"systems/l5r5e/templates/actors/npc/conflict.html",
|
||||
"systems/l5r5e/templates/actors/npc/skill.html",
|
||||
"systems/l5r5e/templates/actors/npc/techniques.html",
|
||||
// items
|
||||
|
||||
@@ -26,11 +26,25 @@ export const RegisterSettings = function () {
|
||||
type: String,
|
||||
default: "skirmish",
|
||||
});
|
||||
game.settings.register("l5r5e", "initiative.prepared", {
|
||||
name: "Initiative NPC prepared or not",
|
||||
game.settings.register("l5r5e", "initiative.prepared.character", {
|
||||
name: "Initiative PC prepared or not",
|
||||
scope: "world",
|
||||
config: false,
|
||||
type: Boolean,
|
||||
default: true,
|
||||
type: String,
|
||||
default: "null",
|
||||
});
|
||||
game.settings.register("l5r5e", "initiative.prepared.adversary", {
|
||||
name: "Initiative NPC adversary are prepared or not",
|
||||
scope: "world",
|
||||
config: false,
|
||||
type: String,
|
||||
default: "null",
|
||||
});
|
||||
game.settings.register("l5r5e", "initiative.prepared.minion", {
|
||||
name: "Initiative NPC minion are prepared or not",
|
||||
scope: "world",
|
||||
config: false,
|
||||
type: String,
|
||||
default: "true",
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user