Refactor DSA types and components for better structure and type consistency.

This commit is contained in:
2025-06-22 01:27:19 +02:00
parent 45ce0ea3cc
commit f148695c59
19 changed files with 609 additions and 156 deletions

View File

View File

@@ -0,0 +1,154 @@
{
"steigerungsfaktoren": [
{
"aktivierung": "0",
"faktoren": {
"A": 1,
"B": 2,
"C": 3,
"D": 4,
"E": null
}
},
{
"aktivierung": "1-12",
"faktoren": {
"A": 1,
"B": 2,
"C": 3,
"D": 4,
"E": 15
}
},
{
"aktivierung": "13",
"faktoren": {
"A": 2,
"B": 4,
"C": 6,
"D": 8,
"E": 15
}
},
{
"aktivierung": "14",
"faktoren": {
"A": 3,
"B": 6,
"C": 9,
"D": 12,
"E": 15
}
},
{
"aktivierung": "15",
"faktoren": {
"A": 4,
"B": 8,
"C": 12,
"D": 16,
"E": 30
}
},
{
"aktivierung": "16",
"faktoren": {
"A": 5,
"B": 10,
"C": 15,
"D": 20,
"E": 45
}
},
{
"aktivierung": "17",
"faktoren": {
"A": 6,
"B": 12,
"C": 18,
"D": 24,
"E": 60
}
},
{
"aktivierung": "18",
"faktoren": {
"A": 7,
"B": 14,
"C": 21,
"D": 28,
"E": 75
}
},
{
"aktivierung": "19",
"faktoren": {
"A": 8,
"B": 16,
"C": 24,
"D": 32,
"E": 90
}
},
{
"aktivierung": "20",
"faktoren": {
"A": 9,
"B": 18,
"C": 27,
"D": 36,
"E": 105
}
},
{
"aktivierung": "21",
"faktoren": {
"A": 10,
"B": 20,
"C": 30,
"D": 40,
"E": 120
}
},
{
"aktivierung": "22",
"faktoren": {
"A": 11,
"B": 22,
"C": 33,
"D": 44,
"E": 135
}
},
{
"aktivierung": "23",
"faktoren": {
"A": 12,
"B": 24,
"C": 36,
"D": 48,
"E": 150
}
},
{
"aktivierung": "24",
"faktoren": {
"A": 13,
"B": 26,
"C": 39,
"D": 52,
"E": 165
}
},
{
"aktivierung": "25",
"faktoren": {
"A": 14,
"B": 28,
"C": 42,
"D": 56,
"E": 180
}
}
]
}

0
src/assets/skills.json Normal file
View File

5
src/classes/Base.ts Normal file
View File

@@ -0,0 +1,5 @@
export default class Base {
constructor() {
}
}

View File

@@ -0,0 +1,88 @@
import type {DSAAttributes, DSAExperienceLevel, DSAPersonalData, DSAStats} from "../types/baseCharacter.ts";
import type {DSACalculatedValue, DSAImprovements} from "../types/baseDSA.ts";
import type {DSABaseEquiment} from "../types/baseEquiment.ts";
import type {DSAArmor, DSAMeleeWeapon, DSARangedWeapon, DSAShieldParryWeapon} from "../types/equiment.ts";
import type {DSASpell} from "../types/character.ts";
import Skill from "./Skill.ts";
// TODO
// === LOGIC ===
// Skillpoint <-> QualityLevel
export default class CharacterSheet extends Skill {
public id: string
personalData: DSAPersonalData
attributes: DSAAttributes
stats: DSAStats
fatePoints: DSACalculatedValue;
experienceLevel: DSAExperienceLevel
advantanges: string[] // TODO
disadvantanges: string[] // TODO
generalSpecialAbilities: string[] // TODO
skills: string[] // TODO: {physical, social, nature, knowledge, craft} generated by GPT
attributeModifiers: string[]
languages: string[]
scripts: string[]
combatTechniques: string[] // TODO
combatSpecialAbilities: string[] // TODO
public inventory: {
closeCombatWeapons: DSAMeleeWeapon[] // TODO
rangedWeapons: DSARangedWeapon[] // TODO
armor: DSAArmor[] // TODO
shieldParryWeapon: DSAShieldParryWeapon[] // TODO
belongings: DSABaseEquiment[]
totalWeight: number // TODO Calculated
carryingCapacity: number // TODO Calculated
purse: {
ducats: number
silverthalers: number
halers: number
kreutzers: number
}
}
public animal?: {
name: string
sizeCategory: 'winzig' | 'klein' | 'mittel' | 'groß' | 'riesig'
type: string
stats: DSAStats
attributes: DSAAttributes
attack: {
attack: number
defence: number
damagePoints: number
armor: number
}
actions: string[]
specialAbilities: string[]
image?: string
}
public spellsAndRituals?: { // TODO
properties: string[]
primaryAttribute: string[]
tradition: string[]
magicalSpecialAbilities: string[]
cantrips: string[]
spells?: DSASpell[]
}
public liturgicalChantsAndCeremonies?: { // TODO
aspects: string[]
primaryAttribute: string[]
tradition: string[]
blessedSpecialAbilities: string[]
blessings: string[]
spells?: DSASpell[]
}
constructor(name: string) {
super()
this.id = crypto.randomUUID()
this.personalData.name = name
}
}

25
src/classes/Skill.ts Normal file
View File

@@ -0,0 +1,25 @@
import type {DSAAttributes} from "../types/baseCharacter.ts";
import type {DSAImprovements} from "../types/baseDSA.ts";
export default class Skill{
skillName: string
attributes: [keyof DSAAttributes, keyof DSAAttributes, keyof DSAAttributes]
encumbrance: "YES" | "NO" | "MAYBE"
improvement: DSAImprovements
skillLevel: number
comment: string
routineCheck() : number{
return this.skillLevel >= 13 ? this.skillLevel/2 : 0
}
improvementCost(){
return DSAImprovementsTable.getCost(this.improvement, this.skillLevel)
}
improve(){
this.skillLevel ++
}
}
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export

View File

@@ -1,6 +1,7 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import type {DSACharacter, DSAAttributes} from '../types/character';
import type {DSACharacter} from '../types/character';
import type {DSAAttributes} from "../types/baseCharacter.ts";
interface AttributesProps {
character: DSACharacter;

View File

@@ -1,6 +1,7 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import type {DSACharacter} from '../types/character';
import type {DSAPersonalData} from "../types/baseCharacter.ts";
interface BasicInfoProps {
character: DSACharacter;
@@ -9,7 +10,7 @@ interface BasicInfoProps {
const BasicInfo: React.FC<BasicInfoProps> = ({ character, setCharacter }) => {
const { t } = useTranslation();
const updateField = (field: keyof DSACharacter, value: string) => {
const updateField = (field: keyof DSACharacter | keyof DSAPersonalData, value: string) => {
setCharacter(prev => ({
...prev,
[field]: value
@@ -25,7 +26,7 @@ const BasicInfo: React.FC<BasicInfoProps> = ({ character, setCharacter }) => {
<input
id="name"
type="text"
value={character.name}
value={character.personalData.name}
onChange={(e) => updateField('name', e.target.value)}
placeholder={t('basicInfo.namePlaceholder')}
/>
@@ -35,7 +36,7 @@ const BasicInfo: React.FC<BasicInfoProps> = ({ character, setCharacter }) => {
<input
id="species"
type="text"
value={character.species}
value={character.personalData.species}
onChange={(e) => updateField('species', e.target.value)}
placeholder={t('basicInfo.speciesPlaceholder')}
/>
@@ -45,7 +46,7 @@ const BasicInfo: React.FC<BasicInfoProps> = ({ character, setCharacter }) => {
<input
id="culture"
type="text"
value={character.culture}
value={character.personalData.culture}
onChange={(e) => updateField('culture', e.target.value)}
placeholder={t('basicInfo.culturePlaceholder')}
/>
@@ -55,7 +56,7 @@ const BasicInfo: React.FC<BasicInfoProps> = ({ character, setCharacter }) => {
<input
id="profession"
type="text"
value={character.profession}
value={character.personalData.profession}
onChange={(e) => updateField('profession', e.target.value)}
placeholder={t('basicInfo.professionPlaceholder')}
/>

View File

@@ -1,7 +1,7 @@
// src/components/CharacterSheet.tsx
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import type { DSACharacter } from '../types/character';
import React, {useState} from 'react';
import {useTranslation} from 'react-i18next';
import type {DSACharacter} from '../types/character';
import BasicInfo from './BasicInfo';
import Attributes from './Attributes';
import Skills from './Skills';
@@ -14,93 +14,241 @@ import '../styles/CharacterSheet.css';
// Erweiterte Initial-Daten mit Astralenergie und Zaubern
const initialCharacter: DSACharacter = {
id: crypto.randomUUID(),
id: crypto.randomUUID(),
personalData: {
name: '',
species: '',
culture: '',
profession: '',
experienceLevel: 'Experienced',
attributes: {
courage: 8,
cleverness: 8,
intuition: 8,
charisma: 8,
dexterity: 8,
agility: 8,
constitution: 8,
strength: 8
socialstatus: '',
hometown: '',
family: '',
looks: {
age: '',
haircolor: '',
eyecolor: '',
height: '',
weight: '',
characteristics: ''
}
},
experienceLevel: 'Experienced',
health: {
lifePoints: {
max: 30,
current: 30,
base: 30,
modifier: 0,
purchased: 0
},
skills: {},
spells: {},
combat: {
lifePoints: { max: 30, current: 30 },
initiative: 10,
speed: 8
conditions: [],
fatigue: 0,
pain: 0
},
attributes: {
courage: 8,
cleverness: 8,
intuition: 8,
charisma: 8,
dexterity: 8,
agility: 8,
constitution: 8,
strength: 8
},
skills: {},
astral: {
spells: {
'test': {
id: '',
name: '',
tradition: [''],
attributes: ['courage', 'agility', 'charisma'],
skillValue: 0,
aspCost: '',
castingTime: '',
range: '',
duration: '',
difficulty: 0,
description: '',
effect: '',
}
},
traditions: [],
astralEnergy: {
max: 0,
current: 0
max: 0,
current: 0
}
},
karmal: {
spells: {
'test': {
id: '',
name: '',
tradition: [''],
attributes: ['courage', 'agility', 'charisma'],
skillValue: 0,
aspCost: '',
castingTime: '',
range: '',
duration: '',
difficulty: 0,
description: '',
effect: '',
}
},
magicalTraditions: [],
advantages: [],
disadvantages: [],
equipment: []
traditions: [],
karmalEnergy: {
max: 0,
current: 0
}
},
hasKarmal: false,
hasMagic: false,
combat: {
initiative: 10,
speed: 8,
armor: {
'leather': {
encumbrance: 0,
equip: {
name: 'hello',
weight: 0
},
penalties: 0,
protection: 0
}
},
combatAbilities: {
'test': {
name: 'test'
}
},
dodge: 0,
meleeWeapons: {
'test': {
weapon: {
equip: {
name: 'test',
weight: 0
},
range: 0,
technique: 'test',
tp: 0
},
atpamod: 0,
at: 0,
damagebonus: 0,
pa: 0
}
},
rangedWeapons: {
'javelin': {
weapon: {
equip: {
name: '',
weight: 0
},
technique: '',
tp: 0,
range: 0,
},
reloaddur: 0,
ammo: {
equip: {
name: '',
weight: 0
},
perUse: 0,
count: 0
},
rangedCombat: 0,
}
},
unarmedAttack: 0,
unarmedParry: 0
},
advantages: [],
disadvantages: [],
equipment: [],
resistances: {
spirit: {
base: 0,
modifier: 0,
purchased: 0,
max: 0,
current: 0
},
thoughness: {
base: 0,
modifier: 0,
purchased: 0,
max: 0,
current: 0,
},
dodge: {
base: 0,
modifier: 0,
purchased: 0,
max: 0,
current: 0
}
}
};
const CharacterSheet: React.FC = () => {
const { t } = useTranslation();
const [character, setCharacter] = useState<DSACharacter>(initialCharacter);
// Hilfsfunktion um zu prüfen ob Charakter Zauberer ist
const isSpellcaster = character.astralEnergy && character.astralEnergy.max > 0;
return (
<div className="character-sheet">
<ThemeToggle />
<LanguageSelector />
<header className="character-sheet-header">
<img
src={iconUrl}
alt={t('app.iconAlt')}
className="character-sheet-icon"
/>
<h1>{t('app.title')}</h1>
</header>
{/* Grundinformationen */}
<BasicInfo character={character} setCharacter={setCharacter} />
{/* Eigenschaften */}
<Attributes character={character} setCharacter={setCharacter} />
{/* Kampfwerte */}
<CombatValues character={character} setCharacter={setCharacter} />
{/* Fertigkeiten */}
<Skills character={character} setCharacter={setCharacter} />
{/* Zauber - nur anzeigen wenn Astralenergie > 0 oder explizit gewünscht */}
{(isSpellcaster || Object.keys(character.spells).length > 0) && (
<Spells character={character} setCharacter={setCharacter} />
)}
{/* Button zum Hinzufügen von Zaubern falls noch kein Zauberer */}
{!isSpellcaster && Object.keys(character.spells).length === 0 && (
<div className="add-magic-section">
<button
className="add-magic-button"
onClick={() => setCharacter(prev => ({
...prev,
astralEnergy: { max: 20, current: 20 }
}))}
>
{t('spells.addMagic')}
</button>
</div>
)}
</div>
);
const {t} = useTranslation();
const [character, setCharacter] = useState<DSACharacter>(initialCharacter);
// Hilfsfunktion um zu prüfen ob Charakter Zauberer ist
const isSpellcaster = character.astral.astralEnergy && character.astral.astralEnergy.max > 0;
return (
<div className="character-sheet">
<ThemeToggle/>
<LanguageSelector/>
<header className="character-sheet-header">
<img
src={iconUrl}
alt={t('app.iconAlt')}
className="character-sheet-icon"
/>
<h1>{t('app.title')}</h1>
</header>
{/* Grundinformationen */}
<BasicInfo character={character} setCharacter={setCharacter}/>
{/* Eigenschaften */}
<Attributes character={character} setCharacter={setCharacter}/>
{/* Kampfwerte */}
<CombatValues character={character} setCharacter={setCharacter}/>
{/* Fertigkeiten */}
<Skills character={character} setCharacter={setCharacter}/>
{/* Zauber - nur anzeigen wenn Astralenergie > 0 oder explizit gewünscht */}
{(isSpellcaster || Object.keys(character.astral.spells).length > 0) && (
<Spells character={character} setCharacter={setCharacter}/>
)}
{/* Button zum Hinzufügen von Zaubern falls noch kein Zauberer */}
{!isSpellcaster && Object.keys(character.astral.spells).length === 0 && (
<div className="add-magic-section">
<button
className="add-magic-button"
onClick={() => setCharacter(prev => ({
...prev,
astralEnergy: {max: 20, current: 20}
}))}
>
{t('spells.addMagic')}
</button>
</div>
)}
</div>
);
};
export default CharacterSheet;

View File

@@ -1,3 +1,36 @@
export interface DSACombatAbilitiy {
name: string;
import type {DSAAttributes} from "./baseCharacter.ts";
import type {DSABelastungen, DSAImprovements} from "./baseDSA.ts";
export interface DSABaseAbility {
name: string
steigerungsfaktor: DSAImprovements
}
export interface DSASkill extends DSABaseAbility {
attributes: [keyof DSAAttributes, keyof DSAAttributes, keyof DSAAttributes]
fertigkeitswert: number
belastung: DSABelastungen
anmerkung: string
routineProbe: number
}
export interface DSACombatTechnique extends DSABaseAbility {
primaryAttribute: keyof DSAAttributes
kampftechnikwert: number
hitcheck: number
paradecheck: number
}
export interface DSASpell {
name: string
check: [keyof DSAAttributes, keyof DSAAttributes, keyof DSAAttributes]
skilllevel: number
cost: number
timeInSec: number
range: number
duration: number
property: string
improvement: DSAImprovements
effect: string[]
special: boolean
}

View File

@@ -5,4 +5,15 @@ export interface BaseAbilities {
int: number;
wis: number;
cha: number;
}
}
export interface DSAAttributes {
courage: number;
cleverness: number;
intuition: number;
charisma: number;
dexterity: number;
agility: number;
constitution: number;
strength: number;
}

View File

@@ -1,4 +1,4 @@
import type {DSACalculatedValue} from "./baseDSA.ts";
import type {DSABelastungen, DSACalculatedValue} from "./baseDSA.ts";
export interface DSAAttributes {
courage: number;
@@ -13,33 +13,43 @@ export interface DSAAttributes {
export interface DSAPersonalData {
name: string;
sex?: 'm' | 'w' | 'd';
species: string;
culture: string;
profession: string;
socialstatus: string;
socialStanding: string;
hometown: string;
placeOfBirth?: string;
family: string;
looks: DSALooks;
}
export interface DSALooks {
age: string;
haircolor: string;
eyecolor: string;
height: string;
weight: string;
characteristics: string;
birthdate?: string;
hairColor?: string;
eyeColor?: string;
height?: string;
weight?: string;
characteristics?: string[];
image?: string;
}
export interface DSAHealth {
lifePoints: DSACalculatedValue;
conditions: string[];
pain: number;
fatigue: number;
}
export interface DSAResistances {
export interface DSAStats {
lifePoints: {
value: DSACalculatedValue;
pain: number;
fatigue: number;
}
conditions?: string[];
astralEnergy?: DSACalculatedValue;
karmaEnergy?: DSACalculatedValue;
spirit: DSACalculatedValue;
thoughness: DSACalculatedValue;
dodge: DSACalculatedValue;
initiative: number; //TODO function?
movement: number;
}
export interface DSAExperienceLevel {
current: "Unerfahren" | "Durchschnittlich" | "Erfahren" | "Kompetent" | "Meisterlich" | "Brillant" | "Legendär"
apTotal: number
apAvailable: number
apSpent: number
}

View File

@@ -1,23 +1,11 @@
export interface DSACalculatedValue {
base: number;
modifier: number;
purchased: number;
purchased?: number;
max: number;
current: number;
}
export interface DSAMinMaxValue {
max: number;
current: number;
}
export type DSAImprovements = 'A' | 'B' | 'C' | 'D' | 'E' | 'other'
export interface DSANamedEntity {
id: string;
name: string;
description?: string;
}
export interface DSATimedEffect {
duration: string;
remaining?: number;
}
export type DSABelastungen = 'JA' | 'NEIN' | 'EVTL';

View File

@@ -1,10 +1,10 @@
export interface DSABaseEquiment {
name: string;
weight: number;
carriedWhere: string;
}
export interface DSABaseWeapon {
equip: DSABaseEquiment;
export interface DSABaseWeapon extends DSABaseEquiment {
technique: string;
tp: number;
range: number;

View File

@@ -1,12 +1,17 @@
import type {DSAAttributes, DSAHealth, DSAPersonalData, DSAResistances} from "./baseCharacter.ts";
import type {DSAArmor, DSAMeleeWeapon, DSARangedWeapon, DSAShield} from "./equiment.ts";
import type {DSACombatAbilitiy} from "./abilities.ts";
import type {DSACombatTechnique} from "./abilities.ts";
import type {DSAFatePoints} from "./fatepoints.ts";
import type {DSABelastungen, DSAImprovements} from "./baseDSA.ts";
export interface DSASkill {
name: string;
attributes: [keyof DSAAttributes, keyof DSAAttributes, keyof DSAAttributes];
value: number;
name: string
attributes: [keyof DSAAttributes, keyof DSAAttributes, keyof DSAAttributes]
fertigkeitswert: number
belastung: DSABelastungen
steigerungsfaktor: DSAImprovements
anmerkung: string
routineProbe: number
}
export interface DSACombatValues {
@@ -19,7 +24,7 @@ export interface DSACombatValues {
rangedWeapons: DSARangedWeapon[];
armor: DSAArmor[];
shield: DSAShield[];
combatAbilities: Record<string, DSACombatAbilitiy>;
combatAbilities: Record<string, DSACombatTechnique>;
}
export interface DSAAstral {

View File

@@ -1,15 +1,4 @@
export enum DSAConditionType {
confusion = "confusion",
encumbrance = "encumbrance",
fear = "fear",
pain = "pain",
paralysis = "paralysis",
sleep = "sleep",
rapture = "rapture",
stupor = "stupor",
}
export interface DSACondition {
condition: DSAConditionType;
level: number;
}
condition: "confusion" | "encumbrance" | "fear" | "pain" | "paralysis" | "rapture" | "stupor";
level: 1 | 2 | 3 | 4;
}

View File

@@ -2,36 +2,31 @@ import type {DSABaseEquiment, DSABaseWeapon} from "./baseEquiment.ts";
import type {DSACalculatedValue} from "./baseDSA.ts";
export interface DSAAmmonution{
equip: DSABaseEquiment;
export interface DSAAmmonution extends DSABaseEquiment {
perUse: number;
count: number;
}
export interface DSAMeleeWeapon{
weapon: DSABaseWeapon;
export interface DSAMeleeWeapon extends DSABaseWeapon{
atpamod: number;
damagebonus: number;
at: number;
pa: number;
}
export interface DSARangedWeapon{
weapon: DSABaseWeapon;
export interface DSARangedWeapon extends DSABaseWeapon{
reloaddur: number;
ammo: DSAAmmonution;
rangedCombat: number;
}
export interface DSAArmor{
equip: DSABaseEquiment;
export interface DSAArmor extends DSABaseEquiment{
protection: number;
encumbrance: number;
penalties: number;
}
export interface DSAShield{
equip: DSABaseEquiment;
export interface DSAShieldParryWeapon extends DSABaseEquiment {
structure: DSACalculatedValue;
atpamod: number;
}
}

View File

@@ -19,7 +19,7 @@
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": false,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},

View File

@@ -17,7 +17,7 @@
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": false,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},