Initial system import

This commit is contained in:
2025-12-25 23:08:06 +01:00
commit 4beb5806eb
4623 changed files with 682363 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
export { default as HamalronArmeSheet } from "./sheets/arme-sheet.mjs"
export { default as HamalronArmureSheet } from "./sheets/armure-sheet.mjs"
export { default as HamalronCompetenceSheet } from "./sheets/competence-sheet.mjs"
export { default as HamalronEquipementSheet } from "./sheets/equipement-sheet.mjs"
export { default as HamalronFactionSheet } from "./sheets/faction-sheet.mjs"
export { default as HamalronLangueSheet } from "./sheets/langue-sheet.mjs"
export { default as HamalronPeupleSheet } from "./sheets/peuple-sheet.mjs"
export { default as HamalronRegionSheet } from "./sheets/region-sheet.mjs"
export { default as HamalronPersonnageSheet } from "./sheets/personnage-sheet.mjs"
export { default as HamalronEnemySheet } from "./sheets/enemy-sheet.mjs"
export { default as HamalronSortilegeSheet } from "./sheets/sortilege-sheet.mjs"
export { default as HamalronTarotSheet } from "./sheets/tarot-sheet.mjs"
export { default as HamalronItemSheet } from "./sheets/base-item-sheet.mjs"
export { default as HamalronActorSheet } from "./sheets/base-actor-sheet.mjs"
export { default as HamalronTarotDeckManager } from "./tarot-deck-manager.mjs"
+27
View File
@@ -0,0 +1,27 @@
import HamalronItemSheet from "./base-item-sheet.mjs"
export default class HamalronArmeSheet extends HamalronItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["weapon"],
position: {
width: 620,
},
window: {
contentClasses: ["weapon-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-hamalron/templates/arme.hbs",
},
}
async _prepareContext() {
const context = await super._prepareContext()
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
return context
}
}
@@ -0,0 +1,28 @@
import HamalronItemSheet from "./base-item-sheet.mjs"
export default class HamalronArmureSheet extends HamalronItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["armor"],
position: {
width: 500,
},
window: {
contentClasses: ["armor-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-hamalron/templates/armure.hbs",
},
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
return context
}
}
@@ -0,0 +1,260 @@
const { HandlebarsApplicationMixin } = foundry.applications.api
export default class HamalronActorSheet 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: ["fvtt-hamalron", "actor"],
position: {
width: 1400,
height: "auto",
},
form: {
submitOnChange: true,
},
window: {
resizable: true,
},
dragDrop: [{ dragSelector: '[data-drag="true"], .rollable', dropSelector: null }],
actions: {
editImage: HamalronActorSheet.#onEditImage,
toggleSheet: HamalronActorSheet.#onToggleSheet,
edit: HamalronActorSheet.#onItemEdit,
delete: HamalronActorSheet.#onItemDelete,
updateCheckboxArray: HamalronActorSheet.#onUpdateCheckboxArray,
toChat: HamalronActorSheet.#toChat,
},
}
/**
* 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(),
enrichedBackstory: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.backstory, { 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 = {
dragover: this._onDragOver.bind(this),
drop: this._onDrop.bind(this),
}
return new foundry.applications.ux.DragDrop.implementation(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 true //this.isEditable && this.document.isOwner
}
/**
* 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) {
console.log("Dropped item", 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 HamalronActorSheet
* @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()
}
static #onUpdateCheckboxArray(event, target) {
console.log("Update checkbox array", event, target)
let arrayName = target.dataset.name
let arrayIdx = Number(target.dataset.index)
let dataPath = `system.mortality.${arrayName}`
let tab = foundry.utils.duplicate(this.document.system.mortality[arrayName])
tab[arrayIdx] = target.checked
this.actor.update({ [dataPath]: tab })
}
static async #toChat(event, target) {
const itemUuid = target.getAttribute("data-item-uuid")
const item = fromUuidSync(itemUuid)
if (!item) return
let content = ""
if (item.type === "perk") {
content = await foundry.applications.handlebars.renderTemplate("systems/fvtt-hamalron/templates/chat-perk.hbs", item.toObject())
}
if (item.type === "malefica") {
content = await foundry.applications.handlebars.renderTemplate("systems/fvtt-hamalron/templates/chat-malefica.hbs", item.toObject())
}
if (item.type === "ritual") {
content = await foundry.applications.handlebars.renderTemplate("systems/fvtt-hamalron/templates/chat-ritual.hbs", item.toObject())
}
if (item.type === "species-trait") {
content = await foundry.applications.handlebars.renderTemplate("systems/fvtt-hamalron/templates/chat-trait.hbs", item.toObject())
}
if (item.type === "tarot") {
content = await foundry.applications.handlebars.renderTemplate("systems/fvtt-hamalron/templates/chat-tarot.hbs", item.toObject())
}
const chatData = {
user: game.user.id,
speaker: ChatMessage.getSpeaker({ actor: this.actor }),
content: content,
type: CONST.CHAT_MESSAGE_TYPES.OTHER,
}
ChatMessage.create(chatData, { renderSheet: false })
}
/**
* 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 CthulhuEternalCharacterSheet
* @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 item = await fromUuid(itemUuid)
await item.deleteDialog()
}
// #endregion
}
@@ -0,0 +1,193 @@
const { HandlebarsApplicationMixin } = foundry.applications.api
export default class HamalronItemSheet 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: ["fvtt-hamalron", "item"],
position: {
width: 600,
height: "auto",
},
form: {
submitOnChange: true,
},
window: {
resizable: true,
},
dragDrop: [{ dragSelector: "[data-drag]", dropSelector: null }],
actions: {
toggleSheet: HamalronItemSheet.#onToggleSheet,
editImage: HamalronItemSheet.#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 foundry.applications.ux.TextEditor.implementation.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 foundry.applications.ux.DragDrop.implementation(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 CthulhuEternalCharacterSheet
* @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
}
@@ -0,0 +1,28 @@
import HamalronItemSheet from "./base-item-sheet.mjs"
export default class HamalronCompetenceSheet extends HamalronItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["perk"],
position: {
width: 600,
},
window: {
contentClasses: ["perk-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-hamalron/templates/competence.hbs",
},
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
return context
}
}
+166
View File
@@ -0,0 +1,166 @@
import HamalronActorSheet from "./base-actor-sheet.mjs"
export default class HamalronEnemySheet extends HamalronActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["enemy"],
position: {
width: 860,
height: 620,
},
window: {
contentClasses: ["enemy-content"],
},
actions: {
createTrait: HamalronEnemySheet.#onCreateTrait,
createEquipment: HamalronEnemySheet.#onCreateEquipment,
createWeapon: HamalronEnemySheet.#onCreateWeapon,
createMalefica: HamalronEnemySheet.#onCreateMalefica,
}
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-hamalron/templates/enemy-main.hbs",
},
tabs: {
template: "templates/generic/tab-navigation.hbs",
},
traits: {
template: "systems/fvtt-hamalron/templates/enemy-trait.hbs",
},
biography: {
template: "systems/fvtt-hamalron/templates/enemy-biography.hbs",
},
}
/** @override */
tabGroups = {
sheet: "traits",
}
/**
* Prepare an array of form header tabs.
* @returns {Record<string, Partial<ApplicationTab>>}
*/
#getTabs() {
const tabs = {
traits: { id: "traits", group: "sheet", icon: "fa-solid fa-shapes", label: "HAMALRON.Label.traits" },
biography: { id: "biography", group: "sheet", icon: "fa-solid fa-book", label: "HAMALRON.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()
return context
}
/** @override */
async _preparePartContext(partId, context) {
const doc = this.document
switch (partId) {
case "main":
break
case "traits":
context.tab = context.tabs.traits
context.traits = doc.itemTypes["species-trait"]
context.traits.sort((a, b) => a.name.localeCompare(b.name))
context.weapons = doc.itemTypes.weapon
context.weapons.sort((a, b) => a.name.localeCompare(b.name))
context.maleficas = doc.itemTypes.malefica
context.maleficas.sort((a, b) => a.name.localeCompare(b.name))
context.equipments = doc.itemTypes.equipment
context.equipments.sort((a, b) => a.name.localeCompare(b.name))
break
case "biography":
context.tab = context.tabs.biography
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(doc.system.description, { async: true })
context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(doc.system.notes, { async: true })
break
}
return context
}
/**
* 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 #onCreateTrait(event, target) {
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newTrait"), type: "species-trait" }])
}
static #onCreateEquipment(event, target) {
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newEquipment"), type: "equipment" }])
}
static #onCreateMalefica(event, target) {
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newMalefica"), type: "malefica" }])
}
static #onCreateWeapon(event, target) {
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.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) {
const rollType = $(event.currentTarget).data("roll-type")
let item
switch (rollType) {
case "stat":
{
let statId = $(event.currentTarget).data("stat-id");
item = this.actor.system.stats[statId];
await this.document.system.roll(rollType, item)
}
break
case "weapon":
case "damage":
{
let li = $(event.currentTarget).parents(".item");
item = this.actor.items.get(li.data("item-id"));
await this.document.system.roll(rollType, item)
}
break
default:
throw new Error(`Unknown roll type ${rollType}`)
}
}
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)
return super._onDropItem(item)
}
}
}
@@ -0,0 +1,28 @@
import HamalronItemSheet from "./base-item-sheet.mjs"
export default class HamalronEquipementSheet extends HamalronItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["equipment"],
position: {
width: 500,
},
window: {
contentClasses: ["equipment-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-hamalron/templates/equipement.hbs",
},
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
return context
}
}
@@ -0,0 +1,52 @@
import HamalronItemSheet from "./base-item-sheet.mjs"
export default class HamalronFactionSheet extends HamalronItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["faction"],
position: {
width: 620,
},
window: {
contentClasses: ["faction-content"],
},
}
/** @override */
static PARTS = {
tabs: {
template: "templates/generic/tab-navigation.hbs",
},
main: {
template: "systems/fvtt-hamalron/templates/faction.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-circle-info", label: "HAMALRON.Label.Details" },
}
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.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
return context
}
}
@@ -0,0 +1,52 @@
import HamalronItemSheet from "./base-item-sheet.mjs"
export default class HamalronLangueSheet extends HamalronItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["langue"],
position: {
width: 620,
},
window: {
contentClasses: ["langue-content"],
},
}
/** @override */
static PARTS = {
tabs: {
template: "templates/generic/tab-navigation.hbs",
},
main: {
template: "systems/fvtt-hamalron/templates/langue.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-circle-info", label: "HAMALRON.Label.Details" },
}
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.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
return context
}
}
@@ -0,0 +1,264 @@
import HamalronActorSheet from "./base-actor-sheet.mjs"
export default class HamalronPersonnageSheet extends HamalronActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["character"],
position: {
width: 860,
height: 620,
},
window: {
contentClasses: ["character-content"],
},
actions: {
createEquipment: HamalronPersonnageSheet.#onCreateEquipment,
createArmor: HamalronPersonnageSheet.#onCreateArmor,
createWeapon: HamalronPersonnageSheet.#onCreateWeapon,
createDeal: HamalronPersonnageSheet.#onCreateDeal,
createMalefica: HamalronPersonnageSheet.#onCreateMalefica,
createRitual: HamalronPersonnageSheet.#onCreateRitual,
createPerk: HamalronPersonnageSheet.#onCreatePerk,
modifyAmmo: HamalronPersonnageSheet.#onModifyAmmo,
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-hamalron/templates/personnage-main.hbs",
},
tabs: {
template: "templates/generic/tab-navigation.hbs",
},
status: {
template: "systems/fvtt-hamalron/templates/personnage-status.hbs",
},
maleficas: {
template: "systems/fvtt-hamalron/templates/personnage-sortileges.hbs",
},
equipment: {
template: "systems/fvtt-hamalron/templates/personnage-equipement.hbs",
},
tarot: {
template: "systems/fvtt-hamalron/templates/personnage-tarot.hbs",
},
biography: {
template: "systems/fvtt-hamalron/templates/personnage-biographie.hbs",
},
}
/** @override */
tabGroups = {
sheet: "status",
}
/**
* Prepare an array of form header tabs.
* @returns {Record<string, Partial<ApplicationTab>>}
*/
#getTabs() {
const tabs = {
status: { id: "status", group: "sheet", icon: "fa-solid fa-compass", label: "HAMALRON.Label.status" },
maleficas: { id: "maleficas", group: "sheet", icon: "fa-solid fa-compass", label: "HAMALRON.Label.maleficas" },
equipment: { id: "equipment", group: "sheet", icon: "fa-solid fa-shapes", label: "HAMALRON.Label.equipment" },
tarot: { id: "tarot", group: "sheet", icon: "fa-solid fa-cards", label: "HAMALRON.Label.tarot" },
biography: { id: "biography", group: "sheet", icon: "fa-solid fa-book", label: "HAMALRON.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()
const doc = this.document
context.trait = doc.itemTypes['species-trait']?.[0]
context.upright = doc.itemTypes.tarot.find(t => t.system.orientation === "Upright")
context.downright = doc.itemTypes.tarot.find(t => t.system.orientation === "Downright")
return context
}
/** @override */
async _preparePartContext(partId, context) {
const doc = this.document
context.systemFields = this.document.system.schema.fields
switch (partId) {
case "main":
break
case "status":
context.tab = context.tabs.status
context.perks = doc.itemTypes.perk || []
// Sort the perks by system.role and then by the system.level
context.perks.sort((a, b) => {
if (a.system.role === b.system.role) {
return a.system.level.localeCompare(b.system.level)
}
return a.system.role.localeCompare(b.system.role)
})
break;
case "maleficas":
context.tab = context.tabs.maleficas
context.maleficas = doc.itemTypes.malefica || []
// Sort the maleficas by system.domain and then by the system.level
context.maleficas.sort((a, b) => {
if (a.system.domain === b.system.domain) {
return a.system.level.localeCompare(b.system.level)
}
return a.system.domain.localeCompare(b.system.domain)
})
context.rituals = doc.itemTypes.ritual || []
context.rituals.sort((a, b) => a.name.localeCompare(b.name))
break
case "equipment":
context.tab = context.tabs.equipment
context.weapons = doc.itemTypes.weapon || []
context.weapons.sort((a, b) => a.name.localeCompare(b.name))
context.armors = doc.itemTypes.armor || []
context.armors.sort((a, b) => a.name.localeCompare(b.name))
context.equipments = doc.itemTypes.equipment || []
context.equipments.sort((a, b) => a.name.localeCompare(b.name))
break
case "tarot":
context.tab = context.tabs.tarot
context.tarotCards = doc.itemTypes.tarot || []
context.tarotCards.sort((a, b) => a.name.localeCompare(b.name))
// Enrich descriptions for each tarot card
for (const card of context.tarotCards) {
card.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(card.system.description, { async: true })
}
break
case "biography":
context.tab = context.tabs.biography
context.deals = doc.itemTypes.deal || []
context.enrichedBackstory = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.backstory, { async: true })
context.enrichedAppearance = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.appearance, { async: true })
context.enrichedScars = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.scars, { async: true })
context.enrichedLikes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.likes, { async: true })
context.enrichedDislikes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.dislikes, { async: true })
context.enrichedFears = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.fears, { async: true })
context.enrichedVices = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.vices, { async: true })
context.enrichedGoals = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.goals, { async: true })
context.enrichedAmbitions = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.ambitions, { async: true })
context.enrichedValues = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.values, { async: true })
context.enrichedBonds = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.bonds, { async: true })
context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.notes, { async: true })
break
}
return context
}
static #onCreateEquipment(event, target) {
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newEquipment"), type: "equipment" }])
}
static #onCreateDeal(event, target) {
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newDeal"), type: "deal" }])
}
static #onCreateMalefica(event, target) {
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newMalefica"), type: "malefica" }])
}
static #onCreateRitual(event, target) {
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newRitual"), type: "ritual" }])
}
static #onCreateWeapon(event, target) {
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newWeapon"), type: "weapon" }])
}
static #onCreateArmor(event, target) {
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newArmor"), type: "armor" }])
}
static #onCreatePerk(event, target) {
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newPerk"), type: "perk" }])
}
static async #onModifyAmmo(event, target) {
const quantity = parseInt($(event.target).data("quantity"))
const itemId = $(event.target).data("item-id")
const item = this.document.items.get(itemId)
if (!item) {
console.warn(`HamalronCharacterSheet: Item with ID ${itemId} not found`)
return
}
const currentAmmo = item.system.ammoQuantity || 0
const newAmmo = Math.max(0, currentAmmo + quantity) // Ensure ammo doesn't go below 0
await item.update({ "system.ammoQuantity": newAmmo })
}
/**
* 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) {
const rollType = $(event.currentTarget).data("roll-type")
let item
let li
let statKey
switch (rollType) {
case "stat":
statKey = $(event.currentTarget).data("stat-id");
item = this.actor.system.stats[statKey];
break
case "weapon":
case "damage":
li = $(event.currentTarget).parents(".item");
item = this.actor.items.get(li.data("item-id"));
break
default:
throw new Error(`Unknown roll type ${rollType}`)
}
await this.document.system.roll(rollType, item)
}
async _onDrop(event) {
if (!this.isEditable || !this.isEditMode) return
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event)
// Handle different data types
if (data.type === "Item") {
const item = await fromUuid(data.uuid)
if (item.type === "tarot") {
// Delete the existing tarot item
const existingTarot = this.document.items.find(i => i.type === "tarot" && i.system.orientation === item.system.orientation)
if (existingTarot) {
await existingTarot.delete()
// Display info message
ui.notifications.info(game.i18n.localize("HAMALRON.Notifications.tarotDeleted") + existingTarot.name)
}
}
if (item.type === "species-trait") {
// Check if the item is already in the actor
const existingTrait = this.document.items.find(i => i.type === "species-trait" && i.name === item.name)
if (existingTrait) {
await existingTrait.delete()
// Display info message
ui.notifications.info(game.i18n.localize("HAMALRON.Notifications.speciesTraitDeleted") + existingTrait.name)
}
}
return super._onDropItem(item)
}
}
}
@@ -0,0 +1,52 @@
import HamalronItemSheet from "./base-item-sheet.mjs"
export default class HamalronPeupleSheet extends HamalronItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["peuple"],
position: {
width: 620,
},
window: {
contentClasses: ["peuple-content"],
},
}
/** @override */
static PARTS = {
tabs: {
template: "templates/generic/tab-navigation.hbs",
},
main: {
template: "systems/fvtt-hamalron/templates/peuple.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-circle-info", label: "HAMALRON.Label.Details" },
}
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.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
return context
}
}
@@ -0,0 +1,52 @@
import HamalronItemSheet from "./base-item-sheet.mjs"
export default class HamalronRegionSheet extends HamalronItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["region"],
position: {
width: 620,
},
window: {
contentClasses: ["region-content"],
},
}
/** @override */
static PARTS = {
tabs: {
template: "templates/generic/tab-navigation.hbs",
},
main: {
template: "systems/fvtt-hamalron/templates/region.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-circle-info", label: "HAMALRON.Label.Details" },
}
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.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
return context
}
}
@@ -0,0 +1,29 @@
import HamalronItemSheet from "./base-item-sheet.mjs"
export default class HamalronSortilegeSheet extends HamalronItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["ritual"],
position: {
width: 600,
},
window: {
contentClasses: ["ritual-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-hamalron/templates/sortilege.hbs",
},
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
return context
}
}
@@ -0,0 +1,31 @@
import HamalronItemSheet from "./base-item-sheet.mjs"
export default class HamalronTarotSheet extends HamalronItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["tarot"],
position: {
width: 800,
height: 800
},
window: {
contentClasses: ["tarot-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-hamalron/templates/tarot.hbs",
},
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
context.enrichedPositiveEffect = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.positiveEffect, { async: true })
context.enrichedNegativeEffect = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.negativeEffect, { async: true })
return context
}
}
+205
View File
@@ -0,0 +1,205 @@
const { HandlebarsApplicationMixin } = foundry.applications.api
export default class HamalronTarotDeckManager extends HandlebarsApplicationMixin(foundry.applications.api.ApplicationV2) {
constructor(options = {}) {
super(options)
this.deck = []
this.drawnCards = []
this.resetDeck()
}
/** @override */
static DEFAULT_OPTIONS = {
id: "tarot-deck-manager",
classes: ["fvtt-hamalron", "tarot-deck-manager"],
tag: "div",
window: {
title: "HAMALRON.TarotDeck.Title",
icon: "fas fa-cards",
resizable: true,
},
position: {
width: 600,
height: 700,
},
actions: {
resetDeck: HamalronTarotDeckManager.#onResetDeck,
drawCard: HamalronTarotDeckManager.#onDrawCard,
drawMultiple: HamalronTarotDeckManager.#onDrawMultiple,
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-hamalron/templates/tarot-deck-manager.hbs",
},
}
/**
* Reset the deck to a full tarot deck
*/
resetDeck() {
this.deck = []
this.drawnCards = []
// Créer toutes les cartes du jeu de tarot
const items = game.items.filter(i => i.type === "tarot")
if (items.length > 0) {
// Utiliser les items existants
this.deck = items.map(item => ({
id: item.id,
name: item.name,
img: item.img,
symbole: item.system.symbole,
valeur: item.system.valeur,
type: item.system.type,
description: item.system.description,
}))
} else {
ui.notifications.warn("HAMALRON.TarotDeck.NoCardsFound", { localize: true })
}
// Mélanger le deck
this.shuffleDeck()
this.render()
}
/**
* Shuffle the deck using Fisher-Yates algorithm
*/
shuffleDeck() {
for (let i = this.deck.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[this.deck[i], this.deck[j]] = [this.deck[j], this.deck[i]]
}
}
/**
* Draw a card from the deck
* @param {number} count - Number of cards to draw
* @returns {Array} - Drawn cards
*/
drawCards(count = 1) {
if (this.deck.length === 0) {
ui.notifications.warn("HAMALRON.TarotDeck.DeckEmpty", { localize: true })
return []
}
const drawn = []
for (let i = 0; i < count && this.deck.length > 0; i++) {
const card = this.deck.shift()
this.drawnCards.push(card)
drawn.push(card)
}
return drawn
}
/**
* Send drawn cards to chat
* @param {Array} cards - Cards to send to chat
*/
async sendCardsToChat(cards) {
if (cards.length === 0) return
const speaker = ChatMessage.getSpeaker({ alias: "Maître du Jeu" })
for (const card of cards) {
const content = await foundry.applications.handlebars.renderTemplate(
"systems/fvtt-hamalron/templates/chat-tarot-draw.hbs",
{
card: card,
symboleLabel: game.i18n.localize(`HAMALRON.Tarot.FIELDS.symbole.${card.symbole}`),
valeurLabel: game.i18n.localize(`HAMALRON.Tarot.FIELDS.valeur.${card.valeur}`),
typeLabel: game.i18n.localize(`HAMALRON.Tarot.FIELDS.type.${card.type}`),
}
)
await ChatMessage.create({
speaker: speaker,
content: content,
type: CONST.CHAT_MESSAGE_TYPES.OTHER,
})
}
}
/** @override */
async _prepareContext(options) {
const context = await super._prepareContext(options)
context.deckCount = this.deck.length
context.drawnCount = this.drawnCards.length
context.totalCards = this.deck.length + this.drawnCards.length
context.hasCards = this.deck.length > 0
context.drawnCards = this.drawnCards.slice(-5).reverse() // Show last 5 drawn cards
return context
}
/**
* Handle reset deck action
* @param {Event} event
* @param {HTMLElement} target
*/
static async #onResetDeck(event, target) {
const confirmed = await foundry.applications.api.DialogV2.confirm({
window: { title: "HAMALRON.TarotDeck.ResetConfirm" },
content: `<p>${game.i18n.localize("HAMALRON.TarotDeck.ResetWarning")}</p>`,
rejectClose: false,
modal: true,
})
if (confirmed) {
this.resetDeck()
ui.notifications.info("HAMALRON.TarotDeck.DeckReset", { localize: true })
}
}
/**
* Handle draw single card action
* @param {Event} event
* @param {HTMLElement} target
*/
static async #onDrawCard(event, target) {
const cards = this.drawCards(1)
if (cards.length > 0) {
await this.sendCardsToChat(cards)
this.render()
}
}
/**
* Handle draw multiple cards action
* @param {Event} event
* @param {HTMLElement} target
*/
static async #onDrawMultiple(event, target) {
const count = parseInt(target.dataset.count || "1")
const cards = this.drawCards(count)
if (cards.length > 0) {
await this.sendCardsToChat(cards)
this.render()
}
}
/**
* Open the Tarot Deck Manager application
* @returns {HamalronTarotDeckManager}
*/
static openManager() {
if (!game.user.isGM) {
ui.notifications.warn("HAMALRON.TarotDeck.GMOnly", { localize: true })
return
}
// Singleton pattern - reuse existing instance or create new one
if (!HamalronTarotDeckManager._instance) {
HamalronTarotDeckManager._instance = new HamalronTarotDeckManager()
}
HamalronTarotDeckManager._instance.render(true, { focus: true })
return HamalronTarotDeckManager._instance
}
}
+88
View File
@@ -0,0 +1,88 @@
export const SYSTEM_ID = "fvtt-hamalron"
export const ASCII = `
, _
/| | | |
|___| __, _ _ _ __, | | ,_ __ _ _
| |\/ | / |/ |/ | / | |/ / | / \_/ |/ |
| |/\_/|_/ | | |_/\_/|_/|__/ |_/\__/ | |_/
`
export const TAROT_SYMBOLES = {
"coupe": { id: "coupe", label: "Coupe" },
"epee": { id: "epee", label: "Epée" },
"denier": { id: "denier", label: "Denier" },
"baton": { id: "baton", label: "Bâton" },
}
export const TAROT_TYPES = {
"atout": { id: "atout", label: "Atout" },
"figure": { id: "figure", label: "Figure" },
"numéro": { id: "numéro", label: "Numéro" },
}
export const TAROT_VALEURS = {
"roi": { id: "roi", label: "Roi", value: 14 },
"reine": { id: "reine", label: "Reine", value: 13 },
"cavalier": { id: "cavalier", label: "Cavalier", value: 12 },
"valet": { id: "valet", label: "Valet", value: 11 },
"10": { id: "10", label: "10", value: 10 },
"9": { id: "9", label: "9", value: 9 },
"8": { id: "8", label: "8", value: 8 },
"7": { id: "7", label: "7", value: 7 },
"6": { id: "6", label: "6", value: 6 },
"5": { id: "5", label: "5", value: 5 },
"4": { id: "4", label: "4", value: 4 },
"3": { id: "3", label: "3", value: 3 },
"2": { id: "2", label: "2", value: 2 },
"1": { id: "1", label: "1", value: 1 },
}
export const DIFFICULTY_CHOICES = {
"tresfacile": { id: "tresfacile", label: "Très Facile", standard: 3, "soustension": -4 },
"facile": { id: "facile", label: "Facile", standard: 5, "soustension": -2 },
"moyenne": { id: "moyenne", label: "Moyenne", standard: 7, "soustension": 0 },
"difficile": { id: "difficile", label: "Difficile", standard: 10, "soustension": 3 },
"tresdifficile": { id: "tresdifficile", label: "Très Difficile", standard: 13, "soustension": 6 },
"heroique": { id: "heroique", label: "Héroïque", standard: 16, "soustension": 9 },
}
export const WEAPON_TYPES = {
"melee": { id: "melee", label: "Mêlée" },
"tir": { id: "tir", label: "Tir" },
}
export const COMPETENCE_TYPES = {
"generale": { id: "generale", label: "Générale" },
"faction": { id: "faction", label: "Faction" },
"peuple": { id: "peuple", label: "Peuple" },
}
export const ENEMY_TYPES = {
"mook": { id: "mook", label: "Sbire" },
"elite": { id: "elite", label: "Élite" },
"boss": { id: "boss", label: "Boss" },
}
export const SORTILEGE_CATEGORIES = {
"malefica": { id: "malefica", label: "Maléfica" },
"ritual": { id: "ritual", label: "Rituel" },
}
/**
* Include all constant definitions within the SYSTEM global export
* @type {Object}
*/
export const SYSTEM = {
id: SYSTEM_ID,
TAROT_SYMBOLES: TAROT_SYMBOLES,
DIFFICULTY_CHOICES: DIFFICULTY_CHOICES,
TAROT_TYPES: TAROT_TYPES,
TAROT_VALEURS: TAROT_VALEURS,
WEAPON_TYPES: WEAPON_TYPES,
COMPETENCE_TYPES: COMPETENCE_TYPES,
ENEMY_TYPES: ENEMY_TYPES,
SORTILEGE_CATEGORIES: SORTILEGE_CATEGORIES,
ASCII
}
+4
View File
@@ -0,0 +1,4 @@
export { default as HamalronActor } from "./actor.mjs"
export { default as HamalronItem } from "./item.mjs"
export { default as HamalronRoll } from "./tirage-tarot.mjs"
export { default as HamalronChatMessage } from "./chat-message.mjs"
+43
View File
@@ -0,0 +1,43 @@
import HamalronUtils from "../utils.mjs"
export default class HamalronActor extends Actor {
static async create(data, options) {
// Case of compendium global import
if (data instanceof Array) {
return super.create(data, options);
}
// If the created actor has items (only applicable to duplicated actors) bypass the new actor creation logic
if (data.items) {
let actor = super.create(data, options);
return actor;
}
if (data.type === 'character') {
}
return super.create(data, options);
}
_onUpdate(changed, options, userId) {
return super._onUpdate(changed, options, userId)
}
async _preCreate(data, options, user) {
await super._preCreate(data, options, user)
// Configure prototype token settings
const prototypeToken = {}
if (this.type === "character") {
Object.assign(prototypeToken, {
sight: { enabled: true },
actorLink: true,
disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY,
})
this.updateSource({ prototypeToken })
}
}
}
+21
View File
@@ -0,0 +1,21 @@
import HamalronTirageTarot from "./tirage-tarot.mjs"
export default class HamalronChatMessage extends ChatMessage {
async _renderRollContent(messageData) {
const data = messageData.message
if (this.rolls[0] instanceof HamalronRoll) {
const isPrivate = !this.isContentVisible
// _renderRollHTML va appeler render sur tous les rolls
const rollHTML = await this._renderRollHTML(isPrivate)
if (isPrivate) {
data.flavor = game.i18n.format("CHAT.PrivateRollContent", { user: this.user.name })
messageData.isWhisper = false
messageData.alias = this.user.name
}
data.content = `<section class="dice-rolls">${rollHTML}</section>`
return
}
return super._renderRollContent(messageData)
}
}
+21
View File
@@ -0,0 +1,21 @@
export const defaultItemImg = {
arme: "systems/fvtt-hamalron/assets/icons/arme.webp",
armure: "systems/fvtt-hamalron/assets/icons/armure.webp",
competence: "systems/fvtt-hamalron/assets/icons/competence.webp",
equipement: "systems/fvtt-hamalron/assets/icons/equipement.webp",
faction: "systems/fvtt-hamalron/assets/icons/faction.webp",
langue: "systems/fvtt-hamalron/assets/icons/langue.webp",
peuple: "systems/fvtt-hamalron/assets/icons/peuple.webp",
region: "systems/fvtt-hamalron/assets/icons/region.webp",
sortilege: "systems/fvtt-hamalron/assets/icons/sortilege.webp",
tarot: "systems/fvtt-hamalron/assets/icons/tarot.webp",
}
export default class HamalronItem extends Item {
constructor(data, context) {
if (!data.img) {
data.img = defaultItemImg[data.type];
}
super(data, context);
}
}
+290
View File
@@ -0,0 +1,290 @@
import { SYSTEM } from "../config/system.mjs"
export default class HamalronTirageTarot {
/**
* The HTML template path used to render dice checks of this type
* @type {string}
*/
static CHAT_TEMPLATE = "systems/fvtt-hamalron/templates/chat-message.hbs"
static async prompt(options = {}) {
let formula = `3D6 + 0D6KH - 0D6KH + ${options?.rollItem?.value}`
let actor = game.actors.get(options.actorId)
switch (options.rollType) {
case "stat":
break
case "damage":
{
let formula = options.rollItem.system.damage
if (options.rollItem?.system?.damageStat && options.rollItem.system.damageStat !== "none" && options.rollItem.system.damageStat !== "") {
let statKey = options.rollItem.system?.damageStat.toLowerCase()
let statValue = actor.system.stats[statKey].value
formula = `${formula} + ${statValue}`
}
let damageRoll = new Roll(formula)
await damageRoll.evaluate()
await damageRoll.toMessage({
flavor: `<div class="flavor-text-damage"><h2>${options.rollItem.name}</h2>
<BR><span class="chat-damage-type"><span>${options.rollItem.system.damageType}</span></span></div>`,
});
return
}
case "weapon":
{
options.weapon = foundry.utils.duplicate(options.rollItem)
let statKey = options.weapon.system.stat.toLowerCase()
options.rollItem = actor.system.stats[statKey]
}
break
default:
break
}
const rollModes = foundry.utils.duplicate(CONFIG.Dice.rollModes)
const fieldRollMode = new foundry.data.fields.StringField({
choices: rollModes,
blank: false,
default: "public",
})
options.formula = formula
options.nbAdvantages = "0"
options.nbDisadvantages = "0"
let dialogContext = {
actorId: options.actorId,
actorName: options.actorName,
rollType: options.rollType,
rollItem: foundry.utils.duplicate(options.rollItem), // Object only, no class
weapon: options?.weapon,
formula: formula,
fullFormula: formula,
rollModes,
fieldRollMode,
choiceAdvantages: SYSTEM.CHOICE_ADVANTAGES_DISADVANTAGES,
choiceDisadvantages: SYSTEM.CHOICE_ADVANTAGES_DISADVANTAGES,
hasTarget: options.hasTarget,
difficulty: "0",
nbAdvantages: "0",
nbDisadvantages: "0",
}
const content = await foundry.applications.handlebars.renderTemplate("systems/fvtt-hamalron/templates/roll-dialog.hbs", dialogContext)
const title = HamalronRoll.createTitle(options.rollType, options.rollTarget)
const label = game.i18n.localize("HAMALRON.Roll.roll")
const rollContext = await foundry.applications.api.DialogV2.wait({
window: { title: title },
classes: ["fvtt-hamalron"],
content,
buttons: [
{
label: label,
callback: (event, button, dialog) => {
const output = Array.from(button.form.elements).reduce((obj, input) => {
if (input.name) obj[input.name] = input.value
return obj
}, {})
return output
},
},
],
actions: {
},
rejectClose: false, // Click on Close button will not launch an error
render: (event, dialog) => {
$(".roll-stat-advantages").change(event => {
options.nbAdvantages = Number(event.target.value)
HamalronRoll.updateFullFormula(options)
})
$(".roll-stat-disadvantages").change(event => {
options.nbDisadvantages = Number(event.target.value)
HamalronRoll.updateFullFormula(options)
})
$(".select-combat-option").change(event => {
let field = $(event.target).data("field")
let modifier = SYSTEM.ATTACK_MODIFIERS[field]
if (event.target.checked) {
options.numericModifier += modifier
} else {
options.numericModifier -= modifier
}
HamalronRoll.updateFullFormula(options)
})
}
})
// If the user cancels the dialog, exit
if (rollContext === null) return
let rollData = foundry.utils.mergeObject(foundry.utils.duplicate(options), rollContext)
rollData.rollMode = rollContext.visibility
rollData.targetScore = 8
if (Hooks.call("fvtt-hamalron.preRoll", options, rollData) === false) return
options.nbAdvantages = Number(options.nbAdvantages)
options.nbDisadvantages = Number(options.nbDisadvantages)
let dice = 3;
let keep = ""
if ((options.nbAdvantages !== options.nbDisadvantages) && options.nbAdvantages > 0 || options.nbDisadvantages > 0) {
dice = 3 + Math.abs(options.nbAdvantages - options.nbDisadvantages)
if (options.nbAdvantages > options.nbDisadvantages) {
keep = "kh3"
} else if (options.nbAdvantages < options.nbDisadvantages) {
keep = "kl3"
}
}
let diceFormula = `${dice}D6${keep} + ${options.rollItem.value}`
const roll = new this(diceFormula, options.data, rollData)
await roll.evaluate()
console.log("Roll", rollData, roll)
options.difficulty = (rollData.difficulty === "unknown") ? 0 : (Number(rollData.difficulty) || 0)
roll.displayRollResult(roll, options, rollData, roll)
if (Hooks.call("fvtt-hamalron.Roll", options, rollData, roll) === false) return
return roll
}
displayRollResult(formula, options, rollData, roll) {
let resultType = "failure"
if (options.difficulty === 0) {
resultType = "unknown"
} else if (this.total >= options.difficulty) {
resultType = "success"
}
// Compute the result quality
this.options.satanicSuccess = false
this.options.fiendishFailure = false
this.options.rollData = foundry.utils.duplicate(rollData)
// Check if all results are equal
let workResults = foundry.utils.duplicate(roll.terms[0].results)
// Get the most common result of the roll
let commonResult = workResults.reduce((acc, r) => {
acc[r.result] = (acc[r.result] || 0) + 1
return acc
}, {})
commonResult = Object.entries(commonResult).reduce((a, b) => (a[1] > b[1]) ? a : b)[0]
let nbEqual = workResults.filter(r => Number(r.result) === Number(commonResult)).length
if (resultType === "success" && commonResult >= 4 && nbEqual >= 3) {
this.options.satanicSuccess = true
if (this.options.satanicSuccess) {
resultType = "success"
}
}
if (resultType === "failure" && commonResult <= 3 && nbEqual >= 3) {
this.options.fiendishFailure = true
if (this.options.fiendishFailure) {
resultType = "failure"
}
}
this.options.resultType = resultType
this.options.isSuccess = resultType === "success"
this.options.isFailure = resultType === "failure"
this.options.results = roll.terms[0].results
}
/**
* Creates a title based on the given type.
*
* @param {string} type The type of the roll.
* @param {string} target The target of the roll.
* @returns {string} The generated title.
*/
static createTitle(type, target) {
switch (type) {
case "stat":
return `${game.i18n.localize("HAMALRON.Label.titleStat")}`
case "weapon":
return `${game.i18n.localize("HAMALRON.Label.titleWeapon")}`
default:
return game.i18n.localize("HAMALRON.Label.titleStandard")
}
}
/** @override */
async render(chatOptions = {}) {
let chatData = await this._getChatCardData(chatOptions.isPrivate)
return await foundry.applications.handlebars.renderTemplate(this.constructor.CHAT_TEMPLATE, chatData)
}
/**
* Generates the data required for rendering a roll chat card.
*
* @param {boolean} isPrivate Indicates if the chat card is private.
* @returns {Promise<Object>} A promise that resolves to an object containing the chat card data.
* @property {Array<string>} css - CSS classes for the chat card.
* @property {Object} data - The data associated with the roll.
* @property {number} diceTotal - The total value of the dice rolled.
* @property {boolean} isGM - Indicates if the user is a Game Master.
* @property {string} formula - The formula used for the roll.
* @property {number} total - The total result of the roll.
* @property {boolean} isFailure - Indicates if the roll is a failure.
* @property {string} actorId - The ID of the actor performing the roll.
* @property {string} actingCharName - The name of the character performing the roll.
* @property {string} actingCharImg - The image of the character performing the roll.
* @property {string} resultType - The type of result (e.g., success, failure).
* @property {boolean} hasTarget - Indicates if the roll has a target.
* @property {string} targetName - The name of the target.
* @property {number} targetArmor - The armor value of the target.
* @property {number} realDamage - The real damage dealt.
* @property {boolean} isPrivate - Indicates if the chat card is private.
* @property {string} cssClass - The combined CSS classes as a single string.
* @property {string} tooltip - The tooltip text for the chat card.
*/
async _getChatCardData(isPrivate) {
let cardData = foundry.utils.duplicate(this.options)
cardData.css = [SYSTEM.id, "dice-roll"]
cardData.data = this.data
cardData.diceTotal = this.dice.reduce((t, d) => t + d.total, 0)
cardData.isGM = game.user.isGM
cardData.formula = this.formula
cardData.fullFormula = this.options.fullFormula
cardData.numericModifier = this.options.numericModifier
cardData.total = this.total
cardData.actorId = this.actorId
cardData.actingCharName = this.actorName
cardData.actingCharImg = this.actorImage
cardData.resultType = this.resultType
cardData.hasTarget = this.hasTarget
cardData.targetName = this.targetName
cardData.targetArmor = this.targetArmor
cardData.realDamage = this.realDamage
cardData.isPrivate = isPrivate
cardData.weapon = this.weapon
cardData.cssClass = cardData.css.join(" ")
cardData.tooltip = isPrivate ? "" : await this.getTooltip()
return cardData
}
/**
* Converts the roll result to a chat message.
*
* @param {Object} [messageData={}] Additional data to include in the message.
* @param {Object} options Options for message creation.
* @param {string} options.rollMode The mode of the roll (e.g., public, private).
* @param {boolean} [options.create=true] Whether to create the message.
* @returns {Promise} - A promise that resolves when the message is created.
*/
async toMessage(messageData = {}, { rollMode, create = true } = {}) {
super.toMessage(
{
isFailure: this.resultType === "failure",
actingCharName: this.actorName,
actingCharImg: this.actorImage,
hasTarget: this.hasTarget,
realDamage: this.realDamage,
...messageData,
},
{ rollMode: rollMode },
)
}
}
+12
View File
@@ -0,0 +1,12 @@
export { default as HamalronArme } from "./arme.mjs"
export { default as HamalronArmure } from "./armure.mjs"
export { default as HamalronCompetence } from "./competence.mjs"
export { default as HamalronEquipement } from "./equipement.mjs"
export { default as HamalronFaction } from "./faction.mjs"
export { default as HamalronLangue } from "./langue.mjs"
export { default as HamalronPersonnage } from "./personnage.mjs"
export { default as HamalronPeuple } from "./peuple.mjs"
export { default as HamalronPNJ } from "./pnj.mjs"
export { default as HamalronRegion } from "./region.mjs"
export { default as HamalronSortilege } from "./sortilege.mjs"
export { default as HamalronTarot } from "./tarot.mjs"
+25
View File
@@ -0,0 +1,25 @@
import { SYSTEM } from "../config/system.mjs"
export default class HamalronArme extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const schema = {}
const requiredInteger = { required: true, nullable: false, integer: true }
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.weaponType = new fields.StringField({ required: true, initial: "melee", choices: Object.fromEntries(Object.entries(SYSTEM.WEAPON_TYPES).map(([key, val]) => [key, val.label])) })
schema.cout = new fields.NumberField({ required: true, initial: 0, min: 0 })
return schema
}
/** @override */
static LOCALIZATION_PREFIXES = ["HAMALRON.Arme"]
prepareDerivedData() {
super.prepareDerivedData();
let actor = this.parent?.actor;
}
}
+20
View File
@@ -0,0 +1,20 @@
import { SYSTEM } from "../config/system.mjs"
export default class HamalronArmure extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const schema = {}
const requiredInteger = { required: true, nullable: false, integer: true }
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.protection = new fields.NumberField({ required: true, initial: 0, min: 0, integer: true })
schema.cost = new fields.NumberField({ required: true, initial: 0, min: 0 })
return schema
}
/** @override */
static LOCALIZATION_PREFIXES = ["HAMALRON.Armure"]
}
+18
View File
@@ -0,0 +1,18 @@
import { SYSTEM } from "../config/system.mjs";
export default class HamalronCompetence extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
const schema = {};
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.type = new fields.StringField({ required: true, initial: "generale", choices: Object.fromEntries(Object.entries(SYSTEM.COMPETENCE_TYPES).map(([key, val]) => [key, val.label])) });
schema.carteSymbole = new fields.StringField({ required: true, initial: "coupe", choices: Object.fromEntries(Object.entries(SYSTEM.TAROT_SYMBOLES).map(([key, val]) => [key, val.label])) });
schema.niveau = new fields.NumberField({ required: true, initial: 0, min: 0, integer: true });
return schema;
}
/** @override */
static LOCALIZATION_PREFIXES = ["HAMALRON.Competence"];
}
+20
View File
@@ -0,0 +1,20 @@
import { SYSTEM } from "../config/system.mjs"
export default class HamalronEquipement extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const schema = {}
const requiredInteger = { required: true, nullable: false, integer: true }
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.quantity = new fields.NumberField({ required: true, initial: 1, min: 0, integer: true })
schema.cost = new fields.NumberField({ required: true, initial: 0, min: 0 })
return schema
}
/** @override */
static LOCALIZATION_PREFIXES = ["HAMALRON.Equipement"]
}
+15
View File
@@ -0,0 +1,15 @@
import { SYSTEM } from "../config/system.mjs";
export default class HamalronFaction extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
const schema = {};
schema.description = new fields.HTMLField({ required: true, textSearch: true })
return schema;
}
/** @override */
static LOCALIZATION_PREFIXES = ["HAMALRON.Faction"];
}
+16
View File
@@ -0,0 +1,16 @@
import { SYSTEM } from "../config/system.mjs";
export default class HamalronFaction extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
const schema = {};
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.niveau = new fields.NumberField({ required: true, initial: 0, min: 0, integer: true });
return schema;
}
/** @override */
static LOCALIZATION_PREFIXES = ["HAMALRON.Faction"];
}
+84
View File
@@ -0,0 +1,84 @@
import { SYSTEM } from "../config/system.mjs"
import HamalronTirageTarot from "../documents/tirage-tarot.mjs"
export default class HamalronPersonnage extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const requiredInteger = { required: true, nullable: false, integer: true }
const schema = {}
// Stats
const symboleCarte = (label) => {
const schema = {
label: new fields.StringField({ required: true, initial: label }),
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
}
return new fields.SchemaField(schema, { label })
}
schema.cartesSucces = new fields.SchemaField(
Object.values(SYSTEM.TAROT_SYMBOLES).reduce((obj, stat) => {
obj[stat.id] = symboleCarte(stat.label)
return obj
}, {}),
)
schema.resistances = new fields.SchemaField(
Object.values(SYSTEM.TAROT_SYMBOLES).reduce((obj, stat) => {
obj[stat.id] = symboleCarte(stat.label)
return obj
}, {}),
)
schema.historial = new fields.HTMLField({ required: true, textSearch: true })
schema.progression = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
schema.biodata = new fields.SchemaField({
age: new fields.StringField({ required: true, nullable: false, initial: "" }),
gender: new fields.StringField({ required: true, nullable: false, initial: "" }),
height: new fields.StringField({ required: true, nullable: false, initial: "" }),
eyes: new fields.StringField({ required: true, nullable: false, initial: "" }),
birthplace: new fields.StringField({ required: true, nullable: false, initial: "" }),
hair: new fields.StringField({ required: true, nullable: false, initial: "" }),
home: new fields.StringField({ required: true, nullable: false, initial: "" }),
weight: new fields.StringField({ required: true, nullable: false, initial: "" }),
})
return schema
}
/** @override */
static LOCALIZATION_PREFIXES = ["HAMALRON.Personnage"]
prepareDerivedData() {
super.prepareDerivedData();
}
/** */
/**
* Rolls a dice for a character.
* @param {("save"|"resource|damage")} rollType The type of the roll.
* @param {number} rollItem The target value for the roll. Which caracteristic or resource. If the roll is a damage roll, this is the id of the item.
* @returns {Promise<null>} - A promise that resolves to null if the roll is cancelled.
*/
async roll(rollType, rollItem) {
let opponentTarget
const hasTarget = opponentTarget !== undefined
let roll = await HamalronTirageTarot.prompt({
rollType,
rollItem,
actorId: this.parent.id,
actorName: this.parent.name,
actorImage: this.parent.img,
hasTarget,
target: opponentTarget
})
if (!roll) return null
await roll.toMessage({}, { rollMode: roll.options.rollMode })
}
}
+15
View File
@@ -0,0 +1,15 @@
import { SYSTEM } from "../config/system.mjs";
export default class HamalronPeuple extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
const schema = {};
schema.description = new fields.HTMLField({ required: true, textSearch: true })
return schema;
}
/** @override */
static LOCALIZATION_PREFIXES = ["HAMALRON.Peuple"];
}
+45
View File
@@ -0,0 +1,45 @@
import { SYSTEM } from "../config/system.mjs"
import HamalronTirageTarot from "../documents/tirage-tarot.mjs"
export default class HamalronPNJ extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const requiredInteger = { required: true, nullable: false, integer: true }
const schema = {}
schema.enemyType = new fields.StringField({
required: true,
initial: "mook",
choices: Object.fromEntries(Object.entries(SYSTEM.ENEMY_TYPES).map(([key, val]) => [key, val.label]))
})
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.notes = new fields.HTMLField({ required: true, textSearch: true })
return schema
}
/** @override */
static LOCALIZATION_PREFIXES = ["HAMALRON.PNJ"]
async roll(rollType, rollItem) {
let opponentTarget
const hasTarget = opponentTarget !== undefined
let roll = await HamalronTirageTarot.prompt({
rollType,
rollItem,
actorId: this.parent.id,
actorName: this.parent.name,
actorImage: this.parent.img,
traits: this.parent.items.filter(i => i.type === "trait"),
hasTarget,
target: opponentTarget
})
if (!roll) return null
await roll.toMessage({}, { rollMode: roll.options.rollMode })
}
}
+17
View File
@@ -0,0 +1,17 @@
import { SYSTEM } from "../config/system.mjs"
export default class HamalronRegion extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const schema = {}
const requiredInteger = { required: true, nullable: false, integer: true }
schema.description = new fields.HTMLField({ required: true, textSearch: true })
return schema
}
/** @override */
static LOCALIZATION_PREFIXES = ["HAMALRON.Region"]
}
+18
View File
@@ -0,0 +1,18 @@
import { SYSTEM } from "../config/system.mjs"
export default class HamalronSortilege extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const schema = {}
const requiredInteger = { required: true, nullable: false, integer: true }
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.categorie = new fields.StringField({ required: true, initial: "malefica", choices: Object.fromEntries(Object.entries(SYSTEM.SORTILEGE_CATEGORIES).map(([key, val]) => [key, val.label])) })
return schema
}
/** @override */
static LOCALIZATION_PREFIXES = ["HAMALRON.Ritual"]
}
+26
View File
@@ -0,0 +1,26 @@
import { SYSTEM } from "../config/system.mjs";
export default class HamalronTarot extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
const schema = {};
schema.type = new fields.StringField({ required: true, nullable: false, choices: Object.fromEntries(Object.entries(SYSTEM.TAROT_TYPES).map(([key, val]) => [key, val.label])), initial: "atout" });
schema.symbole = new fields.StringField({ required: true, nullable: false, choices: Object.fromEntries(Object.entries(SYSTEM.TAROT_SYMBOLES).map(([key, val]) => [key, val.label])), initial: "coupe" });
schema.valeur = new fields.StringField({
required: true, nullable: false, choices: Object.fromEntries(Object.entries(SYSTEM.TAROT_VALEURS).map(([key, val]) => [key, val.label])), initial: "1"
});
schema.description = new fields.HTMLField({ required: true, textSearch: true });
schema.image = new fields.FilePathField({
required: false,
categories: ["IMAGE"],
default: "icons/svg/treasure.svg",
})
return schema;
}
/** @override */
static LOCALIZATION_PREFIXES = ["HAMALRON.Tarot"];
}
+13
View File
@@ -0,0 +1,13 @@
/**
* Handles socket events based on the provided action.
*
* @param {Object} [params={}] The parameters for the socket event.
* @param {string|null} [params.action=null] The action to be performed.
* @param {Object} [params.data={}] The data associated with the action.
* @returns {*} The result of the action handler, if applicable.
*/
export function handleSocketEvent({ action = null, data = {} } = {}) {
console.debug("handleSocketEvent", action, data)
}
+196
View File
@@ -0,0 +1,196 @@
import { SYSTEM } from "./config/system.mjs"
export default class HamalronUtils {
static registerSettings() {
game.settings.register("fvtt-hamalron", "settings-era", {
name: game.i18n.localize("HAMALRON.Settings.era"),
hint: game.i18n.localize("HAMALRON.Settings.eraHint"),
default: "jazz",
scope: "world",
type: String,
choices: SYSTEM.AVAILABLE_SETTINGS,
config: true,
onChange: _ => window.location.reload()
});
}
static async loadCompendiumData(compendium) {
const pack = game.packs.get(compendium)
return await pack?.getDocuments() ?? []
}
static async loadCompendium(compendium, filter = item => true) {
let compendiumData = await HamalronUtils.loadCompendiumData(compendium)
return compendiumData.filter(filter)
}
static registerHandlebarsHelpers() {
Handlebars.registerHelper('isNull', function (val) {
return val == null;
});
Handlebars.registerHelper('exists', function (val) {
return val != null && val !== undefined;
});
Handlebars.registerHelper('isEmpty', function (list) {
if (list) return list.length === 0;
else return false;
});
Handlebars.registerHelper('notEmpty', function (list) {
return list.length > 0;
});
Handlebars.registerHelper('isNegativeOrNull', function (val) {
return val <= 0;
});
Handlebars.registerHelper('isNegative', function (val) {
return val < 0;
});
Handlebars.registerHelper('isPositive', function (val) {
return val > 0;
});
Handlebars.registerHelper('equals', function (val1, val2) {
return val1 === val2;
});
Handlebars.registerHelper('neq', function (val1, val2) {
return val1 !== val2;
});
Handlebars.registerHelper('gt', function (val1, val2) {
return val1 > val2;
})
Handlebars.registerHelper('lt', function (val1, val2) {
return val1 < val2;
})
Handlebars.registerHelper('gte', function (val1, val2) {
return val1 >= val2;
})
Handlebars.registerHelper('lte', function (val1, val2) {
return val1 <= val2;
})
Handlebars.registerHelper('and', function (val1, val2) {
return val1 && val2;
})
Handlebars.registerHelper('or', function (val1, val2) {
return val1 || val2;
})
Handlebars.registerHelper('or3', function (val1, val2, val3) {
return val1 || val2 || val3;
})
Handlebars.registerHelper('for', function (from, to, incr, block) {
let accum = '';
for (let i = from; i < to; i += incr)
accum += block.fn(i);
return accum;
})
Handlebars.registerHelper('not', function (cond) {
return !cond;
})
Handlebars.registerHelper('count', function (list) {
return list.length;
})
Handlebars.registerHelper('countKeys', function (obj) {
return Object.keys(obj).length;
})
Handlebars.registerHelper('isEnabled', function (configKey) {
return game.settings.get("bol", configKey);
})
Handlebars.registerHelper('split', function (str, separator, keep) {
return str.split(separator)[keep];
})
// If you need to add Handlebars helpers, here are a few useful examples:
Handlebars.registerHelper('concat', function () {
let outStr = '';
for (let arg in arguments) {
if (typeof arguments[arg] != 'object') {
outStr += arguments[arg];
}
}
return outStr;
})
Handlebars.registerHelper('add', function (a, b) {
return parseInt(a) + parseInt(b);
});
Handlebars.registerHelper('mul', function (a, b) {
return parseInt(a) * parseInt(b);
})
Handlebars.registerHelper('sub', function (a, b) {
return parseInt(a) - parseInt(b);
})
Handlebars.registerHelper('abbrev2', function (a) {
return a.substring(0, 2);
})
Handlebars.registerHelper('abbrev3', function (a) {
return a.substring(0, 3);
})
Handlebars.registerHelper('valueAtIndex', function (arr, idx) {
return arr[idx];
})
Handlebars.registerHelper('includesKey', function (items, type, key) {
return items.filter(i => i.type === type).map(i => i.system.key).includes(key);
})
Handlebars.registerHelper('includes', function (array, val) {
return array.includes(val);
})
Handlebars.registerHelper('eval', function (expr) {
return eval(expr);
})
Handlebars.registerHelper('isOwnerOrGM', function (actor) {
console.log("Testing actor", actor.isOwner, game.userId)
return actor.isOwner || game.isGM;
})
Handlebars.registerHelper('upperFirst', function (text) {
if (typeof text !== 'string') return text
return text.charAt(0).toUpperCase() + text.slice(1)
})
Handlebars.registerHelper('upperFirstOnly', function (text) {
if (typeof text !== 'string') return text
return text.charAt(0).toUpperCase()
})
Handlebars.registerHelper('isCreature', function (key) {
return key === "creature" || key === "daemon";
})
Handlebars.registerHelper('getRomanLevel', function (level) {
if (level=== "highpowers") return "High";
if (level === "mastery") return "Mastery";
level = parseInt(level);
if (level === 0) return "0";
if (level === 1) return "I";
if (level === 2) return "II";
if (level === 3) return "III";
if (level === 4) return "IV";
if (level === 5) return "V";
if (level === 6) return "VI";
if (level === 7) return "VII";
if (level === 8) return "VIII";
if (level === 9) return "IX";
return level;
})
// Handle v12 removal of this helper
Handlebars.registerHelper('select', function (selected, options) {
const escapedValue = RegExp.escape(Handlebars.escapeExpression(selected));
const rgx = new RegExp(' value=[\"\']' + escapedValue + '[\"\']');
const html = options.fn(this);
return html.replace(rgx, "$& selected");
});
}
}