676 lines
18 KiB
JavaScript
676 lines
18 KiB
JavaScript
const { HandlebarsApplicationMixin } = foundry.applications.api
|
|
|
|
import { HeritiersUtility } from "../../heritiers-utility.js"
|
|
|
|
export default class HeritiersActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) {
|
|
/**
|
|
* Different sheet modes.
|
|
* @enum {number}
|
|
*/
|
|
static SHEET_MODES = { EDIT: 0, PLAY: 1 }
|
|
|
|
constructor(options = {}) {
|
|
super(options)
|
|
this.#dragDrop = this.#createDragDropHandlers()
|
|
this._sheetMode = this.constructor.SHEET_MODES.PLAY
|
|
}
|
|
|
|
#dragDrop
|
|
|
|
/** @override */
|
|
static DEFAULT_OPTIONS = {
|
|
classes: ["fvtt-les-heritiers", "sheet", "actor"],
|
|
position: {
|
|
width: 780,
|
|
height: 840,
|
|
},
|
|
window: {
|
|
resizable: true,
|
|
},
|
|
form: {
|
|
submitOnChange: true,
|
|
closeOnSubmit: false,
|
|
},
|
|
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: "form" }],
|
|
actions: {
|
|
editImage: HeritiersActorSheet.#onEditImage,
|
|
toggleSheet: HeritiersActorSheet.#onToggleSheet,
|
|
editItem: HeritiersActorSheet.#onEditItem,
|
|
deleteItem: HeritiersActorSheet.#onDeleteItem,
|
|
createItem: HeritiersActorSheet.#onCreateItem,
|
|
equipItem: HeritiersActorSheet.#onEquipItem,
|
|
modifyQuantity: HeritiersActorSheet.#onModifyQuantity,
|
|
quantityIncrease: HeritiersActorSheet.#onQuantityIncrease,
|
|
quantityDecrease: HeritiersActorSheet.#onQuantityDecrease,
|
|
pvIncrease: HeritiersActorSheet.#onPvIncrease,
|
|
pvDecrease: HeritiersActorSheet.#onPvDecrease,
|
|
rollInitiative: HeritiersActorSheet.#onRollInitiative,
|
|
rollCarac: HeritiersActorSheet.#onRollCarac,
|
|
rollRang: HeritiersActorSheet.#onRollRang,
|
|
rollRootCompetence: HeritiersActorSheet.#onRollRootCompetence,
|
|
rollCompetence: HeritiersActorSheet.#onRollCompetence,
|
|
rollSort: HeritiersActorSheet.#onRollSort,
|
|
rollAttaqueArme: HeritiersActorSheet.#onRollAttaqueArme,
|
|
rollAttaqueBrutaleArme: HeritiersActorSheet.#onRollAttaqueBrutaleArme,
|
|
rollAttaqueChargeArme: HeritiersActorSheet.#onRollAttaqueChargeArme,
|
|
rollAssomerArme: HeritiersActorSheet.#onRollAssomerArme,
|
|
rollPouvoir: HeritiersActorSheet.#onRollPouvoir,
|
|
toggleMasque: HeritiersActorSheet.#onToggleMasque,
|
|
dialogRecupUsage: HeritiersActorSheet.#onDialogRecupUsage,
|
|
},
|
|
}
|
|
|
|
/**
|
|
* Is the sheet currently in 'Play' mode?
|
|
* @type {boolean}
|
|
*/
|
|
get isPlayMode() {
|
|
if (this._sheetMode === undefined) this._sheetMode = this.constructor.SHEET_MODES.PLAY
|
|
return this._sheetMode === this.constructor.SHEET_MODES.PLAY
|
|
}
|
|
|
|
/**
|
|
* Is the sheet currently in 'Edit' mode?
|
|
* @type {boolean}
|
|
*/
|
|
get isEditMode() {
|
|
if (this._sheetMode === undefined) this._sheetMode = this.constructor.SHEET_MODES.PLAY
|
|
return this._sheetMode === this.constructor.SHEET_MODES.EDIT
|
|
}
|
|
|
|
/**
|
|
* Tab groups state
|
|
* @type {object}
|
|
*/
|
|
tabGroups = { primary: "competences" }
|
|
|
|
/** @override */
|
|
async _prepareContext() {
|
|
const actor = this.document
|
|
|
|
const context = {
|
|
actor: actor,
|
|
system: actor.system,
|
|
source: actor.toObject(),
|
|
fields: actor.schema.fields,
|
|
systemFields: actor.system.schema.fields,
|
|
isEditable: this.isEditable,
|
|
isEditMode: this.isEditMode,
|
|
isPlayMode: this.isPlayMode,
|
|
isGM: game.user.isGM,
|
|
config: CONFIG.HERITIERS,
|
|
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.description || "", { async: true }),
|
|
enrichedHabitat: await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.habitat || "", { async: true }),
|
|
enrichedRevesetranges: await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.revesetranges || "", { async: true }),
|
|
enrichedSecretsdecouverts: await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.secretsdecouverts || "", { async: true }),
|
|
enrichedQuestions: await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.questions || "", { async: true }),
|
|
enrichedPlayernotes: await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.playernotes || "", { async: true }),
|
|
}
|
|
|
|
return context
|
|
}
|
|
|
|
/** @override */
|
|
_onRender(context, options) {
|
|
super._onRender(context, options)
|
|
|
|
// Activate drag & drop handlers
|
|
this.#dragDrop.forEach(d => d.bind(this.element))
|
|
|
|
// Manual tab navigation
|
|
const html = this.element
|
|
const tabLinks = html.querySelectorAll('.sheet-tabs a.item[data-tab]')
|
|
const tabContents = html.querySelectorAll('.sheet-body .tab[data-group="primary"]')
|
|
|
|
// Hide all tabs initially
|
|
tabContents.forEach(tab => {
|
|
tab.classList.remove('active')
|
|
tab.style.display = 'none'
|
|
})
|
|
|
|
// Show active tab
|
|
const activeTab = this.tabGroups.primary
|
|
const activeTabContent = html.querySelector(`.tab[data-group="primary"][data-tab="${activeTab}"]`)
|
|
if (activeTabContent) {
|
|
activeTabContent.classList.add('active')
|
|
activeTabContent.style.display = 'block'
|
|
}
|
|
|
|
// Activate the corresponding nav link
|
|
tabLinks.forEach(link => {
|
|
if (link.dataset.tab === activeTab) {
|
|
link.classList.add('active')
|
|
} else {
|
|
link.classList.remove('active')
|
|
}
|
|
})
|
|
|
|
// Tab click handler
|
|
tabLinks.forEach(link => {
|
|
link.addEventListener('click', (event) => {
|
|
event.preventDefault()
|
|
const tab = link.dataset.tab
|
|
|
|
// Update state
|
|
this.tabGroups.primary = tab
|
|
|
|
// Hide all tabs
|
|
tabContents.forEach(t => {
|
|
t.classList.remove('active')
|
|
t.style.display = 'none'
|
|
})
|
|
|
|
// Show selected tab
|
|
const selectedTab = html.querySelector(`.tab[data-group="primary"][data-tab="${tab}"]`)
|
|
if (selectedTab) {
|
|
selectedTab.classList.add('active')
|
|
selectedTab.style.display = 'block'
|
|
}
|
|
|
|
// Update nav links
|
|
tabLinks.forEach(l => {
|
|
if (l.dataset.tab === tab) {
|
|
l.classList.add('active')
|
|
} else {
|
|
l.classList.remove('active')
|
|
}
|
|
})
|
|
})
|
|
})
|
|
|
|
// Inline item editing
|
|
html.querySelectorAll('.edit-item-data').forEach(input => {
|
|
input.addEventListener('change', (event) => {
|
|
const li = event.target.closest('.item')
|
|
const itemId = li?.dataset.itemId
|
|
const itemType = li?.dataset.itemType
|
|
const itemField = event.target.dataset.itemField
|
|
const dataType = event.target.dataset.dtype
|
|
const value = event.target.value
|
|
if (itemId && itemType && itemField) {
|
|
this.actor.editItemField(itemId, itemType, itemField, dataType, value)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
// #region Drag & Drop
|
|
|
|
/**
|
|
* Create drag-and-drop workflow handlers for this Application
|
|
* @returns {DragDrop[]} An array of DragDrop handlers
|
|
* @private
|
|
*/
|
|
#createDragDropHandlers() {
|
|
return this.options.dragDrop.map((d) => {
|
|
d.permissions = {
|
|
dragstart: this._canDragStart.bind(this),
|
|
drop: this._canDragDrop.bind(this),
|
|
}
|
|
d.callbacks = {
|
|
dragstart: this._onDragStart.bind(this),
|
|
drop: this._onDrop.bind(this),
|
|
}
|
|
return new foundry.applications.ux.DragDrop(d)
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Define whether a user is able to begin a dragstart workflow for a given drag selector
|
|
* @param {string} selector The candidate HTML selector for dragging
|
|
* @returns {boolean} Can the current user drag this selector?
|
|
* @protected
|
|
*/
|
|
_canDragStart(selector) {
|
|
return this.isEditable
|
|
}
|
|
|
|
/**
|
|
* Define whether a user is able to conclude a drag-and-drop workflow for a given drop selector
|
|
* @param {string} selector The candidate HTML selector for the drop target
|
|
* @returns {boolean} Can the current user drop on this selector?
|
|
* @protected
|
|
*/
|
|
_canDragDrop(selector) {
|
|
return this.isEditable
|
|
}
|
|
|
|
/**
|
|
* Callback actions which occur at the beginning of a drag start workflow.
|
|
* @param {DragEvent} event The originating DragEvent
|
|
* @protected
|
|
*/
|
|
_onDragStart(event) {
|
|
const li = event.currentTarget.closest(".item")
|
|
if (!li?.dataset.itemId) return
|
|
const item = this.actor.items.get(li.dataset.itemId)
|
|
if (!item) return
|
|
|
|
const dragData = item.toDragData()
|
|
event.dataTransfer.setData("text/plain", JSON.stringify(dragData))
|
|
}
|
|
|
|
/**
|
|
* Callback actions which occur when a dragged element is dropped on a target.
|
|
* @param {DragEvent} event The originating DragEvent
|
|
* @protected
|
|
*/
|
|
async _onDrop(event) {
|
|
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event)
|
|
const actor = this.actor
|
|
|
|
// Handle different data types
|
|
switch (data.type) {
|
|
case "Item":
|
|
return this._onDropItem(event, data)
|
|
case "Actor":
|
|
return this._onDropActor(event, data)
|
|
case "ActiveEffect":
|
|
return this._onDropActiveEffect(event, data)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle dropping an Item on the actor sheet
|
|
* @param {DragEvent} event
|
|
* @param {object} data
|
|
* @private
|
|
*/
|
|
async _onDropItem(event, data) {
|
|
if (!this.actor.isOwner) return false
|
|
|
|
let item = await fromUuid(data.uuid)
|
|
if (item.pack) {
|
|
item = await HeritiersUtility.searchItem(item)
|
|
}
|
|
|
|
const itemData = item.toObject ? item.toObject() : item
|
|
return this.actor.createEmbeddedDocuments("Item", [itemData])
|
|
}
|
|
|
|
/**
|
|
* Handle dropping an Actor on the sheet
|
|
* @param {DragEvent} event
|
|
* @param {object} data
|
|
* @private
|
|
*/
|
|
async _onDropActor(event, data) {
|
|
return false
|
|
}
|
|
|
|
/**
|
|
* Handle dropping an ActiveEffect on the sheet
|
|
* @param {DragEvent} event
|
|
* @param {object} data
|
|
* @private
|
|
*/
|
|
async _onDropActiveEffect(event, data) {
|
|
return false
|
|
}
|
|
|
|
// #endregion
|
|
|
|
// #region Action Handlers
|
|
|
|
/**
|
|
* Toggle between edit and play mode
|
|
* @param {Event} event
|
|
* @param {HTMLElement} target
|
|
* @private
|
|
*/
|
|
static #onToggleSheet(event, target) {
|
|
const wasEditMode = this.isEditMode
|
|
this._sheetMode = wasEditMode ? this.constructor.SHEET_MODES.PLAY : this.constructor.SHEET_MODES.EDIT
|
|
this.render({ force: true })
|
|
}
|
|
|
|
/**
|
|
* Edit the actor image
|
|
* @param {Event} event
|
|
* @param {HTMLElement} target
|
|
* @private
|
|
*/
|
|
static async #onEditImage(event, target) {
|
|
const fp = new FilePicker({
|
|
type: "image",
|
|
current: this.actor.img,
|
|
callback: (path) => {
|
|
this.actor.update({ img: path })
|
|
},
|
|
})
|
|
return fp.browse()
|
|
}
|
|
|
|
/**
|
|
* Edit an item
|
|
* @param {Event} event
|
|
* @param {HTMLElement} target
|
|
* @private
|
|
*/
|
|
static async #onEditItem(event, target) {
|
|
const li = target.closest(".item")
|
|
const itemId = li?.dataset.itemId
|
|
if (!itemId) return
|
|
const item = this.actor.items.get(itemId)
|
|
if (item) item.sheet.render(true)
|
|
}
|
|
|
|
/**
|
|
* Delete an item
|
|
* @param {Event} event
|
|
* @param {HTMLElement} target
|
|
* @private
|
|
*/
|
|
static async #onDeleteItem(event, target) {
|
|
const li = target.closest(".item")
|
|
await HeritiersUtility.confirmDelete(this, li)
|
|
}
|
|
|
|
/**
|
|
* Create a new item
|
|
* @param {Event} event
|
|
* @param {HTMLElement} target
|
|
* @private
|
|
*/
|
|
static async #onCreateItem(event, target) {
|
|
const itemType = target.dataset.type
|
|
|
|
// Cas spécial pour les sorts avec une compétence spécifique
|
|
if (itemType === "sort" && target.dataset.sortCompetence) {
|
|
const sortCompetence = target.dataset.sortCompetence
|
|
await this.actor.createEmbeddedDocuments('Item', [{
|
|
name: `Nouveau ${itemType} de ${sortCompetence}`,
|
|
type: itemType,
|
|
system: { competence: sortCompetence }
|
|
}], { renderSheet: true })
|
|
return
|
|
}
|
|
|
|
await this.actor.createEmbeddedDocuments("Item", [{ name: `Nouveau ${itemType}`, type: itemType }], { renderSheet: true })
|
|
}
|
|
|
|
/**
|
|
* Equip/unequip an item
|
|
* @param {Event} event
|
|
* @param {HTMLElement} target
|
|
* @private
|
|
*/
|
|
static async #onEquipItem(event, target) {
|
|
const li = target.closest(".item")
|
|
const itemId = li?.dataset.itemId
|
|
if (itemId) {
|
|
await this.actor.equipItem(itemId)
|
|
this.render()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Modify item quantity
|
|
* @param {Event} event
|
|
* @param {HTMLElement} target
|
|
* @private
|
|
*/
|
|
static async #onModifyQuantity(event, target) {
|
|
const li = target.closest(".item")
|
|
const itemId = li?.dataset.itemId
|
|
const value = Number(target.dataset.quantiteValue)
|
|
if (itemId) {
|
|
await this.actor.incDecQuantity(itemId, value)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Increase item quantity
|
|
* @param {Event} event
|
|
* @param {HTMLElement} target
|
|
* @private
|
|
*/
|
|
static async #onQuantityIncrease(event, target) {
|
|
const li = target.closest(".item")
|
|
const itemId = li?.dataset.itemId
|
|
if (itemId) {
|
|
await this.actor.incDecQuantity(itemId, 1)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Decrease item quantity
|
|
* @param {Event} event
|
|
* @param {HTMLElement} target
|
|
* @private
|
|
*/
|
|
static async #onQuantityDecrease(event, target) {
|
|
const li = target.closest(".item")
|
|
const itemId = li?.dataset.itemId
|
|
if (itemId) {
|
|
await this.actor.incDecQuantity(itemId, -1)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Increase PV
|
|
* @param {Event} event
|
|
* @param {HTMLElement} target
|
|
* @private
|
|
*/
|
|
static async #onPvIncrease(event, target) {
|
|
const currentPv = this.actor.system.pv.value || 0
|
|
const maxPv = this.actor.system.pv.max || 0
|
|
const newPv = Math.min(currentPv + 1, maxPv)
|
|
await this.actor.update({ 'system.pv.value': newPv })
|
|
}
|
|
|
|
/**
|
|
* Decrease PV
|
|
* @param {Event} event
|
|
* @param {HTMLElement} target
|
|
* @private
|
|
*/
|
|
static async #onPvDecrease(event, target) {
|
|
const currentPv = this.actor.system.pv.value || 0
|
|
const newPv = Math.max(currentPv - 1, 0)
|
|
await this.actor.update({ 'system.pv.value': newPv })
|
|
}
|
|
|
|
/**
|
|
* Roll initiative
|
|
* @param {Event} event
|
|
* @param {HTMLElement} target
|
|
* @private
|
|
*/
|
|
static async #onRollInitiative(event, target) {
|
|
await this.actor.rollInitiative()
|
|
}
|
|
|
|
/**
|
|
* Roll caractéristique
|
|
* @param {Event} event
|
|
* @param {HTMLElement} target
|
|
* @private
|
|
*/
|
|
static async #onRollCarac(event, target) {
|
|
const key = target.dataset.key
|
|
if (key) {
|
|
await this.actor.rollCarac(key, false)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Roll rang
|
|
* @param {Event} event
|
|
* @param {HTMLElement} target
|
|
* @private
|
|
*/
|
|
static async #onRollRang(event, target) {
|
|
const key = target.dataset.rangKey
|
|
if (key) {
|
|
await this.actor.rollRang(key)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Roll root competence
|
|
* @param {Event} event
|
|
* @param {HTMLElement} target
|
|
* @private
|
|
*/
|
|
static async #onRollRootCompetence(event, target) {
|
|
const compKey = target.dataset.attrKey
|
|
if (compKey) {
|
|
await this.actor.rollRootCompetence(compKey)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Roll competence
|
|
* @param {Event} event
|
|
* @param {HTMLElement} target
|
|
* @private
|
|
*/
|
|
static async #onRollCompetence(event, target) {
|
|
const li = target.closest(".item")
|
|
const compId = li?.dataset.itemId
|
|
if (compId) {
|
|
await this.actor.rollCompetence(compId)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Roll sort
|
|
* @param {Event} event
|
|
* @param {HTMLElement} target
|
|
* @private
|
|
*/
|
|
static async #onRollSort(event, target) {
|
|
const li = target.closest(".item")
|
|
const sortId = li?.dataset.itemId
|
|
if (sortId) {
|
|
await this.actor.rollSort(sortId)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Roll attaque arme
|
|
* @param {Event} event
|
|
* @param {HTMLElement} target
|
|
* @private
|
|
*/
|
|
static async #onRollAttaqueArme(event, target) {
|
|
const li = target.closest(".item")
|
|
const armeId = li?.dataset.itemId
|
|
if (armeId) {
|
|
await this.actor.rollAttaqueArme(armeId)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Roll attaque brutale arme
|
|
* @param {Event} event
|
|
* @param {HTMLElement} target
|
|
* @private
|
|
*/
|
|
static async #onRollAttaqueBrutaleArme(event, target) {
|
|
const li = target.closest(".item")
|
|
const armeId = li?.dataset.itemId
|
|
if (armeId) {
|
|
await this.actor.rollAttaqueBrutaleArme(armeId)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Roll attaque charge arme
|
|
* @param {Event} event
|
|
* @param {HTMLElement} target
|
|
* @private
|
|
*/
|
|
static async #onRollAttaqueChargeArme(event, target) {
|
|
const li = target.closest(".item")
|
|
const armeId = li?.dataset.itemId
|
|
if (armeId) {
|
|
await this.actor.rollAttaqueChargeArme(armeId)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Roll assomer arme
|
|
* @param {Event} event
|
|
* @param {HTMLElement} target
|
|
* @private
|
|
*/
|
|
static async #onRollAssomerArme(event, target) {
|
|
const li = target.closest(".item")
|
|
const armeId = li?.dataset.itemId
|
|
if (armeId) {
|
|
await this.actor.rollAssomerArme(armeId)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Roll pouvoir
|
|
* @param {Event} event
|
|
* @param {HTMLElement} target
|
|
* @private
|
|
*/
|
|
static async #onRollPouvoir(event, target) {
|
|
const li = target.closest(".item")
|
|
const pouvoirId = li?.dataset.itemId
|
|
if (pouvoirId) {
|
|
await this.actor.rollPouvoir(pouvoirId)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Toggle masque
|
|
* @param {Event} event
|
|
* @param {HTMLElement} target
|
|
* @private
|
|
*/
|
|
static async #onToggleMasque(event, target) {
|
|
await this.actor.toggleMasqueStatut()
|
|
this.render()
|
|
}
|
|
|
|
/**
|
|
* Dialog récupération usage
|
|
* @param {Event} event
|
|
* @param {HTMLElement} target
|
|
* @private
|
|
*/
|
|
static async #onDialogRecupUsage(event, target) {
|
|
new Dialog({
|
|
title: "Récupération des Points d'Usage",
|
|
content: "<p>Combien de Points d'Usage souhaitez-vous récupérer ?</p>",
|
|
buttons: {
|
|
one: {
|
|
icon: '<i class="fas fa-check"></i>',
|
|
label: "1 Point",
|
|
callback: () => {
|
|
this.actor.recupUsage(1)
|
|
}
|
|
},
|
|
two: {
|
|
icon: '<i class="fas fa-check"></i>',
|
|
label: "2 Points",
|
|
callback: () => {
|
|
this.actor.recupUsage(2)
|
|
}
|
|
},
|
|
three: {
|
|
icon: '<i class="fas fa-check"></i>',
|
|
label: "3 Points",
|
|
callback: () => {
|
|
this.actor.recupUsage(3)
|
|
}
|
|
},
|
|
cancel: {
|
|
icon: '<i class="fas fa-times"></i>',
|
|
label: "Annuler"
|
|
}
|
|
},
|
|
default: "one"
|
|
}).render(true)
|
|
}
|
|
|
|
// #endregion
|
|
}
|