PP, EP und Gold werden nun korrekt verbraucht

This commit is contained in:
2025-07-26 15:12:43 +02:00
parent 74faa59193
commit f0350a395e
4 changed files with 434 additions and 15 deletions
+198 -9
View File
@@ -592,6 +592,74 @@ func getValueOrDefault(value *int, defaultValue int) int {
return defaultValue
}
// updateOrCreateSkill aktualisiert eine vorhandene Fertigkeit oder erstellt eine neue
func updateOrCreateSkill(character *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 := skills.Fertigkeit{
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 *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 := skills.Zauber{
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
@@ -728,7 +796,7 @@ func LearnSkill(c *gin.Context) {
}
// Für Learning müssen wir von Level 0 (nicht gelernt) auf finalLevel lernen
var totalEP, totalGold int
var totalEP, totalGold, totalPP int
var err error
// Loop für jeden Level von 0 bis finalLevel (für neue Fertigkeiten)
@@ -762,6 +830,7 @@ func LearnSkill(c *gin.Context) {
// Addiere die Kosten
totalEP += costResult.EP
totalGold += costResult.GoldCost
totalPP += costResult.PPUsed
}
// Prüfe, ob genügend EP vorhanden sind
@@ -778,6 +847,28 @@ func LearnSkill(c *gin.Context) {
return
}
// Prüfe, ob genügend PP vorhanden sind (PP der jeweiligen Fertigkeit) - für neue Fertigkeiten normalerweise 0
currentPP := 0
for _, skill := range character.Fertigkeiten {
if skill.Name == request.Name {
currentPP = skill.Pp
break
}
}
// Falls nicht in normalen Fertigkeiten gefunden, prüfe Waffenfertigkeiten
if currentPP == 0 {
for _, skill := range character.Waffenfertigkeiten {
if skill.Name == request.Name {
currentPP = skill.Pp
break
}
}
}
if totalPP > 0 && currentPP < totalPP {
respondWithError(c, http.StatusBadRequest, "Nicht genügend Praxispunkte vorhanden")
return
}
// EP abziehen und Audit-Log erstellen
newEP := currentEP - totalEP
if totalEP > 0 {
@@ -794,6 +885,10 @@ func LearnSkill(c *gin.Context) {
return
}
character.Erfahrungsschatz.EP = newEP
if err := database.DB.Save(&character.Erfahrungsschatz).Error; err != nil {
respondWithError(c, http.StatusInternalServerError, "Fehler beim Speichern der Erfahrungspunkte")
return
}
}
// Gold abziehen und Audit-Log erstellen
@@ -807,10 +902,35 @@ func LearnSkill(c *gin.Context) {
return
}
character.Vermoegen.Goldstücke = newGold
if err := database.DB.Save(&character.Vermoegen).Error; err != nil {
respondWithError(c, http.StatusInternalServerError, "Fehler beim Speichern des Vermögens")
return
}
}
// TODO: Hier sollte die Fertigkeit dem Charakter hinzugefügt werden
// Das hängt davon ab, wie Fertigkeiten in der Datenbank gespeichert werden
// PP abziehen (falls vorhanden und erforderlich)
if totalPP > 0 {
// Suche die richtige Fertigkeit und ziehe PP ab
for i, skill := range character.Fertigkeiten {
if skill.Name == request.Name {
character.Fertigkeiten[i].Pp -= totalPP
break
}
}
// Falls nicht in normalen Fertigkeiten gefunden, prüfe Waffenfertigkeiten
for i, skill := range character.Waffenfertigkeiten {
if skill.Name == request.Name {
character.Waffenfertigkeiten[i].Pp -= totalPP
break
}
}
}
// Erstelle die neue Fertigkeit mit dem finalen Level
if err := updateOrCreateSkill(&character, request.Name, finalLevel); err != nil {
respondWithError(c, http.StatusInternalServerError, "Fehler beim Hinzufügen der Fertigkeit: "+err.Error())
return
}
// Charakter speichern
if err := database.DB.Save(&character).Error; err != nil {
@@ -855,7 +975,13 @@ func ImproveSkill(c *gin.Context) {
// Hole Charakter über die ID aus dem Request
var character Char
if err := character.FirstID(fmt.Sprintf("%d", request.CharId)); err != nil {
err := database.DB.
Preload("Fertigkeiten").
Preload("Waffenfertigkeiten").
Preload("Erfahrungsschatz").
Preload("Vermoegen").
First(&character, request.CharId).Error
if err != nil {
respondWithError(c, http.StatusNotFound, "Charakter nicht gefunden")
return
}
@@ -886,8 +1012,7 @@ func ImproveSkill(c *gin.Context) {
}
// Initialisiere Gesamtkosten
var totalEP, totalGold int
var err error
var totalEP, totalGold, totalPP int
// Loop für jeden Level von currentLevel bis finalLevel
tempLevel := currentLevel
@@ -914,6 +1039,7 @@ func ImproveSkill(c *gin.Context) {
// Addiere die Kosten
totalEP += costResult.EP
totalGold += costResult.GoldCost
totalPP += costResult.PPUsed
tempLevel++
}
@@ -932,6 +1058,28 @@ func ImproveSkill(c *gin.Context) {
return
}
// Prüfe, ob genügend PP vorhanden sind (PP der jeweiligen Fertigkeit)
currentPP := 0
for _, skill := range character.Fertigkeiten {
if skill.Name == request.Name {
currentPP = skill.Pp
break
}
}
// Falls nicht in normalen Fertigkeiten gefunden, prüfe Waffenfertigkeiten
if currentPP == 0 {
for _, skill := range character.Waffenfertigkeiten {
if skill.Name == request.Name {
currentPP = skill.Pp
break
}
}
}
if totalPP > 0 && currentPP < totalPP {
respondWithError(c, http.StatusBadRequest, "Nicht genügend Praxispunkte vorhanden")
return
}
// EP abziehen und Audit-Log erstellen
newEP := currentEP - totalEP
if totalEP > 0 {
@@ -950,6 +1098,10 @@ func ImproveSkill(c *gin.Context) {
return
}
character.Erfahrungsschatz.EP = newEP
if err := database.DB.Save(&character.Erfahrungsschatz).Error; err != nil {
respondWithError(c, http.StatusInternalServerError, "Fehler beim Speichern der Erfahrungspunkte")
return
}
}
// Gold abziehen und Audit-Log erstellen
@@ -963,10 +1115,43 @@ func ImproveSkill(c *gin.Context) {
return
}
character.Vermoegen.Goldstücke = newGold
if err := database.DB.Save(&character.Vermoegen).Error; err != nil {
respondWithError(c, http.StatusInternalServerError, "Fehler beim Speichern des Vermögens")
return
}
}
// TODO: Hier sollte die Fertigkeit des Charakters verbessert werden
// Das hängt davon ab, wie Fertigkeiten in der Datenbank gespeichert werden
// PP abziehen wenn verwendet (PP der jeweiligen Fertigkeit)
if totalPP > 0 {
// Finde die richtige Fertigkeit und ziehe PP ab
for i := range character.Fertigkeiten {
if character.Fertigkeiten[i].Name == request.Name {
character.Fertigkeiten[i].Pp -= totalPP
if err := database.DB.Save(&character.Fertigkeiten[i]).Error; err != nil {
respondWithError(c, http.StatusInternalServerError, "Fehler beim Aktualisieren der Praxispunkte")
return
}
break
}
}
// Falls nicht in normalen Fertigkeiten gefunden, prüfe Waffenfertigkeiten
for i := range character.Waffenfertigkeiten {
if character.Waffenfertigkeiten[i].Name == request.Name {
character.Waffenfertigkeiten[i].Pp -= totalPP
if err := database.DB.Save(&character.Waffenfertigkeiten[i]).Error; err != nil {
respondWithError(c, http.StatusInternalServerError, "Fehler beim Aktualisieren der Praxispunkte")
return
}
break
}
}
}
// Aktualisiere die Fertigkeit mit dem neuen Level
if err := updateOrCreateSkill(&character, request.Name, finalLevel); err != nil {
respondWithError(c, http.StatusInternalServerError, "Fehler beim Aktualisieren der Fertigkeit: "+err.Error())
return
}
// Charakter speichern
if err := database.DB.Save(&character).Error; err != nil {
@@ -1054,7 +1239,11 @@ func LearnSpell(c *gin.Context) {
character.Erfahrungsschatz.EP = newEP
}
// TODO: Hier sollte der Zauber dem Charakter hinzugefügt werden
// Füge den Zauber zum Charakter hinzu
if err := addSpellToCharacter(&character, request.Name); err != nil {
respondWithError(c, http.StatusInternalServerError, "Fehler beim Hinzufügen des Zaubers: "+err.Error())
return
}
// Charakter speichern
if err := database.DB.Save(&character).Error; err != nil {
+214
View File
@@ -0,0 +1,214 @@
package character
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"bamort/database"
"bamort/gsmaster"
"bamort/models"
"bamort/skills"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
)
func TestImproveSkillUpdatesLevel(t *testing.T) {
// Setup test database
database.SetupTestDB()
defer database.ResetTestDB()
// Migrate the schema
err := MigrateStructure()
assert.NoError(t, err)
// Also migrate skills and equipment to avoid preload errors
err = skills.MigrateStructure()
assert.NoError(t, err)
err = gsmaster.MigrateStructure()
assert.NoError(t, err)
// Try to migrate equipment if it exists
if equipmentDB := database.DB.Exec("CREATE TABLE IF NOT EXISTS equi_equipments (id INTEGER PRIMARY KEY, character_id INTEGER, name TEXT)"); equipmentDB.Error != nil {
t.Logf("Warning: Could not create equipment table: %v", equipmentDB.Error)
}
// Create audit log table for tests
database.DB.Exec(`CREATE TABLE IF NOT EXISTS audit_log_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
character_id INTEGER,
field_name TEXT,
old_value INTEGER,
new_value INTEGER,
difference INTEGER,
reason TEXT,
user_id INTEGER,
timestamp DATETIME,
notes TEXT
)`)
// Create container table to avoid preload errors
database.DB.Exec("CREATE TABLE IF NOT EXISTS equi_containers (id INTEGER PRIMARY KEY, character_id INTEGER)")
database.DB.Exec("CREATE TABLE IF NOT EXISTS equi_weapons (id INTEGER PRIMARY KEY, character_id INTEGER)")
database.DB.Exec("CREATE TABLE IF NOT EXISTS equi_transportmittel (id INTEGER PRIMARY KEY, character_id INTEGER)")
database.DB.Exec("CREATE TABLE IF NOT EXISTS equi_equipments (id INTEGER PRIMARY KEY, character_id INTEGER)")
// Create test character with ID 20
testChar := Char{
BamortBase: models.BamortBase{
ID: 20,
Name: "Test Krieger",
},
Typ: "Krieger",
Rasse: "Mensch",
Grad: 1,
Erfahrungsschatz: Erfahrungsschatz{
BamortCharTrait: models.BamortCharTrait{
CharacterID: 20,
},
EP: 326, // Starting EP
},
Vermoegen: Vermoegen{
BamortCharTrait: models.BamortCharTrait{
CharacterID: 20,
},
Goldstücke: 390, // Starting Gold
},
}
// Add Athletik skill at level 9
athletikSkill := skills.Fertigkeit{
BamortCharTrait: models.BamortCharTrait{
BamortBase: models.BamortBase{
Name: "Athletik",
},
CharacterID: 20,
},
Fertigkeitswert: 9,
Improvable: true,
}
testChar.Fertigkeiten = append(testChar.Fertigkeiten, athletikSkill)
err = testChar.Create()
assert.NoError(t, err)
// Verify character was created correctly
var verifyChar Char
err = database.DB.Preload("Fertigkeiten").Preload("Erfahrungsschatz").Preload("Vermoegen").First(&verifyChar, 20).Error
assert.NoError(t, err)
t.Logf("Character created with ID: %d, EP: %d, Skills: %d", verifyChar.ID, verifyChar.Erfahrungsschatz.EP, len(verifyChar.Fertigkeiten))
// Setup Gin in test mode
gin.SetMode(gin.TestMode)
t.Run("ImproveSkill updates skill level from 9 to 10", func(t *testing.T) {
// Create request body
requestData := map[string]interface{}{
"char_id": 20,
"name": "Athletik",
"current_level": 9,
"target_level": 10,
"type": "skill",
"action": "improve",
"reward": "default",
"use_pp": 1,
"use_gold": 0,
"notes": "Fertigkeit Athletik von 9 auf 10 verbessert (1 Level)",
}
requestBody, _ := json.Marshal(requestData)
// Create HTTP request
req, _ := http.NewRequest("POST", "/api/characters/improve-skill", bytes.NewBuffer(requestBody))
req.Header.Set("Content-Type", "application/json")
// Create response recorder
w := httptest.NewRecorder()
// Create Gin context
c, _ := gin.CreateTestContext(w)
c.Request = req
// Call the actual handler function
ImproveSkill(c)
// Print the actual response to see what we get
t.Logf("Response Status: %d", w.Code)
t.Logf("Response Body: %s", w.Body.String())
// Check if we got a successful response
assert.Equal(t, http.StatusOK, w.Code, "Status code should be 200 OK")
// Verify that the skill level was actually updated in the database
var updatedChar Char
err = database.DB.Preload("Fertigkeiten").First(&updatedChar, 20).Error
assert.NoError(t, err)
// Find the Athletik skill and check its level
skillFound := false
for _, skill := range updatedChar.Fertigkeiten {
if skill.Name == "Athletik" {
assert.Equal(t, 10, skill.Fertigkeitswert, "Athletik skill should be level 10 after improvement")
skillFound = true
t.Logf("Found Athletik skill with level: %d", skill.Fertigkeitswert)
break
}
}
assert.True(t, skillFound, "Athletik skill should be found in character's skills")
t.Logf("Test completed successfully!")
t.Logf("Athletik skill successfully updated from level 9 to level 10")
})
t.Run("LearnSkill creates new skill at level 1", func(t *testing.T) {
// Create request body for learning a new skill
requestData := map[string]interface{}{
"name": "Schwimmen",
"current_level": 0,
"target_level": 1,
"type": "skill",
"action": "learn",
"reward": "default",
"notes": "Neue Fertigkeit Schwimmen gelernt",
}
requestBody, _ := json.Marshal(requestData)
// Create HTTP request
req, _ := http.NewRequest("POST", "/api/characters/20/learn-skill", bytes.NewBuffer(requestBody))
req.Header.Set("Content-Type", "application/json")
// Create response recorder
w := httptest.NewRecorder()
// Create Gin context with character ID parameter
c, _ := gin.CreateTestContext(w)
c.Request = req
c.Params = gin.Params{gin.Param{Key: "id", Value: "20"}}
// Call the actual handler function
LearnSkill(c)
// Check if we got a successful response
assert.Equal(t, http.StatusOK, w.Code, "Status code should be 200 OK")
// Verify that the new skill was created in the database
var updatedChar Char
err = updatedChar.FirstID("20")
assert.NoError(t, err)
// Find the Schwimmen skill and check its level
skillFound := false
for _, skill := range updatedChar.Fertigkeiten {
if skill.Name == "Schwimmen" {
assert.Equal(t, 1, skill.Fertigkeitswert, "Schwimmen skill should be level 1 after learning")
skillFound = true
break
}
}
assert.True(t, skillFound, "Schwimmen skill should be found in character's skills")
t.Logf("New skill 'Schwimmen' successfully created at level 1")
})
}
+17 -1
View File
@@ -6,7 +6,7 @@
</div>
<!-- Submenu Content -->
<!-- <div class="character-aspect"> -->
<component :is="currentView" :character="character"/>
<component :is="currentView" :character="character" @character-updated="refreshCharacter"/>
<!-- </div> -->
<!-- Submenu -->
@@ -82,6 +82,22 @@ export default {
this.lastView = this.currentView;
this.currentView = view;
},
async refreshCharacter() {
// Lade die Charakterdaten neu nach einer Aktualisierung
try {
const token = localStorage.getItem('token');
const response = await API.get(`/api/characters/${this.id}`, {
headers: { Authorization: `Bearer ${token}` },
});
this.character = response.data;
console.log('Character data refreshed after skill update');
} catch (error) {
console.error('Failed to refresh character data:', error);
// Optional: Zeige eine Fehlermeldung an
alert('Fehler beim Aktualisieren der Charakterdaten: ' + (error.response?.data?.error || error.message));
}
},
},
};
</script>
@@ -9,7 +9,7 @@
<span class="resource-icon"></span>
<div class="resource-info">
<div class="resource-label">Erfahrungspunkte</div>
<div class="resource-amount">{{ character.erfahrungsschatz?.value || 0 }} EP</div>
<div class="resource-amount">{{ character.erfahrungsschatz?.ep || 0 }} EP</div>
<div class="resource-remaining">
<small :class="{ 'text-warning': remainingEP < 50, 'text-danger': remainingEP <= 0 }">
Verbleibend: {{ remainingEP }} EP
@@ -496,7 +496,7 @@ export default {
return this.selectedLevels.every(levelNum => {
const level = this.availableLevels.find(l => l.targetLevel === levelNum);
return level && level.canAfford;
}) && this.totalCost <= (this.character.erfahrungsschatz?.value || 0) &&
}) && this.totalCost <= (this.character.erfahrungsschatz?.ep || 0) &&
this.totalGoldCost <= (this.character.vermoegen?.goldstücke || 0) &&
this.totalPPCost <= (this.skill?.pp || 0);
},
@@ -550,7 +550,7 @@ export default {
},
remainingEP() {
const current = this.character.erfahrungsschatz?.value || 0;
const current = this.character.erfahrungsschatz?.ep || 0;
return Math.max(0, current - this.totalCost);
},
@@ -757,7 +757,7 @@ export default {
if (response.data && Array.isArray(response.data) && response.data.length > 0) {
// Konvertiere gsmaster.SkillCostResultNew Array zu unserem internen Format
const availableEP = this.character.erfahrungsschatz?.value || 0;
const availableEP = this.character.erfahrungsschatz?.ep || 0;
const availableGold = this.character.vermoegen?.goldstücke || 0;
const availablePP = this.skill?.pp || 0;
@@ -823,7 +823,7 @@ export default {
// Einfache Fallback-Methode für Kostenberechnung (nur für Notfälle)
const currentLevel = this.skill.fertigkeitswert || 0;
const maxLevel = 20;
const availableEP = this.character.erfahrungsschatz?.value || 0;
const availableEP = this.character.erfahrungsschatz?.ep || 0;
const availableGold = this.character.vermoegen?.goldstücke || 0;
const availablePP = this.skill?.pp || 0;