Files
bamort/backend/character/handlers.go
T

2972 lines
90 KiB
Go
Raw Normal View History

package character
2024-12-21 07:34:38 +01:00
import (
"bamort/database"
2025-07-24 07:39:43 +02:00
"bamort/gsmaster"
2025-01-03 14:03:27 +01:00
"bamort/models"
2025-08-08 06:35:11 +02:00
"strconv"
"strings"
2025-08-08 06:39:46 +02:00
"time"
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-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) {
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-07-28 21:35:29 +02:00
var characters []models.Char
var listOfChars []models.CharList
if err := database.DB.Find(&characters).Error; err != nil {
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-01-03 14:03:27 +01:00
for i := range characters {
2025-07-28 21:35:29 +02:00
listOfChars = append(listOfChars, models.CharList{
2025-01-03 14:03:27 +01:00
BamortBase: models.BamortBase{
ID: characters[i].ID,
Name: characters[i].Name,
},
Rasse: characters[i].Rasse,
Typ: characters[i].Typ,
Grad: characters[i].Grad,
Owner: "test",
})
}
c.JSON(http.StatusOK, listOfChars)
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
}
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
}
// Bind the updated data
if err := c.ShouldBindJSON(&character); err != nil {
respondWithError(c, http.StatusBadRequest, err.Error())
return
}
// Save the updated character
if err := database.DB.Save(&character).Error; err != nil {
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
2025-08-01 13:53:10 +02:00
// GetLearnSkillCostOld is deprecated. Use GetLearnSkillCost instead.
// This function uses the old hardcoded learning cost system.
2025-08-01 06:27:38 +02:00
func GetLearnSkillCostOld(c *gin.Context) {
2025-07-24 07:39:43 +02:00
// Get the character ID from the request
charID := c.Param("id")
// Load the character from the database
2025-07-28 21:35:29 +02:00
var character models.Char
2025-07-24 07:39:43 +02:00
if err := character.FirstID(charID); err != nil {
respondWithError(c, http.StatusInternalServerError, "Failed to retrieve character")
return
}
// Load the skill from the request
2025-07-28 22:08:19 +02:00
var s models.SkFertigkeit
2025-07-24 07:39:43 +02:00
if err := c.ShouldBindJSON(&s); err != nil {
respondWithError(c, http.StatusBadRequest, err.Error())
return
}
2025-07-27 23:13:04 +02:00
var skill models.Skill
2025-07-24 07:39:43 +02:00
if err := skill.First(s.Name); err != nil {
respondWithError(c, http.StatusBadRequest, "can not find speel in gsmaster: "+err.Error())
return
}
2025-08-01 06:27:38 +02:00
cost, err := gsmaster.CalculateSkillLearnCostOld(skill.Name, character.Typ)
2025-07-24 07:39:43 +02:00
if err != nil {
respondWithError(c, http.StatusBadRequest, "error getting costs to learn skill: "+err.Error())
return
}
// Return the updated character
c.JSON(http.StatusOK, cost)
}
2025-08-01 13:53:10 +02:00
// GetLearnSpellCostOld is deprecated. Use GetLearnSpellCost instead.
// This function uses the old hardcoded learning cost system.
2025-08-01 06:27:38 +02:00
func GetLearnSpellCostOld(c *gin.Context) {
2025-07-24 07:39:43 +02:00
// Get the character ID from the request
charID := c.Param("id")
// Load the character from the database
2025-07-28 21:35:29 +02:00
var character models.Char
2025-07-24 07:39:43 +02:00
if err := character.FirstID(charID); err != nil {
respondWithError(c, http.StatusInternalServerError, "Failed to retrieve character")
return
}
// Load the spell from the request
2025-07-28 22:08:19 +02:00
var s models.SkZauber
2025-07-24 07:39:43 +02:00
if err := c.ShouldBindJSON(&s); err != nil {
respondWithError(c, http.StatusBadRequest, err.Error())
return
}
2025-07-27 23:50:19 +02:00
var spell models.Spell
2025-07-24 07:39:43 +02:00
if err := spell.First(s.Name); err != nil {
respondWithError(c, http.StatusBadRequest, "can not find speel in gsmaster: "+err.Error())
return
}
sd := gsmaster.SpellDefinition{
Name: spell.Name,
Stufe: spell.Stufe,
School: spell.Category,
}
2025-08-01 06:27:38 +02:00
cost, err := gsmaster.CalculateSpellLearnCostOld(spell.Name, character.Typ)
2025-07-24 07:39:43 +02:00
if err != nil {
respondWithError(c, http.StatusBadRequest, "error getting costs to learn spell: "+err.Error())
return
}
sd.CostEP = cost
// Return the updated character
c.JSON(http.StatusOK, sd)
}
// ExperienceAndWealthResponse repräsentiert die Antwort für EP und Vermögen
type ExperienceAndWealthResponse struct {
ExperiencePoints int `json:"experience_points"`
Wealth struct {
Goldstücke int `json:"gold_coins"` // GS
Silberstücke int `json:"silver_coins"` // SS
Kupferstücke int `json:"copper_coins"` // KS
TotalInGS int `json:"total_in_ss"` // Gesamt in Silberstücken
} `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)
gs := character.Vermoegen.Goldstücke
ss := character.Vermoegen.Silberstücke
ks := character.Vermoegen.Kupferstücke
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
}
response.Wealth.Goldstücke = gs
response.Wealth.Silberstücke = ss
response.Wealth.Kupferstücke = ks
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 {
oldGold = character.Vermoegen.Goldstücke
oldSilver = character.Vermoegen.Silberstücke
oldCopper = character.Vermoegen.Kupferstücke
}
// Aktualisiere oder erstelle Vermögen
if character.Vermoegen.ID == 0 {
// Erstelle neues Vermögen
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,
},
Goldstücke: getValueOrDefault(req.Goldstücke, 0),
Silberstücke: getValueOrDefault(req.Silberstücke, 0),
Kupferstücke: getValueOrDefault(req.Kupferstücke, 0),
}
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 {
character.Vermoegen.Goldstücke = *req.Goldstücke
}
if req.Silberstücke != nil {
character.Vermoegen.Silberstücke = *req.Silberstücke
}
if req.Kupferstücke != nil {
character.Vermoegen.Kupferstücke = *req.Kupferstücke
}
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
if req.Goldstücke != nil && oldGold != character.Vermoegen.Goldstücke {
CreateAuditLogEntry(
character.ID,
"gold",
oldGold,
character.Vermoegen.Goldstücke,
AuditLogReason(req.Reason),
userID,
req.Notes,
)
}
if req.Silberstücke != nil && oldSilver != character.Vermoegen.Silberstücke {
CreateAuditLogEntry(
character.ID,
"silver",
oldSilver,
character.Vermoegen.Silberstücke,
AuditLogReason(req.Reason),
userID,
req.Notes,
)
}
if req.Kupferstücke != nil && oldCopper != character.Vermoegen.Kupferstücke {
CreateAuditLogEntry(
character.ID,
"copper",
oldCopper,
character.Vermoegen.Kupferstücke,
AuditLogReason(req.Reason),
userID,
req.Notes,
)
}
c.JSON(http.StatusOK, gin.H{
"message": "Wealth updated successfully",
"wealth": gin.H{
"goldstücke": character.Vermoegen.Goldstücke,
"silberstücke": character.Vermoegen.Silberstücke,
"kupferstücke": character.Vermoegen.Kupferstücke,
},
})
}
// 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
}
// 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 {
// 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{
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 {
// 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{
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-01 13:53:10 +02:00
// calculateMultiLevelCostsOld is deprecated. Use the new database-based learning cost system instead.
// This function uses the old hardcoded learning cost system.
2025-08-01 06:27:38 +02:00
// calculateMultiLevelCostsOld berechnet die Kosten für mehrere Level-Verbesserungen mit gsmaster.GetLernCostNextLevel
func calculateMultiLevelCostsOld(character *models.Char, skillName string, currentLevel int, levelsToLearn []int, rewardType string, usePP, useGold int) (*models.LearnCost, error) {
2025-07-25 14:00:56 +02:00
if len(levelsToLearn) == 0 {
return nil, fmt.Errorf("keine Level zum Lernen angegeben")
}
2025-07-24 07:39:43 +02:00
2025-07-25 14:00:56 +02:00
// Sortiere die Level aufsteigend
sortedLevels := make([]int, len(levelsToLearn))
copy(sortedLevels, levelsToLearn)
for i := 0; i < len(sortedLevels)-1; i++ {
for j := i + 1; j < len(sortedLevels); j++ {
if sortedLevels[i] > sortedLevels[j] {
sortedLevels[i], sortedLevels[j] = sortedLevels[j], sortedLevels[i]
}
}
}
// Erstelle LernCostRequest
var rewardTypePtr *string
if rewardType != "" {
rewardTypePtr = &rewardType
}
request := gsmaster.LernCostRequest{
CharId: uint(character.ID),
Name: skillName,
CurrentLevel: currentLevel,
Type: "skill",
Action: "improve",
TargetLevel: sortedLevels[len(sortedLevels)-1], // Höchstes Level als Ziel
UsePP: usePP,
UseGold: useGold,
Reward: rewardTypePtr,
}
2025-07-27 23:31:04 +02:00
totalCost := &models.LearnCost{
2025-07-25 14:00:56 +02:00
Stufe: sortedLevels[len(sortedLevels)-1],
LE: 0,
Ep: 0,
Money: 0,
}
remainingPP := usePP
remainingGold := useGold
// Berechne Kosten für jedes Level
for _, targetLevel := range sortedLevels {
2025-08-01 06:27:38 +02:00
classAbr := getCharacterClassOld(character)
cat, difficulty, _ := gsmaster.FindBestCategoryForSkillLearningOld(skillName, classAbr)
2025-07-25 14:00:56 +02:00
levelResult := gsmaster.SkillCostResultNew{
CharacterID: fmt.Sprintf("%d", character.ID),
CharacterClass: classAbr,
2025-07-25 14:00:56 +02:00
SkillName: skillName,
Category: cat,
2025-08-01 06:27:38 +02:00
Difficulty: gsmaster.GetSkillDifficultyOld(difficulty, skillName),
2025-07-25 14:00:56 +02:00
TargetLevel: targetLevel,
}
// Temporäre Request für dieses Level
tempRequest := request
tempRequest.CurrentLevel = targetLevel - 1
tempRequest.UsePP = remainingPP
tempRequest.UseGold = remainingGold
2025-08-01 06:27:38 +02:00
err := gsmaster.GetLernCostNextLevelOld(&tempRequest, &levelResult, rewardTypePtr, targetLevel, character.Typ)
2025-07-25 14:00:56 +02:00
if err != nil {
return nil, fmt.Errorf("fehler bei Level %d: %v", targetLevel, err)
}
// Aktualisiere verbleibende Ressourcen
if levelResult.PPUsed > 0 {
remainingPP -= levelResult.PPUsed
if remainingPP < 0 {
remainingPP = 0
}
}
if levelResult.GoldUsed > 0 {
remainingGold -= levelResult.GoldUsed
if remainingGold < 0 {
remainingGold = 0
}
}
totalCost.Ep += levelResult.EP
totalCost.Money += levelResult.GoldCost
totalCost.LE += levelResult.LE
}
return totalCost, nil
2025-07-24 07:39:43 +02:00
}
2025-08-01 13:53:10 +02:00
// getCharacterClassOld is deprecated. Use character.Klasse directly or appropriate database lookups.
// This function provides backwards compatibility for character class access.
2025-08-01 06:27:38 +02:00
// getCharacterClassOld gibt die Charakterklassen-Abkürzung zurück
func getCharacterClassOld(character *models.Char) string {
2025-07-25 14:00:56 +02:00
if len(character.Typ) > 3 {
2025-08-01 06:27:38 +02:00
return gsmaster.GetClassAbbreviationOld(character.Typ)
2025-07-25 14:00:56 +02:00
}
return character.Typ
2025-07-24 07:39:43 +02:00
}
2025-08-01 13:53:10 +02:00
// LearnSkillOld is deprecated. Use LearnSkill instead.
// This function uses the old hardcoded learning cost system.
2025-08-01 06:27:38 +02:00
// LearnSkillOld lernt eine neue Fertigkeit und erstellt Audit-Log-Einträge
func LearnSkillOld(c *gin.Context) {
2025-07-24 07:39:43 +02:00
charID := c.Param("id")
2025-07-28 21:35:29 +02:00
var character models.Char
2025-07-24 07:39:43 +02:00
if err := character.FirstID(charID); err != nil {
respondWithError(c, http.StatusNotFound, "Charakter nicht gefunden")
return
}
2025-07-25 14:00:56 +02:00
// Verwende gsmaster.LernCostRequest direkt
var request gsmaster.LernCostRequest
2025-07-24 07:39:43 +02:00
if err := c.ShouldBindJSON(&request); err != nil {
respondWithError(c, http.StatusBadRequest, "Ungültige Anfrageparameter: "+err.Error())
return
}
2025-07-25 14:00:56 +02:00
// 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
2025-07-24 07:39:43 +02:00
}
// Verwende Klassenabkürzung wenn der Typ länger als 3 Zeichen ist
var characterClass string
if len(character.Typ) > 3 {
2025-08-01 06:27:38 +02:00
characterClass = gsmaster.GetClassAbbreviationOld(character.Typ)
} else {
characterClass = character.Typ
}
2025-07-24 07:39:43 +02:00
2025-07-25 14:00:56 +02:00
// Bestimme das finale Level
finalLevel := request.TargetLevel
if finalLevel <= 0 {
finalLevel = 1 // Standard für neue Fertigkeit
}
// Für Learning müssen wir von Level 0 (nicht gelernt) auf finalLevel lernen
var totalEP, totalGold, totalPP int
2025-07-25 14:00:56 +02:00
var err error
// Loop für jeden Level von 0 bis finalLevel (für neue Fertigkeiten)
for tempLevel := 0; 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"
}
// Berechne Kosten für diesen einen Level
var costResult gsmaster.SkillCostResultNew
costResult.CharacterID = fmt.Sprintf("%d", character.ID)
costResult.CharacterClass = characterClass
2025-07-25 14:00:56 +02:00
costResult.SkillName = request.Name
2025-08-01 06:27:38 +02:00
err = gsmaster.GetLernCostNextLevelOld(&tempRequest, &costResult, request.Reward, nextLevel, character.Rasse)
2025-07-25 14:00:56 +02:00
if err != nil {
respondWithError(c, http.StatusBadRequest, fmt.Sprintf("Fehler bei Level %d: %v", nextLevel, err))
return
}
// Addiere die Kosten
totalEP += costResult.EP
totalGold += costResult.GoldCost
totalPP += costResult.PPUsed
2025-07-24 07:39:43 +02:00
}
// Prüfe, ob genügend EP vorhanden sind
2025-07-25 20:43:28 +02:00
currentEP := character.Erfahrungsschatz.EP
2025-07-25 14:00:56 +02:00
if currentEP < totalEP {
2025-07-24 07:39:43 +02:00
respondWithError(c, http.StatusBadRequest, "Nicht genügend Erfahrungspunkte vorhanden")
return
}
// Prüfe, ob genügend Gold vorhanden ist
currentGold := character.Vermoegen.Goldstücke
2025-07-25 14:00:56 +02:00
if currentGold < totalGold {
2025-07-24 07:39:43 +02:00
respondWithError(c, http.StatusBadRequest, "Nicht genügend Gold vorhanden")
return
}
// Prüfe, ob genügend PP vorhanden sind (PP der jeweiligen Fertigkeit) - für neue Fertigkeiten normalerweise 0
currentPP := 0
for _, skill := range character.Fertigkeiten {
if skill.Name == request.Name {
currentPP = skill.Pp
break
}
}
// Falls nicht in normalen Fertigkeiten gefunden, prüfe Waffenfertigkeiten
if currentPP == 0 {
for _, skill := range character.Waffenfertigkeiten {
if skill.Name == request.Name {
currentPP = skill.Pp
break
}
}
}
if totalPP > 0 && currentPP < totalPP {
respondWithError(c, http.StatusBadRequest, "Nicht genügend Praxispunkte vorhanden")
return
}
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 {
var notes string
if finalLevel > 1 {
notes = fmt.Sprintf("Fertigkeit '%s' bis Level %d gelernt", request.Name, finalLevel)
} else {
notes = fmt.Sprintf("Fertigkeit '%s' gelernt", request.Name)
2025-07-24 07:39:43 +02:00
}
err = CreateAuditLogEntry(character.ID, "experience_points", currentEP, newEP, ReasonSkillLearning, 0, notes)
if err != nil {
respondWithError(c, http.StatusInternalServerError, "Fehler beim Erstellen des Audit-Log-Eintrags")
return
}
2025-07-25 20:43:28 +02:00
character.Erfahrungsschatz.EP = newEP
if err := database.DB.Save(&character.Erfahrungsschatz).Error; err != nil {
respondWithError(c, http.StatusInternalServerError, "Fehler beim Speichern der Erfahrungspunkte")
return
}
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-07-24 07:39:43 +02:00
notes := fmt.Sprintf("Gold für Fertigkeit '%s' ausgegeben", request.Name)
err = CreateAuditLogEntry(character.ID, "gold", currentGold, newGold, ReasonSkillLearning, 0, notes)
if err != nil {
respondWithError(c, http.StatusInternalServerError, "Fehler beim Erstellen des Audit-Log-Eintrags")
return
}
character.Vermoegen.Goldstücke = newGold
if err := database.DB.Save(&character.Vermoegen).Error; err != nil {
respondWithError(c, http.StatusInternalServerError, "Fehler beim Speichern des Vermögens")
return
}
}
// PP abziehen (falls vorhanden und erforderlich)
if totalPP > 0 {
// Suche die richtige Fertigkeit und ziehe PP ab
for i, skill := range character.Fertigkeiten {
if skill.Name == request.Name {
character.Fertigkeiten[i].Pp -= totalPP
break
}
}
// Falls nicht in normalen Fertigkeiten gefunden, prüfe Waffenfertigkeiten
for i, skill := range character.Waffenfertigkeiten {
if skill.Name == request.Name {
character.Waffenfertigkeiten[i].Pp -= totalPP
break
}
}
2025-07-24 07:39:43 +02:00
}
// Erstelle die neue Fertigkeit mit dem finalen Level
if err := updateOrCreateSkill(&character, request.Name, finalLevel); err != nil {
respondWithError(c, http.StatusInternalServerError, "Fehler beim Hinzufügen der Fertigkeit: "+err.Error())
return
}
2025-07-24 07:39:43 +02:00
// Charakter speichern
if err := database.DB.Save(&character).Error; err != nil {
respondWithError(c, http.StatusInternalServerError, "Fehler beim Speichern des Charakters")
return
}
2025-07-25 14:00:56 +02:00
// Response für Multi-Level oder Single-Level
response := gin.H{
2025-07-24 07:39:43 +02:00
"message": "Fertigkeit erfolgreich gelernt",
"skill_name": request.Name,
2025-07-25 14:00:56 +02:00
"final_level": finalLevel,
"ep_cost": totalEP,
"gold_cost": totalGold,
2025-07-24 07:39:43 +02:00
"remaining_ep": newEP,
"remaining_gold": newGold,
2025-07-25 14:00:56 +02:00
}
// 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)
}
response["levels_learned"] = levelsLearned
response["level_count"] = finalLevel
response["multi_level"] = true
}
c.JSON(http.StatusOK, response)
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
currentGold := char.Vermoegen.Goldstücke
// 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)
}
char.Vermoegen.Goldstücke = newGold
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
}
// 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)
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)
}
// Aktuellen Level ermitteln, falls nicht angegeben oder zu klein
currentLevel := request.CurrentLevel
if currentLevel <= 3 {
currentLevel = getCurrentSkillLevel(char, request.Name, "skill")
if currentLevel == -1 {
return "", nil, 0, fmt.Errorf("Fertigkeit nicht bei diesem Charakter vorhanden")
}
request.CurrentLevel = currentLevel
}
return characterClass, skillInfo, currentLevel, nil
}
// 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
var totalEP, totalGold, totalPP int
// 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
tempRequest := *request
tempRequest.CurrentLevel = tempLevel
tempRequest.TargetLevel = nextLevel
// Berechne Kosten für diesen einen Level
var costResult gsmaster.SkillCostResultNew
costResult.CharacterID = fmt.Sprintf("%d", char.ID)
costResult.CharacterClass = characterClass
costResult.SkillName = request.Name
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
tempLevel++
}
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 {
// Prüfe, ob genügend EP vorhanden sind
currentEP := char.Erfahrungsschatz.EP
if currentEP < totalEP {
return fmt.Errorf("Nicht genügend Erfahrungspunkte vorhanden")
}
// Prüfe, ob genügend Gold vorhanden ist
currentGold := char.Vermoegen.Goldstücke
if currentGold < totalGold {
return fmt.Errorf("Nicht genügend Gold vorhanden")
}
// Prüfe, ob genügend PP vorhanden sind (PP der jeweiligen Fertigkeit)
currentPP := 0
for _, skill := range char.Fertigkeiten {
if skill.Name == skillName {
currentPP = skill.Pp
break
}
}
// Falls nicht in normalen Fertigkeiten gefunden, prüfe Waffenfertigkeiten
if currentPP == 0 {
for _, skill := range char.Waffenfertigkeiten {
if skill.Name == skillName {
currentPP = skill.Pp
break
}
}
}
if totalPP > 0 && currentPP < totalPP {
return fmt.Errorf("Nicht genügend Praxispunkte vorhanden")
}
return nil
}
// deductResources zieht die Kosten von den Charakterressourcen ab
2025-08-05 21:12:03 +02:00
// 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
currentGold := char.Vermoegen.Goldstücke
// EP abziehen und Audit-Log erstellen
newEP := currentEP - totalEP
if totalEP > 0 {
// Erstelle Notiz für Multi-Level Improvement
levelCount := finalLevel - currentLevel
var notes string
if levelCount > 1 {
notes = fmt.Sprintf("Fertigkeit '%s' von %d auf %d verbessert (%d Level)", skillName, currentLevel, finalLevel, levelCount)
} else {
notes = fmt.Sprintf("Fertigkeit '%s' von %d auf %d verbessert", skillName, currentLevel, finalLevel)
}
err := CreateAuditLogEntry(char.ID, "experience_points", currentEP, newEP, ReasonSkillImprovement, 0, notes)
if err != nil {
2025-08-05 21:12:03 +02:00
return newEP, 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 {
2025-08-05 21:12:03 +02:00
return newEP, 0, fmt.Errorf("Fehler beim Speichern der Erfahrungspunkte: %v", err)
}
}
// Gold abziehen und Audit-Log erstellen
newGold := currentGold - totalGold
if totalGold > 0 {
notes := fmt.Sprintf("Gold für Verbesserung von '%s' ausgegeben", skillName)
err := CreateAuditLogEntry(char.ID, "gold", currentGold, newGold, ReasonSkillImprovement, 0, notes)
if err != nil {
2025-08-05 21:12:03 +02:00
return newEP, newGold, fmt.Errorf("Fehler beim Erstellen des Audit-Log-Eintrags: %v", err)
}
char.Vermoegen.Goldstücke = newGold
if err := database.DB.Save(&char.Vermoegen).Error; err != nil {
2025-08-05 21:12:03 +02:00
return newEP, newGold, fmt.Errorf("Fehler beim Speichern des Vermögens: %v", err)
}
}
// PP abziehen wenn verwendet (PP der jeweiligen Fertigkeit)
if totalPP > 0 {
// Finde die richtige Fertigkeit und ziehe PP ab
for i := range char.Fertigkeiten {
if char.Fertigkeiten[i].Name == skillName {
char.Fertigkeiten[i].Pp -= totalPP
if err := database.DB.Save(&char.Fertigkeiten[i]).Error; err != nil {
2025-08-05 21:12:03 +02:00
return newEP, newGold, fmt.Errorf("Fehler beim Aktualisieren der Praxispunkte: %v", err)
}
break
}
}
// Falls nicht in normalen Fertigkeiten gefunden, prüfe Waffenfertigkeiten
for i := range char.Waffenfertigkeiten {
if char.Waffenfertigkeiten[i].Name == skillName {
char.Waffenfertigkeiten[i].Pp -= totalPP
if err := database.DB.Save(&char.Waffenfertigkeiten[i]).Error; err != nil {
2025-08-05 21:12:03 +02:00
return newEP, newGold, fmt.Errorf("Fehler beim Aktualisieren der Praxispunkte: %v", err)
}
break
}
}
}
return newEP, newGold, nil
}
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())
return
}
// 1. Charakter laden
char, err := loadCharacterForImprovement(request.CharId)
if err != nil {
respondWithError(c, http.StatusNotFound, "Charakter nicht gefunden")
return
}
// 2. Skill validieren und Level ermitteln
characterClass, skillInfo, currentLevel, err := validateSkillForImprovement(char, &request)
if err != nil {
respondWithError(c, http.StatusBadRequest, err.Error())
return
}
// Bestimme das finale Level
finalLevel := request.TargetLevel
if finalLevel <= 0 {
finalLevel = currentLevel + 1
}
// 3. Kosten berechnen
response, totalEP, totalGold, totalPP, err := calculateImprovementCosts(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 := deductResources(char, request.Name, currentLevel, finalLevel, totalEP, totalGold, totalPP)
if err != nil {
respondWithError(c, http.StatusInternalServerError, err.Error())
return
}
// 6. Skill-Level aktualisieren
if err := updateOrCreateSkill(char, request.Name, finalLevel); err != nil {
respondWithError(c, http.StatusInternalServerError, "Fehler beim Aktualisieren 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 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-08-01 13:53:10 +02:00
// ImproveSkillOld is deprecated. Use ImproveSkill instead.
// This function uses the old hardcoded learning cost system.
2025-08-01 06:27:38 +02:00
// ImproveSkillOld verbessert eine bestehende Fertigkeit und erstellt Audit-Log-Einträge
func ImproveSkillOld(c *gin.Context) {
2025-07-25 14:00:56 +02:00
// 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
}
2025-07-24 07:39:43 +02:00
2025-07-25 14:00:56 +02:00
// Hole Charakter über die ID aus dem Request
var char models.Char
err := database.DB.
Preload("Fertigkeiten").
Preload("Waffenfertigkeiten").
Preload("Erfahrungsschatz").
Preload("Vermoegen").
First(&char, request.CharId).Error
if err != nil {
2025-07-24 07:39:43 +02:00
respondWithError(c, http.StatusNotFound, "Charakter nicht gefunden")
return
}
2025-07-25 14:00:56 +02:00
// Verwende Klassenabkürzung wenn der Typ länger als 3 Zeichen ist
var characterClass string
if len(char.Typ) > 3 {
characterClass = gsmaster.GetClassAbbreviationOld(char.Typ)
2025-07-25 14:00:56 +02:00
} else {
characterClass = char.Typ
2025-07-24 07:39:43 +02:00
}
// Aktuellen Level ermitteln, falls nicht angegeben
currentLevel := request.CurrentLevel
if currentLevel <= 0 {
currentLevel = getCurrentSkillLevel(&char, request.Name, "skill")
2025-07-24 07:39:43 +02:00
if currentLevel == -1 {
respondWithError(c, http.StatusBadRequest, "Fertigkeit nicht bei diesem Charakter vorhanden")
return
}
2025-07-25 14:00:56 +02:00
request.CurrentLevel = currentLevel
2025-07-24 07:39:43 +02:00
}
2025-07-25 14:00:56 +02:00
// Bestimme das finale Level
finalLevel := request.TargetLevel
if finalLevel <= 0 {
finalLevel = currentLevel + 1
2025-07-24 07:39:43 +02:00
}
2025-07-25 14:00:56 +02:00
// Initialisiere Gesamtkosten
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
tempRequest := request
tempRequest.CurrentLevel = tempLevel
tempRequest.TargetLevel = nextLevel
// Berechne Kosten für diesen einen Level
var costResult gsmaster.SkillCostResultNew
costResult.CharacterID = fmt.Sprintf("%d", char.ID)
2025-07-25 14:00:56 +02:00
costResult.CharacterClass = characterClass
costResult.SkillName = request.Name
err = gsmaster.GetLernCostNextLevelOld(&tempRequest, &costResult, request.Reward, nextLevel, char.Rasse)
2025-07-25 14:00:56 +02:00
if err != nil {
respondWithError(c, http.StatusBadRequest, fmt.Sprintf("Fehler bei Level %d: %v", nextLevel, err))
return
}
// Addiere die Kosten
totalEP += costResult.EP
totalGold += costResult.GoldCost
totalPP += costResult.PPUsed
2025-07-25 14:00:56 +02:00
tempLevel++
2025-07-24 07:39:43 +02:00
}
// Prüfe, ob genügend EP vorhanden sind
currentEP := char.Erfahrungsschatz.EP
2025-07-25 14:00:56 +02:00
if currentEP < totalEP {
2025-07-24 07:39:43 +02:00
respondWithError(c, http.StatusBadRequest, "Nicht genügend Erfahrungspunkte vorhanden")
return
}
// Prüfe, ob genügend Gold vorhanden ist
currentGold := char.Vermoegen.Goldstücke
2025-07-25 14:00:56 +02:00
if currentGold < totalGold {
2025-07-24 07:39:43 +02:00
respondWithError(c, http.StatusBadRequest, "Nicht genügend Gold vorhanden")
return
}
// Prüfe, ob genügend PP vorhanden sind (PP der jeweiligen Fertigkeit)
currentPP := 0
for _, skill := range char.Fertigkeiten {
if skill.Name == request.Name {
currentPP = skill.Pp
break
}
}
// Falls nicht in normalen Fertigkeiten gefunden, prüfe Waffenfertigkeiten
if currentPP == 0 {
for _, skill := range char.Waffenfertigkeiten {
if skill.Name == request.Name {
currentPP = skill.Pp
break
}
}
}
if totalPP > 0 && currentPP < totalPP {
respondWithError(c, http.StatusBadRequest, "Nicht genügend Praxispunkte vorhanden")
return
}
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 {
notes = fmt.Sprintf("Fertigkeit '%s' von %d auf %d verbessert (%d Level)", request.Name, currentLevel, finalLevel, levelCount)
} else {
notes = fmt.Sprintf("Fertigkeit '%s' von %d auf %d verbessert", request.Name, currentLevel, finalLevel)
2025-07-24 07:39:43 +02:00
}
err = CreateAuditLogEntry(char.ID, "experience_points", currentEP, newEP, ReasonSkillImprovement, 0, notes)
2025-07-24 07:39:43 +02:00
if err != nil {
respondWithError(c, http.StatusInternalServerError, "Fehler beim Erstellen des Audit-Log-Eintrags")
return
}
char.Erfahrungsschatz.EP = newEP
if err := database.DB.Save(&char.Erfahrungsschatz).Error; err != nil {
respondWithError(c, http.StatusInternalServerError, "Fehler beim Speichern der Erfahrungspunkte")
return
}
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-07-24 07:39:43 +02:00
notes := fmt.Sprintf("Gold für Verbesserung von '%s' ausgegeben", request.Name)
err = CreateAuditLogEntry(char.ID, "gold", currentGold, newGold, ReasonSkillImprovement, 0, notes)
2025-07-24 07:39:43 +02:00
if err != nil {
respondWithError(c, http.StatusInternalServerError, "Fehler beim Erstellen des Audit-Log-Eintrags")
return
}
char.Vermoegen.Goldstücke = newGold
if err := database.DB.Save(&char.Vermoegen).Error; err != nil {
respondWithError(c, http.StatusInternalServerError, "Fehler beim Speichern des Vermögens")
return
}
}
// PP abziehen wenn verwendet (PP der jeweiligen Fertigkeit)
if totalPP > 0 {
// Finde die richtige Fertigkeit und ziehe PP ab
for i := range char.Fertigkeiten {
if char.Fertigkeiten[i].Name == request.Name {
char.Fertigkeiten[i].Pp -= totalPP
if err := database.DB.Save(&char.Fertigkeiten[i]).Error; err != nil {
respondWithError(c, http.StatusInternalServerError, "Fehler beim Aktualisieren der Praxispunkte")
return
}
break
}
}
// Falls nicht in normalen Fertigkeiten gefunden, prüfe Waffenfertigkeiten
for i := range char.Waffenfertigkeiten {
if char.Waffenfertigkeiten[i].Name == request.Name {
char.Waffenfertigkeiten[i].Pp -= totalPP
if err := database.DB.Save(&char.Waffenfertigkeiten[i]).Error; err != nil {
respondWithError(c, http.StatusInternalServerError, "Fehler beim Aktualisieren der Praxispunkte")
return
}
break
}
}
2025-07-24 07:39:43 +02:00
}
// Aktualisiere die Fertigkeit mit dem neuen Level
if err := updateOrCreateSkill(&char, request.Name, finalLevel); err != nil {
respondWithError(c, http.StatusInternalServerError, "Fehler beim Aktualisieren der Fertigkeit: "+err.Error())
return
}
2025-07-24 07:39:43 +02:00
// 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-07-25 14:00:56 +02:00
// Response für Multi-Level oder Single-Level
response := gin.H{
2025-07-24 07:39:43 +02:00
"message": "Fertigkeit erfolgreich verbessert",
"skill_name": request.Name,
"from_level": currentLevel,
2025-07-25 14:00:56 +02:00
"to_level": finalLevel,
"ep_cost": totalEP,
"gold_cost": totalGold,
2025-07-24 07:39:43 +02:00
"remaining_ep": newEP,
"remaining_gold": newGold,
2025-07-25 14:00:56 +02:00
}
// Füge Multi-Level-spezifische Informationen hinzu
levelCount := finalLevel - currentLevel
if levelCount > 1 {
// Erstelle Array der gelernten Level für Kompatibilität
var levelsLearned []int
for i := currentLevel + 1; i <= finalLevel; i++ {
levelsLearned = append(levelsLearned, i)
}
response["levels_learned"] = levelsLearned
response["level_count"] = levelCount
response["multi_level"] = true
}
c.JSON(http.StatusOK, response)
2025-07-24 07:39:43 +02:00
}
2025-08-01 13:53:10 +02:00
// LearnSpellOld is deprecated. Use LearnSpell instead.
// This function uses the old hardcoded learning cost system.
2025-08-01 06:27:38 +02:00
// LearnSpellOld lernt einen neuen Zauber und erstellt Audit-Log-Einträge
func LearnSpellOld(c *gin.Context) {
2025-07-24 07:39:43 +02:00
charID := c.Param("id")
2025-07-28 21:35:29 +02:00
var character models.Char
2025-07-24 07:39:43 +02:00
if err := character.FirstID(charID); err != nil {
respondWithError(c, http.StatusNotFound, "Charakter nicht gefunden")
return
}
var request LearnSpellRequest
if err := c.ShouldBindJSON(&request); err != nil {
respondWithError(c, http.StatusBadRequest, "Ungültige Anfrageparameter: "+err.Error())
return
}
// Berechne Kosten mit GetSkillCost
costRequest := SkillCostRequest{
Name: request.Name,
Type: "spell",
Action: "learn",
}
2025-08-01 06:27:38 +02:00
cost, _, _, err := calculateSingleCostOld(&character, &costRequest)
2025-07-24 07:39:43 +02:00
if err != nil {
respondWithError(c, http.StatusBadRequest, "Fehler bei der Kostenberechnung: "+err.Error())
return
}
// Prüfe, ob genügend EP vorhanden sind
2025-07-25 20:43:28 +02:00
currentEP := character.Erfahrungsschatz.EP
2025-07-24 07:39:43 +02:00
if currentEP < cost.Ep {
respondWithError(c, http.StatusBadRequest, "Nicht genügend Erfahrungspunkte vorhanden")
return
}
// EP abziehen und Audit-Log erstellen
newEP := currentEP - cost.Ep
if cost.Ep > 0 {
notes := fmt.Sprintf("Zauber '%s' gelernt", request.Name)
if request.Notes != "" {
notes += " - " + request.Notes
}
err = CreateAuditLogEntry(character.ID, "experience_points", currentEP, newEP, ReasonSpellLearning, 0, notes)
if err != nil {
respondWithError(c, http.StatusInternalServerError, "Fehler beim Erstellen des Audit-Log-Eintrags")
return
}
2025-07-25 20:43:28 +02:00
character.Erfahrungsschatz.EP = newEP
2025-07-24 07:39:43 +02:00
}
// Füge den Zauber zum Charakter hinzu
if err := addSpellToCharacter(&character, request.Name); err != nil {
respondWithError(c, http.StatusInternalServerError, "Fehler beim Hinzufügen des Zaubers: "+err.Error())
return
}
2025-07-24 07:39:43 +02:00
// Charakter speichern
if err := database.DB.Save(&character).Error; err != nil {
respondWithError(c, http.StatusInternalServerError, "Fehler beim Speichern des Charakters")
return
}
c.JSON(http.StatusOK, gin.H{
"message": "Zauber erfolgreich gelernt",
"spell_name": request.Name,
"ep_cost": cost.Ep,
"remaining_ep": newEP,
})
}
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-01 13:53:10 +02:00
// GetRewardTypesOld is deprecated. Use GetRewardTypes instead.
// This function provides hardcoded reward type mappings.
2025-08-01 06:27:38 +02:00
// GetRewardTypesOld liefert verfügbare Belohnungsarten für ein bestimmtes Lernszenario
func GetRewardTypesOld(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":
// Neue Fertigkeit lernen - noGold Belohnung verfügbar
2025-07-24 07:39:43 +02:00
rewardTypes = append(rewardTypes,
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":
// Zauber lernen - halveepnoGold verfügbar
2025-07-24 07:39:43 +02:00
rewardTypes = append(rewardTypes,
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":
// Fertigkeit verbessern - halveepnoGold verfügbar
2025-07-24 07:39:43 +02:00
rewardTypes = append(rewardTypes,
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
// GetAvailableSkillsNewSystem gibt alle verfügbaren Fertigkeiten mit Lernkosten zurück (POST mit LernCostRequest)
func GetAvailableSkillsNewSystem(c *gin.Context) {
2025-07-31 10:28:35 +02:00
characterID := c.Param("id")
// 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
var character models.Char
if err := database.DB.Preload("Fertigkeiten").Preload("Erfahrungsschatz").Preload("Vermoegen").First(&character, characterID).Error; err != nil {
respondWithError(c, http.StatusNotFound, "Character not found")
return
}
// 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
}
*/
// Erstelle eine Map der bereits gelernten Fertigkeiten
learnedSkills := make(map[string]bool)
for _, skill := range character.Fertigkeiten {
learnedSkills[skill.Name] = true
}
// 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
}
// Erstelle LernCostRequest für diese Fertigkeit basierend auf der Basis-Anfrage
request := baseRequest
request.CharId = character.ID
request.Name = skill.Name
request.CurrentLevel = 0 // Nicht gelernt
request.TargetLevel = 1 // Auf Level 1 lernen
request.Type = "skill"
request.Action = "learn"
// Erstelle SkillCostResultNew
levelResult := gsmaster.SkillCostResultNew{
CharacterID: fmt.Sprintf("%d", character.ID),
2025-08-01 06:27:38 +02:00
CharacterClass: getCharacterClassOld(&character),
SkillName: skill.Name,
TargetLevel: 1,
2025-07-31 10:28:35 +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-01 07:19:02 +02:00
skillLearningInfo, err := models.GetSkillCategoryAndDifficultyNewSystem(skill.Name, getCharacterClassOld(&character))
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
}
}
// Berechne Lernkosten mit calculateSkillLearnCostNewSystem
2025-07-31 13:43:29 +02:00
err = calculateSkillLearnCostNewSystem(&request, &levelResult, &remainingPP, &remainingGold, skillLearningInfo)
epCost := 10000 // Fallback-Wert
goldCost := 50000 // Fallback-Wert
2025-07-31 10:28:35 +02:00
if err == nil {
epCost = levelResult.EP
goldCost = levelResult.GoldCost
}
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-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-01 06:27:38 +02:00
charakteClass := getCharacterClassOld(&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-01 13:53:10 +02:00
// GetAvailableSkillsOld is deprecated. Use GetAvailableSkillsNewSystem instead.
// This function uses the old hardcoded learning cost system.
2025-08-01 06:27:38 +02:00
// GetAvailableSkillsOld gibt alle verfügbaren Fertigkeiten mit Lernkosten zurück
func GetAvailableSkillsOld(c *gin.Context) {
2025-07-26 23:15:11 +02:00
characterID := c.Param("id")
rewardType := c.Query("reward_type")
2025-07-28 21:35:29 +02:00
var character models.Char
2025-07-26 23:15:11 +02:00
if err := database.DB.Preload("Fertigkeiten").Preload("Erfahrungsschatz").Preload("Vermoegen").First(&character, characterID).Error; err != nil {
respondWithError(c, http.StatusNotFound, "Character not found")
return
}
// Hole alle verfügbaren Fertigkeiten aus der gsmaster Datenbank, aber filtere Placeholder aus
2025-07-27 23:13:04 +02:00
var allSkills []models.Skill
2025-07-27 14:11:19 +02:00
2025-07-27 23:13:04 +02:00
allSkills, err := models.SelectSkills("", "")
2025-07-27 14:11:19 +02:00
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 {
2025-07-26 23:15:11 +02:00
respondWithError(c, http.StatusInternalServerError, "Failed to retrieve skills")
return
}
2025-07-27 14:11:19 +02:00
*/
2025-07-26 23:15:11 +02:00
// Erstelle eine Map der bereits gelernten Fertigkeiten
learnedSkills := make(map[string]bool)
for _, skill := range character.Fertigkeiten {
learnedSkills[skill.Name] = true
}
// 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
}
// Berechne Lernkosten mit GetLernCostNextLevel
2025-08-01 06:27:38 +02:00
epCost, goldCost := calculateSkillLearningCostsOld(skill, character, rewardType)
2025-07-26 23:15:11 +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-01 13:53:10 +02:00
// calculateSkillLearningCostsOld is deprecated. Use calculateSkillLearnCostNewSystem instead.
// This function uses the old hardcoded learning cost system.
2025-08-01 06:27:38 +02:00
// calculateSkillLearningCostsOld berechnet die EP- und Goldkosten für das Lernen einer Fertigkeit mit GetLernCostNextLevel
func calculateSkillLearningCostsOld(skill models.Skill, character models.Char, rewardType string) (int, int) {
2025-07-26 23:15:11 +02:00
// Erstelle LernCostRequest für das Lernen (Level 0 -> 1)
var rewardTypePtr *string
if rewardType != "" && rewardType != "default" {
rewardTypePtr = &rewardType
}
request := gsmaster.LernCostRequest{
CharId: character.ID,
Name: skill.Name,
CurrentLevel: 0, // Nicht gelernt
TargetLevel: 1, // Auf Level 1 lernen
Type: "skill",
Action: "learn",
UsePP: 0,
UseGold: 0,
Reward: rewardTypePtr,
}
// Erstelle SkillCostResultNew
costResult := gsmaster.SkillCostResultNew{
CharacterID: fmt.Sprintf("%d", character.ID),
2025-08-01 06:27:38 +02:00
CharacterClass: getCharacterClassOld(&character),
2025-07-26 23:15:11 +02:00
SkillName: skill.Name,
Category: skill.Category,
Difficulty: skill.Difficulty,
TargetLevel: 1,
}
// Berechne Kosten mit GetLernCostNextLevel
2025-08-01 06:27:38 +02:00
err := gsmaster.GetLernCostNextLevelOld(&request, &costResult, rewardTypePtr, 1, character.Typ)
2025-07-26 23:15:11 +02:00
if err != nil {
// Fallback zu Standard-Kosten bei Fehler
epCost := 100
goldCost := 50
return epCost, goldCost
}
return costResult.EP, costResult.GoldCost
}
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-08 22:37:55 +02:00
userID := c.GetUint("userID")
if userID == 0 {
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-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: "",
Rasse: "",
Typ: "",
Herkunft: "",
Glaube: "",
Attributes: models.AttributesData{},
DerivedValues: models.DerivedValuesData{},
Skills: []models.CharacterCreationSkill{},
Spells: []models.CharacterCreationSpell{},
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-08 22:37:55 +02:00
// Session in Datenbank speichern
err := database.DB.Create(&session).Error
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create session"})
return
}
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-08 22:37:55 +02:00
userID := c.GetUint("userID")
if userID == 0 {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
return
}
// Sessions aus Datenbank laden
sessions, err := models.GetUserSessions(database.DB, userID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to load sessions"})
return
}
// Sessions für Frontend formatieren
var formattedSessions []gin.H
for _, session := range sessions {
// Schritt-Text bestimmen
progressText := getProgressText(session.CurrentStep)
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
}
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 {
switch step {
case 1:
return "Grundinformationen"
case 2:
return "Attribute"
case 3:
return "Abgeleitete Werte"
case 4:
return "Fertigkeiten"
case 5:
return "Zauber"
default:
return "Unbekannt"
}
}
2025-08-08 06:39:46 +02:00
// GetCharacterSession gibt Session-Daten zurück
func GetCharacterSession(c *gin.Context) {
sessionID := c.Param("sessionId")
2025-08-08 22:37:55 +02:00
userID := c.GetUint("userID")
if userID == 0 {
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
var session models.CharacterCreationSession
err := database.DB.Where("id = ? AND user_id = ?", sessionID, userID).First(&session).Error
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Session not found"})
return
}
// Prüfen ob Session noch gültig ist
if session.ExpiresAt.Before(time.Now()) {
// Abgelaufene Session löschen
database.DB.Delete(&session)
c.JSON(http.StatusGone, gin.H{"error": "Session expired"})
return
2025-08-08 06:39:46 +02:00
}
c.JSON(http.StatusOK, session)
}
// UpdateCharacterBasicInfo Request
type UpdateBasicInfoRequest struct {
Name string `json:"name" binding:"required,min=2,max=50"`
Rasse string `json:"rasse" binding:"required"`
Typ string `json:"typ" binding:"required"`
Herkunft string `json:"herkunft" binding:"required"`
Glaube string `json:"glaube"`
}
// UpdateCharacterBasicInfo speichert Grundinformationen
func UpdateCharacterBasicInfo(c *gin.Context) {
sessionID := c.Param("sessionId")
2025-08-08 22:37:55 +02:00
userID := c.GetUint("userID")
if userID == 0 {
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 {
respondWithError(c, http.StatusBadRequest, "Ungültige Eingabedaten: "+err.Error())
return
}
2025-08-08 22:37:55 +02:00
// Session aus Datenbank laden
var session models.CharacterCreationSession
err := database.DB.Where("id = ? AND user_id = ?", sessionID, userID).First(&session).Error
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Session not found"})
return
}
// Grundinformationen aktualisieren
session.Name = request.Name
session.Rasse = request.Rasse
session.Typ = request.Typ
session.Herkunft = request.Herkunft
session.Glaube = request.Glaube
session.CurrentStep = 2
session.UpdatedAt = time.Now()
// Session in Datenbank aktualisieren
err = database.DB.Save(&session).Error
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update session"})
return
}
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
PA int `json:"pa" binding:"required,min=1,max=100"` // Psi-Kraft
WK int `json:"wk" binding:"required,min=1,max=100"` // Willenskraft
}
// UpdateCharacterAttributes speichert Grundwerte
func UpdateCharacterAttributes(c *gin.Context) {
sessionID := c.Param("sessionId")
2025-08-08 22:37:55 +02:00
userID := c.GetUint("userID")
if userID == 0 {
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 {
respondWithError(c, http.StatusBadRequest, "Ungültige Attributswerte: "+err.Error())
return
}
2025-08-08 22:37:55 +02:00
// Session aus Datenbank laden
var session models.CharacterCreationSession
err := database.DB.Where("id = ? AND user_id = ?", sessionID, userID).First(&session).Error
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Session not found"})
return
}
// 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,
PA: request.PA,
WK: request.WK,
}
session.CurrentStep = 3
session.UpdatedAt = time.Now()
// Session in Datenbank aktualisieren
err = database.DB.Save(&session).Error
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update session"})
return
}
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 {
LP_Max int `json:"lp_max"` // Lebenspunkte Maximum
AP_Max int `json:"ap_max"` // Abenteuerpunkte Maximum
B_Max int `json:"b_max"` // Belastung Maximum
SG int `json:"sg"` // Schicksalsgunst
GG int `json:"gg"` // Göttliche Gnade
GP int `json:"gp"` // Glückspunkte
}
// UpdateCharacterDerivedValues speichert abgeleitete Werte
func UpdateCharacterDerivedValues(c *gin.Context) {
sessionID := c.Param("sessionId")
2025-08-08 22:37:55 +02:00
userID := c.GetUint("userID")
if userID == 0 {
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 {
respondWithError(c, http.StatusBadRequest, "Ungültige abgeleitete Werte: "+err.Error())
return
}
2025-08-08 22:37:55 +02:00
// Session aus Datenbank laden
var session models.CharacterCreationSession
err := database.DB.Where("id = ? AND user_id = ?", sessionID, userID).First(&session).Error
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Session not found"})
return
}
// Abgeleitete Werte aktualisieren
session.DerivedValues = models.DerivedValuesData{
LPMax: request.LP_Max,
APMax: request.AP_Max,
BMax: request.B_Max,
SG: request.SG,
GG: request.GG,
GP: request.GP,
}
session.CurrentStep = 4
session.UpdatedAt = time.Now()
// Session in Datenbank aktualisieren
err = database.DB.Save(&session).Error
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update session"})
return
}
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-08 22:37:55 +02:00
Skills []models.CharacterCreationSkill `json:"skills"`
Spells []models.CharacterCreationSpell `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) {
sessionID := c.Param("sessionId")
2025-08-08 22:37:55 +02:00
userID := c.GetUint("userID")
if userID == 0 {
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 {
respondWithError(c, http.StatusBadRequest, "Ungültige Fertigkeitsdaten: "+err.Error())
return
}
2025-08-08 22:37:55 +02:00
// Session aus Datenbank laden
var session models.CharacterCreationSession
err := database.DB.Where("id = ? AND user_id = ?", sessionID, userID).First(&session).Error
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Session not found"})
return
}
// Fertigkeiten und Zauber aktualisieren
session.Skills = request.Skills
session.Spells = request.Spells
session.SkillPoints = request.SkillPoints
session.CurrentStep = 5
session.UpdatedAt = time.Now()
// Session in Datenbank aktualisieren
err = database.DB.Save(&session).Error
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update session"})
return
}
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) {
sessionID := c.Param("sessionId")
2025-08-08 22:37:55 +02:00
userID := c.GetUint("userID")
if userID == 0 {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
return
}
// Session laden
var session models.CharacterCreationSession
err := database.DB.Where("id = ? AND user_id = ?", sessionID, userID).First(&session).Error
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Session not found"})
return
}
// Session validieren
if session.CurrentStep < 5 {
c.JSON(http.StatusBadRequest, gin.H{"error": "Character creation not complete"})
return
}
// 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
// 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,
},
// Bennies (Glückspunkte, etc.)
Bennies: models.Bennies{
Gg: session.DerivedValues.GG,
Gp: session.DerivedValues.GP,
Sg: session.DerivedValues.SG,
},
}
// Eigenschaften (Attribute) hinzufügen
char.Eigenschaften = []models.Eigenschaft{
{Name: "St", Value: session.Attributes.ST},
{Name: "Gs", Value: session.Attributes.GS},
{Name: "Gw", Value: session.Attributes.GW},
{Name: "Ko", Value: session.Attributes.KO},
{Name: "In", Value: session.Attributes.IN},
{Name: "Zt", Value: session.Attributes.ZT},
{Name: "Au", Value: session.Attributes.AU},
{Name: "pA", Value: session.Attributes.PA},
{Name: "Wk", Value: session.Attributes.WK},
}
2025-08-08 06:39:46 +02:00
2025-08-08 22:37:55 +02:00
// Character in Datenbank speichern
err = char.Create()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create character"})
return
}
2025-08-08 06:39:46 +02:00
2025-08-08 22:37:55 +02:00
// Session löschen
database.DB.Delete(&session)
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) {
sessionID := c.Param("sessionId")
2025-08-08 22:37:55 +02:00
userID := c.GetUint("userID")
2025-08-08 06:39:46 +02:00
2025-08-08 22:37:55 +02:00
if userID == 0 {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
return
}
// Session aus Datenbank löschen (nur eigene Sessions)
result := database.DB.Where("id = ? AND user_id = ?", sessionID, userID).Delete(&models.CharacterCreationSession{})
if result.Error != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete session"})
return
}
if result.RowsAffected == 0 {
c.JSON(http.StatusNotFound, gin.H{"error": "Session not found"})
return
}
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{
"Abenteurer", "Assassine", "Barbar", "Barde", "Bauer",
"Glückspriester", "Heiler", "Händler", "Kämpfer", "Krieger",
"Magier", "Ordenskrieger", "Priester", "Schamane", "Seefahrer",
"Späher", "Thaumaturg", "Waldläufer", "Zauberer", "Zaubersänger",
}
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-08 22:37:55 +02:00
"Alba", "Aran", "Buluga", "Cangebiet", "Chryseia", "Dwerunlande",
2025-08-08 06:39:46 +02:00
"Eschar", "Fuardain", "Ikenga", "KanThaiPan", "Küstenstaaten",
"Medjis", "Moravod", "Nahuatlan", "Rawindra", "Scharidis",
"Tegarisch Gebiete", "Valian", "Waeland", "Ywerddon",
}
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"`
}
// GetSkillCategoriesWithPoints gibt Kategorien mit Lernpunkten zurück
func GetSkillCategoriesWithPoints(c *gin.Context) {
// TODO: Basierend auf Charakter-Klasse und -Typ berechnen
categories := []SkillCategoryWithPoints{
{Name: "alltag", DisplayName: "Alltag", Points: 150, MaxPoints: 150},
{Name: "wissen", DisplayName: "Wissen", Points: 100, MaxPoints: 100},
{Name: "kampf", DisplayName: "Kampf", Points: 80, MaxPoints: 80},
{Name: "korper", DisplayName: "Körper", Points: 120, MaxPoints: 120},
{Name: "gesellschaft", DisplayName: "Gesellschaft", Points: 60, MaxPoints: 60},
{Name: "natur", DisplayName: "Natur", Points: 90, MaxPoints: 90},
{Name: "unterwelt", DisplayName: "Unterwelt", Points: 40, MaxPoints: 40},
{Name: "zauber", DisplayName: "Zauber", Points: 200, MaxPoints: 200},
}
c.JSON(http.StatusOK, gin.H{"categories": categories})
}