feat: Add armor and shields to equipment picker
All checks were successful
Deploy / deploy (push) Successful in 52s
All checks were successful
Deploy / deploy (push) Successful in 52s
Import armor_shields.yml and merge it with weapons in the '+ Add Equipment' picker. Armor/shield entries are formatted with DEF/MDEF/Init fields. Adds armor and shield filter categories alongside existing weapon categories. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -48,7 +48,7 @@ armor_shields:
|
|||||||
category: armor
|
category: armor
|
||||||
description: No Quality.
|
description: No Quality.
|
||||||
|
|
||||||
- name: Bronze Plate (E)
|
- name: Bronze Plate ✦
|
||||||
cost: 200
|
cost: 200
|
||||||
defense: "11"
|
defense: "11"
|
||||||
magic_defense: "INS size"
|
magic_defense: "INS size"
|
||||||
@@ -56,7 +56,7 @@ armor_shields:
|
|||||||
category: armor
|
category: armor
|
||||||
description: No Quality.
|
description: No Quality.
|
||||||
|
|
||||||
- name: Runic Plate (E)
|
- name: Runic Plate ✦
|
||||||
cost: 250
|
cost: 250
|
||||||
defense: "11"
|
defense: "11"
|
||||||
magic_defense: "INS size +1"
|
magic_defense: "INS size +1"
|
||||||
@@ -64,7 +64,7 @@ armor_shields:
|
|||||||
category: armor
|
category: armor
|
||||||
description: No Quality.
|
description: No Quality.
|
||||||
|
|
||||||
- name: Steel Plate (E)
|
- name: Steel Plate ✦
|
||||||
cost: 300
|
cost: 300
|
||||||
defense: "12"
|
defense: "12"
|
||||||
magic_defense: "INS size"
|
magic_defense: "INS size"
|
||||||
@@ -80,7 +80,7 @@ armor_shields:
|
|||||||
category: shield
|
category: shield
|
||||||
description: No Quality.
|
description: No Quality.
|
||||||
|
|
||||||
- name: Runic Shield (E)
|
- name: Runic Shield ✦
|
||||||
cost: 150
|
cost: 150
|
||||||
defense: "+2"
|
defense: "+2"
|
||||||
magic_defense: "+2"
|
magic_defense: "+2"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React, { useState, useEffect, useCallback, useRef } from "react";
|
|||||||
import "./fabula-ultima-sheet.css";
|
import "./fabula-ultima-sheet.css";
|
||||||
import spellsFile from "../data/spells.yml";
|
import spellsFile from "../data/spells.yml";
|
||||||
import weaponsFile from "../data/weapons.yml";
|
import weaponsFile from "../data/weapons.yml";
|
||||||
|
import armorShieldsFile from "../data/armor_shields.yml";
|
||||||
|
|
||||||
const STATUSES = ["Slow", "Enraged", "Dazed", "Weak", "Poisoned", "Shaken"];
|
const STATUSES = ["Slow", "Enraged", "Dazed", "Weak", "Poisoned", "Shaken"];
|
||||||
const FEELINGS = [
|
const FEELINGS = [
|
||||||
@@ -1165,13 +1166,31 @@ export default function CharacterSheet() {
|
|||||||
|
|
||||||
{weaponPickerOpen && (() => {
|
{weaponPickerOpen && (() => {
|
||||||
const allWeapons = (weaponsFile as WeaponsFile).weapons;
|
const allWeapons = (weaponsFile as WeaponsFile).weapons;
|
||||||
const categories = ["all", ...Array.from(new Set(allWeapons.map(w => w.category))).sort()];
|
const allArmorShields = (armorShieldsFile as ArmorShieldsFile).armor_shields;
|
||||||
const visible = weaponCategory === "all" ? allWeapons : allWeapons.filter(w => w.category === weaponCategory);
|
type PickerItem =
|
||||||
|
| { kind: "weapon"; data: WeaponTemplate }
|
||||||
|
| { kind: "armor"; data: ArmorShieldTemplate };
|
||||||
|
const allItems: PickerItem[] = [
|
||||||
|
...allWeapons.map(w => ({ kind: "weapon" as const, data: w })),
|
||||||
|
...allArmorShields.map(a => ({ kind: "armor" as const, data: a })),
|
||||||
|
];
|
||||||
|
const categories = ["all", ...Array.from(new Set(allItems.map(i => i.data.category))).sort()];
|
||||||
|
const visible = weaponCategory === "all" ? allItems : allItems.filter(i => i.data.category === weaponCategory);
|
||||||
|
const formatLine = (item: PickerItem) => {
|
||||||
|
if (item.kind === "weapon") {
|
||||||
|
const w = item.data;
|
||||||
|
return `• ${w.name}: Acc ${w.accuracy}, Dmg ${w.damage}${w.description ? ` | ${w.description}` : ""}${w.cost > 0 ? ` (${w.cost}z)` : ""}`;
|
||||||
|
} else {
|
||||||
|
const a = item.data;
|
||||||
|
const init = a.initiative ?? a.initative ?? 0;
|
||||||
|
return `• ${a.name}: DEF ${a.defense}, MDEF ${a.magic_defense}, Init ${init}${a.description ? ` | ${a.description}` : ""}${a.cost > 0 ? ` (${a.cost}z)` : ""}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<div className="spell-picker-overlay" onClick={() => setWeaponPickerOpen(false)}>
|
<div className="spell-picker-overlay" onClick={() => setWeaponPickerOpen(false)}>
|
||||||
<div className="spell-picker-modal" onClick={e => e.stopPropagation()}>
|
<div className="spell-picker-modal" onClick={e => e.stopPropagation()}>
|
||||||
<div className="spell-picker-header">
|
<div className="spell-picker-header">
|
||||||
<span>Choose a weapon</span>
|
<span>Choose equipment</span>
|
||||||
<button className="spell-picker-close" onClick={() => setWeaponPickerOpen(false)}>✕</button>
|
<button className="spell-picker-close" onClick={() => setWeaponPickerOpen(false)}>✕</button>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: "flex", gap: 6, flexWrap: "wrap", padding: "8px 14px", borderBottom: "1px solid var(--border)" }}>
|
<div style={{ display: "flex", gap: 6, flexWrap: "wrap", padding: "8px 14px", borderBottom: "1px solid var(--border)" }}>
|
||||||
@@ -1187,18 +1206,18 @@ export default function CharacterSheet() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<ul className="spell-picker-list">
|
<ul className="spell-picker-list">
|
||||||
{visible.map((w, i) => (
|
{visible.map((item, i) => (
|
||||||
<li
|
<li
|
||||||
key={i}
|
key={i}
|
||||||
className="spell-picker-item"
|
className="spell-picker-item"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const line = `• ${w.name}: Acc ${w.accuracy}, Dmg ${w.damage}${w.description ? ` | ${w.description}` : ""}${w.cost > 0 ? ` (${w.cost}z)` : ""}`;
|
const line = formatLine(item);
|
||||||
f("backpack", fields.backpack ? fields.backpack + "\n" + line : line);
|
f("backpack", fields.backpack ? fields.backpack + "\n" + line : line);
|
||||||
setWeaponPickerOpen(false);
|
setWeaponPickerOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="spell-picker-name">{w.name}</span>
|
<span className="spell-picker-name">{item.data.name}</span>
|
||||||
<span className="spell-picker-class">{w.category}</span>
|
<span className="spell-picker-class">{item.data.category}</span>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
15
src/globals.d.ts
vendored
15
src/globals.d.ts
vendored
@@ -18,6 +18,21 @@ interface WeaponsFile {
|
|||||||
weapons: WeaponTemplate[];
|
weapons: WeaponTemplate[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ArmorShieldTemplate {
|
||||||
|
name: string;
|
||||||
|
cost: number;
|
||||||
|
defense: string;
|
||||||
|
magic_defense: string;
|
||||||
|
initiative?: number;
|
||||||
|
initative?: number; // typo present in source YAML
|
||||||
|
category: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ArmorShieldsFile {
|
||||||
|
armor_shields: ArmorShieldTemplate[];
|
||||||
|
}
|
||||||
|
|
||||||
interface SpellTemplate {
|
interface SpellTemplate {
|
||||||
name: string;
|
name: string;
|
||||||
cost: string;
|
cost: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user