Charakter erstellung in 5 schritten

spätere Fortsetzung soll bis zu 14 Tage lang möglich sein. Rückwärts navigation durch die einzelenen Schritte sollen möglich sein
This commit is contained in:
2025-08-08 06:39:46 +02:00
parent ec445a891b
commit dc0e23a48c
15 changed files with 3178 additions and 12 deletions
+357
View File
@@ -6,6 +6,7 @@ import (
"bamort/models"
"strconv"
"strings"
"time"
"fmt"
"net/http"
@@ -2352,3 +2353,359 @@ func calculateSkillLearningCostsOld(skill models.Skill, character models.Char, r
return costResult.EP, costResult.GoldCost
}
// Character Creation Session Management
// CharacterCreationSession repräsentiert eine Charakter-Erstellungssession
type CharacterCreationSession struct {
ID string `json:"id" gorm:"primaryKey"`
UserID uint `json:"user_id" gorm:"index"`
Name string `json:"name"`
Rasse string `json:"rasse"`
Typ string `json:"typ"`
Herkunft string `json:"herkunft"`
Glaube string `json:"glaube"`
Attributes map[string]int `json:"attributes" gorm:"type:json"`
DerivedValues map[string]int `json:"derived_values" gorm:"type:json"`
Skills []CharacterCreationSkill `json:"skills" gorm:"type:json"`
Spells []CharacterCreationSpell `json:"spells" gorm:"type:json"`
SkillPoints map[string]int `json:"skill_points" gorm:"type:json"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
ExpiresAt time.Time `json:"expires_at"`
CurrentStep int `json:"current_step"` // 1=Basic, 2=Attributes, 3=Derived, 4=Skills
}
type CharacterCreationSkill struct {
Name string `json:"name"`
Level int `json:"level"`
Category string `json:"category"`
Cost int `json:"cost"`
}
type CharacterCreationSpell struct {
Name string `json:"name"`
Cost int `json:"cost"`
}
// CreateCharacterSession erstellt eine neue Charakter-Erstellungssession
func CreateCharacterSession(c *gin.Context) {
// TODO: UserID aus Authentication Context holen
userID := uint(1) // Placeholder
sessionID := fmt.Sprintf("char_create_%d_%d", userID, time.Now().Unix())
session := CharacterCreationSession{
ID: sessionID,
UserID: userID,
Attributes: make(map[string]int),
DerivedValues: make(map[string]int),
Skills: []CharacterCreationSkill{},
Spells: []CharacterCreationSpell{},
SkillPoints: make(map[string]int),
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
ExpiresAt: time.Now().AddDate(0, 0, 14), // 14 Tage
CurrentStep: 1,
}
// Session in Datenbank speichern (Dummy - würde in Session-Tabelle gespeichert)
// TODO: Implementiere Session-Speicherung in Redis oder Datenbank
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) {
// TODO: UserID aus Authentication Context holen
// userID := uint(1) // Placeholder - würde für Datenbank-Abfrage verwendet
// TODO: Sessions aus Datenbank/Redis laden
// Dummy-Implementierung mit Sample-Sessions
sessions := []gin.H{
{
"session_id": "char_create_1_1722888000",
"name": "Mein Magier",
"rasse": "Elf",
"typ": "Zauberer",
"current_step": 3,
"total_steps": 5,
"created_at": time.Now().AddDate(0, 0, -2),
"updated_at": time.Now().AddDate(0, 0, -1),
"expires_at": time.Now().AddDate(0, 0, 12),
"progress_text": "Abgeleitete Werte",
},
{
"session_id": "char_create_1_1722974400",
"name": "Kämpfer Draft",
"rasse": "Mensch",
"typ": "Krieger",
"current_step": 1,
"total_steps": 5,
"created_at": time.Now().AddDate(0, 0, -1),
"updated_at": time.Now().AddDate(0, 0, -1),
"expires_at": time.Now().AddDate(0, 0, 13),
"progress_text": "Grundinformationen",
},
}
c.JSON(http.StatusOK, gin.H{
"sessions": sessions,
"count": len(sessions),
})
}
// GetCharacterSession gibt Session-Daten zurück
func GetCharacterSession(c *gin.Context) {
sessionID := c.Param("sessionId")
// TODO: Session aus Datenbank/Redis laden
// Dummy-Response
session := CharacterCreationSession{
ID: sessionID,
UserID: 1,
CurrentStep: 1,
ExpiresAt: time.Now().AddDate(0, 0, 14),
}
c.JSON(http.StatusOK, session)
}
// UpdateCharacterBasicInfo Request
type UpdateBasicInfoRequest struct {
Name string `json:"name" binding:"required,min=2,max=50"`
Rasse string `json:"rasse" binding:"required"`
Typ string `json:"typ" binding:"required"`
Herkunft string `json:"herkunft" binding:"required"`
Glaube string `json:"glaube"`
}
// UpdateCharacterBasicInfo speichert Grundinformationen
func UpdateCharacterBasicInfo(c *gin.Context) {
sessionID := c.Param("sessionId")
var request UpdateBasicInfoRequest
if err := c.ShouldBindJSON(&request); err != nil {
respondWithError(c, http.StatusBadRequest, "Ungültige Eingabedaten: "+err.Error())
return
}
// TODO: Session aus Datenbank laden und aktualisieren
// Dummy-Response
c.JSON(http.StatusOK, gin.H{
"message": "Grundinformationen gespeichert",
"session_id": sessionID,
"current_step": 2,
})
}
// UpdateAttributesRequest
type UpdateAttributesRequest struct {
ST int `json:"st" binding:"required,min=1,max=100"` // Stärke
GS int `json:"gs" binding:"required,min=1,max=100"` // Geschicklichkeit
GW int `json:"gw" binding:"required,min=1,max=100"` // Gewandtheit
KO int `json:"ko" binding:"required,min=1,max=100"` // Konstitution
IN int `json:"in" binding:"required,min=1,max=100"` // Intelligenz
ZT int `json:"zt" binding:"required,min=1,max=100"` // Zaubertalent
AU int `json:"au" binding:"required,min=1,max=100"` // Ausstrahlung
PA int `json:"pa" binding:"required,min=1,max=100"` // Psi-Kraft
WK int `json:"wk" binding:"required,min=1,max=100"` // Willenskraft
}
// UpdateCharacterAttributes speichert Grundwerte
func UpdateCharacterAttributes(c *gin.Context) {
sessionID := c.Param("sessionId")
var request UpdateAttributesRequest
if err := c.ShouldBindJSON(&request); err != nil {
respondWithError(c, http.StatusBadRequest, "Ungültige Attributswerte: "+err.Error())
return
}
// TODO: Session aktualisieren
c.JSON(http.StatusOK, gin.H{
"message": "Grundwerte gespeichert",
"session_id": sessionID,
"current_step": 3,
})
}
// UpdateDerivedValuesRequest
type UpdateDerivedValuesRequest struct {
LP_Max int `json:"lp_max"` // Lebenspunkte Maximum
AP_Max int `json:"ap_max"` // Abenteuerpunkte Maximum
B_Max int `json:"b_max"` // Belastung Maximum
SG int `json:"sg"` // Schicksalsgunst
GG int `json:"gg"` // Göttliche Gnade
GP int `json:"gp"` // Glückspunkte
}
// UpdateCharacterDerivedValues speichert abgeleitete Werte
func UpdateCharacterDerivedValues(c *gin.Context) {
sessionID := c.Param("sessionId")
var request UpdateDerivedValuesRequest
if err := c.ShouldBindJSON(&request); err != nil {
respondWithError(c, http.StatusBadRequest, "Ungültige abgeleitete Werte: "+err.Error())
return
}
// TODO: Session aktualisieren
c.JSON(http.StatusOK, gin.H{
"message": "Abgeleitete Werte gespeichert",
"session_id": sessionID,
"current_step": 4,
})
}
// UpdateSkillsRequest
type UpdateSkillsRequest struct {
Skills []CharacterCreationSkill `json:"skills"`
Spells []CharacterCreationSpell `json:"spells"`
SkillPoints map[string]int `json:"skill_points"` // Verbleibende Punkte pro Kategorie
}
// UpdateCharacterSkills speichert Fertigkeiten und Zauber
func UpdateCharacterSkills(c *gin.Context) {
sessionID := c.Param("sessionId")
var request UpdateSkillsRequest
if err := c.ShouldBindJSON(&request); err != nil {
respondWithError(c, http.StatusBadRequest, "Ungültige Fertigkeitsdaten: "+err.Error())
return
}
// TODO: Session aktualisieren
c.JSON(http.StatusOK, gin.H{
"message": "Fertigkeiten gespeichert",
"session_id": sessionID,
"current_step": 5,
})
}
// FinalizeCharacterCreation schließt die Charakter-Erstellung ab
func FinalizeCharacterCreation(c *gin.Context) {
sessionID := c.Param("sessionId")
// TODO: Session laden, validieren und finalen Charakter erstellen
// TODO: Character in Datenbank speichern
// TODO: Session löschen
// Dummy-Response
characterID := uint(123) // Würde aus DB kommen
c.JSON(http.StatusCreated, gin.H{
"message": "Charakter erfolgreich erstellt",
"character_id": characterID,
"session_id": sessionID,
})
}
// DeleteCharacterSession löscht eine Session
func DeleteCharacterSession(c *gin.Context) {
sessionID := c.Param("sessionId")
// TODO: Session aus Datenbank löschen
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) {
// TODO: Aus Datenbank laden
classes := []string{
"Abenteurer", "Assassine", "Barbar", "Barde", "Bauer",
"Glückspriester", "Heiler", "Händler", "Kämpfer", "Krieger",
"Magier", "Ordenskrieger", "Priester", "Schamane", "Seefahrer",
"Späher", "Thaumaturg", "Waldläufer", "Zauberer", "Zaubersänger",
}
c.JSON(http.StatusOK, gin.H{"classes": classes})
}
// GetOrigins gibt verfügbare Herkünfte zurück
func GetOrigins(c *gin.Context) {
// TODO: Aus Datenbank laden
origins := []string{
"Albai", "Aran", "Buluga", "Cangebiet", "Chryseia", "Dwerunlande",
"Eschar", "Fuardain", "Ikenga", "KanThaiPan", "Küstenstaaten",
"Medjis", "Moravod", "Nahuatlan", "Rawindra", "Scharidis",
"Tegarisch Gebiete", "Valian", "Waeland", "Ywerddon",
}
c.JSON(http.StatusOK, gin.H{"origins": origins})
}
// SearchBeliefs sucht Glaubensrichtungen
func SearchBeliefs(c *gin.Context) {
query := c.Query("q")
if len(query) < 2 {
c.JSON(http.StatusBadRequest, gin.H{"error": "Mindestens 2 Zeichen erforderlich"})
return
}
// TODO: Datenbanksuche implementieren
// Dummy-Daten für Demo
allBeliefs := []string{
"Apshai", "Arthusos", "Beschützer", "Dwyllas", "Elfen",
"Fruchtbarkeitsgöttin", "Gaia", "Grafschafter", "Heiler",
"Jäger", "Kämpfer", "Lichbringer", "Meeresherr", "Natur",
"Ostküste", "Priester", "Rechtschaffener", "Schutzpatron",
"Stammesgeist", "Totengott", "Unterwelt", "Vater", "Weisheit",
"Xan", "Ylhoon", "Zauberer",
}
var results []string
queryLower := strings.ToLower(query)
for _, belief := range allBeliefs {
if strings.Contains(strings.ToLower(belief), queryLower) {
results = append(results, belief)
}
}
c.JSON(http.StatusOK, gin.H{"beliefs": results})
}
// SkillCategoryWithPoints repräsentiert eine Kategorie mit verfügbaren Lernpunkten
type SkillCategoryWithPoints struct {
Name string `json:"name"`
DisplayName string `json:"display_name"`
Points int `json:"points"`
MaxPoints int `json:"max_points"`
}
// GetSkillCategoriesWithPoints gibt Kategorien mit Lernpunkten zurück
func GetSkillCategoriesWithPoints(c *gin.Context) {
// TODO: Basierend auf Charakter-Klasse und -Typ berechnen
categories := []SkillCategoryWithPoints{
{Name: "alltag", DisplayName: "Alltag", Points: 150, MaxPoints: 150},
{Name: "wissen", DisplayName: "Wissen", Points: 100, MaxPoints: 100},
{Name: "kampf", DisplayName: "Kampf", Points: 80, MaxPoints: 80},
{Name: "korper", DisplayName: "Körper", Points: 120, MaxPoints: 120},
{Name: "gesellschaft", DisplayName: "Gesellschaft", Points: 60, MaxPoints: 60},
{Name: "natur", DisplayName: "Natur", Points: 90, MaxPoints: 90},
{Name: "unterwelt", DisplayName: "Unterwelt", Points: 40, MaxPoints: 40},
{Name: "zauber", DisplayName: "Zauber", Points: 200, MaxPoints: 200},
}
c.JSON(http.StatusOK, gin.H{"categories": categories})
}
+18
View File
@@ -50,4 +50,22 @@ func RegisterRoutes(r *gin.RouterGroup) {
// System-Information
charGrp.GET("/character-classes", GetCharacterClassesHandlerOld)
charGrp.GET("/skill-categories", GetSkillCategoriesHandlerOld)
// Character Creation
charGrp.GET("/create-sessions", ListCharacterSessions) // Aktive Sessions für Benutzer auflisten
charGrp.POST("/create-session", CreateCharacterSession) // Neue Charakter-Erstellungssession
charGrp.GET("/create-session/:sessionId", GetCharacterSession) // Session-Daten abrufen
charGrp.PUT("/create-session/:sessionId/basic", UpdateCharacterBasicInfo) // Grundinformationen speichern
charGrp.PUT("/create-session/:sessionId/attributes", UpdateCharacterAttributes) // Grundwerte speichern
charGrp.PUT("/create-session/:sessionId/derived", UpdateCharacterDerivedValues) // Abgeleitete Werte speichern
charGrp.PUT("/create-session/:sessionId/skills", UpdateCharacterSkills) // Fertigkeiten speichern
charGrp.POST("/create-session/:sessionId/finalize", FinalizeCharacterCreation) // Charakter-Erstellung abschließen
charGrp.DELETE("/create-session/:sessionId", DeleteCharacterSession) // Session löschen
// Reference Data für Character Creation
charGrp.GET("/races", GetRaces) // Verfügbare Rassen
charGrp.GET("/classes", GetCharacterClasses) // Verfügbare Klassen
charGrp.GET("/origins", GetOrigins) // Verfügbare Herkünfte
charGrp.GET("/beliefs", SearchBeliefs) // Glaube-Suche
charGrp.GET("/skill-categories-with-points", GetSkillCategoriesWithPoints) // Kategorien mit Lernpunkten
}
+6 -7
View File
@@ -10,6 +10,7 @@ import (
"log"
"gorm.io/driver/mysql"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
@@ -38,13 +39,11 @@ var (
func ConnectDatabase() *gorm.DB {
SetupTestDB()
/*
db, err := gorm.Open(sqlite.Open(PreparedTestDB), &gorm.Config{})
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
DB = db
*/
db, err := gorm.Open(sqlite.Open(PreparedTestDB), &gorm.Config{})
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
DB = db
return DB
}
func ConnectDatabaseOrig() *gorm.DB {
+132
View File
@@ -0,0 +1,132 @@
package models
import (
"encoding/json"
"time"
"gorm.io/gorm"
)
// CharacterCreationSession speichert den Fortschritt der Charakter-Erstellung
type CharacterCreationSession struct {
ID string `json:"id" gorm:"primaryKey"`
UserID uint `json:"user_id" gorm:"index"`
Name string `json:"name"`
Rasse string `json:"rasse"`
Typ string `json:"typ"`
Herkunft string `json:"herkunft"`
Glaube string `json:"glaube"`
Attributes AttributesData `json:"attributes" gorm:"type:json"`
DerivedValues DerivedValuesData `json:"derived_values" gorm:"type:json"`
Skills []CharacterCreationSkill `json:"skills" gorm:"type:json"`
Spells []CharacterCreationSpell `json:"spells" gorm:"type:json"`
SkillPoints SkillPointsData `json:"skill_points" gorm:"type:json"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
ExpiresAt time.Time `json:"expires_at"`
CurrentStep int `json:"current_step"` // 1=Basic, 2=Attributes, 3=Derived, 4=Skills
}
// AttributesData speichert die Grundwerte
type AttributesData struct {
ST int `json:"st"` // Stärke
GS int `json:"gs"` // Geschicklichkeit
GW int `json:"gw"` // Gewandtheit
KO int `json:"ko"` // Konstitution
IN int `json:"in"` // Intelligenz
ZT int `json:"zt"` // Zaubertalent
AU int `json:"au"` // Ausstrahlung
PA int `json:"pa"` // Psi-Kraft
WK int `json:"wk"` // Willenskraft
}
// DerivedValuesData speichert die abgeleiteten Werte
type DerivedValuesData struct {
LPMax int `json:"lp_max"` // Lebenspunkte Maximum
APMax int `json:"ap_max"` // Abenteuerpunkte Maximum
BMax int `json:"b_max"` // Belastung Maximum
SG int `json:"sg"` // Schicksalsgunst
GG int `json:"gg"` // Göttliche Gnade
GP int `json:"gp"` // Glückspunkte
}
// SkillPointsData speichert die verbleibenden Lernpunkte pro Kategorie
type SkillPointsData map[string]int
// CharacterCreationSkill repräsentiert eine ausgewählte Fertigkeit
type CharacterCreationSkill struct {
Name string `json:"name"`
Level int `json:"level"`
Category string `json:"category"`
Cost int `json:"cost"`
}
// CharacterCreationSpell repräsentiert einen ausgewählten Zauber
type CharacterCreationSpell struct {
Name string `json:"name"`
Cost int `json:"cost"`
}
// Scanning methods for JSON fields in GORM
func (a *AttributesData) Scan(value interface{}) error {
if value == nil {
return nil
}
bytes, ok := value.([]byte)
if !ok {
return nil
}
return json.Unmarshal(bytes, a)
}
func (a AttributesData) Value() (interface{}, error) {
return json.Marshal(a)
}
func (d *DerivedValuesData) Scan(value interface{}) error {
if value == nil {
return nil
}
bytes, ok := value.([]byte)
if !ok {
return nil
}
return json.Unmarshal(bytes, d)
}
func (d DerivedValuesData) Value() (interface{}, error) {
return json.Marshal(d)
}
func (s *SkillPointsData) Scan(value interface{}) error {
if value == nil {
return nil
}
bytes, ok := value.([]byte)
if !ok {
return nil
}
return json.Unmarshal(bytes, s)
}
func (s SkillPointsData) Value() (interface{}, error) {
return json.Marshal(s)
}
// Cleanup expired sessions
func CleanupExpiredSessions(db *gorm.DB) error {
return db.Where("expires_at < ?", time.Now()).Delete(&CharacterCreationSession{}).Error
}
// Find sessions by user ID
func GetUserSessions(db *gorm.DB, userID uint) ([]CharacterCreationSession, error) {
var sessions []CharacterCreationSession
err := db.Where("user_id = ? AND expires_at > ?", userID, time.Now()).Find(&sessions).Error
return sessions, err
}