vagabond-rpg-foundryvtt/module/sheets/character-sheet.mjs
Cal Corum fe91764f3b Fix ranged attack stat to use Awareness instead of Dexterity
The character sheet was incorrectly using Dexterity for ranged attacks.
Config and weapon model already had the correct Awareness stat.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-17 19:05:38 -06:00

368 lines
10 KiB
JavaScript

/**
* Character Sheet for Vagabond RPG
*
* Extended sheet for player characters with:
* - Stats section with all six attributes
* - Combat section (HP, Armor, Fatigue, Speed)
* - Saves section (Reflex, Endure, Will)
* - Skills section (12 skills with trained/difficulty)
* - Attacks section (weapons and attack skills)
* - Inventory tab
* - Abilities tab (features, perks, ancestry)
* - Magic tab (mana, spells, focus)
* - Biography tab
*
* @extends VagabondActorSheet
*/
import VagabondActorSheet from "./base-actor-sheet.mjs";
export default class VagabondCharacterSheet extends VagabondActorSheet {
/* -------------------------------------------- */
/* Static Properties */
/* -------------------------------------------- */
/** @override */
static DEFAULT_OPTIONS = foundry.utils.mergeObject(
super.DEFAULT_OPTIONS,
{
classes: ["vagabond", "sheet", "actor", "character"],
position: {
width: 750,
height: 850,
},
},
{ inplace: false }
);
/** @override */
static PARTS = {
header: {
template: "systems/vagabond/templates/actor/character-header.hbs",
},
tabs: {
template: "systems/vagabond/templates/actor/parts/tabs.hbs",
},
main: {
template: "systems/vagabond/templates/actor/character-main.hbs",
},
inventory: {
template: "systems/vagabond/templates/actor/character-inventory.hbs",
},
abilities: {
template: "systems/vagabond/templates/actor/character-abilities.hbs",
},
magic: {
template: "systems/vagabond/templates/actor/character-magic.hbs",
},
biography: {
template: "systems/vagabond/templates/actor/character-biography.hbs",
},
};
/* -------------------------------------------- */
/* Getters */
/* -------------------------------------------- */
/** @override */
get tabs() {
return [
{ id: "main", label: "VAGABOND.TabMain", icon: "fa-solid fa-user" },
{ id: "inventory", label: "VAGABOND.TabInventory", icon: "fa-solid fa-suitcase" },
{ id: "abilities", label: "VAGABOND.TabAbilities", icon: "fa-solid fa-star" },
{ id: "magic", label: "VAGABOND.TabMagic", icon: "fa-solid fa-wand-sparkles" },
{ id: "biography", label: "VAGABOND.TabBiography", icon: "fa-solid fa-book" },
];
}
/* -------------------------------------------- */
/* Data Preparation */
/* -------------------------------------------- */
/** @override */
async _prepareTypeContext(context, _options) {
// Stats with labels
context.stats = this._prepareStats();
// Skills organized by associated stat
context.skills = this._prepareSkills();
// Saves with calculated difficulties
context.saves = this._prepareSaves();
// Attack skills
context.attackSkills = this._prepareAttackSkills();
// Resources with display data
context.resources = this._prepareResources();
// Speed display
context.speed = this._prepareSpeed();
// Wealth display
context.wealth = this.actor.system.wealth;
// Item slots
context.itemSlots = this.actor.system.itemSlots;
// Character details
context.details = this.actor.system.details;
context.sizeOptions = CONFIG.VAGABOND?.sizes || {};
context.beingTypeOptions = CONFIG.VAGABOND?.beingTypes || {};
// Focus tracking
context.focus = this.actor.system.focus;
context.hasFocus = this.actor.system.focus?.active?.length > 0;
// Class and ancestry info
context.ancestry = context.items.ancestry;
context.classes = context.items.classes;
context.className = context.items.classes[0]?.name || "None";
context.ancestryName = context.items.ancestry?.name || "None";
}
/**
* Prepare stats for display with labels and colors.
* @returns {Object}
* @private
*/
_prepareStats() {
const system = this.actor.system;
const stats = {};
const statConfig = {
might: { label: "VAGABOND.StatMight", abbr: "MIT", color: "stat-might" },
dexterity: { label: "VAGABOND.StatDexterity", abbr: "DEX", color: "stat-dexterity" },
awareness: { label: "VAGABOND.StatAwareness", abbr: "AWR", color: "stat-awareness" },
reason: { label: "VAGABOND.StatReason", abbr: "RSN", color: "stat-reason" },
presence: { label: "VAGABOND.StatPresence", abbr: "PRS", color: "stat-presence" },
luck: { label: "VAGABOND.StatLuck", abbr: "LUK", color: "stat-luck" },
};
for (const [key, config] of Object.entries(statConfig)) {
stats[key] = {
...config,
value: system.stats[key].value,
path: `system.stats.${key}.value`,
};
}
return stats;
}
/**
* Prepare skills for display with associated stats and difficulties.
* @returns {Object}
* @private
*/
_prepareSkills() {
const system = this.actor.system;
const skillConfig = CONFIG.VAGABOND?.skills || {};
const skills = {};
for (const [skillId, config] of Object.entries(skillConfig)) {
const skillData = system.skills[skillId];
if (!skillData) continue;
skills[skillId] = {
id: skillId,
label: config.label || skillId,
stat: config.stat,
statAbbr: this._getStatAbbr(config.stat),
trained: skillData.trained,
difficulty: skillData.difficulty,
critThreshold: skillData.critThreshold,
hasCritBonus: skillData.critThreshold < 20,
};
}
return skills;
}
/**
* Prepare saves for display.
* @returns {Object}
* @private
*/
_prepareSaves() {
const system = this.actor.system;
return {
reflex: {
id: "reflex",
label: "VAGABOND.SaveReflex",
stats: "DEX + AWR",
difficulty: system.saves.reflex.difficulty,
bonus: system.saves.reflex.bonus,
},
endure: {
id: "endure",
label: "VAGABOND.SaveEndure",
stats: "MIT + MIT",
difficulty: system.saves.endure.difficulty,
bonus: system.saves.endure.bonus,
},
will: {
id: "will",
label: "VAGABOND.SaveWill",
stats: "RSN + PRS",
difficulty: system.saves.will.difficulty,
bonus: system.saves.will.bonus,
},
};
}
/**
* Prepare attack skills for display.
* @returns {Object}
* @private
*/
_prepareAttackSkills() {
const system = this.actor.system;
const attackConfig = {
melee: { label: "VAGABOND.AttackMelee", stat: "might" },
brawl: { label: "VAGABOND.AttackBrawl", stat: "might" },
ranged: { label: "VAGABOND.AttackRanged", stat: "awareness" },
finesse: { label: "VAGABOND.AttackFinesse", stat: "dexterity" },
};
const attacks = {};
for (const [key, config] of Object.entries(attackConfig)) {
const statValue = system.stats[config.stat]?.value || 0;
// Attack difficulty is 20 - stat (always trained)
const difficulty = 20 - statValue * 2;
attacks[key] = {
id: key,
label: config.label,
stat: config.stat,
statAbbr: this._getStatAbbr(config.stat),
difficulty,
critThreshold: system.attacks[key]?.critThreshold || 20,
hasCritBonus: (system.attacks[key]?.critThreshold || 20) < 20,
};
}
return attacks;
}
/**
* Prepare resources for display.
* @returns {Object}
* @private
*/
_prepareResources() {
const system = this.actor.system;
return {
hp: {
label: "VAGABOND.ResourceHP",
value: system.resources.hp.value,
max: system.resources.hp.max,
percent: Math.round((system.resources.hp.value / system.resources.hp.max) * 100) || 0,
color: this._getResourceColor(system.resources.hp.value, system.resources.hp.max),
},
mana: {
label: "VAGABOND.ResourceMana",
value: system.resources.mana.value,
max: system.resources.mana.max,
castingMax: system.resources.mana.castingMax,
percent: Math.round((system.resources.mana.value / system.resources.mana.max) * 100) || 0,
},
luck: {
label: "VAGABOND.ResourceLuck",
value: system.resources.luck.value,
max: system.resources.luck.max,
},
fatigue: {
label: "VAGABOND.ResourceFatigue",
value: system.resources.fatigue.value,
max: 5,
isDangerous: system.resources.fatigue.value >= 4,
},
};
}
/**
* Prepare speed display.
* @returns {Object}
* @private
*/
_prepareSpeed() {
const system = this.actor.system;
return {
walk: system.speed.walk,
fly: system.speed.fly,
swim: system.speed.swim,
climb: system.speed.climb,
hasSpecialMovement: system.speed.fly > 0 || system.speed.swim > 0 || system.speed.climb > 0,
};
}
/**
* Get stat abbreviation.
* @param {string} stat
* @returns {string}
* @private
*/
_getStatAbbr(stat) {
const abbrs = {
might: "MIT",
dexterity: "DEX",
awareness: "AWR",
reason: "RSN",
presence: "PRS",
luck: "LUK",
};
return abbrs[stat] || stat.toUpperCase().slice(0, 3);
}
/**
* Get color class for resource bar based on percentage.
* @param {number} value
* @param {number} max
* @returns {string}
* @private
*/
_getResourceColor(value, max) {
const percent = (value / max) * 100;
if (percent <= 25) return "critical";
if (percent <= 50) return "warning";
return "healthy";
}
/* -------------------------------------------- */
/* Rendering */
/* -------------------------------------------- */
/** @override */
async _preparePartContext(partId, context, options) {
context = await super._preparePartContext(partId, context, options);
// Only render the active tab's content
if (["main", "inventory", "abilities", "magic", "biography"].includes(partId)) {
context.isActiveTab = partId === this._activeTab;
}
return context;
}
/** @override */
_configureRenderOptions(options) {
super._configureRenderOptions(options);
// Always render header and tabs
options.parts = ["header", "tabs"];
// Add the active tab's part
if (this._activeTab && VagabondCharacterSheet.PARTS[this._activeTab]) {
options.parts.push(this._activeTab);
} else {
options.parts.push("main");
}
}
}