diff --git a/web/src/components/weapons/AddWeaponModal.tsx b/web/src/components/weapons/AddWeaponModal.tsx index dfe93d1..886df3b 100644 --- a/web/src/components/weapons/AddWeaponModal.tsx +++ b/web/src/components/weapons/AddWeaponModal.tsx @@ -1,5 +1,7 @@ import { useState } from 'react'; import Modal from '../ui/Modal'; +import { weaponTemplates } from '../../data/weapon-templates'; +import type { WeaponTemplate } from '../../data/weapon-templates'; interface Props { open: boolean; @@ -7,90 +9,133 @@ interface Props { onCreate: (weapon: Record) => void; } +interface FormState { + name: string; + type: string; + damage: string; + attackMod: string; + range: string; + ammoMax: string; + notes: string; +} + +function defaults(): FormState { + return { name: '', type: '', damage: '', attackMod: '', range: '', ammoMax: '', notes: '' }; +} + +function fromTemplate(t: WeaponTemplate): FormState { + return { + name: t.name, + type: t.type, + damage: t.damage, + attackMod: '', + range: t.range, + ammoMax: '', + notes: t.notes ?? '', + }; +} + export default function AddWeaponModal({ open, onClose, onCreate }: Props) { - const [name, setName] = useState(''); - const [damage, setDamage] = useState(''); - const [attackMod, setAttackMod] = useState(''); - const [range, setRange] = useState(''); - const [ammoMax, setAmmoMax] = useState(''); + const [templateIdx, setTemplateIdx] = useState(''); + const [form, setForm] = useState(defaults()); + + const set = (field: keyof FormState) => (e: React.ChangeEvent) => { + setForm((f) => ({ ...f, [field]: e.target.value })); + }; + + const handleTemplateChange = (e: React.ChangeEvent) => { + const val = e.target.value; + setTemplateIdx(val); + if (val === '') { + setForm(defaults()); + } else { + setForm(fromTemplate(weaponTemplates[parseInt(val)])); + } + }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - if (!name.trim()) return; + if (!form.name.trim()) return; - const aMax = parseInt(ammoMax) || null; + const aMax = parseInt(form.ammoMax) || null; onCreate({ - name: name.trim(), - damage: damage.trim() || null, - attack_mod: parseInt(attackMod) || null, - range: range.trim() || null, + name: form.name.trim(), + type: form.type || null, + damage: form.damage.trim() || null, + attack_mod: parseInt(form.attackMod) || null, + range: form.range.trim() || null, ammo_max: aMax, ammo_current: aMax, status: 'ok', + notes: form.notes.trim() || null, }); - setName(''); - setDamage(''); - setAttackMod(''); - setRange(''); - setAmmoMax(''); + setForm(defaults()); + setTemplateIdx(''); onClose(); }; + // Group templates by type for the dropdown + const mundane = weaponTemplates.map((t, i) => ({ t, i })).filter(({ t }) => t.type === 'mundane'); + const spellcannons = weaponTemplates.map((t, i) => ({ t, i })).filter(({ t }) => t.type === 'spellcannon'); + return (
+ + + +
+ + +
+ +
+ + +
+ - - - +
-
diff --git a/web/src/data/weapon-templates.ts b/web/src/data/weapon-templates.ts new file mode 100644 index 0000000..2da552e --- /dev/null +++ b/web/src/data/weapon-templates.ts @@ -0,0 +1,165 @@ +export interface WeaponTemplate { + name: string; + type: 'mundane' | 'spellcannon'; + damage: string; + attack_mod: number | null; + range: string; + notes: string | null; +} + +export const weaponTemplates: WeaponTemplate[] = [ + // Mundane weapons + { + name: 'Scorpion', + type: 'mundane', + damage: '1d6', + attack_mod: null, + range: '200/500', + notes: 'Dex. Free reload.', + }, + { + name: 'Ballista', + type: 'mundane', + damage: '1d10', + attack_mod: null, + range: '250/800', + notes: 'Dex.', + }, + { + name: 'Falconet (Cannon)', + type: 'mundane', + damage: '2d6', + attack_mod: null, + range: '500/1200', + notes: 'Dex or Int.', + }, + { + name: 'Carronade (Cannon)', + type: 'mundane', + damage: '3d6', + attack_mod: null, + range: '300/800', + notes: 'Dex or Int.', + }, + { + name: 'Saker (Cannon)', + type: 'mundane', + damage: '2d10', + attack_mod: null, + range: '750/2000', + notes: 'Dex or Int.', + }, + { + name: 'Bombard', + type: 'mundane', + damage: '4d8', + attack_mod: null, + range: '750/2000', + notes: 'Dex or Int. Special mount, limited arc, slow reload.', + }, + { + name: 'Minigun', + type: 'mundane', + damage: '5d4', + attack_mod: null, + range: '150/400', + notes: 'Dex. +2d4 vs subsystems. Half dmg for advantage.', + }, + { + name: 'Javelin Launcher', + type: 'mundane', + damage: '2d8', + attack_mod: null, + range: '150', + notes: 'Dex. Human-scale damage. Subsystems only.', + }, + { + name: 'Harpoon Launcher', + type: 'mundane', + damage: '1d6', + attack_mod: null, + range: '150', + notes: 'Dex. Attaches 150ft chain on hit.', + }, + // Spellcannons + { + name: 'Magic Missile Cannon', + type: 'spellcannon', + damage: '3×(1d4+1) force', + attack_mod: null, + range: '1000', + notes: 'Auto-hit. No reload. 10 charges/day.', + }, + { + name: 'Fireball Cannon', + type: 'spellcannon', + damage: '8d6 fire', + attack_mod: null, + range: '750', + notes: 'AoE 150ft radius. 5 charges/day.', + }, + { + name: 'Acid Arrow Cannon', + type: 'spellcannon', + damage: '6d4 acid', + attack_mod: null, + range: '750', + notes: '7 charges/day.', + }, + { + name: 'Web Cannon', + type: 'spellcannon', + damage: 'n/a', + attack_mod: null, + range: '400', + notes: '5 charges/day.', + }, + { + name: 'Lightning Bolt Cannon', + type: 'spellcannon', + damage: '8d6 lightning', + attack_mod: null, + range: '750', + notes: 'AoE 500ft×50ft line. 5 charges/day.', + }, + { + name: 'Disintegrate Cannon', + type: 'spellcannon', + damage: '10d6+40 force', + attack_mod: null, + range: '400', + notes: '3 charges/day.', + }, + { + name: 'Cone of Cold Cannon', + type: 'spellcannon', + damage: '8d8 cold', + attack_mod: null, + range: '600', + notes: '250ft cone. 4 charges/day.', + }, + { + name: 'Spiritual Weapon Cannon', + type: 'spellcannon', + damage: '1d8+4', + attack_mod: null, + range: '150', + notes: 'Lasts 1 min/charge. 3 charges/day.', + }, + { + name: 'Guiding Bolt Cannon', + type: 'spellcannon', + damage: '4d6 radiant', + attack_mod: null, + range: '1000', + notes: 'Advantage to next attack. 6 charges/day.', + }, + { + name: 'Flame Strike Cannon', + type: 'spellcannon', + damage: '4d6 fire + 4d6 radiant', + attack_mod: null, + range: '750', + notes: 'AoE 50ft radius. 3 charges/day.', + }, +];