System development, WIP
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
export { default as MGNEActorSheet } from "./sheets/base-actor-sheet.mjs"
|
||||
export { default as MGNEItemSheet } from "./sheets/base-item-sheet.mjs"
|
||||
export { default as MGNECharacterSheet } from "./sheets/character-sheet.mjs"
|
||||
export { default as MGNECreatureSheet } from "./sheets/creature-sheet.mjs"
|
||||
export { default as MGNECompanionSheet } from "./sheets/companion-sheet.mjs"
|
||||
export { default as MGNEWeaponSheet } from "./sheets/weapon-sheet.mjs"
|
||||
export { default as MGNEArmorSheet } from "./sheets/armor-sheet.mjs"
|
||||
export { default as MGNEShieldSheet } from "./sheets/shield-sheet.mjs"
|
||||
export { default as MGNEEquipmentSheet } from "./sheets/equipment-sheet.mjs"
|
||||
export { default as MGNEResonanceCoreSheet } from "./sheets/resonance-core-sheet.mjs"
|
||||
export { default as MGNEArtifactSheet } from "./sheets/artifact-sheet.mjs"
|
||||
export { default as MGNEFeatureSheet } from "./sheets/feature-sheet.mjs"
|
||||
@@ -0,0 +1,7 @@
|
||||
import MGNEItemSheet from "./base-item-sheet.mjs"
|
||||
|
||||
export default class MGNEArmorSheet extends MGNEItemSheet {
|
||||
static PARTS = {
|
||||
main: { template: "systems/fvtt-machine-gods-noxian-expanse/templates/armor.hbs" },
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import MGNEItemSheet from "./base-item-sheet.mjs"
|
||||
|
||||
export default class MGNEArtifactSheet extends MGNEItemSheet {
|
||||
static PARTS = {
|
||||
main: { template: "systems/fvtt-machine-gods-noxian-expanse/templates/artifact.hbs" },
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
import { SYSTEM } from "../../config/system.mjs"
|
||||
import { buildSharedSelectOptions } from "./select-options.mjs"
|
||||
import { enrichHTMLFields } from "./rich-text.mjs"
|
||||
|
||||
const { HandlebarsApplicationMixin } = foundry.applications.api
|
||||
|
||||
export default class MGNEActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) {
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["mgne", "actor-sheet"],
|
||||
position: {
|
||||
width: 900,
|
||||
height: "auto",
|
||||
},
|
||||
window: {
|
||||
resizable: true,
|
||||
},
|
||||
form: {
|
||||
submitOnChange: true,
|
||||
},
|
||||
actions: {
|
||||
editImage: MGNEActorSheet.onEditImage,
|
||||
changeTab: MGNEActorSheet.onChangeTab,
|
||||
createItem: MGNEActorSheet.onCreateItem,
|
||||
editItem: MGNEActorSheet.onEditItem,
|
||||
deleteItem: MGNEActorSheet.onDeleteItem,
|
||||
toggleEquipped: MGNEActorSheet.onToggleEquipped,
|
||||
syncArtifact: MGNEActorSheet.onSyncArtifact,
|
||||
resetDaily: MGNEActorSheet.onResetDaily,
|
||||
rollResonancePerDay: MGNEActorSheet.onRollResonancePerDay,
|
||||
quickRest: MGNEActorSheet.onQuickRest,
|
||||
fullRest: MGNEActorSheet.onFullRest,
|
||||
},
|
||||
}
|
||||
|
||||
async _prepareContext() {
|
||||
const systemFields = this.document.system.schema.fields
|
||||
|
||||
return {
|
||||
fields: this.document.schema.fields,
|
||||
systemFields,
|
||||
actor: this.document,
|
||||
system: this.document.system,
|
||||
source: this.document.toObject(),
|
||||
config: SYSTEM,
|
||||
enrichedFields: await enrichHTMLFields(this.document.system, systemFields),
|
||||
isEditable: this.isEditable,
|
||||
selectOptions: buildSharedSelectOptions(),
|
||||
}
|
||||
}
|
||||
|
||||
_onRender(context, options) {
|
||||
super._onRender?.(context, options)
|
||||
this.element.querySelectorAll(".rollable").forEach(element => {
|
||||
element.addEventListener("click", this._onRoll.bind(this))
|
||||
})
|
||||
}
|
||||
|
||||
async _onRoll(event) {
|
||||
const target = event.currentTarget
|
||||
const itemId = target.closest("[data-item-id]")?.dataset.itemId
|
||||
const rollType = target.dataset.rollType
|
||||
|
||||
switch (rollType) {
|
||||
case "ability":
|
||||
return this.document.rollAbility(target.dataset.abilityId)
|
||||
case "defense":
|
||||
return this.document.rollDefense()
|
||||
case "weapon":
|
||||
return this.document.rollWeapon(itemId)
|
||||
case "profile-attack":
|
||||
return this.document.rollProfileAttack()
|
||||
case "profile-damage":
|
||||
return this.document.rollProfileDamage()
|
||||
case "damage":
|
||||
return this.document.rollDamage(itemId)
|
||||
case "resonation":
|
||||
return this.document.rollResonation(itemId)
|
||||
case "morale":
|
||||
return this.document.rollMorale()
|
||||
case "usage":
|
||||
return this.document.rollUsage(itemId)
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
static async onEditImage(_event, target) {
|
||||
const attr = target.dataset.edit
|
||||
const current = foundry.utils.getProperty(this.document, attr)
|
||||
const picker = new FilePicker({
|
||||
current,
|
||||
type: "image",
|
||||
callback: path => this.document.update({ [attr]: path }),
|
||||
top: this.position.top + 40,
|
||||
left: this.position.left + 10,
|
||||
})
|
||||
return picker.browse()
|
||||
}
|
||||
|
||||
static onChangeTab(_event, target) {
|
||||
const group = target.dataset.group
|
||||
const tab = target.dataset.tab
|
||||
this.tabGroups[group] = tab
|
||||
this.render()
|
||||
}
|
||||
|
||||
static async onCreateItem(_event, target) {
|
||||
const itemType = target.dataset.itemType
|
||||
const typeLabel = SYSTEM.itemTypes[itemType]?.label ?? itemType
|
||||
const [created] = await this.document.createEmbeddedDocuments("Item", [{
|
||||
name: game.i18n.format("MGNE.Common.NewItem", { type: typeLabel }),
|
||||
type: itemType,
|
||||
img: SYSTEM.itemTypes[itemType]?.icon,
|
||||
}])
|
||||
created?.sheet?.render(true)
|
||||
}
|
||||
|
||||
static async onEditItem(_event, target) {
|
||||
const itemId = target.closest("[data-item-id]")?.dataset.itemId
|
||||
const item = this.document.items.get(itemId)
|
||||
return item?.sheet?.render(true)
|
||||
}
|
||||
|
||||
static async onDeleteItem(_event, target) {
|
||||
const itemId = target.closest("[data-item-id]")?.dataset.itemId
|
||||
const item = this.document.items.get(itemId)
|
||||
if (!item) return null
|
||||
return item.delete()
|
||||
}
|
||||
|
||||
static async onToggleEquipped(_event, target) {
|
||||
const itemId = target.closest("[data-item-id]")?.dataset.itemId
|
||||
return this.document.toggleItemEquipped(itemId)
|
||||
}
|
||||
|
||||
static async onSyncArtifact(_event, target) {
|
||||
const itemId = target.closest("[data-item-id]")?.dataset.itemId
|
||||
return this.document.syncArtifact(itemId)
|
||||
}
|
||||
|
||||
static async onResetDaily() {
|
||||
return this.document.resetDaily()
|
||||
}
|
||||
|
||||
static async onRollResonancePerDay() {
|
||||
return this.document.rollResonancePerDay()
|
||||
}
|
||||
|
||||
static async onQuickRest() {
|
||||
return this.document.quickRest()
|
||||
}
|
||||
|
||||
static async onFullRest() {
|
||||
return this.document.fullRest()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { SYSTEM } from "../../config/system.mjs"
|
||||
import { buildSharedSelectOptions, numericOptions } from "./select-options.mjs"
|
||||
import { enrichHTMLFields } from "./rich-text.mjs"
|
||||
|
||||
const { HandlebarsApplicationMixin } = foundry.applications.api
|
||||
|
||||
export default class MGNEItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["mgne", "item-sheet"],
|
||||
position: {
|
||||
width: 720,
|
||||
height: "auto",
|
||||
},
|
||||
window: {
|
||||
resizable: true,
|
||||
},
|
||||
form: {
|
||||
submitOnChange: true,
|
||||
},
|
||||
actions: {
|
||||
editImage: MGNEItemSheet.onEditImage,
|
||||
},
|
||||
}
|
||||
|
||||
async _prepareContext() {
|
||||
const selectOptions = buildSharedSelectOptions()
|
||||
const systemFields = this.document.system.schema.fields
|
||||
if (this.document.type === "armor") selectOptions.penalties = numericOptions(0, 6, this.document.system.penalty)
|
||||
if (this.document.type === "shield") selectOptions.penalties = numericOptions(0, 4, this.document.system.penalty)
|
||||
|
||||
return {
|
||||
fields: this.document.schema.fields,
|
||||
systemFields,
|
||||
item: this.document,
|
||||
system: this.document.system,
|
||||
source: this.document.toObject(),
|
||||
config: SYSTEM,
|
||||
enrichedFields: await enrichHTMLFields(this.document.system, systemFields),
|
||||
isEditable: this.isEditable,
|
||||
selectOptions,
|
||||
}
|
||||
}
|
||||
|
||||
static async onEditImage(_event, target) {
|
||||
const attr = target.dataset.edit
|
||||
const current = foundry.utils.getProperty(this.document, attr)
|
||||
const picker = new FilePicker({
|
||||
current,
|
||||
type: "image",
|
||||
callback: path => this.document.update({ [attr]: path }),
|
||||
top: this.position.top + 40,
|
||||
left: this.position.left + 10,
|
||||
})
|
||||
return picker.browse()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import MGNEActorSheet from "./base-actor-sheet.mjs"
|
||||
import { SYSTEM } from "../../config/system.mjs"
|
||||
import { buildCharacterSelectOptions } from "./select-options.mjs"
|
||||
|
||||
export default class MGNECharacterSheet extends MGNEActorSheet {
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["character"],
|
||||
position: {
|
||||
width: 1040,
|
||||
height: 760,
|
||||
},
|
||||
}
|
||||
|
||||
static PARTS = {
|
||||
main: { template: "systems/fvtt-machine-gods-noxian-expanse/templates/character-main.hbs" },
|
||||
tabs: { template: "systems/fvtt-machine-gods-noxian-expanse/templates/character-tabs.hbs" },
|
||||
overview: { template: "systems/fvtt-machine-gods-noxian-expanse/templates/character-overview.hbs" },
|
||||
daily: { template: "systems/fvtt-machine-gods-noxian-expanse/templates/character-daily.hbs" },
|
||||
equipment: { template: "systems/fvtt-machine-gods-noxian-expanse/templates/character-equipment.hbs" },
|
||||
features: { template: "systems/fvtt-machine-gods-noxian-expanse/templates/character-features.hbs" },
|
||||
notes: { template: "systems/fvtt-machine-gods-noxian-expanse/templates/character-notes.hbs" },
|
||||
}
|
||||
|
||||
tabGroups = { sheet: "overview" }
|
||||
|
||||
getTabs() {
|
||||
const tabs = {
|
||||
overview: { id: "overview", group: "sheet", label: game.i18n.localize("MGNE.Tabs.overview") },
|
||||
daily: { id: "daily", group: "sheet", label: game.i18n.localize("MGNE.Tabs.daily") },
|
||||
equipment: { id: "equipment", group: "sheet", label: game.i18n.localize("MGNE.Tabs.equipment") },
|
||||
features: { id: "features", group: "sheet", label: game.i18n.localize("MGNE.Tabs.features") },
|
||||
notes: { id: "notes", group: "sheet", label: game.i18n.localize("MGNE.Tabs.notes") },
|
||||
}
|
||||
for (const tab of Object.values(tabs)) {
|
||||
tab.active = this.tabGroups[tab.group] === tab.id
|
||||
tab.cssClass = tab.active ? "active" : ""
|
||||
}
|
||||
return tabs
|
||||
}
|
||||
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext()
|
||||
context.tabs = this.getTabs()
|
||||
context.abilityList = SYSTEM.abilityOrder.map(id => ({
|
||||
id,
|
||||
...SYSTEM.abilities[id],
|
||||
value: context.source.system.abilities?.[id]?.value ?? 0,
|
||||
}))
|
||||
context.selectOptions = {
|
||||
...context.selectOptions,
|
||||
...buildCharacterSelectOptions(context.system),
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
async _preparePartContext(partId, context) {
|
||||
const doc = this.document
|
||||
switch (partId) {
|
||||
case "overview":
|
||||
context.tab = context.tabs.overview
|
||||
context.valueConditions = Object.entries(doc.system.conditions ?? {})
|
||||
.filter(([id]) => SYSTEM.conditions[id]?.hasValue)
|
||||
.map(([id, cond]) => ({
|
||||
id,
|
||||
label: SYSTEM.conditions[id].label,
|
||||
value: cond.value,
|
||||
options: context.selectOptions.conditionValues,
|
||||
}))
|
||||
context.flagConditions = Object.entries(doc.system.conditions ?? {})
|
||||
.filter(([id]) => !SYSTEM.conditions[id]?.hasValue)
|
||||
.map(([id, cond]) => ({
|
||||
id,
|
||||
label: SYSTEM.conditions[id].label,
|
||||
active: cond.active,
|
||||
}))
|
||||
break
|
||||
case "daily":
|
||||
context.tab = context.tabs.daily
|
||||
break
|
||||
case "equipment":
|
||||
context.tab = context.tabs.equipment
|
||||
context.weapons = doc.itemTypes.weapon
|
||||
context.armors = doc.itemTypes.armor
|
||||
context.shields = doc.itemTypes.shield
|
||||
context.equipmentItems = doc.itemTypes.equipment
|
||||
context.cores = doc.itemTypes["resonance-core"]
|
||||
context.artifacts = doc.itemTypes.artifact
|
||||
break
|
||||
case "features":
|
||||
context.tab = context.tabs.features
|
||||
context.features = doc.itemTypes.feature
|
||||
break
|
||||
case "notes":
|
||||
context.tab = context.tabs.notes
|
||||
break
|
||||
}
|
||||
return context
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import MGNEActorSheet from "./base-actor-sheet.mjs"
|
||||
import { SYSTEM } from "../../config/system.mjs"
|
||||
|
||||
export default class MGNECompanionSheet extends MGNEActorSheet {
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["companion"],
|
||||
position: {
|
||||
width: 820,
|
||||
height: 700,
|
||||
},
|
||||
}
|
||||
|
||||
static PARTS = {
|
||||
main: { template: "systems/fvtt-machine-gods-noxian-expanse/templates/companion-main.hbs" },
|
||||
}
|
||||
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext()
|
||||
context.abilityList = SYSTEM.abilityOrder.map(id => ({
|
||||
id,
|
||||
...SYSTEM.abilities[id],
|
||||
value: context.source.system.abilities?.[id]?.value ?? 0,
|
||||
}))
|
||||
return context
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import MGNEActorSheet from "./base-actor-sheet.mjs"
|
||||
import { SYSTEM } from "../../config/system.mjs"
|
||||
|
||||
export default class MGNECreatureSheet extends MGNEActorSheet {
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["creature"],
|
||||
position: {
|
||||
width: 760,
|
||||
height: 640,
|
||||
},
|
||||
}
|
||||
|
||||
static PARTS = {
|
||||
main: { template: "systems/fvtt-machine-gods-noxian-expanse/templates/creature-main.hbs" },
|
||||
}
|
||||
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext()
|
||||
context.abilityList = SYSTEM.abilityOrder.map(id => ({
|
||||
id,
|
||||
...SYSTEM.abilities[id],
|
||||
value: context.source.system.abilities?.[id]?.value ?? 0,
|
||||
}))
|
||||
return context
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import MGNEItemSheet from "./base-item-sheet.mjs"
|
||||
|
||||
export default class MGNEEquipmentSheet extends MGNEItemSheet {
|
||||
static PARTS = {
|
||||
main: { template: "systems/fvtt-machine-gods-noxian-expanse/templates/equipment.hbs" },
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import MGNEItemSheet from "./base-item-sheet.mjs"
|
||||
|
||||
export default class MGNEFeatureSheet extends MGNEItemSheet {
|
||||
static PARTS = {
|
||||
main: { template: "systems/fvtt-machine-gods-noxian-expanse/templates/feature.hbs" },
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import MGNEItemSheet from "./base-item-sheet.mjs"
|
||||
|
||||
export default class MGNEResonanceCoreSheet extends MGNEItemSheet {
|
||||
static PARTS = {
|
||||
main: { template: "systems/fvtt-machine-gods-noxian-expanse/templates/resonance-core.hbs" },
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
export async function enrichHTMLFields(data, schemaFields) {
|
||||
const enrichedFields = {}
|
||||
|
||||
for (const [key, field] of Object.entries(schemaFields ?? {})) {
|
||||
if (field instanceof foundry.data.fields.HTMLField) {
|
||||
enrichedFields[key] = await foundry.applications.ux.TextEditor.implementation.enrichHTML(data?.[key] ?? "", { async: true })
|
||||
continue
|
||||
}
|
||||
|
||||
if (field instanceof foundry.data.fields.SchemaField) {
|
||||
const nested = await enrichHTMLFields(data?.[key], field.fields)
|
||||
if (Object.keys(nested).length) enrichedFields[key] = nested
|
||||
}
|
||||
}
|
||||
|
||||
return enrichedFields
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { SYSTEM } from "../../config/system.mjs"
|
||||
|
||||
function normalizeMax(max, current) {
|
||||
return Math.max(max ?? 0, Number.isFinite(current) ? current : 0)
|
||||
}
|
||||
|
||||
export function numericOptions(min, max, current = null) {
|
||||
const resolvedMin = Math.min(min, Number.isFinite(current) ? current : min)
|
||||
const resolvedMax = normalizeMax(max, current)
|
||||
return Array.from({ length: Math.max(0, resolvedMax - resolvedMin) + 1 }, (_, index) => {
|
||||
const value = resolvedMin + index
|
||||
return { value, label: String(value) }
|
||||
})
|
||||
}
|
||||
|
||||
export function objectOptions(choices) {
|
||||
return Object.entries(choices).map(([value, label]) => ({ value, label }))
|
||||
}
|
||||
|
||||
export function dieMax(die) {
|
||||
if (typeof die !== "string" || !die.startsWith("d")) return 0
|
||||
const faces = Number.parseInt(die.slice(1), 10)
|
||||
return Number.isFinite(faces) ? faces : 0
|
||||
}
|
||||
|
||||
export function buildSharedSelectOptions() {
|
||||
return {
|
||||
abilityValues: numericOptions(-3, 6),
|
||||
conditionValues: numericOptions(0, 12),
|
||||
moraleValues: numericOptions(2, 12),
|
||||
armorPenalties: numericOptions(0, 6),
|
||||
shieldPenalties: numericOptions(0, 4),
|
||||
weaponCategories: objectOptions(SYSTEM.weaponCategories),
|
||||
usageDice: objectOptions(SYSTEM.usageDieChoices),
|
||||
armorDice: objectOptions(SYSTEM.armorDieChoices),
|
||||
omenDice: objectOptions(SYSTEM.omenDieChoices),
|
||||
resonanceList: objectOptions(SYSTEM.resonanceList),
|
||||
equipmentSubtypes: objectOptions(SYSTEM.equipmentSubtypes),
|
||||
artifactIds: objectOptions(SYSTEM.artifactChoices),
|
||||
featureIds: objectOptions(SYSTEM.featureChoices),
|
||||
}
|
||||
}
|
||||
|
||||
export function buildCharacterSelectOptions(system) {
|
||||
return {
|
||||
omenCurrent: numericOptions(0, dieMax(system.omens?.die), system.omens?.current),
|
||||
resonanceUsed: numericOptions(0, system.resonance?.max ?? 0, system.resonance?.used),
|
||||
artifactSyncUsed: numericOptions(0, system.syncLimit ?? 0, system.artifactSync?.used),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import MGNEItemSheet from "./base-item-sheet.mjs"
|
||||
|
||||
export default class MGNEShieldSheet extends MGNEItemSheet {
|
||||
static PARTS = {
|
||||
main: { template: "systems/fvtt-machine-gods-noxian-expanse/templates/shield.hbs" },
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import MGNEItemSheet from "./base-item-sheet.mjs"
|
||||
|
||||
export default class MGNEWeaponSheet extends MGNEItemSheet {
|
||||
static PARTS = {
|
||||
main: { template: "systems/fvtt-machine-gods-noxian-expanse/templates/weapon.hbs" },
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user