042a1d4773
* introduced central package registry by package init function * dynamic registration of routes, model, migrations and initializers. * setting a docker compose project name to prevent shutdown of other containers with the same (composer)name * ai documentation * app template * Create tests for ALL API entpoints in ALL packages Based on current data. Ensure that all API endpoints used in frontend are tested. These tests are crucial for the next refactoring tasks. * adopting agent instructions for a more consistent coding style * added desired module layout and debugging information * Fix All Failing tests All failing tests are fixed now that makes the refactoring more easy since all tests must pass * restored routes for maintenance * added common translations * added new tests for API Endpoint * Merge branch 'separate_business_logic' * added lern and skill improvement cost editing * Set Docker image tag when building to prevent rebuild when nothing has changed * add and remove PP for Weaponskill fixed * add and remove PP for same named skills fixed * add new task
3581 lines
113 KiB
Go
3581 lines
113 KiB
Go
package character
|
|
|
|
import (
|
|
"bamort/database"
|
|
"bamort/bmrt/gsmaster"
|
|
"bamort/logger"
|
|
"bamort/bmrt/models"
|
|
"errors"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// Character Handlers
|
|
|
|
type LearnRequestStruct struct {
|
|
SkillType string `json:"skillType"`
|
|
Name string `json:"name"`
|
|
Stufe int `json:"stufe"`
|
|
}
|
|
|
|
func respondWithError(c *gin.Context, status int, message string) {
|
|
logger.Warn("HTTP Fehler %d: %s", status, message)
|
|
c.JSON(status, gin.H{"error": message})
|
|
}
|
|
|
|
// checkCharacterOwnership verifies that the logged-in user owns the character
|
|
func checkCharacterOwnership(c *gin.Context, character *models.Char) bool {
|
|
userID := c.GetUint("userID")
|
|
if character.UserID != userID {
|
|
logger.Warn("Unauthorized access attempt: user %d tried to modify character %d owned by user %d", userID, character.ID, character.UserID)
|
|
respondWithError(c, http.StatusForbidden, "You are not authorized to modify this character")
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func ListCharacters(c *gin.Context) {
|
|
logger.Debug("ListCharacters aufgerufen")
|
|
|
|
type AllCharacters struct {
|
|
SelfOwned []models.CharList `json:"self_owned"`
|
|
Others []models.CharList `json:"others"`
|
|
}
|
|
allCharacters := AllCharacters{}
|
|
|
|
logger.Debug("Lade Charaktere aus der Datenbank...")
|
|
//if err := database.DB.Find(&characters).Error; err != nil {
|
|
listOfChars, err := models.FindCharListByUserID(c.GetUint("userID"))
|
|
if err != nil {
|
|
|
|
logger.Error("Fehler beim Laden der Charaktere: %s", err.Error())
|
|
respondWithError(c, http.StatusInternalServerError, "Failed to retrieve characters")
|
|
return
|
|
}
|
|
|
|
logger.Debug("Gefundene Charaktere: %d", len(listOfChars))
|
|
allCharacters.SelfOwned = listOfChars
|
|
|
|
listPublic, err := models.FindPublicCharList()
|
|
|
|
if err != nil {
|
|
logger.Error("Fehler beim Laden der öffentlichen Charaktere: %s", err.Error())
|
|
respondWithError(c, http.StatusInternalServerError, "Failed to retrieve public characters")
|
|
return
|
|
}
|
|
listShared, err := models.FindSharedCharList(c.GetUint("userID"))
|
|
if err != nil {
|
|
logger.Error("Fehler beim Laden der geteilten Charaktere: %s", err.Error())
|
|
respondWithError(c, http.StatusInternalServerError, "Failed to retrieve shared characters")
|
|
return
|
|
}
|
|
listPublic = append(listPublic, listShared...)
|
|
|
|
allCharacters.Others = listPublic
|
|
|
|
logger.Info("Charakterliste erfolgreich geladen: %d Charaktere", len(listOfChars))
|
|
c.JSON(http.StatusOK, allCharacters)
|
|
}
|
|
|
|
func CreateCharacter(c *gin.Context) {
|
|
var character models.Char
|
|
if err := c.ShouldBindJSON(&character); err != nil {
|
|
respondWithError(c, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
|
|
if err := database.DB.Create(&character).Error; err != nil {
|
|
respondWithError(c, http.StatusInternalServerError, "Failed to create character")
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, character)
|
|
}
|
|
func GetCharacter(c *gin.Context) {
|
|
id := c.Param("id")
|
|
var character models.Char
|
|
err := character.FirstID(id)
|
|
if err != nil {
|
|
respondWithError(c, http.StatusInternalServerError, "Failed to retrieve character")
|
|
return
|
|
}
|
|
feChar := ToFeChar(&character)
|
|
c.JSON(http.StatusOK, feChar)
|
|
}
|
|
func UpdateCharacter(c *gin.Context) {
|
|
id := c.Param("id")
|
|
var character models.Char
|
|
|
|
// First, find the existing character
|
|
err := character.FirstID(id)
|
|
if err != nil {
|
|
respondWithError(c, http.StatusNotFound, "Character not found")
|
|
return
|
|
}
|
|
|
|
// Check ownership
|
|
if !checkCharacterOwnership(c, &character) {
|
|
return
|
|
}
|
|
|
|
// Store the original ID to preserve it
|
|
originalID := character.ID
|
|
originalGameSystem := character.GameSystem
|
|
originalGameSystemId := character.GameSystemId
|
|
|
|
// Bind the updated data
|
|
if err := c.ShouldBindJSON(&character); err != nil {
|
|
respondWithError(c, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
|
|
// Restore the ID
|
|
character.ID = originalID
|
|
character.GameSystem = originalGameSystem
|
|
character.GameSystemId = originalGameSystemId
|
|
|
|
// Update all associations
|
|
if err := database.DB.Session(&gorm.Session{FullSaveAssociations: true}).Save(&character).Error; err != nil {
|
|
respondWithError(c, http.StatusInternalServerError, "Failed to update character")
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, character)
|
|
}
|
|
func DeleteCharacter(c *gin.Context) {
|
|
id := c.Param("id")
|
|
var character models.Char
|
|
err := character.FirstID(id)
|
|
if err != nil {
|
|
respondWithError(c, http.StatusNotFound, "Character not found")
|
|
return
|
|
}
|
|
|
|
// Check ownership
|
|
if !checkCharacterOwnership(c, &character) {
|
|
return
|
|
}
|
|
|
|
err = character.Delete()
|
|
if err != nil {
|
|
respondWithError(c, http.StatusInternalServerError, "Failed to delete character")
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "Character deleted successfully"})
|
|
}
|
|
|
|
// Add Fertigkeit by putting it directly to the DB
|
|
func AddFertigkeit(charID uint, fertigkeit *models.SkFertigkeit) error {
|
|
// 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)
|
|
|
|
func ToFeChar(object *models.Char) *models.FeChar {
|
|
feC := &models.FeChar{
|
|
Char: *object,
|
|
}
|
|
for idx, fertigkeit := range object.Fertigkeiten {
|
|
fertigkeit.Bonus = GetSkillBonus(&object.Eigenschaften, &fertigkeit)
|
|
if fertigkeit.Bonus > 0 {
|
|
object.Fertigkeiten[idx].Bonus = fertigkeit.Bonus
|
|
}
|
|
}
|
|
skills, innateSkills, categories := splitSkills(object.Fertigkeiten)
|
|
feC.Fertigkeiten = skills
|
|
feC.InnateSkills = innateSkills
|
|
feC.CategorizedSkills = categories
|
|
feC.Git = object.GetGiftToleranz()
|
|
return feC
|
|
}
|
|
|
|
func GetSkillBonus(eigenschaften *[]models.Eigenschaft, skill *models.SkFertigkeit) int {
|
|
bonus := 0
|
|
gsmsk := skill.GetSkillByName()
|
|
if gsmsk.Bonuseigenschaft != "check" {
|
|
for _, eigenschaft := range *eigenschaften {
|
|
if eigenschaft.Name == gsmsk.Bonuseigenschaft {
|
|
if eigenschaft.Value < 6 {
|
|
bonus = -2
|
|
break
|
|
} else if eigenschaft.Value < 21 {
|
|
bonus = -1
|
|
break
|
|
} else if eigenschaft.Value > 81 && eigenschaft.Value < 96 {
|
|
bonus = 1
|
|
break
|
|
} else if eigenschaft.Value >= 96 {
|
|
bonus = 2
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
skill.Bonus = bonus
|
|
return bonus
|
|
}
|
|
|
|
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)
|
|
for _, skill := range object {
|
|
gsmsk := skill.GetSkillByName()
|
|
if gsmsk != nil && gsmsk.Improvable {
|
|
// Use GetCategory() which fetches from learning_skill_category_difficulties table
|
|
// with lowest ID when multiple categories exist
|
|
category := skill.GetCategory()
|
|
normSkills = append(normSkills, skill)
|
|
if _, exists := categories[category]; !exists {
|
|
categories[category] = make([]models.SkFertigkeit, 0)
|
|
}
|
|
categories[category] = append(categories[category], skill)
|
|
} else if gsmsk == nil {
|
|
// Skill not found in gsmaster - could be custom skill
|
|
// Treat as improvable and use GetCategory which will return Unkategorisiert
|
|
category := skill.GetCategory()
|
|
normSkills = append(normSkills, skill)
|
|
if _, exists := categories[category]; !exists {
|
|
categories[category] = make([]models.SkFertigkeit, 0)
|
|
}
|
|
categories[category] = append(categories[category], skill)
|
|
} else {
|
|
innateSkills = append(innateSkills, skill)
|
|
}
|
|
}
|
|
|
|
return normSkills, innateSkills, categories
|
|
}
|
|
|
|
// ExperienceAndWealthResponse repräsentiert die Antwort für EP und Vermögen
|
|
type ExperienceAndWealthResponse struct {
|
|
ExperiencePoints int `json:"experience_points"`
|
|
Wealth struct {
|
|
Goldstuecke int `json:"gold_coins"` // GS
|
|
Silberstuecke int `json:"silver_coins"` // SS
|
|
Kupferstuecke int `json:"copper_coins"` // KS
|
|
TotalInGS int `json:"total_in_ss"` // Gesamt in Silberstücken
|
|
} `json:"wealth"`
|
|
}
|
|
|
|
// GetCharacterExperienceAndWealth gibt nur die EP und Vermögensdaten eines Charakters zurück
|
|
func GetCharacterExperienceAndWealth(c *gin.Context) {
|
|
id := c.Param("id")
|
|
var character models.Char
|
|
|
|
// 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 PRG Währung)
|
|
gs := character.Vermoegen.Goldstuecke
|
|
ss := character.Vermoegen.Silberstuecke
|
|
ks := character.Vermoegen.Kupferstuecke
|
|
totalInSS := (gs * 10) + ss + (ks / 10)
|
|
|
|
response := ExperienceAndWealthResponse{
|
|
ExperiencePoints: character.Erfahrungsschatz.EP,
|
|
}
|
|
response.Wealth.Goldstuecke = gs
|
|
response.Wealth.Silberstuecke = ss
|
|
response.Wealth.Kupferstuecke = 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
|
|
// TODO Wenn EP verändert werden ändert sich auch ES
|
|
func UpdateCharacterExperience(c *gin.Context) {
|
|
id := c.Param("id")
|
|
var character models.Char
|
|
|
|
// Lade den Charakter
|
|
err := database.DB.
|
|
Preload("Erfahrungsschatz").
|
|
First(&character, id).Error
|
|
if err != nil {
|
|
respondWithError(c, http.StatusNotFound, "Character not found")
|
|
return
|
|
}
|
|
|
|
// Check ownership
|
|
if !checkCharacterOwnership(c, &character) {
|
|
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 {
|
|
oldValue = character.Erfahrungsschatz.EP
|
|
}
|
|
|
|
// Aktualisiere oder erstelle Erfahrungsschatz
|
|
if character.Erfahrungsschatz.ID == 0 {
|
|
// Erstelle neuen Erfahrungsschatz
|
|
character.Erfahrungsschatz = models.Erfahrungsschatz{
|
|
BamortCharTrait: models.BamortCharTrait{
|
|
CharacterID: character.ID,
|
|
},
|
|
EP: req.ExperiencePoints,
|
|
}
|
|
if err := database.DB.Create(&character.Erfahrungsschatz).Error; err != nil {
|
|
respondWithError(c, http.StatusInternalServerError, "Failed to create experience record")
|
|
return
|
|
}
|
|
} else {
|
|
// Aktualisiere existierenden Erfahrungsschatz
|
|
character.Erfahrungsschatz.EP = req.ExperiencePoints
|
|
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")
|
|
var character models.Char
|
|
|
|
// Lade den Charakter
|
|
err := database.DB.
|
|
Preload("Vermoegen").
|
|
First(&character, id).Error
|
|
if err != nil {
|
|
respondWithError(c, http.StatusNotFound, "Character not found")
|
|
return
|
|
}
|
|
|
|
// Check ownership
|
|
if !checkCharacterOwnership(c, &character) {
|
|
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.Goldstuecke
|
|
oldSilver = character.Vermoegen.Silberstuecke
|
|
oldCopper = character.Vermoegen.Kupferstuecke
|
|
}
|
|
|
|
// Aktualisiere oder erstelle Vermögen
|
|
if character.Vermoegen.ID == 0 {
|
|
// Erstelle neues Vermögen
|
|
userID := c.GetUint("userID")
|
|
character.Vermoegen = models.Vermoegen{
|
|
BamortCharTrait: models.BamortCharTrait{
|
|
CharacterID: character.ID,
|
|
UserID: userID,
|
|
},
|
|
Goldstuecke: getValueOrDefault(req.Goldstücke, 0),
|
|
Silberstuecke: getValueOrDefault(req.Silberstücke, 0),
|
|
Kupferstuecke: 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.Goldstuecke = *req.Goldstücke
|
|
}
|
|
if req.Silberstücke != nil {
|
|
character.Vermoegen.Silberstuecke = *req.Silberstücke
|
|
}
|
|
if req.Kupferstücke != nil {
|
|
character.Vermoegen.Kupferstuecke = *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.Goldstuecke {
|
|
CreateAuditLogEntry(
|
|
character.ID,
|
|
"gold",
|
|
oldGold,
|
|
character.Vermoegen.Goldstuecke,
|
|
AuditLogReason(req.Reason),
|
|
userID,
|
|
req.Notes,
|
|
)
|
|
}
|
|
|
|
if req.Silberstücke != nil && oldSilver != character.Vermoegen.Silberstuecke {
|
|
CreateAuditLogEntry(
|
|
character.ID,
|
|
"silver",
|
|
oldSilver,
|
|
character.Vermoegen.Silberstuecke,
|
|
AuditLogReason(req.Reason),
|
|
userID,
|
|
req.Notes,
|
|
)
|
|
}
|
|
|
|
if req.Kupferstücke != nil && oldCopper != character.Vermoegen.Kupferstuecke {
|
|
CreateAuditLogEntry(
|
|
character.ID,
|
|
"copper",
|
|
oldCopper,
|
|
character.Vermoegen.Kupferstuecke,
|
|
AuditLogReason(req.Reason),
|
|
userID,
|
|
req.Notes,
|
|
)
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "Wealth updated successfully",
|
|
"wealth": gin.H{
|
|
"goldstücke": character.Vermoegen.Goldstuecke,
|
|
"silberstücke": character.Vermoegen.Silberstuecke,
|
|
"kupferstücke": character.Vermoegen.Kupferstuecke,
|
|
},
|
|
})
|
|
}
|
|
|
|
// 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
}
|
|
|
|
// Learn and Improve handlers with automatic audit logging
|
|
|
|
// LearnSpellRequest definiert die Struktur für das Lernen eines Zaubers
|
|
type LearnSpellRequest struct {
|
|
Name string `json:"name" binding:"required"`
|
|
Notes string `json:"notes,omitempty"`
|
|
}
|
|
|
|
// getCharacterClass is deprecated. Use character.Klasse directly or appropriate database lookups.
|
|
// This function provides backwards compatibility for character class access.
|
|
// getCharacterClass gibt die Charakterklassen-Abkürzung zurück
|
|
func getCharacterClass(character *models.Char) string {
|
|
if len(character.Typ) > 3 {
|
|
return gsmaster.GetClassAbbreviationNewSystem(character.Typ)
|
|
}
|
|
return character.Typ
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// Check ownership
|
|
if !checkCharacterOwnership(c, &character) {
|
|
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) {
|
|
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) {
|
|
currentEP := char.Erfahrungsschatz.EP
|
|
currentGold := char.Vermoegen.Goldstuecke
|
|
|
|
// EP abziehen und Audit-Log erstellen
|
|
newEP := currentEP - totalEP
|
|
if totalEP > 0 {
|
|
var notes string
|
|
if finalLevel > 1 {
|
|
notes = fmt.Sprintf("Fertigkeit '%s' bis Level %d gelernt", itemName, finalLevel)
|
|
} else if auditReason == ReasonSpellLearning {
|
|
notes = fmt.Sprintf("Zauber '%s' gelernt", itemName)
|
|
} else {
|
|
notes = fmt.Sprintf("Fertigkeit '%s' gelernt", itemName)
|
|
}
|
|
|
|
err := CreateAuditLogEntry(char.ID, "experience_points", currentEP, newEP, auditReason, 0, notes)
|
|
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 {
|
|
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)
|
|
}
|
|
|
|
err := CreateAuditLogEntry(char.ID, "gold", currentGold, newGold, auditReason, 0, notes)
|
|
if err != nil {
|
|
return 0, 0, fmt.Errorf("fehler beim Erstellen des Audit-Log-Eintrags: %v", err)
|
|
}
|
|
char.Vermoegen.Goldstuecke = 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 {
|
|
if char.Fertigkeiten[i].Name == itemName {
|
|
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 {
|
|
if char.Waffenfertigkeiten[i].Name == itemName {
|
|
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.Goldstuecke
|
|
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
|
|
// 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.Goldstuecke
|
|
|
|
// 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 {
|
|
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 {
|
|
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 {
|
|
return newEP, newGold, fmt.Errorf("Fehler beim Erstellen des Audit-Log-Eintrags: %v", err)
|
|
}
|
|
char.Vermoegen.Goldstuecke = newGold
|
|
if err := database.DB.Save(&char.Vermoegen).Error; err != nil {
|
|
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 {
|
|
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 {
|
|
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
|
|
}
|
|
|
|
// Check ownership
|
|
if !checkCharacterOwnership(c, char) {
|
|
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)
|
|
}
|
|
|
|
// 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)
|
|
|
|
// Ensure spell data is sane for learning calculations (some legacy seeds have level 0 or missing categories)
|
|
var spell models.Spell
|
|
if err := database.DB.Where("name = ?", spellName).First(&spell).Error; err == nil {
|
|
updated := false
|
|
if spell.Stufe <= 0 {
|
|
spell.Stufe = 1
|
|
updated = true
|
|
}
|
|
if spell.LearningCategory == "" {
|
|
spell.LearningCategory = "Spruch"
|
|
updated = true
|
|
}
|
|
if spell.Category == "" {
|
|
spell.Category = "Erkennen"
|
|
updated = true
|
|
}
|
|
if updated {
|
|
_ = database.DB.Save(&spell).Error
|
|
}
|
|
}
|
|
|
|
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) {
|
|
char_ID := c.Param("id")
|
|
/*
|
|
var character models.Char
|
|
|
|
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")
|
|
return
|
|
}
|
|
charID := uint(charIDInt)
|
|
|
|
// Load character to check ownership
|
|
var character models.Char
|
|
if err := character.FirstID(char_ID); err != nil {
|
|
respondWithError(c, http.StatusNotFound, "Charakter nicht gefunden")
|
|
return
|
|
}
|
|
|
|
// Check ownership
|
|
if !checkCharacterOwnership(c, &character) {
|
|
return
|
|
}
|
|
|
|
var lernRequest gsmaster.LernCostRequest
|
|
if err := c.ShouldBindJSON(&lernRequest); err != nil {
|
|
respondWithError(c, http.StatusBadRequest, "Ungültige Anfrageparameter: "+err.Error())
|
|
return
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// GetRewardTypesStatic is deprecated. Use GetRewardTypes instead.
|
|
// This function provides hardcoded reward type mappings.
|
|
// GetRewardTypesStatic liefert verfügbare Belohnungsarten für ein bestimmtes Lernszenario
|
|
func GetRewardTypesStatic(c *gin.Context) {
|
|
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
|
|
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"},
|
|
)
|
|
|
|
case "spell":
|
|
// Zauber lernen - halveepnoGold verfügbar
|
|
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"},
|
|
)
|
|
|
|
case "improve":
|
|
// Fertigkeit verbessern - halveepnoGold verfügbar
|
|
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"},
|
|
)
|
|
|
|
// 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,
|
|
})
|
|
}
|
|
|
|
// GetAvailableSkillsNewSystem gibt alle verfügbaren Fertigkeiten mit Lernkosten zurück (POST mit LernCostRequest)
|
|
func GetAvailableSkillsNewSystem(c *gin.Context) {
|
|
// Parse LernCostRequest aus POST body
|
|
var baseRequest gsmaster.LernCostRequest
|
|
if err := c.ShouldBindJSON(&baseRequest); err != nil {
|
|
respondWithError(c, http.StatusBadRequest, "Ungültige Anfrageparameter: "+err.Error())
|
|
return
|
|
}
|
|
|
|
// For character creation (char_id = 0), we don't need to load an existing character
|
|
var character models.Char
|
|
learnedSkills := make(map[string]bool)
|
|
|
|
if baseRequest.CharId != 0 {
|
|
// Load existing character and their learned skills
|
|
if err := database.DB.Preload("Fertigkeiten").Preload("Erfahrungsschatz").Preload("Vermoegen").First(&character, baseRequest.CharId).Error; err != nil {
|
|
respondWithError(c, http.StatusNotFound, "Character not found")
|
|
return
|
|
}
|
|
|
|
// Create map of learned skills for existing character
|
|
for _, skill := range character.Fertigkeiten {
|
|
learnedSkills[skill.Name] = true
|
|
}
|
|
}
|
|
// For character creation (char_id = 0), learnedSkills remains empty
|
|
|
|
// Hole alle verfügbaren Fertigkeiten aus der gsmaster Datenbank
|
|
var allSkills []models.Skill
|
|
|
|
allSkills, err := models.SelectSkills("", "")
|
|
if err != nil {
|
|
respondWithError(c, http.StatusInternalServerError, "Failed to retrieve skills from gsmaster")
|
|
return
|
|
}
|
|
|
|
// Organisiere Fertigkeiten nach Kategorien
|
|
skillsByCategory := make(map[string][]gin.H)
|
|
|
|
for _, skill := range allSkills {
|
|
// Überspringe bereits gelernte Fertigkeiten
|
|
if learnedSkills[skill.Name] {
|
|
continue
|
|
}
|
|
|
|
// Erstelle LernCostRequest für diese Fertigkeit basierend auf der Basis-Anfrage
|
|
request := baseRequest
|
|
request.CharId = baseRequest.CharId // Use the char_id from the request (0 for character creation)
|
|
request.Name = skill.Name
|
|
request.CurrentLevel = 0 // Nicht gelernt
|
|
request.TargetLevel = 1 // Auf Level 1 lernen
|
|
request.Type = "skill"
|
|
request.Action = "learn"
|
|
|
|
// Erstelle SkillCostResultNew
|
|
characterClass := ""
|
|
characterID := "0"
|
|
|
|
if baseRequest.CharId != 0 {
|
|
// Use existing character data
|
|
characterID = fmt.Sprintf("%d", character.ID)
|
|
characterClass = getCharacterClass(&character)
|
|
}
|
|
// For character creation, we don't have a character class yet, use empty string
|
|
|
|
levelResult := gsmaster.SkillCostResultNew{
|
|
CharacterID: characterID,
|
|
CharacterClass: characterClass,
|
|
SkillName: skill.Name,
|
|
TargetLevel: 1,
|
|
}
|
|
|
|
remainingPP := request.UsePP
|
|
remainingGold := request.UseGold
|
|
|
|
// Hole die vollständigen Skill-Informationen für die Kostenberechnung
|
|
skillLearningInfo, err := models.GetSkillCategoryAndDifficultyNewSystem(skill.Name, characterClass)
|
|
if err != nil {
|
|
// Fallback für unbekannte Skills
|
|
skillLearningInfo = &models.SkillLearningInfo{
|
|
SkillName: skill.Name,
|
|
CategoryName: skill.Category,
|
|
LearnCost: 50, // Standard-Lernkosten
|
|
}
|
|
}
|
|
|
|
// For character creation (CharId = 0), use learning costs instead of improvement costs
|
|
var epCost, goldCost int
|
|
if baseRequest.CharId == 0 {
|
|
// Character creation: use basic learning costs from skillLearningInfo
|
|
learnCost := skillLearningInfo.LearnCost
|
|
if learnCost == 0 {
|
|
learnCost = 50 // Default learning cost
|
|
}
|
|
|
|
// For character creation, costs are much lower - just the basic learning cost
|
|
epCost = learnCost * 2 // Simple formula: learning cost * 2 for EP
|
|
goldCost = learnCost * 5 // Simple formula: learning cost * 5 for gold
|
|
} else {
|
|
// Existing character improvement: use the full system
|
|
err = calculateSkillLearnCostNewSystem(&request, &levelResult, &remainingPP, &remainingGold, skillLearningInfo)
|
|
epCost = 10000 // Fallback-Wert for improvements
|
|
goldCost = 50000 // Fallback-Wert for improvements
|
|
if err == nil {
|
|
epCost = levelResult.EP
|
|
goldCost = levelResult.GoldCost
|
|
}
|
|
}
|
|
|
|
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,
|
|
})
|
|
}
|
|
|
|
// getCharacterClassCode converts a character class name to its code using the database
|
|
func getCharacterClassCode(className string) (string, error) {
|
|
var characterClass models.CharacterClass
|
|
err := characterClass.FirstByNameOrCode(className)
|
|
if err != nil {
|
|
return "", fmt.Errorf("character class '%s' not found: %w", className, err)
|
|
}
|
|
return characterClass.Code, nil
|
|
}
|
|
|
|
func GetAvailableSpellsForCreation(c *gin.Context) {
|
|
var request struct {
|
|
CharacterClass string `json:"characterClass" binding:"required"`
|
|
}
|
|
if err := c.ShouldBindJSON(&request); err != nil {
|
|
logger.Warn("HTTP Fehler 400: Ungültige Anfrageparameter: %v", err)
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "Ungültige Anfrageparameter",
|
|
"details": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
logger.Info("GetAvailableSpellsForCreation - CharacterClass: %s", request.CharacterClass)
|
|
|
|
// Convert character class name to code
|
|
characterClassCode, err := getCharacterClassCode(request.CharacterClass)
|
|
if err != nil {
|
|
logger.Error("Fehler beim Konvertieren der Charakterklasse: %v", err)
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": fmt.Sprintf("Unbekannte Charakterklasse: %s", request.CharacterClass),
|
|
})
|
|
return
|
|
}
|
|
|
|
// Get all available spells with their learning costs
|
|
spellsByCategory, err := GetAllSpellsWithLE(characterClassCode, 2)
|
|
if err != nil {
|
|
logger.Error("Fehler beim Abrufen der Zauber: %v", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "Fehler beim Abrufen der Zauber",
|
|
})
|
|
return
|
|
}
|
|
|
|
logger.Info("GetAvailableSpellsForCreation - Gefundene Kategorien: %d", len(spellsByCategory))
|
|
|
|
if len(spellsByCategory) == 0 {
|
|
logger.Warn("GetAvailableSpellsForCreation - Keine Zauber für Klasse %s gefunden", request.CharacterClass)
|
|
c.JSON(http.StatusNotFound, gin.H{
|
|
"spells_by_category": map[string][]gin.H{},
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"spells_by_category": spellsByCategory,
|
|
})
|
|
}
|
|
|
|
// GetAvailableSkillsForCreation returns skills with learning costs for character creation
|
|
func GetAvailableSkillsForCreation(c *gin.Context) {
|
|
var request struct {
|
|
CharacterClass string `json:"characterClass" binding:"required"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&request); err != nil {
|
|
logger.Warn("HTTP Fehler 400: Ungültige Anfrageparameter: %v", err)
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "Ungültige Anfrageparameter",
|
|
"details": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
logger.Info("GetAvailableSkillsForCreation - CharacterClass: %s", request.CharacterClass)
|
|
|
|
// Get all available skills with their learning costs
|
|
skillsByCategory, err := GetAllSkillsWithLE()
|
|
if err != nil {
|
|
logger.Error("Fehler beim Abrufen der Fertigkeiten: %v", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "Fehler beim Abrufen der Fertigkeiten",
|
|
})
|
|
return
|
|
}
|
|
|
|
logger.Info("GetAvailableSkillsForCreation - Gefundene Kategorien: %d", len(skillsByCategory))
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"skills_by_category": skillsByCategory,
|
|
})
|
|
}
|
|
|
|
func GetAllSpellsWithLE(characterClass string, maxLevel int) (map[string][]gin.H, error) {
|
|
// Create mapping of character classes to allowed learning categories
|
|
allowedCategories := getCharacterClassSpellSchoolMapping()
|
|
allowedLearningCategories := getCharacterClassSpellLearningCategoriesMapping()
|
|
|
|
// Check if character class has allowed spell schools
|
|
|
|
allowedSchools, exists := allowedCategories[characterClass]
|
|
if !exists {
|
|
return map[string][]gin.H{}, nil // Return empty map if class can't learn spells
|
|
}
|
|
allowedSpellType, exists := allowedLearningCategories[characterClass]
|
|
if !exists {
|
|
return map[string][]gin.H{}, nil // Return empty map if class can't learn spells
|
|
}
|
|
// Extract allowed school names and spell types from maps
|
|
var allowedSchoolNames []string
|
|
for school, allowed := range allowedSchools {
|
|
if allowed {
|
|
allowedSchoolNames = append(allowedSchoolNames, school)
|
|
}
|
|
}
|
|
|
|
var allowedSpellTypeNames []string
|
|
for spellType, allowed := range allowedSpellType {
|
|
if allowed {
|
|
allowedSpellTypeNames = append(allowedSpellTypeNames, spellType)
|
|
}
|
|
}
|
|
|
|
// Get all spells from database with level filter
|
|
var spells []models.Spell
|
|
err := database.DB.Where("stufe <= ? AND category in (?) and learning_category in (?) AND category IS NOT NULL AND learning_category IS NOT NULL", maxLevel, allowedSchoolNames, allowedSpellTypeNames).Find(&spells).Error
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to fetch spells: %w", err)
|
|
}
|
|
|
|
// Group spells by category (using LearningCategory field)
|
|
spellsByCategory := make(map[string][]gin.H)
|
|
|
|
for _, spell := range spells {
|
|
// Check if this character class can learn this spell school
|
|
//if !allowedSchools[spell.Category] || !allowedSpellType[spell.LearningCategory] {
|
|
// continue // Skip spells from schools this class can't learn
|
|
//}
|
|
|
|
// Calculate learning cost for this spell
|
|
leCost := getSpellLECost(spell.Stufe)
|
|
|
|
spellData := gin.H{
|
|
"id": spell.ID,
|
|
"name": spell.Name,
|
|
"level": spell.Stufe,
|
|
"school": spell.Category, // Display category
|
|
"learning_category": spell.LearningCategory, // Internal category for learning
|
|
"description": spell.Beschreibung,
|
|
"le_cost": leCost,
|
|
"ap": spell.AP,
|
|
"art": spell.Art,
|
|
"zauberdauer": spell.Zauberdauer,
|
|
"reichweite": spell.Reichweite,
|
|
"wirkungsziel": spell.Wirkungsziel,
|
|
"wirkungsbereich": spell.Wirkungsbereich,
|
|
"wirkungsdauer": spell.Wirkungsdauer,
|
|
}
|
|
|
|
// Use LearningCategory for grouping
|
|
category := spell.LearningCategory
|
|
spellsByCategory[category] = append(spellsByCategory[category], spellData)
|
|
}
|
|
|
|
// Sort spells within each category by level, then by name
|
|
for category := range spellsByCategory {
|
|
spells := spellsByCategory[category]
|
|
sort.Slice(spells, func(i, j int) bool {
|
|
levelI := spells[i]["level"].(int)
|
|
levelJ := spells[j]["level"].(int)
|
|
if levelI != levelJ {
|
|
return levelI < levelJ
|
|
}
|
|
nameI := spells[i]["name"].(string)
|
|
nameJ := spells[j]["name"].(string)
|
|
return nameI < nameJ
|
|
})
|
|
spellsByCategory[category] = spells
|
|
}
|
|
|
|
return spellsByCategory, nil
|
|
}
|
|
func getCharacterClassSpellLearningCategoriesMapping() map[string]map[string]bool {
|
|
return map[string]map[string]bool{
|
|
"Ma": { // Magier
|
|
"Spruch": true,
|
|
//"Salz": true,
|
|
//"Runenstab": true,
|
|
},
|
|
"Hx": { // Hexer
|
|
"Spruch": true,
|
|
"Salz": true,
|
|
//"Runenstab": true,
|
|
},
|
|
"Dr": { // Druide
|
|
"Spruch": true,
|
|
//"Salz": true,
|
|
//"Runenstab": true,
|
|
"Dweomer": true,
|
|
},
|
|
"Sc": { // Schamane
|
|
"Spruch": true,
|
|
//"Salz": true,
|
|
//"Runenstab": true,
|
|
"Dweomer": true,
|
|
},
|
|
"PB": { // Priester Beschützer
|
|
"Salz": true,
|
|
"Wundertat": true,
|
|
},
|
|
"PS": { // Priester Streiter
|
|
"Salz": true,
|
|
"Wundertat": true,
|
|
},
|
|
"Ba": { // Barde
|
|
"Lied": true,
|
|
},
|
|
"Or": { // Ordenskrieger
|
|
"Wundertat": true,
|
|
},
|
|
}
|
|
}
|
|
|
|
// getCharacterClassSpellSchoolMapping returns the mapping of character classes to allowed spell schools
|
|
func getCharacterClassSpellSchoolMapping() map[string]map[string]bool {
|
|
return map[string]map[string]bool{
|
|
"Ma": { // Magier
|
|
"Beherrschen": true,
|
|
"Bewegen": true,
|
|
"Dweomer": true,
|
|
"Erkennen": true,
|
|
"Erschaffen": true,
|
|
"Verändern": true,
|
|
"Zerstören": true,
|
|
},
|
|
"Hx": { // Hexer
|
|
"Beherrschen": true,
|
|
"Zerstören": true,
|
|
"Erkennen": true,
|
|
"Verändern": true,
|
|
"Erschaffen": true,
|
|
"Bewegen": true,
|
|
"Formen": true,
|
|
},
|
|
"Dr": { // Druide
|
|
"Bewegen": true,
|
|
"Erkennen": true,
|
|
"Erschaffen": true,
|
|
"Verändern": true,
|
|
},
|
|
"Sc": { // Schamane
|
|
"Beherrschen": true,
|
|
"Erkennen": true,
|
|
"Verändern": true,
|
|
},
|
|
"PB": { // Priester Beschützer
|
|
"Dweomer": true,
|
|
"Erkennen": true,
|
|
"Verändern": true,
|
|
},
|
|
"PS": { // Priester Streiter
|
|
"Dweomer": true,
|
|
"Erkennen": true,
|
|
"Verändern": true,
|
|
},
|
|
"Ba": { // Barde
|
|
"Beherrschen": true,
|
|
"Dweomer": true,
|
|
"Erkennen": true,
|
|
},
|
|
"Or": { // Ordenskrieger
|
|
"Dweomer": true,
|
|
"Erkennen": true,
|
|
},
|
|
}
|
|
}
|
|
|
|
// getSpellLECost returns the learning cost in LE for a given spell level from the database
|
|
func getSpellLECost(level int) int {
|
|
var spellLECost models.SpellLevelLECost
|
|
|
|
// Query the database for the LE cost for this level
|
|
err := database.DB.Where("level = ? AND game_system_id = ?", level, 1).First(&spellLECost).Error
|
|
if err != nil {
|
|
// If not found in database, fall back to standard RPG costs
|
|
spellLECosts := map[int]int{
|
|
1: 1,
|
|
2: 2,
|
|
3: 3,
|
|
4: 4,
|
|
5: 5,
|
|
6: 6,
|
|
7: 8,
|
|
8: 10,
|
|
9: 12,
|
|
10: 15,
|
|
11: 18,
|
|
12: 21,
|
|
}
|
|
|
|
if cost, exists := spellLECosts[level]; exists {
|
|
return cost
|
|
}
|
|
|
|
// Final fallback for unknown levels
|
|
return level
|
|
}
|
|
|
|
return spellLECost.LERequired
|
|
}
|
|
|
|
func GetAllSkillsWithLE() (map[string][]gin.H, error) {
|
|
// Get all skill categories from database
|
|
var skillCategories []models.SkillCategory
|
|
if err := database.DB.Find(&skillCategories).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
skillsByCategory := make(map[string][]gin.H)
|
|
|
|
// For each category, find all skills that can be learned in that category
|
|
for _, category := range skillCategories {
|
|
skillsByCategory[category.Name] = []gin.H{}
|
|
|
|
// Query all skill-category-difficulty combinations for this category
|
|
var skillCategoryDifficulties []models.SkillCategoryDifficulty
|
|
err := database.DB.Preload("Skill").Preload("SkillDifficulty").
|
|
Where("skill_category_id = ?", category.ID).
|
|
Find(&skillCategoryDifficulties).Error
|
|
|
|
if err != nil {
|
|
continue // Skip this category if there's an error
|
|
}
|
|
|
|
// For each skill in this category, add it with its LE cost and difficulty
|
|
for _, scd := range skillCategoryDifficulties {
|
|
if category.Name == "Unbekannt" || scd.Skill.InnateSkill {
|
|
continue
|
|
}
|
|
|
|
// For character creation, use reduced costs based on category and difficulty
|
|
// Regular learning costs (LearnCost) are for existing characters
|
|
creationCost := getSkillCreationCost(category.Name, scd.SkillDifficulty.Name)
|
|
|
|
skillInfo := gin.H{
|
|
"name": scd.Skill.Name,
|
|
"leCost": creationCost,
|
|
"difficulty": scd.SkillDifficulty.Name,
|
|
}
|
|
|
|
skillsByCategory[category.Name] = append(skillsByCategory[category.Name], skillInfo)
|
|
}
|
|
}
|
|
|
|
// Add weapon skills to "Kampf" category
|
|
weaponSkills, err := GetWeaponSkillsWithLE()
|
|
if err == nil {
|
|
if _, exists := skillsByCategory["Waffen"]; !exists {
|
|
skillsByCategory["Waffen"] = []gin.H{}
|
|
}
|
|
skillsByCategory["Waffen"] = append(skillsByCategory["Waffen"], weaponSkills...)
|
|
}
|
|
|
|
return skillsByCategory, nil
|
|
}
|
|
|
|
// getSkillCreationCost returns the LE cost for learning a skill during character creation
|
|
// These costs are much lower than regular learning costs, as they represent initial training
|
|
// Costs vary by both category and difficulty level
|
|
func getSkillCreationCost(category string, difficulty string) int {
|
|
// Normalize difficulty string for comparison
|
|
difficultyLower := strings.ToLower(difficulty)
|
|
|
|
// Define cost mapping per category and difficulty
|
|
switch category {
|
|
case "Alltag":
|
|
switch difficultyLower {
|
|
case "leicht":
|
|
return 1
|
|
case "normal":
|
|
return 1
|
|
case "schwer":
|
|
return 2
|
|
default:
|
|
return 1
|
|
}
|
|
case "Freiland":
|
|
switch difficultyLower {
|
|
case "leicht":
|
|
return 1
|
|
case "normal":
|
|
return 2
|
|
case "schwer":
|
|
return 2
|
|
default:
|
|
return 2
|
|
}
|
|
case "Halbwelt":
|
|
switch difficultyLower {
|
|
case "leicht":
|
|
return 1
|
|
case "normal":
|
|
return 2
|
|
case "schwer":
|
|
return 2
|
|
default:
|
|
return 2
|
|
}
|
|
case "Kampf":
|
|
switch difficultyLower {
|
|
case "leicht":
|
|
return 1
|
|
case "normal":
|
|
return 2
|
|
case "schwer":
|
|
return 3
|
|
default:
|
|
return 2
|
|
}
|
|
case "Körper":
|
|
switch difficultyLower {
|
|
case "leicht":
|
|
return 1
|
|
case "normal":
|
|
return 1
|
|
case "schwer":
|
|
return 2
|
|
default:
|
|
return 1
|
|
}
|
|
case "Sozial":
|
|
switch difficultyLower {
|
|
case "leicht":
|
|
return 2
|
|
case "normal":
|
|
return 2
|
|
case "schwer":
|
|
return 4
|
|
default:
|
|
return 2
|
|
}
|
|
case "Unterwelt":
|
|
switch difficultyLower {
|
|
case "leicht":
|
|
return 2
|
|
case "normal":
|
|
return 4
|
|
case "schwer":
|
|
return 6
|
|
default:
|
|
return 4
|
|
}
|
|
case "Waffen":
|
|
switch difficultyLower {
|
|
case "leicht":
|
|
return 2
|
|
case "normal":
|
|
return 4
|
|
case "schwer":
|
|
return 6
|
|
case "sehr schwer":
|
|
return 8
|
|
default:
|
|
return 4
|
|
}
|
|
case "Wissen":
|
|
switch difficultyLower {
|
|
case "leicht":
|
|
return 1
|
|
case "normal":
|
|
return 2
|
|
case "schwer":
|
|
return 2
|
|
default:
|
|
return 2
|
|
}
|
|
default:
|
|
// Default fallback for unknown categories
|
|
switch difficultyLower {
|
|
case "leicht":
|
|
return 1
|
|
case "normal":
|
|
return 2
|
|
case "schwer":
|
|
return 3
|
|
case "sehr schwer":
|
|
return 4
|
|
default:
|
|
return 2
|
|
}
|
|
}
|
|
}
|
|
|
|
// GetWeaponSkillsWithLE returns all weapon skills with their learning costs
|
|
func GetWeaponSkillsWithLE() ([]gin.H, error) {
|
|
// Query weapon skills with their difficulty from the WeaponSkillCategoryDifficulty table
|
|
var weaponSkillDifficulties []models.WeaponSkillCategoryDifficulty
|
|
|
|
err := database.DB.Preload("WeaponSkill").
|
|
Preload("SkillDifficulty").
|
|
Preload("SkillCategory").
|
|
Find(&weaponSkillDifficulties).Error
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var result []gin.H
|
|
seenWeapons := make(map[string]bool) // Track weapons we've already added
|
|
|
|
for _, wscd := range weaponSkillDifficulties {
|
|
weaponName := wscd.WeaponSkill.Name
|
|
|
|
// Skip if we've already added this weapon (avoid duplicates)
|
|
if seenWeapons[weaponName] {
|
|
continue
|
|
}
|
|
seenWeapons[weaponName] = true
|
|
|
|
// Use the category-based creation cost logic for weapons
|
|
// Weapons are always in the "Waffen" category
|
|
difficulty := wscd.SkillDifficulty.Name
|
|
leCost := getSkillCreationCost("Waffen", difficulty)
|
|
|
|
skillInfo := gin.H{
|
|
"name": weaponName,
|
|
"leCost": leCost,
|
|
"difficulty": difficulty,
|
|
"type": "weapon", // Mark as weapon skill
|
|
}
|
|
|
|
result = append(result, skillInfo)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// GetAllSkillsWithLearningCosts returns all skills with their basic learning costs for all possible categories
|
|
func GetAllSkillsWithLearningCosts(characterClass string) (map[string][]gin.H, error) {
|
|
skills, err := models.SelectSkills("", "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
skillsByCategory := make(map[string][]gin.H)
|
|
|
|
// Define all possible categories for skills
|
|
allCategories := []string{"Alltag", "Kampf", "Körper", "Sozial", "Wissen", "Halbwelt", "Unterwelt", "Freiland", "Sonstige"}
|
|
|
|
for _, skill := range skills {
|
|
// First, always add to the skill's original category
|
|
originalCategory := skill.Category
|
|
if originalCategory == "" {
|
|
originalCategory = "Sonstige"
|
|
}
|
|
|
|
// Try to get the best category and learning cost for this skill and character class
|
|
|
|
skillInfo, err := models.GetSkillCategoryAndDifficultyNewSystem(skill.Name, characterClass)
|
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, err
|
|
}
|
|
|
|
var bestCategory, difficulty string
|
|
if skillInfo != nil {
|
|
bestCategory = skillInfo.CategoryName
|
|
difficulty = skillInfo.DifficultyName
|
|
}
|
|
|
|
var learnCost int
|
|
// error cannot be nil at this point
|
|
//if err == nil && bestCategory != "" {
|
|
if bestCategory != "" {
|
|
// Use the difficulty as a basis for learning cost
|
|
switch difficulty {
|
|
case "Leicht":
|
|
learnCost = 1
|
|
case "Normal":
|
|
learnCost = 2
|
|
case "Schwer":
|
|
learnCost = 4
|
|
case "Sehr Schwer":
|
|
learnCost = 10
|
|
default:
|
|
learnCost = 50 // Default fallback
|
|
}
|
|
|
|
// Add to the best category
|
|
skillInfo := gin.H{
|
|
"name": skill.Name,
|
|
"learnCost": learnCost,
|
|
}
|
|
skillsByCategory[bestCategory] = append(skillsByCategory[bestCategory], skillInfo)
|
|
|
|
// If the best category is different from original, also add to original with higher cost
|
|
if bestCategory != originalCategory {
|
|
skillInfoOriginal := gin.H{
|
|
"name": skill.Name,
|
|
"learnCost": learnCost * 2, // Higher cost for non-optimal category
|
|
}
|
|
skillsByCategory[originalCategory] = append(skillsByCategory[originalCategory], skillInfoOriginal)
|
|
}
|
|
} else {
|
|
// Fallback: add to original category only
|
|
skillInfo := gin.H{
|
|
"name": skill.Name,
|
|
"learnCost": 50, // Default learning cost
|
|
}
|
|
skillsByCategory[originalCategory] = append(skillsByCategory[originalCategory], skillInfo)
|
|
}
|
|
|
|
// Try to add skill to other logical categories with higher costs
|
|
// This allows more flexibility in character creation
|
|
for _, category := range allCategories {
|
|
if category == bestCategory || category == originalCategory {
|
|
continue // Already added
|
|
}
|
|
|
|
// Only add to certain categories if it makes sense
|
|
if shouldSkillBeInCategory(skill.Name, category) {
|
|
higherCost := learnCost
|
|
if higherCost == 0 {
|
|
higherCost = 50
|
|
}
|
|
higherCost = higherCost * 3 // Much higher cost for cross-category learning
|
|
|
|
skillInfo := gin.H{
|
|
"name": skill.Name,
|
|
"learnCost": higherCost,
|
|
}
|
|
skillsByCategory[category] = append(skillsByCategory[category], skillInfo)
|
|
}
|
|
}
|
|
}
|
|
|
|
return skillsByCategory, nil
|
|
}
|
|
|
|
// shouldSkillBeInCategory determines if a skill should be available in a given category
|
|
func shouldSkillBeInCategory(skillName, category string) bool {
|
|
// Define which skills can appear in which categories
|
|
skillCategoryMap := map[string][]string{
|
|
// Physical skills can appear in multiple categories
|
|
"Athletik": {"Körper", "Kampf", "Freiland"},
|
|
"Klettern": {"Körper", "Freiland", "Alltag"},
|
|
"Schwimmen": {"Körper", "Freiland", "Alltag"},
|
|
"Laufen": {"Körper", "Kampf", "Freiland"},
|
|
"Akrobatik": {"Körper", "Kampf"},
|
|
|
|
// Combat skills
|
|
"Dolch": {"Kampf", "Halbwelt"},
|
|
"Schwert": {"Kampf"},
|
|
"Bogen": {"Kampf", "Freiland"},
|
|
|
|
// Social skills
|
|
"Menschenkenntnis": {"Sozial", "Halbwelt"},
|
|
"Verführen": {"Sozial", "Halbwelt"},
|
|
"Anführen": {"Sozial", "Kampf"},
|
|
|
|
// Knowledge skills
|
|
"Schreiben": {"Wissen", "Alltag"},
|
|
"Sprache": {"Wissen", "Sozial"},
|
|
"Naturkunde": {"Wissen", "Freiland"},
|
|
|
|
// Stealth and underworld
|
|
"Schleichen": {"Halbwelt", "Freiland", "Kampf"},
|
|
"Tarnen": {"Halbwelt", "Freiland", "Kampf"},
|
|
"Stehlen": {"Halbwelt"},
|
|
|
|
// Survival and wilderness
|
|
"Überleben": {"Freiland", "Alltag"},
|
|
"Spurensuche": {"Freiland", "Halbwelt"},
|
|
"Orientierung": {"Freiland", "Alltag"},
|
|
}
|
|
|
|
categories, exists := skillCategoryMap[skillName]
|
|
if !exists {
|
|
return false // Only add skills we explicitly define
|
|
}
|
|
|
|
for _, cat := range categories {
|
|
if cat == category {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// GetAvailableSpellsNewSystem gibt alle verfügbaren Zauber mit Lernkosten zurück (POST mit LernCostRequest)
|
|
func GetAvailableSpellsNewSystem(c *gin.Context) {
|
|
//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
|
|
}
|
|
|
|
var character models.Char
|
|
if err := database.DB.Preload("Zauber").Preload("Erfahrungsschatz").Preload("Vermoegen").First(&character, baseRequest.CharId).Error; err != nil {
|
|
respondWithError(c, http.StatusNotFound, "Character not found")
|
|
return
|
|
}
|
|
|
|
charakteClass := getCharacterClass(&character)
|
|
// Hole alle verfügbaren Zauber aus der gsmaster Datenbank
|
|
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
|
|
}
|
|
|
|
// 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,
|
|
})
|
|
}
|
|
|
|
// 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,
|
|
})
|
|
}
|
|
|
|
// Character Creation Session Management
|
|
|
|
// CreateCharacterSession erstellt eine neue Charakter-Erstellungssession
|
|
func CreateCharacterSession(c *gin.Context) {
|
|
logger.Debug("CreateCharacterSession aufgerufen")
|
|
|
|
// Debug: Alle Kontext-Keys anzeigen
|
|
keys := make([]string, 0)
|
|
for key := range c.Keys {
|
|
keys = append(keys, fmt.Sprintf("%s=%v", key, c.Keys[key]))
|
|
}
|
|
logger.Debug("CreateCharacterSession: Verfügbare Kontext-Keys: [%s]", strings.Join(keys, ", "))
|
|
|
|
userID := c.GetUint("userID")
|
|
logger.Debug("CreateCharacterSession: UserID = %d", userID)
|
|
|
|
if userID == 0 {
|
|
logger.Warn("CreateCharacterSession: Unauthorized - UserID ist 0")
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
|
return
|
|
}
|
|
|
|
sessionID := fmt.Sprintf("char_create_%d_%d", userID, time.Now().Unix())
|
|
logger.Debug("CreateCharacterSession: Generierte SessionID = %s", sessionID)
|
|
|
|
session := models.CharacterCreationSession{
|
|
ID: sessionID,
|
|
UserID: userID,
|
|
Name: "",
|
|
Geschlecht: "",
|
|
Rasse: "",
|
|
Typ: "",
|
|
Herkunft: "",
|
|
Stand: "",
|
|
Glaube: "",
|
|
Attributes: models.AttributesData{},
|
|
DerivedValues: models.DerivedValuesData{},
|
|
Skills: models.CharacterCreationSkills{},
|
|
Spells: models.CharacterCreationSpells{},
|
|
SkillPoints: models.SkillPointsData{},
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
ExpiresAt: time.Now().AddDate(0, 0, 14), // 14 Tage
|
|
CurrentStep: 1,
|
|
}
|
|
logger.Debug("CreateCharacterSession: Session-Struktur erstellt, ExpiresAt = %s", session.ExpiresAt.Format(time.RFC3339))
|
|
|
|
// Session in Datenbank speichern
|
|
logger.Debug("CreateCharacterSession: Speichere Session in Datenbank...")
|
|
err := database.DB.Create(&session).Error
|
|
if err != nil {
|
|
logger.Error("CreateCharacterSession: Fehler beim Erstellen der Session: %s", err.Error())
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create session"})
|
|
return
|
|
}
|
|
|
|
logger.Info("CreateCharacterSession: Session erfolgreich erstellt - SessionID: %s, UserID: %d", sessionID, userID)
|
|
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) {
|
|
logger.Debug("ListCharacterSessions aufgerufen")
|
|
|
|
userID := c.GetUint("userID")
|
|
logger.Debug("ListCharacterSessions: UserID = %d", userID)
|
|
|
|
if userID == 0 {
|
|
logger.Warn("ListCharacterSessions: Unauthorized - UserID ist 0")
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
|
return
|
|
}
|
|
|
|
// Sessions aus Datenbank laden
|
|
logger.Debug("ListCharacterSessions: Lade Sessions für UserID %d aus Datenbank...", userID)
|
|
sessions, err := models.GetUserSessions(database.DB, userID)
|
|
if err != nil {
|
|
logger.Error("ListCharacterSessions: Fehler beim Laden der Sessions: %s", err.Error())
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to load sessions"})
|
|
return
|
|
}
|
|
|
|
logger.Debug("ListCharacterSessions: Gefundene Sessions: %d", len(sessions))
|
|
|
|
// Sessions für Frontend formatieren
|
|
var formattedSessions []gin.H
|
|
for i, session := range sessions {
|
|
// Schritt-Text bestimmen
|
|
progressText := getProgressText(session.CurrentStep)
|
|
logger.Debug("ListCharacterSessions: Formatiere Session %d - ID: %s, Step: %d, Name: %s",
|
|
i+1, session.ID, session.CurrentStep, session.Name)
|
|
|
|
formattedSessions = append(formattedSessions, gin.H{
|
|
"session_id": session.ID,
|
|
"name": session.Name,
|
|
"rasse": session.Rasse,
|
|
"typ": session.Typ,
|
|
"current_step": session.CurrentStep,
|
|
"total_steps": 5,
|
|
"created_at": session.CreatedAt,
|
|
"updated_at": session.UpdatedAt,
|
|
"expires_at": session.ExpiresAt,
|
|
"progress_text": progressText,
|
|
})
|
|
}
|
|
|
|
logger.Info("ListCharacterSessions: Sessions erfolgreich geladen für UserID %d - Anzahl: %d", userID, len(formattedSessions))
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"sessions": formattedSessions,
|
|
"count": len(formattedSessions),
|
|
})
|
|
}
|
|
|
|
// getProgressText gibt den Schritt-Text für die Frontend-Anzeige zurück
|
|
func getProgressText(step int) string {
|
|
logger.Debug("getProgressText: Ermittle Text für Schritt %d", step)
|
|
|
|
var text string
|
|
switch step {
|
|
case 1:
|
|
text = "Grundinformationen"
|
|
case 2:
|
|
text = "Attribute"
|
|
case 3:
|
|
text = "Abgeleitete Werte"
|
|
case 4:
|
|
text = "Fertigkeiten"
|
|
case 5:
|
|
text = "Zauber"
|
|
default:
|
|
text = "Unbekannt"
|
|
logger.Warn("getProgressText: Unbekannter Schritt %d", step)
|
|
}
|
|
|
|
logger.Debug("getProgressText: Schritt %d = '%s'", step, text)
|
|
return text
|
|
}
|
|
|
|
// GetCharacterSession gibt Session-Daten zurück
|
|
func GetCharacterSession(c *gin.Context) {
|
|
logger.Debug("GetCharacterSession aufgerufen")
|
|
|
|
sessionID := c.Param("sessionId")
|
|
userID := c.GetUint("userID")
|
|
logger.Debug("GetCharacterSession: SessionID = %s, UserID = %d", sessionID, userID)
|
|
|
|
if userID == 0 {
|
|
logger.Warn("GetCharacterSession: Unauthorized - UserID ist 0")
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
|
return
|
|
}
|
|
|
|
// Session aus Datenbank laden
|
|
logger.Debug("GetCharacterSession: Lade Session aus Datenbank...")
|
|
var session models.CharacterCreationSession
|
|
err := database.DB.Where("id = ? AND user_id = ?", sessionID, userID).First(&session).Error
|
|
if err != nil {
|
|
logger.Error("GetCharacterSession: Session nicht gefunden - SessionID: %s, UserID: %d, Error: %s",
|
|
sessionID, userID, err.Error())
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Session not found"})
|
|
return
|
|
}
|
|
|
|
logger.Debug("GetCharacterSession: Session gefunden - Name: %s, Step: %d, ExpiresAt: %s",
|
|
session.Name, session.CurrentStep, session.ExpiresAt.Format(time.RFC3339))
|
|
|
|
// Prüfen ob Session noch gültig ist
|
|
if session.ExpiresAt.Before(time.Now()) {
|
|
logger.Warn("GetCharacterSession: Session abgelaufen - SessionID: %s, ExpiresAt: %s",
|
|
sessionID, session.ExpiresAt.Format(time.RFC3339))
|
|
|
|
// Abgelaufene Session löschen
|
|
logger.Debug("GetCharacterSession: Lösche abgelaufene Session...")
|
|
database.DB.Delete(&session)
|
|
|
|
c.JSON(http.StatusGone, gin.H{"error": "Session expired"})
|
|
return
|
|
}
|
|
|
|
logger.Info("GetCharacterSession: Session erfolgreich geladen - SessionID: %s, UserID: %d, Step: %d",
|
|
sessionID, userID, session.CurrentStep)
|
|
c.JSON(http.StatusOK, session)
|
|
}
|
|
|
|
// UpdateCharacterBasicInfo Request
|
|
type UpdateBasicInfoRequest struct {
|
|
Name string `json:"name" binding:"required,min=2,max=50"`
|
|
Geschlecht string `json:"geschlecht" binding:"required"`
|
|
Rasse string `json:"rasse" binding:"required"`
|
|
Typ string `json:"typ" binding:"required"`
|
|
Herkunft string `json:"herkunft" binding:"required"`
|
|
Stand string `json:"stand" binding:"required"`
|
|
Glaube string `json:"glaube"`
|
|
}
|
|
|
|
// UpdateCharacterBasicInfo speichert Grundinformationen
|
|
func UpdateCharacterBasicInfo(c *gin.Context) {
|
|
logger.Debug("UpdateCharacterBasicInfo aufgerufen")
|
|
|
|
sessionID := c.Param("sessionId")
|
|
userID := c.GetUint("userID")
|
|
logger.Debug("UpdateCharacterBasicInfo: SessionID = %s, UserID = %d", sessionID, userID)
|
|
|
|
if userID == 0 {
|
|
logger.Warn("UpdateCharacterBasicInfo: Unauthorized - UserID ist 0")
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
|
return
|
|
}
|
|
|
|
var request UpdateBasicInfoRequest
|
|
if err := c.ShouldBindJSON(&request); err != nil {
|
|
logger.Error("UpdateCharacterBasicInfo: Ungültige Eingabedaten - %s", err.Error())
|
|
respondWithError(c, http.StatusBadRequest, "Ungültige Eingabedaten: "+err.Error())
|
|
return
|
|
}
|
|
|
|
logger.Debug("UpdateCharacterBasicInfo: Request-Daten - Name: %s, Geschlecht: %s, Rasse: %s, Typ: %s, Herkunft: %s, Stand: %s, Glaube: %s",
|
|
request.Name, request.Geschlecht, request.Rasse, request.Typ, request.Herkunft, request.Stand, request.Glaube)
|
|
|
|
// Session aus Datenbank laden
|
|
logger.Debug("UpdateCharacterBasicInfo: Lade Session aus Datenbank...")
|
|
var session models.CharacterCreationSession
|
|
err := database.DB.Where("id = ? AND user_id = ?", sessionID, userID).First(&session).Error
|
|
if err != nil {
|
|
logger.Error("UpdateCharacterBasicInfo: Session nicht gefunden - SessionID: %s, UserID: %d, Error: %s",
|
|
sessionID, userID, err.Error())
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Session not found"})
|
|
return
|
|
}
|
|
|
|
logger.Debug("UpdateCharacterBasicInfo: Aktueller Session-Status - Step: %d, Name: %s",
|
|
session.CurrentStep, session.Name)
|
|
|
|
// Grundinformationen aktualisieren
|
|
session.Name = request.Name
|
|
session.Geschlecht = request.Geschlecht
|
|
session.Rasse = request.Rasse
|
|
session.Typ = request.Typ
|
|
session.Herkunft = request.Herkunft
|
|
session.Stand = request.Stand
|
|
session.Glaube = request.Glaube
|
|
session.CurrentStep = 2
|
|
session.UpdatedAt = time.Now()
|
|
|
|
logger.Debug("UpdateCharacterBasicInfo: Session aktualisiert, setze CurrentStep auf 2")
|
|
|
|
// Session in Datenbank aktualisieren
|
|
logger.Debug("UpdateCharacterBasicInfo: Speichere Session in Datenbank...")
|
|
err = database.DB.Save(&session).Error
|
|
if err != nil {
|
|
logger.Error("UpdateCharacterBasicInfo: Fehler beim Speichern der Session: %s", err.Error())
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update session"})
|
|
return
|
|
}
|
|
|
|
logger.Info("UpdateCharacterBasicInfo: Grundinformationen erfolgreich gespeichert - SessionID: %s, Name: %s",
|
|
sessionID, request.Name)
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "Grundinformationen gespeichert",
|
|
"session_id": sessionID,
|
|
"current_step": 2,
|
|
})
|
|
}
|
|
|
|
// UpdateAttributesRequest
|
|
type UpdateAttributesRequest struct {
|
|
ST int `json:"st" binding:"required,min=1,max=100"` // Stärke
|
|
GS int `json:"gs" binding:"required,min=1,max=100"` // Geschicklichkeit
|
|
GW int `json:"gw" binding:"required,min=1,max=100"` // Gewandtheit
|
|
KO int `json:"ko" binding:"required,min=1,max=100"` // Konstitution
|
|
IN int `json:"in" binding:"required,min=1,max=100"` // Intelligenz
|
|
ZT int `json:"zt" binding:"required,min=1,max=100"` // Zaubertalent
|
|
AU int `json:"au" binding:"required,min=1,max=100"` // Ausstrahlung
|
|
}
|
|
|
|
// UpdateCharacterAttributes speichert Grundwerte
|
|
func UpdateCharacterAttributes(c *gin.Context) {
|
|
logger.Debug("UpdateCharacterAttributes aufgerufen")
|
|
|
|
sessionID := c.Param("sessionId")
|
|
userID := c.GetUint("userID")
|
|
logger.Debug("UpdateCharacterAttributes: SessionID = %s, UserID = %d", sessionID, userID)
|
|
|
|
if userID == 0 {
|
|
logger.Warn("UpdateCharacterAttributes: Unauthorized - UserID ist 0")
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
|
return
|
|
}
|
|
|
|
var request UpdateAttributesRequest
|
|
if err := c.ShouldBindJSON(&request); err != nil {
|
|
logger.Error("UpdateCharacterAttributes: Ungültige Attributswerte - %s", err.Error())
|
|
respondWithError(c, http.StatusBadRequest, "Ungültige Attributswerte: "+err.Error())
|
|
return
|
|
}
|
|
|
|
logger.Debug("UpdateCharacterAttributes: Attribute - ST:%d GS:%d GW:%d KO:%d IN:%d ZT:%d AU:%d",
|
|
request.ST, request.GS, request.GW, request.KO, request.IN, request.ZT, request.AU)
|
|
|
|
// Session aus Datenbank laden
|
|
logger.Debug("UpdateCharacterAttributes: Lade Session aus Datenbank...")
|
|
var session models.CharacterCreationSession
|
|
err := database.DB.Where("id = ? AND user_id = ?", sessionID, userID).First(&session).Error
|
|
if err != nil {
|
|
logger.Error("UpdateCharacterAttributes: Session nicht gefunden - SessionID: %s, UserID: %d, Error: %s",
|
|
sessionID, userID, err.Error())
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Session not found"})
|
|
return
|
|
}
|
|
|
|
logger.Debug("UpdateCharacterAttributes: Session geladen - CurrentStep: %d", session.CurrentStep)
|
|
|
|
// Attribute aktualisieren
|
|
session.Attributes = models.AttributesData{
|
|
ST: request.ST,
|
|
GS: request.GS,
|
|
GW: request.GW,
|
|
KO: request.KO,
|
|
IN: request.IN,
|
|
ZT: request.ZT,
|
|
AU: request.AU,
|
|
}
|
|
session.CurrentStep = 3
|
|
session.UpdatedAt = time.Now()
|
|
|
|
logger.Debug("UpdateCharacterAttributes: Attribute gesetzt, CurrentStep auf 3 aktualisiert")
|
|
|
|
// Session in Datenbank aktualisieren
|
|
logger.Debug("UpdateCharacterAttributes: Speichere Session in Datenbank...")
|
|
err = database.DB.Save(&session).Error
|
|
if err != nil {
|
|
logger.Error("UpdateCharacterAttributes: Fehler beim Speichern der Session: %s", err.Error())
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update session"})
|
|
return
|
|
}
|
|
|
|
logger.Info("UpdateCharacterAttributes: Grundwerte erfolgreich gespeichert - SessionID: %s", sessionID)
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "Grundwerte gespeichert",
|
|
"session_id": sessionID,
|
|
"current_step": 3,
|
|
})
|
|
}
|
|
|
|
// UpdateDerivedValuesRequest
|
|
type UpdateDerivedValuesRequest struct {
|
|
PA int `json:"pa" binding:"required,min=1,max=100"` // Persönliche Ausstrahlung
|
|
WK int `json:"wk" binding:"required,min=1,max=100"` // Willenskraft
|
|
LP_Max int `json:"lp_max" binding:"required,min=1,max=50"` // Lebenspunkte Maximum
|
|
AP_Max int `json:"ap_max" binding:"required,min=1,max=200"` // Abenteuerpunkte Maximum
|
|
B_Max int `json:"b_max" binding:"required,min=1,max=50"` // Belastung Maximum
|
|
ResistenzKoerper int `json:"resistenz_koerper" binding:"required,min=1,max=20"` // Resistenz Körper
|
|
ResistenzGeist int `json:"resistenz_geist" binding:"required,min=1,max=20"` // Resistenz Geist
|
|
ResistenzBonusKoerper int `json:"resistenz_bonus_koerper" binding:"min=-5,max=5"` // Resistenz Bonus Körper
|
|
ResistenzBonusGeist int `json:"resistenz_bonus_geist" binding:"min=-5,max=5"` // Resistenz Bonus Geist
|
|
Abwehr int `json:"abwehr" binding:"required,min=1,max=20"` // Abwehr
|
|
AbwehrBonus int `json:"abwehr_bonus" binding:"min=-5,max=5"` // Abwehr Bonus
|
|
AusdauerBonus int `json:"ausdauer_bonus" binding:"min=-50,max=50"` // Ausdauer Bonus
|
|
AngriffsBonus int `json:"angriffs_bonus" binding:"min=-5,max=5"` // Angriffs Bonus
|
|
Zaubern int `json:"zaubern" binding:"required,min=1,max=20"` // Zaubern
|
|
ZauberBonus int `json:"zauber_bonus" binding:"min=-5,max=5"` // Zauber Bonus
|
|
Raufen int `json:"raufen" binding:"required,min=1,max=20"` // Raufen
|
|
SchadensBonus int `json:"schadens_bonus" binding:"min=-10,max=10"` // Schadens Bonus
|
|
SG int `json:"sg" binding:"min=0,max=50"` // Schicksalsgunst
|
|
GG int `json:"gg" binding:"min=0,max=50"` // Göttliche Gnade
|
|
GP int `json:"gp" binding:"min=0,max=50"` // Glückspunkte
|
|
}
|
|
|
|
// UpdateCharacterDerivedValues speichert abgeleitete Werte
|
|
func UpdateCharacterDerivedValues(c *gin.Context) {
|
|
logger.Debug("UpdateCharacterDerivedValues aufgerufen")
|
|
|
|
sessionID := c.Param("sessionId")
|
|
userID := c.GetUint("userID")
|
|
logger.Debug("UpdateCharacterDerivedValues: SessionID = %s, UserID = %d", sessionID, userID)
|
|
|
|
if userID == 0 {
|
|
logger.Warn("UpdateCharacterDerivedValues: Unauthorized - UserID ist 0")
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
|
return
|
|
}
|
|
|
|
var request UpdateDerivedValuesRequest
|
|
if err := c.ShouldBindJSON(&request); err != nil {
|
|
logger.Error("UpdateCharacterDerivedValues: Ungültige abgeleitete Werte - %s", err.Error())
|
|
respondWithError(c, http.StatusBadRequest, "Ungültige abgeleitete Werte: "+err.Error())
|
|
return
|
|
}
|
|
|
|
logger.Debug("UpdateCharacterDerivedValues: Werte - LP_Max:%d AP_Max:%d B_Max:%d PA:%d WK:%d SG:%d GG:%d GP:%d",
|
|
request.LP_Max, request.AP_Max, request.B_Max, request.PA, request.WK, request.SG, request.GG, request.GP)
|
|
|
|
// Session aus Datenbank laden
|
|
logger.Debug("UpdateCharacterDerivedValues: Lade Session aus Datenbank...")
|
|
var session models.CharacterCreationSession
|
|
err := database.DB.Where("id = ? AND user_id = ?", sessionID, userID).First(&session).Error
|
|
if err != nil {
|
|
logger.Error("UpdateCharacterDerivedValues: Session nicht gefunden - SessionID: %s, UserID: %d, Error: %s",
|
|
sessionID, userID, err.Error())
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Session not found"})
|
|
return
|
|
}
|
|
|
|
logger.Debug("UpdateCharacterDerivedValues: Session geladen - CurrentStep: %d", session.CurrentStep)
|
|
|
|
// Abgeleitete Werte aktualisieren
|
|
session.DerivedValues = models.DerivedValuesData{
|
|
PA: request.PA,
|
|
WK: request.WK,
|
|
LPMax: request.LP_Max,
|
|
APMax: request.AP_Max,
|
|
BMax: request.B_Max,
|
|
ResistenzKoerper: request.ResistenzKoerper,
|
|
ResistenzGeist: request.ResistenzGeist,
|
|
ResistenzBonusKoerper: request.ResistenzBonusKoerper,
|
|
ResistenzBonusGeist: request.ResistenzBonusGeist,
|
|
Abwehr: request.Abwehr,
|
|
AbwehrBonus: request.AbwehrBonus,
|
|
AusdauerBonus: request.AusdauerBonus,
|
|
AngriffsBonus: request.AngriffsBonus,
|
|
Zaubern: request.Zaubern,
|
|
ZauberBonus: request.ZauberBonus,
|
|
Raufen: request.Raufen,
|
|
SchadensBonus: request.SchadensBonus,
|
|
SG: request.SG,
|
|
GG: request.GG,
|
|
GP: request.GP,
|
|
}
|
|
session.CurrentStep = 4
|
|
session.UpdatedAt = time.Now()
|
|
|
|
logger.Debug("UpdateCharacterDerivedValues: Abgeleitete Werte gesetzt, CurrentStep auf 4 aktualisiert")
|
|
|
|
// Session in Datenbank aktualisieren
|
|
logger.Debug("UpdateCharacterDerivedValues: Speichere Session in Datenbank...")
|
|
err = database.DB.Save(&session).Error
|
|
if err != nil {
|
|
logger.Error("UpdateCharacterDerivedValues: Fehler beim Speichern der Session: %s", err.Error())
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update session"})
|
|
return
|
|
}
|
|
|
|
logger.Info("UpdateCharacterDerivedValues: Abgeleitete Werte erfolgreich gespeichert - SessionID: %s", sessionID)
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "Abgeleitete Werte gespeichert",
|
|
"session_id": sessionID,
|
|
"current_step": 4,
|
|
})
|
|
}
|
|
|
|
// UpdateSkillsRequest
|
|
type UpdateSkillsRequest struct {
|
|
Skills models.CharacterCreationSkills `json:"skills"`
|
|
Spells models.CharacterCreationSpells `json:"spells"`
|
|
SkillPoints models.SkillPointsData `json:"skill_points"` // Verbleibende Punkte pro Kategorie
|
|
}
|
|
|
|
// UpdateCharacterSkills speichert Fertigkeiten und Zauber
|
|
func UpdateCharacterSkills(c *gin.Context) {
|
|
logger.Debug("UpdateCharacterSkills aufgerufen")
|
|
|
|
sessionID := c.Param("sessionId")
|
|
userID := c.GetUint("userID")
|
|
logger.Debug("UpdateCharacterSkills: SessionID = %s, UserID = %d", sessionID, userID)
|
|
|
|
if userID == 0 {
|
|
logger.Warn("UpdateCharacterSkills: Unauthorized - UserID ist 0")
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
|
return
|
|
}
|
|
|
|
var request UpdateSkillsRequest
|
|
if err := c.ShouldBindJSON(&request); err != nil {
|
|
logger.Error("UpdateCharacterSkills: Ungültige Fertigkeitsdaten - %s", err.Error())
|
|
respondWithError(c, http.StatusBadRequest, "Ungültige Fertigkeitsdaten: "+err.Error())
|
|
return
|
|
}
|
|
|
|
logger.Debug("UpdateCharacterSkills: Skills-Anzahl: %d, Spells-Anzahl: %d",
|
|
len(request.Skills), len(request.Spells))
|
|
|
|
// Session aus Datenbank laden
|
|
logger.Debug("UpdateCharacterSkills: Lade Session aus Datenbank...")
|
|
var session models.CharacterCreationSession
|
|
err := database.DB.Where("id = ? AND user_id = ?", sessionID, userID).First(&session).Error
|
|
if err != nil {
|
|
logger.Error("UpdateCharacterSkills: Session nicht gefunden - SessionID: %s, UserID: %d, Error: %s",
|
|
sessionID, userID, err.Error())
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Session not found"})
|
|
return
|
|
}
|
|
|
|
logger.Debug("UpdateCharacterSkills: Session geladen - CurrentStep: %d", session.CurrentStep)
|
|
|
|
// Fertigkeiten und Zauber aktualisieren
|
|
session.Skills = request.Skills
|
|
session.Spells = request.Spells
|
|
session.SkillPoints = request.SkillPoints
|
|
session.CurrentStep = 5
|
|
session.UpdatedAt = time.Now()
|
|
|
|
logger.Debug("UpdateCharacterSkills: Skills/Spells gesetzt, CurrentStep auf 5 aktualisiert")
|
|
|
|
// Session in Datenbank aktualisieren
|
|
logger.Debug("UpdateCharacterSkills: Speichere Session in Datenbank...")
|
|
err = database.DB.Save(&session).Error
|
|
if err != nil {
|
|
logger.Error("UpdateCharacterSkills: Fehler beim Speichern der Session: %s", err.Error())
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update session"})
|
|
return
|
|
}
|
|
|
|
logger.Info("UpdateCharacterSkills: Fertigkeiten erfolgreich gespeichert - SessionID: %s, Skills: %d, Spells: %d",
|
|
sessionID, len(request.Skills), len(request.Spells))
|
|
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) {
|
|
logger.Debug("FinalizeCharacterCreation aufgerufen")
|
|
|
|
sessionID := c.Param("sessionId")
|
|
userID := c.GetUint("userID")
|
|
logger.Debug("FinalizeCharacterCreation: SessionID = %s, UserID = %d", sessionID, userID)
|
|
|
|
if userID == 0 {
|
|
logger.Warn("FinalizeCharacterCreation: Unauthorized - UserID ist 0")
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
|
return
|
|
}
|
|
|
|
// Session laden
|
|
logger.Debug("FinalizeCharacterCreation: Lade Session aus Datenbank...")
|
|
var session models.CharacterCreationSession
|
|
err := database.DB.Where("id = ? AND user_id = ?", sessionID, userID).First(&session).Error
|
|
if err != nil {
|
|
logger.Error("FinalizeCharacterCreation: Session nicht gefunden - SessionID: %s, UserID: %d, Error: %s",
|
|
sessionID, userID, err.Error())
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Session not found"})
|
|
return
|
|
}
|
|
|
|
logger.Debug("FinalizeCharacterCreation: Session geladen - Name: %s, CurrentStep: %d",
|
|
session.Name, session.CurrentStep)
|
|
|
|
// Session validieren
|
|
if session.CurrentStep < 5 {
|
|
logger.Warn("FinalizeCharacterCreation: Charakter-Erstellung unvollständig - CurrentStep: %d (erwartet: 5)",
|
|
session.CurrentStep)
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Character creation not complete"})
|
|
return
|
|
}
|
|
|
|
logger.Debug("FinalizeCharacterCreation: Erstelle Charakter-Struktur...")
|
|
// Character erstellen
|
|
char := models.Char{
|
|
BamortBase: models.BamortBase{
|
|
Name: session.Name,
|
|
},
|
|
UserID: userID,
|
|
Rasse: session.Rasse,
|
|
Typ: session.Typ,
|
|
Gender: session.Geschlecht,
|
|
SocialClass: session.Stand,
|
|
Herkunft: session.Herkunft,
|
|
Glaube: session.Glaube,
|
|
Public: false, // Default to private
|
|
Grad: 1, // Default starting grade
|
|
|
|
// Static derived values (can increase with grade)
|
|
ResistenzKoerper: session.DerivedValues.ResistenzKoerper,
|
|
ResistenzGeist: session.DerivedValues.ResistenzGeist,
|
|
Abwehr: session.DerivedValues.Abwehr,
|
|
Zaubern: session.DerivedValues.Zaubern,
|
|
Raufen: session.DerivedValues.Raufen,
|
|
|
|
// 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,
|
|
},
|
|
Vermoegen: models.Vermoegen{
|
|
BamortCharTrait: models.BamortCharTrait{
|
|
UserID: userID,
|
|
},
|
|
Goldstuecke: 80,
|
|
},
|
|
|
|
// Bennies (Glückspunkte, etc.)
|
|
Bennies: models.Bennies{
|
|
BamortCharTrait: models.BamortCharTrait{
|
|
UserID: userID,
|
|
},
|
|
Gg: session.DerivedValues.GG,
|
|
Gp: session.DerivedValues.GP,
|
|
Sg: session.DerivedValues.SG,
|
|
},
|
|
}
|
|
|
|
// Eigenschaften (Attribute) hinzufügen
|
|
char.Eigenschaften = []models.Eigenschaft{
|
|
{UserID: userID, Name: "St", Value: session.Attributes.ST},
|
|
{UserID: userID, Name: "Gs", Value: session.Attributes.GS},
|
|
{UserID: userID, Name: "Gw", Value: session.Attributes.GW},
|
|
{UserID: userID, Name: "Ko", Value: session.Attributes.KO},
|
|
{UserID: userID, Name: "In", Value: session.Attributes.IN},
|
|
{UserID: userID, Name: "Zt", Value: session.Attributes.ZT},
|
|
{UserID: userID, Name: "Au", Value: session.Attributes.AU},
|
|
{UserID: userID, Name: "pA", Value: session.DerivedValues.PA}, // PA kommt aus derived values
|
|
{UserID: userID, Name: "Wk", Value: session.DerivedValues.WK}, // WK kommt aus derived values
|
|
}
|
|
|
|
logger.Debug("FinalizeCharacterCreation: Charakter-Struktur erstellt mit %d Eigenschaften",
|
|
len(char.Eigenschaften))
|
|
|
|
// Fertigkeiten aus der Session übertragen
|
|
logger.Debug("FinalizeCharacterCreation: Übertrage %d Fertigkeiten", len(session.Skills))
|
|
for _, skill := range session.Skills {
|
|
// Suche den Initialwert der Fertigkeit aus der Datenbank
|
|
dbSkill := models.Skill{}
|
|
err := dbSkill.First(skill.Name)
|
|
if err != nil {
|
|
logger.Warn("FinalizeCharacterCreation: Konnte Fertigkeit '%s' nicht in der Datenbank finden, verwende Level %d", skill.Name, skill.Level)
|
|
}
|
|
|
|
// Verwende den Initialwert aus der Datenbank wenn verfügbar, sonst fallback auf Session-Level
|
|
initialValue := skill.Level // Fallback
|
|
if err == nil {
|
|
initialValue = dbSkill.Initialwert
|
|
logger.Debug("FinalizeCharacterCreation: Verwende Initialwert %d für Fertigkeit '%s'", initialValue, skill.Name)
|
|
}
|
|
|
|
// Unterscheide zwischen normalen Fertigkeiten und Waffenfertigkeiten
|
|
if skill.Category == "Waffen" || skill.Category == "waffen" {
|
|
dbWPSkill := models.WeaponSkill{}
|
|
err := dbWPSkill.First(skill.Name)
|
|
if err != nil {
|
|
logger.Warn("FinalizeCharacterCreation: Konnte WaffenFertigkeit '%s' nicht in der Datenbank finden, verwende Level %d", skill.Name, skill.Level)
|
|
}
|
|
// Verwende den Initialwert aus der Datenbank wenn verfügbar, sonst fallback auf Session-Level
|
|
initialValue := skill.Level // Fallback
|
|
if err == nil {
|
|
initialValue = dbWPSkill.Initialwert
|
|
logger.Debug("FinalizeCharacterCreation: Verwende Initialwert %d für Fertigkeit '%s'", initialValue, skill.Name)
|
|
}
|
|
// Waffenfertigkeit
|
|
weaponSkill := models.SkWaffenfertigkeit{
|
|
SkFertigkeit: models.SkFertigkeit{
|
|
BamortCharTrait: models.BamortCharTrait{
|
|
BamortBase: models.BamortBase{
|
|
Name: skill.Name,
|
|
},
|
|
UserID: userID,
|
|
},
|
|
Fertigkeitswert: initialValue,
|
|
Improvable: true,
|
|
Category: skill.Category,
|
|
},
|
|
}
|
|
char.Waffenfertigkeiten = append(char.Waffenfertigkeiten, weaponSkill)
|
|
} else {
|
|
// Normale Fertigkeit
|
|
normalSkill := models.SkFertigkeit{
|
|
BamortCharTrait: models.BamortCharTrait{
|
|
BamortBase: models.BamortBase{
|
|
Name: skill.Name,
|
|
},
|
|
CharacterID: char.ID,
|
|
UserID: userID,
|
|
},
|
|
Fertigkeitswert: initialValue,
|
|
Improvable: true,
|
|
Category: skill.Category,
|
|
}
|
|
char.Fertigkeiten = append(char.Fertigkeiten, normalSkill)
|
|
}
|
|
}
|
|
|
|
// Zauber aus der Session übertragen
|
|
logger.Debug("FinalizeCharacterCreation: Übertrage %d Zauber", len(session.Spells))
|
|
for _, spell := range session.Spells {
|
|
zauber := models.SkZauber{
|
|
BamortCharTrait: models.BamortCharTrait{
|
|
BamortBase: models.BamortBase{
|
|
Name: spell.Name,
|
|
},
|
|
CharacterID: char.ID,
|
|
UserID: userID,
|
|
},
|
|
}
|
|
char.Zauber = append(char.Zauber, zauber)
|
|
}
|
|
|
|
logger.Debug("FinalizeCharacterCreation: Charakter vollständig erstellt - %d Fertigkeiten, %d Waffenfertigkeiten, %d Zauber",
|
|
len(char.Fertigkeiten), len(char.Waffenfertigkeiten), len(char.Zauber))
|
|
|
|
// Character in Datenbank speichern
|
|
logger.Debug("FinalizeCharacterCreation: Speichere Charakter in Datenbank...")
|
|
err = char.Create()
|
|
if err != nil {
|
|
logger.Error("FinalizeCharacterCreation: Fehler beim Erstellen des Charakters: %s", err.Error())
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create character: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
logger.Debug("FinalizeCharacterCreation: Charakter erfolgreich erstellt mit ID: %d", char.ID)
|
|
|
|
// Session löschen
|
|
logger.Debug("FinalizeCharacterCreation: Lösche Session aus Datenbank...")
|
|
database.DB.Delete(&session)
|
|
|
|
logger.Info("FinalizeCharacterCreation: Charakter-Erstellung abgeschlossen - CharacterID: %d, SessionID: %s, Name: %s",
|
|
char.ID, sessionID, session.Name)
|
|
|
|
c.JSON(http.StatusCreated, gin.H{
|
|
"message": "Charakter erfolgreich erstellt",
|
|
"character_id": char.ID,
|
|
"session_id": sessionID,
|
|
})
|
|
}
|
|
|
|
// DeleteCharacterSession löscht eine Session
|
|
func DeleteCharacterSession(c *gin.Context) {
|
|
logger.Debug("DeleteCharacterSession aufgerufen")
|
|
|
|
sessionID := c.Param("sessionId")
|
|
userID := c.GetUint("userID")
|
|
logger.Debug("DeleteCharacterSession: SessionID = %s, UserID = %d", sessionID, userID)
|
|
|
|
if userID == 0 {
|
|
logger.Warn("DeleteCharacterSession: Unauthorized - UserID ist 0")
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
|
return
|
|
}
|
|
|
|
// Session aus Datenbank löschen (nur eigene Sessions)
|
|
logger.Debug("DeleteCharacterSession: Lösche Session aus Datenbank...")
|
|
result := database.DB.Where("id = ? AND user_id = ?", sessionID, userID).Delete(&models.CharacterCreationSession{})
|
|
if result.Error != nil {
|
|
logger.Error("DeleteCharacterSession: Fehler beim Löschen der Session: %s", result.Error.Error())
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete session"})
|
|
return
|
|
}
|
|
|
|
if result.RowsAffected == 0 {
|
|
logger.Warn("DeleteCharacterSession: Session nicht gefunden oder bereits gelöscht - SessionID: %s, UserID: %d",
|
|
sessionID, userID)
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Session not found"})
|
|
return
|
|
}
|
|
|
|
logger.Info("DeleteCharacterSession: Session erfolgreich gelöscht - SessionID: %s, UserID: %d, RowsAffected: %d",
|
|
sessionID, userID, result.RowsAffected)
|
|
|
|
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) {
|
|
// Get game system from query parameter, default to GameSystemId: 1
|
|
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)
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"classes": classNames})
|
|
}
|
|
|
|
// GetOrigins gibt verfügbare Herkünfte zurück
|
|
func GetOrigins(c *gin.Context) {
|
|
// TODO: Aus Datenbank laden
|
|
origins := []string{
|
|
"Alba", "Aran", "Buluga", "Chryseia",
|
|
"Eschar", "Fuardain", "Ikenga", "KanThaiPan", "Küstenstaaten",
|
|
"Medjis", "Moravod", "Nahuatlan", "Rawindra", "Scharidis",
|
|
"Tegarisch Steppe", "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
|
|
}
|
|
|
|
// Get game system from query parameter, default to GameSystemId: 1
|
|
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: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
// Extract belief names and filter by query
|
|
var allBeliefs []string
|
|
for _, belief := range believes {
|
|
allBeliefs = append(allBeliefs, belief.Name)
|
|
}
|
|
|
|
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"`
|
|
}
|
|
|
|
// LearningPointsData repräsentiert die Lernpunkte und typischen Fertigkeiten einer Charakterklasse
|
|
type LearningPointsData struct {
|
|
ClassName string `json:"class_name"`
|
|
ClassCode string `json:"class_code"`
|
|
LearningPoints map[string]int `json:"learning_points"` // Kategorie -> Lernpunkte
|
|
WeaponPoints int `json:"weapon_points"` // Waffenlernpunkte
|
|
SpellPoints int `json:"spell_points"` // Zauberlerneinheiten (falls vorhanden)
|
|
TypicalSkills []TypicalSkill `json:"typical_skills"` // Typische Fertigkeiten
|
|
TypicalSpells []string `json:"typical_spells"` // Typische Zauber (falls vorhanden)
|
|
}
|
|
|
|
// TypicalSkill repräsentiert eine typische Fertigkeit mit Bonus
|
|
type TypicalSkill struct {
|
|
Name string `json:"name"`
|
|
Bonus int `json:"bonus"`
|
|
Attribute string `json:"attribute"` // Zugehöriges Attribut (z.B. "Gs", "In")
|
|
Notes string `json:"notes"` // Zusätzliche Notizen
|
|
}
|
|
|
|
// GetCharacterClassLearningPoints gibt die Lernpunkte und typischen Fertigkeiten für eine Charakterklasse zurück
|
|
func GetCharacterClassLearningPoints(c *gin.Context) {
|
|
className := c.Query("class")
|
|
if className == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Charakterklassen-Name ist erforderlich (Parameter 'class')"})
|
|
return
|
|
}
|
|
|
|
stand := c.Query("stand") // Optional: Unfreie, Volk, Mittelschicht, Adel
|
|
|
|
// Hole die Lernpunkte-Daten für die Klasse
|
|
learningData, err := getLearningPointsForClass(className, stand)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Charakterklasse nicht gefunden oder nicht unterstützt: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, learningData)
|
|
}
|
|
|
|
// getLearningPointsForClass gibt die Lernpunkte-Daten für eine bestimmte Charakterklasse zurück
|
|
func getLearningPointsForClass(className string, stand string) (*LearningPointsData, error) {
|
|
// Get character class from database by name or code
|
|
var charClass models.CharacterClass
|
|
result := database.DB.Where("name = ? OR code = ?", className, className).First(&charClass)
|
|
if result.Error != nil {
|
|
return nil, fmt.Errorf("character class not found: %s", className)
|
|
}
|
|
|
|
// Get learning points from database
|
|
learningPoints, err := models.GetLearningPointsForClass(charClass.ID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get learning points: %w", err)
|
|
}
|
|
|
|
// Convert to map format
|
|
learningPointsMap := make(map[string]int)
|
|
for _, lp := range learningPoints {
|
|
learningPointsMap[lp.SkillCategory.Name] = lp.Points
|
|
}
|
|
|
|
// Get spell points if applicable
|
|
spellPoints, err := models.GetSpellPointsForClass(charClass.ID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get spell points: %w", err)
|
|
}
|
|
|
|
// Get typical skills
|
|
typicalSkillsDB, err := models.GetTypicalSkillsForClass(charClass.ID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get typical skills: %w", err)
|
|
}
|
|
|
|
// Convert typical skills
|
|
typicalSkills := make([]TypicalSkill, len(typicalSkillsDB))
|
|
for i, ts := range typicalSkillsDB {
|
|
typicalSkills[i] = TypicalSkill{
|
|
Name: ts.Skill.Name,
|
|
Bonus: ts.Bonus,
|
|
Attribute: ts.Attribute,
|
|
Notes: ts.Notes,
|
|
}
|
|
}
|
|
|
|
// Get typical spells
|
|
typicalSpellsDB, err := models.GetTypicalSpellsForClass(charClass.ID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get typical spells: %w", err)
|
|
}
|
|
|
|
// Convert typical spells
|
|
typicalSpells := make([]string, len(typicalSpellsDB))
|
|
for i, ts := range typicalSpellsDB {
|
|
spellName := ts.Spell.Name
|
|
if ts.Notes != "" {
|
|
spellName = ts.Notes // Use notes for special cases like "beliebig außer..."
|
|
}
|
|
typicalSpells[i] = spellName
|
|
}
|
|
|
|
// Build the response data
|
|
data := &LearningPointsData{
|
|
ClassName: charClass.Name,
|
|
ClassCode: charClass.Code,
|
|
LearningPoints: learningPointsMap,
|
|
SpellPoints: spellPoints.SpellPoints,
|
|
TypicalSkills: typicalSkills,
|
|
TypicalSpells: typicalSpells,
|
|
}
|
|
|
|
// Bonus-Lernpunkte basierend auf Stand hinzufügen
|
|
if stand != "" {
|
|
standBonus := getStandBonusPoints(stand)
|
|
// Füge die Stand-Bonuspunkte zu den normalen Lernpunkten hinzu
|
|
for category, bonus := range standBonus {
|
|
if currentPoints, exists := data.LearningPoints[category]; exists {
|
|
data.LearningPoints[category] = currentPoints + bonus
|
|
} else {
|
|
// Falls die Kategorie noch nicht existiert, füge sie hinzu
|
|
data.LearningPoints[category] = bonus
|
|
}
|
|
}
|
|
// Speichere die Stand-Bonuspunkte auch separat für Referenz
|
|
//data.StandPoints = standBonus
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
|
|
// getStandBonusPoints gibt die Bonus-Lernpunkte basierend auf dem Stand zurück
|
|
func getStandBonusPoints(social_class string) map[string]int {
|
|
bonusPoints, err := gsmaster.GetSocialClassBonusPoints(social_class)
|
|
if err != nil {
|
|
logger.Warn("Fehler beim Laden der Stand-Bonuspunkte: %s", err.Error())
|
|
return make(map[string]int)
|
|
}
|
|
|
|
// Fallback for missing lookup data in test fixtures
|
|
if len(bonusPoints) == 0 {
|
|
switch social_class {
|
|
case "Unfreie":
|
|
return map[string]int{"Halbwelt": 2}
|
|
case "Volk":
|
|
return map[string]int{"Alltag": 2}
|
|
case "Mittelschicht":
|
|
return map[string]int{"Wissen": 2}
|
|
case "Adel":
|
|
return map[string]int{"Sozial": 2}
|
|
}
|
|
}
|
|
return bonusPoints
|
|
}
|
|
|
|
// GetDatasheetOptions returns all available options for datasheet select boxes
|
|
func GetDatasheetOptions(c *gin.Context) {
|
|
logger.Debug("GetDatasheetOptions aufgerufen")
|
|
|
|
gameSystemIDStr := c.DefaultQuery("game_system_id", "")
|
|
var gameSystemID uint
|
|
if gameSystemIDStr != "" {
|
|
parsed, err := strconv.ParseUint(gameSystemIDStr, 10, 64)
|
|
if err != nil {
|
|
respondWithError(c, http.StatusBadRequest, "Invalid game_system_id")
|
|
return
|
|
}
|
|
gameSystemID = uint(parsed)
|
|
}
|
|
|
|
gs := models.GetGameSystem(gameSystemID, "")
|
|
|
|
characterID := c.Param("id")
|
|
|
|
// Load character to get their weapon skills
|
|
var character models.Char
|
|
err := character.FirstID(characterID)
|
|
if err != nil {
|
|
logger.Error("GetDatasheetOptions: Charakter nicht gefunden - ID: %s, Error: %s", characterID, err.Error())
|
|
respondWithError(c, http.StatusNotFound, "Character not found")
|
|
return
|
|
}
|
|
|
|
// Get all available weapons from database
|
|
var allWeapons []models.Weapon
|
|
if err := database.DB.Find(&allWeapons).Error; err != nil {
|
|
logger.Error("GetDatasheetOptions: Fehler beim Laden der Waffen: %s", err.Error())
|
|
respondWithError(c, http.StatusInternalServerError, "Failed to load weapons")
|
|
return
|
|
}
|
|
|
|
// Filter weapons based on character's weapon skills
|
|
characterWeaponSkills := make(map[string]bool)
|
|
for _, skill := range character.Waffenfertigkeiten {
|
|
characterWeaponSkills[skill.Name] = true
|
|
}
|
|
|
|
availableWeapons := []string{}
|
|
for _, weapon := range allWeapons {
|
|
if characterWeaponSkills[weapon.SkillRequired] {
|
|
availableWeapons = append(availableWeapons, weapon.Name)
|
|
}
|
|
}
|
|
|
|
// Load misc lookup data from database
|
|
genders, err := gsmaster.GetMiscLookupByKeyForSystem("gender", gs.ID)
|
|
if err != nil {
|
|
logger.Error("GetDatasheetOptions: Fehler beim Laden der Geschlechter: %s", err.Error())
|
|
respondWithError(c, http.StatusInternalServerError, "Failed to load genders")
|
|
return
|
|
}
|
|
|
|
races, err := gsmaster.GetMiscLookupByKeyForSystem("races", gs.ID)
|
|
if err != nil {
|
|
logger.Error("GetDatasheetOptions: Fehler beim Laden der Rassen: %s", err.Error())
|
|
respondWithError(c, http.StatusInternalServerError, "Failed to load races")
|
|
return
|
|
}
|
|
|
|
origins, err := gsmaster.GetMiscLookupByKeyForSystem("origins", gs.ID)
|
|
if err != nil {
|
|
logger.Error("GetDatasheetOptions: Fehler beim Laden der Herkünfte: %s", err.Error())
|
|
respondWithError(c, http.StatusInternalServerError, "Failed to load origins")
|
|
return
|
|
}
|
|
|
|
socialClasses, err := gsmaster.GetMiscLookupByKeyForSystem("social_classes", gs.ID)
|
|
if err != nil {
|
|
logger.Error("GetDatasheetOptions: Fehler beim Laden der Stände: %s", err.Error())
|
|
respondWithError(c, http.StatusInternalServerError, "Failed to load social classes")
|
|
return
|
|
}
|
|
|
|
faiths, err := gsmaster.GetMiscLookupByKeyForSystem("faiths", gs.ID)
|
|
if err != nil {
|
|
logger.Error("GetDatasheetOptions: Fehler beim Laden der Glaubensrichtungen: %s", err.Error())
|
|
respondWithError(c, http.StatusInternalServerError, "Failed to load faiths")
|
|
return
|
|
}
|
|
|
|
handedness, err := gsmaster.GetMiscLookupByKeyForSystem("handedness", gs.ID)
|
|
if err != nil {
|
|
logger.Error("GetDatasheetOptions: Fehler beim Laden der Händigkeiten: %s", err.Error())
|
|
respondWithError(c, http.StatusInternalServerError, "Failed to load handedness")
|
|
return
|
|
}
|
|
|
|
// Convert to string arrays
|
|
genderValues := make([]string, len(genders))
|
|
for i, g := range genders {
|
|
genderValues[i] = g.Value
|
|
}
|
|
|
|
raceValues := make([]string, len(races))
|
|
for i, r := range races {
|
|
raceValues[i] = r.Value
|
|
}
|
|
|
|
originValues := make([]string, len(origins))
|
|
for i, o := range origins {
|
|
originValues[i] = o.Value
|
|
}
|
|
|
|
socialClassValues := make([]string, len(socialClasses))
|
|
for i, sc := range socialClasses {
|
|
socialClassValues[i] = sc.Value
|
|
}
|
|
|
|
faithValues := make([]string, len(faiths))
|
|
for i, f := range faiths {
|
|
faithValues[i] = f.Value
|
|
}
|
|
|
|
handednessValues := make([]string, len(handedness))
|
|
for i, h := range handedness {
|
|
handednessValues[i] = h.Value
|
|
}
|
|
|
|
// Return all options
|
|
options := gin.H{
|
|
"gender": genderValues,
|
|
"races": raceValues,
|
|
"origins": originValues,
|
|
"social_classes": socialClassValues,
|
|
"faiths": faithValues,
|
|
"handedness": handednessValues,
|
|
"specializations": availableWeapons,
|
|
}
|
|
|
|
logger.Debug("GetDatasheetOptions: Erfolgreich geladen - %d verfügbare Waffen", len(availableWeapons))
|
|
c.JSON(http.StatusOK, options)
|
|
}
|