Generate equipment loot atomically, grant random boxes and prevent repeat claims. Read gacha pools, pity, products, tickets, missions, presets, chapter rewards and inventory costs from GameData; extend both collaboration UR ticket pools. Sync owned recipes, configure startup state and calendars in versioned seeds, and validate unlock tickets and revival talents.
148 lines
4.9 KiB
Go
148 lines
4.9 KiB
Go
package gamedata
|
|
|
|
import (
|
|
"bd2server/internal/server/wire"
|
|
"database/sql"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestFieldObjectLoadIndependentEquipmentAndRandomBox(t *testing.T) {
|
|
pack, err := sql.Open("sqlite", ":memory:")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer pack.Close()
|
|
common, err := sql.Open("sqlite", ":memory:")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer common.Close()
|
|
for _, table := range []string{"FieldRewardObjectGroupTable", "FieldRewardObjectTable"} {
|
|
if _, err := pack.Exec("CREATE TABLE " + table + "(id INTEGER, ProtoBuf BLOB)"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
for _, table := range []string{"RewardGroupTable", "EquipmentTable"} {
|
|
if _, err := common.Exec("CREATE TABLE " + table + "(id INTEGER, ProtoBuf BLOB)"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if _, err := common.Exec("CREATE TABLE EquipmentOptionTable(GroupId INTEGER, id INTEGER, ProtoBuf BLOB)"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
group := wire.AppendVarint(nil, 10, 66)
|
|
group = wire.AppendVarint(group, 12, 8) // A newly introduced object kind must not need an allowlist.
|
|
if _, err := pack.Exec("INSERT INTO FieldRewardObjectGroupTable VALUES(?,?)", 55, group); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
object := wire.AppendVarint(nil, 3, 55)
|
|
object = wire.AppendVarint(object, 6, 44)
|
|
if _, err := pack.Exec("INSERT INTO FieldRewardObjectTable VALUES(?,?)", 33, object); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
loot := wire.AppendVarint(nil, 2, 1)
|
|
for _, r := range []BattleReward{{Type: 10, ID: 77, Count: 2}, {Type: 9, ID: 88, Count: 1}} {
|
|
loot = wire.AppendVarint(loot, 4, r.Count)
|
|
loot = wire.AppendVarint(loot, 5, r.ID)
|
|
loot = wire.AppendVarint(loot, 6, r.Type)
|
|
loot = wire.AppendVarint(loot, 8, 100)
|
|
}
|
|
if _, err := common.Exec("INSERT INTO RewardGroupTable VALUES(?,?)", 66, loot); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
equip := wire.AppendVarint(nil, 3, 3)
|
|
equip = wire.AppendVarint(equip, 12, 99)
|
|
if _, err := common.Exec("INSERT INTO EquipmentTable VALUES(?,?)", 77, equip); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
option := wire.AppendVarint(nil, 5, 7)
|
|
option = wire.AppendVarint(option, 2, 100)
|
|
if _, err := common.Exec("INSERT INTO EquipmentOptionTable VALUES(?,?,?)", 99, 7, option); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
d, err := loadFieldObjects(pack, common)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := d.Objects[33].Draw()
|
|
if err != nil || len(got) != 2 || got[0].ID != 77 || got[0].Count != 2 || got[1].Type != 9 || got[1].ID != 88 {
|
|
t.Fatalf("rewards=%+v err=%v", got, err)
|
|
}
|
|
main, _, _, err := d.Equipment.RollOptions(77)
|
|
if err != nil || len(main) != 1 || main[0].GroupID != 99 || main[0].ID != 7 {
|
|
t.Fatalf("options=%+v err=%v", main, err)
|
|
}
|
|
}
|
|
|
|
func TestFieldResetUsesConfiguredBoundary(t *testing.T) {
|
|
s := FieldResetSchedule{DailyReset: 9 * time.Hour, WeeklyDay: time.Monday}
|
|
before := time.Date(2026, 10, 4, 23, 59, 59, 0, time.UTC)
|
|
after := before.Add(time.Second)
|
|
for _, r := range []int{0, 3} {
|
|
a, _ := s.Period(r, before)
|
|
b, _ := s.Period(r, after)
|
|
if a == b {
|
|
t.Fatalf("reset%d did not advance at official boundary %s", r, a)
|
|
}
|
|
}
|
|
s.DailyReset = 10 * time.Hour
|
|
a, _ := s.Period(0, after)
|
|
b, _ := s.Period(0, after.Add(time.Hour))
|
|
if a == b {
|
|
t.Fatal("changed design reset ignored")
|
|
}
|
|
once, _ := s.Period(1, after)
|
|
if once != "once" {
|
|
t.Fatal(once)
|
|
}
|
|
if _, err := s.Period(2, after); err == nil {
|
|
t.Fatal("invented event reset")
|
|
}
|
|
}
|
|
func TestFieldWeightedDrawCountAndZeroWeight(t *testing.T) {
|
|
o := FieldRewardObject{DropCount: 2, Rewards: []BattleReward{{Type: 5, ID: 1, Count: 142}, {Type: 5, ID: 2, Count: 71}}, Ratios: []uint64{0, 100}}
|
|
for i := 0; i < 20; i++ {
|
|
got, err := o.Draw()
|
|
if err != nil || len(got) != 2 || got[0].ID != 2 || got[1].ID != 2 {
|
|
t.Fatalf("draw=%+v err=%v", got, err)
|
|
}
|
|
}
|
|
o.DropType = 1
|
|
o.DropCount = 0
|
|
got, err := o.Draw()
|
|
if err != nil || len(got) != 1 || got[0].ID != 2 {
|
|
t.Fatalf("independent draw=%+v err=%v", got, err)
|
|
}
|
|
}
|
|
|
|
func TestFieldDrawRejectsMalformedUnselectedBranch(t *testing.T) {
|
|
o := FieldRewardObject{DropCount: 1, Rewards: []BattleReward{{Type: 5, ID: 1, Count: 1}, {Type: 5, ID: 2}}, Ratios: []uint64{100, 0}}
|
|
if _, err := o.Draw(); err == nil {
|
|
t.Fatal("zero-weight malformed reward accepted")
|
|
}
|
|
o.Rewards[1].Count = 1
|
|
o.DropType = 1
|
|
o.Ratios[1] = 101
|
|
if _, err := o.Draw(); err == nil {
|
|
t.Fatal("independent percentage above 100 accepted")
|
|
}
|
|
}
|
|
|
|
func TestFieldIndependentDrawUsesEveryPercentageOnce(t *testing.T) {
|
|
o := FieldRewardObject{DropType: 1, DropCount: 99, Ratios: []uint64{0, 25, 25, 100}, Rewards: []BattleReward{{ID: 1, Count: 1}, {ID: 2, Count: 1}, {ID: 3, Count: 1}, {ID: 4, Count: 1}}}
|
|
values := []uint64{24, 25}
|
|
calls := 0
|
|
got, err := o.draw(func(limit uint64) (uint64, error) {
|
|
if limit != 100 || calls >= len(values) {
|
|
t.Fatalf("unexpected draw limit=%d calls=%d", limit, calls)
|
|
}
|
|
v := values[calls]
|
|
calls++
|
|
return v, nil
|
|
})
|
|
if err != nil || calls != 2 || len(got) != 2 || got[0].ID != 2 || got[1].ID != 4 {
|
|
t.Fatalf("independent percentage boundary: got=%+v calls=%d err=%v", got, calls, err)
|
|
}
|
|
}
|