diff --git a/data/armor_shields.yml b/data/armor_shields.yml new file mode 100644 index 0000000..5eb9933 --- /dev/null +++ b/data/armor_shields.yml @@ -0,0 +1,89 @@ +armor_shields: + + - name: No Armor + cost: 0 + defense: DEX size + magic_defense: INS size + initiative: 0 + category: armor + description: No Quality. + + - name: Silk Shirt + cost: 100 + defense: DEX size + magic_defense: INS size +2 + initative: -1 + category: armor + description: No Quality. + + - name: Travel Garb + cost: 100 + defense: DEX size +1 + magic_defense: INS size +1 + initiative: -1 + category: armor + description: No Quality. + + - name: Combat Tunic + cost: 150 + defense: DEX size +1 + magic_defense: INS size +1 + initiative: 0 + category: armor + description: No Quality. + + - name: Sage Robe + cost: 200 + defense: "DEX size +1" + magic_defense: "INS size +2" + initiative: -2 + category: armor + description: No Quality. + + - name: Brigandine E + cost: 150 + defense: "10" + magic_defense: "INS size" + initiative: -2 + category: armor + description: No Quality. + + - name: Bronze Plate (E) + cost: 200 + defense: "11" + magic_defense: "INS size" + initiative: -3 + category: armor + description: No Quality. + + - name: Runic Plate (E) + cost: 250 + defense: "11" + magic_defense: "INS size +1" + initiative: -3 + category: armor + description: No Quality. + + - name: Steel Plate (E) + cost: 300 + defense: "12" + magic_defense: "INS size" + initative: -4 + category: armor + description: No Quality. + + - name: Bronze Shield + cost: 100 + defense: "+2" + magic_defense: "0" + initiative: 0 + category: shield + description: No Quality. + + - name: Runic Shield (E) + cost: 150 + defense: "+2" + magic_defense: "+2" + initiative: 0 + category: shield + description: No Quality. diff --git a/data/weapons.yml b/data/weapons.yml new file mode 100644 index 0000000..823a598 --- /dev/null +++ b/data/weapons.yml @@ -0,0 +1,149 @@ +weapons: + + - name: Staff + cost: 100 + accuracy: "【WLP + WLP】" + damage: "【HR + 6】 physical" + category: arcane + description: Two-handed, Melee, No Quality. + + - name: Tome + cost: 100 + accuracy: "【INS + INS】" + damage: "【HR + 6】 physical" + category: arcane + description: Two-handed, Melee, No Quality. + + - name: Crossbow + cost: 150 + accuracy: "【DEX + INS】" + damage: "【HR + 8】 physical" + category: bow + description: Two-handed, Ranged, No Quality. + + - name: Shortbow + cost: 200 + accuracy: "【DEX + DEX】" + damage: "【HR + 8】 physical" + category: bow + description: Two-handed, Ranged, No Quality. + + - name: Unarmed Strike + cost: 0 + accuracy: "【DEX + MIG】" + damage: "【HR + 0】 physical" + category: brawling + description: One-handed, Melee, Automatically equipped in each empty hand slot. + + - name: Improvised (Melee) + cost: 0 + accuracy: "【DEX + MIG】" + damage: "【HR + 2】 physical" + category: brawling + description: One-handed, Melee, Breaks after the attack. + + - name: Iron Knuckle + cost: 150 + accuracy: "【DEX + MIG】" + damage: "【HR + 6】 physical" + category: brawling + description: One-handed, Melee, No Quality. + + - name: Steel Dagger + cost: 150 + accuracy: "【DEX + INS】 +1" + damage: "【HR + 4】 physical" + category: dagger + description: One-handed, Melee, No Quality. + + - name: Pistol ✦ + cost: 250 + accuracy: "【DEX + INS】" + damage: "【HR + 8】 physical" + category: firearm + description: One-handed, Ranged, No Quality. + + - name: Chain Whip + cost: 150 + accuracy: "【DEX + DEX】" + damage: "【HR + 8】 physical" + category: flail + description: Two-handed, Melee, No Quality. + + - name: Iron Hammer + cost: 200 + accuracy: "【MIG + MIG】" + damage: "【HR + 6】 physical" + category: "heavy" + description: One-handed w Melee w No Quality. + + - name: Broadaxe ✦ + cost: 250 + accuracy: "【MIG + MIG】" + damage: "【HR + 10】 physical" + category: "heavy" + description: One-handed w Melee w No Quality. + + - name: Waraxe ✦ + cost: 250 + accuracy: "【MIG + MIG】" + damage: "【HR + 14】 physical" + category: "heavy" + description: Two-handed w Melee w No Quality. + + - name: Light Spear ✦ + cost: 200 + accuracy: "【DEX + MIG】" + damage: "【HR + 8】 physical" + category: "spear" + description: One-handed w Melee w No Quality. + + - name: Heavy Spear ✦ + cost: 200 + accuracy: "【DEX + MIG】 【HR + 12】 physical" + damage: "Two-handed w Melee w No Quality." + category: "spear" + description: + + - name: Bronze Sword ✦ + cost: 200 + accuracy: "【DEX + MIG】 +1" + damage: "【HR + 6】 physical" + category: "sword" + description: One-handed w Melee w No Quality. + + - name: Greatsword ✦ + cost: 200 + accuracy: "【DEX + MIG】 +1" + damage: "【HR + 10】 physical" + category: "sword" + description: Two-handed w Melee w No Quality. + + - name: Katana ✦ + cost: 200 + accuracy: "【DEX + INS】 +1" + damage: "【HR + 10】 physical" + category: "sword" + description: Two-handed w Melee w No Quality. + + - name: Rapier ✦ + cost: 200 + accuracy: "【DEX + INS】 +1" + damage: "【HR + 6】 physical" + category: "sword" + description: One-handed w Melee w No Quality. + + - name: Improvised (Ranged) + cost: 0 + accuracy: "【DEX + MIG】" + damage: "【HR + 2】 physical" + category: "thrown" + description: One-handed w Ranged w Breaks after the attack. + + - name: Shuriken + cost: 150 + accuracy: "【DEX + INS】" + damage: "【HR + 4】 physical" + category: "thrown" + description: One-handed w Ranged w No Quality. + diff --git a/find-empty-pages.sh b/find-empty-pages.sh new file mode 100755 index 0000000..ed3527c --- /dev/null +++ b/find-empty-pages.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Finds blank (empty or whitespace-only) HTML files in books/core + +DIR="$(dirname "$0")/books/core" +empty=() +whitespace_only=() + +for f in "$DIR"/*.html; do + if [ ! -s "$f" ]; then + empty+=("$f") + elif ! grep -qE '[^[:space:]]' "$f"; then + whitespace_only+=("$f") + fi +done + +echo "=== Empty (0 bytes): ${#empty[@]} files ===" +for f in "${empty[@]}"; do printf ' %s\n' "$(basename "$f")"; done | sort -V + +if [ ${#whitespace_only[@]} -gt 0 ]; then + echo "" + echo "=== Whitespace-only: ${#whitespace_only[@]} files ===" + for f in "${whitespace_only[@]}"; do printf ' %s\n' "$(basename "$f")"; done | sort -V +fi + +echo "" +echo "Total blank: $((${#empty[@]} + ${#whitespace_only[@]}))" diff --git a/src/CharacterSheet.tsx b/src/CharacterSheet.tsx index 7e6da0b..6e5de3b 100644 --- a/src/CharacterSheet.tsx +++ b/src/CharacterSheet.tsx @@ -1,6 +1,7 @@ import React, { useState, useEffect, useCallback, useRef } from "react"; import "./fabula-ultima-sheet.css"; import spellsFile from "../data/spells.yml"; +import weaponsFile from "../data/weapons.yml"; const STATUSES = ["Slow", "Enraged", "Dazed", "Weak", "Poisoned", "Shaken"]; const FEELINGS = [ @@ -235,6 +236,8 @@ export default function CharacterSheet() { const [otherClasses, setOtherClasses] = useState([]); const [spells, setSpells] = useState([]); const [spellPickerOpen, setSpellPickerOpen] = useState(false); + const [weaponPickerOpen, setWeaponPickerOpen] = useState(false); + const [weaponCategory, setWeaponCategory] = useState("all"); const [statuses, setStatuses] = useState({}); const [martial, setMartial] = useState({}); const [disciplines, setDisciplines] = useState({}); @@ -1134,6 +1137,57 @@ export default function CharacterSheet() { placeholder="Items, notes, lore…" style={{ minHeight: 200 }} /> + + + {weaponPickerOpen && (() => { + const allWeapons = (weaponsFile as WeaponsFile).weapons; + const categories = ["all", ...Array.from(new Set(allWeapons.map(w => w.category))).sort()]; + const visible = weaponCategory === "all" ? allWeapons : allWeapons.filter(w => w.category === weaponCategory); + return ( +
setWeaponPickerOpen(false)}> +
e.stopPropagation()}> +
+ Choose a weapon + +
+
+ {categories.map(cat => ( + + ))} +
+
    + {visible.map((w, i) => ( +
  • { + const line = `• ${w.name}: Acc ${w.accuracy}, Dmg ${w.damage}${w.description ? ` | ${w.description}` : ""}${w.cost > 0 ? ` (${w.cost}z)` : ""}`; + f("backpack", fields.backpack ? fields.backpack + "\n" + line : line); + setWeaponPickerOpen(false); + }} + > + {w.name} + {w.category} +
  • + ))} +
+
+
+ ); + })()} diff --git a/src/fabula-ultima-sheet.css b/src/fabula-ultima-sheet.css index cf5d07f..16fe2e4 100644 --- a/src/fabula-ultima-sheet.css +++ b/src/fabula-ultima-sheet.css @@ -923,6 +923,12 @@ input[type="number"] { text-transform: capitalize; } +.add-btn.active-filter { + background: var(--teal); + color: var(--bg); + border-color: var(--teal); +} + /* ── DISCIPLINES ─────────────────────────────────── */ .disciplines-row { display: flex; diff --git a/src/globals.d.ts b/src/globals.d.ts index 600f9bb..0ca271c 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -5,6 +5,19 @@ declare module "*.yml" { export default data; } +interface WeaponTemplate { + name: string; + cost: number; + accuracy: string; + damage: string; + category: string; + description?: string; +} + +interface WeaponsFile { + weapons: WeaponTemplate[]; +} + interface SpellTemplate { name: string; cost: string;