fix(server): allow ranked equipment with constant health options

This commit is contained in:
2026-10-04 11:26:05 +08:00
parent 8891426abe
commit d585172514
3 changed files with 59 additions and 1 deletions
@@ -118,7 +118,9 @@ func equipmentStatContribution(rule EquipmentStatRule, option EquipmentOption, s
}
var ranks float32
for i, rank := range option.Rank {
if rank == 0 {
// EquipmentInfo.GetEquipOptionByRank returns zero for an empty
// RankValueN curve, even when the equipment has a rank there.
if rank == 0 || len(rule.Ranks[i]) == 0 {
continue
}
if rank < 1 || rank > len(rule.Ranks[i]) {
@@ -75,3 +75,20 @@ func TestEquipmentOptionContributionTruncatesRatherThanRounds(t *testing.T) {
t.Fatalf("fraction percent=%+v err=%v", percent, err)
}
}
func TestEquipmentHealthStatsAllowRankedItemsWithEmptyOptionRankCurves(t *testing.T) {
// EquipmentInfo.GetEquipOptionByRank (0x06003145) returns zero when
// RankValue1/2/3 is empty. Rank belongs to the equipment, not each option.
d := &EquipmentStatDesign{Options: map[[2]uint64]EquipmentStatRule{
{10, 1}: {Default: 10, Growth: 2, Levels: []float64{0, 1}},
{20, 2}: {Default: 0.1, Growth: 0.01, Levels: []float64{0, 1}, Ranks: [3][]float64{nil, {0.5, 1}, nil}},
}}
flat, err := d.HealthContribution(EquipmentOption{GroupID: 10, ID: 1, Level: 1, Rank: [3]int{2, 3, 4}}, false)
if err != nil || flat.Flat != 12 {
t.Fatalf("empty rank curve contribution=%+v err=%v", flat, err)
}
percent, err := d.HealthContribution(EquipmentOption{GroupID: 20, ID: 2, Level: 1, Rank: [3]int{2, 2, 4}}, false)
if err != nil || percent.Percent != 0.12 {
t.Fatalf("mixed rank curve contribution=%+v err=%v", percent, err)
}
}
@@ -46,3 +46,42 @@ func TestEquipmentStatContributionsUseOnlyEquippedOwnerAndAllHealthOptions(t *te
t.Fatal("Find deadlocked calculating equipped health")
}
}
func TestEatFoodWithRankedEquipmentAndEmptyHealthOptionRankCurves(t *testing.T) {
food, inventory, characters := foodTestService(t, stateio.NewMemory())
equipment := &EquipmentInventory{owned: equipmentSnapshot{Equipment: []Equipment{{
InvenIndex: 1, UseChar: 77, Level: 1, Rank: []uint64{2, 2, 2},
MainOption: []EquipmentOption{{GroupID: 10, ID: 1}},
}}}}
if err := equipment.AttachStatDesign(&gamedata.EquipmentStatDesign{Options: map[[2]uint64]gamedata.EquipmentStatRule{
{10, 1}: {Default: 10, Growth: 2, Levels: []float64{0, 1}},
}}); err != nil {
t.Fatal(err)
}
if err := characters.AttachMaxHealth(func(c Character) (uint64, error) {
contributions, err := equipment.StatContributions(c)
if err != nil {
return 0, err
}
return uint64(gamedata.AggregateStats(gamedata.BaseStats{Health: 100}, contributions).Health), nil
}); err != nil {
t.Fatal(err)
}
if err := characters.SetCurrentHealth(77, 20); err != nil {
t.Fatal(err)
}
stacks, err := inventory.GrantOnce("ranked-equipment-food", []gamedata.BattleReward{{Type: 5, ID: 105, Count: 1}})
if err != nil {
t.Fatal(err)
}
if _, _, _, err := food.Handle("/EatFood", foodRequest(1, 77, 21, stacks[0])); err != nil {
t.Fatal(err)
}
// Base 100 + equipment 12 = 112 maximum; the 50% dish heals 56.
if hp, err := characters.CurrentHealth(77); err != nil || hp != 76 {
t.Fatalf("healed health=%d err=%v", hp, err)
}
if len(inventory.All()) != 0 {
t.Fatal("successful recovery did not consume dish")
}
}