Initial import
This commit is contained in:
27
module/applications/sheets/armor-sheet.mjs
Normal file
27
module/applications/sheets/armor-sheet.mjs
Normal file
@ -0,0 +1,27 @@
|
||||
import LethalFantasyItemSheet from "./base-item-sheet.mjs"
|
||||
|
||||
export default class LethalFantasyArmorSheet extends LethalFantasyItemSheet {
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["armor"],
|
||||
position: {
|
||||
width: 400,
|
||||
},
|
||||
window: {
|
||||
contentClasses: ["armor-content"],
|
||||
},
|
||||
}
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: "systems/fvtt-lethal-fantasy/templates/armor.hbs",
|
||||
},
|
||||
}
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext()
|
||||
return context
|
||||
}
|
||||
}
|
27
module/applications/sheets/attack-sheet.mjs
Normal file
27
module/applications/sheets/attack-sheet.mjs
Normal file
@ -0,0 +1,27 @@
|
||||
import LethalFantasyItemSheet from "./base-item-sheet.mjs"
|
||||
|
||||
export default class LethalFantasyAttackSheet extends LethalFantasyItemSheet {
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["attack"],
|
||||
position: {
|
||||
width: 600,
|
||||
},
|
||||
window: {
|
||||
contentClasses: ["attack-content"],
|
||||
},
|
||||
}
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: "systems/fvtt-lethal-fantasy/templates/attack.hbs",
|
||||
},
|
||||
}
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext()
|
||||
return context
|
||||
}
|
||||
}
|
292
module/applications/sheets/base-actor-sheet.mjs
Normal file
292
module/applications/sheets/base-actor-sheet.mjs
Normal file
@ -0,0 +1,292 @@
|
||||
const { HandlebarsApplicationMixin } = foundry.applications.api
|
||||
|
||||
export default class LethalFantasyActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) {
|
||||
/**
|
||||
* Different sheet modes.r
|
||||
* @enum {number}
|
||||
*/
|
||||
static SHEET_MODES = { EDIT: 0, PLAY: 1 }
|
||||
|
||||
constructor(options = {}) {
|
||||
super(options)
|
||||
this.#dragDrop = this.#createDragDropHandlers()
|
||||
}
|
||||
|
||||
#dragDrop
|
||||
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["tenebris", "actor"],
|
||||
position: {
|
||||
width: 1400,
|
||||
height: "auto",
|
||||
},
|
||||
form: {
|
||||
submitOnChange: true,
|
||||
},
|
||||
window: {
|
||||
resizable: true,
|
||||
},
|
||||
dragDrop: [{ dragSelector: '[data-drag="true"], .rollable', dropSelector: null }],
|
||||
actions: {
|
||||
editImage: LethalFantasyActorSheet.#onEditImage,
|
||||
toggleSheet: LethalFantasyActorSheet.#onToggleSheet,
|
||||
edit: LethalFantasyActorSheet.#onItemEdit,
|
||||
delete: LethalFantasyActorSheet.#onItemDelete,
|
||||
createSpell: LethalFantasyActorSheet.#onCreateSpell,
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* The current sheet mode.
|
||||
* @type {number}
|
||||
*/
|
||||
_sheetMode = this.constructor.SHEET_MODES.PLAY
|
||||
|
||||
/**
|
||||
* Is the sheet currently in 'Play' mode?
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isPlayMode() {
|
||||
return this._sheetMode === this.constructor.SHEET_MODES.PLAY
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the sheet currently in 'Edit' mode?
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isEditMode() {
|
||||
return this._sheetMode === this.constructor.SHEET_MODES.EDIT
|
||||
}
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = {
|
||||
fields: this.document.schema.fields,
|
||||
systemFields: this.document.system.schema.fields,
|
||||
actor: this.document,
|
||||
system: this.document.system,
|
||||
source: this.document.toObject(),
|
||||
enrichedDescription: await TextEditor.enrichHTML(this.document.system.description, { async: true }),
|
||||
isEditMode: this.isEditMode,
|
||||
isPlayMode: this.isPlayMode,
|
||||
isEditable: this.isEditable,
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
/** @override */
|
||||
_onRender(context, options) {
|
||||
this.#dragDrop.forEach((d) => d.bind(this.element))
|
||||
// Add listeners to rollable elements
|
||||
const rollables = this.element.querySelectorAll(".rollable")
|
||||
rollables.forEach((d) => d.addEventListener("click", this._onRoll.bind(this)))
|
||||
}
|
||||
|
||||
// #region Drag-and-Drop Workflow
|
||||
/**
|
||||
* 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),
|
||||
dragover: this._onDragOver.bind(this),
|
||||
drop: this._onDrop.bind(this),
|
||||
}
|
||||
return new DragDrop(d)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback actions which occur when a dragged element is dropped on a target.
|
||||
* @param {DragEvent} event The originating DragEvent
|
||||
* @protected
|
||||
*/
|
||||
async _onDrop(event) {}
|
||||
|
||||
/**
|
||||
* 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 && this.document.isOwner
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback actions which occur at the beginning of a drag start workflow.
|
||||
* @param {DragEvent} event The originating DragEvent
|
||||
* @protected
|
||||
*/
|
||||
_onDragStart(event) {
|
||||
if ("link" in event.target.dataset) return
|
||||
|
||||
const el = event.currentTarget.closest('[data-drag="true"]')
|
||||
const dragType = el.dataset.dragType
|
||||
|
||||
let dragData = {}
|
||||
|
||||
let target
|
||||
switch (dragType) {
|
||||
case "save":
|
||||
target = event.currentTarget.querySelector("input")
|
||||
dragData = {
|
||||
actorId: this.document.id,
|
||||
type: "roll",
|
||||
rollType: target.dataset.rollType,
|
||||
rollTarget: target.dataset.rollTarget,
|
||||
value: target.value,
|
||||
}
|
||||
break
|
||||
case "resource":
|
||||
target = event.currentTarget.querySelector("select")
|
||||
dragData = {
|
||||
actorId: this.document.id,
|
||||
type: "roll",
|
||||
rollType: target.dataset.rollType,
|
||||
rollTarget: target.dataset.rollTarget,
|
||||
value: target.value,
|
||||
}
|
||||
break
|
||||
case "damage":
|
||||
dragData = {
|
||||
actorId: this.document.id,
|
||||
type: "rollDamage",
|
||||
rollType: el.dataset.dragType,
|
||||
rollTarget: el.dataset.itemId,
|
||||
}
|
||||
break
|
||||
case "attack":
|
||||
dragData = {
|
||||
actorId: this.document.id,
|
||||
type: "rollAttack",
|
||||
rollValue: el.dataset.rollValue,
|
||||
rollTarget: el.dataset.rollTarget,
|
||||
}
|
||||
break
|
||||
default:
|
||||
// Handle other cases or do nothing
|
||||
break
|
||||
}
|
||||
|
||||
// Extract the data you need
|
||||
|
||||
if (!dragData) return
|
||||
|
||||
// Set data transfer
|
||||
event.dataTransfer.setData("text/plain", JSON.stringify(dragData))
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback actions which occur when a dragged element is over a drop target.
|
||||
* @param {DragEvent} event The originating DragEvent
|
||||
* @protected
|
||||
*/
|
||||
_onDragOver(event) {}
|
||||
|
||||
async _onDropItem(item) {
|
||||
let itemData = item.toObject()
|
||||
await this.document.createEmbeddedDocuments("Item", [itemData], { renderSheet: false })
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Actions
|
||||
/**
|
||||
* Handle toggling between Edit and Play mode.
|
||||
* @param {Event} event The initiating click event.
|
||||
* @param {HTMLElement} target The current target of the event listener.
|
||||
*/
|
||||
static #onToggleSheet(event, target) {
|
||||
const modes = this.constructor.SHEET_MODES
|
||||
this._sheetMode = this.isEditMode ? modes.PLAY : modes.EDIT
|
||||
this.render()
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle changing a Document's image.
|
||||
*
|
||||
* @this LethalFantasyCharacterSheet
|
||||
* @param {PointerEvent} event The originating click event
|
||||
* @param {HTMLElement} target The capturing HTML element which defined a [data-action]
|
||||
* @returns {Promise}
|
||||
* @private
|
||||
*/
|
||||
static async #onEditImage(event, target) {
|
||||
const attr = target.dataset.edit
|
||||
const current = foundry.utils.getProperty(this.document, attr)
|
||||
const { img } = this.document.constructor.getDefaultArtwork?.(this.document.toObject()) ?? {}
|
||||
const fp = new FilePicker({
|
||||
current,
|
||||
type: "image",
|
||||
redirectToRoot: img ? [img] : [],
|
||||
callback: (path) => {
|
||||
this.document.update({ [attr]: path })
|
||||
},
|
||||
top: this.position.top + 40,
|
||||
left: this.position.left + 10,
|
||||
})
|
||||
return fp.browse()
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit an existing item within the Actor
|
||||
* Start with the uuid, if it's not found, fallback to the id (as Embedded item in the actor)
|
||||
* @this LethalFantasyCharacterSheet
|
||||
* @param {PointerEvent} event The originating click event
|
||||
* @param {HTMLElement} target the capturing HTML element which defined a [data-action]
|
||||
*/
|
||||
static async #onItemEdit(event, target) {
|
||||
const id = target.getAttribute("data-item-id")
|
||||
const uuid = target.getAttribute("data-item-uuid")
|
||||
let item
|
||||
item = await fromUuid(uuid)
|
||||
if (!item) item = this.document.items.get(id)
|
||||
if (!item) return
|
||||
item.sheet.render(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an existing talent within the Actor
|
||||
* Use the uuid to display the talent sheet
|
||||
* @param {PointerEvent} event The originating click event
|
||||
* @param {HTMLElement} target the capturing HTML element which defined a [data-action]
|
||||
*/
|
||||
static async #onItemDelete(event, target) {
|
||||
const itemUuid = target.getAttribute("data-item-uuid")
|
||||
const talent = await fromUuid(itemUuid)
|
||||
await talent.deleteDialog()
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the creation of a new attack item.
|
||||
*
|
||||
* @param {Event} event The event that triggered the creation of the attack.
|
||||
* @param {Object} target The target object where the attack will be created.
|
||||
* @private
|
||||
* @static
|
||||
*/
|
||||
static #onCreateSpell(event, target) {
|
||||
const item = this.document.createEmbeddedDocuments("Item", [{ name: "Nouveau sortilège", type: "spell" }])
|
||||
}
|
||||
|
||||
// #endregion
|
||||
}
|
193
module/applications/sheets/base-item-sheet.mjs
Normal file
193
module/applications/sheets/base-item-sheet.mjs
Normal file
@ -0,0 +1,193 @@
|
||||
const { HandlebarsApplicationMixin } = foundry.applications.api
|
||||
|
||||
export default class LethalFantasyItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
|
||||
/**
|
||||
* Different sheet modes.
|
||||
* @enum {number}
|
||||
*/
|
||||
static SHEET_MODES = { EDIT: 0, PLAY: 1 }
|
||||
|
||||
constructor(options = {}) {
|
||||
super(options)
|
||||
this.#dragDrop = this.#createDragDropHandlers()
|
||||
}
|
||||
|
||||
#dragDrop
|
||||
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["tenebris", "item"],
|
||||
position: {
|
||||
width: 600,
|
||||
height: "auto",
|
||||
},
|
||||
form: {
|
||||
submitOnChange: true,
|
||||
},
|
||||
window: {
|
||||
resizable: true,
|
||||
},
|
||||
dragDrop: [{ dragSelector: "[data-drag]", dropSelector: null }],
|
||||
actions: {
|
||||
toggleSheet: LethalFantasyItemSheet.#onToggleSheet,
|
||||
editImage: LethalFantasyItemSheet.#onEditImage,
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* The current sheet mode.
|
||||
* @type {number}
|
||||
*/
|
||||
_sheetMode = this.constructor.SHEET_MODES.PLAY
|
||||
|
||||
/**
|
||||
* Is the sheet currently in 'Play' mode?
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isPlayMode() {
|
||||
return this._sheetMode === this.constructor.SHEET_MODES.PLAY
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the sheet currently in 'Edit' mode?
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isEditMode() {
|
||||
return this._sheetMode === this.constructor.SHEET_MODES.EDIT
|
||||
}
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = {
|
||||
fields: this.document.schema.fields,
|
||||
systemFields: this.document.system.schema.fields,
|
||||
item: this.document,
|
||||
system: this.document.system,
|
||||
source: this.document.toObject(),
|
||||
enrichedDescription: await TextEditor.enrichHTML(this.document.system.description, { async: true }),
|
||||
isEditMode: this.isEditMode,
|
||||
isPlayMode: this.isPlayMode,
|
||||
isEditable: this.isEditable,
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
/** @override */
|
||||
_onRender(context, options) {
|
||||
this.#dragDrop.forEach((d) => d.bind(this.element))
|
||||
}
|
||||
|
||||
// #region Drag-and-Drop Workflow
|
||||
/**
|
||||
* 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),
|
||||
dragover: this._onDragOver.bind(this),
|
||||
drop: this._onDrop.bind(this),
|
||||
}
|
||||
return new 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 && this.document.isOwner
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback actions which occur at the beginning of a drag start workflow.
|
||||
* @param {DragEvent} event The originating DragEvent
|
||||
* @protected
|
||||
*/
|
||||
_onDragStart(event) {
|
||||
const el = event.currentTarget
|
||||
if ("link" in event.target.dataset) return
|
||||
|
||||
// Extract the data you need
|
||||
let dragData = null
|
||||
|
||||
if (!dragData) return
|
||||
|
||||
// Set data transfer
|
||||
event.dataTransfer.setData("text/plain", JSON.stringify(dragData))
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback actions which occur when a dragged element is over a drop target.
|
||||
* @param {DragEvent} event The originating DragEvent
|
||||
* @protected
|
||||
*/
|
||||
_onDragOver(event) {}
|
||||
|
||||
/**
|
||||
* Callback actions which occur when a dragged element is dropped on a target.
|
||||
* @param {DragEvent} event The originating DragEvent
|
||||
* @protected
|
||||
*/
|
||||
async _onDrop(event) {}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Actions
|
||||
/**
|
||||
* Handle toggling between Edit and Play mode.
|
||||
* @param {Event} event The initiating click event.
|
||||
* @param {HTMLElement} target The current target of the event listener.
|
||||
*/
|
||||
static #onToggleSheet(event, target) {
|
||||
const modes = this.constructor.SHEET_MODES
|
||||
this._sheetMode = this.isEditMode ? modes.PLAY : modes.EDIT
|
||||
this.render()
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle changing a Document's image.
|
||||
*
|
||||
* @this LethalFantasyCharacterSheet
|
||||
* @param {PointerEvent} event The originating click event
|
||||
* @param {HTMLElement} target The capturing HTML element which defined a [data-action]
|
||||
* @returns {Promise}
|
||||
* @private
|
||||
*/
|
||||
static async #onEditImage(event, target) {
|
||||
const attr = target.dataset.edit
|
||||
const current = foundry.utils.getProperty(this.document, attr)
|
||||
const { img } = this.document.constructor.getDefaultArtwork?.(this.document.toObject()) ?? {}
|
||||
const fp = new FilePicker({
|
||||
current,
|
||||
type: "image",
|
||||
redirectToRoot: img ? [img] : [],
|
||||
callback: (path) => {
|
||||
this.document.update({ [attr]: path })
|
||||
},
|
||||
top: this.position.top + 40,
|
||||
left: this.position.left + 10,
|
||||
})
|
||||
return fp.browse()
|
||||
}
|
||||
// #endregion
|
||||
}
|
341
module/applications/sheets/character-sheet.mjs
Normal file
341
module/applications/sheets/character-sheet.mjs
Normal file
@ -0,0 +1,341 @@
|
||||
import LethalFantasyActorSheet from "./base-actor-sheet.mjs"
|
||||
import { ROLL_TYPE } from "../../config/system.mjs"
|
||||
|
||||
export default class LethalFantasyCharacterSheet extends LethalFantasyActorSheet {
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["character"],
|
||||
position: {
|
||||
width: 1150,
|
||||
height: 780,
|
||||
},
|
||||
window: {
|
||||
contentClasses: ["character-content"],
|
||||
},
|
||||
actions: {
|
||||
deleteVoieMajeure: LethalFantasyCharacterSheet.#onDeleteVoieMajeure,
|
||||
deleteVoieMineure: LethalFantasyCharacterSheet.#onDeleteVoieMineure,
|
||||
createEquipment: LethalFantasyCharacterSheet.#onCreateEquipment,
|
||||
},
|
||||
}
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: "systems/fvtt-lethal-fantasy/templates/character-main.hbs",
|
||||
},
|
||||
tabs: {
|
||||
template: "templates/generic/tab-navigation.hbs",
|
||||
},
|
||||
items: {
|
||||
template: "systems/fvtt-lethal-fantasy/templates/character-items.hbs",
|
||||
},
|
||||
biography: {
|
||||
template: "systems/fvtt-lethal-fantasy/templates/character-biography.hbs",
|
||||
},
|
||||
}
|
||||
|
||||
/** @override */
|
||||
tabGroups = {
|
||||
sheet: "items",
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare an array of form header tabs.
|
||||
* @returns {Record<string, Partial<ApplicationTab>>}
|
||||
*/
|
||||
#getTabs() {
|
||||
const tabs = {
|
||||
items: { id: "items", group: "sheet", icon: "fa-solid fa-shapes", label: "TENEBRIS.Character.Label.details" },
|
||||
biography: { id: "biography", group: "sheet", icon: "fa-solid fa-book", label: "TENEBRIS.Character.Label.biography" },
|
||||
}
|
||||
for (const v of Object.values(tabs)) {
|
||||
v.active = this.tabGroups[v.group] === v.id
|
||||
v.cssClass = v.active ? "active" : ""
|
||||
}
|
||||
return tabs
|
||||
}
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext()
|
||||
context.tabs = this.#getTabs()
|
||||
|
||||
context.tooltipsCaracteristiques = {
|
||||
rob: this._generateTooltip("save", "rob"),
|
||||
dex: this._generateTooltip("save", "dex"),
|
||||
int: this._generateTooltip("save", "int"),
|
||||
per: this._generateTooltip("save", "per"),
|
||||
vol: this._generateTooltip("save", "vol"),
|
||||
dmax: this._generateTooltip("other", "dmax"),
|
||||
}
|
||||
|
||||
context.tooltipsRessources = {
|
||||
san: this._generateTooltip("resource", "san"),
|
||||
oeil: this._generateTooltip("resource", "oeil"),
|
||||
verbe: this._generateTooltip("resource", "verbe"),
|
||||
bourse: this._generateTooltip("resource", "bourse"),
|
||||
magie: this._generateTooltip("resource", "magie"),
|
||||
}
|
||||
|
||||
context.rollType = {
|
||||
saveRob: {
|
||||
action: "roll",
|
||||
rollType: "save",
|
||||
rollTarget: "rob",
|
||||
tooltip: this._generateTooltip("save", "rob"),
|
||||
},
|
||||
saveDex: {
|
||||
action: "roll",
|
||||
rollType: "save",
|
||||
rollTarget: "dex",
|
||||
tooltip: this._generateTooltip("save", "dex"),
|
||||
},
|
||||
saveInt: {
|
||||
action: "roll",
|
||||
rollType: "save",
|
||||
rollTarget: "int",
|
||||
tooltip: this._generateTooltip("save", "int"),
|
||||
},
|
||||
savePer: {
|
||||
action: "roll",
|
||||
rollType: "save",
|
||||
rollTarget: "per",
|
||||
tooltip: this._generateTooltip("save", "per"),
|
||||
drag: true,
|
||||
},
|
||||
saveVol: {
|
||||
action: "roll",
|
||||
rollType: "save",
|
||||
rollTarget: "vol",
|
||||
tooltip: this._generateTooltip("save", "vol"),
|
||||
drag: true,
|
||||
},
|
||||
resourceSan: {
|
||||
action: "roll",
|
||||
rollType: "resource",
|
||||
rollTarget: "san",
|
||||
tooltip: this._generateTooltip("resource", "san"),
|
||||
drag: true,
|
||||
},
|
||||
resourceOeil: {
|
||||
action: "roll",
|
||||
rollType: "resource",
|
||||
rollTarget: "oeil",
|
||||
tooltip: this._generateTooltip("resource", "oeil"),
|
||||
drag: true,
|
||||
},
|
||||
resourceVerbe: {
|
||||
action: "roll",
|
||||
rollType: "resource",
|
||||
rollTarget: "verbe",
|
||||
tooltip: this._generateTooltip("resource", "verbe"),
|
||||
drag: true,
|
||||
},
|
||||
resourceBourse: {
|
||||
action: "roll",
|
||||
rollType: "resource",
|
||||
rollTarget: "bourse",
|
||||
tooltip: this._generateTooltip("resource", "bourse"),
|
||||
drag: true,
|
||||
},
|
||||
resourceMagie: {
|
||||
action: "roll",
|
||||
rollType: "resource",
|
||||
rollTarget: "magie",
|
||||
tooltip: this._generateTooltip("resource", "magie"),
|
||||
drag: true,
|
||||
},
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
_generateTooltip(type, target) {
|
||||
if (type === ROLL_TYPE.SAVE) {
|
||||
const progres = this.document.system.caracteristiques[target].progression.progres
|
||||
? game.i18n.localize("TENEBRIS.Label.hasProgressed")
|
||||
: game.i18n.localize("TENEBRIS.Label.noProgress")
|
||||
return `${game.i18n.localize("TENEBRIS.Label.experience")} : ${this.document.system.caracteristiques[target].progression.experience} <br> ${progres}`
|
||||
} else if (type === ROLL_TYPE.RESOURCE) {
|
||||
return `${game.i18n.localize("TENEBRIS.Label.maximum")} : ${this.document.system.ressources[target].max} <br> ${game.i18n.localize("TENEBRIS.Label.experience")} : ${this.document.system.ressources[target].experience}`
|
||||
} else if (type === "other") {
|
||||
return `${game.i18n.localize("TENEBRIS.Label.experience")} : ${this.document.system.dmax.experience}`
|
||||
}
|
||||
}
|
||||
|
||||
/** @override */
|
||||
async _preparePartContext(partId, context) {
|
||||
const doc = this.document
|
||||
switch (partId) {
|
||||
case "main":
|
||||
context.enrichedBiens = await TextEditor.enrichHTML(doc.system.biens, { async: true })
|
||||
break
|
||||
case "items":
|
||||
context.tab = context.tabs.items
|
||||
const talents = await this._prepareTalents()
|
||||
context.talents = talents
|
||||
context.talentsAppris = talents.filter((talent) => talent.appris)
|
||||
context.weapons = doc.itemTypes.weapon
|
||||
context.armors = doc.itemTypes.armor
|
||||
context.spells = doc.itemTypes.spell
|
||||
context.hasSpells = context.spells.length > 0
|
||||
break
|
||||
case "biography":
|
||||
context.tab = context.tabs.biography
|
||||
context.enrichedDescription = await TextEditor.enrichHTML(doc.system.description, { async: true })
|
||||
context.enrichedLangues = await TextEditor.enrichHTML(doc.system.langues, { async: true })
|
||||
context.enrichedNotes = await TextEditor.enrichHTML(doc.system.notes, { async: true })
|
||||
break
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the talents for the character sheet.
|
||||
*
|
||||
* @returns {Array} An array of talents with their properties.
|
||||
*/
|
||||
async _prepareTalents() {
|
||||
const talents = await Promise.all(
|
||||
this.document.itemTypes.talent.map(async (talent) => {
|
||||
const pathName = await talent.system.getPathName()
|
||||
return {
|
||||
id: talent.id,
|
||||
uuid: talent.uuid,
|
||||
name: talent.name,
|
||||
img: talent.img,
|
||||
path: `Obtenu par ${pathName}`,
|
||||
description: talent.system.improvedDescription,
|
||||
progression: talent.system.progression,
|
||||
niveau: talent.system.niveau,
|
||||
appris: talent.system.appris,
|
||||
details: talent.system.details,
|
||||
}
|
||||
}),
|
||||
)
|
||||
talents.sort((a, b) => b.appris - a.appris || a.name.localeCompare(b.name, game.i18n.lang))
|
||||
return talents
|
||||
}
|
||||
|
||||
// #region Drag-and-Drop Workflow
|
||||
|
||||
/**
|
||||
* Callback actions which occur when a dragged element is dropped on a target.
|
||||
* @param {DragEvent} event The originating DragEvent
|
||||
* @protected
|
||||
*/
|
||||
async _onDrop(event) {
|
||||
if (!this.isEditable || !this.isEditMode) return
|
||||
const data = TextEditor.getDragEventData(event)
|
||||
|
||||
// Handle different data types
|
||||
switch (data.type) {
|
||||
case "Item":
|
||||
const item = await fromUuid(data.uuid)
|
||||
if (!["path", "weapon", "armor", "spell"].includes(item.type)) return
|
||||
if (item.type === "path") return this.#onDropPathItem(item)
|
||||
if (item.type === "weapon") return super._onDropItem(item)
|
||||
if (item.type === "armor") return this._onDropItem(item)
|
||||
if (item.type === "spell") return this._onDropItem(item)
|
||||
}
|
||||
}
|
||||
|
||||
async #onDropPathItem(item) {
|
||||
await this.document.addPath(item)
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Actions
|
||||
|
||||
/**
|
||||
* Suppression de la voie majeure
|
||||
* @param {Event} event The initiating click event.
|
||||
* @param {HTMLElement} target The current target of the event listener.
|
||||
*/
|
||||
static async #onDeleteVoieMajeure(event, target) {
|
||||
const proceed = await foundry.applications.api.DialogV2.confirm({
|
||||
content: game.i18n.localize("TENEBRIS.Dialog.suppressionTalents"),
|
||||
rejectClose: false,
|
||||
modal: true,
|
||||
})
|
||||
if (!proceed) return
|
||||
const path = this.document.items.get(this.document.system.voies.majeure.id)
|
||||
if (!path) return
|
||||
await this.document.deletePath(path, true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppression de la voie mineure
|
||||
* @param {Event} event The initiating click event.
|
||||
* @param {HTMLElement} target The current target of the event listener.
|
||||
*/
|
||||
static async #onDeleteVoieMineure(event, target) {
|
||||
const proceed = await foundry.applications.api.DialogV2.confirm({
|
||||
content: game.i18n.localize("TENEBRIS.Dialog.suppressionTalents"),
|
||||
rejectClose: false,
|
||||
modal: true,
|
||||
})
|
||||
if (!proceed) return
|
||||
const path = this.document.items.get(this.document.system.voies.mineure.id)
|
||||
if (!path) return
|
||||
await this.document.deletePath(path, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new attack item directly from the sheet and embeds it into the document.
|
||||
* @param {Event} event The initiating click event.
|
||||
* @param {HTMLElement} target The current target of the event listener.
|
||||
*/
|
||||
static #onCreateEquipment(event, target) {
|
||||
// Création d'une armure
|
||||
if (event.shiftKey) {
|
||||
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("TENEBRIS.Label.newArmor"), type: "armor" }])
|
||||
}
|
||||
// Création d'une arme
|
||||
else {
|
||||
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("TENEBRIS.Label.newWeapon"), type: "weapon" }])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the roll action triggered by user interaction.
|
||||
*
|
||||
* @param {PointerEvent} event The event object representing the user interaction.
|
||||
* @param {HTMLElement} target The target element that triggered the roll.
|
||||
*
|
||||
* @returns {Promise<void>} A promise that resolves when the roll action is complete.
|
||||
*
|
||||
* @throws {Error} Throws an error if the roll type is not recognized.
|
||||
*
|
||||
* @description This method checks the current mode (edit or not) and determines the type of roll
|
||||
* (save, resource, or damage) based on the target element's data attributes. It retrieves the
|
||||
* corresponding value from the document's system and performs the roll.
|
||||
*/
|
||||
async _onRoll(event, target) {
|
||||
if (this.isEditMode) return
|
||||
// Jet de sauvegarde
|
||||
let elt = event.currentTarget.querySelector("input")
|
||||
// Jet de ressource
|
||||
if (!elt) elt = event.currentTarget.querySelector("select")
|
||||
// Jet de dégâts
|
||||
if (!elt) elt = event.currentTarget
|
||||
const rollType = elt.dataset.rollType
|
||||
let rollTarget
|
||||
switch (rollType) {
|
||||
case ROLL_TYPE.SAVE:
|
||||
rollTarget = elt.dataset.rollTarget
|
||||
break
|
||||
case ROLL_TYPE.RESOURCE:
|
||||
rollTarget = elt.dataset.rollTarget
|
||||
break
|
||||
case ROLL_TYPE.DAMAGE:
|
||||
rollTarget = elt.dataset.itemId
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
await this.document.system.roll(rollType, rollTarget)
|
||||
}
|
||||
// #endregion
|
||||
}
|
89
module/applications/sheets/opponent-sheet.mjs
Normal file
89
module/applications/sheets/opponent-sheet.mjs
Normal file
@ -0,0 +1,89 @@
|
||||
import LethalFantasyActorSheet from "./base-actor-sheet.mjs"
|
||||
|
||||
export default class LethalFantasyOpponentSheet extends LethalFantasyActorSheet {
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["opponent"],
|
||||
position: {
|
||||
width: 800,
|
||||
height: 700,
|
||||
},
|
||||
window: {
|
||||
contentClasses: ["opponent-content"],
|
||||
},
|
||||
actions: {
|
||||
createAttack: LethalFantasyOpponentSheet.#onCreateAttack,
|
||||
},
|
||||
}
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: "systems/fvtt-lethal-fantasy/templates/opponent.hbs",
|
||||
},
|
||||
}
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext()
|
||||
context.attacks = context.actor.itemTypes.attack
|
||||
context.spells = context.actor.itemTypes.spell
|
||||
context.hasSpells = context.spells.length > 0
|
||||
return context
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback actions which occur when a dragged element is dropped on a target.
|
||||
* @param {DragEvent} event The originating DragEvent
|
||||
* @protected
|
||||
*/
|
||||
async _onDrop(event) {
|
||||
if (!this.isEditable || !this.isEditMode) return
|
||||
const data = TextEditor.getDragEventData(event)
|
||||
|
||||
// Handle different data types
|
||||
switch (data.type) {
|
||||
case "Item":
|
||||
const item = await fromUuid(data.uuid)
|
||||
if (item.type === "attack") return this.#onDropAttackItem(item)
|
||||
if (item.type === "spell") return this._onDropItem(item)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the drop event of an attack item.
|
||||
*
|
||||
* @param {Object} item The attack item being dropped.
|
||||
* @returns {Promise<void>} A promise that resolves when the attack item has been added to the document.
|
||||
* @private
|
||||
*/
|
||||
async #onDropAttackItem(item) {
|
||||
await this.document.addAttack(item)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the creation of a new attack item.
|
||||
*
|
||||
* @param {Event} event The event that triggered the creation of the attack.
|
||||
* @param {Object} target The target object where the attack will be created.
|
||||
* @private
|
||||
* @static
|
||||
*/
|
||||
static #onCreateAttack(event, target) {
|
||||
const item = this.document.createEmbeddedDocuments("Item", [{ name: "Nouvelle attaque", type: "attack" }])
|
||||
}
|
||||
|
||||
/**
|
||||
* Roll a damage roll.
|
||||
* @param {PointerEvent} event The originating click event
|
||||
* @param {HTMLElement} target the capturing HTML element which defined a [data-action]
|
||||
*/
|
||||
async _onRoll(event, target) {
|
||||
if (this.isEditMode) return
|
||||
const elt = event.currentTarget
|
||||
const rollValue = elt.dataset.rollValue
|
||||
const rollTarget = elt.dataset.itemName
|
||||
|
||||
await this.document.system.roll(rollValue, rollTarget)
|
||||
}
|
||||
}
|
141
module/applications/sheets/path-sheet.mjs
Normal file
141
module/applications/sheets/path-sheet.mjs
Normal file
@ -0,0 +1,141 @@
|
||||
import LethalFantasyItemSheet from "./base-item-sheet.mjs"
|
||||
|
||||
export default class LethalFantasyPathSheet extends LethalFantasyItemSheet {
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["path"],
|
||||
position: {
|
||||
width: 1400,
|
||||
},
|
||||
window: {
|
||||
contentClasses: ["path-content"],
|
||||
},
|
||||
dragDrop: [{ dragSelector: "[data-drag]", dropSelector: null }],
|
||||
actions: {
|
||||
edit: LethalFantasyPathSheet.#onTalentEdit,
|
||||
delete: LethalFantasyPathSheet.#onTalentDelete,
|
||||
},
|
||||
}
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
header: {
|
||||
template: "systems/fvtt-lethal-fantasy/templates/path-header.hbs",
|
||||
},
|
||||
tabs: {
|
||||
template: "templates/generic/tab-navigation.hbs",
|
||||
},
|
||||
main: {
|
||||
template: "systems/fvtt-lethal-fantasy/templates/path-main.hbs",
|
||||
},
|
||||
talents: {
|
||||
template: "systems/fvtt-lethal-fantasy/templates/path-talents.hbs",
|
||||
},
|
||||
}
|
||||
|
||||
/** @override */
|
||||
tabGroups = {
|
||||
sheet: "main",
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare an array of form header tabs.
|
||||
* @returns {Record<string, Partial<ApplicationTab>>}
|
||||
*/
|
||||
#getTabs() {
|
||||
const tabs = {
|
||||
main: { id: "main", group: "sheet", icon: "fa-solid fa-user", label: "TENEBRIS.Label.profil" },
|
||||
talents: { id: "talents", group: "sheet", icon: "fa-solid fa-book", label: "TENEBRIS.Label.talents" },
|
||||
}
|
||||
for (const v of Object.values(tabs)) {
|
||||
v.active = this.tabGroups[v.group] === v.id
|
||||
v.cssClass = v.active ? "active" : ""
|
||||
}
|
||||
return tabs
|
||||
}
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext()
|
||||
context.tabs = this.#getTabs()
|
||||
return context
|
||||
}
|
||||
|
||||
/** @override */
|
||||
async _preparePartContext(partId, context) {
|
||||
const doc = this.document
|
||||
switch (partId) {
|
||||
case "main":
|
||||
context.tab = context.tabs.main
|
||||
context.enrichedBiens = await TextEditor.enrichHTML(this.document.system.biens, { async: true })
|
||||
context.enrichedLangues = await TextEditor.enrichHTML(this.document.system.langues, { async: true })
|
||||
break
|
||||
case "talents":
|
||||
context.tab = context.tabs.talents
|
||||
const talentItems = []
|
||||
for (const talent of this.item.system.talents) {
|
||||
const talentItem = await fromUuid(talent)
|
||||
if (talentItem) talentItems.push(talentItem)
|
||||
}
|
||||
|
||||
context.talents = talentItems
|
||||
context.talents.sort((a, b) => a.name.localeCompare(b.name, game.i18n.lang))
|
||||
|
||||
break
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback actions which occur when a dragged element is dropped on a target.
|
||||
* Seul un item de type Talent peut être déposé sur une voie.
|
||||
* @param {DragEvent} event The originating DragEvent
|
||||
* @protected
|
||||
*/
|
||||
async _onDrop(event) {
|
||||
const data = TextEditor.getDragEventData(event)
|
||||
|
||||
switch (data.type) {
|
||||
case "Item":
|
||||
if (this.isPlayMode) return
|
||||
const item = await fromUuid(data.uuid)
|
||||
if (item.type !== "talent") return
|
||||
|
||||
console.debug("dropped item", item)
|
||||
const talents = this.item.toObject().system.talents
|
||||
talents.push(item.uuid)
|
||||
this.item.update({ "system.talents": talents })
|
||||
}
|
||||
}
|
||||
|
||||
// #region Actions
|
||||
|
||||
/**
|
||||
* Edit an existing talent within the path item
|
||||
* Use the uuid to display the talent sheet (from world or compendium)
|
||||
* @param {PointerEvent} event The originating click event
|
||||
* @param {HTMLElement} target the capturing HTML element which defined a [data-action]
|
||||
*/
|
||||
static async #onTalentEdit(event, target) {
|
||||
const itemUuid = target.getAttribute("data-item-uuid")
|
||||
const talent = await fromUuid(itemUuid)
|
||||
talent.sheet.render(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an existing talent within the path item
|
||||
* Use the uuid to remove it form the array of talents
|
||||
* @param {PointerEvent} event The originating click event
|
||||
* @param {HTMLElement} target the capturing HTML element which defined a [data-action]
|
||||
*/
|
||||
static async #onTalentDelete(event, target) {
|
||||
const itemUuid = target.getAttribute("data-item-uuid")
|
||||
|
||||
const talents = this.item.toObject().system.talents
|
||||
const index = talents.indexOf(itemUuid)
|
||||
talents.splice(index, 1)
|
||||
this.item.update({ "system.talents": talents })
|
||||
}
|
||||
|
||||
// #endregion
|
||||
}
|
21
module/applications/sheets/spell-sheet.mjs
Normal file
21
module/applications/sheets/spell-sheet.mjs
Normal file
@ -0,0 +1,21 @@
|
||||
import LethalFantasyItemSheet from "./base-item-sheet.mjs"
|
||||
|
||||
export default class LethalFantasySpellSheet extends LethalFantasyItemSheet {
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["spell"],
|
||||
position: {
|
||||
width: 450,
|
||||
},
|
||||
window: {
|
||||
contentClasses: ["spell-content"],
|
||||
},
|
||||
}
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: "systems/fvtt-lethal-fantasy/templates/spell.hbs",
|
||||
},
|
||||
}
|
||||
}
|
29
module/applications/sheets/talent-sheet.mjs
Normal file
29
module/applications/sheets/talent-sheet.mjs
Normal file
@ -0,0 +1,29 @@
|
||||
import LethalFantasyItemSheet from "./base-item-sheet.mjs"
|
||||
|
||||
export default class LethalFantasyTalentSheet extends LethalFantasyItemSheet {
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["talent"],
|
||||
position: {
|
||||
width: 600,
|
||||
},
|
||||
window: {
|
||||
contentClasses: ["talent-content"],
|
||||
},
|
||||
}
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: "systems/fvtt-lethal-fantasy/templates/talent.hbs",
|
||||
},
|
||||
}
|
||||
|
||||
/** @override */
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext()
|
||||
context.enrichedDescription = await TextEditor.enrichHTML(this.document.system.improvedDescription, { async: true })
|
||||
context.canProgress = this.document.system.canProgress
|
||||
return context
|
||||
}
|
||||
}
|
21
module/applications/sheets/weapon-sheet.mjs
Normal file
21
module/applications/sheets/weapon-sheet.mjs
Normal file
@ -0,0 +1,21 @@
|
||||
import LethalFantasyItemSheet from "./base-item-sheet.mjs"
|
||||
|
||||
export default class LethalFantasyWeaponSheet extends LethalFantasyItemSheet {
|
||||
/** @override */
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["weapon"],
|
||||
position: {
|
||||
width: 400,
|
||||
},
|
||||
window: {
|
||||
contentClasses: ["weapon-content"],
|
||||
},
|
||||
}
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: "systems/fvtt-lethal-fantasy/templates/weapon.hbs",
|
||||
},
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user