2024-12-28 16:30:48 +01:00
package character
2024-12-21 07:34:38 +01:00
import (
2024-12-28 16:30:48 +01:00
"bamort/database"
2025-07-24 07:39:43 +02:00
"bamort/gsmaster"
2025-08-10 20:19:30 +02:00
"bamort/logger"
2025-01-03 14:03:27 +01:00
"bamort/models"
2025-08-26 22:31:46 +02:00
"sort"
2025-08-08 06:35:11 +02:00
"strconv"
2025-08-02 07:15:27 +02:00
"strings"
2025-08-08 06:39:46 +02:00
"time"
2024-12-28 16:30:48 +01:00
2024-12-21 17:59:49 +01:00
"fmt"
2024-12-21 07:34:38 +01:00
"net/http"
"github.com/gin-gonic/gin"
2025-12-29 17:07:45 +01:00
"gorm.io/gorm"
2024-12-21 07:34:38 +01:00
)
2025-07-24 07:39:43 +02:00
// Character Handlers
2024-12-21 07:34:38 +01:00
2025-07-24 07:39:43 +02:00
type LearnRequestStruct struct {
SkillType string ` json:"skillType" `
Name string ` json:"name" `
Stufe int ` json:"stufe" `
}
func respondWithError ( c * gin . Context , status int , message string ) {
2025-08-10 20:19:30 +02:00
logger . Warn ( "HTTP Fehler %d: %s" , status , message )
2025-07-24 07:39:43 +02:00
c . JSON ( status , gin . H { "error" : message } )
}
2024-12-21 07:34:38 +01:00
2025-01-03 00:03:31 +01:00
func ListCharacters ( c * gin . Context ) {
2025-08-10 20:19:30 +02:00
logger . Debug ( "ListCharacters aufgerufen" )
2025-08-29 07:07:08 +02:00
type AllCharacters struct {
SelfOwned [ ] models . CharList ` json:"self_owned" `
Others [ ] models . CharList ` json:"others" `
}
allCharacters := AllCharacters { }
2025-08-10 20:19:30 +02:00
logger . Debug ( "Lade Charaktere aus der Datenbank..." )
2025-08-28 18:22:52 +02:00
//if err := database.DB.Find(&characters).Error; err != nil {
listOfChars , err := models . FindCharListByUserID ( c . GetUint ( "userID" ) )
if err != nil {
2025-08-10 20:19:30 +02:00
logger . Error ( "Fehler beim Laden der Charaktere: %s" , err . Error ( ) )
2025-07-24 07:39:43 +02:00
respondWithError ( c , http . StatusInternalServerError , "Failed to retrieve characters" )
2024-12-21 07:34:38 +01:00
return
}
2025-08-10 20:19:30 +02:00
2025-08-28 18:22:52 +02:00
logger . Debug ( "Gefundene Charaktere: %d" , len ( listOfChars ) )
2025-08-29 07:07:08 +02:00
allCharacters . SelfOwned = listOfChars
listPublic , err := models . FindPublicCharList ( )
2025-08-28 18:22:52 +02:00
2025-08-29 07:07:08 +02:00
if err != nil {
logger . Error ( "Fehler beim Laden der öffentlichen Charaktere: %s" , err . Error ( ) )
respondWithError ( c , http . StatusInternalServerError , "Failed to retrieve public characters" )
return
}
allCharacters . Others = listPublic
2025-08-10 20:19:30 +02:00
logger . Info ( "Charakterliste erfolgreich geladen: %d Charaktere" , len ( listOfChars ) )
2025-08-29 07:07:08 +02:00
c . JSON ( http . StatusOK , allCharacters )
2024-12-21 07:34:38 +01:00
}
func CreateCharacter ( c * gin . Context ) {
2025-07-28 21:35:29 +02:00
var character models . Char
2024-12-21 07:34:38 +01:00
if err := c . ShouldBindJSON ( & character ) ; err != nil {
2025-07-24 07:39:43 +02:00
respondWithError ( c , http . StatusBadRequest , err . Error ( ) )
2024-12-21 07:34:38 +01:00
return
}
2024-12-28 16:30:48 +01:00
if err := database . DB . Create ( & character ) . Error ; err != nil {
2025-07-24 07:39:43 +02:00
respondWithError ( c , http . StatusInternalServerError , "Failed to create character" )
2024-12-21 07:34:38 +01:00
return
}
c . JSON ( http . StatusCreated , character )
}
2025-01-03 00:03:31 +01:00
func GetCharacter ( c * gin . Context ) {
id := c . Param ( "id" )
2025-07-28 21:35:29 +02:00
var character models . Char
2025-01-03 00:03:31 +01:00
err := character . FirstID ( id )
if err != nil {
2025-07-24 07:39:43 +02:00
respondWithError ( c , http . StatusInternalServerError , "Failed to retrieve character" )
2025-01-03 00:03:31 +01:00
return
}
2025-01-18 20:59:35 +01:00
feChar := ToFeChar ( & character )
c . JSON ( http . StatusOK , feChar )
2025-01-03 00:03:31 +01:00
}
func UpdateCharacter ( c * gin . Context ) {
2025-07-24 07:39:43 +02:00
id := c . Param ( "id" )
2025-07-28 21:35:29 +02:00
var character models . Char
2025-01-03 00:03:31 +01:00
2025-07-24 07:39:43 +02:00
// First, find the existing character
err := character . FirstID ( id )
if err != nil {
respondWithError ( c , http . StatusNotFound , "Character not found" )
return
}
2025-12-29 17:07:45 +01:00
// Store the original ID to preserve it
originalID := character . ID
2025-07-24 07:39:43 +02:00
// Bind the updated data
if err := c . ShouldBindJSON ( & character ) ; err != nil {
respondWithError ( c , http . StatusBadRequest , err . Error ( ) )
return
}
2025-12-29 17:07:45 +01:00
// Restore the ID
character . ID = originalID
// Update all associations
if err := database . DB . Session ( & gorm . Session { FullSaveAssociations : true } ) . Save ( & character ) . Error ; err != nil {
2025-07-24 07:39:43 +02:00
respondWithError ( c , http . StatusInternalServerError , "Failed to update character" )
return
}
c . JSON ( http . StatusOK , character )
2025-01-03 00:03:31 +01:00
}
func DeleteCharacter ( c * gin . Context ) {
id := c . Param ( "id" )
2025-07-28 21:35:29 +02:00
var character models . Char
2025-01-03 00:03:31 +01:00
err := character . FirstID ( id )
if err != nil {
2025-07-24 07:39:43 +02:00
respondWithError ( c , http . StatusNotFound , "Character not found" )
2025-01-03 00:03:31 +01:00
return
}
err = character . Delete ( )
if err != nil {
2025-07-24 07:39:43 +02:00
respondWithError ( c , http . StatusInternalServerError , "Failed to delete character" )
2025-01-03 00:03:31 +01:00
return
}
2025-07-24 07:39:43 +02:00
c . JSON ( http . StatusOK , gin . H { "message" : "Character deleted successfully" } )
2025-01-03 00:03:31 +01:00
}
2024-12-21 07:42:20 +01:00
2025-01-02 21:48:13 +01:00
// Add Fertigkeit by putting it directly to the DB
2025-07-28 22:08:19 +02:00
func AddFertigkeit ( charID uint , fertigkeit * models . SkFertigkeit ) error {
2025-01-02 21:48:13 +01:00
// Set the foreign key for the new Eigenschaft
fertigkeit . CharacterID = charID
// Save the new Eigenschaft to the database
if err := database . DB . Create ( & fertigkeit ) . Error ; err != nil {
return fmt . Errorf ( "failed to add Eigenschaft: %w" , err )
}
return nil
}
// Append the new Fertigkeit to the slice of the characters property
//character.Fertigkeiten = append(character.Fertigkeiten, fertigkeit)
2025-01-18 20:59:35 +01:00
2025-07-28 21:35:29 +02:00
func ToFeChar ( object * models . Char ) * models . FeChar {
feC := & models . FeChar {
2025-01-18 20:59:35 +01:00
Char : * object ,
}
skills , innateSkills , categories := splitSkills ( object . Fertigkeiten )
feC . Fertigkeiten = skills
feC . InnateSkills = innateSkills
feC . CategorizedSkills = categories
return feC
}
2025-07-24 07:39:43 +02:00
2025-07-28 22:08:19 +02:00
func splitSkills ( object [ ] models . SkFertigkeit ) ( [ ] models . SkFertigkeit , [ ] models . SkFertigkeit , map [ string ] [ ] models . SkFertigkeit ) {
var normSkills [ ] models . SkFertigkeit
var innateSkills [ ] models . SkFertigkeit
//var categories map[string][]models.Fertigkeit
categories := make ( map [ string ] [ ] models . SkFertigkeit )
2025-01-18 20:59:35 +01:00
for _ , skill := range object {
2025-08-01 06:27:38 +02:00
gsmsk := skill . GetSkillByName ( )
2025-01-18 20:59:35 +01:00
if gsmsk . Improvable {
category := "Unkategorisiert"
if gsmsk . ID != 0 && gsmsk . Category != "" {
category = gsmsk . Category
}
normSkills = append ( normSkills , skill )
if _ , exists := categories [ category ] ; ! exists {
2025-07-28 22:08:19 +02:00
categories [ category ] = make ( [ ] models . SkFertigkeit , 0 )
2025-01-18 20:59:35 +01:00
}
categories [ category ] = append ( categories [ category ] , skill )
} else {
innateSkills = append ( innateSkills , skill )
}
}
return normSkills , innateSkills , categories
}
2025-07-24 07:39:43 +02:00
// ExperienceAndWealthResponse repräsentiert die Antwort für EP und Vermögen
type ExperienceAndWealthResponse struct {
ExperiencePoints int ` json:"experience_points" `
Wealth struct {
2026-01-02 12:07:28 +01:00
Goldstuecke int ` json:"gold_coins" ` // GS
Silberstuecke int ` json:"silver_coins" ` // SS
Kupferstuecke int ` json:"copper_coins" ` // KS
TotalInGS int ` json:"total_in_ss" ` // Gesamt in Silberstücken
2025-07-24 07:39:43 +02:00
} ` json:"wealth" `
}
// GetCharacterExperienceAndWealth gibt nur die EP und Vermögensdaten eines Charakters zurück
func GetCharacterExperienceAndWealth ( c * gin . Context ) {
id := c . Param ( "id" )
2025-07-28 21:35:29 +02:00
var character models . Char
2025-07-24 07:39:43 +02:00
// Lade nur die benötigten Felder
err := database . DB .
Preload ( "Erfahrungsschatz" ) .
Preload ( "Vermoegen" ) .
First ( & character , id ) . Error
if err != nil {
respondWithError ( c , http . StatusNotFound , "Character not found" )
return
}
// Berechne Gesamtvermögen in Silbergroschen
// Annahme: 1 GS = 10 SS, 1 SS = 10 KS (typische Midgard Währung)
2025-12-19 17:04:20 +01:00
gs := character . Vermoegen . Goldstuecke
ss := character . Vermoegen . Silberstuecke
ks := character . Vermoegen . Kupferstuecke
2025-07-24 07:39:43 +02:00
totalInSS := ( gs * 10 ) + ss + ( ks / 10 )
response := ExperienceAndWealthResponse {
2025-07-25 20:43:28 +02:00
ExperiencePoints : character . Erfahrungsschatz . EP ,
2025-07-24 07:39:43 +02:00
}
2026-01-02 12:07:28 +01:00
response . Wealth . Goldstuecke = gs
response . Wealth . Silberstuecke = ss
response . Wealth . Kupferstuecke = ks
2025-07-24 07:39:43 +02:00
response . Wealth . TotalInGS = totalInSS
c . JSON ( http . StatusOK , response )
}
// UpdateExperienceRequest repräsentiert die Anfrage für EP-Update
type UpdateExperienceRequest struct {
ExperiencePoints int ` json:"experience_points" binding:"required,min=0" `
Reason string ` json:"reason,omitempty" ` // Grund der Änderung
Notes string ` json:"notes,omitempty" ` // Zusätzliche Notizen
}
// UpdateCharacterExperience aktualisiert die Erfahrungspunkte eines Charakters
2025-08-01 06:27:38 +02:00
// TODO Wenn EP verändert werden ändert sich auch ES
2025-07-24 07:39:43 +02:00
func UpdateCharacterExperience ( c * gin . Context ) {
id := c . Param ( "id" )
2025-07-28 21:35:29 +02:00
var character models . Char
2025-07-24 07:39:43 +02:00
// Lade den Charakter
err := database . DB .
Preload ( "Erfahrungsschatz" ) .
First ( & character , id ) . Error
if err != nil {
respondWithError ( c , http . StatusNotFound , "Character not found" )
return
}
// Parse Request
var req UpdateExperienceRequest
if err := c . ShouldBindJSON ( & req ) ; err != nil {
respondWithError ( c , http . StatusBadRequest , err . Error ( ) )
return
}
// Standard-Grund setzen, falls nicht angegeben
if req . Reason == "" {
req . Reason = string ( ReasonManual )
}
// Alten Wert für Audit-Log speichern
oldValue := 0
if character . Erfahrungsschatz . ID != 0 {
2025-07-25 20:43:28 +02:00
oldValue = character . Erfahrungsschatz . EP
2025-07-24 07:39:43 +02:00
}
// Aktualisiere oder erstelle Erfahrungsschatz
if character . Erfahrungsschatz . ID == 0 {
// Erstelle neuen Erfahrungsschatz
2025-07-28 21:35:29 +02:00
character . Erfahrungsschatz = models . Erfahrungsschatz {
2025-07-24 07:39:43 +02:00
BamortCharTrait : models . BamortCharTrait {
CharacterID : character . ID ,
} ,
2025-07-25 20:43:28 +02:00
EP : req . ExperiencePoints ,
2025-07-24 07:39:43 +02:00
}
if err := database . DB . Create ( & character . Erfahrungsschatz ) . Error ; err != nil {
respondWithError ( c , http . StatusInternalServerError , "Failed to create experience record" )
return
}
} else {
// Aktualisiere existierenden Erfahrungsschatz
2025-07-25 20:43:28 +02:00
character . Erfahrungsschatz . EP = req . ExperiencePoints
2025-07-24 07:39:43 +02:00
if err := database . DB . Save ( & character . Erfahrungsschatz ) . Error ; err != nil {
respondWithError ( c , http . StatusInternalServerError , "Failed to update experience" )
return
}
}
// Audit-Log-Eintrag erstellen (nur wenn sich der Wert geändert hat)
if oldValue != req . ExperiencePoints {
// TODO: User-ID aus dem Authentifizierungs-Context holen
userID := uint ( 0 ) // Placeholder
err = CreateAuditLogEntry (
character . ID ,
"experience_points" ,
oldValue ,
req . ExperiencePoints ,
AuditLogReason ( req . Reason ) ,
userID ,
req . Notes ,
)
if err != nil {
// Log-Fehler sollten die Hauptoperation nicht blockieren
// TODO: Proper logging implementieren
}
}
c . JSON ( http . StatusOK , gin . H {
"message" : "Experience updated successfully" ,
"experience_points" : req . ExperiencePoints ,
"audit_logged" : oldValue != req . ExperiencePoints ,
} )
}
// UpdateWealthRequest repräsentiert die Anfrage für Vermögens-Update
type UpdateWealthRequest struct {
Goldstücke * int ` json:"goldstücke,omitempty" `
Silberstücke * int ` json:"silberstücke,omitempty" `
Kupferstücke * int ` json:"kupferstücke,omitempty" `
Reason string ` json:"reason,omitempty" ` // Grund der Änderung
Notes string ` json:"notes,omitempty" ` // Zusätzliche Notizen
}
// UpdateCharacterWealth aktualisiert das Vermögen eines Charakters
func UpdateCharacterWealth ( c * gin . Context ) {
id := c . Param ( "id" )
2025-07-28 21:35:29 +02:00
var character models . Char
2025-07-24 07:39:43 +02:00
// Lade den Charakter
err := database . DB .
Preload ( "Vermoegen" ) .
First ( & character , id ) . Error
if err != nil {
respondWithError ( c , http . StatusNotFound , "Character not found" )
return
}
// Parse Request
var req UpdateWealthRequest
if err := c . ShouldBindJSON ( & req ) ; err != nil {
respondWithError ( c , http . StatusBadRequest , err . Error ( ) )
return
}
// Standard-Grund setzen, falls nicht angegeben
if req . Reason == "" {
req . Reason = string ( ReasonManual )
}
// Alte Werte für Audit-Log speichern
oldGold := 0
oldSilver := 0
oldCopper := 0
if character . Vermoegen . ID != 0 {
2025-12-19 17:04:20 +01:00
oldGold = character . Vermoegen . Goldstuecke
oldSilver = character . Vermoegen . Silberstuecke
oldCopper = character . Vermoegen . Kupferstuecke
2025-07-24 07:39:43 +02:00
}
// Aktualisiere oder erstelle Vermögen
if character . Vermoegen . ID == 0 {
// Erstelle neues Vermögen
2025-08-28 00:46:55 +02:00
userID := c . GetUint ( "userID" )
2025-07-28 21:35:29 +02:00
character . Vermoegen = models . Vermoegen {
2025-07-24 07:39:43 +02:00
BamortCharTrait : models . BamortCharTrait {
CharacterID : character . ID ,
2025-08-28 00:46:55 +02:00
UserID : userID ,
2025-07-24 07:39:43 +02:00
} ,
2025-12-19 17:04:20 +01:00
Goldstuecke : getValueOrDefault ( req . Goldstücke , 0 ) ,
Silberstuecke : getValueOrDefault ( req . Silberstücke , 0 ) ,
Kupferstuecke : getValueOrDefault ( req . Kupferstücke , 0 ) ,
2025-07-24 07:39:43 +02:00
}
if err := database . DB . Create ( & character . Vermoegen ) . Error ; err != nil {
respondWithError ( c , http . StatusInternalServerError , "Failed to create wealth record" )
return
}
} else {
// Aktualisiere existierendes Vermögen
if req . Goldstücke != nil {
2025-12-19 17:04:20 +01:00
character . Vermoegen . Goldstuecke = * req . Goldstücke
2025-07-24 07:39:43 +02:00
}
if req . Silberstücke != nil {
2025-12-19 17:04:20 +01:00
character . Vermoegen . Silberstuecke = * req . Silberstücke
2025-07-24 07:39:43 +02:00
}
if req . Kupferstücke != nil {
2025-12-19 17:04:20 +01:00
character . Vermoegen . Kupferstuecke = * req . Kupferstücke
2025-07-24 07:39:43 +02:00
}
if err := database . DB . Save ( & character . Vermoegen ) . Error ; err != nil {
respondWithError ( c , http . StatusInternalServerError , "Failed to update wealth" )
return
}
}
// Audit-Log-Einträge erstellen (nur für geänderte Werte)
// TODO: User-ID aus dem Authentifizierungs-Context holen
userID := uint ( 0 ) // Placeholder
2025-12-19 17:04:20 +01:00
if req . Goldstücke != nil && oldGold != character . Vermoegen . Goldstuecke {
2025-07-24 07:39:43 +02:00
CreateAuditLogEntry (
character . ID ,
"gold" ,
oldGold ,
2025-12-19 17:04:20 +01:00
character . Vermoegen . Goldstuecke ,
2025-07-24 07:39:43 +02:00
AuditLogReason ( req . Reason ) ,
userID ,
req . Notes ,
)
}
2025-12-19 17:04:20 +01:00
if req . Silberstücke != nil && oldSilver != character . Vermoegen . Silberstuecke {
2025-07-24 07:39:43 +02:00
CreateAuditLogEntry (
character . ID ,
"silver" ,
oldSilver ,
2025-12-19 17:04:20 +01:00
character . Vermoegen . Silberstuecke ,
2025-07-24 07:39:43 +02:00
AuditLogReason ( req . Reason ) ,
userID ,
req . Notes ,
)
}
2025-12-19 17:04:20 +01:00
if req . Kupferstücke != nil && oldCopper != character . Vermoegen . Kupferstuecke {
2025-07-24 07:39:43 +02:00
CreateAuditLogEntry (
character . ID ,
"copper" ,
oldCopper ,
2025-12-19 17:04:20 +01:00
character . Vermoegen . Kupferstuecke ,
2025-07-24 07:39:43 +02:00
AuditLogReason ( req . Reason ) ,
userID ,
req . Notes ,
)
}
c . JSON ( http . StatusOK , gin . H {
"message" : "Wealth updated successfully" ,
"wealth" : gin . H {
2025-12-19 17:04:20 +01:00
"goldstücke" : character . Vermoegen . Goldstuecke ,
"silberstücke" : character . Vermoegen . Silberstuecke ,
"kupferstücke" : character . Vermoegen . Kupferstuecke ,
2025-07-24 07:39:43 +02:00
} ,
} )
}
// getValueOrDefault gibt den Wert zurück oder einen Default-Wert falls nil
func getValueOrDefault ( value * int , defaultValue int ) int {
if value != nil {
return * value
}
return defaultValue
}
2025-07-26 15:12:43 +02:00
// updateOrCreateSkill aktualisiert eine vorhandene Fertigkeit oder erstellt eine neue
2025-07-28 21:35:29 +02:00
func updateOrCreateSkill ( character * models . Char , skillName string , newLevel int ) error {
2025-07-26 15:12:43 +02:00
// Suche erst in normalen Fertigkeiten
for i := range character . Fertigkeiten {
if character . Fertigkeiten [ i ] . Name == skillName {
character . Fertigkeiten [ i ] . Fertigkeitswert = newLevel
return database . DB . Save ( & character . Fertigkeiten [ i ] ) . Error
}
}
// Suche in Waffenfertigkeiten
for i := range character . Waffenfertigkeiten {
if character . Waffenfertigkeiten [ i ] . Name == skillName {
character . Waffenfertigkeiten [ i ] . Fertigkeitswert = newLevel
return database . DB . Save ( & character . Waffenfertigkeiten [ i ] ) . Error
}
}
// Fertigkeit nicht gefunden - erstelle neue normale Fertigkeit
2025-07-28 22:08:19 +02:00
newSkill := models . SkFertigkeit {
2025-07-26 15:12:43 +02:00
BamortCharTrait : models . BamortCharTrait {
BamortBase : models . BamortBase {
Name : skillName ,
} ,
CharacterID : character . ID ,
} ,
Fertigkeitswert : newLevel ,
Improvable : true ,
}
if err := database . DB . Create ( & newSkill ) . Error ; err != nil {
return err
}
// Füge zur Charakter-Liste hinzu
character . Fertigkeiten = append ( character . Fertigkeiten , newSkill )
return nil
}
// addSpellToCharacter fügt einen neuen Zauber zum Charakter hinzu
2025-07-28 22:08:19 +02:00
func addSpellToCharacter ( character * models . Char , spellName string ) error {
2025-07-26 15:12:43 +02:00
// Prüfe, ob Zauber bereits existiert
for _ , spell := range character . Zauber {
if spell . Name == spellName {
// Zauber bereits vorhanden, nichts zu tun
return nil
}
}
// Erstelle neuen Zauber
2025-07-28 22:08:19 +02:00
newSpell := models . SkZauber {
2025-07-26 15:12:43 +02:00
BamortCharTrait : models . BamortCharTrait {
BamortBase : models . BamortBase {
Name : spellName ,
} ,
CharacterID : character . ID ,
} ,
}
if err := database . DB . Create ( & newSpell ) . Error ; err != nil {
return err
}
// Füge zur Charakter-Liste hinzu
character . Zauber = append ( character . Zauber , newSpell )
return nil
}
2025-07-24 07:39:43 +02:00
// Learn and Improve handlers with automatic audit logging
2025-07-25 14:00:56 +02:00
// LearnSpellRequest definiert die Struktur für das Lernen eines Zaubers
type LearnSpellRequest struct {
2025-07-24 07:39:43 +02:00
Name string ` json:"name" binding:"required" `
Notes string ` json:"notes,omitempty" `
}
2025-08-30 17:55:20 +02:00
// getCharacterClass is deprecated. Use character.Klasse directly or appropriate database lookups.
2025-08-01 13:53:10 +02:00
// This function provides backwards compatibility for character class access.
2025-08-30 17:55:20 +02:00
// getCharacterClass gibt die Charakterklassen-Abkürzung zurück
func getCharacterClass ( character * models . Char ) string {
2025-07-25 14:00:56 +02:00
if len ( character . Typ ) > 3 {
2025-08-30 17:55:20 +02:00
return gsmaster . GetClassAbbreviationNewSystem ( character . Typ )
2025-07-25 14:00:56 +02:00
}
return character . Typ
2025-07-24 07:39:43 +02:00
}
2025-08-05 21:12:03 +02:00
// LearnSkill lernt eine neue Fertigkeit und erstellt Audit-Log-Einträge
func LearnSkill ( c * gin . Context ) {
charID := c . Param ( "id" )
var character models . Char
if err := character . FirstID ( charID ) ; err != nil {
respondWithError ( c , http . StatusNotFound , "Charakter nicht gefunden" )
return
}
// Verwende gsmaster.LernCostRequest direkt
var request gsmaster . LernCostRequest
if err := c . ShouldBindJSON ( & request ) ; err != nil {
respondWithError ( c , http . StatusBadRequest , "Ungültige Anfrageparameter: " + err . Error ( ) )
return
}
// Setze Charakter-ID und Action für learning
request . CharId = character . ID
request . Action = "learn"
if request . Type == "" {
request . Type = "skill" // Default zu skill für Learning
}
// 1. Charakter laden
char , err := loadCharacterForImprovement ( request . CharId )
if err != nil {
respondWithError ( c , http . StatusNotFound , "Charakter nicht gefunden" )
return
}
// 2. Skill validieren (für Learning beginnen wir bei Level 0)
characterClass , skillInfo , currentLevel , err := validateSkillForLearning ( char , & request )
if err != nil {
respondWithError ( c , http . StatusBadRequest , err . Error ( ) )
return
}
// Bestimme das finale Level
finalLevel := request . TargetLevel
if finalLevel <= 0 {
finalLevel = 1 // Standard für neue Fertigkeit
}
// 3. Kosten berechnen (von Level 0 bis finalLevel)
response , totalEP , totalGold , totalPP , err := calculateLearningCosts ( char , & request , characterClass , skillInfo , currentLevel , finalLevel )
if err != nil {
respondWithError ( c , http . StatusBadRequest , err . Error ( ) )
return
}
// 4. Ressourcen validieren
err = validateResources ( char , request . Name , totalEP , totalGold , totalPP )
if err != nil {
respondWithError ( c , http . StatusBadRequest , err . Error ( ) )
return
}
// 5. Ressourcen abziehen
newEP , newGold , err := deductResourcesForLearning ( char , request . Name , finalLevel , totalEP , totalGold , totalPP )
if err != nil {
respondWithError ( c , http . StatusInternalServerError , err . Error ( ) )
return
}
// 6. Skill hinzufügen/erstellen
if err := updateOrCreateSkill ( char , request . Name , finalLevel ) ; err != nil {
respondWithError ( c , http . StatusInternalServerError , "Fehler beim Hinzufügen der Fertigkeit: " + err . Error ( ) )
return
}
// 7. Charakter speichern
if err := database . DB . Save ( char ) . Error ; err != nil {
respondWithError ( c , http . StatusInternalServerError , "Fehler beim Speichern des Charakters" )
return
}
// 8. Response erstellen
responseData := gin . H {
"message" : "Fertigkeit erfolgreich gelernt" ,
"skill_name" : request . Name ,
"final_level" : finalLevel ,
"ep_cost" : totalEP ,
"gold_cost" : totalGold ,
"remaining_ep" : newEP ,
"remaining_gold" : newGold ,
"cost_details" : response ,
}
// Füge Multi-Level-spezifische Informationen hinzu
if finalLevel > 1 {
// Erstelle Array der gelernten Level für Kompatibilität
var levelsLearned [ ] int
for i := 1 ; i <= finalLevel ; i ++ {
levelsLearned = append ( levelsLearned , i )
}
responseData [ "levels_learned" ] = levelsLearned
responseData [ "level_count" ] = finalLevel
responseData [ "multi_level" ] = true
}
c . JSON ( http . StatusOK , responseData )
}
// ImproveSkill verbessert eine bestehende Fertigkeit und erstellt Audit-Log-Einträge
// validateSkillForLearning validiert Skill-Namen für neue Fertigkeiten (Learning)
func validateSkillForLearning ( char * models . Char , request * gsmaster . LernCostRequest ) ( string , * models . SkillLearningInfo , int , error ) {
// Verwende Klassenabkürzung wenn der Typ länger als 3 Zeichen ist
var characterClass string
if len ( char . Typ ) > 3 {
characterClass = gsmaster . GetClassAbbreviationNewSystem ( char . Typ )
} else {
characterClass = char . Typ
}
// Normalize skill/spell name (trim whitespace, proper case)
skillName := strings . TrimSpace ( request . Name )
skillInfo , err := models . GetSkillCategoryAndDifficultyNewSystem ( skillName , characterClass )
if err != nil {
return "" , nil , 0 , fmt . Errorf ( "fertigkeit '%s' nicht gefunden oder nicht für Klasse '%s' verfügbar: %v" , skillName , characterClass , err )
}
// Für Learning starten wir bei Level 0
currentLevel := 0
// Prüfe, ob die Fertigkeit bereits existiert
existingLevel := getCurrentSkillLevel ( char , request . Name , "skill" )
if existingLevel > 0 {
return "" , nil , 0 , fmt . Errorf ( "fertigkeit '%s' ist bereits auf Level %d - verwende ImproveSkill stattdessen" , request . Name , existingLevel )
}
return characterClass , skillInfo , currentLevel , nil
}
// calculateLearningCosts berechnet die Kosten für das Erlernen einer neuen Fertigkeit
func calculateLearningCosts ( char * models . Char , request * gsmaster . LernCostRequest , characterClass string , skillInfo * models . SkillLearningInfo , currentLevel , finalLevel int ) ( [ ] gsmaster . SkillCostResultNew , int , int , int , error ) {
var response [ ] gsmaster . SkillCostResultNew
var totalEP , totalGold , totalPP int
// Loop für jeden Level von 0 bis finalLevel (für neue Fertigkeiten)
for tempLevel := currentLevel ; tempLevel < finalLevel ; tempLevel ++ {
nextLevel := tempLevel + 1
// Erstelle temporären Request für diesen Level
tempRequest := * request
tempRequest . CurrentLevel = tempLevel
tempRequest . TargetLevel = nextLevel
// Für das erste Level (0->1) ist es ein "learn", für weitere Level "improve"
if tempLevel == 0 {
tempRequest . Action = "learn"
} else {
tempRequest . Action = "improve"
}
// Erstelle cost result structure
costResult := gsmaster . SkillCostResultNew {
CharacterID : fmt . Sprintf ( "%d" , char . ID ) ,
CharacterClass : characterClass ,
SkillName : request . Name ,
TargetLevel : nextLevel ,
}
// Verwende die gleiche Kostenfunktion wie für Improvements
err := CalculateSkillImproveCostNewSystem ( & tempRequest , & costResult , nextLevel , & tempRequest . UsePP , & tempRequest . UseGold , skillInfo )
if err != nil {
return nil , 0 , 0 , 0 , fmt . Errorf ( "fehler bei der Kostenberechnung: %v" , err )
}
// für die nächste Runde die PP und Gold reduzieren die zum Lernen genutzt werden sollen
if costResult . PPUsed > 0 {
request . UsePP -= costResult . PPUsed
if request . UsePP < 0 {
request . UsePP = 0
}
}
if costResult . GoldUsed > 0 {
request . UseGold -= costResult . GoldUsed
if request . UseGold < 0 {
request . UseGold = 0
}
}
response = append ( response , costResult )
// Addiere die Kosten
totalEP += costResult . EP
totalGold += costResult . GoldCost
totalPP += costResult . PPUsed
}
return response , totalEP , totalGold , totalPP , nil
}
// deductResourcesForLearning zieht die Ressourcen für das Lernen ab und erstellt Audit-Log-Einträge
func deductResourcesForLearning ( char * models . Char , skillName string , finalLevel , totalEP , totalGold , totalPP int ) ( int , int , error ) {
2025-08-05 21:29:00 +02:00
return deductResourcesWithAuditReason ( char , skillName , finalLevel , totalEP , totalGold , totalPP , ReasonSkillLearning )
}
// deductResourcesWithAuditReason zieht EP, Gold und PP ab und erstellt entsprechende Audit-Log-Einträge
func deductResourcesWithAuditReason ( char * models . Char , itemName string , finalLevel , totalEP , totalGold , totalPP int , auditReason AuditLogReason ) ( int , int , error ) {
2025-08-05 21:12:03 +02:00
currentEP := char . Erfahrungsschatz . EP
2025-12-19 17:04:20 +01:00
currentGold := char . Vermoegen . Goldstuecke
2025-08-05 21:12:03 +02:00
// EP abziehen und Audit-Log erstellen
newEP := currentEP - totalEP
if totalEP > 0 {
var notes string
if finalLevel > 1 {
2025-08-05 21:29:00 +02:00
notes = fmt . Sprintf ( "Fertigkeit '%s' bis Level %d gelernt" , itemName , finalLevel )
} else if auditReason == ReasonSpellLearning {
notes = fmt . Sprintf ( "Zauber '%s' gelernt" , itemName )
2025-08-05 21:12:03 +02:00
} else {
2025-08-05 21:29:00 +02:00
notes = fmt . Sprintf ( "Fertigkeit '%s' gelernt" , itemName )
2025-08-05 21:12:03 +02:00
}
2025-08-05 21:29:00 +02:00
err := CreateAuditLogEntry ( char . ID , "experience_points" , currentEP , newEP , auditReason , 0 , notes )
2025-08-05 21:12:03 +02:00
if err != nil {
return 0 , 0 , fmt . Errorf ( "fehler beim Erstellen des Audit-Log-Eintrags: %v" , err )
}
char . Erfahrungsschatz . EP = newEP
if err := database . DB . Save ( & char . Erfahrungsschatz ) . Error ; err != nil {
return 0 , 0 , fmt . Errorf ( "fehler beim Speichern der Erfahrungspunkte: %v" , err )
}
}
// Gold abziehen und Audit-Log erstellen
newGold := currentGold - totalGold
if totalGold > 0 {
2025-08-05 21:29:00 +02:00
var notes string
if auditReason == ReasonSpellLearning {
notes = fmt . Sprintf ( "Gold für Zauber '%s' ausgegeben" , itemName )
} else {
notes = fmt . Sprintf ( "Gold für Fertigkeit '%s' ausgegeben" , itemName )
}
2025-08-05 21:12:03 +02:00
2025-08-05 21:29:00 +02:00
err := CreateAuditLogEntry ( char . ID , "gold" , currentGold , newGold , auditReason , 0 , notes )
2025-08-05 21:12:03 +02:00
if err != nil {
return 0 , 0 , fmt . Errorf ( "fehler beim Erstellen des Audit-Log-Eintrags: %v" , err )
}
2025-12-19 17:04:20 +01:00
char . Vermoegen . Goldstuecke = newGold
2025-08-05 21:12:03 +02:00
if err := database . DB . Save ( & char . Vermoegen ) . Error ; err != nil {
return 0 , 0 , fmt . Errorf ( "fehler beim Speichern des Vermögens: %v" , err )
}
}
// PP abziehen (falls vorhanden und erforderlich)
if totalPP > 0 {
// Suche die richtige Fertigkeit und ziehe PP ab
for i := range char . Fertigkeiten {
2025-08-05 21:29:00 +02:00
if char . Fertigkeiten [ i ] . Name == itemName {
2025-08-05 21:12:03 +02:00
char . Fertigkeiten [ i ] . Pp -= totalPP
if err := database . DB . Save ( & char . Fertigkeiten [ i ] ) . Error ; err != nil {
return 0 , 0 , fmt . Errorf ( "fehler beim Aktualisieren der Praxispunkte: %v" , err )
}
break
}
}
// Falls nicht in normalen Fertigkeiten gefunden, prüfe Waffenfertigkeiten
for i := range char . Waffenfertigkeiten {
2025-08-05 21:29:00 +02:00
if char . Waffenfertigkeiten [ i ] . Name == itemName {
2025-08-05 21:12:03 +02:00
char . Waffenfertigkeiten [ i ] . Pp -= totalPP
if err := database . DB . Save ( & char . Waffenfertigkeiten [ i ] ) . Error ; err != nil {
return 0 , 0 , fmt . Errorf ( "fehler beim Aktualisieren der Praxispunkte: %v" , err )
}
break
}
}
}
return newEP , newGold , nil
}
2025-08-02 07:15:27 +02:00
// ImproveSkill verbessert eine bestehende Fertigkeit und erstellt Audit-Log-Einträge
// loadCharacterForImprovement lädt einen Charakter mit allen benötigten Beziehungen
func loadCharacterForImprovement ( characterID uint ) ( * models . Char , error ) {
var char models . Char
err := database . DB .
Preload ( "Fertigkeiten" ) .
Preload ( "Waffenfertigkeiten" ) .
Preload ( "Erfahrungsschatz" ) .
Preload ( "Vermoegen" ) .
Preload ( "Zauber" ) .
First ( & char , characterID ) . Error
return & char , err
}
// validateSkillForImprovement validiert Skill-Namen und ermittelt aktuelle Level
func validateSkillForImprovement ( char * models . Char , request * gsmaster . LernCostRequest ) ( string , * models . SkillLearningInfo , int , error ) {
// Verwende Klassenabkürzung wenn der Typ länger als 3 Zeichen ist
var characterClass string
if len ( char . Typ ) > 3 {
characterClass = gsmaster . GetClassAbbreviationNewSystem ( char . Typ )
} else {
characterClass = char . Typ
}
// Normalize skill/spell name (trim whitespace, proper case)
skillName := strings . TrimSpace ( request . Name )
2025-08-30 17:55:20 +02:00
skillInfo , err := models . GetSkillCategoryAndDifficultyNewSystem ( skillName , characterClass )
if err != nil {
return "" , nil , 0 , fmt . Errorf ( "Fertigkeit '%s' nicht gefunden oder nicht für Klasse '%s' verfügbar: %v" , skillName , characterClass , err )
2025-07-24 07:39:43 +02:00
}
2025-08-30 17:55:20 +02:00
// Aktuellen Level ermitteln, falls nicht angegeben oder zu klein
2025-07-24 07:39:43 +02:00
currentLevel := request . CurrentLevel
2025-08-30 17:55:20 +02:00
if currentLevel <= 3 {
currentLevel = getCurrentSkillLevel ( char , request . Name , "skill" )
2025-07-24 07:39:43 +02:00
if currentLevel == - 1 {
2025-08-30 17:55:20 +02:00
return "" , nil , 0 , fmt . Errorf ( "Fertigkeit nicht bei diesem Charakter vorhanden" )
2025-07-24 07:39:43 +02:00
}
2025-07-25 14:00:56 +02:00
request . CurrentLevel = currentLevel
2025-07-24 07:39:43 +02:00
}
2025-08-30 17:55:20 +02:00
return characterClass , skillInfo , currentLevel , nil
}
2025-07-24 07:39:43 +02:00
2025-08-30 17:55:20 +02:00
// calculateImprovementCosts berechnet die Gesamtkosten für Multi-Level-Verbesserungen
func calculateImprovementCosts ( char * models . Char , request * gsmaster . LernCostRequest , characterClass string , skillInfo * models . SkillLearningInfo , currentLevel , finalLevel int ) ( [ ] gsmaster . SkillCostResultNew , int , int , int , error ) {
var response [ ] gsmaster . SkillCostResultNew
2025-07-26 15:12:43 +02:00
var totalEP , totalGold , totalPP int
2025-07-25 14:00:56 +02:00
// Loop für jeden Level von currentLevel bis finalLevel
tempLevel := currentLevel
for tempLevel < finalLevel {
nextLevel := tempLevel + 1
// Erstelle temporären Request für diesen Level
2025-08-30 17:55:20 +02:00
tempRequest := * request
2025-07-25 14:00:56 +02:00
tempRequest . CurrentLevel = tempLevel
tempRequest . TargetLevel = nextLevel
// Berechne Kosten für diesen einen Level
var costResult gsmaster . SkillCostResultNew
2025-08-02 07:15:27 +02:00
costResult . CharacterID = fmt . Sprintf ( "%d" , char . ID )
2025-07-25 14:00:56 +02:00
costResult . CharacterClass = characterClass
costResult . SkillName = request . Name
2025-08-30 17:55:20 +02:00
err := CalculateSkillImproveCostNewSystem ( & tempRequest , & costResult , nextLevel , & tempRequest . UsePP , & tempRequest . UseGold , skillInfo )
2025-07-25 14:00:56 +02:00
if err != nil {
2025-08-30 17:55:20 +02:00
return nil , 0 , 0 , 0 , fmt . Errorf ( "Fehler bei der Kostenberechnung: %v" , err )
}
// für die nächste Runde die PP und Gold reduzieren die zum Lernen genutzt werden sollen
if costResult . PPUsed > 0 {
request . UsePP -= costResult . PPUsed
if request . UsePP < 0 {
request . UsePP = 0
}
2025-07-25 14:00:56 +02:00
}
2025-08-30 17:55:20 +02:00
if costResult . GoldUsed > 0 {
request . UseGold -= costResult . GoldUsed
if request . UseGold < 0 {
request . UseGold = 0
}
}
response = append ( response , costResult )
2025-07-25 14:00:56 +02:00
// Addiere die Kosten
totalEP += costResult . EP
totalGold += costResult . GoldCost
2025-07-26 15:12:43 +02:00
totalPP += costResult . PPUsed
2025-07-25 14:00:56 +02:00
tempLevel ++
2025-07-24 07:39:43 +02:00
}
2025-08-30 17:55:20 +02:00
return response , totalEP , totalGold , totalPP , nil
}
// validateResources prüft, ob genügend Ressourcen vorhanden sind
func validateResources ( char * models . Char , skillName string , totalEP , totalGold , totalPP int ) error {
2025-07-24 07:39:43 +02:00
// Prüfe, ob genügend EP vorhanden sind
2025-08-02 07:15:27 +02:00
currentEP := char . Erfahrungsschatz . EP
2025-07-25 14:00:56 +02:00
if currentEP < totalEP {
2025-08-30 17:55:20 +02:00
return fmt . Errorf ( "Nicht genügend Erfahrungspunkte vorhanden" )
2025-07-24 07:39:43 +02:00
}
// Prüfe, ob genügend Gold vorhanden ist
2025-12-19 17:04:20 +01:00
currentGold := char . Vermoegen . Goldstuecke
2025-07-25 14:00:56 +02:00
if currentGold < totalGold {
2025-08-30 17:55:20 +02:00
return fmt . Errorf ( "Nicht genügend Gold vorhanden" )
2025-07-24 07:39:43 +02:00
}
2025-07-26 15:12:43 +02:00
// Prüfe, ob genügend PP vorhanden sind (PP der jeweiligen Fertigkeit)
currentPP := 0
2025-08-02 07:15:27 +02:00
for _ , skill := range char . Fertigkeiten {
2025-08-30 17:55:20 +02:00
if skill . Name == skillName {
2025-07-26 15:12:43 +02:00
currentPP = skill . Pp
break
}
}
// Falls nicht in normalen Fertigkeiten gefunden, prüfe Waffenfertigkeiten
if currentPP == 0 {
2025-08-02 07:15:27 +02:00
for _ , skill := range char . Waffenfertigkeiten {
2025-08-30 17:55:20 +02:00
if skill . Name == skillName {
2025-07-26 15:12:43 +02:00
currentPP = skill . Pp
break
}
}
}
if totalPP > 0 && currentPP < totalPP {
2025-08-30 17:55:20 +02:00
return fmt . Errorf ( "Nicht genügend Praxispunkte vorhanden" )
2025-07-26 15:12:43 +02:00
}
2025-08-30 17:55:20 +02:00
return nil
}
// deductResources zieht die Kosten von den Charakterressourcen ab
// TODO Fehlerbehandlung (Falls Tabelle nicht vorhanden ist)
func deductResources ( char * models . Char , skillName string , currentLevel , finalLevel , totalEP , totalGold , totalPP int ) ( int , int , error ) {
currentEP := char . Erfahrungsschatz . EP
2025-12-19 17:04:20 +01:00
currentGold := char . Vermoegen . Goldstuecke
2025-08-30 17:55:20 +02:00
2025-07-24 07:39:43 +02:00
// EP abziehen und Audit-Log erstellen
2025-07-25 14:00:56 +02:00
newEP := currentEP - totalEP
if totalEP > 0 {
// Erstelle Notiz für Multi-Level Improvement
levelCount := finalLevel - currentLevel
var notes string
if levelCount > 1 {
2025-08-30 17:55:20 +02:00
notes = fmt . Sprintf ( "Fertigkeit '%s' von %d auf %d verbessert (%d Level)" , skillName , currentLevel , finalLevel , levelCount )
2025-07-25 14:00:56 +02:00
} else {
2025-08-30 17:55:20 +02:00
notes = fmt . Sprintf ( "Fertigkeit '%s' von %d auf %d verbessert" , skillName , currentLevel , finalLevel )
2025-07-24 07:39:43 +02:00
}
2025-08-30 17:55:20 +02:00
err := CreateAuditLogEntry ( char . ID , "experience_points" , currentEP , newEP , ReasonSkillImprovement , 0 , notes )
2025-07-24 07:39:43 +02:00
if err != nil {
2025-08-30 17:55:20 +02:00
return newEP , 0 , fmt . Errorf ( "Fehler beim Erstellen des Audit-Log-Eintrags: %v" , err )
2025-07-24 07:39:43 +02:00
}
2025-08-02 07:15:27 +02:00
char . Erfahrungsschatz . EP = newEP
if err := database . DB . Save ( & char . Erfahrungsschatz ) . Error ; err != nil {
2025-08-30 17:55:20 +02:00
return newEP , 0 , fmt . Errorf ( "Fehler beim Speichern der Erfahrungspunkte: %v" , err )
2025-07-26 15:12:43 +02:00
}
2025-07-24 07:39:43 +02:00
}
// Gold abziehen und Audit-Log erstellen
2025-07-25 14:00:56 +02:00
newGold := currentGold - totalGold
if totalGold > 0 {
2025-08-30 17:55:20 +02:00
notes := fmt . Sprintf ( "Gold für Verbesserung von '%s' ausgegeben" , skillName )
2025-07-24 07:39:43 +02:00
2025-08-30 17:55:20 +02:00
err := CreateAuditLogEntry ( char . ID , "gold" , currentGold , newGold , ReasonSkillImprovement , 0 , notes )
2025-07-24 07:39:43 +02:00
if err != nil {
2025-08-30 17:55:20 +02:00
return newEP , newGold , fmt . Errorf ( "Fehler beim Erstellen des Audit-Log-Eintrags: %v" , err )
2025-07-24 07:39:43 +02:00
}
2025-12-19 17:04:20 +01:00
char . Vermoegen . Goldstuecke = newGold
2025-08-02 07:15:27 +02:00
if err := database . DB . Save ( & char . Vermoegen ) . Error ; err != nil {
2025-08-30 17:55:20 +02:00
return newEP , newGold , fmt . Errorf ( "Fehler beim Speichern des Vermögens: %v" , err )
2025-07-26 15:12:43 +02:00
}
}
// PP abziehen wenn verwendet (PP der jeweiligen Fertigkeit)
if totalPP > 0 {
// Finde die richtige Fertigkeit und ziehe PP ab
2025-08-02 07:15:27 +02:00
for i := range char . Fertigkeiten {
2025-08-30 17:55:20 +02:00
if char . Fertigkeiten [ i ] . Name == skillName {
2025-08-02 07:15:27 +02:00
char . Fertigkeiten [ i ] . Pp -= totalPP
if err := database . DB . Save ( & char . Fertigkeiten [ i ] ) . Error ; err != nil {
2025-08-30 17:55:20 +02:00
return newEP , newGold , fmt . Errorf ( "Fehler beim Aktualisieren der Praxispunkte: %v" , err )
2025-07-26 15:12:43 +02:00
}
break
}
}
// Falls nicht in normalen Fertigkeiten gefunden, prüfe Waffenfertigkeiten
2025-08-02 07:15:27 +02:00
for i := range char . Waffenfertigkeiten {
2025-08-30 17:55:20 +02:00
if char . Waffenfertigkeiten [ i ] . Name == skillName {
2025-08-02 07:15:27 +02:00
char . Waffenfertigkeiten [ i ] . Pp -= totalPP
if err := database . DB . Save ( & char . Waffenfertigkeiten [ i ] ) . Error ; err != nil {
2025-08-30 17:55:20 +02:00
return newEP , newGold , fmt . Errorf ( "Fehler beim Aktualisieren der Praxispunkte: %v" , err )
2025-07-26 15:12:43 +02:00
}
break
}
}
2025-07-24 07:39:43 +02:00
}
2025-08-30 17:55:20 +02:00
return newEP , newGold , nil
}
2025-07-24 07:39:43 +02:00
2025-08-30 17:55:20 +02:00
func ImproveSkill ( c * gin . Context ) {
var request gsmaster . LernCostRequest
if err := c . ShouldBindJSON ( & request ) ; err != nil {
respondWithError ( c , http . StatusBadRequest , "Ungültige Anfrageparameter: " + err . Error ( ) )
2025-07-24 07:39:43 +02:00
return
}
2025-08-30 17:55:20 +02:00
// 1. Charakter laden
char , err := loadCharacterForImprovement ( request . CharId )
if err != nil {
2025-07-24 07:39:43 +02:00
respondWithError ( c , http . StatusNotFound , "Charakter nicht gefunden" )
return
}
2025-08-30 17:55:20 +02:00
// 2. Skill validieren und Level ermitteln
characterClass , skillInfo , currentLevel , err := validateSkillForImprovement ( char , & request )
if err != nil {
respondWithError ( c , http . StatusBadRequest , err . Error ( ) )
2025-07-24 07:39:43 +02:00
return
}
2025-08-30 17:55:20 +02:00
// Bestimme das finale Level
finalLevel := request . TargetLevel
if finalLevel <= 0 {
finalLevel = currentLevel + 1
2025-07-24 07:39:43 +02:00
}
2025-08-30 17:55:20 +02:00
// 3. Kosten berechnen
response , totalEP , totalGold , totalPP , err := calculateImprovementCosts ( char , & request , characterClass , skillInfo , currentLevel , finalLevel )
2025-07-24 07:39:43 +02:00
if err != nil {
2025-08-30 17:55:20 +02:00
respondWithError ( c , http . StatusBadRequest , err . Error ( ) )
2025-07-24 07:39:43 +02:00
return
}
2025-08-30 17:55:20 +02:00
// 4. Ressourcen validieren
err = validateResources ( char , request . Name , totalEP , totalGold , totalPP )
if err != nil {
respondWithError ( c , http . StatusBadRequest , err . Error ( ) )
2025-07-24 07:39:43 +02:00
return
}
2025-08-30 17:55:20 +02:00
// 5. Ressourcen abziehen
newEP , newGold , err := deductResources ( char , request . Name , currentLevel , finalLevel , totalEP , totalGold , totalPP )
if err != nil {
respondWithError ( c , http . StatusInternalServerError , err . Error ( ) )
return
2025-07-24 07:39:43 +02:00
}
2025-08-30 17:55:20 +02:00
// 6. Skill-Level aktualisieren
if err := updateOrCreateSkill ( char , request . Name , finalLevel ) ; err != nil {
respondWithError ( c , http . StatusInternalServerError , "Fehler beim Aktualisieren der Fertigkeit: " + err . Error ( ) )
2025-07-26 15:12:43 +02:00
return
}
2025-07-24 07:39:43 +02:00
2025-08-30 17:55:20 +02:00
// 7. Charakter speichern
if err := database . DB . Save ( char ) . Error ; err != nil {
2025-07-24 07:39:43 +02:00
respondWithError ( c , http . StatusInternalServerError , "Fehler beim Speichern des Charakters" )
return
}
2025-08-30 17:55:20 +02:00
// 8. Response erstellen
responseData := gin . H {
"message" : "Fertigkeit erfolgreich verbessert" ,
"skill_name" : request . Name ,
"from_level" : currentLevel ,
"to_level" : finalLevel ,
"ep_cost" : totalEP ,
"gold_cost" : totalGold ,
"remaining_ep" : newEP ,
"remaining_gold" : newGold ,
"cost_details" : response ,
}
// Füge Multi-Level-spezifische Informationen hinzu
levelCount := finalLevel - currentLevel
if levelCount > 1 {
var levelsLearned [ ] int
for i := currentLevel + 1 ; i <= finalLevel ; i ++ {
levelsLearned = append ( levelsLearned , i )
}
responseData [ "levels_learned" ] = levelsLearned
responseData [ "level_count" ] = levelCount
responseData [ "multi_level" ] = true
}
c . JSON ( http . StatusOK , responseData )
2025-07-24 07:39:43 +02:00
}
2025-08-05 21:29:00 +02:00
// validateSpellForLearning validiert Zauber-Namen für neue Zauber (Learning)
func validateSpellForLearning ( char * models . Char , request * gsmaster . LernCostRequest ) ( string , * models . SpellLearningInfo , int , error ) {
// Verwende Klassenabkürzung wenn der Typ länger als 3 Zeichen ist
var characterClass string
if len ( char . Typ ) > 3 {
characterClass = gsmaster . GetClassAbbreviationNewSystem ( char . Typ )
} else {
characterClass = char . Typ
}
// Normalize spell name (trim whitespace, proper case)
spellName := strings . TrimSpace ( request . Name )
spellInfo , err := models . GetSpellLearningInfoNewSystem ( spellName , characterClass )
if err != nil {
return "" , nil , 0 , fmt . Errorf ( "zauber '%s' nicht gefunden oder nicht für Klasse '%s' verfügbar: %v" , spellName , characterClass , err )
}
// Für Learning starten wir bei Level 0
currentLevel := 0
// Prüfe, ob der Zauber bereits existiert
for _ , spell := range char . Zauber {
if spell . Name == request . Name {
return "" , nil , 0 , fmt . Errorf ( "zauber '%s' ist bereits gelernt - Zauber können nicht verbessert werden" , request . Name )
}
}
return characterClass , spellInfo , currentLevel , nil
}
// calculateSpellLearningCosts berechnet die Kosten für das Erlernen eines neuen Zaubers
func calculateSpellLearningCosts ( char * models . Char , request * gsmaster . LernCostRequest , characterClass string , spellInfo * models . SpellLearningInfo , currentLevel , finalLevel int ) ( [ ] gsmaster . SkillCostResultNew , int , error ) {
var response [ ] gsmaster . SkillCostResultNew
var totalEP int
// Erstelle cost result structure für Zauber
costResult := gsmaster . SkillCostResultNew {
CharacterID : fmt . Sprintf ( "%d" , char . ID ) ,
CharacterClass : characterClass ,
SkillName : request . Name ,
TargetLevel : finalLevel ,
}
remainingPP := 0
remainingGold := 0
// Verwende die Spell-spezifische Kostenfunktion
err := calculateSpellLearnCostNewSystem ( request , & costResult , & remainingPP , & remainingGold , spellInfo )
if err != nil {
return nil , 0 , fmt . Errorf ( "fehler bei der Kostenberechnung: %v" , err )
}
response = append ( response , costResult )
totalEP = costResult . EP
// Zauber haben normalerweise keine Gold- oder PP-Kosten
return response , totalEP , nil
}
// LearnSpell lernt einen neuen Zauber und erstellt Audit-Log-Einträge
func LearnSpell ( c * gin . Context ) {
2025-08-08 06:35:11 +02:00
char_ID := c . Param ( "id" )
/*
var character models.Char
2025-08-05 21:29:00 +02:00
2025-08-08 06:35:11 +02:00
if err := character.FirstID(charID); err != nil {
respondWithError(c, http.StatusNotFound, "Charakter nicht gefunden")
return
}
*/
charIDInt , err := strconv . Atoi ( char_ID )
if err != nil {
respondWithError ( c , http . StatusBadRequest , "Ungültige Charakter-ID" )
2025-08-05 21:29:00 +02:00
return
}
2025-08-08 06:35:11 +02:00
charID := uint ( charIDInt )
2025-08-05 21:29:00 +02:00
2025-08-08 06:35:11 +02:00
var lernRequest gsmaster . LernCostRequest
if err := c . ShouldBindJSON ( & lernRequest ) ; err != nil {
2025-08-05 21:29:00 +02:00
respondWithError ( c , http . StatusBadRequest , "Ungültige Anfrageparameter: " + err . Error ( ) )
return
}
2025-08-08 06:35:11 +02:00
// Setze die CharId aus der URL, falls sie nicht im Request enthalten ist
if lernRequest . CharId == 0 {
lernRequest . CharId = charID
}
// Setze Standard-Werte für Spell Learning falls nicht gesetzt
if lernRequest . Type == "" {
lernRequest . Type = "spell"
}
if lernRequest . Action == "" {
lernRequest . Action = "learn"
}
if lernRequest . CurrentLevel == 0 && lernRequest . TargetLevel == 0 {
lernRequest . CurrentLevel = 0 // Zauber sind nicht gelernt
lernRequest . TargetLevel = 1 // Zauber werden auf Level 1 gelernt
2025-08-05 21:29:00 +02:00
}
// 1. Charakter laden
char , err := loadCharacterForImprovement ( lernRequest . CharId )
if err != nil {
respondWithError ( c , http . StatusNotFound , "Charakter nicht gefunden" )
return
}
// 2. Zauber validieren (für Learning beginnen wir bei Level 0)
characterClass , spellInfo , currentLevel , err := validateSpellForLearning ( char , & lernRequest )
if err != nil {
respondWithError ( c , http . StatusBadRequest , err . Error ( ) )
return
}
finalLevel := 1 // Zauber werden immer auf Level 1 gelernt
// 3. Kosten berechnen (von Level 0 bis 1)
response , totalEP , err := calculateSpellLearningCosts ( char , & lernRequest , characterClass , spellInfo , currentLevel , finalLevel )
if err != nil {
respondWithError ( c , http . StatusBadRequest , err . Error ( ) )
return
}
// 4. Ressourcen validieren (nur EP für Zauber)
err = validateResources ( char , lernRequest . Name , totalEP , 0 , 0 ) // Gold=0, PP=0 für Zauber
if err != nil {
respondWithError ( c , http . StatusBadRequest , err . Error ( ) )
return
}
// 5. Ressourcen abziehen
newEP , _ , err := deductResourcesWithAuditReason ( char , lernRequest . Name , 1 , totalEP , 0 , 0 , ReasonSpellLearning )
if err != nil {
respondWithError ( c , http . StatusInternalServerError , err . Error ( ) )
return
}
// 6. Zauber hinzufügen
if err := addSpellToCharacter ( char , lernRequest . Name ) ; err != nil {
respondWithError ( c , http . StatusInternalServerError , "Fehler beim Hinzufügen des Zaubers: " + err . Error ( ) )
return
}
// 7. Charakter speichern
if err := database . DB . Save ( char ) . Error ; err != nil {
respondWithError ( c , http . StatusInternalServerError , "Fehler beim Speichern des Charakters" )
return
}
// 8. Response erstellen (kompatibel mit alter Version)
responseData := gin . H {
"message" : "Zauber erfolgreich gelernt" ,
"spell_name" : lernRequest . Name ,
"ep_cost" : totalEP ,
"remaining_ep" : newEP ,
"cost_details" : response ,
}
c . JSON ( http . StatusOK , responseData )
}
2025-08-30 17:55:20 +02:00
// GetRewardTypesStatic is deprecated. Use GetRewardTypes instead.
2025-08-01 13:53:10 +02:00
// This function provides hardcoded reward type mappings.
2025-08-30 17:55:20 +02:00
// GetRewardTypesStatic liefert verfügbare Belohnungsarten für ein bestimmtes Lernszenario
func GetRewardTypesStatic ( c * gin . Context ) {
2025-07-24 07:39:43 +02:00
characterID := c . Param ( "id" )
learningType := c . Query ( "learning_type" ) // 'improve', 'learn', 'spell'
skillName := c . Query ( "skill_name" )
skillType := c . Query ( "skill_type" ) // 'skill', 'weapon', 'spell'
// Basis-Belohnungsarten
rewardTypes := [ ] gin . H { }
// Je nach Lerntyp verschiedene Belohnungsarten anbieten
switch learningType {
case "learn" :
2025-07-24 23:57:59 +02:00
// Neue Fertigkeit lernen - noGold Belohnung verfügbar
2025-07-24 07:39:43 +02:00
rewardTypes = append ( rewardTypes ,
2025-07-24 23:57:59 +02:00
gin . H { "value" : "default" , "label" : "Standard (EP + Gold)" , "description" : "Normale EP- und Goldkosten" } ,
gin . H { "value" : "noGold" , "label" : "Ohne Gold (nur EP)" , "description" : "Keine Goldkosten, nur EP als Belohnung" } ,
2025-07-24 07:39:43 +02:00
)
case "spell" :
2025-07-24 23:57:59 +02:00
// Zauber lernen - halveepnoGold verfügbar
2025-07-24 07:39:43 +02:00
rewardTypes = append ( rewardTypes ,
2025-07-24 23:57:59 +02:00
gin . H { "value" : "default" , "label" : "Standard (EP)" , "description" : "Normale EP-Kosten" } ,
gin . H { "value" : "halveepnoGold" , "label" : "Halbe EP ohne Gold" , "description" : "Halbe EP-Kosten, kein Gold als Belohnung" } ,
2025-07-24 07:39:43 +02:00
)
case "improve" :
2025-07-24 23:57:59 +02:00
// Fertigkeit verbessern - halveepnoGold verfügbar
2025-07-24 07:39:43 +02:00
rewardTypes = append ( rewardTypes ,
2025-07-24 23:57:59 +02:00
gin . H { "value" : "default" , "label" : "Standard (EP + Gold)" , "description" : "Normale EP- und Goldkosten" } ,
gin . H { "value" : "halveepnoGold" , "label" : "Halbe EP ohne Gold" , "description" : "Halbe EP-Kosten, kein Gold als Belohnung" } ,
2025-07-24 07:39:43 +02:00
)
// Spezielle Optionen für bestimmte Fertigkeiten
if skillType == "weapon" {
// Waffenfertigkeiten könnten spezielle Trainingsmethoden haben
rewardTypes = append ( rewardTypes ,
gin . H { "value" : "training" , "label" : "Training mit Meister" , "description" : "Intensives Training mit einem Waffenmeister" } ,
)
}
default :
}
c . JSON ( http . StatusOK , gin . H {
"reward_types" : rewardTypes ,
"learning_type" : learningType ,
"skill_name" : skillName ,
"skill_type" : skillType ,
"character_id" : characterID ,
} )
}
2025-07-26 23:15:11 +02:00
2025-07-31 11:01:32 +02:00
// GetAvailableSkillsNewSystem gibt alle verfügbaren Fertigkeiten mit Lernkosten zurück (POST mit LernCostRequest)
func GetAvailableSkillsNewSystem ( c * gin . Context ) {
// Parse LernCostRequest aus POST body
var baseRequest gsmaster . LernCostRequest
if err := c . ShouldBindJSON ( & baseRequest ) ; err != nil {
respondWithError ( c , http . StatusBadRequest , "Ungültige Anfrageparameter: " + err . Error ( ) )
return
}
2025-07-31 10:28:35 +02:00
2025-08-14 23:25:01 +02:00
// For character creation (char_id = 0), we don't need to load an existing character
2025-07-31 10:28:35 +02:00
var character models . Char
2025-08-14 23:25:01 +02:00
learnedSkills := make ( map [ string ] bool )
2025-08-21 20:57:55 +02:00
2025-08-14 23:25:01 +02:00
if baseRequest . CharId != 0 {
// Load existing character and their learned skills
if err := database . DB . Preload ( "Fertigkeiten" ) . Preload ( "Erfahrungsschatz" ) . Preload ( "Vermoegen" ) . First ( & character , baseRequest . CharId ) . Error ; err != nil {
respondWithError ( c , http . StatusNotFound , "Character not found" )
return
}
2025-08-21 20:57:55 +02:00
2025-08-14 23:25:01 +02:00
// Create map of learned skills for existing character
for _ , skill := range character . Fertigkeiten {
learnedSkills [ skill . Name ] = true
}
2025-07-31 10:28:35 +02:00
}
2025-08-14 23:25:01 +02:00
// For character creation (char_id = 0), learnedSkills remains empty
2025-07-31 10:28:35 +02:00
// Hole alle verfügbaren Fertigkeiten aus der gsmaster Datenbank, aber filtere Placeholder aus
var allSkills [ ] models . Skill
allSkills , err := models . SelectSkills ( "" , "" )
if err != nil {
respondWithError ( c , http . StatusInternalServerError , "Failed to retrieve skills from gsmaster" )
return
}
/*if err := database.DB.Where("name != ?", "Placeholder").Find(&allSkills).Error; err != nil {
respondWithError(c, http.StatusInternalServerError, "Failed to retrieve skills")
return
}
*/
// Organisiere Fertigkeiten nach Kategorien
skillsByCategory := make ( map [ string ] [ ] gin . H )
for _ , skill := range allSkills {
// Überspringe bereits gelernte Fertigkeiten
if learnedSkills [ skill . Name ] {
continue
}
// Überspringe Placeholder-Fertigkeiten (zusätzliche Sicherheit)
if skill . Name == "Placeholder" {
continue
}
2025-07-31 11:01:32 +02:00
// Erstelle LernCostRequest für diese Fertigkeit basierend auf der Basis-Anfrage
request := baseRequest
2025-08-14 23:25:01 +02:00
request . CharId = baseRequest . CharId // Use the char_id from the request (0 for character creation)
2025-07-31 11:01:32 +02:00
request . Name = skill . Name
request . CurrentLevel = 0 // Nicht gelernt
request . TargetLevel = 1 // Auf Level 1 lernen
request . Type = "skill"
request . Action = "learn"
// Erstelle SkillCostResultNew
2025-08-14 23:25:01 +02:00
characterClass := ""
characterID := "0"
2025-08-21 20:57:55 +02:00
2025-08-14 23:25:01 +02:00
if baseRequest . CharId != 0 {
// Use existing character data
characterID = fmt . Sprintf ( "%d" , character . ID )
2025-08-30 17:55:20 +02:00
characterClass = getCharacterClass ( & character )
2025-08-14 23:25:01 +02:00
}
// For character creation, we don't have a character class yet, use empty string
2025-08-21 20:57:55 +02:00
2025-07-31 11:01:32 +02:00
levelResult := gsmaster . SkillCostResultNew {
2025-08-14 23:25:01 +02:00
CharacterID : characterID ,
CharacterClass : characterClass ,
2025-07-31 11:01:32 +02:00
SkillName : skill . Name ,
TargetLevel : 1 ,
2025-07-31 10:28:35 +02:00
}
2025-07-31 11:01:32 +02:00
2025-07-31 10:28:35 +02:00
remainingPP := request . UsePP
remainingGold := request . UseGold
2025-07-31 22:49:46 +02:00
2025-07-31 13:43:29 +02:00
// Hole die vollständigen Skill-Informationen für die Kostenberechnung
2025-08-14 23:25:01 +02:00
skillLearningInfo , err := models . GetSkillCategoryAndDifficultyNewSystem ( skill . Name , characterClass )
2025-07-31 13:43:29 +02:00
if err != nil {
// Fallback für unbekannte Skills
skillLearningInfo = & models . SkillLearningInfo {
SkillName : skill . Name ,
CategoryName : skill . Category ,
LearnCost : 50 , // Standard-Lernkosten
}
}
2025-07-31 11:01:32 +02:00
2025-08-21 20:57:55 +02:00
// For character creation (CharId = 0), use learning costs instead of improvement costs
var epCost , goldCost int
if baseRequest . CharId == 0 {
// Character creation: use basic learning costs from skillLearningInfo
learnCost := skillLearningInfo . LearnCost
if learnCost == 0 {
learnCost = 50 // Default learning cost
}
// For character creation, costs are much lower - just the basic learning cost
epCost = learnCost * 2 // Simple formula: learning cost * 2 for EP
goldCost = learnCost * 5 // Simple formula: learning cost * 5 for gold
} else {
// Existing character improvement: use the full system
err = calculateSkillLearnCostNewSystem ( & request , & levelResult , & remainingPP , & remainingGold , skillLearningInfo )
epCost = 10000 // Fallback-Wert for improvements
goldCost = 50000 // Fallback-Wert for improvements
if err == nil {
epCost = levelResult . EP
goldCost = levelResult . GoldCost
}
2025-07-31 10:28:35 +02:00
}
skillInfo := gin . H {
"name" : skill . Name ,
"epCost" : epCost ,
"goldCost" : goldCost ,
}
category := skill . Category
if category == "" {
category = "Sonstige"
}
skillsByCategory [ category ] = append ( skillsByCategory [ category ] , skillInfo )
}
c . JSON ( http . StatusOK , gin . H {
"skills_by_category" : skillsByCategory ,
} )
}
2025-08-26 22:31:46 +02:00
// getCharacterClassCode converts a character class name to its code using the database
func getCharacterClassCode ( className string ) ( string , error ) {
var characterClass models . CharacterClass
2025-12-06 22:43:53 +01:00
err := characterClass . FirstByNameOrCode ( className )
2025-08-26 22:31:46 +02:00
if err != nil {
2025-12-06 22:43:53 +01:00
return "" , fmt . Errorf ( "character class '%s' not found: %w" , className , err )
2025-08-26 22:31:46 +02:00
}
return characterClass . Code , nil
}
func GetAvailableSpellsForCreation ( c * gin . Context ) {
var request struct {
CharacterClass string ` json:"characterClass" binding:"required" `
}
if err := c . ShouldBindJSON ( & request ) ; err != nil {
logger . Warn ( "HTTP Fehler 400: Ungültige Anfrageparameter: %v" , err )
c . JSON ( http . StatusBadRequest , gin . H {
"error" : "Ungültige Anfrageparameter" ,
"details" : err . Error ( ) ,
} )
return
}
logger . Info ( "GetAvailableSpellsForCreation - CharacterClass: %s" , request . CharacterClass )
// Convert character class name to code
characterClassCode , err := getCharacterClassCode ( request . CharacterClass )
if err != nil {
logger . Error ( "Fehler beim Konvertieren der Charakterklasse: %v" , err )
c . JSON ( http . StatusBadRequest , gin . H {
"error" : fmt . Sprintf ( "Unbekannte Charakterklasse: %s" , request . CharacterClass ) ,
} )
return
}
// Get all available spells with their learning costs
spellsByCategory , err := GetAllSpellsWithLE ( characterClassCode , 2 )
if err != nil {
logger . Error ( "Fehler beim Abrufen der Zauber: %v" , err )
c . JSON ( http . StatusInternalServerError , gin . H {
"error" : "Fehler beim Abrufen der Zauber" ,
} )
return
}
logger . Info ( "GetAvailableSpellsForCreation - Gefundene Kategorien: %d" , len ( spellsByCategory ) )
2025-08-27 21:36:06 +02:00
if len ( spellsByCategory ) == 0 {
logger . Warn ( "GetAvailableSpellsForCreation - Keine Zauber für Klasse %s gefunden" , request . CharacterClass )
c . JSON ( http . StatusNotFound , gin . H {
"spells_by_category" : map [ string ] [ ] gin . H { } ,
} )
return
}
2025-08-26 22:31:46 +02:00
c . JSON ( http . StatusOK , gin . H {
"spells_by_category" : spellsByCategory ,
} )
}
2025-08-21 20:57:55 +02:00
// GetAvailableSkillsForCreation returns skills with learning costs for character creation
func GetAvailableSkillsForCreation ( c * gin . Context ) {
var request struct {
CharacterClass string ` json:"characterClass" binding:"required" `
}
if err := c . ShouldBindJSON ( & request ) ; err != nil {
logger . Warn ( "HTTP Fehler 400: Ungültige Anfrageparameter: %v" , err )
c . JSON ( http . StatusBadRequest , gin . H {
"error" : "Ungültige Anfrageparameter" ,
"details" : err . Error ( ) ,
} )
return
}
logger . Info ( "GetAvailableSkillsForCreation - CharacterClass: %s" , request . CharacterClass )
// Get all available skills with their learning costs
skillsByCategory , err := GetAllSkillsWithLE ( )
if err != nil {
logger . Error ( "Fehler beim Abrufen der Fertigkeiten: %v" , err )
c . JSON ( http . StatusInternalServerError , gin . H {
"error" : "Fehler beim Abrufen der Fertigkeiten" ,
} )
return
}
logger . Info ( "GetAvailableSkillsForCreation - Gefundene Kategorien: %d" , len ( skillsByCategory ) )
c . JSON ( http . StatusOK , gin . H {
"skills_by_category" : skillsByCategory ,
} )
}
2025-08-26 22:31:46 +02:00
func GetAllSpellsWithLE ( characterClass string , maxLevel int ) ( map [ string ] [ ] gin . H , error ) {
// Create mapping of character classes to allowed learning categories
2025-08-27 21:36:06 +02:00
allowedCategories := getCharacterClassSpellSchoolMapping ( )
allowedLearningCategories := getCharacterClassSpellLearningCategoriesMapping ( )
2025-08-26 22:31:46 +02:00
// Check if character class has allowed spell schools
2025-08-27 21:36:06 +02:00
allowedSchools , exists := allowedCategories [ characterClass ]
2025-08-26 22:31:46 +02:00
if ! exists {
return map [ string ] [ ] gin . H { } , nil // Return empty map if class can't learn spells
}
2025-08-27 21:36:06 +02:00
allowedSpellType , exists := allowedLearningCategories [ characterClass ]
if ! exists {
return map [ string ] [ ] gin . H { } , nil // Return empty map if class can't learn spells
}
// Extract allowed school names and spell types from maps
var allowedSchoolNames [ ] string
for school , allowed := range allowedSchools {
if allowed {
allowedSchoolNames = append ( allowedSchoolNames , school )
}
}
var allowedSpellTypeNames [ ] string
for spellType , allowed := range allowedSpellType {
if allowed {
allowedSpellTypeNames = append ( allowedSpellTypeNames , spellType )
}
}
2025-08-26 22:31:46 +02:00
// Get all spells from database with level filter
var spells [ ] models . Spell
2025-08-27 21:36:06 +02:00
err := database . DB . Where ( "stufe <= ? AND category in (?) and learning_category in (?) AND category IS NOT NULL AND learning_category IS NOT NULL" , maxLevel , allowedSchoolNames , allowedSpellTypeNames ) . Find ( & spells ) . Error
2025-08-26 22:31:46 +02:00
if err != nil {
return nil , fmt . Errorf ( "failed to fetch spells: %w" , err )
}
// Group spells by category (using LearningCategory field)
spellsByCategory := make ( map [ string ] [ ] gin . H )
for _ , spell := range spells {
// Check if this character class can learn this spell school
2025-08-27 21:36:06 +02:00
//if !allowedSchools[spell.Category] || !allowedSpellType[spell.LearningCategory] {
// continue // Skip spells from schools this class can't learn
//}
2025-08-26 22:31:46 +02:00
// Calculate learning cost for this spell
leCost := getSpellLECost ( spell . Stufe )
spellData := gin . H {
"id" : spell . ID ,
"name" : spell . Name ,
"level" : spell . Stufe ,
"school" : spell . Category , // Display category
"learning_category" : spell . LearningCategory , // Internal category for learning
"description" : spell . Beschreibung ,
"le_cost" : leCost ,
"ap" : spell . AP ,
"art" : spell . Art ,
"zauberdauer" : spell . Zauberdauer ,
"reichweite" : spell . Reichweite ,
"wirkungsziel" : spell . Wirkungsziel ,
"wirkungsbereich" : spell . Wirkungsbereich ,
"wirkungsdauer" : spell . Wirkungsdauer ,
}
// Use LearningCategory for grouping
category := spell . LearningCategory
spellsByCategory [ category ] = append ( spellsByCategory [ category ] , spellData )
}
// Sort spells within each category by level, then by name
for category := range spellsByCategory {
spells := spellsByCategory [ category ]
sort . Slice ( spells , func ( i , j int ) bool {
levelI := spells [ i ] [ "level" ] . ( int )
levelJ := spells [ j ] [ "level" ] . ( int )
if levelI != levelJ {
return levelI < levelJ
}
nameI := spells [ i ] [ "name" ] . ( string )
nameJ := spells [ j ] [ "name" ] . ( string )
return nameI < nameJ
} )
spellsByCategory [ category ] = spells
}
return spellsByCategory , nil
}
2025-08-27 21:36:06 +02:00
func getCharacterClassSpellLearningCategoriesMapping ( ) map [ string ] map [ string ] bool {
return map [ string ] map [ string ] bool {
"Ma" : { // Magier
"Spruch" : true ,
//"Salz": true,
//"Runenstab": true,
} ,
"Hx" : { // Hexer
"Spruch" : true ,
"Salz" : true ,
//"Runenstab": true,
} ,
"Dr" : { // Druide
"Spruch" : true ,
//"Salz": true,
//"Runenstab": true,
"Dweomer" : true ,
} ,
"Sc" : { // Schamane
"Spruch" : true ,
//"Salz": true,
//"Runenstab": true,
"Dweomer" : true ,
} ,
"PB" : { // Priester Beschützer
"Salz" : true ,
"Wundertat" : true ,
} ,
"PS" : { // Priester Streiter
"Salz" : true ,
"Wundertat" : true ,
} ,
"Ba" : { // Barde
"Lied" : true ,
} ,
"Or" : { // Ordenskrieger
"Wundertat" : true ,
} ,
}
}
2025-08-26 22:31:46 +02:00
// getCharacterClassSpellSchoolMapping returns the mapping of character classes to allowed spell schools
func getCharacterClassSpellSchoolMapping ( ) map [ string ] map [ string ] bool {
return map [ string ] map [ string ] bool {
"Ma" : { // Magier
"Beherrschen" : true ,
"Bewegen" : true ,
"Dweomer" : true ,
"Erkennen" : true ,
"Erschaffen" : true ,
"Verändern" : true ,
"Zerstören" : true ,
} ,
"Hx" : { // Hexer
"Beherrschen" : true ,
2025-08-27 21:36:06 +02:00
"Zerstören" : true ,
2025-08-26 22:31:46 +02:00
"Erkennen" : true ,
"Verändern" : true ,
2025-08-27 21:36:06 +02:00
"Erschaffen" : true ,
"Bewegen" : true ,
"Formen" : true ,
2025-08-26 22:31:46 +02:00
} ,
"Dr" : { // Druide
"Bewegen" : true ,
"Erkennen" : true ,
"Erschaffen" : true ,
"Verändern" : true ,
} ,
"Sc" : { // Schamane
"Beherrschen" : true ,
"Erkennen" : true ,
"Verändern" : true ,
} ,
"PB" : { // Priester Beschützer
"Dweomer" : true ,
"Erkennen" : true ,
"Verändern" : true ,
} ,
"PS" : { // Priester Streiter
"Dweomer" : true ,
"Erkennen" : true ,
"Verändern" : true ,
} ,
"Ba" : { // Barde
"Beherrschen" : true ,
"Dweomer" : true ,
"Erkennen" : true ,
} ,
"Or" : { // Ordenskrieger
"Dweomer" : true ,
"Erkennen" : true ,
} ,
}
}
// getSpellLECost returns the learning cost in LE for a given spell level from the database
func getSpellLECost ( level int ) int {
var spellLECost models . SpellLevelLECost
// Query the database for the LE cost for this level
err := database . DB . Where ( "level = ? AND game_system = ?" , level , "midgard" ) . First ( & spellLECost ) . Error
if err != nil {
// If not found in database, fall back to standard Midgard costs
spellLECosts := map [ int ] int {
1 : 1 ,
2 : 2 ,
3 : 3 ,
4 : 4 ,
5 : 5 ,
6 : 6 ,
7 : 8 ,
8 : 10 ,
9 : 12 ,
10 : 15 ,
11 : 18 ,
12 : 21 ,
}
if cost , exists := spellLECosts [ level ] ; exists {
return cost
}
// Final fallback for unknown levels
return level
}
return spellLECost . LERequired
}
2025-08-21 20:57:55 +02:00
func GetAllSkillsWithLE ( ) ( map [ string ] [ ] gin . H , error ) {
// Get all skill categories from database
var skillCategories [ ] models . SkillCategory
if err := database . DB . Find ( & skillCategories ) . Error ; err != nil {
return nil , err
}
skillsByCategory := make ( map [ string ] [ ] gin . H )
// For each category, find all skills that can be learned in that category
for _ , category := range skillCategories {
skillsByCategory [ category . Name ] = [ ] gin . H { }
// Query all skill-category-difficulty combinations for this category
var skillCategoryDifficulties [ ] models . SkillCategoryDifficulty
err := database . DB . Preload ( "Skill" ) . Preload ( "SkillDifficulty" ) .
Where ( "skill_category_id = ?" , category . ID ) .
Find ( & skillCategoryDifficulties ) . Error
if err != nil {
continue // Skip this category if there's an error
}
// For each skill in this category, add it with its LE cost and difficulty
for _ , scd := range skillCategoryDifficulties {
// Skip Placeholder skills
if category . Name == "Unbekannt" || scd . Skill . Name == "Placeholder" || scd . Skill . InnateSkill {
continue
}
skillInfo := gin . H {
"name" : scd . Skill . Name ,
"leCost" : scd . LearnCost ,
"difficulty" : scd . SkillDifficulty . Name ,
}
skillsByCategory [ category . Name ] = append ( skillsByCategory [ category . Name ] , skillInfo )
}
}
2025-08-21 22:57:13 +02:00
// Add weapon skills to "Kampf" category
weaponSkills , err := GetWeaponSkillsWithLE ( )
if err == nil {
if _ , exists := skillsByCategory [ "Waffen" ] ; ! exists {
skillsByCategory [ "Waffen" ] = [ ] gin . H { }
}
skillsByCategory [ "Waffen" ] = append ( skillsByCategory [ "Waffen" ] , weaponSkills ... )
}
2025-08-21 20:57:55 +02:00
return skillsByCategory , nil
}
2025-08-21 22:57:13 +02:00
// GetWeaponSkillsWithLE returns all weapon skills with their learning costs
func GetWeaponSkillsWithLE ( ) ( [ ] gin . H , error ) {
// Query weapon skills from gsm_weaponskills table
var weaponSkills [ ] struct {
ID uint ` gorm:"column:id" `
Name string ` gorm:"column:name" `
}
err := database . DB . Table ( "gsm_weaponskills" ) .
Select ( "id, name" ) .
Where ( "name IS NOT NULL AND name != ''" ) .
Find ( & weaponSkills ) . Error
if err != nil {
return nil , err
}
var result [ ] gin . H
for _ , weapon := range weaponSkills {
// Get learning cost for this weapon skill
// For now, use default costs, but this could be enhanced to query from a learning costs table
leCost := getDefaultWeaponSkillCost ( weapon . Name )
skillInfo := gin . H {
"name" : weapon . Name ,
"leCost" : leCost ,
"difficulty" : "Normal" , // Default difficulty for weapon skills
"type" : "weapon" , // Mark as weapon skill
}
result = append ( result , skillInfo )
}
return result , nil
}
// getDefaultWeaponSkillCost returns default learning costs for weapon skills
func getDefaultWeaponSkillCost ( weaponName string ) int {
// Basic weapon skill costs - these could be made configurable or stored in database
switch weaponName {
case "Schilde" :
return 1 // Shields are easier to learn
case "Einhandschwerter" , "Zweihandschwerter" , "Fechtwaffen" :
return 4 // Sword skills are more expensive
case "Bögen" , "Armbrüste" :
return 3 // Ranged weapons
case "Spießwaffen" , "Stielwurfwaffen" :
return 2 // Polearms and throwing weapons
default :
return 2 // Default cost for other weapon skills
}
}
2025-08-21 20:57:55 +02:00
// GetAllSkillsWithLearningCosts returns all skills with their basic learning costs for all possible categories
func GetAllSkillsWithLearningCosts ( characterClass string ) ( map [ string ] [ ] gin . H , error ) {
skills , err := models . SelectSkills ( "" , "" )
if err != nil {
return nil , err
}
skillsByCategory := make ( map [ string ] [ ] gin . H )
// Define all possible categories for skills
allCategories := [ ] string { "Alltag" , "Kampf" , "Körper" , "Sozial" , "Wissen" , "Halbwelt" , "Unterwelt" , "Freiland" , "Sonstige" }
for _ , skill := range skills {
// Skip Placeholder skills
if skill . Name == "Placeholder" {
continue
}
// First, always add to the skill's original category
originalCategory := skill . Category
if originalCategory == "" {
originalCategory = "Sonstige"
}
// Try to get the best category and learning cost for this skill and character class
2025-08-30 17:55:20 +02:00
skillInfo , err := models . GetSkillCategoryAndDifficultyNewSystem ( skill . Name , characterClass )
if err != nil {
return nil , err
}
bestCategory := skillInfo . CategoryName
difficulty := skillInfo . DifficultyName
2025-08-21 20:57:55 +02:00
var learnCost int
2025-12-29 17:26:18 +01:00
// error cannot be nil at this point
//if err == nil && bestCategory != "" {
if bestCategory != "" {
2025-08-21 20:57:55 +02:00
// Use the difficulty as a basis for learning cost
switch difficulty {
case "Leicht" :
learnCost = 1
case "Normal" :
learnCost = 2
case "Schwer" :
learnCost = 4
case "Sehr Schwer" :
learnCost = 10
default :
learnCost = 50 // Default fallback
}
// Add to the best category
skillInfo := gin . H {
"name" : skill . Name ,
"learnCost" : learnCost ,
}
skillsByCategory [ bestCategory ] = append ( skillsByCategory [ bestCategory ] , skillInfo )
// If the best category is different from original, also add to original with higher cost
if bestCategory != originalCategory {
skillInfoOriginal := gin . H {
"name" : skill . Name ,
"learnCost" : learnCost * 2 , // Higher cost for non-optimal category
}
skillsByCategory [ originalCategory ] = append ( skillsByCategory [ originalCategory ] , skillInfoOriginal )
}
} else {
// Fallback: add to original category only
skillInfo := gin . H {
"name" : skill . Name ,
"learnCost" : 50 , // Default learning cost
}
skillsByCategory [ originalCategory ] = append ( skillsByCategory [ originalCategory ] , skillInfo )
}
// Try to add skill to other logical categories with higher costs
// This allows more flexibility in character creation
for _ , category := range allCategories {
if category == bestCategory || category == originalCategory {
continue // Already added
}
// Only add to certain categories if it makes sense
if shouldSkillBeInCategory ( skill . Name , category ) {
higherCost := learnCost
if higherCost == 0 {
higherCost = 50
}
higherCost = higherCost * 3 // Much higher cost for cross-category learning
skillInfo := gin . H {
"name" : skill . Name ,
"learnCost" : higherCost ,
}
skillsByCategory [ category ] = append ( skillsByCategory [ category ] , skillInfo )
}
}
}
return skillsByCategory , nil
}
// shouldSkillBeInCategory determines if a skill should be available in a given category
func shouldSkillBeInCategory ( skillName , category string ) bool {
// Define which skills can appear in which categories
skillCategoryMap := map [ string ] [ ] string {
// Physical skills can appear in multiple categories
"Athletik" : { "Körper" , "Kampf" , "Freiland" } ,
"Klettern" : { "Körper" , "Freiland" , "Alltag" } ,
"Schwimmen" : { "Körper" , "Freiland" , "Alltag" } ,
"Laufen" : { "Körper" , "Kampf" , "Freiland" } ,
"Akrobatik" : { "Körper" , "Kampf" } ,
// Combat skills
"Dolch" : { "Kampf" , "Halbwelt" } ,
"Schwert" : { "Kampf" } ,
"Bogen" : { "Kampf" , "Freiland" } ,
// Social skills
"Menschenkenntnis" : { "Sozial" , "Halbwelt" } ,
"Verführen" : { "Sozial" , "Halbwelt" } ,
"Anführen" : { "Sozial" , "Kampf" } ,
// Knowledge skills
"Schreiben" : { "Wissen" , "Alltag" } ,
"Sprache" : { "Wissen" , "Sozial" } ,
"Naturkunde" : { "Wissen" , "Freiland" } ,
// Stealth and underworld
"Schleichen" : { "Halbwelt" , "Freiland" , "Kampf" } ,
"Tarnen" : { "Halbwelt" , "Freiland" , "Kampf" } ,
"Stehlen" : { "Halbwelt" } ,
// Survival and wilderness
"Überleben" : { "Freiland" , "Alltag" } ,
"Spurensuche" : { "Freiland" , "Halbwelt" } ,
"Orientierung" : { "Freiland" , "Alltag" } ,
}
categories , exists := skillCategoryMap [ skillName ]
if ! exists {
return false // Only add skills we explicitly define
}
for _ , cat := range categories {
if cat == category {
return true
}
}
return false
}
2025-07-31 22:49:46 +02:00
// GetAvailableSpellsNewSystem gibt alle verfügbaren Zauber mit Lernkosten zurück (POST mit LernCostRequest)
func GetAvailableSpellsNewSystem ( c * gin . Context ) {
2025-08-06 23:05:59 +02:00
//characterID := c.Param("id")
2025-07-31 22:49:46 +02:00
// Parse LernCostRequest aus POST body
var baseRequest gsmaster . LernCostRequest
if err := c . ShouldBindJSON ( & baseRequest ) ; err != nil {
respondWithError ( c , http . StatusBadRequest , "Ungültige Anfrageparameter: " + err . Error ( ) )
return
}
var character models . Char
2025-08-06 23:05:59 +02:00
if err := database . DB . Preload ( "Zauber" ) . Preload ( "Erfahrungsschatz" ) . Preload ( "Vermoegen" ) . First ( & character , baseRequest . CharId ) . Error ; err != nil {
2025-07-31 22:49:46 +02:00
respondWithError ( c , http . StatusNotFound , "Character not found" )
return
}
2025-08-30 17:55:20 +02:00
charakteClass := getCharacterClass ( & character )
2025-07-31 22:49:46 +02:00
// Hole alle verfügbaren Zauber aus der gsmaster Datenbank, aber filtere Placeholder aus
var allSpells [ ] models . Spell
allSpells , err := models . SelectSpells ( "" , "" )
if err != nil {
respondWithError ( c , http . StatusInternalServerError , "Failed to retrieve spells from gsmaster" )
return
}
// Erstelle eine Map der bereits gelernten Zauber
learnedSpells := make ( map [ string ] bool )
for _ , spell := range character . Zauber {
learnedSpells [ spell . Name ] = true
}
// Organisiere Zauber nach Schulen (analog zu Kategorien bei Fertigkeiten)
spellsBySchool := make ( map [ string ] [ ] gin . H )
for _ , spell := range allSpells {
// Überspringe bereits gelernte Zauber
if learnedSpells [ spell . Name ] {
continue
}
// Überspringe Placeholder-Zauber (zusätzliche Sicherheit)
if spell . Name == "Placeholder" {
continue
}
// Erstelle LernCostRequest für diesen Zauber basierend auf der Basis-Anfrage
request := baseRequest
request . CharId = character . ID
request . Name = spell . Name
request . CurrentLevel = 0 // Nicht gelernt
request . TargetLevel = 1 // Auf Level 1 lernen
request . Type = "spell"
request . Action = "learn"
// Erstelle SkillCostResultNew
levelResult := gsmaster . SkillCostResultNew {
CharacterID : fmt . Sprintf ( "%d" , character . ID ) ,
CharacterClass : charakteClass ,
SkillName : spell . Name ,
TargetLevel : 1 ,
}
remainingPP := request . UsePP
remainingGold := request . UseGold
// Hole die vollständigen Spell-Informationen für die Kostenberechnung
spellLearningInfo , err := models . GetSpellLearningInfoNewSystem ( spell . Name , charakteClass )
if err != nil {
// Fallback für unbekannte Zauber
spellLearningInfo = & models . SpellLearningInfo {
SpellName : spell . Name ,
SpellLevel : spell . Stufe ,
SchoolName : spell . Category ,
LERequired : 20 , // Standard-Lernkosten für Zauber
}
}
// Berechne Lernkosten mit calculateSpellLearnCostNewSystem
err = calculateSpellLearnCostNewSystem ( & request , & levelResult , & remainingPP , & remainingGold , spellLearningInfo )
epCost := 10000 // Fallback-Wert
goldCost := 50000 // Fallback-Wert
if err == nil {
epCost = levelResult . EP
goldCost = levelResult . GoldCost
}
spellInfo := gin . H {
"name" : spell . Name ,
"level" : spell . Stufe ,
"epCost" : epCost ,
"goldCost" : goldCost ,
}
school := spell . Category
if school == "" {
school = "Sonstige"
}
spellsBySchool [ school ] = append ( spellsBySchool [ school ] , spellInfo )
}
c . JSON ( http . StatusOK , gin . H {
"spells_by_school" : spellsBySchool ,
} )
}
2025-08-07 10:23:49 +02:00
// GetSpellDetails gibt detaillierte Informationen zu einem bestimmten Zauber zurück
func GetSpellDetails ( c * gin . Context ) {
spellName := c . Query ( "name" )
if spellName == "" {
respondWithError ( c , http . StatusBadRequest , "Zaubername ist erforderlich" )
return
}
// Lade den Zauber aus der Datenbank
var spell models . Spell
if err := database . DB . Where ( "name = ?" , spellName ) . First ( & spell ) . Error ; err != nil {
respondWithError ( c , http . StatusNotFound , "Zauber nicht gefunden" )
return
}
// Erstelle Response mit allen verfügbaren Details
spellDetails := gin . H {
"id" : spell . ID ,
"name" : spell . Name ,
"beschreibung" : spell . Beschreibung ,
"level" : spell . Stufe ,
"bonus" : spell . Bonus ,
"ap" : spell . AP ,
"art" : spell . Art ,
"zauberdauer" : spell . Zauberdauer ,
"reichweite" : spell . Reichweite ,
"wirkungsziel" : spell . Wirkungsziel ,
"wirkungsbereich" : spell . Wirkungsbereich ,
"wirkungsdauer" : spell . Wirkungsdauer ,
"ursprung" : spell . Ursprung ,
"category" : spell . Category ,
"learning_category" : spell . LearningCategory ,
"quelle" : spell . Quelle ,
"page_number" : spell . PageNumber ,
"game_system" : spell . GameSystem ,
}
c . JSON ( http . StatusOK , gin . H {
"spell" : spellDetails ,
} )
}
2025-08-08 06:39:46 +02:00
// Character Creation Session Management
// CreateCharacterSession erstellt eine neue Charakter-Erstellungssession
func CreateCharacterSession ( c * gin . Context ) {
2025-08-10 22:26:07 +02:00
logger . Debug ( "CreateCharacterSession aufgerufen" )
// Debug: Alle Kontext-Keys anzeigen
keys := make ( [ ] string , 0 )
for key := range c . Keys {
keys = append ( keys , fmt . Sprintf ( "%s=%v" , key , c . Keys [ key ] ) )
}
logger . Debug ( "CreateCharacterSession: Verfügbare Kontext-Keys: [%s]" , strings . Join ( keys , ", " ) )
2025-08-08 22:37:55 +02:00
userID := c . GetUint ( "userID" )
2025-08-10 22:26:07 +02:00
logger . Debug ( "CreateCharacterSession: UserID = %d" , userID )
2025-08-08 22:37:55 +02:00
if userID == 0 {
2025-08-10 22:26:07 +02:00
logger . Warn ( "CreateCharacterSession: Unauthorized - UserID ist 0" )
2025-08-08 22:37:55 +02:00
c . JSON ( http . StatusUnauthorized , gin . H { "error" : "Unauthorized" } )
return
}
2025-08-08 06:39:46 +02:00
sessionID := fmt . Sprintf ( "char_create_%d_%d" , userID , time . Now ( ) . Unix ( ) )
2025-08-10 22:26:07 +02:00
logger . Debug ( "CreateCharacterSession: Generierte SessionID = %s" , sessionID )
2025-08-08 06:39:46 +02:00
2025-08-08 22:37:55 +02:00
session := models . CharacterCreationSession {
2025-08-08 06:39:46 +02:00
ID : sessionID ,
UserID : userID ,
2025-08-08 22:37:55 +02:00
Name : "" ,
2025-08-13 23:47:27 +02:00
Geschlecht : "" ,
2025-08-08 22:37:55 +02:00
Rasse : "" ,
Typ : "" ,
Herkunft : "" ,
2025-08-13 23:47:27 +02:00
Stand : "" ,
2025-08-08 22:37:55 +02:00
Glaube : "" ,
Attributes : models . AttributesData { } ,
DerivedValues : models . DerivedValuesData { } ,
2025-08-10 22:26:07 +02:00
Skills : models . CharacterCreationSkills { } ,
Spells : models . CharacterCreationSpells { } ,
2025-08-08 22:37:55 +02:00
SkillPoints : models . SkillPointsData { } ,
2025-08-08 06:39:46 +02:00
CreatedAt : time . Now ( ) ,
UpdatedAt : time . Now ( ) ,
ExpiresAt : time . Now ( ) . AddDate ( 0 , 0 , 14 ) , // 14 Tage
CurrentStep : 1 ,
}
2025-08-10 22:26:07 +02:00
logger . Debug ( "CreateCharacterSession: Session-Struktur erstellt, ExpiresAt = %s" , session . ExpiresAt . Format ( time . RFC3339 ) )
2025-08-08 06:39:46 +02:00
2025-08-08 22:37:55 +02:00
// Session in Datenbank speichern
2025-08-10 22:26:07 +02:00
logger . Debug ( "CreateCharacterSession: Speichere Session in Datenbank..." )
2025-08-08 22:37:55 +02:00
err := database . DB . Create ( & session ) . Error
if err != nil {
2025-08-10 22:26:07 +02:00
logger . Error ( "CreateCharacterSession: Fehler beim Erstellen der Session: %s" , err . Error ( ) )
2025-08-08 22:37:55 +02:00
c . JSON ( http . StatusInternalServerError , gin . H { "error" : "Failed to create session" } )
return
}
2025-08-08 06:39:46 +02:00
2025-08-10 22:26:07 +02:00
logger . Info ( "CreateCharacterSession: Session erfolgreich erstellt - SessionID: %s, UserID: %d" , sessionID , userID )
2025-08-08 06:39:46 +02:00
c . JSON ( http . StatusCreated , gin . H {
"session_id" : sessionID ,
"expires_at" : session . ExpiresAt ,
} )
}
// ListCharacterSessions gibt alle aktiven Sessions für einen Benutzer zurück
func ListCharacterSessions ( c * gin . Context ) {
2025-08-10 22:26:07 +02:00
logger . Debug ( "ListCharacterSessions aufgerufen" )
2025-08-08 22:37:55 +02:00
userID := c . GetUint ( "userID" )
2025-08-10 22:26:07 +02:00
logger . Debug ( "ListCharacterSessions: UserID = %d" , userID )
2025-08-08 22:37:55 +02:00
if userID == 0 {
2025-08-10 22:26:07 +02:00
logger . Warn ( "ListCharacterSessions: Unauthorized - UserID ist 0" )
2025-08-08 22:37:55 +02:00
c . JSON ( http . StatusUnauthorized , gin . H { "error" : "Unauthorized" } )
return
}
// Sessions aus Datenbank laden
2025-08-10 22:26:07 +02:00
logger . Debug ( "ListCharacterSessions: Lade Sessions für UserID %d aus Datenbank..." , userID )
2025-08-08 22:37:55 +02:00
sessions , err := models . GetUserSessions ( database . DB , userID )
if err != nil {
2025-08-10 22:26:07 +02:00
logger . Error ( "ListCharacterSessions: Fehler beim Laden der Sessions: %s" , err . Error ( ) )
2025-08-08 22:37:55 +02:00
c . JSON ( http . StatusInternalServerError , gin . H { "error" : "Failed to load sessions" } )
return
}
2025-08-10 22:26:07 +02:00
logger . Debug ( "ListCharacterSessions: Gefundene Sessions: %d" , len ( sessions ) )
2025-08-08 22:37:55 +02:00
// Sessions für Frontend formatieren
var formattedSessions [ ] gin . H
2025-08-10 22:26:07 +02:00
for i , session := range sessions {
2025-08-08 22:37:55 +02:00
// Schritt-Text bestimmen
progressText := getProgressText ( session . CurrentStep )
2025-08-10 22:26:07 +02:00
logger . Debug ( "ListCharacterSessions: Formatiere Session %d - ID: %s, Step: %d, Name: %s" ,
i + 1 , session . ID , session . CurrentStep , session . Name )
2025-08-08 22:37:55 +02:00
formattedSessions = append ( formattedSessions , gin . H {
"session_id" : session . ID ,
"name" : session . Name ,
"rasse" : session . Rasse ,
"typ" : session . Typ ,
"current_step" : session . CurrentStep ,
2025-08-08 06:39:46 +02:00
"total_steps" : 5 ,
2025-08-08 22:37:55 +02:00
"created_at" : session . CreatedAt ,
"updated_at" : session . UpdatedAt ,
"expires_at" : session . ExpiresAt ,
"progress_text" : progressText ,
} )
2025-08-08 06:39:46 +02:00
}
2025-08-10 22:26:07 +02:00
logger . Info ( "ListCharacterSessions: Sessions erfolgreich geladen für UserID %d - Anzahl: %d" , userID , len ( formattedSessions ) )
2025-08-08 06:39:46 +02:00
c . JSON ( http . StatusOK , gin . H {
2025-08-08 22:37:55 +02:00
"sessions" : formattedSessions ,
"count" : len ( formattedSessions ) ,
2025-08-08 06:39:46 +02:00
} )
}
2025-08-08 22:37:55 +02:00
// getProgressText gibt den Schritt-Text für die Frontend-Anzeige zurück
func getProgressText ( step int ) string {
2025-08-10 22:26:07 +02:00
logger . Debug ( "getProgressText: Ermittle Text für Schritt %d" , step )
var text string
2025-08-08 22:37:55 +02:00
switch step {
case 1 :
2025-08-10 22:26:07 +02:00
text = "Grundinformationen"
2025-08-08 22:37:55 +02:00
case 2 :
2025-08-10 22:26:07 +02:00
text = "Attribute"
2025-08-08 22:37:55 +02:00
case 3 :
2025-08-10 22:26:07 +02:00
text = "Abgeleitete Werte"
2025-08-08 22:37:55 +02:00
case 4 :
2025-08-10 22:26:07 +02:00
text = "Fertigkeiten"
2025-08-08 22:37:55 +02:00
case 5 :
2025-08-10 22:26:07 +02:00
text = "Zauber"
2025-08-08 22:37:55 +02:00
default :
2025-08-10 22:26:07 +02:00
text = "Unbekannt"
logger . Warn ( "getProgressText: Unbekannter Schritt %d" , step )
2025-08-08 22:37:55 +02:00
}
2025-08-10 22:26:07 +02:00
logger . Debug ( "getProgressText: Schritt %d = '%s'" , step , text )
return text
2025-08-08 22:37:55 +02:00
}
2025-08-08 06:39:46 +02:00
// GetCharacterSession gibt Session-Daten zurück
func GetCharacterSession ( c * gin . Context ) {
2025-08-10 22:26:07 +02:00
logger . Debug ( "GetCharacterSession aufgerufen" )
2025-08-08 06:39:46 +02:00
sessionID := c . Param ( "sessionId" )
2025-08-08 22:37:55 +02:00
userID := c . GetUint ( "userID" )
2025-08-10 22:26:07 +02:00
logger . Debug ( "GetCharacterSession: SessionID = %s, UserID = %d" , sessionID , userID )
2025-08-08 22:37:55 +02:00
if userID == 0 {
2025-08-10 22:26:07 +02:00
logger . Warn ( "GetCharacterSession: Unauthorized - UserID ist 0" )
2025-08-08 22:37:55 +02:00
c . JSON ( http . StatusUnauthorized , gin . H { "error" : "Unauthorized" } )
return
}
2025-08-08 06:39:46 +02:00
2025-08-08 22:37:55 +02:00
// Session aus Datenbank laden
2025-08-10 22:26:07 +02:00
logger . Debug ( "GetCharacterSession: Lade Session aus Datenbank..." )
2025-08-08 22:37:55 +02:00
var session models . CharacterCreationSession
err := database . DB . Where ( "id = ? AND user_id = ?" , sessionID , userID ) . First ( & session ) . Error
if err != nil {
2025-08-10 22:26:07 +02:00
logger . Error ( "GetCharacterSession: Session nicht gefunden - SessionID: %s, UserID: %d, Error: %s" ,
sessionID , userID , err . Error ( ) )
2025-08-08 22:37:55 +02:00
c . JSON ( http . StatusNotFound , gin . H { "error" : "Session not found" } )
return
}
2025-08-10 22:26:07 +02:00
logger . Debug ( "GetCharacterSession: Session gefunden - Name: %s, Step: %d, ExpiresAt: %s" ,
session . Name , session . CurrentStep , session . ExpiresAt . Format ( time . RFC3339 ) )
2025-08-08 22:37:55 +02:00
// Prüfen ob Session noch gültig ist
if session . ExpiresAt . Before ( time . Now ( ) ) {
2025-08-10 22:26:07 +02:00
logger . Warn ( "GetCharacterSession: Session abgelaufen - SessionID: %s, ExpiresAt: %s" ,
sessionID , session . ExpiresAt . Format ( time . RFC3339 ) )
2025-08-08 22:37:55 +02:00
// Abgelaufene Session löschen
2025-08-10 22:26:07 +02:00
logger . Debug ( "GetCharacterSession: Lösche abgelaufene Session..." )
2025-08-08 22:37:55 +02:00
database . DB . Delete ( & session )
2025-08-10 22:26:07 +02:00
2025-08-08 22:37:55 +02:00
c . JSON ( http . StatusGone , gin . H { "error" : "Session expired" } )
return
2025-08-08 06:39:46 +02:00
}
2025-08-10 22:26:07 +02:00
logger . Info ( "GetCharacterSession: Session erfolgreich geladen - SessionID: %s, UserID: %d, Step: %d" ,
sessionID , userID , session . CurrentStep )
2025-08-08 06:39:46 +02:00
c . JSON ( http . StatusOK , session )
}
// UpdateCharacterBasicInfo Request
type UpdateBasicInfoRequest struct {
2025-08-13 23:47:27 +02:00
Name string ` json:"name" binding:"required,min=2,max=50" `
Geschlecht string ` json:"geschlecht" binding:"required" `
Rasse string ` json:"rasse" binding:"required" `
Typ string ` json:"typ" binding:"required" `
Herkunft string ` json:"herkunft" binding:"required" `
Stand string ` json:"stand" binding:"required" `
Glaube string ` json:"glaube" `
2025-08-08 06:39:46 +02:00
}
// UpdateCharacterBasicInfo speichert Grundinformationen
func UpdateCharacterBasicInfo ( c * gin . Context ) {
2025-08-10 22:26:07 +02:00
logger . Debug ( "UpdateCharacterBasicInfo aufgerufen" )
2025-08-08 06:39:46 +02:00
sessionID := c . Param ( "sessionId" )
2025-08-08 22:37:55 +02:00
userID := c . GetUint ( "userID" )
2025-08-10 22:26:07 +02:00
logger . Debug ( "UpdateCharacterBasicInfo: SessionID = %s, UserID = %d" , sessionID , userID )
2025-08-08 22:37:55 +02:00
if userID == 0 {
2025-08-10 22:26:07 +02:00
logger . Warn ( "UpdateCharacterBasicInfo: Unauthorized - UserID ist 0" )
2025-08-08 22:37:55 +02:00
c . JSON ( http . StatusUnauthorized , gin . H { "error" : "Unauthorized" } )
return
}
2025-08-08 06:39:46 +02:00
var request UpdateBasicInfoRequest
if err := c . ShouldBindJSON ( & request ) ; err != nil {
2025-08-10 22:26:07 +02:00
logger . Error ( "UpdateCharacterBasicInfo: Ungültige Eingabedaten - %s" , err . Error ( ) )
2025-08-08 06:39:46 +02:00
respondWithError ( c , http . StatusBadRequest , "Ungültige Eingabedaten: " + err . Error ( ) )
return
}
2025-08-13 23:47:27 +02:00
logger . Debug ( "UpdateCharacterBasicInfo: Request-Daten - Name: %s, Geschlecht: %s, Rasse: %s, Typ: %s, Herkunft: %s, Stand: %s, Glaube: %s" ,
request . Name , request . Geschlecht , request . Rasse , request . Typ , request . Herkunft , request . Stand , request . Glaube )
2025-08-10 22:26:07 +02:00
2025-08-08 22:37:55 +02:00
// Session aus Datenbank laden
2025-08-10 22:26:07 +02:00
logger . Debug ( "UpdateCharacterBasicInfo: Lade Session aus Datenbank..." )
2025-08-08 22:37:55 +02:00
var session models . CharacterCreationSession
err := database . DB . Where ( "id = ? AND user_id = ?" , sessionID , userID ) . First ( & session ) . Error
if err != nil {
2025-08-10 22:26:07 +02:00
logger . Error ( "UpdateCharacterBasicInfo: Session nicht gefunden - SessionID: %s, UserID: %d, Error: %s" ,
sessionID , userID , err . Error ( ) )
2025-08-08 22:37:55 +02:00
c . JSON ( http . StatusNotFound , gin . H { "error" : "Session not found" } )
return
}
2025-08-10 22:26:07 +02:00
logger . Debug ( "UpdateCharacterBasicInfo: Aktueller Session-Status - Step: %d, Name: %s" ,
session . CurrentStep , session . Name )
2025-08-08 22:37:55 +02:00
// Grundinformationen aktualisieren
session . Name = request . Name
2025-08-13 23:47:27 +02:00
session . Geschlecht = request . Geschlecht
2025-08-08 22:37:55 +02:00
session . Rasse = request . Rasse
session . Typ = request . Typ
session . Herkunft = request . Herkunft
2025-08-13 23:47:27 +02:00
session . Stand = request . Stand
2025-08-08 22:37:55 +02:00
session . Glaube = request . Glaube
session . CurrentStep = 2
session . UpdatedAt = time . Now ( )
2025-08-10 22:26:07 +02:00
logger . Debug ( "UpdateCharacterBasicInfo: Session aktualisiert, setze CurrentStep auf 2" )
2025-08-08 22:37:55 +02:00
// Session in Datenbank aktualisieren
2025-08-10 22:26:07 +02:00
logger . Debug ( "UpdateCharacterBasicInfo: Speichere Session in Datenbank..." )
2025-08-08 22:37:55 +02:00
err = database . DB . Save ( & session ) . Error
if err != nil {
2025-08-10 22:26:07 +02:00
logger . Error ( "UpdateCharacterBasicInfo: Fehler beim Speichern der Session: %s" , err . Error ( ) )
2025-08-08 22:37:55 +02:00
c . JSON ( http . StatusInternalServerError , gin . H { "error" : "Failed to update session" } )
return
}
2025-08-10 22:26:07 +02:00
logger . Info ( "UpdateCharacterBasicInfo: Grundinformationen erfolgreich gespeichert - SessionID: %s, Name: %s" ,
sessionID , request . Name )
2025-08-08 06:39:46 +02:00
c . JSON ( http . StatusOK , gin . H {
"message" : "Grundinformationen gespeichert" ,
"session_id" : sessionID ,
"current_step" : 2 ,
} )
}
// UpdateAttributesRequest
type UpdateAttributesRequest struct {
ST int ` json:"st" binding:"required,min=1,max=100" ` // Stärke
GS int ` json:"gs" binding:"required,min=1,max=100" ` // Geschicklichkeit
GW int ` json:"gw" binding:"required,min=1,max=100" ` // Gewandtheit
KO int ` json:"ko" binding:"required,min=1,max=100" ` // Konstitution
IN int ` json:"in" binding:"required,min=1,max=100" ` // Intelligenz
ZT int ` json:"zt" binding:"required,min=1,max=100" ` // Zaubertalent
AU int ` json:"au" binding:"required,min=1,max=100" ` // Ausstrahlung
}
// UpdateCharacterAttributes speichert Grundwerte
func UpdateCharacterAttributes ( c * gin . Context ) {
2025-08-10 22:26:07 +02:00
logger . Debug ( "UpdateCharacterAttributes aufgerufen" )
2025-08-08 06:39:46 +02:00
sessionID := c . Param ( "sessionId" )
2025-08-08 22:37:55 +02:00
userID := c . GetUint ( "userID" )
2025-08-10 22:26:07 +02:00
logger . Debug ( "UpdateCharacterAttributes: SessionID = %s, UserID = %d" , sessionID , userID )
2025-08-08 22:37:55 +02:00
if userID == 0 {
2025-08-10 22:26:07 +02:00
logger . Warn ( "UpdateCharacterAttributes: Unauthorized - UserID ist 0" )
2025-08-08 22:37:55 +02:00
c . JSON ( http . StatusUnauthorized , gin . H { "error" : "Unauthorized" } )
return
}
2025-08-08 06:39:46 +02:00
var request UpdateAttributesRequest
if err := c . ShouldBindJSON ( & request ) ; err != nil {
2025-08-10 22:26:07 +02:00
logger . Error ( "UpdateCharacterAttributes: Ungültige Attributswerte - %s" , err . Error ( ) )
2025-08-08 06:39:46 +02:00
respondWithError ( c , http . StatusBadRequest , "Ungültige Attributswerte: " + err . Error ( ) )
return
}
2025-08-13 23:47:27 +02:00
logger . Debug ( "UpdateCharacterAttributes: Attribute - ST:%d GS:%d GW:%d KO:%d IN:%d ZT:%d AU:%d" ,
request . ST , request . GS , request . GW , request . KO , request . IN , request . ZT , request . AU )
2025-08-10 22:26:07 +02:00
2025-08-08 22:37:55 +02:00
// Session aus Datenbank laden
2025-08-10 22:26:07 +02:00
logger . Debug ( "UpdateCharacterAttributes: Lade Session aus Datenbank..." )
2025-08-08 22:37:55 +02:00
var session models . CharacterCreationSession
err := database . DB . Where ( "id = ? AND user_id = ?" , sessionID , userID ) . First ( & session ) . Error
if err != nil {
2025-08-10 22:26:07 +02:00
logger . Error ( "UpdateCharacterAttributes: Session nicht gefunden - SessionID: %s, UserID: %d, Error: %s" ,
sessionID , userID , err . Error ( ) )
2025-08-08 22:37:55 +02:00
c . JSON ( http . StatusNotFound , gin . H { "error" : "Session not found" } )
return
}
2025-08-10 22:26:07 +02:00
logger . Debug ( "UpdateCharacterAttributes: Session geladen - CurrentStep: %d" , session . CurrentStep )
2025-08-08 22:37:55 +02:00
// Attribute aktualisieren
session . Attributes = models . AttributesData {
ST : request . ST ,
GS : request . GS ,
GW : request . GW ,
KO : request . KO ,
IN : request . IN ,
ZT : request . ZT ,
AU : request . AU ,
}
session . CurrentStep = 3
session . UpdatedAt = time . Now ( )
2025-08-10 22:26:07 +02:00
logger . Debug ( "UpdateCharacterAttributes: Attribute gesetzt, CurrentStep auf 3 aktualisiert" )
2025-08-08 22:37:55 +02:00
// Session in Datenbank aktualisieren
2025-08-10 22:26:07 +02:00
logger . Debug ( "UpdateCharacterAttributes: Speichere Session in Datenbank..." )
2025-08-08 22:37:55 +02:00
err = database . DB . Save ( & session ) . Error
if err != nil {
2025-08-10 22:26:07 +02:00
logger . Error ( "UpdateCharacterAttributes: Fehler beim Speichern der Session: %s" , err . Error ( ) )
2025-08-08 22:37:55 +02:00
c . JSON ( http . StatusInternalServerError , gin . H { "error" : "Failed to update session" } )
return
}
2025-08-10 22:26:07 +02:00
logger . Info ( "UpdateCharacterAttributes: Grundwerte erfolgreich gespeichert - SessionID: %s" , sessionID )
2025-08-08 06:39:46 +02:00
c . JSON ( http . StatusOK , gin . H {
"message" : "Grundwerte gespeichert" ,
"session_id" : sessionID ,
"current_step" : 3 ,
} )
}
// UpdateDerivedValuesRequest
type UpdateDerivedValuesRequest struct {
2025-08-13 23:47:27 +02:00
PA int ` json:"pa" binding:"required,min=1,max=100" ` // Persönliche Ausstrahlung
WK int ` json:"wk" binding:"required,min=1,max=100" ` // Willenskraft
LP_Max int ` json:"lp_max" binding:"required,min=1,max=50" ` // Lebenspunkte Maximum
AP_Max int ` json:"ap_max" binding:"required,min=1,max=200" ` // Abenteuerpunkte Maximum
B_Max int ` json:"b_max" binding:"required,min=1,max=50" ` // Belastung Maximum
ResistenzKoerper int ` json:"resistenz_koerper" binding:"required,min=1,max=20" ` // Resistenz Körper
ResistenzGeist int ` json:"resistenz_geist" binding:"required,min=1,max=20" ` // Resistenz Geist
ResistenzBonusKoerper int ` json:"resistenz_bonus_koerper" binding:"min=-5,max=5" ` // Resistenz Bonus Körper
ResistenzBonusGeist int ` json:"resistenz_bonus_geist" binding:"min=-5,max=5" ` // Resistenz Bonus Geist
Abwehr int ` json:"abwehr" binding:"required,min=1,max=20" ` // Abwehr
AbwehrBonus int ` json:"abwehr_bonus" binding:"min=-5,max=5" ` // Abwehr Bonus
AusdauerBonus int ` json:"ausdauer_bonus" binding:"min=-50,max=50" ` // Ausdauer Bonus
AngriffsBonus int ` json:"angriffs_bonus" binding:"min=-5,max=5" ` // Angriffs Bonus
Zaubern int ` json:"zaubern" binding:"required,min=1,max=20" ` // Zaubern
ZauberBonus int ` json:"zauber_bonus" binding:"min=-5,max=5" ` // Zauber Bonus
Raufen int ` json:"raufen" binding:"required,min=1,max=20" ` // Raufen
SchadensBonus int ` json:"schadens_bonus" binding:"min=-10,max=10" ` // Schadens Bonus
SG int ` json:"sg" binding:"min=0,max=50" ` // Schicksalsgunst
GG int ` json:"gg" binding:"min=0,max=50" ` // Göttliche Gnade
GP int ` json:"gp" binding:"min=0,max=50" ` // Glückspunkte
2025-08-08 06:39:46 +02:00
}
// UpdateCharacterDerivedValues speichert abgeleitete Werte
func UpdateCharacterDerivedValues ( c * gin . Context ) {
2025-08-10 22:26:07 +02:00
logger . Debug ( "UpdateCharacterDerivedValues aufgerufen" )
2025-08-08 06:39:46 +02:00
sessionID := c . Param ( "sessionId" )
2025-08-08 22:37:55 +02:00
userID := c . GetUint ( "userID" )
2025-08-10 22:26:07 +02:00
logger . Debug ( "UpdateCharacterDerivedValues: SessionID = %s, UserID = %d" , sessionID , userID )
2025-08-08 22:37:55 +02:00
if userID == 0 {
2025-08-10 22:26:07 +02:00
logger . Warn ( "UpdateCharacterDerivedValues: Unauthorized - UserID ist 0" )
2025-08-08 22:37:55 +02:00
c . JSON ( http . StatusUnauthorized , gin . H { "error" : "Unauthorized" } )
return
}
2025-08-08 06:39:46 +02:00
var request UpdateDerivedValuesRequest
if err := c . ShouldBindJSON ( & request ) ; err != nil {
2025-08-10 22:26:07 +02:00
logger . Error ( "UpdateCharacterDerivedValues: Ungültige abgeleitete Werte - %s" , err . Error ( ) )
2025-08-08 06:39:46 +02:00
respondWithError ( c , http . StatusBadRequest , "Ungültige abgeleitete Werte: " + err . Error ( ) )
return
}
2025-08-13 23:47:27 +02:00
logger . Debug ( "UpdateCharacterDerivedValues: Werte - LP_Max:%d AP_Max:%d B_Max:%d PA:%d WK:%d SG:%d GG:%d GP:%d" ,
request . LP_Max , request . AP_Max , request . B_Max , request . PA , request . WK , request . SG , request . GG , request . GP )
2025-08-10 22:26:07 +02:00
2025-08-08 22:37:55 +02:00
// Session aus Datenbank laden
2025-08-10 22:26:07 +02:00
logger . Debug ( "UpdateCharacterDerivedValues: Lade Session aus Datenbank..." )
2025-08-08 22:37:55 +02:00
var session models . CharacterCreationSession
err := database . DB . Where ( "id = ? AND user_id = ?" , sessionID , userID ) . First ( & session ) . Error
if err != nil {
2025-08-10 22:26:07 +02:00
logger . Error ( "UpdateCharacterDerivedValues: Session nicht gefunden - SessionID: %s, UserID: %d, Error: %s" ,
sessionID , userID , err . Error ( ) )
2025-08-08 22:37:55 +02:00
c . JSON ( http . StatusNotFound , gin . H { "error" : "Session not found" } )
return
}
2025-08-10 22:26:07 +02:00
logger . Debug ( "UpdateCharacterDerivedValues: Session geladen - CurrentStep: %d" , session . CurrentStep )
2025-08-08 22:37:55 +02:00
// Abgeleitete Werte aktualisieren
session . DerivedValues = models . DerivedValuesData {
2025-08-13 23:47:27 +02:00
PA : request . PA ,
WK : request . WK ,
LPMax : request . LP_Max ,
APMax : request . AP_Max ,
BMax : request . B_Max ,
ResistenzKoerper : request . ResistenzKoerper ,
ResistenzGeist : request . ResistenzGeist ,
ResistenzBonusKoerper : request . ResistenzBonusKoerper ,
ResistenzBonusGeist : request . ResistenzBonusGeist ,
Abwehr : request . Abwehr ,
AbwehrBonus : request . AbwehrBonus ,
AusdauerBonus : request . AusdauerBonus ,
AngriffsBonus : request . AngriffsBonus ,
Zaubern : request . Zaubern ,
ZauberBonus : request . ZauberBonus ,
Raufen : request . Raufen ,
SchadensBonus : request . SchadensBonus ,
SG : request . SG ,
GG : request . GG ,
GP : request . GP ,
2025-08-08 22:37:55 +02:00
}
session . CurrentStep = 4
session . UpdatedAt = time . Now ( )
2025-08-10 22:26:07 +02:00
logger . Debug ( "UpdateCharacterDerivedValues: Abgeleitete Werte gesetzt, CurrentStep auf 4 aktualisiert" )
2025-08-08 22:37:55 +02:00
// Session in Datenbank aktualisieren
2025-08-10 22:26:07 +02:00
logger . Debug ( "UpdateCharacterDerivedValues: Speichere Session in Datenbank..." )
2025-08-08 22:37:55 +02:00
err = database . DB . Save ( & session ) . Error
if err != nil {
2025-08-10 22:26:07 +02:00
logger . Error ( "UpdateCharacterDerivedValues: Fehler beim Speichern der Session: %s" , err . Error ( ) )
2025-08-08 22:37:55 +02:00
c . JSON ( http . StatusInternalServerError , gin . H { "error" : "Failed to update session" } )
return
}
2025-08-10 22:26:07 +02:00
logger . Info ( "UpdateCharacterDerivedValues: Abgeleitete Werte erfolgreich gespeichert - SessionID: %s" , sessionID )
2025-08-08 06:39:46 +02:00
c . JSON ( http . StatusOK , gin . H {
"message" : "Abgeleitete Werte gespeichert" ,
"session_id" : sessionID ,
"current_step" : 4 ,
} )
}
// UpdateSkillsRequest
type UpdateSkillsRequest struct {
2025-08-10 22:26:07 +02:00
Skills models . CharacterCreationSkills ` json:"skills" `
Spells models . CharacterCreationSpells ` json:"spells" `
SkillPoints models . SkillPointsData ` json:"skill_points" ` // Verbleibende Punkte pro Kategorie
2025-08-08 06:39:46 +02:00
}
// UpdateCharacterSkills speichert Fertigkeiten und Zauber
func UpdateCharacterSkills ( c * gin . Context ) {
2025-08-10 22:26:07 +02:00
logger . Debug ( "UpdateCharacterSkills aufgerufen" )
2025-08-08 06:39:46 +02:00
sessionID := c . Param ( "sessionId" )
2025-08-08 22:37:55 +02:00
userID := c . GetUint ( "userID" )
2025-08-10 22:26:07 +02:00
logger . Debug ( "UpdateCharacterSkills: SessionID = %s, UserID = %d" , sessionID , userID )
2025-08-08 22:37:55 +02:00
if userID == 0 {
2025-08-10 22:26:07 +02:00
logger . Warn ( "UpdateCharacterSkills: Unauthorized - UserID ist 0" )
2025-08-08 22:37:55 +02:00
c . JSON ( http . StatusUnauthorized , gin . H { "error" : "Unauthorized" } )
return
}
2025-08-08 06:39:46 +02:00
var request UpdateSkillsRequest
if err := c . ShouldBindJSON ( & request ) ; err != nil {
2025-08-10 22:26:07 +02:00
logger . Error ( "UpdateCharacterSkills: Ungültige Fertigkeitsdaten - %s" , err . Error ( ) )
2025-08-08 06:39:46 +02:00
respondWithError ( c , http . StatusBadRequest , "Ungültige Fertigkeitsdaten: " + err . Error ( ) )
return
}
2025-08-10 22:26:07 +02:00
logger . Debug ( "UpdateCharacterSkills: Skills-Anzahl: %d, Spells-Anzahl: %d" ,
len ( request . Skills ) , len ( request . Spells ) )
2025-08-08 22:37:55 +02:00
// Session aus Datenbank laden
2025-08-10 22:26:07 +02:00
logger . Debug ( "UpdateCharacterSkills: Lade Session aus Datenbank..." )
2025-08-08 22:37:55 +02:00
var session models . CharacterCreationSession
err := database . DB . Where ( "id = ? AND user_id = ?" , sessionID , userID ) . First ( & session ) . Error
if err != nil {
2025-08-10 22:26:07 +02:00
logger . Error ( "UpdateCharacterSkills: Session nicht gefunden - SessionID: %s, UserID: %d, Error: %s" ,
sessionID , userID , err . Error ( ) )
2025-08-08 22:37:55 +02:00
c . JSON ( http . StatusNotFound , gin . H { "error" : "Session not found" } )
return
}
2025-08-10 22:26:07 +02:00
logger . Debug ( "UpdateCharacterSkills: Session geladen - CurrentStep: %d" , session . CurrentStep )
2025-08-08 22:37:55 +02:00
// Fertigkeiten und Zauber aktualisieren
session . Skills = request . Skills
session . Spells = request . Spells
session . SkillPoints = request . SkillPoints
session . CurrentStep = 5
session . UpdatedAt = time . Now ( )
2025-08-10 22:26:07 +02:00
logger . Debug ( "UpdateCharacterSkills: Skills/Spells gesetzt, CurrentStep auf 5 aktualisiert" )
2025-08-08 22:37:55 +02:00
// Session in Datenbank aktualisieren
2025-08-10 22:26:07 +02:00
logger . Debug ( "UpdateCharacterSkills: Speichere Session in Datenbank..." )
2025-08-08 22:37:55 +02:00
err = database . DB . Save ( & session ) . Error
if err != nil {
2025-08-10 22:26:07 +02:00
logger . Error ( "UpdateCharacterSkills: Fehler beim Speichern der Session: %s" , err . Error ( ) )
2025-08-08 22:37:55 +02:00
c . JSON ( http . StatusInternalServerError , gin . H { "error" : "Failed to update session" } )
return
}
2025-08-10 22:26:07 +02:00
logger . Info ( "UpdateCharacterSkills: Fertigkeiten erfolgreich gespeichert - SessionID: %s, Skills: %d, Spells: %d" ,
sessionID , len ( request . Skills ) , len ( request . Spells ) )
2025-08-08 06:39:46 +02:00
c . JSON ( http . StatusOK , gin . H {
"message" : "Fertigkeiten gespeichert" ,
"session_id" : sessionID ,
"current_step" : 5 ,
} )
}
// FinalizeCharacterCreation schließt die Charakter-Erstellung ab
func FinalizeCharacterCreation ( c * gin . Context ) {
2025-08-10 22:26:07 +02:00
logger . Debug ( "FinalizeCharacterCreation aufgerufen" )
2025-08-08 06:39:46 +02:00
sessionID := c . Param ( "sessionId" )
2025-08-08 22:37:55 +02:00
userID := c . GetUint ( "userID" )
2025-08-10 22:26:07 +02:00
logger . Debug ( "FinalizeCharacterCreation: SessionID = %s, UserID = %d" , sessionID , userID )
2025-08-08 22:37:55 +02:00
if userID == 0 {
2025-08-10 22:26:07 +02:00
logger . Warn ( "FinalizeCharacterCreation: Unauthorized - UserID ist 0" )
2025-08-08 22:37:55 +02:00
c . JSON ( http . StatusUnauthorized , gin . H { "error" : "Unauthorized" } )
return
}
// Session laden
2025-08-10 22:26:07 +02:00
logger . Debug ( "FinalizeCharacterCreation: Lade Session aus Datenbank..." )
2025-08-08 22:37:55 +02:00
var session models . CharacterCreationSession
err := database . DB . Where ( "id = ? AND user_id = ?" , sessionID , userID ) . First ( & session ) . Error
if err != nil {
2025-08-10 22:26:07 +02:00
logger . Error ( "FinalizeCharacterCreation: Session nicht gefunden - SessionID: %s, UserID: %d, Error: %s" ,
sessionID , userID , err . Error ( ) )
2025-08-08 22:37:55 +02:00
c . JSON ( http . StatusNotFound , gin . H { "error" : "Session not found" } )
return
}
2025-08-10 22:26:07 +02:00
logger . Debug ( "FinalizeCharacterCreation: Session geladen - Name: %s, CurrentStep: %d" ,
session . Name , session . CurrentStep )
2025-08-08 22:37:55 +02:00
// Session validieren
if session . CurrentStep < 5 {
2025-08-10 22:26:07 +02:00
logger . Warn ( "FinalizeCharacterCreation: Charakter-Erstellung unvollständig - CurrentStep: %d (erwartet: 5)" ,
session . CurrentStep )
2025-08-08 22:37:55 +02:00
c . JSON ( http . StatusBadRequest , gin . H { "error" : "Character creation not complete" } )
return
}
2025-08-10 22:26:07 +02:00
logger . Debug ( "FinalizeCharacterCreation: Erstelle Charakter-Struktur..." )
2025-08-08 22:37:55 +02:00
// Character erstellen
char := models . Char {
BamortBase : models . BamortBase {
Name : session . Name ,
} ,
UserID : userID ,
Rasse : session . Rasse ,
Typ : session . Typ ,
Glaube : session . Glaube ,
Public : false , // Default to private
2025-08-28 00:46:55 +02:00
Grad : 1 , // Default starting grade
2025-08-08 22:37:55 +02:00
2025-12-27 08:33:42 +01:00
// Static derived values (can increase with grade)
ResistenzKoerper : session . DerivedValues . ResistenzKoerper ,
ResistenzGeist : session . DerivedValues . ResistenzGeist ,
Abwehr : session . DerivedValues . Abwehr ,
Zaubern : session . DerivedValues . Zaubern ,
Raufen : session . DerivedValues . Raufen ,
2025-08-08 22:37:55 +02:00
// Lebenspunkte
Lp : models . Lp {
Max : session . DerivedValues . LPMax ,
Value : session . DerivedValues . LPMax ,
} ,
// Ausdauerpunkte
Ap : models . Ap {
Max : session . DerivedValues . APMax ,
Value : session . DerivedValues . APMax ,
} ,
// Bewegung
B : models . B {
Max : session . DerivedValues . BMax ,
Value : session . DerivedValues . BMax ,
} ,
2025-08-28 00:46:55 +02:00
Vermoegen : models . Vermoegen {
BamortCharTrait : models . BamortCharTrait {
UserID : userID ,
} ,
2025-12-19 17:04:20 +01:00
Goldstuecke : 80 ,
2025-08-28 00:46:55 +02:00
} ,
2025-08-08 22:37:55 +02:00
// Bennies (Glückspunkte, etc.)
Bennies : models . Bennies {
2025-08-28 00:46:55 +02:00
BamortCharTrait : models . BamortCharTrait {
UserID : userID ,
} ,
2025-08-08 22:37:55 +02:00
Gg : session . DerivedValues . GG ,
Gp : session . DerivedValues . GP ,
Sg : session . DerivedValues . SG ,
} ,
}
// Eigenschaften (Attribute) hinzufügen
char . Eigenschaften = [ ] models . Eigenschaft {
2025-08-28 00:46:55 +02:00
{ UserID : userID , Name : "St" , Value : session . Attributes . ST } ,
{ UserID : userID , Name : "Gs" , Value : session . Attributes . GS } ,
{ UserID : userID , Name : "Gw" , Value : session . Attributes . GW } ,
{ UserID : userID , Name : "Ko" , Value : session . Attributes . KO } ,
{ UserID : userID , Name : "In" , Value : session . Attributes . IN } ,
{ UserID : userID , Name : "Zt" , Value : session . Attributes . ZT } ,
{ UserID : userID , Name : "Au" , Value : session . Attributes . AU } ,
{ UserID : userID , Name : "pA" , Value : session . DerivedValues . PA } , // PA kommt aus derived values
{ UserID : userID , Name : "Wk" , Value : session . DerivedValues . WK } , // WK kommt aus derived values
2025-08-08 22:37:55 +02:00
}
2025-08-08 06:39:46 +02:00
2025-08-10 22:26:07 +02:00
logger . Debug ( "FinalizeCharacterCreation: Charakter-Struktur erstellt mit %d Eigenschaften" ,
len ( char . Eigenschaften ) )
2025-08-28 00:46:55 +02:00
// Fertigkeiten aus der Session übertragen
logger . Debug ( "FinalizeCharacterCreation: Übertrage %d Fertigkeiten" , len ( session . Skills ) )
for _ , skill := range session . Skills {
// Suche den Initialwert der Fertigkeit aus der Datenbank
dbSkill := models . Skill { }
err := dbSkill . First ( skill . Name )
if err != nil {
logger . Warn ( "FinalizeCharacterCreation: Konnte Fertigkeit '%s' nicht in der Datenbank finden, verwende Level %d" , skill . Name , skill . Level )
}
// Verwende den Initialwert aus der Datenbank wenn verfügbar, sonst fallback auf Session-Level
initialValue := skill . Level // Fallback
if err == nil {
initialValue = dbSkill . Initialwert
logger . Debug ( "FinalizeCharacterCreation: Verwende Initialwert %d für Fertigkeit '%s'" , initialValue , skill . Name )
}
// Unterscheide zwischen normalen Fertigkeiten und Waffenfertigkeiten
if skill . Category == "Waffen" || skill . Category == "waffen" {
dbWPSkill := models . WeaponSkill { }
err := dbWPSkill . First ( skill . Name )
if err != nil {
logger . Warn ( "FinalizeCharacterCreation: Konnte WaffenFertigkeit '%s' nicht in der Datenbank finden, verwende Level %d" , skill . Name , skill . Level )
}
// Verwende den Initialwert aus der Datenbank wenn verfügbar, sonst fallback auf Session-Level
initialValue := skill . Level // Fallback
if err == nil {
initialValue = dbWPSkill . Initialwert
logger . Debug ( "FinalizeCharacterCreation: Verwende Initialwert %d für Fertigkeit '%s'" , initialValue , skill . Name )
}
// Waffenfertigkeit
weaponSkill := models . SkWaffenfertigkeit {
SkFertigkeit : models . SkFertigkeit {
BamortCharTrait : models . BamortCharTrait {
BamortBase : models . BamortBase {
Name : skill . Name ,
} ,
UserID : userID ,
} ,
Fertigkeitswert : initialValue ,
Improvable : true ,
Category : skill . Category ,
} ,
}
char . Waffenfertigkeiten = append ( char . Waffenfertigkeiten , weaponSkill )
} else {
// Normale Fertigkeit
normalSkill := models . SkFertigkeit {
BamortCharTrait : models . BamortCharTrait {
BamortBase : models . BamortBase {
Name : skill . Name ,
} ,
CharacterID : char . ID ,
UserID : userID ,
} ,
Fertigkeitswert : initialValue ,
Improvable : true ,
Category : skill . Category ,
}
char . Fertigkeiten = append ( char . Fertigkeiten , normalSkill )
}
}
// Zauber aus der Session übertragen
logger . Debug ( "FinalizeCharacterCreation: Übertrage %d Zauber" , len ( session . Spells ) )
for _ , spell := range session . Spells {
zauber := models . SkZauber {
BamortCharTrait : models . BamortCharTrait {
BamortBase : models . BamortBase {
Name : spell . Name ,
} ,
CharacterID : char . ID ,
UserID : userID ,
} ,
}
char . Zauber = append ( char . Zauber , zauber )
}
logger . Debug ( "FinalizeCharacterCreation: Charakter vollständig erstellt - %d Fertigkeiten, %d Waffenfertigkeiten, %d Zauber" ,
len ( char . Fertigkeiten ) , len ( char . Waffenfertigkeiten ) , len ( char . Zauber ) )
2025-08-08 22:37:55 +02:00
// Character in Datenbank speichern
2025-08-10 22:26:07 +02:00
logger . Debug ( "FinalizeCharacterCreation: Speichere Charakter in Datenbank..." )
2025-08-08 22:37:55 +02:00
err = char . Create ( )
if err != nil {
2025-08-10 22:26:07 +02:00
logger . Error ( "FinalizeCharacterCreation: Fehler beim Erstellen des Charakters: %s" , err . Error ( ) )
2025-08-28 00:46:55 +02:00
c . JSON ( http . StatusInternalServerError , gin . H { "error" : "Failed to create character: " + err . Error ( ) } )
2025-08-08 22:37:55 +02:00
return
}
2025-08-08 06:39:46 +02:00
2025-08-10 22:26:07 +02:00
logger . Debug ( "FinalizeCharacterCreation: Charakter erfolgreich erstellt mit ID: %d" , char . ID )
2025-08-08 22:37:55 +02:00
// Session löschen
2025-08-10 22:26:07 +02:00
logger . Debug ( "FinalizeCharacterCreation: Lösche Session aus Datenbank..." )
2025-08-08 22:37:55 +02:00
database . DB . Delete ( & session )
2025-08-08 06:39:46 +02:00
2025-08-10 22:26:07 +02:00
logger . Info ( "FinalizeCharacterCreation: Charakter-Erstellung abgeschlossen - CharacterID: %d, SessionID: %s, Name: %s" ,
char . ID , sessionID , session . Name )
2025-08-08 06:39:46 +02:00
c . JSON ( http . StatusCreated , gin . H {
"message" : "Charakter erfolgreich erstellt" ,
2025-08-08 22:37:55 +02:00
"character_id" : char . ID ,
2025-08-08 06:39:46 +02:00
"session_id" : sessionID ,
} )
}
// DeleteCharacterSession löscht eine Session
func DeleteCharacterSession ( c * gin . Context ) {
2025-08-10 22:26:07 +02:00
logger . Debug ( "DeleteCharacterSession aufgerufen" )
2025-08-08 06:39:46 +02:00
sessionID := c . Param ( "sessionId" )
2025-08-08 22:37:55 +02:00
userID := c . GetUint ( "userID" )
2025-08-10 22:26:07 +02:00
logger . Debug ( "DeleteCharacterSession: SessionID = %s, UserID = %d" , sessionID , userID )
2025-08-08 06:39:46 +02:00
2025-08-08 22:37:55 +02:00
if userID == 0 {
2025-08-10 22:26:07 +02:00
logger . Warn ( "DeleteCharacterSession: Unauthorized - UserID ist 0" )
2025-08-08 22:37:55 +02:00
c . JSON ( http . StatusUnauthorized , gin . H { "error" : "Unauthorized" } )
return
}
// Session aus Datenbank löschen (nur eigene Sessions)
2025-08-10 22:26:07 +02:00
logger . Debug ( "DeleteCharacterSession: Lösche Session aus Datenbank..." )
2025-08-08 22:37:55 +02:00
result := database . DB . Where ( "id = ? AND user_id = ?" , sessionID , userID ) . Delete ( & models . CharacterCreationSession { } )
if result . Error != nil {
2025-08-10 22:26:07 +02:00
logger . Error ( "DeleteCharacterSession: Fehler beim Löschen der Session: %s" , result . Error . Error ( ) )
2025-08-08 22:37:55 +02:00
c . JSON ( http . StatusInternalServerError , gin . H { "error" : "Failed to delete session" } )
return
}
if result . RowsAffected == 0 {
2025-08-10 22:26:07 +02:00
logger . Warn ( "DeleteCharacterSession: Session nicht gefunden oder bereits gelöscht - SessionID: %s, UserID: %d" ,
sessionID , userID )
2025-08-08 22:37:55 +02:00
c . JSON ( http . StatusNotFound , gin . H { "error" : "Session not found" } )
return
}
2025-08-08 06:39:46 +02:00
2025-08-10 22:26:07 +02:00
logger . Info ( "DeleteCharacterSession: Session erfolgreich gelöscht - SessionID: %s, UserID: %d, RowsAffected: %d" ,
sessionID , userID , result . RowsAffected )
2025-08-08 06:39:46 +02:00
c . JSON ( http . StatusOK , gin . H {
"message" : "Session gelöscht" ,
"session_id" : sessionID ,
} )
}
// Reference Data Handlers
// GetRaces gibt verfügbare Rassen zurück
func GetRaces ( c * gin . Context ) {
// TODO: Aus Datenbank laden
races := [ ] string {
"Mensch" , "Elf" , "Halbling" , "Zwerg" , "Gnom" ,
}
c . JSON ( http . StatusOK , gin . H { "races" : races } )
}
// GetCharacterClasses gibt verfügbare Klassen zurück
func GetCharacterClasses ( c * gin . Context ) {
2025-08-08 22:37:55 +02:00
// Get game system from query parameter, default to "midgard"
gameSystem := c . DefaultQuery ( "game_system" , "midgard" )
// Load character classes from database
classes , err := models . GetCharacterClassesByActiveSources ( gameSystem )
if err != nil {
c . JSON ( http . StatusInternalServerError , gin . H { "error" : "Failed to load character classes" } )
return
}
// Extract class names for the frontend
var classNames [ ] string
for _ , class := range classes {
classNames = append ( classNames , class . Name )
}
// If no classes found in database, fall back to hardcoded list
if len ( classNames ) == 0 {
classNames = [ ] string {
2025-08-28 00:46:55 +02:00
"Assassine" , "Barbar" , "Barde" ,
"Heiler" , "Händler" , "Kämpfer" , "Krieger" ,
"Magier" , "Ordenskrieger" , "Priester Beschützer" , "Schamane" , "Skalde" ,
"Magister" , "Thaumaturg" , "Waldläufer" , "Zauberer" ,
2025-08-08 22:37:55 +02:00
}
2025-08-08 06:39:46 +02:00
}
2025-08-08 22:37:55 +02:00
c . JSON ( http . StatusOK , gin . H { "classes" : classNames } )
2025-08-08 06:39:46 +02:00
}
// GetOrigins gibt verfügbare Herkünfte zurück
func GetOrigins ( c * gin . Context ) {
// TODO: Aus Datenbank laden
origins := [ ] string {
2025-08-28 00:46:55 +02:00
"Alba" , "Aran" , "Buluga" , "Chryseia" ,
2025-08-08 06:39:46 +02:00
"Eschar" , "Fuardain" , "Ikenga" , "KanThaiPan" , "Küstenstaaten" ,
"Medjis" , "Moravod" , "Nahuatlan" , "Rawindra" , "Scharidis" ,
2025-08-28 00:46:55 +02:00
"Tegarisch Steppe" , "Valian" , "Waeland" , "Ywerddon" ,
2025-08-08 06:39:46 +02:00
}
c . JSON ( http . StatusOK , gin . H { "origins" : origins } )
}
// SearchBeliefs sucht Glaubensrichtungen
func SearchBeliefs ( c * gin . Context ) {
query := c . Query ( "q" )
if len ( query ) < 2 {
c . JSON ( http . StatusBadRequest , gin . H { "error" : "Mindestens 2 Zeichen erforderlich" } )
return
}
2025-08-09 06:46:16 +02:00
// Get game system from query parameter, default to "midgard"
gameSystem := c . DefaultQuery ( "game_system" , "midgard" )
// Load beliefs from database
believes , err := models . GetBelievesByActiveSources ( gameSystem )
if err != nil {
c . JSON ( http . StatusInternalServerError , gin . H { "error" : "Failed to load beliefs from database" } )
return
}
// Extract belief names and filter by query
var allBeliefs [ ] string
for _ , belief := range believes {
allBeliefs = append ( allBeliefs , belief . Name )
}
// If no beliefs found in database, fall back to hardcoded list
if len ( allBeliefs ) == 0 {
allBeliefs = [ ] string {
"Apshai" , "Arthusos" , "Beschützer" , "Dwyllas" , "Elfen" ,
"Fruchtbarkeitsgöttin" , "Gaia" , "Grafschafter" , "Heiler" ,
"Jäger" , "Kämpfer" , "Lichbringer" , "Meeresherr" , "Natur" ,
"Ostküste" , "Priester" , "Rechtschaffener" , "Schutzpatron" ,
"Stammesgeist" , "Totengott" , "Unterwelt" , "Vater" , "Weisheit" ,
"Xan" , "Ylhoon" , "Zauberer" ,
}
2025-08-08 06:39:46 +02:00
}
var results [ ] string
queryLower := strings . ToLower ( query )
for _ , belief := range allBeliefs {
if strings . Contains ( strings . ToLower ( belief ) , queryLower ) {
results = append ( results , belief )
}
}
c . JSON ( http . StatusOK , gin . H { "beliefs" : results } )
}
// SkillCategoryWithPoints repräsentiert eine Kategorie mit verfügbaren Lernpunkten
type SkillCategoryWithPoints struct {
Name string ` json:"name" `
DisplayName string ` json:"display_name" `
Points int ` json:"points" `
MaxPoints int ` json:"max_points" `
}
2025-08-14 23:25:01 +02:00
// LearningPointsData repräsentiert die Lernpunkte und typischen Fertigkeiten einer Charakterklasse
type LearningPointsData struct {
ClassName string ` json:"class_name" `
ClassCode string ` json:"class_code" `
LearningPoints map [ string ] int ` json:"learning_points" ` // Kategorie -> Lernpunkte
WeaponPoints int ` json:"weapon_points" ` // Waffenlernpunkte
SpellPoints int ` json:"spell_points" ` // Zauberlerneinheiten (falls vorhanden)
TypicalSkills [ ] TypicalSkill ` json:"typical_skills" ` // Typische Fertigkeiten
TypicalSpells [ ] string ` json:"typical_spells" ` // Typische Zauber (falls vorhanden)
}
// TypicalSkill repräsentiert eine typische Fertigkeit mit Bonus
type TypicalSkill struct {
Name string ` json:"name" `
Bonus int ` json:"bonus" `
Attribute string ` json:"attribute" ` // Zugehöriges Attribut (z.B. "Gs", "In")
Notes string ` json:"notes" ` // Zusätzliche Notizen
}
// GetCharacterClassLearningPoints gibt die Lernpunkte und typischen Fertigkeiten für eine Charakterklasse zurück
func GetCharacterClassLearningPoints ( c * gin . Context ) {
className := c . Query ( "class" )
if className == "" {
c . JSON ( http . StatusBadRequest , gin . H { "error" : "Charakterklassen-Name ist erforderlich (Parameter 'class')" } )
return
}
stand := c . Query ( "stand" ) // Optional: Unfreie, Volk, Mittelschicht, Adel
// Hole die Lernpunkte-Daten für die Klasse
learningData , err := getLearningPointsForClass ( className , stand )
if err != nil {
c . JSON ( http . StatusNotFound , gin . H { "error" : "Charakterklasse nicht gefunden oder nicht unterstützt: " + err . Error ( ) } )
return
}
c . JSON ( http . StatusOK , learningData )
}
// getLearningPointsForClass gibt die Lernpunkte-Daten für eine bestimmte Charakterklasse zurück
func getLearningPointsForClass ( className string , stand string ) ( * LearningPointsData , error ) {
// Mapping der Klassennamen zu Codes (falls notwendig)
classMapping := map [ string ] string {
"Assassine" : "As" ,
"Barbar" : "Bb" ,
"Glücksritter" : "Gl" ,
"Händler" : "Hä" ,
"Krieger" : "Kr" ,
"Spitzbube" : "Sp" ,
"Waldläufer" : "Wa" ,
"Barde" : "Ba" ,
"Ordenskrieger" : "Or" ,
"Druide" : "Dr" ,
"Hexer" : "Hx" ,
"Magier" : "Ma" ,
"Priester Beschützer" : "PB" ,
"Priester Streiter" : "PS" ,
"Schamane" : "Sc" ,
}
classCode := classMapping [ className ]
if classCode == "" {
classCode = className // Falls der Name bereits ein Code ist
}
// Definiere die Lernpunkte-Daten basierend auf Lerntabelle_Erstellung.md
var data * LearningPointsData
switch classCode {
case "As" , "Assassine" :
data = & LearningPointsData {
ClassName : "Assassine" ,
ClassCode : "As" ,
LearningPoints : map [ string ] int {
"Alltag" : 1 ,
"Halbwelt" : 2 ,
"Sozial" : 4 ,
"Unterwelt" : 8 ,
2025-08-21 22:57:13 +02:00
"Waffen" : 24 ,
2025-08-14 23:25:01 +02:00
} ,
2025-08-21 22:57:13 +02:00
//WeaponPoints: 24,
2025-08-14 23:25:01 +02:00
TypicalSkills : [ ] TypicalSkill {
{ Name : "Meucheln" , Bonus : 8 , Attribute : "Gs" , Notes : "" } ,
} ,
}
case "Bb" , "Barbar" :
data = & LearningPointsData {
ClassName : "Barbar" ,
ClassCode : "Bb" ,
LearningPoints : map [ string ] int {
"Alltag" : 2 ,
"Freiland" : 4 ,
"Kampf" : 1 ,
"Körper" : 2 ,
2025-08-21 22:57:13 +02:00
"Waffen" : 24 ,
2025-08-14 23:25:01 +02:00
} ,
2025-08-21 22:57:13 +02:00
//WeaponPoints: 24,
2025-08-14 23:25:01 +02:00
TypicalSkills : [ ] TypicalSkill {
{ Name : "Spurensuche" , Bonus : 8 , Attribute : "In" , Notes : "in Heimatlandschaft" } ,
{ Name : "Überleben" , Bonus : 8 , Attribute : "In" , Notes : "in Heimatlandschaft" } ,
} ,
}
case "Gl" , "Glücksritter" :
data = & LearningPointsData {
ClassName : "Glücksritter" ,
ClassCode : "Gl" ,
LearningPoints : map [ string ] int {
"Alltag" : 2 ,
"Halbwelt" : 3 ,
"Sozial" : 8 ,
2025-08-21 22:57:13 +02:00
"Waffen" : 24 ,
2025-08-14 23:25:01 +02:00
} ,
2025-08-21 22:57:13 +02:00
//WeaponPoints: 24,
2025-08-14 23:25:01 +02:00
TypicalSkills : [ ] TypicalSkill {
{ Name : "Fechten" , Bonus : 5 , Attribute : "Gs" , Notes : "oder beidhändiger Kampf+5 (Gs)" } ,
} ,
}
case "Hä" , "Händler" :
data = & LearningPointsData {
ClassName : "Händler" ,
ClassCode : "Hä" ,
LearningPoints : map [ string ] int {
"Alltag" : 4 ,
"Sozial" : 8 ,
"Wissen" : 4 ,
2025-08-21 22:57:13 +02:00
"Waffen" : 20 ,
2025-08-14 23:25:01 +02:00
} ,
2025-08-21 22:57:13 +02:00
//WeaponPoints: 20,
2025-08-14 23:25:01 +02:00
TypicalSkills : [ ] TypicalSkill {
{ Name : "Geschäftssinn" , Bonus : 8 , Attribute : "In" , Notes : "" } ,
} ,
}
case "Kr" , "Krieger" :
data = & LearningPointsData {
ClassName : "Krieger" ,
ClassCode : "Kr" ,
LearningPoints : map [ string ] int {
"Alltag" : 2 ,
"Kampf" : 3 ,
"Körper" : 1 ,
2025-08-21 22:57:13 +02:00
"Waffen" : 36 ,
2025-08-14 23:25:01 +02:00
} ,
2025-08-21 22:57:13 +02:00
//WeaponPoints: 36,
2025-08-14 23:25:01 +02:00
TypicalSkills : [ ] TypicalSkill {
{ Name : "Kampf in Vollrüstung" , Bonus : 5 , Attribute : "St" , Notes : "" } ,
} ,
}
case "Sp" , "Spitzbube" :
data = & LearningPointsData {
ClassName : "Spitzbube" ,
ClassCode : "Sp" ,
LearningPoints : map [ string ] int {
"Alltag" : 2 ,
"Halbwelt" : 6 ,
"Unterwelt" : 12 ,
2025-08-21 22:57:13 +02:00
"Waffen" : 20 ,
2025-08-14 23:25:01 +02:00
} ,
2025-08-21 22:57:13 +02:00
//WeaponPoints: 20,
2025-08-14 23:25:01 +02:00
TypicalSkills : [ ] TypicalSkill {
{ Name : "Fallenmechanik" , Bonus : 8 , Attribute : "Gs" , Notes : "oder Geschäftssinn+8 (In)" } ,
} ,
}
case "Wa" , "Waldläufer" :
data = & LearningPointsData {
ClassName : "Waldläufer" ,
ClassCode : "Wa" ,
LearningPoints : map [ string ] int {
"Alltag" : 1 ,
"Freiland" : 11 ,
"Körper" : 4 ,
2025-08-21 22:57:13 +02:00
"Waffen" : 20 ,
2025-08-14 23:25:01 +02:00
} ,
2025-08-21 22:57:13 +02:00
//WeaponPoints: 20,
2025-08-14 23:25:01 +02:00
TypicalSkills : [ ] TypicalSkill {
{ Name : "Scharfschießen" , Bonus : 5 , Attribute : "Gs" , Notes : "" } ,
} ,
}
case "Ba" , "Barde" :
data = & LearningPointsData {
ClassName : "Barde" ,
ClassCode : "Ba" ,
LearningPoints : map [ string ] int {
"Alltag" : 2 ,
"Sozial" : 4 ,
"Wissen" : 4 ,
2025-08-21 22:57:13 +02:00
"Waffen" : 16 ,
2025-08-14 23:25:01 +02:00
} ,
2025-08-21 22:57:13 +02:00
//WeaponPoints: 16,
SpellPoints : 3 ,
2025-08-14 23:25:01 +02:00
TypicalSkills : [ ] TypicalSkill {
{ Name : "Musizieren" , Bonus : 12 , Attribute : "Gs" , Notes : "" } ,
{ Name : "Landeskunde" , Bonus : 8 , Attribute : "In" , Notes : "für Heimat" } ,
} ,
TypicalSpells : [ ] string { "Zauberlieder" } ,
}
case "Or" , "Ordenskrieger" :
data = & LearningPointsData {
ClassName : "Ordenskrieger" ,
ClassCode : "Or" ,
LearningPoints : map [ string ] int {
"Alltag" : 2 ,
"Kampf" : 3 ,
"Wissen" : 2 ,
2025-08-21 22:57:13 +02:00
"Waffen" : 18 ,
2025-08-14 23:25:01 +02:00
} ,
2025-08-21 22:57:13 +02:00
//WeaponPoints: 18,
SpellPoints : 3 ,
2025-08-14 23:25:01 +02:00
TypicalSkills : [ ] TypicalSkill {
{ Name : "Athletik" , Bonus : 8 , Attribute : "St" , Notes : "oder Meditieren+8 (Wk)" } ,
} ,
TypicalSpells : [ ] string { "Wundertaten" } ,
}
case "Dr" , "Druide" :
data = & LearningPointsData {
ClassName : "Druide" ,
ClassCode : "Dr" ,
LearningPoints : map [ string ] int {
"Alltag" : 2 ,
"Freiland" : 4 ,
"Wissen" : 2 ,
2025-08-21 22:57:13 +02:00
"Waffen" : 6 ,
2025-08-14 23:25:01 +02:00
} ,
2025-08-21 22:57:13 +02:00
//WeaponPoints: 6,
SpellPoints : 5 ,
2025-08-14 23:25:01 +02:00
TypicalSkills : [ ] TypicalSkill {
{ Name : "Pflanzenkunde" , Bonus : 8 , Attribute : "In" , Notes : "" } ,
{ Name : "Schreiben" , Bonus : 12 , Attribute : "In" , Notes : "für Ogam-Zeichen" } ,
} ,
TypicalSpells : [ ] string { "Dweomer" , "Tiere rufen" } ,
}
case "Hx" , "Hexer" :
data = & LearningPointsData {
ClassName : "Hexer" ,
ClassCode : "Hx" ,
LearningPoints : map [ string ] int {
"Alltag" : 3 ,
"Sozial" : 2 ,
"Wissen" : 2 ,
2025-08-21 22:57:13 +02:00
"Waffen" : 2 ,
2025-08-14 23:25:01 +02:00
} ,
2025-08-21 22:57:13 +02:00
//WeaponPoints: 2,
SpellPoints : 6 ,
2025-08-14 23:25:01 +02:00
TypicalSkills : [ ] TypicalSkill {
{ Name : "Gassenwissen" , Bonus : 8 , Attribute : "In" , Notes : "oder Verführen+8 (pA)" } ,
} ,
TypicalSpells : [ ] string { "Beherrschen" , "Verändern" , "Verwünschen" , "Binden des Vertrauten" } ,
}
case "Ma" , "Magier" :
data = & LearningPointsData {
ClassName : "Magier" ,
ClassCode : "Ma" ,
LearningPoints : map [ string ] int {
"Alltag" : 1 ,
"Wissen" : 5 ,
2025-08-21 22:57:13 +02:00
"Waffen" : 2 ,
2025-08-14 23:25:01 +02:00
} ,
2025-08-21 22:57:13 +02:00
//WeaponPoints: 2,
SpellPoints : 7 ,
2025-08-14 23:25:01 +02:00
TypicalSkills : [ ] TypicalSkill {
{ Name : "Zauberkunde" , Bonus : 8 , Attribute : "In" , Notes : "" } ,
{ Name : "Schreiben" , Bonus : 12 , Attribute : "In" , Notes : "für Muttersprache" } ,
} ,
TypicalSpells : [ ] string { "beliebig außer Dweomer, Wundertaten, Zauberlieder" , "Erkennen von Zauberei" } ,
}
case "PB" , "Priester Beschützer" :
data = & LearningPointsData {
ClassName : "Priester Beschützer" ,
ClassCode : "PB" ,
LearningPoints : map [ string ] int {
"Alltag" : 2 ,
"Sozial" : 2 ,
"Wissen" : 3 ,
2025-08-21 22:57:13 +02:00
"Waffen" : 6 ,
2025-08-14 23:25:01 +02:00
} ,
2025-08-21 22:57:13 +02:00
//WeaponPoints: 6,
SpellPoints : 5 ,
2025-08-14 23:25:01 +02:00
TypicalSkills : [ ] TypicalSkill {
{ Name : "Menschenkenntnis" , Bonus : 8 , Attribute : "In" , Notes : "" } ,
{ Name : "Schreiben" , Bonus : 12 , Attribute : "In" , Notes : "für Muttersprache" } ,
} ,
TypicalSpells : [ ] string { "Wundertaten" , "Heilen von Wunden" } ,
}
case "PS" , "Priester Streiter" :
data = & LearningPointsData {
ClassName : "Priester Streiter" ,
ClassCode : "PS" ,
LearningPoints : map [ string ] int {
"Alltag" : 3 ,
"Kampf" : 2 ,
"Wissen" : 2 ,
2025-08-21 22:57:13 +02:00
"Waffen" : 8 ,
2025-08-14 23:25:01 +02:00
} ,
2025-08-21 22:57:13 +02:00
//WeaponPoints: 8,
SpellPoints : 5 ,
2025-08-14 23:25:01 +02:00
TypicalSkills : [ ] TypicalSkill {
{ Name : "Erste Hilfe" , Bonus : 8 , Attribute : "Gs" , Notes : "" } ,
{ Name : "Schreiben" , Bonus : 12 , Attribute : "In" , Notes : "für Muttersprache" } ,
} ,
TypicalSpells : [ ] string { "Wundertaten" , "Bannen von Finsterwerk" , "Strahlender Panzer" } ,
}
case "Sc" , "Schamane" :
data = & LearningPointsData {
ClassName : "Schamane" ,
ClassCode : "Sc" ,
LearningPoints : map [ string ] int {
"Alltag" : 2 ,
"Körper" : 4 ,
"Wissen" : 2 ,
2025-08-21 22:57:13 +02:00
"Waffen" : 6 ,
2025-08-14 23:25:01 +02:00
} ,
2025-08-21 22:57:13 +02:00
//WeaponPoints: 6,
SpellPoints : 5 ,
2025-08-14 23:25:01 +02:00
TypicalSkills : [ ] TypicalSkill {
{ Name : "Tierkunde" , Bonus : 8 , Attribute : "In" , Notes : "" } ,
{ Name : "Überleben" , Bonus : 8 , Attribute : "In" , Notes : "in Heimatlandschaft" } ,
} ,
TypicalSpells : [ ] string { "Dweomer" , "Wundertaten" , "Austreibung des Bösen" , "Bannen von Gift" } ,
}
default :
return nil , fmt . Errorf ( "unbekannte Charakterklasse: %s" , className )
}
// Bonus-Lernpunkte basierend auf Stand hinzufügen
if stand != "" && data != nil {
standBonus := getStandBonusPoints ( stand )
// Füge die Stand-Bonuspunkte zu den normalen Lernpunkten hinzu
for category , bonus := range standBonus {
if currentPoints , exists := data . LearningPoints [ category ] ; exists {
data . LearningPoints [ category ] = currentPoints + bonus
} else {
// Falls die Kategorie noch nicht existiert, füge sie hinzu
data . LearningPoints [ category ] = bonus
}
}
// Speichere die Stand-Bonuspunkte auch separat für Referenz
//data.StandPoints = standBonus
}
return data , nil
}
// getStandBonusPoints gibt die Bonus-Lernpunkte basierend auf dem Stand zurück
func getStandBonusPoints ( stand string ) map [ string ] int {
switch stand {
case "Unfreie" :
return map [ string ] int { "Halbwelt" : 2 }
case "Volk" :
return map [ string ] int { "Alltag" : 2 }
case "Mittelschicht" :
return map [ string ] int { "Wissen" : 2 }
case "Adel" :
return map [ string ] int { "Sozial" : 2 }
default :
return make ( map [ string ] int )
}
}