233 lines
9.0 KiB
JavaScript
233 lines
9.0 KiB
JavaScript
import { SYSTEM } from "../config/system.mjs"
|
|
import FTLNomadRoll from "../documents/roll.mjs"
|
|
|
|
export default class FTLNomadProtagonist 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 })
|
|
schema.name = new fields.StringField({ required: true, nullable: false, initial: "" })
|
|
schema.concept = new fields.StringField({ required: true, nullable: false, initial: "" })
|
|
schema.species = new fields.StringField({ required: true, nullable: false, initial: "" })
|
|
schema.archetype = new fields.StringField({ required: true, nullable: false, initial: "" })
|
|
|
|
// Carac
|
|
const skillField = (label) => {
|
|
const schema = {
|
|
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
|
}
|
|
return new fields.SchemaField(schema, { label })
|
|
}
|
|
|
|
schema.skills = new fields.SchemaField(
|
|
Object.values(SYSTEM.SKILLS).reduce((obj, characteristic) => {
|
|
obj[characteristic.id] = skillField(characteristic.label)
|
|
return obj
|
|
}, {}),
|
|
)
|
|
|
|
schema.wp = new fields.SchemaField({
|
|
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
|
max: new fields.NumberField({ ...requiredInteger, initial: 3, min: 0 }),
|
|
exhausted: new fields.BooleanField({ required: true, initial: false })
|
|
})
|
|
|
|
schema.hp = new fields.SchemaField({
|
|
value: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),
|
|
max: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),
|
|
stunned: new fields.BooleanField({ required: true, initial: false })
|
|
})
|
|
|
|
schema.san = new fields.SchemaField({
|
|
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
|
max: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
|
recovery: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
|
violence: new fields.ArrayField(new fields.BooleanField(), { required: true, initial: [false, false, false], min:3, max:3}),
|
|
helplessness: new fields.ArrayField(new fields.BooleanField(), { required: true, initial: [false, false, false], min:3, max:3 }),
|
|
breakingPoint: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
|
insanity: new fields.StringField({ required: true, nullable: false, initial: "none", choices:SYSTEM.INSANITY }),
|
|
})
|
|
|
|
schema.damageBonus = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
|
|
|
|
schema.resources = new fields.SchemaField({
|
|
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }), // Unused but kept for compatibility
|
|
permanentRating: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
|
hand: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
|
currentHand: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
|
stowed: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
|
currentStowed: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
|
storage: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
|
currentStorage: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
|
checks: new fields.ArrayField(new fields.BooleanField(), { required: true, initial: [false, false, false], min:3, max:3 }),
|
|
nbValidChecks: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
|
|
})
|
|
|
|
schema.biodata = new fields.SchemaField({
|
|
age: new fields.NumberField({ ...requiredInteger, initial: 15, min: 6 }),
|
|
archetype: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
|
height: new fields.NumberField({ ...requiredInteger, initial: 170, min: 50 }),
|
|
gender: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
|
home: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
|
birthplace: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
|
eyes: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
|
hair: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
|
harshness: new fields.StringField({ required: true, nullable: false, initial: "normal", choices:SYSTEM.HARSHNESS }),
|
|
adaptedToViolence: new fields.BooleanField({ required: true, initial: false }),
|
|
adaptedToHelplessness: new fields.BooleanField({ required: true, initial: false })
|
|
})
|
|
|
|
return schema
|
|
}
|
|
|
|
/** @override */
|
|
static LOCALIZATION_PREFIXES = ["FTLNOMAD.Protagonist"]
|
|
|
|
prepareDerivedData() {
|
|
super.prepareDerivedData();
|
|
|
|
let updates = {}
|
|
if ( this.wp.max !== this.characteristics.pow.value) {
|
|
updates[`system.wp.max`] = this.characteristics.pow.value
|
|
}
|
|
let hpMax = Math.round((this.characteristics.con.value + this.characteristics.str.value) / 2)
|
|
if ( this.hp.max !== hpMax) {
|
|
updates[`system.hp.max`] = hpMax
|
|
}
|
|
|
|
// Get Unnatural skill for MAX SAN
|
|
let unnatural = this.parent.items.find(i => i.type === "skill" && i.name.toLowerCase() === game.i18n.localize("FTLNOMAD.Skill.Unnatural").toLowerCase())
|
|
let minus = 0
|
|
if (unnatural) {
|
|
minus = unnatural.system.skillTotal
|
|
}
|
|
let maxSan = Math.max(99 - minus, 0)
|
|
if ( this.san.max !== maxSan) {
|
|
updates[`system.san.max`] = maxSan
|
|
}
|
|
|
|
let recoverySan = this.characteristics.pow.value * 5
|
|
if (recoverySan > this.san.max) {
|
|
recoverySan = this.san.max
|
|
}
|
|
if ( this.san.recovery !== recoverySan) {
|
|
updates[`system.san.recovery`] = recoverySan
|
|
}
|
|
|
|
let dmgBonus = 0
|
|
if (this.characteristics.str.value <= 4) {
|
|
dmgBonus = -2
|
|
} else if (this.characteristics.str.value <= 8) {
|
|
dmgBonus = -1
|
|
} else if (this.characteristics.str.value <= 12) {
|
|
dmgBonus = 0
|
|
} else if (this.characteristics.str.value <= 16) {
|
|
dmgBonus = 1
|
|
} else if (this.characteristics.str.value <= 20) {
|
|
dmgBonus = 2
|
|
}
|
|
if ( this.damageBonus !== dmgBonus) {
|
|
updates[`system.damageBonus`] = dmgBonus
|
|
}
|
|
|
|
// Sanity check
|
|
if (this.san.value > this.san.max) {
|
|
updates[`system.san.value`] = this.san.max
|
|
}
|
|
if (this.wp.value > this.wp.max) {
|
|
updates[`system.wp.value`] = this.wp.max
|
|
}
|
|
if (this.hp.value > this.hp.max) {
|
|
updates[`system.hp.value`] = this.hp.max
|
|
}
|
|
|
|
if (this.resources.permanentRating < 0) {
|
|
updates[`system.resources.permanentRating`] = 0
|
|
}
|
|
|
|
let resourceIndex = Math.max(Math.min(this.resources.permanentRating, 20), 0)
|
|
let breakdown = SYSTEM.RESOURCE_BREAKDOWN[resourceIndex]
|
|
if (this.resources.hand !== breakdown.hand) {
|
|
updates[`system.resources.hand`] = breakdown.hand
|
|
}
|
|
if (this.resources.stowed !== breakdown.stowed) {
|
|
updates[`system.resources.stowed`] = breakdown.stowed
|
|
}
|
|
if (this.resources.storage !== breakdown.storage) {
|
|
updates[`system.resources.storage`] = breakdown.storage + (this.resources.permanentRating - resourceIndex)
|
|
}
|
|
if (this.resources.nbValidChecks !== breakdown.checks) {
|
|
updates[`system.resources.nbValidChecks`] = breakdown.checks
|
|
}
|
|
|
|
if (Object.keys(updates).length > 0) {
|
|
this.parent.update(updates)
|
|
}
|
|
}
|
|
|
|
isLowWP() {
|
|
return this.wp.value <= 2
|
|
}
|
|
|
|
isZeroWP() {
|
|
return this.wp.value === 0
|
|
}
|
|
|
|
isExhausted() {
|
|
return this.wp.exhausted
|
|
}
|
|
|
|
modifyWP(value) {
|
|
let updates = {}
|
|
let wp = Math.max(Math.min(this.wp.value + value, this.wp.max), 0)
|
|
if ( this.wp.value !== wp) {
|
|
updates[`system.wp.value`] = wp
|
|
}
|
|
if (Object.keys(updates).length > 0) {
|
|
this.parent.update(updates)
|
|
}
|
|
}
|
|
|
|
setBP() {
|
|
let updates = {}
|
|
let bp = Math.max(this.san.value - this.characteristics.pow.value, 0)
|
|
if ( this.san.breakingPoint !== bp) {
|
|
updates[`system.san.breakingPoint`] = bp
|
|
}
|
|
if (Object.keys(updates).length > 0) {
|
|
this.parent.update(updates)
|
|
}
|
|
}
|
|
|
|
/** */
|
|
/**
|
|
* Rolls a dice for a character.
|
|
* @param {("save"|"resource|damage")} rollType The type of the roll.
|
|
* @param {number} rollItem The target value for the roll. Which caracteristic or resource. If the roll is a damage roll, this is the id of the item.
|
|
* @returns {Promise<null>} - A promise that resolves to null if the roll is cancelled.
|
|
*/
|
|
async roll(rollType, rollItem) {
|
|
let opponentTarget
|
|
const hasTarget = opponentTarget !== undefined
|
|
|
|
let roll = await CthulhuEternalRoll.prompt({
|
|
rollType,
|
|
rollItem,
|
|
isLowWP: this.isLowWP(),
|
|
isZeroWP: this.isZeroWP(),
|
|
isExhausted: this.isExhausted(),
|
|
actorId: this.parent.id,
|
|
actorName: this.parent.name,
|
|
actorImage: this.parent.img,
|
|
hasTarget,
|
|
target: opponentTarget
|
|
})
|
|
if (!roll) return null
|
|
|
|
await roll.toMessage({}, { rollMode: roll.options.rollMode })
|
|
}
|
|
}
|