CalculateStaticFieldsLogic

calculation of Bonus values defence zaubern and other values that depend on base stats
This commit is contained in:
2025-12-27 08:33:42 +01:00
parent fb63ef72fb
commit 3a7e699507
17 changed files with 858 additions and 142 deletions
+102 -44
View File
@@ -23,6 +23,7 @@ type CalculateStaticFieldsRequest struct {
// Charakterdaten
Rasse string `json:"rasse" binding:"required"`
Typ string `json:"typ" binding:"required"`
Grad int `json:"grad" binding:"omitempty,min=1,max=100"` // Charaktergrad (optional, default 1)
}
// StaticFieldsResponse für berechnete Felder ohne Würfelwürfe
@@ -69,17 +70,8 @@ type RolledFieldResponse struct {
Details interface{} `json:"details"`
}
// CalculateStaticFields berechnet alle Felder ohne Würfelwürfe
func CalculateStaticFields(c *gin.Context) {
var req CalculateStaticFieldsRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.Error("Fehler beim Parsen der Static Fields Anfrage: %v", err)
c.JSON(http.StatusBadRequest, gin.H{"error": "Ungültige Anfrage"})
return
}
logger.Info("Berechne statische Felder für %s %s", req.Rasse, req.Typ)
// CalculateStaticFieldsLogic contains the pure business logic for static field calculation
func CalculateStaticFieldsLogic(req CalculateStaticFieldsRequest) StaticFieldsResponse {
response := StaticFieldsResponse{}
// Ausdauer Bonus: Ko/10 + St/20
@@ -103,20 +95,42 @@ func CalculateStaticFields(c *gin.Context) {
// Resistenz Bonus Geist
response.ResistenzBonusGeist = calculateResistenzBonusGeist(req.In, req.Rasse, req.Typ)
// Finale Resistenzwerte
response.ResistenzKoerper = 11 + response.ResistenzBonusKoerper
response.ResistenzGeist = 11 + response.ResistenzBonusGeist
// Grad standardmäßig auf 1 setzen wenn nicht angegeben
grad := req.Grad
if grad < 1 {
grad = 1
}
// Finale Kampfwerte
response.Abwehr = 11 + response.AbwehrBonus
response.Zaubern = 11 + response.ZauberBonus
// Finale Resistenzwerte (mit Gradbonus)
response.ResistenzKoerper = getResistenzBaseByGrade(grad) // + response.ResistenzBonusKoerper
response.ResistenzGeist = getResistenzBaseByGrade(grad) //+ response.ResistenzBonusGeist
// Finale Kampfwerte (mit Gradbonus)
response.Abwehr = getAbwehrBaseByGrade(grad) //+ response.AbwehrBonus
response.Zaubern = getZaubernBaseByGrade(grad) //+ response.ZauberBonus
// Raufen: (St + GW)/20 + angriffs_bonus + Rassenboni
raceBonus := 0
if req.Rasse == "Zwerge" {
if req.Rasse == "Zwerg" {
raceBonus = 1
}
response.Raufen = (req.St+req.Gw)/20 + response.AngriffsBonus + raceBonus
response.Raufen = (req.St+req.Gw)/20 + raceBonus //+ response.AngriffsBonus
return response
}
// CalculateStaticFields berechnet alle Felder ohne Würfelwürfe (HTTP Handler)
func CalculateStaticFields(c *gin.Context) {
var req CalculateStaticFieldsRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.Error("Fehler beim Parsen der Static Fields Anfrage: %v", err)
c.JSON(http.StatusBadRequest, gin.H{"error": "Ungültige Anfrage"})
return
}
logger.Info("Berechne statische Felder für %s %s", req.Rasse, req.Typ)
response := CalculateStaticFieldsLogic(req)
c.JSON(http.StatusOK, response)
}
@@ -291,19 +305,17 @@ func calculateAttributeBonus(value int) int {
// calculateResistenzBonusKoerper berechnet den Körper-Resistenzbonus
func calculateResistenzBonusKoerper(ko int, rasse string, typ string) int {
bonus := 0
bonus := calculateAttributeBonus(ko)
if rasse == "Menschen" {
bonus = calculateAttributeBonus(ko)
} else {
switch rasse {
case "Elfen":
bonus = 2
case "Gnome", "Halblinge":
bonus = 4
case "Zwerge":
bonus = 3
}
switch rasse {
//case "Mensch":
// bonus = calculateAttributeBonus(ko)
case "Elf":
bonus = 2
case "Gnom", "Halbling":
bonus = 4
case "Zwerg":
bonus = 3
}
// Klassenmodifikator
@@ -318,19 +330,17 @@ func calculateResistenzBonusKoerper(ko int, rasse string, typ string) int {
// calculateResistenzBonusGeist berechnet den Geist-Resistenzbonus
func calculateResistenzBonusGeist(in int, rasse string, typ string) int {
bonus := 0
bonus := calculateAttributeBonus(in)
if rasse == "Menschen" {
bonus = calculateAttributeBonus(in)
} else {
switch rasse {
case "Elfen":
bonus = 2
case "Gnome", "Halblinge":
bonus = 4
case "Zwerge":
bonus = 3
}
switch rasse {
//case "Mensch":
//bonus = calculateAttributeBonus(in)
case "Elf":
bonus = 2
case "Gnom", "Halbling":
bonus = 4
case "Zwerg":
bonus = 3
}
// Klassenmodifikator (nur Zauberer bekommen Geist-Bonus)
@@ -354,7 +364,7 @@ func isKaempfer(typ string) bool {
// isZauberer prüft ob eine Klasse als Zauberer gilt
func isZauberer(typ string) bool {
zaubererKlassen := []string{"Magier", "Druide", "Priester", "Schamane"}
zaubererKlassen := []string{"Magier", "Druide", "Priester", "Schamane", "Hexer"}
for _, z := range zaubererKlassen {
if z == typ {
return true
@@ -400,3 +410,51 @@ func getMovementBaseAndFormula(rasse string) (int, string) {
return 16, "4d3 + 16"
}
}
// getAbwehrBaseByGrade gibt den Basis-Fertigkeitswert für Abwehr nach Grad zurück
// Grad: 1->11, 2->12, 5->13, 10->14, 15->15, 20->16, 25->17, 30->18
func getAbwehrBaseByGrade(grad int) int {
if grad >= 30 {
return 18
} else if grad >= 25 {
return 17
} else if grad >= 20 {
return 16
} else if grad >= 15 {
return 15
} else if grad >= 10 {
return 14
} else if grad >= 5 {
return 13
} else if grad >= 2 {
return 12
}
return 11
}
// getResistenzBaseByGrade gibt den Basis-Fertigkeitswert für Resistenz nach Grad zurück
// Verwendet dieselbe Tabelle wie Abwehr
func getResistenzBaseByGrade(grad int) int {
return getAbwehrBaseByGrade(grad)
}
// getZaubernBaseByGrade gibt den Basis-Fertigkeitswert für Zaubern nach Grad zurück
// Grad: 1->11, 2->12, 4->13, 6->14, 8->15, 10->16, 15->17, 20->18
func getZaubernBaseByGrade(grad int) int {
if grad >= 20 {
return 18
} else if grad >= 15 {
return 17
} else if grad >= 10 {
return 16
} else if grad >= 8 {
return 15
} else if grad >= 6 {
return 14
} else if grad >= 4 {
return 13
} else if grad >= 2 {
return 12
}
return 11
}
@@ -0,0 +1,182 @@
package character
import (
"testing"
)
func TestGetGradeBonus_Abwehr(t *testing.T) {
tests := []struct {
name string
grad int
expected int
}{
{"Grad 1", 1, 11},
{"Grad 2", 2, 12},
{"Grad 3", 3, 12},
{"Grad 4", 4, 12},
{"Grad 5", 5, 13},
{"Grad 9", 9, 13},
{"Grad 10", 10, 14},
{"Grad 14", 14, 14},
{"Grad 15", 15, 15},
{"Grad 19", 19, 15},
{"Grad 20", 20, 16},
{"Grad 24", 24, 16},
{"Grad 25", 25, 17},
{"Grad 29", 29, 17},
{"Grad 30", 30, 18},
{"Grad 35", 35, 18},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := getAbwehrBaseByGrade(tt.grad)
if result != tt.expected {
t.Errorf("Expected %d for Grad %d, got %d", tt.expected, tt.grad, result)
}
})
}
}
func TestGetGradeBonus_Resistenz(t *testing.T) {
tests := []struct {
name string
grad int
expected int
}{
{"Grad 1", 1, 11},
{"Grad 2", 2, 12},
{"Grad 3", 3, 12},
{"Grad 4", 4, 12},
{"Grad 5", 5, 13},
{"Grad 9", 9, 13},
{"Grad 10", 10, 14},
{"Grad 14", 14, 14},
{"Grad 15", 15, 15},
{"Grad 19", 19, 15},
{"Grad 20", 20, 16},
{"Grad 24", 24, 16},
{"Grad 25", 25, 17},
{"Grad 29", 29, 17},
{"Grad 30", 30, 18},
{"Grad 35", 35, 18},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := getResistenzBaseByGrade(tt.grad)
if result != tt.expected {
t.Errorf("Expected %d for Grad %d, got %d", tt.expected, tt.grad, result)
}
})
}
}
func TestGetGradeBonus_Zaubern(t *testing.T) {
tests := []struct {
name string
grad int
expected int
}{
{"Grad 1", 1, 11},
{"Grad 2", 2, 12},
{"Grad 3", 3, 12},
{"Grad 4", 4, 13},
{"Grad 5", 5, 13},
{"Grad 6", 6, 14},
{"Grad 7", 7, 14},
{"Grad 8", 8, 15},
{"Grad 9", 9, 15},
{"Grad 10", 10, 16},
{"Grad 14", 14, 16},
{"Grad 15", 15, 17},
{"Grad 19", 19, 17},
{"Grad 20", 20, 18},
{"Grad 25", 25, 18},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := getZaubernBaseByGrade(tt.grad)
if result != tt.expected {
t.Errorf("Expected %d for Grad %d, got %d", tt.expected, tt.grad, result)
}
})
}
}
func TestCalculateStaticFieldsLogic_WithGrade(t *testing.T) {
tests := []struct {
name string
grad int
expectedAbwehr int
expectedResistenzBody int
expectedResistenzMind int
expectedZaubern int
}{
{
name: "Grad 1",
grad: 1,
expectedAbwehr: 11, // base 11 + bonus 0
expectedResistenzBody: 12, // base 11 + bonus 1 (Krieger)
expectedResistenzMind: 11, // base 11 + bonus 0 (Krieger gets no mind bonus)
expectedZaubern: 11, // base 11 + bonus 0
},
{
name: "Grad 5",
grad: 5,
expectedAbwehr: 13, // base 13 + bonus 0
expectedResistenzBody: 14, // base 13 + bonus 1 (Krieger)
expectedResistenzMind: 13, // base 13 + bonus 0
expectedZaubern: 13, // base 13 + bonus 0
},
{
name: "Grad 10",
grad: 10,
expectedAbwehr: 14, // base 14 + bonus 0
expectedResistenzBody: 15, // base 14 + bonus 1 (Krieger)
expectedResistenzMind: 14, // base 14 + bonus 0
expectedZaubern: 16, // base 16 + bonus 0
},
{
name: "Grad 20",
grad: 20,
expectedAbwehr: 16, // base 16 + bonus 0
expectedResistenzBody: 17, // base 16 + bonus 1 (Krieger)
expectedResistenzMind: 16, // base 16 + bonus 0
expectedZaubern: 18, // base 18 + bonus 0
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := CalculateStaticFieldsRequest{
St: 50,
Gs: 50,
Gw: 50,
Ko: 50,
In: 50,
Zt: 50,
Au: 50,
Rasse: "Menschen",
Typ: "Krieger",
Grad: tt.grad,
}
result := CalculateStaticFieldsLogic(req)
if result.Abwehr != tt.expectedAbwehr {
t.Errorf("Expected Abwehr %d for Grad %d, got %d", tt.expectedAbwehr, tt.grad, result.Abwehr)
}
if result.ResistenzKoerper != tt.expectedResistenzBody {
t.Errorf("Expected ResistenzKoerper %d for Grad %d, got %d", tt.expectedResistenzBody, tt.grad, result.ResistenzKoerper)
}
if result.ResistenzGeist != tt.expectedResistenzMind {
t.Errorf("Expected ResistenzGeist %d for Grad %d, got %d", tt.expectedResistenzMind, tt.grad, result.ResistenzGeist)
}
if result.Zaubern != tt.expectedZaubern {
t.Errorf("Expected Zaubern %d for Grad %d, got %d", tt.expectedZaubern, tt.grad, result.Zaubern)
}
})
}
}
+154
View File
@@ -0,0 +1,154 @@
package character
import (
"bamort/database"
"bamort/models"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestDerivedValuesStorage(t *testing.T) {
// Setup test environment
original := os.Getenv("ENVIRONMENT")
os.Setenv("ENVIRONMENT", "test")
t.Cleanup(func() {
if original != "" {
os.Setenv("ENVIRONMENT", original)
} else {
os.Unsetenv("ENVIRONMENT")
}
})
// Setup test database
database.SetupTestDB(true, true)
defer database.ResetTestDB()
err := models.MigrateStructure()
assert.NoError(t, err)
t.Run("Derived values are stored in database", func(t *testing.T) {
// Create a character with derived values
char := models.Char{
BamortBase: models.BamortBase{
Name: "Test Character",
},
UserID: 1,
Rasse: "Mensch",
Typ: "Krieger",
Grad: 1,
ResistenzKoerper: 12,
ResistenzGeist: 11,
Abwehr: 13,
Zaubern: 10,
Raufen: 14,
}
// Save character
err := char.Create()
assert.NoError(t, err, "Should create character successfully")
// Reload character from database
var loadedChar models.Char
err = database.DB.First(&loadedChar, char.ID).Error
assert.NoError(t, err, "Should load character from database")
// Verify derived values are persisted
assert.Equal(t, 12, loadedChar.ResistenzKoerper, "ResistenzKoerper should be persisted")
assert.Equal(t, 11, loadedChar.ResistenzGeist, "ResistenzGeist should be persisted")
assert.Equal(t, 13, loadedChar.Abwehr, "Abwehr should be persisted")
assert.Equal(t, 10, loadedChar.Zaubern, "Zaubern should be persisted")
assert.Equal(t, 14, loadedChar.Raufen, "Raufen should be persisted")
})
}
func TestCalculateBonuses(t *testing.T) {
// Setup test environment
original := os.Getenv("ENVIRONMENT")
os.Setenv("ENVIRONMENT", "test")
t.Cleanup(func() {
if original != "" {
os.Setenv("ENVIRONMENT", original)
} else {
os.Unsetenv("ENVIRONMENT")
}
})
t.Run("Calculate bonuses from character attributes", func(t *testing.T) {
char := models.Char{
BamortBase: models.BamortBase{ID: 1},
Rasse: "Mensch",
Typ: "Krieger",
Eigenschaften: []models.Eigenschaft{
{CharacterID: 1, Name: "St", Value: 85},
{CharacterID: 1, Name: "Gs", Value: 70},
{CharacterID: 1, Name: "Gw", Value: 65},
{CharacterID: 1, Name: "Ko", Value: 75},
{CharacterID: 1, Name: "In", Value: 50},
{CharacterID: 1, Name: "Zt", Value: 30},
},
}
bonuses := char.CalculateBonuses()
// Ausdauer Bonus: Ko/10 + St/20 = 75/10 + 85/20 = 7 + 4 = 11
assert.Equal(t, 11, bonuses.AusdauerBonus, "AusdauerBonus should be calculated correctly")
// Schadens Bonus: St/20 + Gs/30 - 3 = 85/20 + 70/30 - 3 = 4 + 2 - 3 = 3
assert.Equal(t, 3, bonuses.SchadensBonus, "SchadensBonus should be calculated correctly")
// Angriffs Bonus: Gs 70 -> bonus 2 (61-80 range)
assert.Equal(t, 2, bonuses.AngriffsBonus, "AngriffsBonus should be calculated correctly")
// Abwehr Bonus: Gw 65 -> bonus 2 (61-80 range)
assert.Equal(t, 2, bonuses.AbwehrBonus, "AbwehrBonus should be calculated correctly")
// Zauber Bonus: Zt 30 -> bonus 0 (21-40 range)
assert.Equal(t, 0, bonuses.ZauberBonus, "ZauberBonus should be calculated correctly")
// Resistenz Bonus Körper: Ko 75 -> bonus 2 (61-80), Mensch Krieger +1 = 3
assert.Equal(t, 3, bonuses.ResistenzBonusKoerper, "ResistenzBonusKoerper should be calculated correctly")
// Resistenz Bonus Geist: In 50 -> bonus 1 (41-60), Mensch = 1
assert.Equal(t, 1, bonuses.ResistenzBonusGeist, "ResistenzBonusGeist should be calculated correctly")
})
t.Run("Calculate bonuses for Zwerg Kämpfer", func(t *testing.T) {
char := models.Char{
Rasse: "Zwerge",
Typ: "Krieger",
Eigenschaften: []models.Eigenschaft{
{Name: "Ko", Value: 85},
{Name: "In", Value: 50},
},
}
bonuses := char.CalculateBonuses()
// Zwerge get +3 base, Kämpfer +1 = 4
assert.Equal(t, 4, bonuses.ResistenzBonusKoerper, "Zwerge Kämpfer ResistenzBonusKoerper should be 4")
// Zwerge get +3 base
assert.Equal(t, 3, bonuses.ResistenzBonusGeist, "Zwerge ResistenzBonusGeist should be 3")
})
t.Run("Calculate bonuses for Elf Magier", func(t *testing.T) {
char := models.Char{
Rasse: "Elfen",
Typ: "Magier",
Eigenschaften: []models.Eigenschaft{
{Name: "Ko", Value: 50},
{Name: "In", Value: 85},
},
}
bonuses := char.CalculateBonuses()
// Elfen get +2 base, Magier (Zauberer) +2 = 4
assert.Equal(t, 4, bonuses.ResistenzBonusKoerper, "Elfen Magier ResistenzBonusKoerper should be 4")
// Elfen get +2 base, Magier (Zauberer) +2 = 4
assert.Equal(t, 4, bonuses.ResistenzBonusGeist, "Elfen Magier ResistenzBonusGeist should be 4")
})
}
+7
View File
@@ -2748,6 +2748,13 @@ func FinalizeCharacterCreation(c *gin.Context) {
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,
+8 -1
View File
@@ -716,7 +716,7 @@ func TestFinalizeCharacterCreation(t *testing.T) {
// Verify character exists in database
var createdChar models.Char
err = database.DB.Preload("Fertigkeiten").Preload("Waffenfertigkeiten").Preload("Zauber").
err = database.DB.Preload("Lp").Preload("Ap").Preload("B").Preload("Eigenschaften").Preload("Fertigkeiten").Preload("Waffenfertigkeiten").Preload("Zauber").
First(&createdChar, "id = ?", characterID).Error
assert.NoError(t, err, "Created character should exist in database")
@@ -756,6 +756,13 @@ func TestFinalizeCharacterCreation(t *testing.T) {
assert.Equal(t, 8, createdChar.B.Max, "B Max should match session")
assert.Equal(t, 8, createdChar.B.Value, "B Value should equal Max initially")
// Validate static derived values (Resistenz, Abwehr, Zaubern, Raufen)
assert.Equal(t, 11, createdChar.ResistenzKoerper, "Resistenz Körper should match session")
assert.Equal(t, 11, createdChar.ResistenzGeist, "Resistenz Geist should match session")
assert.Equal(t, 11, createdChar.Abwehr, "Abwehr should match session")
assert.Equal(t, 11, createdChar.Zaubern, "Zaubern should match session")
assert.Equal(t, 8, createdChar.Raufen, "Raufen should match session")
// Validate skills were transferred (session has 10 skills: 6 regular skills + 4 weapon skills)
// Regular skills: Klettern, Reiten, Sprache, Athletik, Heilkunde, Naturkunde (6)
// Weapon skills: Spießwaffen, Stielwurfwaffen, Waffenloser Kampf, Stichwaffen (4)