refine abilities and stuff

This commit is contained in:
2026-05-16 18:58:05 +02:00
parent b0a4bd554e
commit a281592541
6 changed files with 283 additions and 27 deletions
+3 -3
View File
@@ -34,9 +34,9 @@ export function newCharacter(partial?: { name?: string; id?: string }): Characte
aup: { mod: 0 },
mr: { mod: 0 }
},
vorteile: [],
nachteile: [],
sonderfertigkeiten: [],
advantages: [],
disadvantages: [],
abilities: [],
talente: [],
kampftalente: [],
inventar: [],
+99
View File
@@ -0,0 +1,99 @@
/** Abilities (Sonderfertigkeiten, extensible); modifiers for melee and ranged combat */
export type AbilityDef = {
id: string;
name: string;
at_mod: number;
pa_mod: number;
fk_mod: number;
};
export const ABILITIES: AbilityDef[] = [
{
id: 'evade_1',
name: 'Ausweichen I',
at_mod: 0,
pa_mod: 0,
fk_mod: 0
},
{
id: 'evade_2',
name: 'Ausweichen II',
at_mod: 0,
pa_mod: 0,
fk_mod: 0
},
{
id: 'evade_3',
name: 'Ausweichen III',
at_mod: 0,
pa_mod: 0,
fk_mod: 0
},
{
id: 'sniper',
name: 'Scharfschütze',
at_mod: 0,
pa_mod: 0,
fk_mod: 0
},
{
id: 'marksman',
name: 'Meisterschütze',
at_mod: 0,
pa_mod: 0,
fk_mod: 0
},
{
id: 'fast_reload',
name: 'Schnellladen',
at_mod: 0,
pa_mod: 0,
fk_mod: 0
},
{
id: 'specialize_elven_bow',
name: 'Talentspezialisierung Bogen (Elfenbogen)',
at_mod: 0,
pa_mod: 0,
fk_mod: 2
},
{
id: 'specialize_composite_bow',
name: 'Talentspezialisierung Bogen (Kompositbogen)',
at_mod: 0,
pa_mod: 0,
fk_mod: 2
},
{
id: 'specialize_warbow',
name: 'Talentspezialisierung Bogen (Kriegsbogen)',
at_mod: 0,
pa_mod: 0,
fk_mod: 2
},
{
id: 'specialize_shortbow',
name: 'Talentspezialisierung Bogen (Kurzbogen)',
at_mod: 0,
pa_mod: 0,
fk_mod: 2
},
{
id: 'specialize_longbow',
name: 'Talentspezialisierung Bogen (Langbogen)',
at_mod: 0,
pa_mod: 0,
fk_mod: 2
},
{
id: 'specialize_orc_bow',
name: 'Talentspezialisierung Bogen (Ork. Reiterbogen)',
at_mod: 0,
pa_mod: 0,
fk_mod: 2
}
];
export function getAbility(id: string): AbilityDef | undefined {
return ABILITIES.find((s) => s.id === id);
}
+45
View File
@@ -0,0 +1,45 @@
/** Advantage definitions (extensible); modifiers for melee and ranged combat */
export type AdvantageDef = {
id: string;
name: string;
has_levels: boolean;
/** z. B. [1, 2, 3], wenn has_levels sonst leer */
levels: number[];
at_mod: number;
pa_mod: number;
fk_mod: number;
};
export const ADVANTAGES: AdvantageDef[] = [
{
id: 'entfernungssinn',
name: 'Entfernungssinn',
has_levels: false,
levels: [],
at_mod: 0,
pa_mod: 0,
fk_mod: 2
},
{
id: 'innerer_kompass',
name: 'Innerer Kompass',
has_levels: false,
levels: [],
at_mod: 0,
pa_mod: 0,
fk_mod: 0
},
{
id: 'kampfreflexe',
name: 'Kampfreflexe',
has_levels: true,
levels: [1, 2, 3],
at_mod: 1,
pa_mod: 0,
fk_mod: 0
}
];
export function getAdvantage(id: string): AdvantageDef | undefined {
return ADVANTAGES.find((v) => v.id === id);
}
+26
View File
@@ -0,0 +1,26 @@
/** Disadvantage definitions (extensible); modifiers for melee and ranged combat */
export type DisadvantageDef = {
id: string;
name: string;
has_levels: boolean;
levels: number[];
at_mod: number;
pa_mod: number;
fk_mod: number;
};
export const DISADVANTAGES: DisadvantageDef[] = [
{
id: 'einaeuig',
name: 'Einäugig',
has_levels: false,
levels: [],
at_mod: 0,
pa_mod: 0,
fk_mod: -2
}
];
export function getDisadvantage(id: string): DisadvantageDef | undefined {
return DISADVANTAGES.find((n) => n.id === id);
}
+9 -5
View File
@@ -14,13 +14,17 @@ const energySchema = z.object({
const traitSchema = z.object({
id: z.string(),
name: z.string(),
/** Referenz auf Eintrag in rules (advantage/disadvantage) */
defId: z.string().default(''),
name: z.string().optional(),
stufe: z.number().int().optional(),
note: z.string().optional()
});
const sfSchema = z.object({
id: z.string(),
name: z.string(),
defId: z.string().default(''),
name: z.string().optional(),
note: z.string().optional()
});
@@ -108,9 +112,9 @@ export const characterSchema = z.object({
aup: energySchema,
mr: energySchema
}),
vorteile: z.array(traitSchema).default([]),
nachteile: z.array(traitSchema).default([]),
sonderfertigkeiten: z.array(sfSchema).default([]),
advantages: z.array(traitSchema).default([]),
disadvantages: z.array(traitSchema).default([]),
abilities: z.array(sfSchema).default([]),
talente: z.array(talentEntrySchema).default([]),
kampftalente: z.array(combatTalentSchema).default([]),
zauber: z.array(spellEntrySchema).optional(),
+101 -19
View File
@@ -5,7 +5,10 @@
import { ATTRIBUTE_KEYS, ATTRIBUTE_LABELS } from '$lib/rules/attributes';
import { getCharacter, saveCharacter } from '$lib/storage/repo';
import { RACES } from '$lib/rules/races';
import { DISADVANTAGES, getDisadvantage } from '$lib/rules/disadvantages';
import { ABILITIES } from '$lib/rules/abilities';
import { TALENTS } from '$lib/rules/talents';
import { ADVANTAGES, getAdvantage } from '$lib/rules/advantages';
import { computeDerived } from '$lib/engine/derived';
import { atBasis, paBasis } from '$lib/engine/derived';
@@ -55,21 +58,59 @@
char = char;
}
function addVorteil() {
function addAdvantage() {
if (!char) return;
char.vorteile = [...char.vorteile, { id: crypto.randomUUID(), name: '' }];
const first = ADVANTAGES[0];
char.advantages = [
...char.advantages,
{
id: crypto.randomUUID(),
defId: first?.id ?? '',
stufe: first?.has_levels && first.levels[0] !== undefined ? first.levels[0] : undefined
}
];
char = char;
}
function addNachteil() {
function addDisadvantage() {
if (!char) return;
char.nachteile = [...char.nachteile, { id: crypto.randomUUID(), name: '' }];
const first = DISADVANTAGES[0];
char.disadvantages = [
...char.disadvantages,
{
id: crypto.randomUUID(),
defId: first?.id ?? '',
stufe: first?.has_levels && first.levels[0] !== undefined ? first.levels[0] : undefined
}
];
char = char;
}
function addSF() {
function addAbility() {
if (!char) return;
char.sonderfertigkeiten = [...char.sonderfertigkeiten, { id: crypto.randomUUID(), name: '' }];
const first = ABILITIES[0];
char.abilities = [
...char.abilities,
{ id: crypto.randomUUID(), defId: first?.id ?? '' }
];
char = char;
}
function onAdvantageDefChange(v: Character['advantages'][number], defId: string) {
if (!char) return;
v.defId = defId;
const d = getAdvantage(defId);
if (d?.has_levels && d.levels.length) v.stufe = d.levels[0];
else v.stufe = undefined;
char = char;
}
function onDisadvantageDefChange(v: Character['disadvantages'][number], defId: string) {
if (!char) return;
v.defId = defId;
const d = getDisadvantage(defId);
if (d?.has_levels && d.levels.length) v.stufe = d.levels[0];
else v.stufe = undefined;
char = char;
}
@@ -232,7 +273,7 @@
{#each char.talente as tal, i}
<div class="inline">
<select bind:value={tal.id}>
{#each TALENTS as t}
{#each TALENTS.filter((t) => t.category !== 'kampf') as t}
<option value={t.id}>{t.name}</option>
{/each}
</select>
@@ -252,56 +293,97 @@
<section class="card stack">
<h2>Vorteile</h2>
{#each char.vorteile as v, i}
{#each char.advantages as v, i}
{@const vd = getAdvantage(v.defId)}
<div class="inline">
<input placeholder="Name" bind:value={v.name} />
<select
value={v.defId}
on:change={(e) =>
onAdvantageDefChange(v, (e.currentTarget as HTMLSelectElement).value)}
>
<option value=""></option>
{#each ADVANTAGES as d}
<option value={d.id}>{d.name}</option>
{/each}
</select>
{#if vd?.has_levels && vd.levels.length}
<label class="sr" for="advantage-stufe-{v.id}">Stufe</label>
<select id="advantage-stufe-{v.id}" class="w-tiny" bind:value={v.stufe}>
{#each vd.levels as lvl}
<option value={lvl}>{lvl}</option>
{/each}
</select>
{/if}
<button
type="button"
class="btn danger"
on:click={() => {
char!.vorteile = char!.vorteile.filter((_, j) => j !== i);
char!.advantages = char!.advantages.filter((_, j) => j !== i);
char = char;
}}>✕</button
>
</div>
{/each}
<button type="button" class="btn" on:click={addVorteil}>Vorteil</button>
<button type="button" class="btn" on:click={addAdvantage}>Vorteil</button>
</section>
<section class="card stack">
<h2>Nachteile</h2>
{#each char.nachteile as v, i}
{#each char.disadvantages as v, i}
{@const nd = getDisadvantage(v.defId)}
<div class="inline">
<input placeholder="Name" bind:value={v.name} />
<select
value={v.defId}
on:change={(e) =>
onDisadvantageDefChange(v, (e.currentTarget as HTMLSelectElement).value)}
>
<option value=""></option>
{#each DISADVANTAGES as d}
<option value={d.id}>{d.name}</option>
{/each}
</select>
{#if nd?.has_levels && nd.levels.length}
<label class="sr" for="disadvantage-stufe-{v.id}">Stufe</label>
<select id="disadvantage-stufe-{v.id}" class="w-tiny" bind:value={v.stufe}>
{#each nd.levels as lvl}
<option value={lvl}>{lvl}</option>
{/each}
</select>
{/if}
<button
type="button"
class="btn danger"
on:click={() => {
char!.nachteile = char!.nachteile.filter((_, j) => j !== i);
char!.disadvantages = char!.disadvantages.filter((_, j) => j !== i);
char = char;
}}>✕</button
>
</div>
{/each}
<button type="button" class="btn" on:click={addNachteil}>Nachteil</button>
<button type="button" class="btn" on:click={addDisadvantage}>Nachteil</button>
</section>
<section class="card stack">
<h2>Sonderfertigkeiten</h2>
{#each char.sonderfertigkeiten as s, i}
{#each char.abilities as s, i}
<div class="inline">
<input placeholder="Name" bind:value={s.name} />
<select bind:value={s.defId}>
<option value=""></option>
{#each ABILITIES as d}
<option value={d.id}>{d.name}</option>
{/each}
</select>
<button
type="button"
class="btn danger"
on:click={() => {
char!.sonderfertigkeiten = char!.sonderfertigkeiten.filter((_, j) => j !== i);
char!.abilities = char!.abilities.filter((_, j) => j !== i);
char = char;
}}>✕</button
>
</div>
{/each}
<button type="button" class="btn" on:click={addSF}>SF</button>
<button type="button" class="btn" on:click={addAbility}>SF</button>
</section>
<section class="card stack">