diff --git a/src/app.css b/src/app.css index 4642924..f4be054 100644 --- a/src/app.css +++ b/src/app.css @@ -157,15 +157,30 @@ button { color: var(--muted); } .field input, -.field select, .field textarea { padding: 0.45rem 0.55rem; border-radius: 8px; border: 1px solid var(--input-border); - background: var(--input-bg); + background-color: var(--input-bg); color: var(--text); } +/* Do not use `background:` shorthand on select — it clears the chevron from global `select` rules */ +.field select { + padding: 0.45rem 0.55rem; + padding-right: 2rem; + border-radius: 8px; + border: 1px solid var(--input-border); + background-color: var(--input-bg); + background-image: var(--select-chevron); + background-repeat: no-repeat; + background-position: right 0.55rem center; + color: var(--text); + cursor: pointer; + appearance: none; + -webkit-appearance: none; +} + .grid-2 { display: grid; grid-template-columns: 1fr 1fr; diff --git a/src/lib/engine/ranged.ts b/src/lib/engine/ranged.ts index b8f371e..2deeaf4 100644 --- a/src/lib/engine/ranged.ts +++ b/src/lib/engine/ranged.ts @@ -55,12 +55,6 @@ function rowById(rows: T[], id: string): T | undefined return rows.find((r) => r.id === id); } -function parseBonusDamage(bonusDamage: string, rangeIndex: number): number { - const parts = bonusDamage.split('/').map((p) => parseInt(p.trim(), 10)); - const v = parts[rangeIndex]; - return Number.isFinite(v) ? v : 0; -} - function rangeIndex(id: RangeId): number { const order: RangeId[] = ['very_near', 'near', 'medium', 'far', 'very_far']; return order.indexOf(id); @@ -198,9 +192,12 @@ export function computeRangedTarget( const finalTarget = baseTarget - totalModifier; const ri = rangeIndex(selections.range); - const bonus = parseBonusDamage(weapon.bonusDamage, ri); + const bonus = + ri >= 0 && ri < weapon.bonusDamageByRange.length ? weapon.bonusDamageByRange[ri] : 0; const damageSummary = - bonus === 0 ? `${weapon.damage}` : `${weapon.damage} + ${bonus} (Entfernungs-Bonus)`; + bonus === 0 + ? `${weapon.damage}` + : `${weapon.damage} ${bonus > 0 ? `+ ${bonus}` : `− ${Math.abs(bonus)}`} (Entfernungs-Bonus)`; return { fkBase, diff --git a/src/lib/rules/weapons-ranged.ts b/src/lib/rules/weapons-ranged.ts index 26108ab..90db3f6 100644 --- a/src/lib/rules/weapons-ranged.ts +++ b/src/lib/rules/weapons-ranged.ts @@ -9,15 +9,26 @@ export type RangedWeaponId = /** Maps to Kampftalent id in talents (Fernkampf subtypes). */ export type RangedSkillId = 'bogen' | 'armbrust' | 'wurfwaffe' | 'schleuder'; +/** + * Maximale Reichweite in Schritt je Fernkampf-Distanzstufe. + * Reihenfolge wie in der Regeltabelle / wie `RANGES` in `modifiers-ranged.ts`: + * sehr nah, nah, mittel, weit, extrem weit. + */ +export type WeaponRangeStepsSchritt = readonly [number, number, number, number, number]; + +/** + * Entfernungs-Bonusschaden (TP) je Distanzstufe. + * Reihenfolge wie `RANGES` / `rangeStepsSchritt`: sehr nah, nah, mittel, weit, extrem weit. + */ +export type WeaponBonusDamageByRange = readonly [number, number, number, number, number]; + export type RangedWeaponDef = { id: RangedWeaponId; name: string; skill: RangedSkillId; damage: string; - /** Entfernungs-Bonusschaden pro Reichweitenstufe (nah → extrem weit), slash-getrennt */ - bonusDamage: string; - /** Reichweiten in Schritt, slash-getrennt */ - ranges: string; + bonusDamageByRange: WeaponBonusDamageByRange; + rangeStepsSchritt: WeaponRangeStepsSchritt; reload: number; /** Mindest-KK für ungehinderten Einsatz (optional) */ strengthRequirement?: number; @@ -31,8 +42,8 @@ export const RANGED_WEAPONS: RangedWeaponDef[] = [ name: 'Elfenbogen', skill: 'bogen', damage: '1W6+5', - bonusDamage: '3/2/1/1/0', - ranges: '10/25/50/100/200', + bonusDamageByRange: [3, 2, 1, 1, 0], + rangeStepsSchritt: [10, 25, 50, 100, 200], reload: 3 }, { @@ -40,8 +51,8 @@ export const RANGED_WEAPONS: RangedWeaponDef[] = [ name: 'Kompositbogen', skill: 'bogen', damage: '1W6+5', - bonusDamage: '2/1/1/0/0', - ranges: '10/20/35/50/80', + bonusDamageByRange: [2, 1, 1, 0, 0], + rangeStepsSchritt: [10, 20, 35, 50, 80], reload: 3, strengthRequirement: 15, strengthModifier: -2 @@ -51,8 +62,8 @@ export const RANGED_WEAPONS: RangedWeaponDef[] = [ name: 'Kriegsbogen', skill: 'bogen', damage: '1W6+7', - bonusDamage: '3/2/1/0/0', - ranges: '10/20/40/80/150', + bonusDamageByRange: [3, 2, 1, 0, 0], + rangeStepsSchritt: [10, 20, 40, 80, 150], reload: 4, strengthRequirement: 16, strengthModifier: -2 @@ -62,8 +73,8 @@ export const RANGED_WEAPONS: RangedWeaponDef[] = [ name: 'Kurzbogen', skill: 'bogen', damage: '1W6+4', - bonusDamage: '1/1/0/0/-1', - ranges: '5/15/25/40/60', + bonusDamageByRange: [1, 1, 0, 0, -1], + rangeStepsSchritt: [5, 15, 25, 40, 60], reload: 2 }, { @@ -71,8 +82,8 @@ export const RANGED_WEAPONS: RangedWeaponDef[] = [ name: 'Langbogen', skill: 'bogen', damage: '1W6+6', - bonusDamage: '3/2/1/0/-1', - ranges: '10/25/50/100/200', + bonusDamageByRange: [3, 2, 1, 0, -1], + rangeStepsSchritt: [10, 25, 50, 100, 200], reload: 4, strengthRequirement: 15, strengthModifier: -2 @@ -82,8 +93,8 @@ export const RANGED_WEAPONS: RangedWeaponDef[] = [ name: 'Ork. Reiterbogen', skill: 'bogen', damage: '1W6+5', - bonusDamage: '3/1/0/-1/-2', - ranges: '5/15/30/60/100', + bonusDamageByRange: [3, 1, 0, -1, -2], + rangeStepsSchritt: [5, 15, 30, 60, 100], reload: 3, strengthRequirement: 15, strengthModifier: -2 diff --git a/src/routes/characters/[id]/combat/ranged/+page.svelte b/src/routes/characters/[id]/combat/ranged/+page.svelte index b9cec17..54cc9ba 100644 --- a/src/routes/characters/[id]/combat/ranged/+page.svelte +++ b/src/routes/characters/[id]/combat/ranged/+page.svelte @@ -4,7 +4,7 @@ import type { Character } from '$lib/schema/character'; import { getCharacter } from '$lib/storage/repo'; import { computeRangedTarget, type Expertise } from '$lib/engine/ranged'; - import { RANGED_WEAPONS, type RangedWeaponId } from '$lib/rules/weapons-ranged'; + import { getRangedWeapon, RANGED_WEAPONS, type RangedWeaponId } from '$lib/rules/weapons-ranged'; import { RANGES, TARGET_SIZES, @@ -15,10 +15,19 @@ RELOAD_STATES, type ExtraModifierId, type HitZoneId, + type ModifierRow, + type MovementId, type RangeId, - type ReloadStateId + type ReloadStateId, + type TargetSizeId, + type VisibilityId } from '$lib/rules/modifiers-ranged'; + function optionLabel(row: ModifierRow): string { + const m = row.modifier; + return `${row.name} (${m})`; + } + let char: Character | undefined; let err = ''; const id = $page.params.id ?? ''; @@ -26,9 +35,9 @@ let weaponId: RangedWeaponId = 'shortbow'; let expertise: Expertise = 'none'; let range: RangeId = 'near'; - let targetSize = 'medium' as const; - let visibility = 'clear' as const; - let movement = 'slow' as const; + let targetSize: TargetSizeId = 'medium'; + let visibility: VisibilityId = 'clear'; + let movement: MovementId = 'slow'; let hitZone = '' as HitZoneId | ''; let reloadState = '' as ReloadStateId | ''; let targetBuild: 'biped' | 'quadruped' = 'biped'; @@ -79,7 +88,9 @@ } })(); - const hitZoneOptions = HIT_ZONES.filter((z) => z.build === targetBuild); + $: hitZoneOptions = HIT_ZONES.filter((z) => z.build === targetBuild); + + $: weaponForExamples = getRangedWeapon(weaponId); {#if err} @@ -97,6 +108,16 @@ {/each} + {#if weaponForExamples} +

+ Reichweiten: + {weaponForExamples.rangeStepsSchritt.join('/')} +

+

+ TP+: + {weaponForExamples.bonusDamageByRange.join('/')} +

+ {/if}
@@ -121,45 +142,72 @@

Entfernung

-
- {#each RANGES as r} - - {/each} +
+ +
+ {#if weaponForExamples} +
+

Beispiele:

+
    + {#each RANGES as r, i} +
  • + {r.name} + — {weaponForExamples.rangeStepsSchritt[i]} Schritt +
  • + {/each} +
+
+ {/if}

Zielgröße

-
- {#each TARGET_SIZES as r} - - {/each} +
+ + +
+
+

Beispiele:

+
    + {#each TARGET_SIZES as r} + {#if r.description} +
  • {r.name} — {r.description}
  • + {/if} + {/each} +

Sicht

-
- {#each VISIBILITY as r} - - {/each} +
+ +

Bewegung

-
- {#each TARGET_MOVEMENT as r} - - {/each} +
+ +
@@ -264,4 +312,35 @@ .err { color: var(--danger); } - + .ref-examples { + margin-top: 0.75rem; + } + .examples-label { + margin: 0 0 0.35rem; + font-size: 0.9rem; + color: var(--text); + } + .examples-list { + margin: 0; + padding-left: 1.15rem; + font-size: 0.88rem; + line-height: 1.45; + } + .examples-list li { + margin-bottom: 0.25rem; + } + .examples-name { + font-weight: 600; + color: var(--text); + } + .weapon-stat-line { + margin: 0.5rem 0 0; + font-size: 0.9rem; + font-variant-numeric: tabular-nums; + word-break: break-all; + } + .weapon-stat-line strong { + color: var(--text); + margin-right: 0.35rem; + } + \ No newline at end of file