202 lines
8.2 KiB
JavaScript

import { SYSTEM } from "../config/system.mjs"
import LethalFantasyRoll from "../documents/roll.mjs"
export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const requiredInteger = { required: true, nullable: false, integer: true }
const schema = {}
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.notes = new fields.HTMLField({ required: true, textSearch: true })
// Carac
const characteristicField = (label) => {
const schema = {
value: new fields.NumberField({ ...requiredInteger, initial: 3, min: 0 }),
percent: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0, max: 100 }),
attackMod: new fields.NumberField({ ...requiredInteger, initial: 0 }),
defenseMod: new fields.NumberField({ ...requiredInteger, initial: 0 })
}
return new fields.SchemaField(schema, { label })
}
schema.characteristics = new fields.SchemaField(
Object.values(SYSTEM.MONSTER_CHARACTERISTICS).reduce((obj, characteristic) => {
obj[characteristic.id] = characteristicField(characteristic.label)
return obj
}, {}),
)
// Save
const saveField = (label) => {
const schema = {
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
}
return new fields.SchemaField(schema, { label })
}
schema.saves = new fields.SchemaField(
Object.values(SYSTEM.MONSTER_SAVES).reduce((obj, save) => {
obj[save.id] = saveField(save.label)
return obj
}, {}),
)
// Resist
const resistField = (label) => {
const schema = {
value: new fields.StringField({ initial: "0", required: true, nullable: false }),
}
return new fields.SchemaField(schema, { label })
}
schema.resists = new fields.SchemaField(
Object.values(SYSTEM.MONSTER_RESIST).reduce((obj, save) => {
obj[save.id] = resistField(save.label)
return obj
}, {}),
)
schema.hp = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),
average: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),
damageResistance: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
painDamage: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
})
const attackField = (label) => {
const schema = {
key: new fields.StringField({ required: true, nullable: false, initial: `attack${label}` }),
name: new fields.StringField({ required: true, nullable: false, initial: `Attack ${label}` }),
attackScore: new fields.NumberField({ ...requiredInteger, initial: Number(label), min: 0 }),
attackModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
defenseModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
damageDice: new fields.StringField({ required: true, nullable: false, initial: "1D6" }),
damageModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
}
return new fields.SchemaField(schema, { label })
}
// Add 4 attackFields in an attack schema
schema.attacks = new fields.SchemaField({
attack1: attackField("1"),
attack2: attackField("2"),
attack3: attackField("3"),
attack4: attackField("4"),
})
schema.perception = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
})
schema.movement = new fields.SchemaField({
walk: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
jog: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
sprint: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
run: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
armorAdjust: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
})
schema.jump = new fields.SchemaField({
broad: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
running: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
vertical: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
})
schema.biodata = new fields.SchemaField({
alignment: new fields.StringField({ required: true, nullable: false, initial: "" }),
vision: new fields.StringField({ required: true, nullable: false, initial: "" }),
height: new fields.StringField({ required: true, nullable: false, initial: "" }),
length: new fields.StringField({ required: true, nullable: false, initial: "" }),
weight: new fields.StringField({ required: true, nullable: false, initial: "" })
})
schema.combat = new fields.SchemaField({
attackModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
defenseModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
damageModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
armorHitPoints: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
})
return schema
}
/** @override */
static LOCALIZATION_PREFIXES = ["LETHALFANTASY.Monster"]
/**
* Rolls a dice for a character.
* @param {("save"|"resource|damage")} rollType The type of the roll.
* @param {number} rollTarget The target value for the roll. Which caracteristic or resource. If the roll is a damage roll, this is the id of the item.
* @param {"="|"+"|"++"|"-"|"--"} rollAdvantage If there is an avantage (+), a disadvantage (-), a double advantage (++), a double disadvantage (--) or a normal roll (=).
* @returns {Promise<null>} - A promise that resolves to null if the roll is cancelled.
*/
async roll(rollType, rollTarget) {
const hasTarget = false
let roll = await LethalFantasyRoll.prompt({
rollType,
rollTarget,
actorId: this.parent.id,
actorName: this.parent.name,
actorImage: this.parent.img,
hasTarget,
target: false
})
if (!roll) return null
await roll.toMessage({}, { rollMode: roll.options.rollMode })
}
async rollInitiative(combatId = undefined, combatantId = undefined) {
const hasTarget = false
let maxInit = 100
let roll = await LethalFantasyRoll.promptInitiative({
actorId: this.parent.id,
actorName: this.parent.name,
actorImage: this.parent.img,
combatId,
combatantId,
actorClass: "fighter",
maxInit,
})
if (!roll) return null
await roll.toMessage({}, { rollMode: roll.options.rollMode })
}
async rollProgressionDice(combatId, combatantId, rollProgressionCount) {
const rollModes = Object.fromEntries(Object.entries(CONFIG.Dice.rollModes).map(([key, value]) => [key, game.i18n.localize(value)]))
const fieldRollMode = new foundry.data.fields.StringField({
choices: rollModes,
blank: false,
default: "public",
})
let roll = new Roll("1D8")
await roll.evaluate()
let max = rollProgressionCount
let msg = await roll.toMessage({ flavor: `Progression Roll for ${this.parent.name}` } )
if (game?.dice3d) {
await game.dice3d.waitFor3DAnimationByMessageID(msg.id)
}
let hasAttack = false
for (let key in this.attacks) {
let attack = this.attacks[key]
if (attack.attackScore > 0 && attack.attackScore === roll.total) {
hasAttack = true
let message = game.i18n.format("LETHALFANTASY.Notifications.messageProgressionOKMonster", { isMonster: true, name: this.parent.name, weapon: attack.name, roll: roll.total })
ChatMessage.create({ content: message, speaker: ChatMessage.getSpeaker({ actor: this.parent }) })
}
}
if (!hasAttack) {
let message = game.i18n.format("LETHALFANTASY.Notifications.messageProgressionKOMonster", { isMonster: true, name: this.parent.name, roll: roll.total })
ChatMessage.create({ content: message, speaker: ChatMessage.getSpeaker({ actor: this.parent }) })
}
}
}