refine abilities and stuff
This commit is contained in:
@@ -34,9 +34,9 @@ export function newCharacter(partial?: { name?: string; id?: string }): Characte
|
|||||||
aup: { mod: 0 },
|
aup: { mod: 0 },
|
||||||
mr: { mod: 0 }
|
mr: { mod: 0 }
|
||||||
},
|
},
|
||||||
vorteile: [],
|
advantages: [],
|
||||||
nachteile: [],
|
disadvantages: [],
|
||||||
sonderfertigkeiten: [],
|
abilities: [],
|
||||||
talente: [],
|
talente: [],
|
||||||
kampftalente: [],
|
kampftalente: [],
|
||||||
inventar: [],
|
inventar: [],
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -14,13 +14,17 @@ const energySchema = z.object({
|
|||||||
|
|
||||||
const traitSchema = z.object({
|
const traitSchema = z.object({
|
||||||
id: z.string(),
|
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()
|
note: z.string().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
const sfSchema = z.object({
|
const sfSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
name: z.string(),
|
defId: z.string().default(''),
|
||||||
|
name: z.string().optional(),
|
||||||
note: z.string().optional()
|
note: z.string().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -108,9 +112,9 @@ export const characterSchema = z.object({
|
|||||||
aup: energySchema,
|
aup: energySchema,
|
||||||
mr: energySchema
|
mr: energySchema
|
||||||
}),
|
}),
|
||||||
vorteile: z.array(traitSchema).default([]),
|
advantages: z.array(traitSchema).default([]),
|
||||||
nachteile: z.array(traitSchema).default([]),
|
disadvantages: z.array(traitSchema).default([]),
|
||||||
sonderfertigkeiten: z.array(sfSchema).default([]),
|
abilities: z.array(sfSchema).default([]),
|
||||||
talente: z.array(talentEntrySchema).default([]),
|
talente: z.array(talentEntrySchema).default([]),
|
||||||
kampftalente: z.array(combatTalentSchema).default([]),
|
kampftalente: z.array(combatTalentSchema).default([]),
|
||||||
zauber: z.array(spellEntrySchema).optional(),
|
zauber: z.array(spellEntrySchema).optional(),
|
||||||
|
|||||||
@@ -5,7 +5,10 @@
|
|||||||
import { ATTRIBUTE_KEYS, ATTRIBUTE_LABELS } from '$lib/rules/attributes';
|
import { ATTRIBUTE_KEYS, ATTRIBUTE_LABELS } from '$lib/rules/attributes';
|
||||||
import { getCharacter, saveCharacter } from '$lib/storage/repo';
|
import { getCharacter, saveCharacter } from '$lib/storage/repo';
|
||||||
import { RACES } from '$lib/rules/races';
|
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 { TALENTS } from '$lib/rules/talents';
|
||||||
|
import { ADVANTAGES, getAdvantage } from '$lib/rules/advantages';
|
||||||
import { computeDerived } from '$lib/engine/derived';
|
import { computeDerived } from '$lib/engine/derived';
|
||||||
import { atBasis, paBasis } from '$lib/engine/derived';
|
import { atBasis, paBasis } from '$lib/engine/derived';
|
||||||
|
|
||||||
@@ -55,21 +58,59 @@
|
|||||||
char = char;
|
char = char;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addVorteil() {
|
function addAdvantage() {
|
||||||
if (!char) return;
|
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;
|
char = char;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addNachteil() {
|
function addDisadvantage() {
|
||||||
if (!char) return;
|
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;
|
char = char;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addSF() {
|
function addAbility() {
|
||||||
if (!char) return;
|
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;
|
char = char;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,7 +273,7 @@
|
|||||||
{#each char.talente as tal, i}
|
{#each char.talente as tal, i}
|
||||||
<div class="inline">
|
<div class="inline">
|
||||||
<select bind:value={tal.id}>
|
<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>
|
<option value={t.id}>{t.name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
@@ -252,56 +293,97 @@
|
|||||||
|
|
||||||
<section class="card stack">
|
<section class="card stack">
|
||||||
<h2>Vorteile</h2>
|
<h2>Vorteile</h2>
|
||||||
{#each char.vorteile as v, i}
|
{#each char.advantages as v, i}
|
||||||
|
{@const vd = getAdvantage(v.defId)}
|
||||||
<div class="inline">
|
<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
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn danger"
|
class="btn danger"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
char!.vorteile = char!.vorteile.filter((_, j) => j !== i);
|
char!.advantages = char!.advantages.filter((_, j) => j !== i);
|
||||||
char = char;
|
char = char;
|
||||||
}}>✕</button
|
}}>✕</button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
<button type="button" class="btn" on:click={addVorteil}>Vorteil</button>
|
<button type="button" class="btn" on:click={addAdvantage}>Vorteil</button>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="card stack">
|
<section class="card stack">
|
||||||
<h2>Nachteile</h2>
|
<h2>Nachteile</h2>
|
||||||
{#each char.nachteile as v, i}
|
{#each char.disadvantages as v, i}
|
||||||
|
{@const nd = getDisadvantage(v.defId)}
|
||||||
<div class="inline">
|
<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
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn danger"
|
class="btn danger"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
char!.nachteile = char!.nachteile.filter((_, j) => j !== i);
|
char!.disadvantages = char!.disadvantages.filter((_, j) => j !== i);
|
||||||
char = char;
|
char = char;
|
||||||
}}>✕</button
|
}}>✕</button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
<button type="button" class="btn" on:click={addNachteil}>Nachteil</button>
|
<button type="button" class="btn" on:click={addDisadvantage}>Nachteil</button>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="card stack">
|
<section class="card stack">
|
||||||
<h2>Sonderfertigkeiten</h2>
|
<h2>Sonderfertigkeiten</h2>
|
||||||
{#each char.sonderfertigkeiten as s, i}
|
{#each char.abilities as s, i}
|
||||||
<div class="inline">
|
<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
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn danger"
|
class="btn danger"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
char!.sonderfertigkeiten = char!.sonderfertigkeiten.filter((_, j) => j !== i);
|
char!.abilities = char!.abilities.filter((_, j) => j !== i);
|
||||||
char = char;
|
char = char;
|
||||||
}}>✕</button
|
}}>✕</button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
<button type="button" class="btn" on:click={addSF}>SF</button>
|
<button type="button" class="btn" on:click={addAbility}>SF</button>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="card stack">
|
<section class="card stack">
|
||||||
|
|||||||
Reference in New Issue
Block a user