Upgrade item sheets

This commit is contained in:
2024-12-03 13:51:38 +01:00
parent df8995f8b7
commit 9775ec9fa1
38 changed files with 1095 additions and 506 deletions

View File

@ -8,4 +8,5 @@ export { default as LethalFantasySaveSheet } from "./sheets/save-sheet.mjs"
export { default as LethalFantasyArmorSheet } from "./sheets/armor-sheet.mjs"
export { default as LethalFantasySpellSheet } from "./sheets/spell-sheet.mjs"
export { default as LethalFantasyEquipmentSheet } from "./sheets/equipment-sheet.mjs"
export { default as LethalFantasyShieldSheet } from "./sheets/shield-sheet.mjs"
export { default as LethalFantasyManager } from "./manager.mjs"

View File

@ -13,8 +13,6 @@ export default class LethalFantasyCharacterSheet extends LethalFantasyActorSheet
contentClasses: ["character-content"],
},
actions: {
deleteVoieMajeure: LethalFantasyCharacterSheet.#onDeleteVoieMajeure,
deleteVoieMineure: LethalFantasyCharacterSheet.#onDeleteVoieMineure,
createEquipment: LethalFantasyCharacterSheet.#onCreateEquipment,
},
}
@ -25,7 +23,7 @@ export default class LethalFantasyCharacterSheet extends LethalFantasyActorSheet
template: "systems/fvtt-lethal-fantasy/templates/character-main.hbs",
},
tabs: {
template: "templates/generic/tab-navigation.hbs",
template: "systems/fvtt-lethal-fantasy/templates/generic/tab-navigation.hbs",
},
items: {
template: "systems/fvtt-lethal-fantasy/templates/character-items.hbs",
@ -46,8 +44,8 @@ export default class LethalFantasyCharacterSheet extends LethalFantasyActorSheet
*/
#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" },
items: { id: "items", group: "sheet", icon: "fa-solid fa-shapes", label: "LETHALFANTASY.Character.Label.details" },
biography: { id: "biography", group: "sheet", icon: "fa-solid fa-book", label: "LETHALFANTASY.Character.Label.biography" },
}
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id
@ -62,105 +60,17 @@ export default class LethalFantasyCharacterSheet extends LethalFantasyActorSheet
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 */
@ -172,9 +82,6 @@ export default class LethalFantasyCharacterSheet extends LethalFantasyActorSheet
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
@ -183,40 +90,12 @@ export default class LethalFantasyCharacterSheet extends LethalFantasyActorSheet
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
/**
@ -244,43 +123,6 @@ export default class LethalFantasyCharacterSheet extends LethalFantasyActorSheet
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.
@ -290,11 +132,11 @@ export default class LethalFantasyCharacterSheet extends LethalFantasyActorSheet
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" }])
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("LETHALFANTASY.Label.newArmor"), type: "armor" }])
}
// Création d'une arme
else {
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("TENEBRIS.Label.newWeapon"), type: "weapon" }])
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("LETHALFANTASY.Label.newWeapon"), type: "weapon" }])
}
}

View File

@ -22,7 +22,7 @@ export default class LethalFantasyGiftSheet extends LethalFantasyItemSheet {
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.enrichedDescription = await TextEditor.enrichHTML(this.document.system.improvedDescription, { async: true })
context.enrichedDescription = await TextEditor.enrichHTML(this.document.system.description, { async: true })
return context
}
}

View File

@ -0,0 +1,27 @@
import LethalFantasyItemSheet from "./base-item-sheet.mjs"
export default class LethalFantasyShieldSheet extends LethalFantasyItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["shield"],
position: {
width: 620,
},
window: {
contentClasses: ["shield-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-lethal-fantasy/templates/shield.hbs",
},
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
return context
}
}

View File

@ -5,7 +5,7 @@ export default class LethalFantasyWeaponSheet extends LethalFantasyItemSheet {
static DEFAULT_OPTIONS = {
classes: ["weapon"],
position: {
width: 400,
width: 620,
},
window: {
contentClasses: ["weapon-content"],

View File

@ -1,5 +1,5 @@
export const CATEGORY = Object.freeze({
sommaire: { id: "sommaire", label: "LETHALFANTASY.Armor.Category.sommaire" },
legere: { id: "legere", label: "LETHALFANTASY.Armor.Category.legere" },
guerre: { id: "guerre", label: "LETHALFANTASY.Armor.Category.guerre" },
export const TYPE = Object.freeze({
light: { id: "light", label: "LETHALFANTASY.Armor.Category.light" },
medium: { id: "medium", label: "LETHALFANTASY.Armor.Category.medium" },
heavy: { id: "medium", label: "LETHALFANTASY.Armor.Category.heavy" }
})

View File

@ -0,0 +1,51 @@
export const CATEGORY = Object.freeze({
essentialkit: {
id: "essentialkit",
label: "LETHALFANTASY.EquipmentCategories.EssentialKit",
},
classkit: {
id: "classkit",
label: "LETHALFANTASY.EquipmentCategories.ClassKit",
},
clothing: {
id: "clothing",
label: "LETHALFANTASY.EquipmentCategories.Clothing",
},
music: {
id: "music",
label: "LETHALFANTASY.EquipmentCategories.Music",
},
sleeping: {
id: "sleeping",
label: "LETHALFANTASY.EquipmentCategories.Sleeping",
},
landtransport: {
id: "landtransport",
label: "LETHALFANTASY.EquipmentCategories.LandTransport",
},
watertransport: {
id: "watertransport",
label: "LETHALFANTASY.EquipmentCategories.WaterTransport",
},
mount: {
id: "mount",
label: "LETHALFANTASY.EquipmentCategories.Mount",
},
light: {
id: "light",
label: "LETHALFANTASY.EquipmentCategories.Light",
},
loadbearing: {
id: "loadbearing",
label: "LETHALFANTASY.EquipmentCategories.LoadBearing",
},
misc: {
id: "misc",
label: "LETHALFANTASY.EquipmentCategories.Misc",
},
fooddrink: {
id: "fooddrink",
label: "LETHALFANTASY.EquipmentCategories.FoodDrink",
},
})

View File

@ -3,6 +3,7 @@ import * as WEAPON from "./weapon.mjs"
import * as ARMOR from "./armor.mjs"
import * as SPELL from "./spell.mjs"
import * as SKILL from "./skill.mjs"
import * as EQUIPMENT from "./equipment.mjs"
export const SYSTEM_ID = "fvtt-lethal-fantasy"
export const DEV_MODE = false
@ -81,8 +82,10 @@ export const SYSTEM = {
SKILL_CATEGORY: SKILL.CATEGORY,
WEAPON_CATEGORY: WEAPON.CATEGORY,
WEAPON_DAMAGE: WEAPON.DAMAGE,
ARMOR_CATEGORY: ARMOR.CATEGORY,
ARMOR_TYPE: ARMOR.TYPE,
EQUIPMENT_CATEGORY: EQUIPMENT.CATEGORY,
SPELL_RANGE: SPELL.RANGE,
WEAPON_TYPE: WEAPON.WEAPON_TYPE,
MONEY,
ASCII,
ROLL_TYPE,

View File

@ -21,6 +21,11 @@ export const CATEGORY = Object.freeze({
},
})
export const WEAPON_TYPE = {
"melee": "LETHALFANTASY.Weapon.WeaponType.melee",
"ranged": "LETHALFANTASY.Weapon.WeaponType.ranged"
}
export const DAMAGE = Object.freeze({
UN: "1",
D4: "d4",

View File

@ -4,6 +4,8 @@ export { default as LethalFantasyWeapon } from "./weapon.mjs"
export { default as LethalFantasySpell } from "./spell.mjs"
export { default as LethalFantasySkill } from "./skill.mjs"
export { default as LethalFantasyArmor } from "./armor.mjs"
export { default as LethalFantasyShield } from "./shield.mjs"
export { default as LethalFantasyGift } from "./gift.mjs"
export { default as LethalFantasyVulnerability } from "./vulnerability.mjs"
export { default as LethalFantasySave } from "./save.mjs"
export { default as LethalFantasyEquipment } from "./equipment.mjs"

View File

@ -3,13 +3,17 @@ export default class LethalFantasyArmor 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.category = new fields.StringField({ required: true, initial: "melee", choices: SYSTEM.ARMOR_CATEGORY })
schema.protection = new fields.StringField({
required: true,
initial: ""
})
schema.armortype = new fields.StringField({ required: true, initial: "light", choices: SYSTEM.ARMOR_TYPE })
schema.defense = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: -50 })
schema.movementreduction = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
schema.hp = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
schema.damagereduction = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
schema.load = new fields.StringField({ required: true, initial: "L" })
schema.cost = new fields.NumberField({ required: true, initial: 0, min: 0 })
schema.money = new fields.StringField({ required: true, initial: "tinbit", choices: SYSTEM.MONEY })

View File

@ -7,11 +7,12 @@ export default class LethalFantasyEquipment extends foundry.abstract.TypeDataMod
const requiredInteger = { required: true, nullable: false, integer: true }
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.category = new fields.StringField({ required: true, initial: "melee", choices: SYSTEM.WEAPON_CATEGORY })
schema.damages = new fields.StringField({
required: true,
initial: ""
})
schema.category = new fields.StringField({ required: true, initial: "tinbit", choices: SYSTEM.EQUIPMENT_CATEGORIES })
schema.load = new fields.StringField({ required: true, initial: "L" })
schema.hi = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
schema.medium = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
schema.lo = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
schema.cost = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
schema.money = new fields.StringField({ required: true, initial: "tinbit", choices: SYSTEM.MONEY })
@ -21,7 +22,4 @@ export default class LethalFantasyEquipment extends foundry.abstract.TypeDataMod
/** @override */
static LOCALIZATION_PREFIXES = ["LETHALFANTASY.Equipment"]
get weaponCategory() {
return game.i18n.localize(CATEGORY[this.category].label)
}
}

41
module/models/shield.mjs Normal file
View File

@ -0,0 +1,41 @@
import { SYSTEM } from "../config/system.mjs"
export default class LethalFantasyShield 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.defense = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: -50 })
schema.movementreduction = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
schema.standing = new fields.SchemaField({
min: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
max: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
})
schema.crouching = new fields.SchemaField({
min: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
max: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
})
schema.destruction = new fields.SchemaField({
bashing: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
slashing: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
piercing: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
})
schema.autodestruction = new fields.SchemaField({
bashing: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
slashing: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
piercing: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
})
schema.load = new fields.StringField({ required: true, initial: "L" })
schema.cost = new fields.NumberField({ required: true, initial: 0, min: 0 })
schema.money = new fields.StringField({ required: true, initial: "tinbit", choices: SYSTEM.MONEY })
return schema
}
/** @override */
static LOCALIZATION_PREFIXES = ["LETHALFANTASY.Shield"]
}

View File

@ -21,4 +21,47 @@ export default class LethalFantasySkill extends foundry.abstract.TypeDataModel {
get skillCategory() {
return game.i18n.localize(CATEGORY[this.category].label)
}
prepareDerivedData() {
super.prepareDerivedData();
this.skillTotal = this.computeBase();
}
computeBase() {
let actor = this.parent?.actor;
if (!actor) {
return `${this.base } + ${ String(this.bonus)}`;
}
// Split the base value per stat : WIS,DEX,STR,INT,CHA (example)
const base = this.base;
let baseSplit = base.split(",");
let baseSplitLength = baseSplit.length;
if ( baseSplitLength > 0) {
// Select the max stat value from the parent actor
let maxStat = 0;
for (let i = 0; i < baseSplitLength; i++) {
const stat = baseSplit[i];
const statValue = actor.system.characteristics[stat.toLowerCase()]?.value || 0;
if (statValue > maxStat) {
maxStat = statValue;
}
}
return maxStat;
} else {
// Split with + calculate the total
baseSplit = base.split("+");
baseSplitLength = baseSplit.length;
if ( baseSplitLength > 0) {
let total = 0;
for (let i = 0; i < baseSplitLength; i++) {
const stat = baseSplit[i];
const statValue = actor.system.characteristics[stat.toLowerCase()]?.value || 0;
total += statValue;
}
return total
}
}
return `${this.base } + ${ String(this.bonus)}`;
}
}

View File

@ -18,6 +18,19 @@ export default class LethalFantasySpell extends foundry.abstract.TypeDataModel {
max: 20,
})
schema.cost = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
schema.components = new fields.SchemaField({
verbal: new fields.BooleanField(),
somatic: new fields.BooleanField(),
material: new fields.BooleanField(),
})
schema.castingTime = new fields.StringField({ required: true, initial: "" })
schema.range = new fields.StringField({ required: true, initial: "" })
schema.areaAffected = new fields.StringField({ required: true, initial: "" })
schema.duration = new fields.StringField({ required: true, initial: "" })
schema.savingThrow = new fields.StringField({ required: true, initial: "" })
schema.extraAetherPoints = new fields.StringField({ required: true, initial: "" })
return schema
}

View File

@ -7,11 +7,38 @@ export default class LethalFantasySkill extends foundry.abstract.TypeDataModel {
const requiredInteger = { required: true, nullable: false, integer: true }
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.category = new fields.StringField({ required: true, initial: "melee", choices: SYSTEM.WEAPON_CATEGORY })
schema.damages = new fields.StringField({
required: true,
initial: ""
schema.weaponType = new fields.StringField({ required: true, initial: "melee", choices: SYSTEM.WEAPON_TYPE })
schema.damageType = new fields.SchemaField({
typeP: new fields.BooleanField(),
typeB: new fields.BooleanField(),
typeS: new fields.BooleanField()
})
schema.damage = new fields.SchemaField({
damageS: new fields.StringField({required: true, initial: ""}),
damageM: new fields.StringField({required: true, initial: ""})
})
schema.hands = new fields.StringField({ required: true, initial: "1", choices: ["1", "2"] })
schema.defenseMax = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
schema.secondsToAttack = new fields.StringField({required: true, initial: ""})
schema.speed = new fields.SchemaField({
simpleAim: new fields.StringField({required: true, initial: ""}),
carefulAim: new fields.StringField({required: true, initial: ""}),
focusedAim: new fields.StringField({required: true, initial: ""})
})
schema.defense = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
schema.range = new fields.SchemaField({
pointBlank: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
short: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
medium: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
long: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
extreme: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
outOfSkill: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
})
schema.load = new fields.StringField({ required: true, initial: "L" })
schema.cost = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
schema.money = new fields.StringField({ required: true, initial: "tinbit", choices: SYSTEM.MONEY })