- Remplace les vieilles règles table (mortes) des dialog-gerer-herbes et dialog-gerer-ingredients par des règles flex scoped - Dialog-gerer-herbes/potions/ingredients/deniers : style= flex/gap/padding → classes LESS par dialog avec nth-child - actor-sheet.hbs : gap+flex-wrap → .oeuvre-buttons, height:200px → .tab.hautreve - carac-derivee.hbs : style sur .sust-buttons → déplacé dans LESS - regles-optionnelles.hbs : width:100% → .btn-voir-tables-encaissement - dialog-lancer-recette (hbs+js) : status colors → classes .status-* - roll-part-appelmoral.hbs : font-size conditionnel → classe .moral-low - rdd-utility.js : span style → .resultat-created - rdd-import-stats.js : textarea style → #statBlock - base-actor-sheet.js : select style → .qty-amount - export-scriptarium/*.hbs : flex-grow, margin-bottom → classes - app-personnage-aleatoire.hbs : text-align → .random-char-right Seuls 2 styles dynamiques persistent : background-image option (partial-roll-coeur.hbs) et width fillPercent barre (inventaire-item.hbs).
367 lines
14 KiB
JavaScript
367 lines
14 KiB
JavaScript
import { RdDUtility } from "../rdd-utility.js";
|
|
import { Misc } from "../misc.js";
|
|
import { DialogSplitItem } from "../dialog-split-item.js";
|
|
import { RdDSheetUtility } from "../rdd-sheet-utility.js";
|
|
import { Monnaie } from "../item-monnaie.js";
|
|
import { ITEM_TYPES } from "../constants.js";
|
|
import { RdDItem } from "../item.js";
|
|
import { RdDTextEditor } from "../apps/rdd-text-roll-editor.js";
|
|
import { ItemAction } from "../item/item-actions.js";
|
|
import { RdDItemCompetenceCreature } from "../item-competencecreature.js";
|
|
import { DialogVerifierIngredients } from "../dialog-verifier-ingredients.js";
|
|
import { DialogLancerRecette } from "../dialog-lancer-recette.js";
|
|
|
|
/* -------------------------------------------- */
|
|
/**
|
|
* Extend the basic ActorSheet with some very simple modifications
|
|
* @extends {ActorSheet}
|
|
*/
|
|
export class RdDBaseActorSheet extends foundry.appv1.sheets.ActorSheet {
|
|
static _warnedAppV1 = true
|
|
|
|
/** @override */
|
|
static get defaultOptions() {
|
|
return foundry.utils.mergeObject(foundry.appv1.sheets.ActorSheet.defaultOptions, {
|
|
classes: ["rdd", "sheet", "actor"],
|
|
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "carac" }],
|
|
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: undefined }],
|
|
scrollY: [".sheet-body"],
|
|
vueDetaillee: false,
|
|
triEquipement: 'name'
|
|
}, { inplace: false })
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async getData() {
|
|
Monnaie.validerMonnaies(this.actor)
|
|
|
|
this.actor.computeEtatGeneral();
|
|
let formData = {
|
|
title: this.title,
|
|
id: this.actor.id,
|
|
type: this.actor.type,
|
|
img: this.actor.img,
|
|
name: this.actor.name,
|
|
system: this.actor.system,
|
|
description: await RdDTextEditor.enrichHTML(this.actor.system.description, this.actor),
|
|
notesmj: await RdDTextEditor.enrichHTML(this.actor.system.notesmj, this.actor),
|
|
options: RdDSheetUtility.mergeDocumentRights(this.options, this.actor, this.isEditable),
|
|
effects: this.actor.effects
|
|
}
|
|
|
|
RdDUtility.filterItemsPerTypeForSheet(formData, this.actor.itemTypes, this.options.triEquipement);
|
|
formData.calc = {
|
|
fortune: Monnaie.toSolsDeniers(this.actor.getFortune()),
|
|
prixTotalEquipement: this.actor.computePrixTotalEquipement(),
|
|
encTotal: this.actor.getEncTotal(),
|
|
}
|
|
|
|
this.objetVersConteneur = RdDUtility.buildArbreDeConteneurs(formData.conteneurs, formData.inventaires);
|
|
this._appliquerRechercheObjets(formData.conteneurs, formData.inventaires);
|
|
formData.conteneurs = RdDUtility.conteneursRacine(formData.conteneurs);
|
|
formData.competences.filter(it => it.type == ITEM_TYPES.competencecreature)
|
|
.forEach(it => it.isdommages = it.isDommages())
|
|
|
|
return formData;
|
|
}
|
|
|
|
_appliquerRechercheObjets(conteneurs, inventaires) {
|
|
if (this.options.recherche?.text) {
|
|
const recherche = this.options.recherche;
|
|
const allVisible = inventaires.filter(it => it.isNomTypeLike(recherche.text)).map(it => it.id);
|
|
let addVisible = conteneurs.filter(it => it.isNomTypeLike(recherche.text)).map(it => it.id)
|
|
do {
|
|
allVisible.push(...addVisible)
|
|
const parentsIds = conteneurs.filter(it => it.system.contenu.find(id => allVisible.includes(id))).map(it => it.id)
|
|
addVisible = parentsIds.filter(id => !allVisible.includes(id))
|
|
}
|
|
while (addVisible.length > 0)
|
|
inventaires.forEach(it => it.system.isHidden = !allVisible.includes(it.id))
|
|
conteneurs.forEach(it => it.system.isHidden = !allVisible.includes(it.id))
|
|
}
|
|
else {
|
|
inventaires.forEach(it => it.system.isHidden = false)
|
|
conteneurs.forEach(it => it.system.isHidden = false)
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */ /** @override */
|
|
activateListeners(html) {
|
|
super.activateListeners(html);
|
|
this.html = html;
|
|
|
|
this.html.find('.actionItem').click(async event => await ItemAction.onActionItem(event, this.actor, this.options))
|
|
this.html.find('.item-edit').click(async event => await this.itemActionEdit(event))
|
|
this.html.find('.item-verify-ingredients').click(async event => await this._onVerifyIngredients(event))
|
|
this.html.find('.item-lancer-recette').click(async event => await this._onLancerRecette(event))
|
|
this.html.find('.conteneur-name a').click(async event => {
|
|
RdDUtility.toggleAfficheContenu(this.getItemId(event))
|
|
this.render(true)
|
|
})
|
|
|
|
this.html.find('.actor-montrer').click(async event => await this.actor.postActorToChat());
|
|
|
|
this.html.find('.recherche')
|
|
.each((index, field) => {
|
|
this._rechercheSelectArea(field);
|
|
})
|
|
.keyup(async event => this._rechercherKeyup(event))
|
|
.change(async event => this._rechercherKeyup(event))
|
|
|
|
this.html.find('.recherche').prop("disabled", false)
|
|
|
|
this.html.find('.item-list .item').dblclick(async event => {
|
|
const item = this.getItem(event)
|
|
if (item) item.sheet.render(true)
|
|
})
|
|
|
|
this.html.find('.tri-equipement').change(event => {
|
|
this.options.triEquipement = event.currentTarget.value;
|
|
this.render(true);
|
|
})
|
|
|
|
// Everything below here is only needed if the sheet is editable
|
|
if (!this.options.editable) return;
|
|
|
|
this.html.find('.item-equip-armure').click(async event => await this.actor.equiperObjet(this.getItem(event)))
|
|
this.html.find('.item-delete').click(async event => await RdDUtility.confirmActorItemDelete(this.getItem(event), this.actor));
|
|
this.html.find('.item-quantite-plus').click(async event => await this.actor.itemQuantiteIncDec(this.getItemId(event), 1));
|
|
this.html.find('.item-quantite-moins').click(async event => await this.actor.itemQuantiteIncDec(this.getItemId(event), -1));
|
|
this.html.find('.item-quantite').change(async event => {
|
|
const input = event.currentTarget;
|
|
const item = this.actor.items.get(input.dataset.itemId);
|
|
if (item) {
|
|
const newQty = Math.max(0, parseInt(input.value) || 0);
|
|
await item.update({ 'system.quantite': newQty });
|
|
if (newQty === 0) this.render(true);
|
|
}
|
|
})
|
|
|
|
this.html.find('.item-quantite').on('contextmenu', async event => {
|
|
event.preventDefault()
|
|
const item = this.getItem(event)
|
|
if (!item?.isInventaire()) return
|
|
new Dialog({
|
|
title: `Quantité — ${item.name}`,
|
|
content: `<p>Réduire la quantité de :</p>
|
|
<select class="qty-amount">
|
|
<option value="1">1</option>
|
|
<option value="5">5</option>
|
|
<option value="10">10</option>
|
|
<option value="all">Tout jeter</option>
|
|
</select>`,
|
|
buttons: {
|
|
reduce: {
|
|
icon: '<i class="fas fa-minus"></i>',
|
|
label: 'Retirer',
|
|
callback: html => {
|
|
const amount = html.find('.qty-amount').val()
|
|
if (amount === 'all') {
|
|
item.delete()
|
|
this.render(true)
|
|
} else {
|
|
item.update({ 'system.quantite': Math.max(0, item.system.quantite - parseInt(amount)) })
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}).render(true)
|
|
})
|
|
|
|
this.html.find('.creer-un-objet').click(async event => await this.selectObjetTypeToCreate())
|
|
this.html.find('.nettoyer-conteneurs').click(async event => await this.actor.nettoyerConteneurs())
|
|
this.html.find('.quick-add-ration').click(async event => {
|
|
const existante = this.actor.items.find(it => it.type === "nourritureboisson" && it.name === "Ration")
|
|
if (existante) {
|
|
await existante.update({ "system.quantite": Math.max(0, Number(existante.system.quantite) + 1) })
|
|
} else {
|
|
await this.actor.createEmbeddedDocuments('Item', [{
|
|
name: "Ration", type: "nourritureboisson",
|
|
img: "systems/foundryvtt-reve-de-dragon/icons/objets/provision_crue.webp",
|
|
system: { sust: 1, encombrement: 0.1, quantite: 1, cout: 0.1, boisson: false }
|
|
}], { renderSheet: false })
|
|
}
|
|
this.render(true)
|
|
})
|
|
this.html.find('.quick-add-eau').click(async event => {
|
|
const existante = this.actor.items.find(it => it.type === "nourritureboisson" && it.name === "Eau")
|
|
if (existante) {
|
|
await existante.update({ "system.quantite": Math.max(0, Number(existante.system.quantite) + 1) })
|
|
} else {
|
|
await this.actor.createEmbeddedDocuments('Item', [{
|
|
name: "Eau", type: "nourritureboisson",
|
|
img: "systems/foundryvtt-reve-de-dragon/icons/objets/outre.webp",
|
|
system: { desaltere: 1, encombrement: 0.1, quantite: 1, cout: 0.05, boisson: true }
|
|
}], { renderSheet: false })
|
|
}
|
|
this.render(true)
|
|
})
|
|
|
|
this.html.find('.vue-detaillee').click(async event => {
|
|
this.options.vueDetaillee = !this.options.vueDetaillee
|
|
this.render(true)
|
|
});
|
|
}
|
|
|
|
itemActionEdit(event) {
|
|
const item = this.getItem(event)
|
|
if (item) item.sheet.render(true)
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _onVerifyIngredients(event) {
|
|
event.preventDefault()
|
|
const item = this.getItem(event)
|
|
if (!item || item.type !== ITEM_TYPES.recettealchimique) return
|
|
DialogVerifierIngredients.create(item, this.actor)
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _onLancerRecette(event) {
|
|
event.preventDefault()
|
|
const item = this.getItem(event)
|
|
if (!item || item.type !== ITEM_TYPES.recettealchimique) return
|
|
const ingredients = Misc.safeJsonParse(item.system.ingredients, [])
|
|
if (!ingredients.length) {
|
|
ui.notifications.warn('Cette recette n\'a pas d\'ingrédients définis.')
|
|
return
|
|
}
|
|
DialogLancerRecette.create(item, this.actor)
|
|
}
|
|
|
|
_rechercherKeyup(event) {
|
|
const currentTarget = event.currentTarget;
|
|
const nouvelleRecherche = this._optionRecherche(currentTarget);
|
|
if (this.options.recherche?.text != nouvelleRecherche?.text) {
|
|
this.options.recherche = nouvelleRecherche;
|
|
if (this.timerRecherche) {
|
|
clearTimeout(this.timerRecherche);
|
|
}
|
|
this.timerRecherche = setTimeout(() => {
|
|
this.timerRecherche = undefined;
|
|
this.render(true);
|
|
}, 500);
|
|
}
|
|
}
|
|
|
|
_rechercheSelectArea(field) {
|
|
if (this.options.recherche) {
|
|
field.focus();
|
|
field.setSelectionRange(this.options.recherche.start, this.options.recherche.end);
|
|
}
|
|
}
|
|
|
|
getItemId(event) {
|
|
return RdDSheetUtility.getItemId(event);
|
|
}
|
|
|
|
getItem(event) {
|
|
return RdDSheetUtility.getItem(event, this.actor);
|
|
}
|
|
|
|
_optionRecherche(target) {
|
|
if (!target.value?.length) {
|
|
return undefined;
|
|
}
|
|
return {
|
|
text: target.value,
|
|
start: target.selectionStart,
|
|
end: target.selectionEnd,
|
|
};
|
|
}
|
|
/* -------------------------------------------- */
|
|
_getHeaderButtons() {
|
|
let buttons = super._getHeaderButtons();
|
|
buttons.unshift({
|
|
class: "montrer",
|
|
icon: "fas fa-comment",
|
|
onclick: ev => this.actor.postActorToChat()
|
|
});
|
|
return buttons
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _onDropItem(event, dragData) {
|
|
const destItemId = this.html.find(event.target)?.closest('.item').attr('data-item-id')
|
|
const previousItemIds = new Set(this.actor.items.keys())
|
|
const dropParams = await RdDSheetUtility.prepareItemDropParameters(destItemId, this.actor, dragData, this.objetVersConteneur)
|
|
if (dropParams) {
|
|
const callSuper = await this.actor.processDropItem(dropParams)
|
|
if (callSuper) {
|
|
await super._onDropItem(event, dragData)
|
|
}
|
|
// For external drops onto a container row, place the new item inside
|
|
if (destItemId && dropParams.sourceActorId !== this.actor.id) {
|
|
const container = this.actor.items.get(destItemId)
|
|
if (container?.isConteneur()) {
|
|
const dropped = [...this.actor.items].find(i => !previousItemIds.has(i.id))
|
|
if (dropped && !this.actor.findConteneur(dropped)) {
|
|
await this.actor.ajouterDansConteneur(dropped, container, () => {})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------- */
|
|
async selectObjetTypeToCreate() {
|
|
let types = this.getTypesInventaire().sort(Misc.ascending(type => Misc.typeName('Item', type)));
|
|
let content = `<span class="generic-label">Selectionnez le type d'équipement</span><select class="item-type">`;
|
|
for (let typeName of types) {
|
|
content += `<option value="${typeName}">${Misc.typeName('Item', typeName)}</option>`
|
|
}
|
|
content += '</select>';
|
|
let d = new Dialog({
|
|
title: "Créer un équipement",
|
|
content: content,
|
|
buttons: {
|
|
create: {
|
|
icon: '<i class="fas fa-check"></i>',
|
|
label: "Créer l'objet",
|
|
callback: () => this.actor.createItem($(".item-type").val())
|
|
}
|
|
}
|
|
});
|
|
d.render(true);
|
|
}
|
|
|
|
getTypesInventaire() {
|
|
return RdDItem.getItemTypesInventaire();
|
|
}
|
|
|
|
/** @override */
|
|
setPosition(options = {}) {
|
|
const position = super.setPosition(options);
|
|
const sheetHeader = this.element.find(".sheet-header");
|
|
const sheetTabs = this.element.find(".sheet-tabs");
|
|
const sheetBody = this.element.find(".sheet-body");
|
|
let bodyHeight = position.height - sheetHeader[0].clientHeight;
|
|
if (sheetTabs.length > 0) {
|
|
bodyHeight -= sheetTabs[0].clientHeight;
|
|
}
|
|
sheetBody.css("height", bodyHeight);
|
|
return position;
|
|
}
|
|
|
|
async splitItem(item) {
|
|
const dialog = await DialogSplitItem.create(item, (item, split) => this._onSplitItem(item, split));
|
|
dialog.render(true);
|
|
}
|
|
|
|
async _onSplitItem(item, split) {
|
|
if (split >= 1 && split < item.system.quantite) {
|
|
await item.diminuerQuantite(split);
|
|
const splitItem = foundry.utils.duplicate(item);
|
|
splitItem.system.quantite = split;
|
|
await this.actor.createEmbeddedDocuments('Item', [splitItem])
|
|
}
|
|
}
|
|
|
|
vendre(item) {
|
|
item?.proposerVente(this.actor.getQuantiteDisponible(item));
|
|
}
|
|
|
|
}
|