import { BaseCharacterSheetL5r5e } from "./base-character-sheet.js"; import { TwentyQuestionsDialog } from "./twenty-questions-dialog.js"; /** * Actor / Character Sheet */ export class CharacterSheetL5r5e extends BaseCharacterSheetL5r5e { static get defaultOptions() { return foundry.utils.mergeObject(super.defaultOptions, { classes: ["l5r5e", "sheet", "actor"], template: CONFIG.l5r5e.paths.templates + "actors/character-sheet.html", tabs: [ { navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "skills" }, { navSelector: ".advancements-tabs", contentSelector: ".advancements-body", initial: "last" }, ], }); } /** * Add the TwentyQuestions button in L5R specific bar * @override * @return {{label: string, class: string, icon: string, onclick: Function|null}[]} */ _getL5rHeaderButtons() { const buttons = super._getL5rHeaderButtons(); if (!this.isEditable || this.actor.limited) { return buttons; } buttons.unshift({ label: game.i18n.localize("l5r5e.twenty_questions.bt_abrev"), class: "twenty-questions", icon: "fas fa-graduation-cap", onclick: async () => { await new TwentyQuestionsDialog(this.actor).render(true); }, }); return buttons; } /** * Commons datas */ async getData(options = {}) { const sheetData = await super.getData(options); // Min rank = 1 this.actor.system.identity.school_rank = Math.max(1, this.actor.system.identity.school_rank); // Money — read separate fields directly (koku/bu/zeni stored independently) sheetData.data.system.money = { koku: this.actor.system.koku ?? 0, bu: this.actor.system.bu ?? 0, zeni: this.actor.system.zeni ?? 0, }; // Split school advancements by rank, and calculate xp spent and add it to total this._prepareSchoolAdvancement(sheetData); // Split Others advancements, and calculate xp spent and add it to total this._prepareOthersAdvancement(sheetData); // Update spent_xp to actor this.actor.system.xp_spent = sheetData.data.system.xp_spent; // Total sheetData.data.system.xp_saved = Math.floor( parseInt(sheetData.data.system.xp_total) - parseInt(sheetData.data.system.xp_spent) ); // Chiaroscuro: Skill ranks list for ) back to 0 (number) for (const [key, value] of Object.entries(formData)) { if (key.startsWith("system.skills.") && value === "0") { formData[key] = 0; } } // Save computed values const currentData = this.object.system; formData["system.focus"] = currentData.focus; formData["system.vigilance"] = currentData.vigilance; formData["system.endurance"] = currentData.endurance; formData["system.composure"] = currentData.composure; formData["system.fatigue.max"] = currentData.fatigue.max; formData["system.strife.max"] = currentData.strife.max; formData["system.void_points.max"] = currentData.void_points.max; return super._updateObject(event, formData); } /** * Convert a sum in Zeni to Zeni, Bu and Koku * @param {number} zeni * @return {{bu: number, koku: number, zeni: number}} * @private */ _zeniToMoney(zeni) { const money = { koku: 0, bu: 0, zeni: zeni, }; if (money.zeni >= CONFIG.l5r5e.money[0]) { money.koku = Math.floor(money.zeni / CONFIG.l5r5e.money[0]); money.zeni = Math.floor(money.zeni % CONFIG.l5r5e.money[0]); } if (money.zeni >= CONFIG.l5r5e.money[1]) { money.bu = Math.floor(money.zeni / CONFIG.l5r5e.money[1]); money.zeni = Math.floor(money.zeni % CONFIG.l5r5e.money[1]); } return money; } /** * Convert a sum in Zeni, Bu and Koku to Zeni * @param {number} koku * @param {number} bu * @param {number} zeni * @return {number} * @private */ _moneyToZeni(koku, bu, zeni) { return Math.floor(koku * CONFIG.l5r5e.money[0]) + Math.floor(bu * CONFIG.l5r5e.money[1]) + Math.floor(zeni); } /** * Add or Subtract money (+/- buttons) * @param {Event} event * @private */ _modifyMoney(event) { event.preventDefault(); event.stopPropagation(); const elmt = $(event.currentTarget); const type = elmt.data("type"); const mod = parseInt(elmt.data("value")) || 0; if (!mod || !type) return; let koku = parseInt(this.actor.system.koku) || 0; let bu = parseInt(this.actor.system.bu) || 0; let zeni = parseInt(this.actor.system.zeni) || 0; if (type === "koku") koku += mod; else if (type === "bu") bu += mod; else zeni += mod; // Auto-convert if (zeni >= 10) { bu += Math.floor(zeni / 10); zeni = zeni % 10; } if (bu >= 10) { koku += Math.floor(bu / 10); bu = bu % 10; } // Clamp negatives if (zeni < 0) { bu += Math.ceil(zeni / 10); zeni = ((zeni % 10) + 10) % 10; } if (bu < 0) { koku += Math.ceil(bu / 10); bu = ((bu % 10) + 10) % 10; } koku = Math.max(0, koku); this.actor.update({ system: { koku, bu, zeni } }); this.render(false); } /** * Add or Subtract XP (+/- buttons) * @param {Event} event * @private */ async _modifyXP(event) { event.preventDefault(); event.stopPropagation(); const elmt = $(event.currentTarget); let mod = elmt.data("value"); if (!mod) { return; } const new_xp_total = Math.max(0, this.actor.system.xp_total + mod); this.actor.update({ system: { xp_total: new_xp_total, }, }); if(this.actor.system.xp_spent > new_xp_total) { ui.notifications.warn("l5r5e.advancements.warning.total_less_then_spent", { localize: true }) } this.render(false); } /** * Add +1 to actor school rank * @param {Event} event * @private */ async _actorAddOneToRank(event) { event.preventDefault(); event.stopPropagation(); this.actor.system.identity.school_rank = this.actor.system.identity.school_rank + 1; await this.actor.update({ system: { identity: { school_rank: this.actor.system.identity.school_rank, }, }, }); this.render(false); } /** * Open the linked school curriculum journal * @param {Event} event * @private */ async _openLinkedJournal(event) { event.preventDefault(); event.stopPropagation(); const actorJournal = this.actor.system.identity.school_curriculum_journal; if (!actorJournal.id) { return; } const journal = await game.l5r5e.HelpersL5r5e.getObjectGameOrPack({ id: actorJournal.id, pack: actorJournal.pack, type: "JournalEntry", }); if (journal) { journal.sheet.render(true); } } }