added spells
This commit is contained in:
BIN
public/icon64.png
Normal file
BIN
public/icon64.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.1 KiB |
@@ -14,9 +14,10 @@
|
|||||||
"initTimeout": 10
|
"initTimeout": 10
|
||||||
},
|
},
|
||||||
"icons": {
|
"icons": {
|
||||||
"64x64": "/icon.png"
|
"64x64": "/icon64.png"
|
||||||
},
|
},
|
||||||
"environment": {
|
"environment": {
|
||||||
|
"webViewBackgroundColor": "#1a1a1a",
|
||||||
"capabilities": ["chat", "dice"],
|
"capabilities": ["chat", "dice"],
|
||||||
"extras": ["colorStyles"]
|
"extras": ["colorStyles"]
|
||||||
}
|
}
|
||||||
|
@@ -541,3 +541,8 @@ button:active {
|
|||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.3);
|
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;
|
||||||
|
}
|
@@ -1,13 +1,16 @@
|
|||||||
|
// src/components/CharacterSheet.tsx
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import type { DSACharacter } from '../types/character';
|
import type { DSACharacter } from '../types/character';
|
||||||
import BasicInfo from './BasicInfo';
|
import BasicInfo from './BasicInfo';
|
||||||
import Attributes from './Attributes';
|
import Attributes from './Attributes';
|
||||||
import Skills from './Skills';
|
import Skills from './Skills';
|
||||||
|
import Spells from './Spells';
|
||||||
import CombatValues from './CombatValues';
|
import CombatValues from './CombatValues';
|
||||||
import './CharacterSheet.css';
|
import ThemeToggle from './ThemeToggle';
|
||||||
import ThemeToggle from "./ThemeToggle.tsx";
|
|
||||||
import iconUrl from '/icon.png';
|
import iconUrl from '/icon.png';
|
||||||
|
import './CharacterSheet.css';
|
||||||
|
|
||||||
|
// Erweiterte Initial-Daten mit Astralenergie und Zaubern
|
||||||
const initialCharacter: DSACharacter = {
|
const initialCharacter: DSACharacter = {
|
||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
name: '',
|
name: '',
|
||||||
@@ -26,12 +29,18 @@ const initialCharacter: DSACharacter = {
|
|||||||
strength: 8
|
strength: 8
|
||||||
},
|
},
|
||||||
skills: {},
|
skills: {},
|
||||||
|
spells: {}, // ← Neu hinzugefügt
|
||||||
combat: {
|
combat: {
|
||||||
lifePoints: { max: 30, current: 30 },
|
lifePoints: { max: 30, current: 30 },
|
||||||
stamina: { max: 30, current: 30 },
|
stamina: { max: 30, current: 30 },
|
||||||
initiative: 10,
|
initiative: 10,
|
||||||
speed: 8
|
speed: 8
|
||||||
},
|
},
|
||||||
|
astralEnergy: { // ← Neu hinzugefügt für Zauberer
|
||||||
|
max: 0,
|
||||||
|
current: 0
|
||||||
|
},
|
||||||
|
magicalTraditions: [], // ← Neu hinzugefügt
|
||||||
advantages: [],
|
advantages: [],
|
||||||
disadvantages: [],
|
disadvantages: [],
|
||||||
equipment: []
|
equipment: []
|
||||||
@@ -40,17 +49,53 @@ const initialCharacter: DSACharacter = {
|
|||||||
const CharacterSheet: React.FC = () => {
|
const CharacterSheet: React.FC = () => {
|
||||||
const [character, setCharacter] = useState<DSACharacter>(initialCharacter);
|
const [character, setCharacter] = useState<DSACharacter>(initialCharacter);
|
||||||
|
|
||||||
|
// Hilfsfunktion um zu prüfen ob Charakter Zauberer ist
|
||||||
|
const isSpellcaster = character.astralEnergy && character.astralEnergy.max > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="character-sheet">
|
<div className="character-sheet">
|
||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
|
|
||||||
<header className="character-sheet-header">
|
<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>
|
<h1>DSA 5 Character Sheet</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
{/* Grundinformationen */}
|
||||||
<BasicInfo character={character} setCharacter={setCharacter} />
|
<BasicInfo character={character} setCharacter={setCharacter} />
|
||||||
|
|
||||||
|
{/* Eigenschaften */}
|
||||||
<Attributes character={character} setCharacter={setCharacter} />
|
<Attributes character={character} setCharacter={setCharacter} />
|
||||||
<Skills character={character} setCharacter={setCharacter} />
|
|
||||||
|
{/* Kampfwerte */}
|
||||||
<CombatValues character={character} setCharacter={setCharacter} />
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
235
src/components/Spells.tsx
Normal file
235
src/components/Spells.tsx
Normal 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;
|
@@ -12,4 +12,5 @@
|
|||||||
/* Add other TaleSpire variables as you discover them */
|
/* Add other TaleSpire variables as you discover them */
|
||||||
--ts-color-background: #f5f5f5;
|
--ts-color-background: #f5f5f5;
|
||||||
--ts-color-on-background: #222222;
|
--ts-color-on-background: #222222;
|
||||||
|
--ts-background-primary: #1a1a1a,
|
||||||
}
|
}
|
||||||
|
@@ -32,6 +32,21 @@ export interface DSACombatValues {
|
|||||||
speed: number;
|
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 {
|
export interface DSACharacter {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -45,4 +60,10 @@ export interface DSACharacter {
|
|||||||
advantages: string[]; // Vorteile
|
advantages: string[]; // Vorteile
|
||||||
disadvantages: string[]; // Nachteile
|
disadvantages: string[]; // Nachteile
|
||||||
equipment: string[]; // Ausrüstung
|
equipment: string[]; // Ausrüstung
|
||||||
|
astralEnergy?: {
|
||||||
|
max: number;
|
||||||
|
current: number;
|
||||||
|
};
|
||||||
|
spells: Record<string, DSASpell>;
|
||||||
|
magicalTraditions: string[];
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user