Эх сурвалжийг харах

Merge branch 'feature/spell-system' into develop

Warafear 1 жил өмнө
parent
commit
e5f42c0aa4
22 өөрчлөгдсөн 1072 нэмэгдсэн , 562 устгасан
  1. 72 0
      .nx/cache/d/daemon.log
  2. 1 1
      .nx/cache/d/server-process.json
  3. 9 0
      src/app/character/character-creator/character-creator.component.ts
  4. 39 23
      src/app/journal/journal-spellcards/add-card/add-card.component.html
  5. 72 30
      src/app/journal/journal-spellcards/add-card/add-card.component.scss
  6. 69 31
      src/app/journal/journal-spellcards/add-card/add-card.component.ts
  7. 8 3
      src/app/journal/journal-spellcards/journal-spellcards.component.html
  8. 6 7
      src/app/journal/journal-spellcards/journal-spellcards.component.scss
  9. 88 35
      src/app/journal/journal-spellcards/journal-spellcards.component.ts
  10. 3 1
      src/app/journal/journal-spellcards/spellcard/spellcard.component.html
  11. 2 6
      src/app/journal/journal-spellcards/spellcard/spellcard.component.scss
  12. 1 1
      src/app/journal/journal-stats/weapons-container/spell-table/spell-details/spell-details.component.html
  13. 1 1
      src/app/journal/journal-stats/weapons-container/spell-table/spell-table.component.html
  14. 3 3
      src/app/journal/journal-stats/weapons-container/spell-table/spell-table.component.ts
  15. 9 4
      src/app/journal/spell-modal/spell-modal.component.html
  16. 39 10
      src/app/journal/spell-modal/spell-modal.component.ts
  17. 10 4
      src/app/shared-components/full-spellcard/full-spellcard.component.html
  18. 4 0
      src/app/shared-components/full-spellcard/full-spellcard.component.ts
  19. 1 1
      src/assets/icons/UIIcons/add.svg
  20. 5 1
      src/interfaces/spell.ts
  21. 99 2
      src/services/data/data.service.ts
  22. 531 398
      src/services/spells/spells.service.ts

+ 72 - 0
.nx/cache/d/daemon.log

@@ -463402,3 +463402,75 @@ To fix this, set a unique name for each project in a project.json inside the pro
     at async processFilesAndCreateAndSerializeProjectGraph (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:138:17)
 [NX Daemon Server] - 2024-01-26T06:34:52.561Z - Time taken for 'hash changed files from watcher' 26.63969999551773ms
 [NX Daemon Server] - 2024-01-26T06:34:52.561Z - Done responding to the client null
+[NX Daemon Server] - 2024-01-27T08:03:11.547Z - Started listening on: \\.\pipe\nx\C:\Users\chris\AppData\Local\Temp\83d14e7134fc08a15480\d.sock
+[NX Daemon Server] - 2024-01-27T08:03:11.550Z - [WATCHER]: Subscribed to changes within: c:\Softwareprojekte\DnD (native)
+[NX Daemon Server] - 2024-01-27T08:03:11.558Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2024-01-27T08:03:11.561Z - Closed a connection. Number of open connections: 0
+[NX Daemon Server] - 2024-01-27T08:03:11.562Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2024-01-27T08:03:11.565Z - [REQUEST]: Client Request for Project Graph Received
+[NX Daemon Server] - 2024-01-27T08:03:12.486Z - Error detected when recomputing project file map: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+[NX Daemon Server] - 2024-01-27T08:03:12.486Z - [REQUEST]: Responding to the client with an error. Error when preparing serialized project graph. The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+Error: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+    at readProjectConfigurationsFromRootMap (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:97:15)
+    at buildProjectsConfigurationsFromProjectPathsAndPlugins (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:70:19)
+    at createProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:131:129)
+    at WorkspaceContext.<anonymous> (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:82:39)
+    at getProjectConfigurationsFromContext (c:\Softwareprojekte\DnD\node_modules\nx\src\utils\workspace-context.js:26:29)
+    at _retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:81:72)
+    at retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:58:12)
+    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
+    at async processCollectedUpdatedAndDeletedFiles (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:102:34)
+    at async processFilesAndCreateAndSerializeProjectGraph (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:138:17)
+[NX Daemon Server] - 2024-01-27T08:03:12.487Z - Time taken for 'hash changed files from watcher' 17.576200008392334ms
+[NX Daemon Server] - 2024-01-27T08:03:12.488Z - Done responding to the client null
+[NX Daemon Server] - 2024-01-27T15:44:47.258Z - Started listening on: \\.\pipe\nx\C:\Users\chris\AppData\Local\Temp\83d14e7134fc08a15480\d.sock
+[NX Daemon Server] - 2024-01-27T15:44:47.265Z - [WATCHER]: Subscribed to changes within: c:\Softwareprojekte\DnD (native)
+[NX Daemon Server] - 2024-01-27T15:44:47.267Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2024-01-27T15:44:47.268Z - Established a connection. Number of open connections: 2
+[NX Daemon Server] - 2024-01-27T15:44:47.269Z - Closed a connection. Number of open connections: 1
+[NX Daemon Server] - 2024-01-27T15:44:47.272Z - [REQUEST]: Client Request for Project Graph Received
+[NX Daemon Server] - 2024-01-27T15:44:48.283Z - Error detected when recomputing project file map: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+[NX Daemon Server] - 2024-01-27T15:44:48.284Z - [REQUEST]: Responding to the client with an error. Error when preparing serialized project graph. The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+Error: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+    at readProjectConfigurationsFromRootMap (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:97:15)
+    at buildProjectsConfigurationsFromProjectPathsAndPlugins (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:70:19)
+    at createProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:131:129)
+    at WorkspaceContext.<anonymous> (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:82:39)
+    at getProjectConfigurationsFromContext (c:\Softwareprojekte\DnD\node_modules\nx\src\utils\workspace-context.js:26:29)
+    at _retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:81:72)
+    at retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:58:12)
+    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
+    at async processCollectedUpdatedAndDeletedFiles (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:102:34)
+    at async processFilesAndCreateAndSerializeProjectGraph (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:138:17)
+[NX Daemon Server] - 2024-01-27T15:44:48.285Z - Time taken for 'hash changed files from watcher' 22.69249999523163ms
+[NX Daemon Server] - 2024-01-27T15:44:48.286Z - Done responding to the client null

+ 1 - 1
.nx/cache/d/server-process.json

@@ -1 +1 @@
-{"processId":1292}
+{"processId":22332}

+ 9 - 0
src/app/character/character-creator/character-creator.component.ts

@@ -349,6 +349,15 @@ export class CharacterCreatorComponent {
         'favoriteSpells'
       ),
       // Spells
+
+      this.dataAccessor.addData(
+        this.characterName,
+        {
+          spells: [],
+          id: 1000,
+        },
+        'customSpells'
+      ),
       this.dataAccessor.addData(
         this.characterName,
         {

+ 39 - 23
src/app/journal/journal-spellcards/add-card/add-card.component.html

@@ -1,38 +1,54 @@
 <div class="add-card">
-  @if(isModification == undefined) {
-  <icon
-    class="add"
-    [size]="'l'"
-    [type]="'UI'"
-    [icon]="'add'"
-    [class]="'pointer'"
-  ></icon>
+  @if(state === 1) {
+  <div class="clickable-background" (click)="state = 2">
+    <img class="add-icon" src="assets/icons/UIIcons/add.svg" />
+  </div>
+  } @else if(state === 2) {
   <button
-    class="slide-button add-card-button"
-    (click)="continueToSpellSelection(false)"
+    class="add-button"
+    (click)="continueToSpellSelection(false); state = 3"
   >
-    Offiziellen Zauber auswählen
+    Aus bekannten Zaubern wählen
   </button>
   <button
-    class="slide-button add-card-button"
-    (click)="continueToSpellSelection(true)"
+    class="add-button"
+    (click)="continueToSpellSelection(true); state = 3"
   >
-    Offiziellen Zauber bearbeiten
+    Einen bekannten Zauber bearbeiten
   </button>
-  <button class="slide-button add-card-button" (click)="emitNewSpell()">
-    Neuen Zauber erstellen
+  <button class="add-button" (click)="emitNewSpell()">
+    Einen neuen Zauber erstellen
   </button>
-  } @else {
+
+  <button class="abort-button" (click)="resetThis()">Abbrechen</button>
+  } @else if (state === 3) {
 
   <input
-    id="typeahead-basic"
     type="text"
-    class="form-control"
-    (selectItem)="spellSelected($event.item); $event.preventDefault()"
+    class="spell-name"
     [(ngModel)]="newSpellName"
-    [ngbTypeahead]="search"
-    placeholder="Name des Zaubers"
+    placeholder="Zauber durchsuchen"
+    (keyup)="
+      isModification ? filterSpellArrayForModification() : filterSpellArray()
+    "
   />
-  <button (click)="resetThis()">Abbrechen</button>
+  <div class="available-spells">
+    <ul>
+      @for(spell of availableSpells; track spell) {
+      <li>
+        <button
+          (click)="
+            isModification
+              ? emitNewSpellFromOfficial(spell)
+              : spellSelected(spell)
+          "
+        >
+          {{ spell.german }}
+        </button>
+      </li>
+      } @empty { Keine Zauber gefunden }
+    </ul>
+  </div>
+  <button class="abort-button" (click)="resetThis()">Abbrechen</button>
   }
 </div>

+ 72 - 30
src/app/journal/journal-spellcards/add-card/add-card.component.scss

@@ -1,51 +1,93 @@
 .add-card {
     position: relative;
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    justify-content: space-around;
-    height: 14rem;
-    width: 10rem;
-    border: solid 1px var(--border-color);
+    height: 20rem;
+    width: 15rem;
+    border: var(--border);
     border-radius: 10px;
-    // background-image: url("../../../../assets/images/spells/add-spell.jpg");
-    // background-size: auto 100%;
-    // background-position: center;
     box-shadow: var(--shadow);
     overflow: hidden;
     transition: all 0.3s ease-in-out;
 
-    &:hover {
-        .slide-button {
-            transform: translateX(0);
+    .clickable-background {
+        width: 100%;
+        height: 100%;
+        position: relative;
+        cursor: pointer;
+        transition: all 0.2s ease-in-out;
+
+        &:hover {
+            background-color: var(--items);
         }
-    }
 
-    .add {
-        position: absolute;
-        top: 50%;
-        left: 50%;
-        transform: translate(-50%, -50%);
+        .add-icon {
+            position: absolute;
+            top: 50%;
+            left: 50%;
+            transform: translate(-50%, -50%);
+            height: 4rem;
+        }
     }
 }
 
-.slide-button {
-    transform: translateX(-120%);
-    transition: transform 0.3s ease-in-out;
-}
-
-.add-card-button {
+.add-button {
+    width: 90%;
+    margin: 0 0.5rem;
     background-color: var(--primary-color);
-    border: 1px solid var(--border-color);
+    border: var(--border);
     border-radius: 10px;
     box-shadow: var(--shadow);
     font-weight: 600;
-    width: 90%;
-    margin: 0 0.5rem;
-    transition: all 0.3s ease-in-out;
+    // opacity: 0;
+    transition: all 0.25s ease-in-out;
 
     &:hover {
         background-color: var(--primary-color-dark);
-        scale: 1.03;
     }
 }
+
+.spell-name {
+    width: 94%;
+    margin: 1rem auto;
+    display: block;
+    border-radius: 5px;
+    border: var(--border);
+}
+
+.available-spells {
+    margin: 0.5rem;
+    padding: 0.5rem;
+    border: var(--border);
+    border-radius: 5px;
+    // box-shadow: var(--shadow);
+    overflow: auto;
+    height: 12rem;
+
+    button {
+        all: unset;
+        border: var(--border);
+        border-radius: 5px;
+        padding: 0.25rem;
+        margin: 0 auto 0.375rem auto;
+        display: block;
+        width: 90%;
+    }
+}
+
+ul {
+    list-style-type: none;
+    padding: 0;
+    margin: 0;
+}
+
+.abort-button {
+    width: 10rem;
+    height: 2.5rem;
+    display: block;
+    margin: 1rem auto 0 auto;
+    background-color: var(--delete);
+    border: var(--border);
+    border-radius: 10px;
+    box-shadow: var(--shadow);
+    font-weight: 600;
+    font-size: 1.25rem;
+}

+ 69 - 31
src/app/journal/journal-spellcards/add-card/add-card.component.ts

@@ -1,11 +1,7 @@
 import { Component, Input, Output, EventEmitter } from '@angular/core';
-import { NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap';
-import { Observable, OperatorFunction } from 'rxjs';
-import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators';
-import { FormsModule } from '@angular/forms';
-import { JsonPipe } from '@angular/common';
+import { DataService } from 'src/services/data/data.service';
+import { Spell } from 'src/interfaces/spell';
 import { SpellsService } from 'src/services/spells/spells.service';
-import { FormControl, ReactiveFormsModule } from '@angular/forms';
 
 @Component({
   selector: 'add-card',
@@ -14,18 +10,29 @@ import { FormControl, ReactiveFormsModule } from '@angular/forms';
 })
 export class AddCardComponent {
   @Input() level!: number;
+  @Input() alreadyUsedSpells!: any[];
 
   @Output() createNewSpell = new EventEmitter<any>();
+  @Output() createNewSpellFromOfficial = new EventEmitter<any>();
   @Output() onSpellSelected = new EventEmitter<any>();
 
   public newSpellName: string = '';
-  private availableSpells: string[] = [];
+  private allAvailableSpells: any[] = [];
+  public availableSpells: any[] = [];
   public isModification: boolean | undefined;
+  public state: number = 1;
 
-  public constructor(private spellsAccessor: SpellsService) {}
+  public constructor(
+    private spellsAccessor: SpellsService,
+    private dataAccessor: DataService
+  ) {}
 
   public ngOnInit(): void {
-    this.availableSpells = this.spellsAccessor.getListOfSpells(this.level);
+    this.allAvailableSpells = this.spellsAccessor.getAvailableSpells(
+      this.level,
+      this.dataAccessor.characterData.class
+    );
+    this.filterSpellArray();
     this.spellsAccessor.closeSubject$.subscribe((level) => {
       if (level !== this.level) {
         this.resetThis();
@@ -33,44 +40,75 @@ export class AddCardComponent {
     });
   }
 
-  // Enlarged view
-
   public emitNewSpell(): void {
+    console.warn('Emitting new spell to create: ', this.level);
     this.createNewSpell.emit(this.level);
     this.resetThis();
   }
 
+  public emitNewSpellFromOfficial(spell: Spell): void {
+    console.warn('Emitting new spell to modify from official: ', spell);
+
+    this.createNewSpellFromOfficial.emit(spell);
+    this.resetThis();
+  }
+
   public continueToSpellSelection(modify: boolean): void {
+    this.allAvailableSpells = this.spellsAccessor.getAvailableSpells(
+      this.level,
+      this.dataAccessor.characterData.class
+    );
+    this.filterSpellArray();
     this.isModification = modify;
+    if (modify) {
+      this.filterSpellArrayForModification();
+    } else {
+      this.filterSpellArray();
+    }
     this.spellsAccessor.closeAllOthers(this.level);
   }
 
-  public spellSelected(spellname: any): void {
-    const response = {
-      spell: this.spellsAccessor.getSpell(spellname),
-      isToModify: this.isModification,
-    };
-    this.onSpellSelected.emit(response);
+  public spellSelected(spell: any): void {
+    this.onSpellSelected.emit(spell);
     this.resetThis();
   }
 
   public resetThis(): void {
     this.newSpellName = '';
+    this.state = 1;
     this.isModification = undefined;
   }
 
-  public search: OperatorFunction<string, readonly string[]> = (
-    text$: Observable<string>
-  ) =>
-    text$.pipe(
-      debounceTime(200),
-      distinctUntilChanged(),
-      map((term) =>
-        term.length < 2
-          ? []
-          : this.availableSpells
-              .filter((v) => v.toLowerCase().indexOf(term.toLowerCase()) > -1)
-              .slice(0, 5)
-      )
-    );
+  public filterSpellArray(): void {
+    if (this.newSpellName.length >= 1) {
+      this.availableSpells = this.allAvailableSpells.filter(
+        (spell) =>
+          (spell.german
+            .toLowerCase()
+            .includes(this.newSpellName.toLowerCase()) ||
+            spell.english
+              .toLowerCase()
+              .includes(this.newSpellName.toLowerCase())) &&
+          !this.alreadyUsedSpells.includes(spell.id)
+      );
+    } else {
+      this.availableSpells = this.allAvailableSpells.filter(
+        (spell) => !this.alreadyUsedSpells.includes(spell.id)
+      );
+    }
+  }
+
+  public filterSpellArrayForModification(): void {
+    if (this.newSpellName.length >= 1) {
+      this.availableSpells = this.allAvailableSpells.filter(
+        (spell) =>
+          spell.german
+            .toLowerCase()
+            .includes(this.newSpellName.toLowerCase()) ||
+          spell.english.toLowerCase().includes(this.newSpellName.toLowerCase())
+      );
+    } else {
+      this.availableSpells = this.allAvailableSpells;
+    }
+  }
 }

+ 8 - 3
src/app/journal/journal-spellcards/journal-spellcards.component.html

@@ -1,6 +1,7 @@
 <div class="spellcards-container">
   <div cdkDropListGroup>
-    @for(level of [0,1,2,3,4,5,6,7,8,9]; track level; let index = $index) {
+    <!-- TODO: revert array to 0-9 -->
+    @for(level of [0,1]; track level; let index = $index) {
 
     <div class="example-container">
       <div
@@ -38,13 +39,17 @@
           (click)="showFullSpellcard(spell, level, spellIndex)"
         ></spellcard>
         } @if (draggingIndex === index){
-        <div class="deletion-card" [id]="'deletion' + index">
+        <div class="removal-card" [id]="'deletion' + index">
           Hier zum Löschen ablegen
         </div>
         } @else {
         <add-card
           [level]="index"
-          (createNewSpell)="openSpellModal(false, index)"
+          [alreadyUsedSpells]="getUsedIDs(index)"
+          (createNewSpell)="openSpellCreationModal(index, false)"
+          (createNewSpellFromOfficial)="
+            openSpellCreationModal(index, true, $event)
+          "
           (onSpellSelected)="handleSpellSelection($event, index)"
         ></add-card>
         }

+ 6 - 7
src/app/journal/journal-spellcards/journal-spellcards.component.scss

@@ -66,7 +66,7 @@
 
 .spell-list {
   border: solid 1px var(--border-color);
-  height: 16rem;
+  height: 22rem;
   background: white;
   border-radius: 0 10px 10px 10px;
   display: flex;
@@ -103,9 +103,9 @@
   }
 }
 
-.deletion-card {
-  height: 14rem;
-  width: 10rem;
+.removal-card {
+  height: 20rem;
+  width: 15rem;
   font-size: 1.25rem;
   font-weight: 600;
   border: solid 1px var(--border-color);
@@ -119,7 +119,7 @@
   box-shadow: var(--shadow);
 
   &:hover {
-    scale: 1.1;
+    scale: 1.05;
   }
 }
 
@@ -137,8 +137,7 @@
 .cdk-drag-placeholder {
   background: #e0e0e0;
   border: dotted 3px #999;
-  border-radius: 10px;
-  min-height: 60px;
+  border-radius: 100px;
   transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
   color: transparent !important;
 

+ 88 - 35
src/app/journal/journal-spellcards/journal-spellcards.component.ts

@@ -54,7 +54,8 @@ export class JournalSpellcardsComponent {
 
   public constructor(
     public dataAccessor: DataService,
-    private modalAccessor: ModalService
+    private modalAccessor: ModalService,
+    private spellsService: SpellsService
   ) {
     this.loadSpells();
     this.hideEmptySpelllists();
@@ -62,6 +63,14 @@ export class JournalSpellcardsComponent {
 
   ///////// FUNCTIONS //////////
 
+  public getUsedIDs(level: number): number[] {
+    const usedIDs: number[] = [];
+    this.getSpellList(level).forEach((spell) => {
+      usedIDs.push(spell.id);
+    });
+    return usedIDs;
+  }
+
   public showFullSpellcard(
     spell: Spell,
     level: number,
@@ -69,7 +78,7 @@ export class JournalSpellcardsComponent {
   ): void {
     const favorites = this.dataAccessor.favoriteSpells;
     const alreadyInFavorites = favorites.some(
-      (currentSpell) => currentSpell.name === spell.name
+      (currentSpell) => currentSpell.id === spell.id
     );
     this.modalAccessor.openModal(FullSpellcardComponent, {
       spell: spell,
@@ -80,80 +89,94 @@ export class JournalSpellcardsComponent {
       (result) => {
         resultSubscription.unsubscribe();
         if (result.state === 'delete') {
+          this.spellsService.deleteCustomSpell(spell);
+          this.dataAccessor.deleteCustomSpell(spell);
+          this.dataAccessor.removeFavoriteSpell(spell);
+          this.getSpellList(level).splice(spellIndex, 1);
+          this.updateSpellsInDatabase(level);
+        } else if (result.state === 'remove') {
+          this.dataAccessor.removeFavoriteSpell(spell);
           this.getSpellList(level).splice(spellIndex, 1);
           this.updateSpellsInDatabase(level);
         } else if (result.state === 'update') {
           setTimeout(() => {
-            this.openSpellModal(true, level, spellIndex);
+            this.openSpellModificationModal(level, spellIndex);
           }, 100);
         } else if (result.state === 'add') {
           this.dataAccessor.addFavoriteSpell(spell);
-        } else if (result.state !== 'cancel') {
-          throw new Error('Unexpected result state, please send a bug report.');
         }
       }
     );
   }
 
-  public openSpellModal(
-    isUpdate: boolean,
-    level: number,
-    spellIndex?: number
-  ): void {
+  // TODO: Update favorites, when modifying a spell
+  public openSpellModificationModal(level: number, index: number): void {
     this.modalAccessor.openModal(SpellModalComponent, {
       spell:
-        spellIndex !== undefined
-          ? JSON.parse(JSON.stringify(this.getSpellList(level)![spellIndex]))
+        index !== undefined
+          ? JSON.parse(JSON.stringify(this.getSpellList(level)![index]))
           : undefined,
-      isUpdate: isUpdate,
-      level: level,
+      isModification: true,
     });
     const resultSubscription = this.modalAccessor.result$.subscribe(
       (result) => {
         if (result.state === 'update') {
           // level was not modified
           if (level === result.data.level) {
-            this.updateSpell(result.data, level!, spellIndex!);
+            this.updateSpell(result.data, level, index);
+            this.dataAccessor.updateFavoriteSpell(result.data);
+            this.dataAccessor.updateCustomSpell(result.data);
           } else {
             // level was modified
-            this.getSpellList(level).splice(spellIndex!, 1);
+            this.getSpellList(level).splice(index, 1);
             this.addSpell(result.data, result.data.level);
           }
-        } else if (result.state === 'add') {
-          this.addSpell(result.data, level);
+        } else {
+          throw new Error('REsult state from modal: ' + result.state);
         }
         resultSubscription.unsubscribe();
       }
     );
   }
 
-  public openSpellModalModifyOfficial(spell: Spell): void {
+  /**
+   * In this modal new spells can be created. This can be completely new spells or
+   * modified official spells. If successful, the spell is added to the spell list,
+   * sent to the daService and sent to the spellsService which in return sends it to the dataBase
+   * @param level
+   * @param isBasedOnOfficialSpell
+   * @param spell
+   */
+  public openSpellCreationModal(
+    level: number,
+    isBasedOnOfficialSpell: boolean,
+    spell?: Spell
+  ): void {
     this.modalAccessor.openModal(SpellModalComponent, {
-      spell: spell,
-      isUpdate: true,
-      level: spell.level,
+      spell: isBasedOnOfficialSpell ? spell : undefined,
+      level: level,
+      id: this.dataAccessor.customSpellId,
+      isBasedOnOfficialSpell: isBasedOnOfficialSpell,
+      classes: [this.dataAccessor.characterData.class],
     });
     const resultSubscription = this.modalAccessor.result$.subscribe(
       (result) => {
-        if (result.state === 'update') {
-          this.addSpell(result.data, result.data.level);
+        if (result.state === 'add') {
+          console.warn('Add spell: ', result.data);
+
+          this.addSpell(result.data, level);
+          // this.spellsService.addCustomSpell(result.data);
+          this.dataAccessor.addCustomSpell(result.data);
         } else {
-          throw new Error('Unexpected result state, please send a bug report.');
+          console.warn('Result state from modal: ' + result.state);
         }
         resultSubscription.unsubscribe();
       }
     );
   }
 
-  public handleSpellSelection(event: any, level: number): void {
-    const newSpell = event.spell;
-    const isToModify = event.isToModify;
-
-    if (isToModify) {
-      this.openSpellModalModifyOfficial(newSpell);
-    } else {
-      this.addSpell(newSpell, level);
-    }
+  public handleSpellSelection(spell: Spell, level: number): void {
+    this.addSpell(spell, level);
   }
 
   public addSpell(spell: Spell, level: number) {
@@ -250,42 +273,72 @@ export class JournalSpellcardsComponent {
         case 'cdk-drop-list-0':
           movedSpell.level = 0;
           this.updateSpellsInDatabase(0);
+          if (movedSpell.isCustom) {
+            this.dataAccessor.updateCustomSpell(movedSpell);
+          }
           break;
         case 'cdk-drop-list-1':
           movedSpell.level = 1;
           this.updateSpellsInDatabase(1);
+          if (movedSpell.isCustom) {
+            this.dataAccessor.updateCustomSpell(movedSpell);
+          }
           break;
         case 'cdk-drop-list-2':
           movedSpell.level = 2;
           this.updateSpellsInDatabase(2);
+          if (movedSpell.isCustom) {
+            this.dataAccessor.updateCustomSpell(movedSpell);
+          }
           break;
         case 'cdk-drop-list-3':
           movedSpell.level = 3;
           this.updateSpellsInDatabase(3);
+          if (movedSpell.isCustom) {
+            this.dataAccessor.updateCustomSpell(movedSpell);
+          }
           break;
         case 'cdk-drop-list-4':
           movedSpell.level = 4;
           this.updateSpellsInDatabase(4);
+          if (movedSpell.isCustom) {
+            this.dataAccessor.updateCustomSpell(movedSpell);
+          }
           break;
         case 'cdk-drop-list-5':
           movedSpell.level = 5;
           this.updateSpellsInDatabase(5);
+          if (movedSpell.isCustom) {
+            this.dataAccessor.updateCustomSpell(movedSpell);
+          }
           break;
         case 'cdk-drop-list-6':
           movedSpell.level = 6;
           this.updateSpellsInDatabase(6);
+          if (movedSpell.isCustom) {
+            this.dataAccessor.updateCustomSpell(movedSpell);
+          }
           break;
         case 'cdk-drop-list-7':
           movedSpell.level = 7;
           this.updateSpellsInDatabase(7);
+          if (movedSpell.isCustom) {
+            this.dataAccessor.updateCustomSpell(movedSpell);
+          }
           break;
         case 'cdk-drop-list-8':
           movedSpell.level = 8;
           this.updateSpellsInDatabase(8);
+          if (movedSpell.isCustom) {
+            this.dataAccessor.updateCustomSpell(movedSpell);
+          }
           break;
         case 'cdk-drop-list-9':
           movedSpell.level = 9;
           this.updateSpellsInDatabase(9);
+          if (movedSpell.isCustom) {
+            this.dataAccessor.updateCustomSpell(movedSpell);
+          }
           break;
       }
     }
@@ -323,7 +376,7 @@ export class JournalSpellcardsComponent {
   }
 
   public dragEnd(event: any) {
-    if (event.event.target.classList.contains('deletion-card')) {
+    if (event.event.target.classList.contains('removal-card')) {
       this.getSpellList(this.draggingIndex!).splice(
         event.source.element.nativeElement.id,
         1

+ 3 - 1
src/app/journal/journal-spellcards/spellcard/spellcard.component.html

@@ -1,5 +1,7 @@
 <div class="spellcard" #spellcard>
-  <div class="name">{{ spell.name }}</div>
+  <div class="name">{{ spell.german }}</div>
   <div>{{ spell.cost }}</div>
   <div>{{ spell.school }}</div>
+  <div>ID: {{ spell.id }}</div>
+  <div>{{ spell.classes }}</div>
 </div>

+ 2 - 6
src/app/journal/journal-spellcards/spellcard/spellcard.component.scss

@@ -1,6 +1,6 @@
 .spellcard {
-    height: 14rem;
-    width: 10rem;
+    height: 20rem;
+    width: 15rem;
     border: solid 1px var(--border-color);
     border-radius: 10px;
     background: white;
@@ -8,10 +8,6 @@
     cursor: pointer;
     transition: all 0.3s ease-in-out;
 
-    // &:hover {
-    //     box-shadow: var(--shadow);
-    // }
-
     .name {
         font-weight: 600;
         font-size: 1.125rem;

+ 1 - 1
src/app/journal/journal-stats/weapons-container/spell-table/spell-details/spell-details.component.html

@@ -1,4 +1,4 @@
-<div class="details-title">{{ spell.name }}</div>
+<div class="details-title">{{ spell.german }}</div>
 
 <div class="details-scrollcontainer">
   <div class="details-subheading">Beschreibung</div>

+ 1 - 1
src/app/journal/journal-stats/weapons-container/spell-table/spell-table.component.html

@@ -102,7 +102,7 @@
 <!-- NAME -->
 <ng-template #spellNameTemplate let-spell="spell">
   <div>
-    <div class="bold">{{ spell.name }}</div>
+    <div class="bold">{{ spell.german }}</div>
     <div class="bold small">
       <span *ngIf="spell.cost === 'action'">A</span>
       <span *ngIf="spell.cost === 'bonus'">B</span>

+ 3 - 3
src/app/journal/journal-stats/weapons-container/spell-table/spell-table.component.ts

@@ -55,7 +55,7 @@ export class SpellTableComponent {
   public ngOnInit(): void {
     this.spells = this.dataAccessor.favoriteSpells;
     this.preparedSpells = this.dataAccessor.getAllPreparedSpells();
-    this.preparedSpellsNames = this.preparedSpells.map((spell) => spell.name);
+    this.preparedSpellsNames = this.preparedSpells.map((spell) => spell.german);
     this.subscribeToData();
   }
 
@@ -130,7 +130,7 @@ export class SpellTableComponent {
 
   public onSpellSelect(spellname: any): void {
     const newSpell = this.preparedSpells.filter(
-      (spell) => spell.name === spellname
+      (spell) => spell.german === spellname
     );
     if (newSpell.length !== 1) {
       throw new Error('Spell not found.');
@@ -208,7 +208,7 @@ export class SpellTableComponent {
               .filter(
                 (v) =>
                   !this.spells.some(
-                    (spell) => spell.name.toLowerCase() === v.toLowerCase()
+                    (spell) => spell.german.toLowerCase() === v.toLowerCase()
                   )
               )
               .slice(0, 5)

+ 9 - 4
src/app/journal/spell-modal/spell-modal.component.html

@@ -1,12 +1,17 @@
 <div class="modal-dimensions">
   <h2 style="text-align: center">
-    @if(isUpdate){Zauber anpassen}@else{Zauber hinzufügen}
+    @if(isModification){Zauber bearbeiten}@else{Zauber erstellen}
   </h2>
 
   <div class="add-form-group">
     <div class="input-element">
       <label for="weaponName">Name</label>
-      <input type="text" class="add-input" id="weaponName" [(ngModel)]="name" />
+      <input
+        type="text"
+        class="add-input"
+        id="weaponName"
+        [(ngModel)]="german"
+      />
     </div>
     <div class="form-element-row">
       <!-- general -->
@@ -140,7 +145,7 @@
 
     <!-- Button section -->
     <div class="button-wrapper-2-block">
-      @if(isUpdate){
+      @if(isModification){
       <ui-button
         [type]="'update'"
         [size]="'xlarge'"
@@ -149,7 +154,7 @@
       ></ui-button>
       }@else{
       <ui-button
-        *ngIf="!isUpdate"
+        *ngIf="!isModification"
         [type]="'add'"
         [size]="'xlarge'"
         [color]="'primary'"

+ 39 - 10
src/app/journal/spell-modal/spell-modal.component.ts

@@ -12,11 +12,15 @@ import { Spell } from 'src/interfaces/spell';
 export class SpellModalComponent {
   public constructor(private modalAccessor: ModalService) {}
   @Input() public spell: any;
-  @Input() public isUpdate: boolean = false;
   @Input() public level: number = 0;
-
-  public name: string = '';
-  // public level: number = 0;
+  @Input() classes: string[] = [];
+  @Input() public id: number = 0;
+  @Input() public isModification: boolean = false;
+  @Input() public isBasedOnOfficialSpell: boolean = false;
+
+  // #region Properties
+  public german: string = '';
+  public english: string = '';
   public cost: string = 'action';
   public canRitual: boolean = false;
   public needsConcentration: boolean = false;
@@ -43,7 +47,9 @@ export class SpellModalComponent {
   public radius: number = 0;
   public areaOfEffectType: string = '';
 
-  // Options for the select boxes
+  // #endregion
+
+  // #region OPTIONS
   public areaTypes: any[] = [
     { display: 'Kegel', value: 'cone' },
     { display: 'Kugel', value: 'sphere' },
@@ -110,13 +116,15 @@ export class SpellModalComponent {
     { display: 'Reaktion', value: 'reaction' },
   ];
 
+  // #endregion
+
   public ngOnInit(): void {
-    if (this.isUpdate) {
+    if (this.isModification || this.isBasedOnOfficialSpell) {
       this.loadspell();
     }
   }
 
-  // RESPONSES
+  // #region RESPONSES
 
   public cancel(): void {
     this.modalAccessor.handleModalClosing('cancel', undefined);
@@ -133,8 +141,20 @@ export class SpellModalComponent {
     this.resetSpell();
   }
 
+  // #endregion
+
+  // #region FUNCTIONS
+
   private loadspell(): void {
-    this.name = this.spell.name;
+    if (this.isModification) {
+      this.id = this.spell.id;
+      this.german = this.spell.german;
+      this.classes = this.spell.classes;
+    } else {
+      this.german = this.spell.german + ' (Kopie)';
+    }
+
+    this.english = this.spell.english;
     this.level = this.spell.level;
     this.cost = this.spell.cost;
     this.canRitual = this.spell.canRitual;
@@ -161,7 +181,11 @@ export class SpellModalComponent {
 
   private createSpell(): Spell {
     const spell: Spell = {
-      name: this.name,
+      id: this.id,
+      isCustom: true,
+      english: this.english,
+      german: this.german,
+      classes: this.classes,
       duration: 0, // FIXME: only mocked
       damage: this.damage,
       heal: this.heal,
@@ -189,7 +213,10 @@ export class SpellModalComponent {
   }
 
   private resetSpell(): void {
-    this.name = '';
+    this.id = 0;
+    this.german = '';
+    this.english = '';
+    this.classes = [];
     this.level = 0;
     this.cost = 'action';
     this.canRitual = false;
@@ -221,4 +248,6 @@ export class SpellModalComponent {
   public removeDamage(index: number): void {
     this.damage.splice(index, 1);
   }
+
+  // #endregion
 }

+ 10 - 4
src/app/shared-components/full-spellcard/full-spellcard.component.html

@@ -1,6 +1,6 @@
 <div class="full-spellcard-container">
   <div class="full-spellcard">
-    <h2 style="text-align: center; margin-top: 1rem">{{ spell.name }}</h2>
+    <h2 style="text-align: center; margin-top: 1rem">{{ spell.german }}</h2>
 
     <table class="table table-striped" style="height: 20rem; overflow: auto">
       <thead>
@@ -113,7 +113,11 @@
   </div>
 
   <div class="delete-row">
-    @if(!isFromDashboard){
+    @if(isFromDashboard){
+    <button class="delete-button" (click)="delete()">Entfernen</button>
+    }@else {
+
+    <!-- Add to favorites -->
     <button
       [class]="alreadyInFavorites ? 'disabled add-button' : 'add-button'"
       (click)="alreadyInFavorites ? '' : addToFavorites()"
@@ -121,10 +125,12 @@
       @if(alreadyInFavorites){ Bereits in Favoriten} @else{ Zu Favoriten
       hinzufügen }
     </button>
+    <!-- Modify spell (only available for custom spells) -->
+    @if(spell.isCustom){
     <button class="edit-button" (click)="update()">Anpassen</button>
+    }
+    <button class="delete-button" (click)="remove()">Entfernen</button>
     <button class="delete-button" (click)="delete()">Löschen</button>
-    } @else {
-    <button class="delete-button" (click)="delete()">Entfernen</button>
     }
   </div>
 </div>

+ 4 - 0
src/app/shared-components/full-spellcard/full-spellcard.component.ts

@@ -18,6 +18,10 @@ export class FullSpellcardComponent {
     this.modalAccessor.handleModalClosing('delete', undefined);
   }
 
+  public remove(): void {
+    this.modalAccessor.handleModalClosing('remove', undefined);
+  }
+
   public update(): void {
     this.modalAccessor.handleModalClosing('update', undefined);
   }

+ 1 - 1
src/assets/icons/UIIcons/add.svg

@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M256 80c0-17.7-14.3-32-32-32s-32 14.3-32 32V224H48c-17.7 0-32 14.3-32 32s14.3 32 32 32H192V432c0 17.7 14.3 32 32 32s32-14.3 32-32V288H400c17.7 0 32-14.3 32-32s-14.3-32-32-32H256V80z"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M440-440H200v-80h240v-240h80v240h240v80H520v240h-80v-240Z"/></svg>

+ 5 - 1
src/interfaces/spell.ts

@@ -1,5 +1,9 @@
 export interface Spell {
-  name: string;
+  id: number;
+  isCustom: boolean;
+  german: string;
+  english: string;
+  classes: string[];
   level: number;
   cost: string;
   duration: number;

+ 99 - 2
src/services/data/data.service.ts

@@ -9,6 +9,7 @@ import { Ability } from 'src/interfaces/ability';
 import { Trait } from 'src/interfaces/traits';
 import { SimpleItem } from 'src/interfaces/simple-item';
 import { Food } from 'src/interfaces/food';
+import { SpellsService } from '../spells/spells.service';
 
 @Injectable({
   providedIn: 'root',
@@ -19,9 +20,9 @@ export class DataService {
   public dataLoaded = false;
   public characterName: string = '';
 
-  constructor() {
+  constructor(private spellsService: SpellsService) {
     this.db = new Localbase('DNDTools');
-    // this.db.config.debug = false;
+    this.db.config.debug = false;
   }
 
   async loadData(): Promise<any> {
@@ -44,6 +45,7 @@ export class DataService {
       characterData,
       combatStatsData,
       consumablesData,
+      customSpellsData,
       favoriteSpellsData,
       favoriteWeaponsData,
       foodData,
@@ -101,6 +103,9 @@ export class DataService {
     this.inspiration = combatStatsData.inspiration;
 
     // Spells
+
+    this.customSpellId = customSpellsData.id;
+    this.customSpells = customSpellsData.spells;
     this.spellslots = spellslotsData;
     this.favoriteSpells = favoriteSpellsData.spells;
     this.spellLevel0 = spellLevel0.spells;
@@ -114,6 +119,10 @@ export class DataService {
     this.spellLevel8 = spellLevel8.spells;
     this.spellLevel9 = spellLevel9.spells;
 
+    // Upon initialization of the data, the custom spells need to be added to the spellsService
+
+    this.spellsService.customSpells = this.customSpells;
+
     // Items
     this.favoriteWeapons = favoriteWeaponsData.data;
     this.weaponsAndArmor = weaponsAndArmorData.data;
@@ -184,6 +193,94 @@ export class DataService {
     this.setData('favoriteSpells', { spells: this._favoriteSpells });
   }
 
+  /**
+   * Checks if the given spell is in the favorite spells array.
+   * If so, it is matched by the id and updated.
+   * @param spell The spell to be updated.
+   */
+  public updateFavoriteSpell(spell: Spell): void {
+    const index = this._favoriteSpells.findIndex(
+      (favorite) => favorite.id === spell.id
+    );
+    if (index > -1) {
+      this._favoriteSpells[index] = spell;
+      this.setData('favoriteSpells', { spells: this._favoriteSpells });
+    }
+  }
+
+  public removeFavoriteSpell(spell: Spell): void {
+    const index = this._favoriteSpells.indexOf(spell);
+    this._favoriteSpells.splice(index, 1);
+    this.setData('favoriteSpells', { spells: this._favoriteSpells });
+  }
+
+  private _customSpells: Spell[] = [];
+
+  public get customSpells(): Spell[] {
+    return this._customSpells;
+  }
+
+  public set customSpells(spells: Spell[]) {
+    this._customSpells = spells;
+    this.setData('customSpells', { spells: spells, id: this.customSpellId });
+  }
+
+  /**
+   * Adds a custom spell to the data service and uploads the resulting array to the database.
+   * Additionally, the custom spell ID is incremented by one.
+   * The custom spell array in the spells service is also updated.
+   * @param spell The spell to be added.
+   */
+  public addCustomSpell(spell: Spell): void {
+    this._customSpells.push(spell);
+    this._customSpellId++;
+    this.spellsService.customSpells = this._customSpells;
+    this.setData('customSpells', {
+      spells: this._customSpells,
+      id: this.customSpellId,
+    });
+  }
+
+  /**
+   * Updates a custom spell in the data service and uploads the resulting array to the database.
+   * Moreover, the custom spell array in the spells service is also updated.
+   * @param spell The spell to be updated.
+   */
+  public updateCustomSpell(spell: Spell): void {
+    const index = this._customSpells.findIndex(
+      (customSpell) => customSpell.id === spell.id
+    );
+    this._customSpells[index] = spell;
+    this.spellsService.customSpells = this._customSpells;
+    this.setData('customSpells', {
+      spells: this._customSpells,
+      id: this.customSpellId,
+    });
+  }
+
+  /**
+   * Deletes a custom spell from the data service and uploads the resulting array to the database.
+   * @param spell The spell to be deleted.
+   */
+  public deleteCustomSpell(spell: Spell): void {
+    const index = this._customSpells.indexOf(spell);
+    this._customSpells.splice(index, 1);
+    this.setData('customSpells', {
+      spells: this._customSpells,
+      id: this.customSpellId,
+    });
+  }
+
+  private _customSpellId: number = 100;
+
+  public get customSpellId(): number {
+    return this._customSpellId;
+  }
+
+  private set customSpellId(id: number) {
+    this._customSpellId = id;
+  }
+
   private _spellLevel0: Spell[] = [];
 
   public get spellLevel0(): Spell[] {

+ 531 - 398
src/services/spells/spells.service.ts

@@ -12,413 +12,546 @@ export class SpellsService {
 
   public closeSubject$ = this._closeSubject.asObservable();
 
+  constructor() {}
+
   public closeAllOthers(level: number): void {
     this._closeSubject.next(level);
   }
 
-  // Spells
-  private level0: string[] = [
-    'Führung (Guidance)',
-    'Totenläuten (Toll the Dead)',
-    'Thaumaturgie (Thaumaturgy)',
-  ];
+  // Custom Spells
 
-  private level1: string[] = [
-    'Wunden Heilen (Cure Wounds)',
-    'Wunden hinzufügen (Inflict Wounds)',
-    'Magie entdecken (Detect Magic)',
-    'Gift und Krankheiten entdecken (Detect Poison and Disease)',
-    'Segnen (Bless)',
-    'Heilendes Wort (Healing Word)',
-    'Lenkendes Geschoss (Guiding Bolt)',
-    'Heldenmut (Heroism)',
-    'Heiligtum (Sanctuary)',
-  ];
+  public customSpells: Spell[] = [];
 
-  private level2: string[] = [];
-
-  private level3: string[] = [];
-
-  private level4: string[] = [];
-
-  private level5: string[] = [];
-
-  private level6: string[] = [];
-
-  private level7: string[] = [];
-
-  private level8: string[] = [];
-
-  private level9: string[] = [];
-
-  public getListOfSpells(level: number) {
-    switch (level) {
-      case 0:
-        return this.level0;
-      case 1:
-        return this.level1;
-      case 2:
-        return this.level2;
-      case 3:
-        return this.level3;
-      case 4:
-        return this.level4;
-      case 5:
-        return this.level5;
-      case 6:
-        return this.level6;
-      case 7:
-        return this.level7;
-      case 8:
-        return this.level8;
-      case 9:
-        return this.level9;
-      default:
-        return this.level0;
-    }
+  /**
+   * Returns an array of available spells for the given level and character class.
+   * The results come from the official apells array and the custom spells array.
+   * @param level The level of the spell.
+   * @param characterClass The character class of the spell.
+   * @returns An array of available spells.
+   */
+  public getAvailableSpells(level: number, characterClass: string): Spell[] {
+    let result = this.spells
+      .filter((spell) => spell.level === level)
+      .filter((spell) => spell.classes.includes(characterClass));
+    result.push(...this.customSpells.filter((spell) => spell.level === level));
+    return result;
   }
 
-  public getSpell(name: string): Spell {
-    switch (name) {
-      case 'Führung (Guidance)':
-        return this.guidance;
-      case 'Totenläuten (Toll the Dead)':
-        return this.tollTheDead;
-      case 'Thaumaturgie (Thaumaturgy)':
-        return this.thaumaturgie;
-      case 'Wunden Heilen (Cure Wounds)':
-        return this.cureWounds;
-      case 'Wunden hinzufügen (Inflict Wounds)':
-        return this.inflictWounds;
-      case 'Magie entdecken (Detect Magic)':
-        return this.detectMagic;
-      case 'Segnen (Bless)':
-        return this.bless;
-      case 'Heilendes Wort (Healing Word)':
-        return this.healingWord;
-      case 'Lenkendes Geschoss (Guiding Bolt)':
-        return this.guidingBolt;
-      case 'Gift und Krankheiten entdecken (Detect Poison and Disease)':
-        return this.detectPoisonAndDisease;
-      case 'Heldenmut (Heroism)':
-        return this.heroism;
-      case 'Heiligtum (Sanctuary)':
-        return this.santcuary;
-      default:
-        return this.guidance;
+  /**
+   * Deletets a single spell from the custom spells array and the full spells array.
+   * @param spellToDelete The spell to be deleted from the custom spells.
+   */
+  public deleteCustomSpell(spellToDelete: Spell): void {
+    const customIndex = this.customSpells.findIndex(
+      (spell) => spell.id === spellToDelete.id
+    );
+    if (customIndex > -1) {
+      this.customSpells.splice(customIndex, 1);
+    }
+    const fullIndex = this.spells.findIndex(
+      (spell) => spell.id === spellToDelete.id
+    );
+    if (fullIndex > -1) {
+      this.spells.splice(fullIndex, 1);
     }
   }
 
-  private inflictWounds: Spell = {
-    name: 'Wunden hinzufügen',
-    level: 1,
-    cost: 'action',
-    duration: 0,
-    canRitual: false,
-    needsVerbal: true,
-    needsSomatic: true,
-    needsMaterial: false,
-    needsConcentration: false,
-    needsAttackRoll: true,
-    needsSavingThrow: false,
-    doesDamage: true,
-    damage: [{ diceNumber: '3', diceType: 'd10', damageType: 'necrotic' }],
-    doesHeal: false,
-    heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
-    description:
-      'Mach einen Nahkampf-Zauberangriff gegen eine Kreatur deiner Wahl. Bei einem Erfolg erleidet das Ziel 3W10 Nekrotischen Schaden. Auf höheren Graden: Wirkst du diesen Zauber, indem du einen Zauberplatz des 2. Grades oder höher nutzt, steigt der Schaden für jeden Grad über dem 1. um 1W10.',
-    school: 'necromancy',
-    isRanged: false,
-    range: 5,
-    hasAreaOfEffect: false,
-    areaOfEffectType: '',
-    radius: 0,
-  };
-
-  private guidance: Spell = {
-    name: 'Göttliche Führung',
-    level: 0,
-    cost: 'action',
-    duration: 10,
-    canRitual: false,
-    needsVerbal: true,
-    needsSomatic: true,
-    needsMaterial: false,
-    needsConcentration: true,
-    needsAttackRoll: false,
-    needsSavingThrow: false,
-    doesDamage: false,
-    damage: [{ diceNumber: '', diceType: '', damageType: '' }],
-    doesHeal: false,
-    heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
-    description:
-      'Du berührst eine bereitwillige Kreatur. Einmal vor dem Ende des Zaubers kann das Ziel mit einem W4 würfeln und das Ergebnis zu einem Attributswurf seiner Wahl addieren. Es kann mit dem W4 vor oder nach dem Attributswurf würfeln. Dann endet der Zauber.',
-    school: 'divination',
-    isRanged: false,
-    range: 5,
-    hasAreaOfEffect: false,
-    areaOfEffectType: '',
-    radius: 0,
-  };
-
-  private tollTheDead: Spell = {
-    name: 'Totenläuten',
-    level: 0,
-    cost: 'action',
-    duration: 0,
-    canRitual: false,
-    needsVerbal: true,
-    needsSomatic: true,
-    needsMaterial: false,
-    needsConcentration: false,
-    needsAttackRoll: false,
-    needsSavingThrow: true,
-    savingThrowAttribute: 'wisdom',
-    doesDamage: true,
-    damage: [{ diceNumber: '1', diceType: 'd8', damageType: 'necrotic' }],
-    doesHeal: false,
-    heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
-    description:
-      'Du zeigst auf eine Kreatur innehalb von 18 Meter, die du sehen kannst und der Klang schmerzhafter Glocken füllt die Luft um sie herum für einen Moment. Das Ziel muss einen Weisheitsrettungswurf machen oder 1W8 Nekrotischen Schaden erleiden. Wenn das Ziel bereits Schaden erlitten hat, erhöht sich der Schaden auf 1W12.',
-    school: 'necromancy',
-    isRanged: true,
-    range: 60,
-    hasAreaOfEffect: false,
-    areaOfEffectType: '',
-    radius: 0,
-  };
-
-  private thaumaturgie: Spell = {
-    name: 'Thaumaturgie',
-    level: 0,
-    cost: 'action',
-    duration: 10,
-    canRitual: false,
-    needsVerbal: true,
-    needsSomatic: false,
-    needsMaterial: false,
-    needsConcentration: false,
-    needsAttackRoll: false,
-    needsSavingThrow: false,
-    doesDamage: false,
-    damage: [{ diceNumber: '', diceType: '', damageType: '' }],
-    doesHeal: false,
-    heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
-    description:
-      'Du erzeugst einen der folgenden magischen Effekte innerhalb von 9 Metern: \b\b • Deine Stimme wird dreimal lauter.\b • Du lässt einen unverschlossenen Tür oder ein Fenster aufspringen. \b• Du lässt eine Flamme aufleuchten, erlöschen oder ihre Farbe ändern. \b• Du lässt den Boden für eine Minute beben.\b • Du erzeugst Geräusche, die von dir ausgehen, wie das Flüstern einer Stimme, das Brüllen eines Löwen, das Knurren von Donner oder das Klappern von Metall. \b• Du lässt eine Tür oder ein Fenster, das du berührst, für eine Minute verriegeln oder entriegeln.',
-    school: 'transmutation',
-    isRanged: true,
-    range: 30,
-    hasAreaOfEffect: false,
-    areaOfEffectType: '',
-    radius: 0,
-  };
-
-  private cureWounds: Spell = {
-    name: 'Heilende Berührung',
-    level: 1,
-    cost: 'action',
-    duration: 0,
-    canRitual: false,
-    needsVerbal: true,
-    needsSomatic: true,
-    needsMaterial: false,
-    needsConcentration: false,
-    needsAttackRoll: false,
-    needsSavingThrow: false,
-    doesDamage: false,
-    damage: [{ diceNumber: '', diceType: '', damageType: '' }],
-    doesHeal: true,
-    heal: { diceNumber: '1', diceType: 'd8', additionalHeal: 4 },
-    description:
-      'Eine Kreatur, die du berührst, gewinnt Trefferpunkte in Höhe von 1W8 + deinem Attributsmodifikator im Zauberwirken zurück. Dieser Zauber wirkt nicht auf Untote oder Konstrukte. Auf höheren Graden: Wirkst du diesen Zauber, indem du einen Zauberplatz des 2. Grades oder höher nutzt, steigt die Heilung für jeden Grad über dem 1. um 1W8.',
-    school: 'evocation',
-    isRanged: false,
-    range: 5,
-    hasAreaOfEffect: false,
-    areaOfEffectType: '',
-    radius: 0,
-  };
-
-  private detectMagic: Spell = {
-    name: 'Magie entdecken',
-    level: 1,
-    cost: 'action',
-    duration: 100,
-    canRitual: true,
-    needsVerbal: true,
-    needsSomatic: true,
-    needsMaterial: false,
-    needsConcentration: false,
-    needsAttackRoll: false,
-    needsSavingThrow: false,
-    doesDamage: false,
-    damage: [{ diceNumber: '', diceType: '', damageType: '' }],
-    doesHeal: false,
-    heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
-    description:
-      'Während der Wirkungsdauer nimmst du die Gegenwart von Magie im Abstand von bis zu neun Metern von dir wahr. Wenn du auf diese Weise Magie wahrnimmst, kannst du deine Aktion verwenden, um schwache Auren um sichtbare magische Kreaturen oder Objekte sowie ihre magische Schule zu erkennen, falls vorhanden. Dieser Zauber durchdringt die meisten Barrieren, wird aber von 30 Zentimetern Stein, 2,5 Zentimetern gewöhnlichem Metall, dünnem Bleiblech sowie von einem Meter Holz oder Erde blockiert.',
-    school: 'divination',
-    isRanged: true,
-    range: 30,
-    hasAreaOfEffect: false,
-    areaOfEffectType: '',
-    radius: 0,
-  };
-
-  private bless: Spell = {
-    name: 'Segnen',
-    level: 1,
-    cost: 'action',
-    duration: 10,
-    canRitual: false,
-    needsVerbal: true,
-    needsSomatic: true,
-    needsMaterial: true,
-    needsConcentration: true,
-    needsAttackRoll: false,
-    needsSavingThrow: false,
-    doesDamage: false,
-    damage: [{ diceNumber: '', diceType: '', damageType: '' }],
-    doesHeal: false,
-    heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
-    description:
-      'Du segnest bis zu drei Kreaturen deiner Wahl in Reichweite. Wenn ein Ziel während der Wirkungsdauer einen Angriffs- oder Rettungswurf ausführt, darf es mit einem W4 würfeln und das Würfelergebnis zu seinem Angriffs- oder Rettungswurf addieren. Auf höheren Graden: Wirkst du diesen Zauber, indem du einen Zauberplatz des 2. Grades oder höher nutzt, kannst du für jeden Grad über dem 1. eine zusätzliche Kreatur als Ziel wählen.',
-    school: 'enchantment',
-    isRanged: true,
-    range: 60,
-    hasAreaOfEffect: false,
-    areaOfEffectType: '',
-    radius: 0,
-  };
-
-  private healingWord: Spell = {
-    name: 'Heilendes Wort',
-    level: 1,
-    cost: 'bonus action',
-    duration: 0,
-    canRitual: false,
-    needsVerbal: true,
-    needsSomatic: false,
-    needsMaterial: false,
-    needsConcentration: false,
-    needsAttackRoll: false,
-    needsSavingThrow: false,
-    doesDamage: false,
-    damage: [{ diceNumber: '', diceType: '', damageType: '' }],
-    doesHeal: true,
-    heal: { diceNumber: '1', diceType: 'd4', additionalHeal: 4 },
-    description:
-      'Eine Kreatur deiner Wahl in Reichweite, die du sehen kannst, gewinnt Trefferpunkte in Höhe von 1W4 + deinem Zauberwirken-Attributsmodifikator zurück. Dieser Zauber wirkt nicht auf Untote oder Konstrukte. Auf höheren Graden: Wirkst du diesen Zauber, indem du einen Zauberplatz des 2. Grades oder höher nutzt, steigt die Heilung für jeden Grad über dem 1. um 1W4.',
-    school: 'evocation',
-    isRanged: true,
-    range: 60,
-    hasAreaOfEffect: false,
-    areaOfEffectType: '',
-    radius: 0,
-  };
-
-  private guidingBolt: Spell = {
-    name: 'Lenkendes Geschoss',
-    level: 1,
-    cost: 'action',
-    duration: 0,
-    canRitual: false,
-    needsVerbal: true,
-    needsSomatic: true,
-    needsMaterial: false,
-    needsConcentration: false,
-    needsAttackRoll: true,
-    needsSavingThrow: false,
-    doesDamage: true,
-    damage: [{ diceNumber: '4', diceType: 'd6', damageType: 'radiant' }],
-    doesHeal: false,
-    heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
-    description:
-      'Ein Lichtblitz schießt auf eine Kreatur deiner Wahl in Reichweite zu. Führe einen Fernkampf-Zauberangriff gegen das Ziel aus. Bei einem Erfolg erleidet das Ziel 4W6 gleißenden Schaden. Dank des mystischen dämmrigen Lichts, das auf dem Ziel glitzert, ist der nächste Angriffswurf gegen das Ziel vor Ende deines nächsten Zugs im Vorteil. Auf höheren Graden: Wirkst du diesen Zauber, indem du einen Zauberplatz des 2. Grades oder höher nutzt, steigt der Schaden für jeden Grad über dem 1. um 1W6.',
-    school: 'evocation',
-    isRanged: true,
-    range: 120,
-    hasAreaOfEffect: false,
-    areaOfEffectType: '',
-    radius: 0,
-  };
-
-  private detectPoisonAndDisease: Spell = {
-    name: 'Gift und Krankheiten entdecken',
-    level: 1,
-    cost: 'action',
-    duration: 100,
-    canRitual: true,
-    needsVerbal: true,
-    needsSomatic: true,
-    needsMaterial: true,
-    needsConcentration: true,
-    needsAttackRoll: false,
-    needsSavingThrow: false,
-    doesDamage: false,
-    damage: [{ diceNumber: '', diceType: '', damageType: '' }],
-    doesHeal: false,
-    heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
-    description:
-      'Während der Wirkungsdauer nimmst du die Gegenwart und die Position von Giften, giftigen Kreaturen und Krankheiten im Abstand von bis zu neun Metern von dir wahr. Du kannst auch die Art des Gifts, der giftigen Kreatur oder der Krankheit bestimmen. Dieser Zauber durchdringt die meisten Barrieren, wird aber von 30 Zentimetern Stein, 2,5 Zentimetern gewöhnlichem Metall, dünnem Bleiblech sowie von einem Meter Holz oder Erde blockiert.',
-    school: 'divination',
-    isRanged: true,
-    range: 30,
-    hasAreaOfEffect: false,
-    areaOfEffectType: '',
-    radius: 0,
-  };
-
-  private heroism: Spell = {
-    name: 'Heldenmut',
-    level: 1,
-    cost: 'action',
-    duration: 10,
-    canRitual: false,
-    needsVerbal: true,
-    needsSomatic: true,
-    needsMaterial: false,
-    needsConcentration: true,
-    needsAttackRoll: false,
-    needsSavingThrow: false,
-    doesDamage: false,
-    damage: [{ diceNumber: '', diceType: '', damageType: '' }],
-    doesHeal: false,
-    heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
-    description:
-      'Du berührst eine bereitwillige Kreatur und erfüllst sie mit Tapferkeit. Bis der Zauber endet, kann die Kreatur nicht verängstigt werden, und sie erhält zu Beginn jedes ihrer Züge temporäre Trefferpunkte in Höhe deines Zauberwirken-Attributsmodifikators. Wenn der Zauber endet, verliert das Ziel alle verbleibenden temporären Trefferpunkte dieses Zaubers. Auf höheren Graden: Wirkst du diesen Zauber, indem du einen Zauberplatz des 2. Grades oder höher nutzt, kannst du für jeden Grad über dem 1. eine zusätzliche Kreatur als Ziel wählen.',
-    school: 'enchantment',
-    isRanged: false,
-    range: 5,
-    hasAreaOfEffect: false,
-    areaOfEffectType: '',
-    radius: 0,
-  };
-
-  private santcuary: Spell = {
-    name: 'Heiligtum',
-    level: 1,
-    cost: 'bonus action',
-    duration: 10,
-    canRitual: false,
-    needsVerbal: true,
-    needsSomatic: true,
-    needsMaterial: true,
-    needsConcentration: true,
-    needsAttackRoll: false,
-    needsSavingThrow: false,
-    doesDamage: false,
-    damage: [{ diceNumber: '', diceType: '', damageType: '' }],
-    doesHeal: false,
-    heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
-    description:
-      'Du schützt eine Kreatur in Reichweite vor Angriffen. Während der Wirkungsdauer muss jede Kreatur, die bei einem Angriff oder schädlichen Zauber die geschützte Kreatur als Ziel hat, zuerst einen Weisheitsrettungswurf bestehen. Scheitert der Wurf, muss die Kreatur ein neues Ziel wählen, oder sie verliert den Angriff oder Zauber. Dieser Zauber schützt die entsprechende Kreatur nicht vor Flächeneffekten wie der Explosion eines Feuerballs. Greift die geschützte Kreatur an oder wirkt sie einen Zauber auf eine feindliche Kreatur, endet der Effekt dieses Zaubers.',
-    school: 'abjuration',
-    isRanged: true,
-    range: 60,
-    hasAreaOfEffect: false,
-    areaOfEffectType: '',
-    radius: 0,
-  };
+  //
+
+  private spells: Spell[] = [
+    {
+      id: 0,
+      isCustom: false,
+      german: 'Göttliche Führung',
+      english: 'Guidance',
+      classes: ['Cleric', 'Druid'],
+      level: 0,
+      cost: 'action',
+      duration: 10,
+      canRitual: false,
+      needsVerbal: true,
+      needsSomatic: true,
+      needsMaterial: false,
+      needsConcentration: true,
+      needsAttackRoll: false,
+      needsSavingThrow: false,
+      doesDamage: false,
+      damage: [{ diceNumber: '', diceType: '', damageType: '' }],
+      doesHeal: false,
+      heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
+      description:
+        'Du berührst eine bereitwillige Kreatur. Einmal vor dem Ende des Zaubers kann das Ziel mit einem W4 würfeln und das Ergebnis zu einem Attributswurf seiner Wahl addieren. Es kann mit dem W4 vor oder nach dem Attributswurf würfeln. Dann endet der Zauber.',
+      school: 'divination',
+      isRanged: false,
+      range: 5,
+      hasAreaOfEffect: false,
+      areaOfEffectType: '',
+      radius: 0,
+    },
+    {
+      id: 1,
+      isCustom: false,
+      german: 'Totenläuten',
+      english: 'Toll the Dead',
+      classes: ['Cleric', 'Warlock', 'Wizard'],
+      level: 0,
+      cost: 'action',
+      duration: 0,
+      canRitual: false,
+      needsVerbal: true,
+      needsSomatic: true,
+      needsMaterial: false,
+      needsConcentration: false,
+      needsAttackRoll: false,
+      needsSavingThrow: true,
+      savingThrowAttribute: 'wisdom',
+      doesDamage: true,
+      damage: [{ diceNumber: '1', diceType: 'd8', damageType: 'necrotic' }],
+      doesHeal: false,
+      heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
+      description:
+        'Du zeigst auf eine Kreatur innehalb von 18 Meter, die du sehen kannst und der Klang schmerzhafter Glocken füllt die Luft um sie herum für einen Moment. Das Ziel muss einen Weisheitsrettungswurf machen oder 1W8 Nekrotischen Schaden erleiden. Wenn das Ziel bereits Schaden erlitten hat, erhöht sich der Schaden auf 1W12.',
+      school: 'necromancy',
+      isRanged: true,
+      range: 60,
+      hasAreaOfEffect: false,
+      areaOfEffectType: '',
+      radius: 0,
+    },
+    {
+      id: 2,
+      isCustom: false,
+      german: 'Thaumaturgie',
+      english: 'Thaumaturgy',
+      classes: ['Cleric'],
+      level: 0,
+      cost: 'action',
+      duration: 10,
+      canRitual: false,
+      needsVerbal: true,
+      needsSomatic: false,
+      needsMaterial: false,
+      needsConcentration: false,
+      needsAttackRoll: false,
+      needsSavingThrow: false,
+      doesDamage: false,
+      damage: [{ diceNumber: '', diceType: '', damageType: '' }],
+      doesHeal: false,
+      heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
+      description:
+        'Du erzeugst einen der folgenden magischen Effekte innerhalb von 9 Metern: \b\b • Deine Stimme wird dreimal lauter.\b • Du lässt einen unverschlossenen Tür oder ein Fenster aufspringen. \b• Du lässt eine Flamme aufleuchten, erlöschen oder ihre Farbe ändern. \b• Du lässt den Boden für eine Minute beben.\b • Du erzeugst Geräusche, die von dir ausgehen, wie das Flüstern einer Stimme, das Brüllen eines Löwen, das Knurren von Donner oder das Klappern von Metall. \b• Du lässt eine Tür oder ein Fenster, das du berührst, für eine Minute verriegeln oder entriegeln.',
+      school: 'transmutation',
+      isRanged: true,
+      range: 30,
+      hasAreaOfEffect: false,
+      areaOfEffectType: '',
+      radius: 0,
+    },
+  ];
+
+  // // Spells
+  // private level0: string[] = [
+  //   'Führung (Guidance)',
+  //   'Totenläuten (Toll the Dead)',
+  //   'Thaumaturgie (Thaumaturgy)',
+  // ];
+
+  // private level1: string[] = [
+  //   'Wunden Heilen (Cure Wounds)',
+  //   'Wunden hinzufügen (Inflict Wounds)',
+  //   'Magie entdecken (Detect Magic)',
+  //   'Gift und Krankheiten entdecken (Detect Poison and Disease)',
+  //   'Segnen (Bless)',
+  //   'Heilendes Wort (Healing Word)',
+  //   'Lenkendes Geschoss (Guiding Bolt)',
+  //   'Heldenmut (Heroism)',
+  //   'Heiligtum (Sanctuary)',
+  // ];
+
+  // private level2: string[] = [];
+
+  // private level3: string[] = [];
+
+  // private level4: string[] = [];
+
+  // private level5: string[] = [];
+
+  // private level6: string[] = [];
+
+  // private level7: string[] = [];
+
+  // private level8: string[] = [];
+
+  // private level9: string[] = [];
+
+  // public getListOfSpells(level: number) {
+  //   switch (level) {
+  //     case 0:
+  //       return this.level0;
+  //     case 1:
+  //       return this.level1;
+  //     case 2:
+  //       return this.level2;
+  //     case 3:
+  //       return this.level3;
+  //     case 4:
+  //       return this.level4;
+  //     case 5:
+  //       return this.level5;
+  //     case 6:
+  //       return this.level6;
+  //     case 7:
+  //       return this.level7;
+  //     case 8:
+  //       return this.level8;
+  //     case 9:
+  //       return this.level9;
+  //     default:
+  //       return this.level0;
+  //   }
+  // }
+
+  // public getSpell(name: string): Spell {
+  //   switch (name) {
+  //     case 'Führung (Guidance)':
+  //       return this.guidance;
+  //     case 'Totenläuten (Toll the Dead)':
+  //       return this.tollTheDead;
+  //     case 'Thaumaturgie (Thaumaturgy)':
+  //       return this.thaumaturgie;
+  //     case 'Wunden Heilen (Cure Wounds)':
+  //       return this.cureWounds;
+  //     case 'Wunden hinzufügen (Inflict Wounds)':
+  //       return this.inflictWounds;
+  //     case 'Magie entdecken (Detect Magic)':
+  //       return this.detectMagic;
+  //     case 'Segnen (Bless)':
+  //       return this.bless;
+  //     case 'Heilendes Wort (Healing Word)':
+  //       return this.healingWord;
+  //     case 'Lenkendes Geschoss (Guiding Bolt)':
+  //       return this.guidingBolt;
+  //     case 'Gift und Krankheiten entdecken (Detect Poison and Disease)':
+  //       return this.detectPoisonAndDisease;
+  //     case 'Heldenmut (Heroism)':
+  //       return this.heroism;
+  //     case 'Heiligtum (Sanctuary)':
+  //       return this.santcuary;
+  //     default:
+  //       return this.guidance;
+  //   }
+  // }
+
+  // private inflictWounds: Spell = {
+  //   name: 'Wunden hinzufügen',
+  //   level: 1,
+  //   cost: 'action',
+  //   duration: 0,
+  //   canRitual: false,
+  //   needsVerbal: true,
+  //   needsSomatic: true,
+  //   needsMaterial: false,
+  //   needsConcentration: false,
+  //   needsAttackRoll: true,
+  //   needsSavingThrow: false,
+  //   doesDamage: true,
+  //   damage: [{ diceNumber: '3', diceType: 'd10', damageType: 'necrotic' }],
+  //   doesHeal: false,
+  //   heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
+  //   description:
+  //     'Mach einen Nahkampf-Zauberangriff gegen eine Kreatur deiner Wahl. Bei einem Erfolg erleidet das Ziel 3W10 Nekrotischen Schaden. Auf höheren Graden: Wirkst du diesen Zauber, indem du einen Zauberplatz des 2. Grades oder höher nutzt, steigt der Schaden für jeden Grad über dem 1. um 1W10.',
+  //   school: 'necromancy',
+  //   isRanged: false,
+  //   range: 5,
+  //   hasAreaOfEffect: false,
+  //   areaOfEffectType: '',
+  //   radius: 0,
+  // };
+
+  // private guidance: Spell = {
+  //   name: 'Göttliche Führung',
+  //   level: 0,
+  //   cost: 'action',
+  //   duration: 10,
+  //   canRitual: false,
+  //   needsVerbal: true,
+  //   needsSomatic: true,
+  //   needsMaterial: false,
+  //   needsConcentration: true,
+  //   needsAttackRoll: false,
+  //   needsSavingThrow: false,
+  //   doesDamage: false,
+  //   damage: [{ diceNumber: '', diceType: '', damageType: '' }],
+  //   doesHeal: false,
+  //   heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
+  //   description:
+  //     'Du berührst eine bereitwillige Kreatur. Einmal vor dem Ende des Zaubers kann das Ziel mit einem W4 würfeln und das Ergebnis zu einem Attributswurf seiner Wahl addieren. Es kann mit dem W4 vor oder nach dem Attributswurf würfeln. Dann endet der Zauber.',
+  //   school: 'divination',
+  //   isRanged: false,
+  //   range: 5,
+  //   hasAreaOfEffect: false,
+  //   areaOfEffectType: '',
+  //   radius: 0,
+  // };
+
+  // private tollTheDead: Spell = {
+  //   name: 'Totenläuten',
+  //   level: 0,
+  //   cost: 'action',
+  //   duration: 0,
+  //   canRitual: false,
+  //   needsVerbal: true,
+  //   needsSomatic: true,
+  //   needsMaterial: false,
+  //   needsConcentration: false,
+  //   needsAttackRoll: false,
+  //   needsSavingThrow: true,
+  //   savingThrowAttribute: 'wisdom',
+  //   doesDamage: true,
+  //   damage: [{ diceNumber: '1', diceType: 'd8', damageType: 'necrotic' }],
+  //   doesHeal: false,
+  //   heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
+  //   description:
+  //     'Du zeigst auf eine Kreatur innehalb von 18 Meter, die du sehen kannst und der Klang schmerzhafter Glocken füllt die Luft um sie herum für einen Moment. Das Ziel muss einen Weisheitsrettungswurf machen oder 1W8 Nekrotischen Schaden erleiden. Wenn das Ziel bereits Schaden erlitten hat, erhöht sich der Schaden auf 1W12.',
+  //   school: 'necromancy',
+  //   isRanged: true,
+  //   range: 60,
+  //   hasAreaOfEffect: false,
+  //   areaOfEffectType: '',
+  //   radius: 0,
+  // };
+
+  // private thaumaturgie: Spell = {
+  //   name: 'Thaumaturgie',
+  //   level: 0,
+  //   cost: 'action',
+  //   duration: 10,
+  //   canRitual: false,
+  //   needsVerbal: true,
+  //   needsSomatic: false,
+  //   needsMaterial: false,
+  //   needsConcentration: false,
+  //   needsAttackRoll: false,
+  //   needsSavingThrow: false,
+  //   doesDamage: false,
+  //   damage: [{ diceNumber: '', diceType: '', damageType: '' }],
+  //   doesHeal: false,
+  //   heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
+  //   description:
+  //     'Du erzeugst einen der folgenden magischen Effekte innerhalb von 9 Metern: \b\b • Deine Stimme wird dreimal lauter.\b • Du lässt einen unverschlossenen Tür oder ein Fenster aufspringen. \b• Du lässt eine Flamme aufleuchten, erlöschen oder ihre Farbe ändern. \b• Du lässt den Boden für eine Minute beben.\b • Du erzeugst Geräusche, die von dir ausgehen, wie das Flüstern einer Stimme, das Brüllen eines Löwen, das Knurren von Donner oder das Klappern von Metall. \b• Du lässt eine Tür oder ein Fenster, das du berührst, für eine Minute verriegeln oder entriegeln.',
+  //   school: 'transmutation',
+  //   isRanged: true,
+  //   range: 30,
+  //   hasAreaOfEffect: false,
+  //   areaOfEffectType: '',
+  //   radius: 0,
+  // };
+
+  // private cureWounds: Spell = {
+  //   name: 'Heilende Berührung',
+  //   level: 1,
+  //   cost: 'action',
+  //   duration: 0,
+  //   canRitual: false,
+  //   needsVerbal: true,
+  //   needsSomatic: true,
+  //   needsMaterial: false,
+  //   needsConcentration: false,
+  //   needsAttackRoll: false,
+  //   needsSavingThrow: false,
+  //   doesDamage: false,
+  //   damage: [{ diceNumber: '', diceType: '', damageType: '' }],
+  //   doesHeal: true,
+  //   heal: { diceNumber: '1', diceType: 'd8', additionalHeal: 4 },
+  //   description:
+  //     'Eine Kreatur, die du berührst, gewinnt Trefferpunkte in Höhe von 1W8 + deinem Attributsmodifikator im Zauberwirken zurück. Dieser Zauber wirkt nicht auf Untote oder Konstrukte. Auf höheren Graden: Wirkst du diesen Zauber, indem du einen Zauberplatz des 2. Grades oder höher nutzt, steigt die Heilung für jeden Grad über dem 1. um 1W8.',
+  //   school: 'evocation',
+  //   isRanged: false,
+  //   range: 5,
+  //   hasAreaOfEffect: false,
+  //   areaOfEffectType: '',
+  //   radius: 0,
+  // };
+
+  // private detectMagic: Spell = {
+  //   name: 'Magie entdecken',
+  //   level: 1,
+  //   cost: 'action',
+  //   duration: 100,
+  //   canRitual: true,
+  //   needsVerbal: true,
+  //   needsSomatic: true,
+  //   needsMaterial: false,
+  //   needsConcentration: false,
+  //   needsAttackRoll: false,
+  //   needsSavingThrow: false,
+  //   doesDamage: false,
+  //   damage: [{ diceNumber: '', diceType: '', damageType: '' }],
+  //   doesHeal: false,
+  //   heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
+  //   description:
+  //     'Während der Wirkungsdauer nimmst du die Gegenwart von Magie im Abstand von bis zu neun Metern von dir wahr. Wenn du auf diese Weise Magie wahrnimmst, kannst du deine Aktion verwenden, um schwache Auren um sichtbare magische Kreaturen oder Objekte sowie ihre magische Schule zu erkennen, falls vorhanden. Dieser Zauber durchdringt die meisten Barrieren, wird aber von 30 Zentimetern Stein, 2,5 Zentimetern gewöhnlichem Metall, dünnem Bleiblech sowie von einem Meter Holz oder Erde blockiert.',
+  //   school: 'divination',
+  //   isRanged: true,
+  //   range: 30,
+  //   hasAreaOfEffect: false,
+  //   areaOfEffectType: '',
+  //   radius: 0,
+  // };
+
+  // private bless: Spell = {
+  //   name: 'Segnen',
+  //   level: 1,
+  //   cost: 'action',
+  //   duration: 10,
+  //   canRitual: false,
+  //   needsVerbal: true,
+  //   needsSomatic: true,
+  //   needsMaterial: true,
+  //   needsConcentration: true,
+  //   needsAttackRoll: false,
+  //   needsSavingThrow: false,
+  //   doesDamage: false,
+  //   damage: [{ diceNumber: '', diceType: '', damageType: '' }],
+  //   doesHeal: false,
+  //   heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
+  //   description:
+  //     'Du segnest bis zu drei Kreaturen deiner Wahl in Reichweite. Wenn ein Ziel während der Wirkungsdauer einen Angriffs- oder Rettungswurf ausführt, darf es mit einem W4 würfeln und das Würfelergebnis zu seinem Angriffs- oder Rettungswurf addieren. Auf höheren Graden: Wirkst du diesen Zauber, indem du einen Zauberplatz des 2. Grades oder höher nutzt, kannst du für jeden Grad über dem 1. eine zusätzliche Kreatur als Ziel wählen.',
+  //   school: 'enchantment',
+  //   isRanged: true,
+  //   range: 60,
+  //   hasAreaOfEffect: false,
+  //   areaOfEffectType: '',
+  //   radius: 0,
+  // };
+
+  // private healingWord: Spell = {
+  //   name: 'Heilendes Wort',
+  //   level: 1,
+  //   cost: 'bonus action',
+  //   duration: 0,
+  //   canRitual: false,
+  //   needsVerbal: true,
+  //   needsSomatic: false,
+  //   needsMaterial: false,
+  //   needsConcentration: false,
+  //   needsAttackRoll: false,
+  //   needsSavingThrow: false,
+  //   doesDamage: false,
+  //   damage: [{ diceNumber: '', diceType: '', damageType: '' }],
+  //   doesHeal: true,
+  //   heal: { diceNumber: '1', diceType: 'd4', additionalHeal: 4 },
+  //   description:
+  //     'Eine Kreatur deiner Wahl in Reichweite, die du sehen kannst, gewinnt Trefferpunkte in Höhe von 1W4 + deinem Zauberwirken-Attributsmodifikator zurück. Dieser Zauber wirkt nicht auf Untote oder Konstrukte. Auf höheren Graden: Wirkst du diesen Zauber, indem du einen Zauberplatz des 2. Grades oder höher nutzt, steigt die Heilung für jeden Grad über dem 1. um 1W4.',
+  //   school: 'evocation',
+  //   isRanged: true,
+  //   range: 60,
+  //   hasAreaOfEffect: false,
+  //   areaOfEffectType: '',
+  //   radius: 0,
+  // };
+
+  // private guidingBolt: Spell = {
+  //   name: 'Lenkendes Geschoss',
+  //   level: 1,
+  //   cost: 'action',
+  //   duration: 0,
+  //   canRitual: false,
+  //   needsVerbal: true,
+  //   needsSomatic: true,
+  //   needsMaterial: false,
+  //   needsConcentration: false,
+  //   needsAttackRoll: true,
+  //   needsSavingThrow: false,
+  //   doesDamage: true,
+  //   damage: [{ diceNumber: '4', diceType: 'd6', damageType: 'radiant' }],
+  //   doesHeal: false,
+  //   heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
+  //   description:
+  //     'Ein Lichtblitz schießt auf eine Kreatur deiner Wahl in Reichweite zu. Führe einen Fernkampf-Zauberangriff gegen das Ziel aus. Bei einem Erfolg erleidet das Ziel 4W6 gleißenden Schaden. Dank des mystischen dämmrigen Lichts, das auf dem Ziel glitzert, ist der nächste Angriffswurf gegen das Ziel vor Ende deines nächsten Zugs im Vorteil. Auf höheren Graden: Wirkst du diesen Zauber, indem du einen Zauberplatz des 2. Grades oder höher nutzt, steigt der Schaden für jeden Grad über dem 1. um 1W6.',
+  //   school: 'evocation',
+  //   isRanged: true,
+  //   range: 120,
+  //   hasAreaOfEffect: false,
+  //   areaOfEffectType: '',
+  //   radius: 0,
+  // };
+
+  // private detectPoisonAndDisease: Spell = {
+  //   name: 'Gift und Krankheiten entdecken',
+  //   level: 1,
+  //   cost: 'action',
+  //   duration: 100,
+  //   canRitual: true,
+  //   needsVerbal: true,
+  //   needsSomatic: true,
+  //   needsMaterial: true,
+  //   needsConcentration: true,
+  //   needsAttackRoll: false,
+  //   needsSavingThrow: false,
+  //   doesDamage: false,
+  //   damage: [{ diceNumber: '', diceType: '', damageType: '' }],
+  //   doesHeal: false,
+  //   heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
+  //   description:
+  //     'Während der Wirkungsdauer nimmst du die Gegenwart und die Position von Giften, giftigen Kreaturen und Krankheiten im Abstand von bis zu neun Metern von dir wahr. Du kannst auch die Art des Gifts, der giftigen Kreatur oder der Krankheit bestimmen. Dieser Zauber durchdringt die meisten Barrieren, wird aber von 30 Zentimetern Stein, 2,5 Zentimetern gewöhnlichem Metall, dünnem Bleiblech sowie von einem Meter Holz oder Erde blockiert.',
+  //   school: 'divination',
+  //   isRanged: true,
+  //   range: 30,
+  //   hasAreaOfEffect: false,
+  //   areaOfEffectType: '',
+  //   radius: 0,
+  // };
+
+  // private heroism: Spell = {
+  //   name: 'Heldenmut',
+  //   level: 1,
+  //   cost: 'action',
+  //   duration: 10,
+  //   canRitual: false,
+  //   needsVerbal: true,
+  //   needsSomatic: true,
+  //   needsMaterial: false,
+  //   needsConcentration: true,
+  //   needsAttackRoll: false,
+  //   needsSavingThrow: false,
+  //   doesDamage: false,
+  //   damage: [{ diceNumber: '', diceType: '', damageType: '' }],
+  //   doesHeal: false,
+  //   heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
+  //   description:
+  //     'Du berührst eine bereitwillige Kreatur und erfüllst sie mit Tapferkeit. Bis der Zauber endet, kann die Kreatur nicht verängstigt werden, und sie erhält zu Beginn jedes ihrer Züge temporäre Trefferpunkte in Höhe deines Zauberwirken-Attributsmodifikators. Wenn der Zauber endet, verliert das Ziel alle verbleibenden temporären Trefferpunkte dieses Zaubers. Auf höheren Graden: Wirkst du diesen Zauber, indem du einen Zauberplatz des 2. Grades oder höher nutzt, kannst du für jeden Grad über dem 1. eine zusätzliche Kreatur als Ziel wählen.',
+  //   school: 'enchantment',
+  //   isRanged: false,
+  //   range: 5,
+  //   hasAreaOfEffect: false,
+  //   areaOfEffectType: '',
+  //   radius: 0,
+  // };
+
+  // private santcuary: Spell = {
+  //   name: 'Heiligtum',
+  //   level: 1,
+  //   cost: 'bonus action',
+  //   duration: 10,
+  //   canRitual: false,
+  //   needsVerbal: true,
+  //   needsSomatic: true,
+  //   needsMaterial: true,
+  //   needsConcentration: true,
+  //   needsAttackRoll: false,
+  //   needsSavingThrow: false,
+  //   doesDamage: false,
+  //   damage: [{ diceNumber: '', diceType: '', damageType: '' }],
+  //   doesHeal: false,
+  //   heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
+  //   description:
+  //     'Du schützt eine Kreatur in Reichweite vor Angriffen. Während der Wirkungsdauer muss jede Kreatur, die bei einem Angriff oder schädlichen Zauber die geschützte Kreatur als Ziel hat, zuerst einen Weisheitsrettungswurf bestehen. Scheitert der Wurf, muss die Kreatur ein neues Ziel wählen, oder sie verliert den Angriff oder Zauber. Dieser Zauber schützt die entsprechende Kreatur nicht vor Flächeneffekten wie der Explosion eines Feuerballs. Greift die geschützte Kreatur an oder wirkt sie einen Zauber auf eine feindliche Kreatur, endet der Effekt dieses Zaubers.',
+  //   school: 'abjuration',
+  //   isRanged: true,
+  //   range: 60,
+  //   hasAreaOfEffect: false,
+  //   areaOfEffectType: '',
+  //   radius: 0,
+  // };
 }