Anzeige erlernbare Fertigkeiten OK
Kostenanzeige OK selektion zum lernen OK Update verfügbare Ressourcen OK
This commit is contained in:
@@ -1310,3 +1310,106 @@ func GetRewardTypes(c *gin.Context) {
|
||||
"character_id": characterID,
|
||||
})
|
||||
}
|
||||
|
||||
// GetAvailableSkills gibt alle verfügbaren Fertigkeiten mit Lernkosten zurück
|
||||
func GetAvailableSkills(c *gin.Context) {
|
||||
characterID := c.Param("id")
|
||||
rewardType := c.Query("reward_type")
|
||||
|
||||
var character Char
|
||||
if err := database.DB.Preload("Fertigkeiten").Preload("Erfahrungsschatz").Preload("Vermoegen").First(&character, characterID).Error; err != nil {
|
||||
respondWithError(c, http.StatusNotFound, "Character not found")
|
||||
return
|
||||
}
|
||||
|
||||
// Hole alle verfügbaren Fertigkeiten aus der gsmaster Datenbank, aber filtere Placeholder aus
|
||||
var allSkills []gsmaster.Skill
|
||||
if err := database.DB.Where("name != ?", "Placeholder").Find(&allSkills).Error; err != nil {
|
||||
respondWithError(c, http.StatusInternalServerError, "Failed to retrieve skills")
|
||||
return
|
||||
}
|
||||
|
||||
// Erstelle eine Map der bereits gelernten Fertigkeiten
|
||||
learnedSkills := make(map[string]bool)
|
||||
for _, skill := range character.Fertigkeiten {
|
||||
learnedSkills[skill.Name] = true
|
||||
}
|
||||
|
||||
// Organisiere Fertigkeiten nach Kategorien
|
||||
skillsByCategory := make(map[string][]gin.H)
|
||||
|
||||
for _, skill := range allSkills {
|
||||
// Überspringe bereits gelernte Fertigkeiten
|
||||
if learnedSkills[skill.Name] {
|
||||
continue
|
||||
}
|
||||
|
||||
// Überspringe Placeholder-Fertigkeiten (zusätzliche Sicherheit)
|
||||
if skill.Name == "Placeholder" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Berechne Lernkosten mit GetLernCostNextLevel
|
||||
epCost, goldCost := calculateSkillLearningCosts(skill, character, rewardType)
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
// calculateSkillLearningCosts berechnet die EP- und Goldkosten für das Lernen einer Fertigkeit mit GetLernCostNextLevel
|
||||
func calculateSkillLearningCosts(skill gsmaster.Skill, character Char, rewardType string) (int, int) {
|
||||
// Erstelle LernCostRequest für das Lernen (Level 0 -> 1)
|
||||
var rewardTypePtr *string
|
||||
if rewardType != "" && rewardType != "default" {
|
||||
rewardTypePtr = &rewardType
|
||||
}
|
||||
|
||||
request := gsmaster.LernCostRequest{
|
||||
CharId: character.ID,
|
||||
Name: skill.Name,
|
||||
CurrentLevel: 0, // Nicht gelernt
|
||||
TargetLevel: 1, // Auf Level 1 lernen
|
||||
Type: "skill",
|
||||
Action: "learn",
|
||||
UsePP: 0,
|
||||
UseGold: 0,
|
||||
Reward: rewardTypePtr,
|
||||
}
|
||||
|
||||
// Erstelle SkillCostResultNew
|
||||
costResult := gsmaster.SkillCostResultNew{
|
||||
CharacterID: fmt.Sprintf("%d", character.ID),
|
||||
CharacterClass: getCharacterClass(&character),
|
||||
SkillName: skill.Name,
|
||||
Category: skill.Category,
|
||||
Difficulty: skill.Difficulty,
|
||||
TargetLevel: 1,
|
||||
}
|
||||
|
||||
// Berechne Kosten mit GetLernCostNextLevel
|
||||
err := gsmaster.GetLernCostNextLevel(&request, &costResult, rewardTypePtr, 1, character.Typ)
|
||||
if err != nil {
|
||||
// Fallback zu Standard-Kosten bei Fehler
|
||||
epCost := 100
|
||||
goldCost := 50
|
||||
|
||||
return epCost, goldCost
|
||||
}
|
||||
|
||||
return costResult.EP, costResult.GoldCost
|
||||
}
|
||||
|
||||
@@ -28,6 +28,9 @@ func RegisterRoutes(r *gin.RouterGroup) {
|
||||
charGrp.POST("/:id/learn-skill", LearnSkill) // Fertigkeit lernen
|
||||
charGrp.POST("/:id/learn-spell", LearnSpell) // Zauber lernen
|
||||
|
||||
// Fertigkeiten-Information
|
||||
charGrp.GET("/:id/available-skills", GetAvailableSkills) // Verfügbare Fertigkeiten mit Kosten (bereits gelernte ausgeschlossen)
|
||||
|
||||
// Belohnungsarten für verschiedene Lernszenarien
|
||||
charGrp.GET("/:id/reward-types", GetRewardTypes) // Verfügbare Belohnungsarten je nach Kontext
|
||||
|
||||
|
||||
@@ -0,0 +1,258 @@
|
||||
package character
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"bamort/database"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetAvailableSkills(t *testing.T) {
|
||||
// Setup test database with real data
|
||||
database.SetupTestDB(true, true)
|
||||
defer database.ResetTestDB()
|
||||
|
||||
// Setup Gin in test mode
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
t.Run("Get available skills for existing character - default reward type", func(t *testing.T) {
|
||||
// Get a character ID from the test data
|
||||
var testChar Char
|
||||
err := database.DB.Preload("Fertigkeiten").Preload("Erfahrungsschatz").Preload("Vermoegen").First(&testChar).Error
|
||||
assert.NoError(t, err, "Should find a test character")
|
||||
|
||||
// Gib dem Charakter genug EP und Gold für Tests
|
||||
testChar.Erfahrungsschatz.EP = 1000
|
||||
testChar.Vermoegen.Goldstücke = 1000
|
||||
database.DB.Save(&testChar.Erfahrungsschatz)
|
||||
database.DB.Save(&testChar.Vermoegen)
|
||||
|
||||
characterID := fmt.Sprintf("%d", testChar.ID)
|
||||
|
||||
// Create HTTP request with default reward type
|
||||
req, _ := http.NewRequest("GET", fmt.Sprintf("/api/characters/%s/available-skills?reward_type=default", characterID), nil)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// Create response recorder
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
// Create Gin context
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Request = req
|
||||
c.Params = []gin.Param{{Key: "id", Value: characterID}}
|
||||
|
||||
fmt.Printf("Test: Get available skills for character ID %s (default reward)\n", characterID)
|
||||
fmt.Printf("Character EP: %d, Gold: %d\n", testChar.Erfahrungsschatz.EP, testChar.Vermoegen.Goldstücke)
|
||||
|
||||
// Call the handler function
|
||||
GetAvailableSkills(c)
|
||||
|
||||
// Print the response for debugging
|
||||
fmt.Printf("Response Status: %d\n", w.Code)
|
||||
fmt.Printf("Response Body: %s\n", w.Body.String())
|
||||
|
||||
// Assert response status
|
||||
assert.Equal(t, http.StatusOK, w.Code, "Status code should be 200 OK")
|
||||
|
||||
// Parse response
|
||||
var response map[string]interface{}
|
||||
err = json.Unmarshal(w.Body.Bytes(), &response)
|
||||
assert.NoError(t, err, "Response should be valid JSON")
|
||||
|
||||
// Check response structure
|
||||
assert.Contains(t, response, "skills_by_category", "Response should contain skills_by_category field")
|
||||
|
||||
// Check if skills_by_category is an object
|
||||
skillsByCategory, ok := response["skills_by_category"].(map[string]interface{})
|
||||
assert.True(t, ok, "skills_by_category should be an object")
|
||||
|
||||
fmt.Printf("Found %d skill categories\n", len(skillsByCategory))
|
||||
|
||||
// Check each category
|
||||
totalSkills := 0
|
||||
for categoryName, categorySkills := range skillsByCategory {
|
||||
fmt.Printf("Category: %s\n", categoryName)
|
||||
|
||||
skills, ok := categorySkills.([]interface{})
|
||||
assert.True(t, ok, fmt.Sprintf("Category %s should contain an array of skills", categoryName))
|
||||
|
||||
totalSkills += len(skills)
|
||||
fmt.Printf(" Skills in category: %d\n", len(skills))
|
||||
|
||||
// Check structure of first skill in category if exists
|
||||
if len(skills) > 0 {
|
||||
firstSkill, ok := skills[0].(map[string]interface{})
|
||||
assert.True(t, ok, "First skill should be an object")
|
||||
|
||||
// Check required fields
|
||||
assert.Contains(t, firstSkill, "name", "Skill should have name field")
|
||||
assert.Contains(t, firstSkill, "epCost", "Skill should have epCost field")
|
||||
assert.Contains(t, firstSkill, "goldCost", "Skill should have goldCost field")
|
||||
|
||||
skillName, ok := firstSkill["name"].(string)
|
||||
assert.True(t, ok, "Skill name should be a string")
|
||||
assert.NotEmpty(t, skillName, "Skill name should not be empty")
|
||||
|
||||
epCost, ok := firstSkill["epCost"].(float64)
|
||||
assert.True(t, ok, "EP cost should be a number")
|
||||
assert.GreaterOrEqual(t, epCost, float64(0), "EP cost should be non-negative")
|
||||
|
||||
goldCost, ok := firstSkill["goldCost"].(float64)
|
||||
assert.True(t, ok, "Gold cost should be a number")
|
||||
assert.GreaterOrEqual(t, goldCost, float64(0), "Gold cost should be non-negative")
|
||||
|
||||
fmt.Printf(" First skill: %s (EP: %.0f, Gold: %.0f)\n",
|
||||
skillName, epCost, goldCost)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Total available skills: %d\n", totalSkills)
|
||||
})
|
||||
|
||||
t.Run("Get available skills for existing character - noGold reward type", func(t *testing.T) {
|
||||
// Get a character ID from the test data
|
||||
var testChar Char
|
||||
err := database.DB.Preload("Fertigkeiten").Preload("Erfahrungsschatz").Preload("Vermoegen").First(&testChar).Error
|
||||
assert.NoError(t, err, "Should find a test character")
|
||||
|
||||
// Gib dem Charakter genug EP und Gold für Tests
|
||||
testChar.Erfahrungsschatz.EP = 1000
|
||||
testChar.Vermoegen.Goldstücke = 1000
|
||||
database.DB.Save(&testChar.Erfahrungsschatz)
|
||||
database.DB.Save(&testChar.Vermoegen)
|
||||
|
||||
characterID := fmt.Sprintf("%d", testChar.ID)
|
||||
|
||||
// Create HTTP request with noGold reward type
|
||||
req, _ := http.NewRequest("GET", fmt.Sprintf("/api/characters/%s/available-skills?reward_type=noGold", characterID), nil)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// Create response recorder
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
// Create Gin context
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Request = req
|
||||
c.Params = []gin.Param{{Key: "id", Value: characterID}}
|
||||
|
||||
fmt.Printf("Test: Get available skills for character ID %s (noGold reward)\n", characterID)
|
||||
|
||||
// Call the handler function
|
||||
GetAvailableSkills(c)
|
||||
|
||||
// Print the response for debugging
|
||||
fmt.Printf("Response Status: %d\n", w.Code)
|
||||
fmt.Printf("Response Body: %s\n", w.Body.String())
|
||||
|
||||
// Assert response status
|
||||
assert.Equal(t, http.StatusOK, w.Code, "Status code should be 200 OK")
|
||||
|
||||
// Parse response
|
||||
var response map[string]interface{}
|
||||
err = json.Unmarshal(w.Body.Bytes(), &response)
|
||||
assert.NoError(t, err, "Response should be valid JSON")
|
||||
|
||||
// Check response structure
|
||||
assert.Contains(t, response, "skills_by_category", "Response should contain skills_by_category field")
|
||||
|
||||
skillsByCategory, ok := response["skills_by_category"].(map[string]interface{})
|
||||
assert.True(t, ok, "skills_by_category should be an object")
|
||||
|
||||
// Check that skills have goldCost = 0 for noGold reward type
|
||||
for categoryName, categorySkills := range skillsByCategory {
|
||||
skills, ok := categorySkills.([]interface{})
|
||||
assert.True(t, ok, fmt.Sprintf("Category %s should contain an array of skills", categoryName))
|
||||
|
||||
if len(skills) > 0 {
|
||||
firstSkill, ok := skills[0].(map[string]interface{})
|
||||
assert.True(t, ok, "First skill should be an object")
|
||||
|
||||
goldCost, ok := firstSkill["goldCost"].(float64)
|
||||
assert.True(t, ok, "Gold cost should be a number")
|
||||
assert.Equal(t, float64(0), goldCost, "Gold cost should be 0 for noGold reward type")
|
||||
|
||||
fmt.Printf("Category %s - First skill gold cost: %.0f (should be 0)\n", categoryName, goldCost)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Error case - character not found", func(t *testing.T) {
|
||||
// Test with non-existent character ID
|
||||
req, _ := http.NewRequest("GET", "/api/characters/99999/available-skills", nil)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Request = req
|
||||
c.Params = []gin.Param{{Key: "id", Value: "99999"}}
|
||||
|
||||
GetAvailableSkills(c)
|
||||
|
||||
// Should return 404
|
||||
assert.Equal(t, http.StatusNotFound, w.Code, "Status code should be 404 Not Found")
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
assert.NoError(t, err, "Response should be valid JSON")
|
||||
assert.Contains(t, response, "error", "Response should contain error message")
|
||||
})
|
||||
|
||||
t.Run("Check that learned skills are excluded", func(t *testing.T) {
|
||||
// Get a character with some skills
|
||||
var testChar Char
|
||||
err := database.DB.Preload("Fertigkeiten").First(&testChar).Error
|
||||
assert.NoError(t, err, "Should find a test character")
|
||||
|
||||
characterID := fmt.Sprintf("%d", testChar.ID)
|
||||
|
||||
// Get list of learned skills
|
||||
learnedSkillNames := make(map[string]bool)
|
||||
for _, skill := range testChar.Fertigkeiten {
|
||||
learnedSkillNames[skill.Name] = true
|
||||
}
|
||||
|
||||
// Make request
|
||||
req, _ := http.NewRequest("GET", fmt.Sprintf("/api/characters/%s/available-skills", characterID), nil)
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Request = req
|
||||
c.Params = []gin.Param{{Key: "id", Value: characterID}}
|
||||
|
||||
GetAvailableSkills(c)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code, "Status code should be 200 OK")
|
||||
|
||||
var response map[string]interface{}
|
||||
err = json.Unmarshal(w.Body.Bytes(), &response)
|
||||
assert.NoError(t, err, "Response should be valid JSON")
|
||||
|
||||
skillsByCategory, ok := response["skills_by_category"].(map[string]interface{})
|
||||
assert.True(t, ok, "skills_by_category should be an object")
|
||||
|
||||
// Check that no learned skills appear in available skills
|
||||
for categoryName, categorySkills := range skillsByCategory {
|
||||
skills, ok := categorySkills.([]interface{})
|
||||
assert.True(t, ok, fmt.Sprintf("Category %s should contain an array of skills", categoryName))
|
||||
|
||||
for _, skillInterface := range skills {
|
||||
skill, ok := skillInterface.(map[string]interface{})
|
||||
assert.True(t, ok, "Skill should be an object")
|
||||
|
||||
skillName, ok := skill["name"].(string)
|
||||
assert.True(t, ok, "Skill name should be a string")
|
||||
|
||||
// Assert that this skill is not in the learned skills list
|
||||
assert.False(t, learnedSkillNames[skillName],
|
||||
fmt.Sprintf("Learned skill '%s' should not appear in available skills", skillName))
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Verified that %d learned skills are excluded from available skills\n", len(testChar.Fertigkeiten))
|
||||
})
|
||||
}
|
||||
@@ -98,7 +98,7 @@ func (object *LookupList) Create() error {
|
||||
|
||||
func (object *LookupList) First(value string) error {
|
||||
gameSystem := "midgard"
|
||||
err := database.DB.First(&object, "game_system=? AND name = ?", gameSystem, value).Error
|
||||
err := database.DB.First(&object, "game_system=? AND name!='Placeholder' AND name = ?", gameSystem, value).Error
|
||||
if err != nil {
|
||||
// zauber found
|
||||
return err
|
||||
@@ -108,7 +108,7 @@ func (object *LookupList) First(value string) error {
|
||||
|
||||
func (object *LookupList) FirstId(value uint) error {
|
||||
gameSystem := "midgard"
|
||||
err := database.DB.First(&object, "game_system=? AND id = ?", gameSystem, value).Error
|
||||
err := database.DB.First(&object, "game_system=? AND name!='Placeholder' AND id = ?", gameSystem, value).Error
|
||||
if err != nil {
|
||||
// zauber found
|
||||
return err
|
||||
|
||||
@@ -9,16 +9,47 @@
|
||||
<!-- Ressourcen-Anzeige -->
|
||||
<div class="resources-section">
|
||||
<h4>Verfügbare Ressourcen</h4>
|
||||
<div class="resources-display">
|
||||
<div class="resource-item">
|
||||
<div class="current-resources">
|
||||
<div class="resource-display-card">
|
||||
<span class="resource-icon">⚡</span>
|
||||
<span class="resource-label">Erfahrungspunkte:</span>
|
||||
<span class="resource-value">{{ character.erfahrungsschatz?.ep || 0 }} EP</span>
|
||||
<div class="resource-info">
|
||||
<div class="resource-label">Erfahrungspunkte</div>
|
||||
<div class="resource-amount">{{ character.erfahrungsschatz?.ep || 0 }} EP</div>
|
||||
<div class="resource-remaining" v-if="totalSelectedEP > 0">
|
||||
<small>
|
||||
Verwendet: {{ totalSelectedEP }} EP |
|
||||
Verbleibend: {{ remainingEP }} EP
|
||||
</small>
|
||||
</div>
|
||||
<div class="resource-remaining" v-if="totalSelectedEP > 0">
|
||||
<small :class="{ 'text-warning': remainingEP < 50, 'text-danger': remainingEP <= 0 }">
|
||||
Nach Lernen: {{ remainingEP }} EP
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="resource-item">
|
||||
<div class="resource-display-card">
|
||||
<span class="resource-icon">💰</span>
|
||||
<span class="resource-label">Gold:</span>
|
||||
<span class="resource-value">{{ character.vermoegen?.goldstücke || 0 }} GS</span>
|
||||
<div class="resource-info">
|
||||
<div class="resource-label">Gold</div>
|
||||
<div class="resource-amount">{{ character.vermoegen?.goldstücke || 0 }} GS</div>
|
||||
<div class="resource-remaining" v-if="totalSelectedGold > 0 && rewardType === 'default'">
|
||||
<small>
|
||||
Verwendet: {{ totalSelectedGold }} GS |
|
||||
Verbleibend: {{ remainingGold }} GS
|
||||
</small>
|
||||
</div>
|
||||
<div class="resource-remaining" v-if="totalSelectedGold > 0 && rewardType === 'default'">
|
||||
<small :class="{ 'text-warning': remainingGold < 20, 'text-danger': remainingGold <= 0 }">
|
||||
Nach Lernen: {{ remainingGold }} GS
|
||||
</small>
|
||||
</div>
|
||||
<div class="resource-remaining" v-if="rewardType === 'noGold' && totalSelectedEP > 0">
|
||||
<small class="text-info">
|
||||
Kein Gold benötigt (Belohnungslernen)
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -46,17 +77,19 @@
|
||||
@click="setCategoryFilter(null)"
|
||||
class="category-filter-btn"
|
||||
:class="{ 'active': selectedCategoryFilter === null }"
|
||||
title="Alle Kategorien anzeigen"
|
||||
>
|
||||
Alle
|
||||
</button>
|
||||
<button
|
||||
v-for="category in availableCategories"
|
||||
:key="category"
|
||||
@click="setCategoryFilter(category)"
|
||||
:key="category.key || category.name"
|
||||
@click="setCategoryFilter(category.name)"
|
||||
class="category-filter-btn"
|
||||
:class="{ 'active': selectedCategoryFilter === category }"
|
||||
:class="{ 'active': selectedCategoryFilter === category.name }"
|
||||
:title="category.description"
|
||||
>
|
||||
{{ category }}
|
||||
{{ category.name }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -264,7 +297,6 @@ export default {
|
||||
skillSearchFilter: '',
|
||||
isDragOver: false,
|
||||
isLoadingSkills: false,
|
||||
learnedSkillNames: [], // Namen der bereits gelernten Fertigkeiten
|
||||
|
||||
// Kategorie-Filter
|
||||
availableCategories: [],
|
||||
@@ -318,16 +350,12 @@ export default {
|
||||
this.availableSkillsByCategory[category].forEach(skill => {
|
||||
allSkills.push({
|
||||
...skill,
|
||||
category: category
|
||||
category: category,
|
||||
canAfford: this.canAffordSkill(skill) // Berechne canAfford im Frontend
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Entferne bereits gelernte Fertigkeiten
|
||||
allSkills = allSkills.filter(skill =>
|
||||
!this.learnedSkillNames.includes(skill.name)
|
||||
)
|
||||
|
||||
// Entferne bereits ausgewählte Fertigkeiten
|
||||
const selectedSkillNames = this.selectedSkills.map(s => s.name)
|
||||
allSkills = allSkills.filter(skill =>
|
||||
@@ -368,6 +396,16 @@ export default {
|
||||
return availableEP >= this.totalSelectedEP
|
||||
}
|
||||
return false
|
||||
},
|
||||
|
||||
remainingEP() {
|
||||
const current = this.character.erfahrungsschatz?.ep || 0
|
||||
return Math.max(0, current - this.totalSelectedEP)
|
||||
},
|
||||
|
||||
remainingGold() {
|
||||
const current = this.character.vermoegen?.goldstücke || 0
|
||||
return Math.max(0, current - this.totalSelectedGold)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -398,6 +436,18 @@ export default {
|
||||
this.$emit('close')
|
||||
},
|
||||
|
||||
canAffordSkill(skill) {
|
||||
const availableEP = this.character.erfahrungsschatz?.ep || 0
|
||||
const availableGold = this.character.vermoegen?.goldstücke || 0
|
||||
|
||||
if (this.rewardType === 'default' || this.rewardType === '') {
|
||||
return availableEP >= (skill.epCost || 0) && availableGold >= (skill.goldCost || 0)
|
||||
} else if (this.rewardType === 'noGold') {
|
||||
return availableEP >= (skill.epCost || 0)
|
||||
}
|
||||
return false
|
||||
},
|
||||
|
||||
resetForm() {
|
||||
this.skillName = ''
|
||||
this.rewardType = 'default'
|
||||
@@ -409,7 +459,6 @@ export default {
|
||||
this.isDragOver = false
|
||||
this.selectedCategoryFilter = null
|
||||
this.availableSkillsByCategory = null
|
||||
this.learnedSkillNames = []
|
||||
},
|
||||
|
||||
async loadAvailableSkills() {
|
||||
@@ -418,10 +467,7 @@ export default {
|
||||
// Lade verfügbare Kategorien
|
||||
await this.loadAvailableCategories()
|
||||
|
||||
// Lade bereits gelernte Fertigkeiten des Charakters
|
||||
await this.loadLearnedSkills()
|
||||
|
||||
// Lade alle verfügbaren Fertigkeiten mit Kosten
|
||||
// Lade alle verfügbaren Fertigkeiten mit Kosten (bereits ohne gelernte gefiltert)
|
||||
const response = await this.$api.get(`/api/characters/${this.character.id}/available-skills`, {
|
||||
params: {
|
||||
reward_type: this.rewardType
|
||||
@@ -431,7 +477,6 @@ export default {
|
||||
if (response.data && response.data.skills_by_category) {
|
||||
this.availableSkillsByCategory = response.data.skills_by_category
|
||||
console.log('Loaded skills by category:', this.availableSkillsByCategory)
|
||||
console.log('Learned skills to exclude:', this.learnedSkillNames)
|
||||
} else {
|
||||
// Fallback: Generiere Beispiel-Fertigkeiten
|
||||
this.generateSampleSkills()
|
||||
@@ -446,52 +491,37 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
async loadLearnedSkills() {
|
||||
try {
|
||||
// Lade bereits gelernte Fertigkeiten des Charakters
|
||||
const response = await this.$api.get(`/api/characters/${this.character.id}/skills`)
|
||||
if (response.data && response.data.skills) {
|
||||
// Extrahiere die Namen aller bereits gelernten Fertigkeiten
|
||||
this.learnedSkillNames = response.data.skills.map(skill => skill.name)
|
||||
console.log('Already learned skills:', this.learnedSkillNames)
|
||||
} else if (this.character.fertigkeiten && Array.isArray(this.character.fertigkeiten)) {
|
||||
// Fallback: Verwende Fertigkeiten aus dem Character-Objekt
|
||||
this.learnedSkillNames = this.character.fertigkeiten.map(skill => skill.name)
|
||||
console.log('Already learned skills (from character):', this.learnedSkillNames)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der gelernten Fertigkeiten:', error)
|
||||
// Fallback: Verwende Fertigkeiten aus dem Character-Objekt falls verfügbar
|
||||
if (this.character.fertigkeiten && Array.isArray(this.character.fertigkeiten)) {
|
||||
this.learnedSkillNames = this.character.fertigkeiten.map(skill => skill.name)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async loadAvailableCategories() {
|
||||
try {
|
||||
const response = await this.$api.get('/skill-categories')
|
||||
if (response.data && response.data.categories) {
|
||||
this.availableCategories = response.data.categories
|
||||
const response = await this.$api.get('/api/characters/skill-categories')
|
||||
if (response.data && response.data.skill_categories) {
|
||||
// Extrahiere die Namen und Beschreibungen der Kategorien
|
||||
const categories = response.data.skill_categories
|
||||
this.availableCategories = Object.keys(categories).map(key => ({
|
||||
name: categories[key].name,
|
||||
description: categories[key].description,
|
||||
key: key
|
||||
}))
|
||||
console.log('Loaded categories:', this.availableCategories)
|
||||
} else {
|
||||
// Fallback: Standard-Kategorien
|
||||
this.availableCategories = [
|
||||
'Körperliche Fertigkeiten',
|
||||
'Geistige Fertigkeiten',
|
||||
'Handwerkliche Fertigkeiten',
|
||||
'Magische Fertigkeiten',
|
||||
'Soziale Fertigkeiten'
|
||||
{ name: 'Körperliche Fertigkeiten', description: 'Körperliche Fertigkeiten', key: 'körper' },
|
||||
{ name: 'Geistige Fertigkeiten', description: 'Geistige Fertigkeiten', key: 'geist' },
|
||||
{ name: 'Handwerkliche Fertigkeiten', description: 'Handwerkliche Fertigkeiten', key: 'handwerk' },
|
||||
{ name: 'Magische Fertigkeiten', description: 'Magische Fertigkeiten', key: 'magie' },
|
||||
{ name: 'Soziale Fertigkeiten', description: 'Soziale Fertigkeiten', key: 'sozial' }
|
||||
]
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Kategorien:', error)
|
||||
// Fallback: Standard-Kategorien
|
||||
this.availableCategories = [
|
||||
'Körperliche Fertigkeiten',
|
||||
'Geistige Fertigkeiten',
|
||||
'Handwerkliche Fertigkeiten',
|
||||
'Magische Fertigkeiten',
|
||||
'Soziale Fertigkeiten'
|
||||
{ name: 'Körperliche Fertigkeiten', description: 'Körperliche Fertigkeiten', key: 'körper' },
|
||||
{ name: 'Geistige Fertigkeiten', description: 'Geistige Fertigkeiten', key: 'geist' },
|
||||
{ name: 'Handwerkliche Fertigkeiten', description: 'Handwerkliche Fertigkeiten', key: 'handwerk' },
|
||||
{ name: 'Magische Fertigkeiten', description: 'Magische Fertigkeiten', key: 'magie' },
|
||||
{ name: 'Soziale Fertigkeiten', description: 'Soziale Fertigkeiten', key: 'sozial' }
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -519,13 +549,18 @@ export default {
|
||||
}
|
||||
|
||||
// Setze verfügbare Kategorien aus den Beispieldaten
|
||||
this.availableCategories = Object.keys(this.availableSkillsByCategory)
|
||||
this.availableCategories = Object.keys(this.availableSkillsByCategory).map(key => ({
|
||||
name: key,
|
||||
description: key,
|
||||
key: key.toLowerCase().replace(/\s+/g, '_')
|
||||
}))
|
||||
|
||||
console.log('Generated sample skills:', this.availableSkillsByCategory)
|
||||
},
|
||||
|
||||
setCategoryFilter(category) {
|
||||
this.selectedCategoryFilter = category
|
||||
setCategoryFilter(categoryName) {
|
||||
this.selectedCategoryFilter = categoryName
|
||||
console.log('Category filter set to:', categoryName)
|
||||
},
|
||||
|
||||
onDragStart(event, skill) {
|
||||
@@ -732,36 +767,65 @@ export default {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.resources-display {
|
||||
.current-resources {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.resource-item {
|
||||
.resource-display-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 16px;
|
||||
gap: 10px;
|
||||
padding: 12px 16px;
|
||||
background: white;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.resource-icon {
|
||||
font-size: 18px;
|
||||
.resource-display-card .resource-icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.resource-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.resource-label {
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
font-size: 12px;
|
||||
color: #6c757d;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.resource-value {
|
||||
.resource-amount {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #1da766;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.resource-remaining {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.resource-remaining small {
|
||||
color: #6c757d;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.text-warning {
|
||||
color: #f0ad4e !important;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: #d9534f !important;
|
||||
}
|
||||
|
||||
.text-info {
|
||||
color: #17a2b8 !important;
|
||||
}
|
||||
|
||||
.reward-method-section {
|
||||
@@ -1359,6 +1423,15 @@ export default {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.current-resources {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.resource-display-card {
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
.cost-breakdown {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
Reference in New Issue
Block a user