added spells

This commit is contained in:
2025-06-15 18:40:15 +02:00
parent d8bad758f5
commit 7f280fc741
7 changed files with 314 additions and 6 deletions

BIN
public/icon64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@@ -14,9 +14,10 @@
"initTimeout": 10
},
"icons": {
"64x64": "/icon.png"
"64x64": "/icon64.png"
},
"environment": {
"webViewBackgroundColor": "#1a1a1a",
"capabilities": ["chat", "dice"],
"extras": ["colorStyles"]
}

View File

@@ -541,3 +541,8 @@ button:active {
transform: scale(1.1);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.3);
}
body {
background: var(--ts-background-primary, var(--background-color));
transition: background-color 0.3s ease;
}

View File

@@ -1,13 +1,16 @@
// src/components/CharacterSheet.tsx
import React, { useState } from 'react';
import type {DSACharacter} from '../types/character';
import type { DSACharacter } from '../types/character';
import BasicInfo from './BasicInfo';
import Attributes from './Attributes';
import Skills from './Skills';
import Spells from './Spells';
import CombatValues from './CombatValues';
import './CharacterSheet.css';
import ThemeToggle from "./ThemeToggle.tsx";
import ThemeToggle from './ThemeToggle';
import iconUrl from '/icon.png';
import './CharacterSheet.css';
// Erweiterte Initial-Daten mit Astralenergie und Zaubern
const initialCharacter: DSACharacter = {
id: crypto.randomUUID(),
name: '',
@@ -26,12 +29,18 @@ const initialCharacter: DSACharacter = {
strength: 8
},
skills: {},
spells: {}, // ← Neu hinzugefügt
combat: {
lifePoints: { max: 30, current: 30 },
stamina: { max: 30, current: 30 },
initiative: 10,
speed: 8
},
astralEnergy: { // ← Neu hinzugefügt für Zauberer
max: 0,
current: 0
},
magicalTraditions: [], // ← Neu hinzugefügt
advantages: [],
disadvantages: [],
equipment: []
@@ -40,17 +49,53 @@ const initialCharacter: DSACharacter = {
const CharacterSheet: React.FC = () => {
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 />
<header className="character-sheet-header">
<img src={iconUrl} alt="DSA 5e Logo" className="character-sheet-icon" />
<img
src={iconUrl}
alt="DSA 5e Character Sheet"
className="character-sheet-icon"
/>
<h1>DSA 5 Character Sheet</h1>
</header>
{/* Grundinformationen */}
<BasicInfo character={character} setCharacter={setCharacter} />
{/* Eigenschaften */}
<Attributes character={character} setCharacter={setCharacter} />
<Skills 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 }
}))}
>
🪄 Zauberfähigkeiten hinzufügen
</button>
</div>
)}
</div>
);
};

235
src/components/Spells.tsx Normal file
View File

@@ -0,0 +1,235 @@
import React, { useState } from 'react';
import type { DSACharacter, DSASpell, DSAAttributes } from '../types/character';
interface SpellsProps {
character: DSACharacter;
setCharacter: React.Dispatch<React.SetStateAction<DSACharacter>>;
}
const Spells: React.FC<SpellsProps> = ({ character, setCharacter }) => {
const [newSpell, setNewSpell] = useState<Partial<DSASpell>>({
name: '',
tradition: [],
attributes: ['cleverness', 'intuition', 'charisma'],
skillValue: 0,
aspCost: '4 AsP',
castingTime: '2 Aktionen',
range: 'Berührung',
duration: 'Sofort',
difficulty: 0,
description: '',
effect: ''
});
// Häufige DSA 5e Zauber für Quick-Add
const commonSpells: Partial<DSASpell>[] = [
{
name: 'Ignifaxius',
tradition: ['Gildenmagier'],
attributes: ['cleverness', 'dexterity', 'constitution'],
aspCost: '2 AsP',
castingTime: '2 Aktionen',
range: '7 Meter',
duration: 'Sofort',
difficulty: 0,
description: 'Entzündet brennbares Material'
},
{
name: 'Odem Arcanum',
tradition: ['Gildenmagier'],
attributes: ['cleverness', 'intuition', 'intuition'],
aspCost: '1 AsP',
castingTime: '2 Aktionen',
range: 'Berührung',
duration: 'Sofort',
difficulty: 0,
description: 'Erkennt magische Objekte und Wesen'
},
{
name: 'Balsam Salabunde',
tradition: ['Gildenmagier', 'Druide'],
attributes: ['cleverness', 'intuition', 'dexterity'],
aspCost: '4 AsP (A), 2 AsP (B), 8 AsP (C)',
castingTime: '8 Aktionen',
range: 'Berührung',
duration: 'Sofort',
difficulty: 0,
description: 'Heilt Wunden'
}
];
const addSpell = (spellData: Partial<DSASpell>) => {
const spell: DSASpell = {
id: crypto.randomUUID(),
name: spellData.name || '',
tradition: spellData.tradition || [],
attributes: spellData.attributes || ['cleverness', 'intuition', 'charisma'],
skillValue: spellData.skillValue || 0,
aspCost: spellData.aspCost || '4 AsP',
castingTime: spellData.castingTime || '2 Aktionen',
range: spellData.range || 'Berührung',
duration: spellData.duration || 'Sofort',
difficulty: spellData.difficulty || 0,
description: spellData.description || '',
effect: spellData.effect || ''
};
setCharacter(prev => ({
...prev,
spells: {
...prev.spells,
[spell.id]: spell
}
}));
};
const updateSpell = (spellId: string, field: keyof DSASpell, value: any) => {
setCharacter(prev => ({
...prev,
spells: {
...prev.spells,
[spellId]: {
...prev.spells[spellId],
[field]: value
}
}
}));
};
const removeSpell = (spellId: string) => {
setCharacter(prev => {
const { [spellId]: removed, ...remainingSpells } = prev.spells;
return {
...prev,
spells: remainingSpells
};
});
};
const calculateSpellCheck = (spell: DSASpell): number => {
const [attr1, attr2, attr3] = spell.attributes;
return character.attributes[attr1] + character.attributes[attr2] + character.attributes[attr3] + spell.skillValue;
};
const updateAstralEnergy = (field: 'max' | 'current', value: number) => {
setCharacter(prev => ({
...prev,
astralEnergy: {
...prev.astralEnergy!,
[field]: Math.max(0, value)
}
}));
};
return (
<div className="spells section">
<h2>Zauber & Astralenergie</h2>
{/* Astralenergie Tracking */}
{character.astralEnergy && (
<div className="astral-energy">
<h3>Astralenergie (AsP)</h3>
<div className="value-pair">
<div>
<label htmlFor="asp-current">Aktuell</label>
<input
id="asp-current"
type="number"
value={character.astralEnergy.current}
onChange={(e) => updateAstralEnergy('current', parseInt(e.target.value) || 0)}
min="0"
/>
</div>
<div>
<label htmlFor="asp-max">Maximum</label>
<input
id="asp-max"
type="number"
value={character.astralEnergy.max}
onChange={(e) => updateAstralEnergy('max', parseInt(e.target.value) || 0)}
min="0"
/>
</div>
</div>
</div>
)}
{/* Häufige Zauber hinzufügen */}
<div className="common-spells">
<h3>Häufige Zauber hinzufügen</h3>
<div className="spell-buttons">
{commonSpells.map((spell, index) => (
<button
key={index}
onClick={() => addSpell(spell)}
className="spell-add-button"
disabled={Object.values(character.spells).some(s => s.name === spell.name)}
>
{spell.name}
</button>
))}
</div>
</div>
{/* Zauberliste */}
<div className="spells-list">
<h3>Beherrschte Zauber</h3>
{Object.keys(character.spells).length === 0 ? (
<p className="no-spells">Noch keine Zauber erlernt. Füge welche hinzu!</p>
) : (
<div className="spells-grid">
{Object.entries(character.spells).map(([spellId, spell]) => (
<div key={spellId} className="spell-item">
<div className="spell-header">
<h4>{spell.name}</h4>
<button
onClick={() => removeSpell(spellId)}
className="remove-button"
title="Zauber entfernen"
>
×
</button>
</div>
<div className="spell-info">
<div className="spell-attributes">
<span>{spell.attributes.map(attr => attr.substring(0, 2).toUpperCase()).join('/')}</span>
</div>
<div className="spell-details">
<div><strong>AsP:</strong> {spell.aspCost}</div>
<div><strong>Dauer:</strong> {spell.castingTime}</div>
<div><strong>Reichweite:</strong> {spell.range}</div>
</div>
</div>
<div className="spell-value">
<label>Fertigkeitswert:</label>
<input
type="number"
value={spell.skillValue}
onChange={(e) => updateSpell(spellId, 'skillValue', parseInt(e.target.value) || 0)}
min="0"
max="20"
/>
</div>
<div className="spell-total">
<strong>Gesamt: {calculateSpellCheck(spell)}</strong>
</div>
{spell.description && (
<div className="spell-description">
<em>{spell.description}</em>
</div>
)}
</div>
))}
</div>
)}
</div>
</div>
);
};
export default Spells;

View File

@@ -12,4 +12,5 @@
/* Add other TaleSpire variables as you discover them */
--ts-color-background: #f5f5f5;
--ts-color-on-background: #222222;
--ts-background-primary: #1a1a1a,
}

View File

@@ -32,6 +32,21 @@ export interface DSACombatValues {
speed: number;
}
export interface DSASpell {
id: string;
name: string;
tradition: string[]; // Gildenmagier, Hexe, Druide, etc.
attributes: [keyof DSAAttributes, keyof DSAAttributes, keyof DSAAttributes];
skillValue: number; // FW - Fertigkeitswert
aspCost: string; // "4 AsP", "2 AsP pro Stufe"
castingTime: string; // "2 Aktionen", "5 Minuten"
range: string; // "Berührung", "7 Meter"
duration: string; // "Sofort", "5 Minuten"
difficulty: number; // Erschwernis/Erleichterung
description: string;
effect: string;
}
export interface DSACharacter {
id: string;
name: string;
@@ -45,4 +60,10 @@ export interface DSACharacter {
advantages: string[]; // Vorteile
disadvantages: string[]; // Nachteile
equipment: string[]; // Ausrüstung
astralEnergy?: {
max: number;
current: number;
};
spells: Record<string, DSASpell>;
magicalTraditions: string[];
}