First adaptation pass
This commit is contained in:
+106
-86
@@ -1,29 +1,75 @@
|
||||
import { SYSTEM } from "../config/system.mjs"
|
||||
import { CATEGORY } from "../config/skill.mjs"
|
||||
|
||||
/**
|
||||
* Core Skill data model for Prism RPG
|
||||
*
|
||||
* Core Skills are skills in which the character is particularly proficient.
|
||||
* - Basic skill checks: +5 modifier
|
||||
* - Advanced skill checks: Only accessible with Core Skill
|
||||
* - Core Skill Class: Gives access to a class based on archetype
|
||||
* - Attribute Bonus: +2 to one of 3 associated attributes
|
||||
*/
|
||||
export default class PrismRPGSkill extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields
|
||||
const schema = {}
|
||||
const requiredInteger = { required: true, nullable: false, integer: true }
|
||||
|
||||
schema.description = new fields.HTMLField({ required: true, textSearch: true })
|
||||
schema.category = new fields.StringField({ required: true, initial: "layperson", choices: SYSTEM.SKILL_CATEGORY })
|
||||
schema.base = new fields.StringField({ required: true, initial: "WIS" })
|
||||
schema.bonus = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
|
||||
|
||||
schema.classesCost = new fields.SchemaField(
|
||||
Object.values(SYSTEM.CHAR_CLASSES_DEFINES).reduce((obj, pcClass) => {
|
||||
obj[pcClass.id] = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
|
||||
return obj
|
||||
}, {}),
|
||||
)
|
||||
schema.cost = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
|
||||
schema.description = new fields.HTMLField({
|
||||
required: true,
|
||||
textSearch: true,
|
||||
initial: ""
|
||||
})
|
||||
|
||||
schema.weaponClass = new fields.StringField({ required: true, initial: "shortblade", choices: SYSTEM.WEAPON_CLASS })
|
||||
schema.weaponBonus = new fields.SchemaField({
|
||||
attack: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
|
||||
defense: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
|
||||
damage: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
|
||||
// Core Skill type (from the 18 available Core Skills)
|
||||
schema.coreSkill = new fields.StringField({
|
||||
required: true,
|
||||
initial: "acrobatics",
|
||||
choices: Object.keys(SYSTEM.CORE_SKILLS || {}),
|
||||
label: "Core Skill"
|
||||
})
|
||||
|
||||
// Is this the character's chosen Core Skill?
|
||||
schema.isCoreSkill = new fields.BooleanField({
|
||||
required: true,
|
||||
initial: false,
|
||||
label: "Is Core Skill"
|
||||
})
|
||||
|
||||
// If Core Skill, which attribute receives the +2 bonus?
|
||||
schema.attributeBonus = new fields.StringField({
|
||||
required: true,
|
||||
initial: "",
|
||||
label: "Attribute Bonus"
|
||||
})
|
||||
|
||||
// Skill modifier (includes Core Skill bonus if applicable)
|
||||
schema.modifier = new fields.NumberField({
|
||||
...requiredInteger,
|
||||
required: true,
|
||||
initial: 0,
|
||||
label: "Skill Modifier"
|
||||
})
|
||||
|
||||
// Can perform advanced checks
|
||||
schema.canAdvancedCheck = new fields.BooleanField({
|
||||
required: true,
|
||||
initial: false,
|
||||
label: "Can Perform Advanced Checks"
|
||||
})
|
||||
|
||||
// Associated Core Skill Class (if any)
|
||||
schema.coreSkillClass = new fields.StringField({
|
||||
required: true,
|
||||
initial: "",
|
||||
label: "Core Skill Class"
|
||||
})
|
||||
|
||||
// Notes/Custom description
|
||||
schema.notes = new fields.HTMLField({
|
||||
required: true,
|
||||
initial: "",
|
||||
label: "Notes"
|
||||
})
|
||||
|
||||
return schema
|
||||
@@ -32,83 +78,57 @@ export default class PrismRPGSkill extends foundry.abstract.TypeDataModel {
|
||||
/** @override */
|
||||
static LOCALIZATION_PREFIXES = ["PRISMRPG.Skill"]
|
||||
|
||||
get skillCategory() {
|
||||
return game.i18n.localize(CATEGORY[this.category].label)
|
||||
/**
|
||||
* Get the Core Skill definition from SYSTEM
|
||||
*/
|
||||
get coreSkillDefinition() {
|
||||
return SYSTEM.CORE_SKILLS?.[this.coreSkill] || null
|
||||
}
|
||||
|
||||
validate(options) {
|
||||
let isError = super.validate(options)
|
||||
let bonus = this._source.weaponBonus.attack + this._source.weaponBonus.defense + this._source.weaponBonus.damage
|
||||
if (bonus > Math.floor(this._source.skillTotal / 10)) {
|
||||
ui.notifications.error(game.i18n.localize("PRISMRPG.Skill.error.weaponBonus"))
|
||||
isError = true
|
||||
}
|
||||
return isError
|
||||
/**
|
||||
* Get the localized Core Skill name
|
||||
*/
|
||||
get coreSkillLabel() {
|
||||
const definition = this.coreSkillDefinition
|
||||
return definition ? game.i18n.localize(definition.label) : this.coreSkill
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the available attribute choices for this Core Skill
|
||||
*/
|
||||
get attributeChoices() {
|
||||
const definition = this.coreSkillDefinition
|
||||
return definition?.attributeChoices || []
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare derived data
|
||||
*/
|
||||
prepareDerivedData() {
|
||||
super.prepareDerivedData();
|
||||
this.skillTotal = this.computeBase();
|
||||
if (this.category === "weapon") {
|
||||
this.totalBonus = this.weaponBonus.attack + this.weaponBonus.defense + this.weaponBonus.damage;
|
||||
if (Number(this.skillTotal)) {
|
||||
this.availableBonus = Math.max(Math.floor(this.skillTotal / 10) - 1, 0)
|
||||
} else {
|
||||
this.availableBonus = "N/A"
|
||||
}
|
||||
super.prepareDerivedData()
|
||||
|
||||
// If this is the character's Core Skill, apply bonuses
|
||||
if (this.isCoreSkill) {
|
||||
this.modifier = SYSTEM.CORE_SKILL_BONUS?.basic || 5
|
||||
this.canAdvancedCheck = true
|
||||
} else {
|
||||
this.modifier = 0
|
||||
this.canAdvancedCheck = false
|
||||
}
|
||||
}
|
||||
|
||||
computeBase() {
|
||||
let actor = this.parent?.actor;
|
||||
if (!actor) {
|
||||
return `${this.base} + ${String(this.bonus)}`;
|
||||
}
|
||||
/**
|
||||
* Calculate skill check bonus
|
||||
* @param {string} attributeKey The attribute to use (str, dex, con, int, wis, cha)
|
||||
* @returns {number} Total skill check bonus
|
||||
*/
|
||||
getSkillCheckBonus(attributeKey) {
|
||||
let actor = this.parent?.actor
|
||||
if (!actor) return this.modifier
|
||||
|
||||
if (this.base === "N/A" || this.base === "None") {
|
||||
return this.bonus
|
||||
}
|
||||
const attribute = actor.system.characteristics?.[attributeKey]
|
||||
const attributeMod = attribute?.mod || 0
|
||||
|
||||
// Split the base value per stat : WIS,DEX,STR,INT,CHA (example)
|
||||
let base = this.base;
|
||||
// Fix errors in the base value
|
||||
base.replace("CHARISMA", "CHA");
|
||||
|
||||
if (base.match(/OR/)) {
|
||||
let baseSplit = base.split("OR");
|
||||
let baseSplitLength = baseSplit.length;
|
||||
if (baseSplitLength > 0) {
|
||||
// Select the max stat value from the parent actor
|
||||
let maxStat = 0;
|
||||
for (let i = 0; i < baseSplitLength; i++) {
|
||||
const stat = baseSplit[i].trim();
|
||||
const statValue = actor.system.characteristics[stat.toLowerCase()]?.value || 0;
|
||||
if (statValue > maxStat) {
|
||||
maxStat = statValue;
|
||||
}
|
||||
}
|
||||
return maxStat + this.bonus
|
||||
}
|
||||
} else {
|
||||
if (base.match(/\+/)) {
|
||||
// Split with + calculate the total
|
||||
let baseSplit = base.split("+");
|
||||
let baseSplitLength = baseSplit.length;
|
||||
if (baseSplitLength > 0) {
|
||||
let total = 0;
|
||||
for (let i = 0; i < baseSplitLength; i++) {
|
||||
const stat = baseSplit[i].trim();
|
||||
const statValue = actor.system.characteristics[stat.toLowerCase()]?.value || 0;
|
||||
total += statValue;
|
||||
}
|
||||
return total + this.bonus
|
||||
}
|
||||
} else {
|
||||
// Single stat
|
||||
const statValue = actor.system.characteristics[base.trim().toLowerCase()]?.value || 0;
|
||||
return statValue + this.bonus
|
||||
}
|
||||
}
|
||||
return `${this.base} + ${String(this.bonus)}`;
|
||||
return attributeMod + this.modifier
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user