Import initial

This commit is contained in:
2026-05-02 09:16:24 +02:00
parent e4b91948d2
commit 839b2b606e
76 changed files with 10025 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
export { default as LesOubliesPersonnageSheet } from "./personnage-sheet.mjs"
export { default as LesOubliesCompagnieSheet } from "./compagnie-sheet.mjs"
export { default as LesOubliesCreatureSheet } from "./creature-sheet.mjs"
export { default as LesOubliesReferenceItemSheet } from "./reference-item-sheet.mjs"
export { default as LesOubliesCompetenceSheet } from "./competence-sheet.mjs"
export { default as LesOubliesSortilegeSheet } from "./sortilege-sheet.mjs"
export { default as LesOubliesArmeSheet } from "./arme-sheet.mjs"
export { default as LesOubliesArmureSheet } from "./armure-sheet.mjs"
export { default as LesOubliesEquipementSheet } from "./equipement-sheet.mjs"
export { default as LesOubliesPouvoirCompagnieSheet } from "./pouvoir-compagnie-sheet.mjs"
@@ -0,0 +1,9 @@
import LesOubliesItemSheet from "./base-item-sheet.mjs"
export default class LesOubliesArmeSheet extends LesOubliesItemSheet {
static PARTS = {
sheet: {
template: "systems/fvtt-les-oublies/templates/item-arme-sheet.hbs",
},
}
}
@@ -0,0 +1,9 @@
import LesOubliesItemSheet from "./base-item-sheet.mjs"
export default class LesOubliesArmureSheet extends LesOubliesItemSheet {
static PARTS = {
sheet: {
template: "systems/fvtt-les-oublies/templates/item-armure-sheet.hbs",
},
}
}
@@ -0,0 +1,186 @@
const { HandlebarsApplicationMixin } = foundry.applications.api
import { LesOubliesUtility } from "../../les-oublies-utility.js"
export default class LesOubliesActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) {
static SHEET_MODES = { EDIT: 0, PLAY: 1 }
static DEFAULT_OPTIONS = {
classes: ["fvtt-les-oublies", "sheet", "actor"],
position: {
width: 980,
height: 860,
},
window: {
resizable: true,
},
form: {
submitOnChange: true,
closeOnSubmit: false,
},
dragDrop: [{ dragSelector: ".item-card", dropSelector: "form" }],
actions: {
toggleSheet: LesOubliesActorSheet.#onToggleSheet,
editImage: LesOubliesActorSheet.#onEditImage,
createItem: LesOubliesActorSheet.#onCreateItem,
editItem: LesOubliesActorSheet.#onEditItem,
deleteItem: LesOubliesActorSheet.#onDeleteItem,
openRoll: LesOubliesActorSheet.#onOpenRoll,
openConfrontation: LesOubliesActorSheet.#onOpenConfrontation,
openInitiative: LesOubliesActorSheet.#onOpenInitiative,
rollProfile: LesOubliesActorSheet.#onRollProfile,
rollSkill: LesOubliesActorSheet.#onRollSkill,
useWeapon: LesOubliesActorSheet.#onUseWeapon,
resolveWeaponDamage: LesOubliesActorSheet.#onResolveWeaponDamage,
useSpell: LesOubliesActorSheet.#onUseSpell,
openCombatPreset: LesOubliesActorSheet.#onOpenCombatPreset,
openThreadHarvest: LesOubliesActorSheet.#onOpenThreadHarvest,
},
}
_sheetMode = this.constructor.SHEET_MODES.EDIT
get isEditMode() {
return this._sheetMode === this.constructor.SHEET_MODES.EDIT
}
get isPlayMode() {
return this._sheetMode === this.constructor.SHEET_MODES.PLAY
}
async _prepareContext() {
return {
actor: this.document,
system: this.document.system,
source: this.document.toObject(),
fields: this.document.schema.fields,
systemFields: this.document.system.schema.fields,
isEditable: this.isEditable,
isEditMode: this.isEditMode,
isPlayMode: this.isPlayMode,
isGM: game.user.isGM,
config: CONFIG.LESOUBLIES,
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.biodata?.description ?? this.document.system.description ?? "", { async: true }),
}
}
_onRender(context, options) {
super._onRender(context, options)
}
_canDragStart() {
return this.isEditable
}
_canDragDrop() {
return this.isEditable
}
async _onDrop(event) {
const data = TextEditor.getDragEventData(event)
if (data.type !== "Item" || !data.uuid) return
const item = await fromUuid(data.uuid)
if (!item) return
return this._onDropItem(item)
}
async _onDropItem(item) {
const itemData = item.toObject()
delete itemData._id
return this.document.createEmbeddedDocuments("Item", [itemData], { renderSheet: false })
}
static #onToggleSheet() {
const modes = this.constructor.SHEET_MODES
this._sheetMode = this.isEditMode ? modes.PLAY : modes.EDIT
this.render()
}
static async #onEditImage(event, target) {
const attr = target.dataset.edit
const current = foundry.utils.getProperty(this.document, attr)
const fp = new FilePicker({
current,
type: "image",
callback: (path) => this.document.update({ [attr]: path }),
top: this.position.top + 40,
left: this.position.left + 10,
})
return fp.browse()
}
static async #onCreateItem(event, target) {
const type = target.dataset.type
if (!type) return
const label = game.i18n.localize(`TYPES.Item.${type}`)
return this.document.createEmbeddedDocuments("Item", [{
name: label,
type,
img: LesOubliesUtility.getDefaultItemImage(type),
}])
}
static async #onEditItem(event, target) {
const itemId = target.dataset.itemId
const item = this.document.items.get(itemId)
if (item) item.sheet.render(true)
}
static async #onDeleteItem(event, target) {
const itemId = target.dataset.itemId
const item = this.document.items.get(itemId)
if (item) await item.delete()
}
static async #onOpenRoll() {
await this.document.openTestRollDialog()
}
static async #onOpenConfrontation() {
await this.document.openConfrontationRollDialog()
}
static async #onOpenInitiative() {
await this.document.openInitiativeRollDialog()
}
static async #onRollProfile(event, target) {
const profileKey = target.dataset.profileKey
if (!profileKey) return
await this.document.rollProfile(profileKey)
}
static async #onRollSkill(event, target) {
const itemId = target.dataset.itemId
if (!itemId) return
await this.document.rollCompetence(itemId)
}
static async #onUseWeapon(event, target) {
const itemId = target.dataset.itemId
if (!itemId) return
await this.document.openAttackRollDialog({ itemId })
}
static async #onResolveWeaponDamage(event, target) {
const itemId = target.dataset.itemId
if (!itemId) return
await this.document.openDamageDialog({ itemId })
}
static async #onUseSpell(event, target) {
const itemId = target.dataset.itemId
if (!itemId) return
await this.document.openSpellActivationDialog(itemId)
}
static async #onOpenCombatPreset(event, target) {
const actionKey = target.dataset.preset
if (!actionKey) return
await this.document.openCombatPresetDialog(actionKey)
}
static async #onOpenThreadHarvest() {
await this.document.openThreadHarvestDialog()
}
}
@@ -0,0 +1,69 @@
const { HandlebarsApplicationMixin } = foundry.applications.api
export default class LesOubliesItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
static SHEET_MODES = { EDIT: 0, PLAY: 1 }
static DEFAULT_OPTIONS = {
classes: ["fvtt-les-oublies", "sheet", "item"],
position: {
width: 760,
height: 720,
},
window: {
resizable: true,
},
form: {
submitOnChange: true,
closeOnSubmit: false,
},
actions: {
toggleSheet: LesOubliesItemSheet.#onToggleSheet,
editImage: LesOubliesItemSheet.#onEditImage,
},
}
_sheetMode = this.constructor.SHEET_MODES.EDIT
get isEditMode() {
return this._sheetMode === this.constructor.SHEET_MODES.EDIT
}
get isPlayMode() {
return this._sheetMode === this.constructor.SHEET_MODES.PLAY
}
async _prepareContext() {
return {
item: this.document,
system: this.document.system,
source: this.document.toObject(),
fields: this.document.schema.fields,
systemFields: this.document.system.schema.fields,
isEditable: this.isEditable,
isEditMode: this.isEditMode,
isPlayMode: this.isPlayMode,
isGM: game.user.isGM,
config: CONFIG.LESOUBLIES,
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description ?? "", { async: true }),
}
}
static #onToggleSheet() {
const modes = this.constructor.SHEET_MODES
this._sheetMode = this.isEditMode ? modes.PLAY : modes.EDIT
this.render()
}
static async #onEditImage(event, target) {
const attr = target.dataset.edit
const current = foundry.utils.getProperty(this.document, attr)
const fp = new FilePicker({
current,
type: "image",
callback: (path) => this.document.update({ [attr]: path }),
top: this.position.top + 40,
left: this.position.left + 10,
})
return fp.browse()
}
}
@@ -0,0 +1,33 @@
import LesOubliesActorSheet from "./base-actor-sheet.mjs"
export default class LesOubliesCompagnieSheet extends LesOubliesActorSheet {
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "compagnie"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "TYPES.Actor.compagnie",
},
}
static PARTS = {
sheet: {
template: "systems/fvtt-les-oublies/templates/actor-compagnie-sheet.hbs",
},
}
async _prepareContext() {
const context = await super._prepareContext()
context.members = (this.document.system.memberIds ?? []).map((id) => game.actors.get(id)).filter(Boolean)
context.captain = this.document.system.captainId ? game.actors.get(this.document.system.captainId) : null
context.shadow = this.document.system.ombreDuTourmentId ? game.actors.get(this.document.system.ombreDuTourmentId) : null
context.powers = this.document.getEmbeddedItems("pouvoircompagnie")
context.primaryPower = context.powers[0] ?? null
context.links = (this.document.system.links ?? []).map((link) => ({
...link,
sourceLabel: game.actors.get(link.sourceId)?.name ?? link.sourceId,
targetLabel: game.actors.get(link.targetId)?.name ?? link.targetId,
}))
return context
}
}
@@ -0,0 +1,9 @@
import LesOubliesItemSheet from "./base-item-sheet.mjs"
export default class LesOubliesCompetenceSheet extends LesOubliesItemSheet {
static PARTS = {
sheet: {
template: "systems/fvtt-les-oublies/templates/item-competence-sheet.hbs",
},
}
}
@@ -0,0 +1,29 @@
import LesOubliesActorSheet from "./base-actor-sheet.mjs"
export default class LesOubliesCreatureSheet extends LesOubliesActorSheet {
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "creature"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "TYPES.Actor.creature",
},
}
static PARTS = {
sheet: {
template: "systems/fvtt-les-oublies/templates/actor-creature-sheet.hbs",
},
}
async _prepareContext() {
const context = await super._prepareContext()
context.derived = this.document.getDerivedOverview()
context.skillGroups = this.document.getGroupedCompetences()
context.spells = this.document.getEmbeddedItems("sortilege")
context.weapons = this.document.getEmbeddedItems("arme")
context.armors = this.document.getEmbeddedItems("armure")
context.equipment = this.document.getEmbeddedItems("equipement")
return context
}
}
@@ -0,0 +1,9 @@
import LesOubliesItemSheet from "./base-item-sheet.mjs"
export default class LesOubliesEquipementSheet extends LesOubliesItemSheet {
static PARTS = {
sheet: {
template: "systems/fvtt-les-oublies/templates/item-equipement-sheet.hbs",
},
}
}
@@ -0,0 +1,37 @@
import LesOubliesActorSheet from "./base-actor-sheet.mjs"
export default class LesOubliesPersonnageSheet extends LesOubliesActorSheet {
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "personnage"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "TYPES.Actor.personnage",
},
}
static PARTS = {
sheet: {
template: "systems/fvtt-les-oublies/templates/actor-personnage-sheet.hbs",
},
}
async _prepareContext() {
const context = await super._prepareContext()
context.derived = this.document.getDerivedOverview()
context.creation = {
race: this.document.getCreationItem("race"),
tribu: this.document.getCreationItem("tribu"),
metier: this.document.getCreationItem("metier"),
}
context.profileEntries = this.document.system.profils
context.skillGroups = this.document.getGroupedCompetences()
context.spells = this.document.getEmbeddedItems("sortilege")
context.weapons = this.document.getEmbeddedItems("arme")
context.armors = this.document.getEmbeddedItems("armure")
context.equipment = this.document.getEmbeddedItems("equipement")
context.companyPowers = this.document.getEmbeddedItems("pouvoircompagnie")
context.activeCompanyPower = context.derived.compagnie?.getEmbeddedItems?.("pouvoircompagnie")?.[0] ?? null
return context
}
}
@@ -0,0 +1,9 @@
import LesOubliesItemSheet from "./base-item-sheet.mjs"
export default class LesOubliesPouvoirCompagnieSheet extends LesOubliesItemSheet {
static PARTS = {
sheet: {
template: "systems/fvtt-les-oublies/templates/item-pouvoir-compagnie-sheet.hbs",
},
}
}
@@ -0,0 +1,17 @@
import LesOubliesItemSheet from "./base-item-sheet.mjs"
export default class LesOubliesReferenceItemSheet extends LesOubliesItemSheet {
static PARTS = {
sheet: {
template: "systems/fvtt-les-oublies/templates/item-reference-sheet.hbs",
},
}
async _prepareContext() {
const context = await super._prepareContext()
context.isRace = this.document.type === "race"
context.isTribu = this.document.type === "tribu"
context.isMetier = this.document.type === "metier"
return context
}
}
@@ -0,0 +1,9 @@
import LesOubliesItemSheet from "./base-item-sheet.mjs"
export default class LesOubliesSortilegeSheet extends LesOubliesItemSheet {
static PARTS = {
sheet: {
template: "systems/fvtt-les-oublies/templates/item-sortilege-sheet.hbs",
},
}
}
+164
View File
@@ -0,0 +1,164 @@
import { LESOUBLIES_CONFIG } from "./les-oublies-config.js"
import { LesOubliesUtility } from "./les-oublies-utility.js"
import { LesOubliesRolls } from "./les-oublies-rolls.js"
export class LesOubliesActor extends Actor {
prepareDerivedData() {
super.prepareDerivedData()
if (this.type === "personnage") {
const system = this.system
const sizeValue = Math.clamp(Number(system.size?.value ?? 1), 1, 4)
const hpMax = Math.max(sizeValue * 4 + Number(system.hp?.bonus ?? 0), 0)
system.hp.max = hpMax
system.hp.value = Math.min(Number(system.hp.value ?? hpMax), hpMax)
const songesValue = Number(system.songes?.value ?? 0)
const cauchemarValue = Number(system.cauchemar?.value ?? 0)
const totals = LesOubliesUtility.computeDreamPointTotals(songesValue, cauchemarValue)
system.songes.max = totals.songesPoints
system.cauchemar.max = totals.cauchemarPoints
system.songes.points = Math.clamp(Number(system.songes.points ?? totals.songesPoints), 0, totals.songesPoints)
system.cauchemar.points = Math.clamp(Number(system.cauchemar.points ?? totals.cauchemarPoints), 0, totals.cauchemarPoints)
return
}
if (this.type !== "creature") return
const system = this.system
const hpValue = Math.max(Number(system.hp?.value ?? 0), 0)
const hpMax = Math.max(Number(system.hp?.max ?? hpValue), hpValue, 0)
system.hp.max = hpMax
system.hp.value = Math.min(hpValue, hpMax)
const songesPoints = Math.max(Number(system.songes?.points ?? 0), 0)
const cauchemarPoints = Math.max(Number(system.cauchemar?.points ?? 0), 0)
system.songes.max = Math.max(Number(system.songes?.max ?? songesPoints), songesPoints)
system.cauchemar.max = Math.max(Number(system.cauchemar?.max ?? cauchemarPoints), cauchemarPoints)
system.songes.points = Math.min(songesPoints, system.songes.max)
system.cauchemar.points = Math.min(cauchemarPoints, system.cauchemar.max)
}
getProfileValue(profileKey) {
return Number(this.system.profils?.[profileKey] ?? 0)
}
getCreationItem(type) {
return this.items.find((item) => item.type === type) ?? null
}
getEmbeddedItems(type) {
const items = this.itemTypes?.[type] ?? this.items.filter((item) => item.type === type)
return LesOubliesUtility.sortByName(items)
}
getCompagnie() {
const compagnieId = this.system.references?.compagnieId
return compagnieId ? game.actors.get(compagnieId) ?? null : null
}
getCompetenceByKey(skillKey) {
return this.getEmbeddedItems("competence").find((item) => item.system.key === skillKey) ?? null
}
getSkillScoreByKey(skillKey) {
const competence = this.getCompetenceByKey(skillKey)
return competence ? this.computeSkillValue(competence) : 0
}
computeSkillValue(item) {
const base = Number(item.system.base ?? 0)
const profileValue = this.getProfileValue(item.system.profileKey)
if (item.system.closed && base === 0) return 0
return base + profileValue
}
getCompetences() {
return this.getEmbeddedItems("competence").map((item) => ({
item,
finalValue: this.computeSkillValue(item),
profileLabel: LESOUBLIES_CONFIG.profileLabels[item.system.profileKey] ?? item.system.profileKey,
}))
}
getGroupedCompetences() {
return LESOUBLIES_CONFIG.profiles.map((profile) => ({
...profile,
items: this.getCompetences().filter((entry) => entry.item.system.profileKey === profile.id),
}))
}
getDerivedOverview() {
const hpValue = Number(this.system.hp?.value ?? 0)
const hpMax = Number(this.system.hp?.max ?? 0)
const hpDisplay = this.type === "creature"
? (this.system.hp?.display || (hpValue === hpMax ? String(hpValue) : `${hpValue}/${hpMax}`))
: `${hpValue}/${hpMax}`
return {
sizeLabel: LESOUBLIES_CONFIG.sizes[this.system.size?.value] ?? this.system.size?.value,
hpMax,
hpValue,
hpDisplay,
songesMax: this.system.songes?.max ?? this.system.songes?.points ?? 0,
cauchemarMax: this.system.cauchemar?.max ?? this.system.cauchemar?.points ?? 0,
songesPoints: this.system.songes?.points ?? 0,
cauchemarPoints: this.system.cauchemar?.points ?? 0,
race: this.getCreationItem("race"),
tribu: this.getCreationItem("tribu"),
metier: this.getCreationItem("metier"),
compagnie: this.getCompagnie(),
}
}
async openTestRollDialog(preset = {}) {
return LesOubliesRolls.openTestDialog(this, preset)
}
async openConfrontationRollDialog() {
return LesOubliesRolls.openConfrontationDialog(this)
}
async openInitiativeRollDialog() {
return LesOubliesRolls.openInitiativeDialog(this)
}
async openAttackRollDialog({ itemId = null, mode = null } = {}) {
return LesOubliesRolls.openAttackDialog(this, { itemId, mode })
}
async openDamageDialog({ itemId = null } = {}) {
return LesOubliesRolls.openDamageDialog(this, { itemId })
}
async openSpellActivationDialog(itemId) {
return LesOubliesRolls.openSpellDialog(this, itemId)
}
async openCombatPresetDialog(actionKey) {
return LesOubliesRolls.openCombatPresetDialog(this, actionKey)
}
async openThreadHarvestDialog() {
return LesOubliesRolls.openThreadHarvestDialog(this)
}
async rollProfile(profileKey) {
return this.openTestRollDialog({
label: LESOUBLIES_CONFIG.profileLabels[profileKey] ?? profileKey,
score: this.getProfileValue(profileKey),
difficulty: 0,
rollMode: LesOubliesRolls.getDefaultRollMode(this),
})
}
async rollCompetence(itemId) {
const item = this.items.get(itemId)
if (!item) return null
return this.openTestRollDialog({
label: item.name,
score: this.computeSkillValue(item),
difficulty: 0,
rollMode: LesOubliesRolls.getDefaultRollMode(this),
})
}
}
+89
View File
@@ -0,0 +1,89 @@
export const PROFILE_KEYS = [
"artiste",
"athlete",
"chasseur",
"faiseur",
"forceNature",
"guerrier",
"mystique",
"ombre",
"savant",
]
export const PROFILE_LABELS = {
artiste: "Artiste",
athlete: "Athlète",
chasseur: "Chasseur",
faiseur: "Faiseur",
forceNature: "Force de la nature",
guerrier: "Guerrier",
mystique: "Mystique",
ombre: "Ombre",
savant: "Savant",
}
export const SKILLS = {
arts: { label: "Arts", profileKey: "artiste", closed: false, domainSkill: true },
empathie: { label: "Empathie", profileKey: "artiste", closed: false, domainSkill: false },
seduction: { label: "Séduction", profileKey: "artiste", closed: false, domainSkill: false },
athletisme: { label: "Athlétisme", profileKey: "athlete", closed: false, domainSkill: false },
rapidite: { label: "Rapidité", profileKey: "athlete", closed: false, domainSkill: false },
volonte: { label: "Volonté", profileKey: "athlete", closed: false, domainSkill: false },
sens: { label: "Sens", profileKey: "chasseur", closed: false, domainSkill: false },
survie: { label: "Survie", profileKey: "chasseur", closed: false, domainSkill: false },
tir: { label: "Tir", profileKey: "chasseur", closed: false, domainSkill: false },
artisanat: { label: "Artisanat", profileKey: "faiseur", closed: false, domainSkill: true },
intellect: { label: "Intellect", profileKey: "faiseur", closed: false, domainSkill: false },
soins: { label: "Soins", profileKey: "faiseur", closed: false, domainSkill: false },
commandement: { label: "Commandement", profileKey: "forceNature", closed: false, domainSkill: false },
endurance: { label: "Endurance", profileKey: "forceNature", closed: false, domainSkill: false },
force: { label: "Force", profileKey: "forceNature", closed: false, domainSkill: false },
corpsacorps: { label: "Corps à corps", profileKey: "guerrier", closed: false, domainSkill: false },
melee: { label: "Mêlée", profileKey: "guerrier", closed: false, domainSkill: false },
montures: { label: "Montures", profileKey: "guerrier", closed: false, domainSkill: false },
chimerisme: { label: "Chimérisme", profileKey: "mystique", closed: true, domainSkill: false },
magie: { label: "Magie", profileKey: "mystique", closed: true, domainSkill: false },
onirologie: { label: "Onirologie", profileKey: "mystique", closed: true, domainSkill: false },
discretion: { label: "Discrétion", profileKey: "ombre", closed: false, domainSkill: false },
esquive: { label: "Esquive", profileKey: "ombre", closed: false, domainSkill: false },
subterfuge: { label: "Subterfuge", profileKey: "ombre", closed: false, domainSkill: false },
erudition: { label: "Érudition", profileKey: "savant", closed: true, domainSkill: true },
langues: { label: "Langues", profileKey: "savant", closed: true, domainSkill: true },
strategie: { label: "Stratégie", profileKey: "savant", closed: false, domainSkill: false },
}
export const SIZE_LABELS = {
1: "Minuscule",
2: "Petite",
3: "Moyenne",
4: "Grande",
}
export const ACTOR_IMAGES = {
personnage: "icons/svg/mystery-man.svg",
compagnie: "icons/svg/book.svg",
creature: "icons/svg/eye.svg",
}
export const ITEM_IMAGES = {
race: "icons/svg/mystery-man.svg",
tribu: "icons/svg/ruins.svg",
metier: "icons/svg/upgrade.svg",
competence: "icons/svg/book.svg",
sortilege: "icons/svg/daze.svg",
arme: "icons/svg/sword.svg",
armure: "icons/svg/shield.svg",
equipement: "icons/svg/chest.svg",
pouvoircompagnie: "icons/svg/aura.svg",
}
export const LESOUBLIES_CONFIG = {
id: "fvtt-les-oublies",
title: "Les Oubliés",
profiles: PROFILE_KEYS.map((key) => ({ id: key, label: PROFILE_LABELS[key] })),
profileLabels: PROFILE_LABELS,
skills: SKILLS,
sizes: SIZE_LABELS,
actorImages: ACTOR_IMAGES,
itemImages: ITEM_IMAGES,
}
+9
View File
@@ -0,0 +1,9 @@
export class LesOubliesItem extends Item {
getChatData() {
const data = super.getChatData()
return {
...data,
description: this.system.description,
}
}
}
+55
View File
@@ -0,0 +1,55 @@
import { LESOUBLIES_CONFIG } from "./les-oublies-config.js"
import { LesOubliesUtility } from "./les-oublies-utility.js"
import { LesOubliesActor } from "./les-oublies-actor.js"
import { LesOubliesItem } from "./les-oublies-item.js"
import { LesOubliesRolls } from "./les-oublies-rolls.js"
import * as models from "./models/index.mjs"
import * as sheets from "./applications/sheets/_module.mjs"
Hooks.once("init", function () {
console.info("Les Oubliés | Initialisation du système")
CONFIG.Actor.documentClass = LesOubliesActor
CONFIG.Actor.dataModels = {
personnage: models.PersonnageDataModel,
compagnie: models.CompagnieDataModel,
creature: models.CreatureDataModel,
}
CONFIG.Item.documentClass = LesOubliesItem
CONFIG.Item.dataModels = {
race: models.RaceDataModel,
tribu: models.TribuDataModel,
metier: models.MetierDataModel,
competence: models.CompetenceDataModel,
sortilege: models.SortilegeDataModel,
arme: models.ArmeDataModel,
armure: models.ArmureDataModel,
equipement: models.EquipementDataModel,
pouvoircompagnie: models.PouvoirCompagnieDataModel,
}
CONFIG.LESOUBLIES = LESOUBLIES_CONFIG
game.system.lesOublies = {
config: LESOUBLIES_CONFIG,
models,
sheets,
rolls: LesOubliesRolls,
utility: LesOubliesUtility,
}
foundry.documents.collections.Actors.registerSheet("fvtt-les-oublies", sheets.LesOubliesPersonnageSheet, { types: ["personnage"], makeDefault: true })
foundry.documents.collections.Actors.registerSheet("fvtt-les-oublies", sheets.LesOubliesCompagnieSheet, { types: ["compagnie"], makeDefault: true })
foundry.documents.collections.Actors.registerSheet("fvtt-les-oublies", sheets.LesOubliesCreatureSheet, { types: ["creature"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-oublies", sheets.LesOubliesReferenceItemSheet, { types: ["race", "tribu", "metier"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-oublies", sheets.LesOubliesCompetenceSheet, { types: ["competence"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-oublies", sheets.LesOubliesSortilegeSheet, { types: ["sortilege"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-oublies", sheets.LesOubliesArmeSheet, { types: ["arme"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-oublies", sheets.LesOubliesArmureSheet, { types: ["armure"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-oublies", sheets.LesOubliesEquipementSheet, { types: ["equipement"], makeDefault: true })
foundry.documents.collections.Items.registerSheet("fvtt-les-oublies", sheets.LesOubliesPouvoirCompagnieSheet, { types: ["pouvoircompagnie"], makeDefault: true })
LesOubliesUtility.registerHandlebarsHelpers()
})
File diff suppressed because it is too large Load Diff
+76
View File
@@ -0,0 +1,76 @@
import { ACTOR_IMAGES, ITEM_IMAGES, LESOUBLIES_CONFIG, PROFILE_KEYS } from "./les-oublies-config.js"
export class LesOubliesUtility {
static registerHandlebarsHelpers() {
Handlebars.registerHelper("eq", (left, right) => left === right)
Handlebars.registerHelper("join", (values, separator = ", ") => Array.isArray(values) ? values.filter(Boolean).join(separator) : "")
Handlebars.registerHelper("count", (values) => Array.isArray(values) ? values.length : 0)
Handlebars.registerHelper("concat", (...parts) => parts.slice(0, -1).join(""))
Handlebars.registerHelper("profileLabel", (key) => LESOUBLIES_CONFIG.profileLabels[key] ?? key)
Handlebars.registerHelper("skillLabel", (key) => LESOUBLIES_CONFIG.skills[key]?.label ?? key)
Handlebars.registerHelper("formatPrice", (value) => Number(value) > 0 ? `${value} e` : "—")
Handlebars.registerHelper("formatSkillBonus", (bonus) => {
const keys = [bonus.key, ...(bonus.alternativeKeys ?? [])]
.filter(Boolean)
.map((key) => LESOUBLIES_CONFIG.skills[key]?.label ?? key)
const label = keys.join(" ou ")
const domains = []
if (Array.isArray(bonus.domainsGranted) && bonus.domainsGranted.length) domains.push(bonus.domainsGranted.join(", "))
if (Number(bonus.domainsToChoose ?? 0) > 0) {
const prefix = bonus.domainsToChoose > 1 ? `${bonus.domainsToChoose} choix` : "1 choix"
domains.push(bonus.domainsChoiceText ? `${prefix} : ${bonus.domainsChoiceText}` : prefix)
}
return domains.length ? `${label} +${bonus.base}${domains.join(" ; ")}` : `${label} +${bonus.base}`
})
Handlebars.registerHelper("formatEquipmentEntry", (entry) => {
const baseLabel = entry.choiceText || entry.name || ""
const quantity = Number(entry.quantity ?? 0) > 1 ? ` x${entry.quantity}` : ""
const extras = []
if (entry.details) extras.push(entry.details)
if (Number(entry.ecorces ?? 0) > 0) extras.push(`${entry.ecorces} écorces`)
return extras.length ? `${baseLabel}${quantity}${extras.join(", ")}` : `${baseLabel}${quantity}`
})
Handlebars.registerHelper("formatSpellGrant", (grant) => {
const discipline = LESOUBLIES_CONFIG.skills[grant.skillKey]?.label ?? grant.skillKey ?? grant.tradition
return `${grant.tradition} (${discipline}) / ${grant.polarity} : ${grant.amount}`
})
}
static getDefaultActorImage(type) {
return ACTOR_IMAGES[type] ?? "icons/svg/mystery-man.svg"
}
static getDefaultItemImage(type) {
return ITEM_IMAGES[type] ?? "icons/svg/item-bag.svg"
}
static createEmptyProfiles() {
return PROFILE_KEYS.reduce((profiles, key) => {
profiles[key] = 0
return profiles
}, {})
}
static computeDreamPointTotals(songesValue, cauchemarValue) {
if (songesValue > cauchemarValue) {
return {
songesPoints: songesValue * 2 - cauchemarValue,
cauchemarPoints: cauchemarValue,
}
}
if (songesValue === cauchemarValue) {
return {
songesPoints: songesValue,
cauchemarPoints: cauchemarValue,
}
}
return {
songesPoints: songesValue,
cauchemarPoints: cauchemarValue * 3 - songesValue * 2,
}
}
static sortByName(documents = []) {
return [...documents].sort((left, right) => left.name.localeCompare(right.name, "fr"))
}
}
+22
View File
@@ -0,0 +1,22 @@
import { BaseItemDataModel } from "./base-item.mjs"
export default class ArmeDataModel extends BaseItemDataModel {
static defineSchema() {
const fields = foundry.data.fields
return {
...this.defineBaseSchema(),
category: new fields.StringField({ initial: "melee" }),
origin: new fields.StringField({ initial: "petitPeuple" }),
sizeMode: new fields.StringField({ initial: "variable" }),
sizeValue: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
sizeModifier: new fields.NumberField({ initial: 0, integer: true }),
damage: new fields.StringField({ initial: "" }),
range: new fields.StringField({ initial: "" }),
properties: new fields.ArrayField(new fields.StringField(), { initial: [] }),
restrictedRace: new fields.StringField({ initial: "" }),
quantity: new fields.NumberField({ initial: 1, integer: true, min: 0 }),
price: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
equipped: new fields.BooleanField({ initial: false }),
}
}
}
+17
View File
@@ -0,0 +1,17 @@
import { BaseItemDataModel } from "./base-item.mjs"
export default class ArmureDataModel extends BaseItemDataModel {
static defineSchema() {
const fields = foundry.data.fields
return {
...this.defineBaseSchema(),
state: new fields.StringField({ initial: "protege" }),
protection: new fields.NumberField({ initial: 1, integer: true, min: 0 }),
physicalPenalty: new fields.NumberField({ initial: 1, integer: true, min: 0 }),
initiativePenalty: new fields.NumberField({ initial: 1, integer: true, min: 0 }),
quantity: new fields.NumberField({ initial: 1, integer: true, min: 0 }),
price: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
equipped: new fields.BooleanField({ initial: false }),
}
}
}
+11
View File
@@ -0,0 +1,11 @@
export class BaseItemDataModel extends foundry.abstract.TypeDataModel {
static defineBaseSchema() {
const fields = foundry.data.fields
return {
description: new fields.HTMLField({ initial: "" }),
notes: new fields.HTMLField({ initial: "" }),
source: new fields.StringField({ initial: "Livre de règles Les Oubliés" }),
tags: new fields.ArrayField(new fields.StringField(), { initial: [] }),
}
}
}
+26
View File
@@ -0,0 +1,26 @@
export default class CompagnieDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
return {
description: new fields.HTMLField({ initial: "" }),
notes: new fields.HTMLField({ initial: "" }),
captainId: new fields.StringField({ initial: "" }),
memberIds: new fields.ArrayField(new fields.StringField(), { initial: [] }),
ombreDuTourmentId: new fields.StringField({ initial: "" }),
power: new fields.SchemaField({
name: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" }),
sharedDreamPoints: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
activationCondition: new fields.StringField({ initial: "À portée de vue du capitaine" }),
captainVisible: new fields.BooleanField({ initial: true }),
captainNeedsWitness: new fields.BooleanField({ initial: true }),
}),
links: new fields.ArrayField(new fields.SchemaField({
sourceId: new fields.StringField({ initial: "" }),
targetId: new fields.StringField({ initial: "" }),
label: new fields.StringField({ initial: "" }),
details: new fields.StringField({ initial: "" }),
}), { initial: [] }),
}
}
}
+18
View File
@@ -0,0 +1,18 @@
import { BaseItemDataModel } from "./base-item.mjs"
export default class CompetenceDataModel extends BaseItemDataModel {
static defineSchema() {
const fields = foundry.data.fields
return {
...this.defineBaseSchema(),
key: new fields.StringField({ initial: "" }),
profileKey: new fields.StringField({ initial: "" }),
base: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
closed: new fields.BooleanField({ initial: false }),
domainSkill: new fields.BooleanField({ initial: false }),
domains: new fields.ArrayField(new fields.StringField(), { initial: [] }),
fixedDomains: new fields.ArrayField(new fields.StringField(), { initial: [] }),
exampleDomains: new fields.ArrayField(new fields.StringField(), { initial: [] }),
}
}
}
+52
View File
@@ -0,0 +1,52 @@
export default class CreatureDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
return {
biodata: new fields.SchemaField({
categorie: new fields.StringField({ initial: "autre" }),
habitat: new fields.HTMLField({ initial: "" }),
motscles: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" }),
notes: new fields.HTMLField({ initial: "" }),
gmnotes: new fields.HTMLField({ initial: "" }),
}),
size: new fields.SchemaField({
value: new fields.NumberField({ initial: 2, integer: true, min: 1, max: 8 }),
label: new fields.StringField({ initial: "" }),
}),
profils: new fields.SchemaField({
artiste: new fields.NumberField({ initial: 0, integer: true }),
athlete: new fields.NumberField({ initial: 0, integer: true }),
chasseur: new fields.NumberField({ initial: 0, integer: true }),
faiseur: new fields.NumberField({ initial: 0, integer: true }),
forceNature: new fields.NumberField({ initial: 0, integer: true }),
guerrier: new fields.NumberField({ initial: 0, integer: true }),
mystique: new fields.NumberField({ initial: 0, integer: true }),
ombre: new fields.NumberField({ initial: 0, integer: true }),
savant: new fields.NumberField({ initial: 0, integer: true }),
}),
songes: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
points: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
max: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
}),
cauchemar: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
points: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
max: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
}),
hp: new fields.SchemaField({
value: new fields.NumberField({ initial: 8, integer: true, min: 0 }),
max: new fields.NumberField({ initial: 8, integer: true, min: 0 }),
display: new fields.StringField({ initial: "" }),
}),
protection: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
statblock: new fields.SchemaField({
damage: new fields.HTMLField({ initial: "" }),
special: new fields.HTMLField({ initial: "" }),
spellSonges: new fields.HTMLField({ initial: "" }),
spellCauchemar: new fields.HTMLField({ initial: "" }),
}),
}
}
}
+18
View File
@@ -0,0 +1,18 @@
import { BaseItemDataModel } from "./base-item.mjs"
export default class EquipementDataModel extends BaseItemDataModel {
static defineSchema() {
const fields = foundry.data.fields
return {
...this.defineBaseSchema(),
category: new fields.StringField({ initial: "survie" }),
quantity: new fields.NumberField({ initial: 1, integer: true, min: 0 }),
price: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
bonus: new fields.StringField({ initial: "" }),
usage: new fields.StringField({ initial: "" }),
lifespan: new fields.StringField({ initial: "" }),
equipped: new fields.BooleanField({ initial: false }),
consumable: new fields.BooleanField({ initial: false }),
}
}
}
+13
View File
@@ -0,0 +1,13 @@
export { BaseItemDataModel } from "./base-item.mjs"
export { default as PersonnageDataModel } from "./personnage.mjs"
export { default as CompagnieDataModel } from "./compagnie.mjs"
export { default as CreatureDataModel } from "./creature.mjs"
export { default as RaceDataModel } from "./race.mjs"
export { default as TribuDataModel } from "./tribu.mjs"
export { default as MetierDataModel } from "./metier.mjs"
export { default as CompetenceDataModel } from "./competence.mjs"
export { default as SortilegeDataModel } from "./sortilege.mjs"
export { default as ArmeDataModel } from "./arme.mjs"
export { default as ArmureDataModel } from "./armure.mjs"
export { default as EquipementDataModel } from "./equipement.mjs"
export { default as PouvoirCompagnieDataModel } from "./pouvoir-compagnie.mjs"
+39
View File
@@ -0,0 +1,39 @@
import { BaseItemDataModel } from "./base-item.mjs"
export default class MetierDataModel extends BaseItemDataModel {
static defineSchema() {
const fields = foundry.data.fields
return {
...this.defineBaseSchema(),
specialRules: new fields.HTMLField({ initial: "" }),
roleplayNotes: new fields.HTMLField({ initial: "" }),
skillBonuses: new fields.ArrayField(new fields.SchemaField({
key: new fields.StringField({ initial: "" }),
alternativeKeys: new fields.ArrayField(new fields.StringField(), { initial: [] }),
base: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
domainsGranted: new fields.ArrayField(new fields.StringField(), { initial: [] }),
domainsToChoose: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
domainsChoiceText: new fields.StringField({ initial: "" }),
}), { initial: [] }),
startingEquipment: new fields.ArrayField(new fields.SchemaField({
name: new fields.StringField({ initial: "" }),
type: new fields.StringField({ initial: "equipement" }),
quantity: new fields.NumberField({ initial: 1, integer: true, min: 0 }),
details: new fields.StringField({ initial: "" }),
choiceText: new fields.StringField({ initial: "" }),
ecorces: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
}), { initial: [] }),
spellGrants: new fields.ArrayField(new fields.SchemaField({
tradition: new fields.StringField({ initial: "" }),
skillKey: new fields.StringField({ initial: "" }),
polarity: new fields.StringField({ initial: "" }),
amount: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
}), { initial: [] }),
revenues: new fields.SchemaField({
beginner: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
intermediate: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
expert: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
}),
}
}
}
+66
View File
@@ -0,0 +1,66 @@
export default class PersonnageDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
return {
biodata: new fields.SchemaField({
age: new fields.NumberField({ initial: 20, integer: true, min: 0 }),
sexe: new fields.StringField({ initial: "" }),
motscles: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" }),
notes: new fields.HTMLField({ initial: "" }),
gmnotes: new fields.HTMLField({ initial: "" }),
}),
references: new fields.SchemaField({
raceId: new fields.StringField({ initial: "" }),
tribuId: new fields.StringField({ initial: "" }),
metierId: new fields.StringField({ initial: "" }),
compagnieId: new fields.StringField({ initial: "" }),
}),
size: new fields.SchemaField({
value: new fields.NumberField({ initial: 2, integer: true, min: 1, max: 4 }),
label: new fields.StringField({ initial: "" }),
}),
profils: new fields.SchemaField({
artiste: new fields.NumberField({ initial: 0, integer: true }),
athlete: new fields.NumberField({ initial: 0, integer: true }),
chasseur: new fields.NumberField({ initial: 0, integer: true }),
faiseur: new fields.NumberField({ initial: 0, integer: true }),
forceNature: new fields.NumberField({ initial: 0, integer: true }),
guerrier: new fields.NumberField({ initial: 0, integer: true }),
mystique: new fields.NumberField({ initial: 0, integer: true }),
ombre: new fields.NumberField({ initial: 0, integer: true }),
savant: new fields.NumberField({ initial: 0, integer: true }),
}),
songes: new fields.SchemaField({
value: new fields.NumberField({ initial: 1, integer: true, min: 0 }),
points: new fields.NumberField({ initial: 2, integer: true, min: 0 }),
max: new fields.NumberField({ initial: 2, integer: true, min: 0 }),
debt: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
xpCredit: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
}),
cauchemar: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
points: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
max: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
debt: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
xpCredit: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
}),
hp: new fields.SchemaField({
value: new fields.NumberField({ initial: 8, integer: true, min: 0 }),
max: new fields.NumberField({ initial: 8, integer: true, min: 0 }),
bonus: new fields.NumberField({ initial: 0, integer: true }),
}),
experience: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
}),
money: new fields.SchemaField({
ecorces: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
}),
flagsNarratifs: new fields.SchemaField({
ombreDuTourment: new fields.BooleanField({ initial: false }),
isCaptain: new fields.BooleanField({ initial: false }),
}),
visions: new fields.HTMLField({ initial: "" }),
}
}
}
+18
View File
@@ -0,0 +1,18 @@
import { BaseItemDataModel } from "./base-item.mjs"
export default class PouvoirCompagnieDataModel extends BaseItemDataModel {
static defineSchema() {
const fields = foundry.data.fields
return {
...this.defineBaseSchema(),
scope: new fields.StringField({ initial: "compagnie" }),
effectMode: new fields.StringField({ initial: "" }),
ruleText: new fields.HTMLField({ initial: "" }),
limitedUses: new fields.StringField({ initial: "" }),
resourceImpact: new fields.StringField({ initial: "" }),
activationCondition: new fields.StringField({ initial: "À portée de vue du capitaine" }),
captainVisible: new fields.BooleanField({ initial: true }),
captainNeedsWitness: new fields.BooleanField({ initial: true }),
}
}
}
+30
View File
@@ -0,0 +1,30 @@
import { BaseItemDataModel } from "./base-item.mjs"
export default class RaceDataModel extends BaseItemDataModel {
static defineSchema() {
const fields = foundry.data.fields
return {
...this.defineBaseSchema(),
size: new fields.NumberField({ initial: 2, integer: true, min: 1, max: 4 }),
lifeExpectancy: new fields.NumberField({ initial: 50, integer: true, min: 0 }),
keywords: new fields.ArrayField(new fields.StringField(), { initial: [] }),
mainTribes: new fields.ArrayField(new fields.StringField(), { initial: [] }),
language: new fields.StringField({ initial: "" }),
languageDomains: new fields.ArrayField(new fields.StringField(), { initial: [] }),
specialRules: new fields.HTMLField({ initial: "" }),
appearance: new fields.HTMLField({ initial: "" }),
roleplayHints: new fields.ArrayField(new fields.StringField(), { initial: [] }),
profiles: new fields.SchemaField({
artiste: new fields.NumberField({ initial: 0, integer: true }),
athlete: new fields.NumberField({ initial: 0, integer: true }),
chasseur: new fields.NumberField({ initial: 0, integer: true }),
faiseur: new fields.NumberField({ initial: 0, integer: true }),
forceNature: new fields.NumberField({ initial: 0, integer: true }),
guerrier: new fields.NumberField({ initial: 0, integer: true }),
mystique: new fields.NumberField({ initial: 0, integer: true }),
ombre: new fields.NumberField({ initial: 0, integer: true }),
savant: new fields.NumberField({ initial: 0, integer: true }),
}),
}
}
}
+25
View File
@@ -0,0 +1,25 @@
import { BaseItemDataModel } from "./base-item.mjs"
export default class SortilegeDataModel extends BaseItemDataModel {
static defineSchema() {
const fields = foundry.data.fields
return {
...this.defineBaseSchema(),
tradition: new fields.StringField({ initial: "" }),
skillKey: new fields.StringField({ initial: "" }),
polarity: new fields.StringField({ initial: "songes" }),
cost: new fields.NumberField({ initial: 1, integer: true, min: 0 }),
costFormula: new fields.StringField({ initial: "" }),
variableCost: new fields.BooleanField({ initial: false }),
preparation: new fields.StringField({ initial: "" }),
duration: new fields.StringField({ initial: "" }),
range: new fields.StringField({ initial: "" }),
area: new fields.StringField({ initial: "" }),
stacking: new fields.StringField({ initial: "" }),
requiredDomains: new fields.ArrayField(new fields.StringField(), { initial: [] }),
artsDomains: new fields.ArrayField(new fields.StringField(), { initial: [] }),
effectsText: new fields.HTMLField({ initial: "" }),
ruleTags: new fields.ArrayField(new fields.StringField(), { initial: [] }),
}
}
}
+30
View File
@@ -0,0 +1,30 @@
import { BaseItemDataModel } from "./base-item.mjs"
export default class TribuDataModel extends BaseItemDataModel {
static defineSchema() {
const fields = foundry.data.fields
return {
...this.defineBaseSchema(),
keywords: new fields.ArrayField(new fields.StringField(), { initial: [] }),
mainRace: new fields.StringField({ initial: "" }),
spokenLanguage: new fields.StringField({ initial: "" }),
philosophy: new fields.StringField({ initial: "" }),
pride: new fields.StringField({ initial: "" }),
mythNature: new fields.StringField({ initial: "" }),
mythEdenia: new fields.StringField({ initial: "" }),
territory: new fields.StringField({ initial: "" }),
specialRules: new fields.HTMLField({ initial: "" }),
roleplayNotes: new fields.HTMLField({ initial: "" }),
restrictedJobs: new fields.ArrayField(new fields.StringField(), { initial: [] }),
allowedJobs: new fields.ArrayField(new fields.StringField(), { initial: [] }),
skillBonuses: new fields.ArrayField(new fields.SchemaField({
key: new fields.StringField({ initial: "" }),
alternativeKeys: new fields.ArrayField(new fields.StringField(), { initial: [] }),
base: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
domainsGranted: new fields.ArrayField(new fields.StringField(), { initial: [] }),
domainsToChoose: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
domainsChoiceText: new fields.StringField({ initial: "" }),
}), { initial: [] }),
}
}
}