Update actor sheet

This commit is contained in:
2025-12-20 00:09:42 +01:00
parent 65dfb3ddff
commit 189b03ca91
24 changed files with 4669 additions and 1791 deletions
+18 -1
View File
@@ -1,12 +1,13 @@
import PrismRPGActorSheet from "./base-actor-sheet.mjs"
import PrismRPGRoll from "../../documents/roll.mjs"
import { SYSTEM } from "../../config/system.mjs"
export default class PrismRPGCharacterSheet extends PrismRPGActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["character"],
position: {
width: 972,
width: 750,
height: 780,
},
window: {
@@ -36,6 +37,9 @@ export default class PrismRPGCharacterSheet extends PrismRPGActorSheet {
skills: {
template: "systems/fvtt-prism-rpg/templates/character-skills.hbs",
},
subattributes: {
template: "systems/fvtt-prism-rpg/templates/character-subattributes.hbs",
},
combat: {
template: "systems/fvtt-prism-rpg/templates/character-combat.hbs",
},
@@ -67,6 +71,7 @@ export default class PrismRPGCharacterSheet extends PrismRPGActorSheet {
#getTabs() {
let tabs = {
skills: { id: "skills", group: "sheet", icon: "fa-solid fa-shapes", label: "PRISMRPG.Label.skills" },
subattributes: { id: "subattributes", group: "sheet", icon: "fa-solid fa-diagram-project", label: "PRISMRPG.Label.subattributes" },
combat: { id: "combat", group: "sheet", icon: "fa-solid fa-swords", label: "PRISMRPG.Label.combat" },
equipment: { id: "equipment", group: "sheet", icon: "fa-solid fa-backpack", label: "PRISMRPG.Label.equipment" },
biography: { id: "biography", group: "sheet", icon: "fa-solid fa-book", label: "PRISMRPG.Label.biography" },
@@ -90,6 +95,7 @@ export default class PrismRPGCharacterSheet extends PrismRPGActorSheet {
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
context.config = SYSTEM
return context
}
@@ -99,6 +105,14 @@ export default class PrismRPGCharacterSheet extends PrismRPGActorSheet {
const doc = this.document
switch (partId) {
case "main":
context.race = doc.itemTypes.race?.[0] || null
const classes = doc.itemTypes.class || []
// Create 3 class slots
context.classSlots = [
classes[0] || null,
classes[1] || null,
classes[2] || null
]
break
case "skills":
context.tab = context.tabs.skills
@@ -106,6 +120,9 @@ export default class PrismRPGCharacterSheet extends PrismRPGActorSheet {
context.racialAbilities = doc.itemTypes["racial-ability"]
context.vulnerabilities = doc.itemTypes.vulnerability
break
case "subattributes":
context.tab = context.tabs.subattributes
break
case "spells":
context.tab = context.tabs.spells
context.spells = doc.itemTypes.spell
+18 -18
View File
@@ -135,28 +135,28 @@ export const CHALLENGES = Object.freeze({
})
export const SAVES = Object.freeze({
will: {
id: "will",
label: "PRISMRPG.Character.will.label"
str: {
id: "str",
label: "PRISMRPG.Character.str.label"
},
dodge: {
id: "dodge",
label: "PRISMRPG.Character.dodge.label"
dex: {
id: "dex",
label: "PRISMRPG.Character.dex.label"
},
toughness: {
id: "toughness",
label: "PRISMRPG.Character.toughness.label"
con: {
id: "con",
label: "PRISMRPG.Character.con.label"
},
contagion: {
id: "contagion",
label: "PRISMRPG.Character.contagion.label"
int: {
id: "int",
label: "PRISMRPG.Character.int.label"
},
poison: {
id: "poison",
label: "PRISMRPG.Character.poison.label"
wis: {
id: "wis",
label: "PRISMRPG.Character.wis.label"
},
pain: {
id: "pain",
label: "PRISMRPG.Character.pain.label"
cha: {
id: "cha",
label: "PRISMRPG.Character.cha.label"
}
})
+4 -4
View File
@@ -56,10 +56,10 @@ export const MORTAL_CHOICES = {
"halflings": { label: "Halfling", id: "halflings", defenseBonus: 2 }
}
export const FAVOR_CHOICES = {
export const ADVANTAGE_CHOICES = {
"none": { label: "None", value: "none" },
"favor": { label: "Favor", value: "favor" },
"disfavor": { label: "Disfavor", value: "disfavor" }
"advantage": { label: "Advantage", value: "advantage" },
"disadvantage": { label: "Disadvantage", value: "disadvantage" }
}
export const MOVEMENT_CHOICES = {
@@ -327,7 +327,7 @@ export const SYSTEM = {
MOVE_DIRECTION_CHOICES,
SIZE_CHOICES,
RANGE_CHOICES,
FAVOR_CHOICES,
ADVANTAGE_CHOICES,
ATTACKER_AIM_CHOICES,
MORTAL_CHOICES,
MIRACLE_TYPES,
+61 -45
View File
@@ -79,17 +79,28 @@ export default class PrismRPGActor extends Actor {
}
/* *************************************************/
async prepareRoll(rollType, rollKey, rollDice ) {
async prepareRoll(rollType, rollKey, rollDice) {
console.log("Preparing roll", rollType, rollKey, rollDice)
let rollTarget
switch (rollType) {
case "characteristic":
if (!this.system.characteristics || !this.system.characteristics[rollKey]) {
ui.notifications.error(`Characteristic ${rollKey} not found`)
return
}
rollTarget = {
...foundry.utils.duplicate(this.system.characteristics[rollKey]),
rollKey: rollKey,
name: game.i18n.localize(`PRISMRPG.Label.${rollKey}`)
}
break
case "granted":
rollTarget = {
name: rollKey,
formula: foundry.utils.duplicate(this.system.granted[rollKey]),
rollKey: rollKey
}
if ( rollTarget.formula === "" || rollTarget.formula === undefined) {
if (rollTarget.formula === "" || rollTarget.formula === undefined) {
rollTarget.formula = 0
}
break;
@@ -126,70 +137,75 @@ export default class PrismRPGActor extends Actor {
rollTarget.rollKey = rollKey
break
case "shield-roll": {
rollTarget = this.items.find((i) => i.type === "shield" && i.id === rollKey)
let shieldSkill = this.items.find((i) => i.type === "skill" && i.name.toLowerCase() === rollTarget.name.toLowerCase())
rollTarget.skill = shieldSkill
rollTarget.rollKey = rollKey
}
rollTarget = this.items.find((i) => i.type === "shield" && i.id === rollKey)
let shieldSkill = this.items.find((i) => i.type === "skill" && i.name.toLowerCase() === rollTarget.name.toLowerCase())
rollTarget.skill = shieldSkill
rollTarget.rollKey = rollKey
}
break;
case "weapon-damage-small":
case "weapon-damage-medium":
case "weapon-attack":
case "weapon-defense": {
let weapon = this.items.find((i) => i.type === "weapon" && i.id === rollKey)
let skill
let skills = this.items.filter((i) => i.type === "skill" && i.name.toLowerCase() === weapon.name.toLowerCase())
let weapon = this.items.find((i) => i.type === "weapon" && i.id === rollKey)
let skill
let skills = this.items.filter((i) => i.type === "skill" && i.name.toLowerCase() === weapon.name.toLowerCase())
if (skills.length > 0) {
skill = this.getBestWeaponClassSkill(skills, rollType, 1.0)
} else {
skills = this.items.filter((i) => i.type === "skill" && i.name.toLowerCase().replace(" skill", "") === weapon.name.toLowerCase())
if (skills.length > 0) {
skill = this.getBestWeaponClassSkill(skills, rollType, 1.0)
} else {
skills = this.items.filter((i) => i.type === "skill" && i.name.toLowerCase().replace(" skill", "") === weapon.name.toLowerCase())
skills = this.items.filter((i) => i.type === "skill" && i.system.weaponClass === weapon.system.weaponClass)
if (skills.length > 0) {
skill = this.getBestWeaponClassSkill(skills, rollType, 1.0)
skill = this.getBestWeaponClassSkill(skills, rollType, 0.5)
} else {
skills = this.items.filter((i) => i.type === "skill" && i.system.weaponClass === weapon.system.weaponClass)
skills = this.items.filter((i) => i.type === "skill" && i.system.weaponClass.includes(SYSTEM.WEAPON_CATEGORIES[weapon.system.weaponClass]))
if (skills.length > 0) {
skill = this.getBestWeaponClassSkill(skills, rollType, 0.5)
skill = this.getBestWeaponClassSkill(skills, rollType, 0.25)
} else {
skills = this.items.filter((i) => i.type === "skill" && i.system.weaponClass.includes(SYSTEM.WEAPON_CATEGORIES[weapon.system.weaponClass]))
if (skills.length > 0) {
skill = this.getBestWeaponClassSkill(skills, rollType, 0.25)
} else {
ui.notifications.warn(game.i18n.localize("PRISMRPG.Notifications.skillNotFound"))
return
}
ui.notifications.warn(game.i18n.localize("PRISMRPG.Notifications.skillNotFound"))
return
}
}
}
if (!weapon || !skill) {
console.error("Weapon or skill not found", weapon, skill)
ui.notifications.warn(game.i18n.localize("PRISMRPG.Notifications.skillNotFound"))
return
}
rollTarget = skill
rollTarget.weapon = weapon
rollTarget.weaponSkillModifier = skill.weaponSkillModifier
rollTarget.rollKey = rollKey
rollTarget.combat = foundry.utils.duplicate(this.system.combat)
if ( rollType === "weapon-damage-small" || rollType === "weapon-damage-medium") {
rollTarget.grantedDice = this.system.granted.damageDice
}
if ( rollType === "weapon-attack") {
rollTarget.grantedDice = this.system.granted.attackDice
}
if ( rollType === "weapon-defense") {
rollTarget.grantedDice = this.system.granted.defenseDice
}
}
if (!weapon || !skill) {
console.error("Weapon or skill not found", weapon, skill)
ui.notifications.warn(game.i18n.localize("PRISMRPG.Notifications.skillNotFound"))
return
}
rollTarget = skill
rollTarget.weapon = weapon
rollTarget.weaponSkillModifier = skill.weaponSkillModifier
rollTarget.rollKey = rollKey
rollTarget.combat = foundry.utils.duplicate(this.system.combat)
if (rollType === "weapon-damage-small" || rollType === "weapon-damage-medium") {
rollTarget.grantedDice = this.system.granted.damageDice
}
if (rollType === "weapon-attack") {
rollTarget.grantedDice = this.system.granted.attackDice
}
if (rollType === "weapon-defense") {
rollTarget.grantedDice = this.system.granted.defenseDice
}
}
break
default:
ui.notifications.error(game.i18n.localize("PRISMRPG.Notifications.rollTypeNotFound") + String(rollType))
break
return
}
// In all cases
rollTarget.magicUser = this.system.biodata.magicUser
rollTarget.actorModifiers = foundry.utils.duplicate(this.system.modifiers)
rollTarget.actorLevel = this.system.biodata.level
// In all cases - verify rollTarget exists
if (!rollTarget) {
console.error("Roll target is undefined for rollType:", rollType)
return
}
rollTarget.magicUser = this.system.biodata?.magicUser || false
rollTarget.actorModifiers = this.system.modifiers ? foundry.utils.duplicate(this.system.modifiers) : {}
rollTarget.actorLevel = this.system.biodata?.level || 1
await this.system.roll(rollType, rollTarget)
}
File diff suppressed because it is too large Load Diff
+152 -1000
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+42 -73
View File
@@ -43,6 +43,20 @@ export default class PrismRPGCharacter extends foundry.abstract.TypeDataModel {
}, {}),
)
// Sub-Attributes (derived from two parent characteristics)
const subAttributeField = (label) => {
const schema = {
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
}
return new fields.SchemaField(schema, { label })
}
schema.subAttributes = new fields.SchemaField(
Object.values(SYSTEM.SUB_ATTRIBUTES).reduce((obj, subAttr) => {
obj[subAttr.id] = subAttributeField(subAttr.label)
return obj
}, {}),
)
// Challenges
const challengeField = (label) => {
const schema = {
@@ -67,50 +81,21 @@ export default class PrismRPGCharacter extends foundry.abstract.TypeDataModel {
schema.hp = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),
painDamage: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
wounds: new fields.ArrayField(new fields.SchemaField(woundFieldSchema), {
initial: [{ description: "", value: 0, duration: 0 }, { description: "", value: 0, duration: 0 },
{ description: "", value: 0, duration: 0 }, { description: "", value: 0, duration: 0 }, { description: "", value: 0, duration: 0 }, { description: "", value: 0, duration: 0 },
{ description: "", value: 0, duration: 0 }, { description: "", value: 0, duration: 0 }], min: 8
}),
damageResistance: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
temp: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
})
schema.perception = new fields.SchemaField({
schema.magicPoints = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
})
schema.grit = new fields.SchemaField({
starting: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
earned: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
current: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
})
schema.luck = new fields.SchemaField({
earned: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
current: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
})
schema.granted = new fields.SchemaField({
attackDice: new fields.StringField({ required: true, nullable: false, initial: "0", choices: SYSTEM.GRANTED_DICE_CHOICES }),
defenseDice: new fields.StringField({ required: true, nullable: false, initial: "0", choices: SYSTEM.GRANTED_DICE_CHOICES }),
damageDice: new fields.StringField({ required: true, nullable: false, initial: "0", choices: SYSTEM.GRANTED_DICE_CHOICES })
max: 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.armorPoints = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
})
schema.biodata = new fields.SchemaField({
class: new fields.StringField({ required: true, initial: "untrained", choices: SYSTEM.CHAR_CLASSES }),
level: new fields.NumberField({ ...requiredInteger, initial: 1, min: 1 }),
mortal: new fields.StringField({ required: true, initial: "mankind", choices: SYSTEM.MORTAL_CHOICES }),
alignment: new fields.StringField({ required: true, nullable: false, initial: "" }),
age: new fields.NumberField({ ...requiredInteger, initial: 15, min: 6 }),
height: new fields.NumberField({ ...requiredInteger, initial: 170, min: 10 }),
@@ -118,16 +103,7 @@ export default class PrismRPGCharacter extends foundry.abstract.TypeDataModel {
eyes: new fields.StringField({ required: true, nullable: false, initial: "" }),
hair: new fields.StringField({ required: true, nullable: false, initial: "" }),
magicUser: new fields.BooleanField({ initial: false }),
clericUser: new fields.BooleanField({ initial: false }),
hpPerLevel: new fields.StringField({ required: true, nullable: false, initial: "" }),
})
schema.modifiers = new fields.SchemaField({
levelSpellModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
saveModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
levelMiracleModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
intSpellModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
chaMiracleModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
clericUser: new fields.BooleanField({ initial: false })
})
schema.developmentPoints = new fields.SchemaField({
@@ -171,14 +147,16 @@ export default class PrismRPGCharacter extends foundry.abstract.TypeDataModel {
// Core Skill system (Prism RPG)
schema.coreSkill = new fields.SchemaField({
skill: new fields.StringField({
required: true,
initial: "",
required: false,
nullable: true,
initial: null,
choices: SYSTEM.CORE_SKILLS_CHOICES,
label: "Selected Core Skill"
}),
attributeChoice: new fields.StringField({
required: true,
initial: "",
required: false,
nullable: true,
initial: null,
label: "Attribute Choice for +2 Bonus"
})
})
@@ -216,43 +194,34 @@ export default class PrismRPGCharacter extends foundry.abstract.TypeDataModel {
prepareDerivedData() {
super.prepareDerivedData();
let grit = 0
for (let c in this.characteristics) {
if (SYSTEM.CHARACTERISTICS_MAJOR[c.id]) {
grit += this.characteristics[c].value
}
// Calculate sub-attributes from parent characteristics
// Sub-attribute = lowest ability modifier between the two parent characteristics
for (let subAttrKey in SYSTEM.SUB_ATTRIBUTES) {
const subAttr = SYSTEM.SUB_ATTRIBUTES[subAttrKey]
const parent1Value = this.characteristics[subAttr.parents[0]].value
const parent2Value = this.characteristics[subAttr.parents[1]].value
// Calculate D&D 5e style ability modifiers: (ability - 10) / 2
const parent1Bonus = Math.floor((parent1Value - 10) / 2)
const parent2Bonus = Math.floor((parent2Value - 10) / 2)
// Take the lowest modifier
this.subAttributes[subAttrKey].value = Math.min(parent1Bonus, parent2Bonus)
}
this.modifiers.saveModifier = Math.floor((Number(this.biodata.level) / 5))
this.modifiers.levelSpellModifier = Math.floor((Number(this.biodata.level) / 5))
this.modifiers.levelMiracleModifier = Math.floor((Number(this.biodata.level) / 5))
this.grit.starting = Math.round(grit / 6)
// Calculate save modifier locally (not stored)
const saveModifier = Math.floor((Number(this.biodata.level) / 5))
let strDef = SYSTEM.CHARACTERISTICS_TABLES.str.find(s => s.value === this.characteristics.str.value)
this.challenges.str.value = strDef.challenge
let intDef = SYSTEM.CHARACTERISTICS_TABLES.int.find(s => s.value === this.characteristics.int.value)
this.modifiers.intSpellModifier = intDef.arkane_casting_mod
let dexDef = SYSTEM.CHARACTERISTICS_TABLES.dex.find(s => s.value === this.characteristics.dex.value)
this.challenges.agility.value = dexDef.challenge
this.saves.dodge.value = dexDef.dodge + this.modifiers.saveModifier
let wisDef = SYSTEM.CHARACTERISTICS_TABLES.wis.find(s => s.value === this.characteristics.wis.value)
this.saves.will.value = wisDef.willpower_save + this.modifiers.saveModifier
let chaDef = SYSTEM.CHARACTERISTICS_TABLES.cha.find(s => s.value === this.characteristics.cha.value)
this.modifiers.chaMiracleModifier = chaDef.divine_miracle_bonus
let conDef = SYSTEM.CHARACTERISTICS_TABLES.con.find(s => s.value === this.characteristics.con.value)
this.saves.pain.value = conDef.pain_save + this.modifiers.saveModifier
this.saves.toughness.value = conDef.toughness_save + this.modifiers.saveModifier
this.challenges.dying.value = conDef.stabilization_dice
this.saves.contagion.value = this.characteristics.con.value;// + this.modifiers.saveModifier
this.saves.poison.value = this.characteristics.con.value; // + this.modifiers.saveModifier
this.combat.attackModifier = 0
for (let chaKey of SYSTEM.CHARACTERISTIC_ATTACK) {
let chaDef = SYSTEM.CHARACTERISTICS_TABLES[chaKey].find(s => s.value === this.characteristics[chaKey].value)
+6
View File
@@ -170,6 +170,12 @@ export default class PrismRPGUtils {
Handlebars.registerHelper('countKeys', function (obj) {
return Object.keys(obj).length;
})
Handlebars.registerHelper('entries', function (obj) {
return Object.entries(obj);
})
Handlebars.registerHelper('uppercase', function (str) {
return str ? str.toUpperCase() : '';
})
Handlebars.registerHelper('isEnabled', function (configKey) {
return game.settings.get("bol", configKey);