155 lines
3.5 KiB
Svelte
155 lines
3.5 KiB
Svelte
<script lang="ts">
|
||
import { onMount } from 'svelte';
|
||
import { page } from '$app/stores';
|
||
import type { Character } from '$lib/schema/character';
|
||
import { getCharacter } from '$lib/storage/repo';
|
||
import { computeMeleeTargets } from '$lib/engine/melee';
|
||
import { TALENTS } from '$lib/rules/talents';
|
||
|
||
let char: Character | undefined;
|
||
let err = '';
|
||
const id = $page.params.id ?? '';
|
||
|
||
let talentId = 'dolche';
|
||
let weaponIndex = 0;
|
||
let situation = '0';
|
||
let atExtra = 0;
|
||
let paExtra = 0;
|
||
let ansage = 0;
|
||
|
||
onMount(async () => {
|
||
if (!id) {
|
||
err = 'Ungültige URL.';
|
||
return;
|
||
}
|
||
char = await getCharacter(id);
|
||
if (!char) err = 'Held nicht gefunden.';
|
||
else if (char.waffen.nahkampf.length) {
|
||
talentId = char.waffen.nahkampf[0].talentId;
|
||
}
|
||
});
|
||
|
||
$: wIdx = Number(weaponIndex);
|
||
$: weapon = char?.waffen.nahkampf[wIdx];
|
||
$: result =
|
||
char &&
|
||
weapon &&
|
||
computeMeleeTargets(
|
||
char,
|
||
talentId,
|
||
weapon.atMod,
|
||
weapon.paMod,
|
||
{
|
||
situationModifiers: situation
|
||
.split(/[,;]+/)
|
||
.map((s) => parseInt(s.trim(), 10))
|
||
.filter((n) => Number.isFinite(n)),
|
||
atExtra,
|
||
paExtra,
|
||
ansageAtAnteil: ansage
|
||
}
|
||
);
|
||
</script>
|
||
|
||
{#if err}
|
||
<p class="err">{err}</p>
|
||
{:else if !char}
|
||
<p>Lade …</p>
|
||
{:else}
|
||
<h1>Nahkampf · {char.meta.name}</h1>
|
||
<p class="muted">
|
||
Wähle Waffe und Erschwernisse (Zahlen). Manöver-Details am Tisch – hier nur Summen.
|
||
</p>
|
||
|
||
<section class="card stack">
|
||
<h2>Waffe</h2>
|
||
{#if char.waffen.nahkampf.length === 0}
|
||
<p>Keine Nahkampfwaffe im Bogen. Unter „Bogen“ anlegen.</p>
|
||
{:else}
|
||
<select bind:value={weaponIndex}>
|
||
{#each char.waffen.nahkampf as w, i}
|
||
<option value={i}>{w.name}</option>
|
||
{/each}
|
||
</select>
|
||
<div class="field">
|
||
<label>Kampftalent für diese Attacke</label>
|
||
<select bind:value={talentId}>
|
||
{#each TALENTS.filter((t) => t.category === 'kampf') as t}
|
||
<option value={t.id}>{t.name}</option>
|
||
{/each}
|
||
</select>
|
||
</div>
|
||
{/if}
|
||
</section>
|
||
|
||
<section class="card stack">
|
||
<h2>Situation</h2>
|
||
<div class="field">
|
||
<label>Zusätzliche Erschwernisse (Komma-getrennt, z.B. 3,2)</label>
|
||
<input bind:value={situation} placeholder="0" />
|
||
</div>
|
||
<div class="grid-2">
|
||
<div class="field">
|
||
<label>AT-Zusatz</label>
|
||
<input type="number" bind:value={atExtra} />
|
||
</div>
|
||
<div class="field">
|
||
<label>PA-Zusatz</label>
|
||
<input type="number" bind:value={paExtra} />
|
||
</div>
|
||
</div>
|
||
<div class="field">
|
||
<label>Ansage (AT-Anteil, vereinfacht)</label>
|
||
<input type="number" min="0" bind:value={ansage} />
|
||
</div>
|
||
</section>
|
||
|
||
{#if result}
|
||
<section class="card stack highlight">
|
||
<h2>Zielzahlen</h2>
|
||
<div class="grid-2">
|
||
<div>
|
||
<p class="muted">Attacke</p>
|
||
<p class="big-number">{result.atTarget}</p>
|
||
</div>
|
||
<div>
|
||
<p class="muted">Parade</p>
|
||
<p class="big-number">{result.paTarget}</p>
|
||
</div>
|
||
</div>
|
||
<ul class="kv">
|
||
<li><span>AT (inkl. Waffe)</span><strong>{result.at}</strong></li>
|
||
<li><span>PA (inkl. Waffe)</span><strong>{result.pa}</strong></li>
|
||
<li><span>Summe Modifikatoren</span><strong>{result.modifierSum}</strong></li>
|
||
</ul>
|
||
{#each result.notes as n}
|
||
<p class="muted">{n}</p>
|
||
{/each}
|
||
</section>
|
||
{/if}
|
||
|
||
<p><a href="/characters/{char.id}">Zurück</a></p>
|
||
{/if}
|
||
|
||
<style>
|
||
.highlight {
|
||
border-color: var(--accent-dim);
|
||
}
|
||
.kv {
|
||
list-style: none;
|
||
padding: 0;
|
||
margin: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.25rem;
|
||
}
|
||
.kv li {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
gap: 1rem;
|
||
}
|
||
.err {
|
||
color: var(--danger);
|
||
}
|
||
</style>
|