Files
GaCy-GO/internal/gameserver/currency_wars_test.go
T
2026-10-28 18:54:43 +07:00

160 lines
5.7 KiB
Go

package gameserver
import (
"strings"
"testing"
"gac-hkrpg-go/internal/common"
goproto "gac-hkrpg-go/protocol/proto"
)
func TestCurrencyWarsEntranceUsesCapturedRoomConditionWithoutMutatingScene(t *testing.T) {
scene := &common.LevelOutputConfig{SavedValues: map[string]int32{
"FSV_370GridFight": 0,
"unrelated": 7,
}}
group := common.CurrencyWarsRoomGroup{GroupID: 1}
group.Condition.Name = "FSV_370GridFight"
group.Condition.Comparison = "GreaterEqual"
group.Condition.Value = 1
entrance := common.CurrencyWarsEntrance{
EntryID: 2,
RoomGroups: []common.CurrencyWarsRoomGroup{group},
SavedValues: map[string]int32{"FSV_370GridFight": 2},
}
got, err := currencyWarsEntranceScene(scene, entrance)
if err != nil {
t.Fatalf("apply captured CW entrance profile: %v", err)
}
if got.SavedValues["FSV_370GridFight"] != 2 || got.SavedValues["unrelated"] != 7 {
t.Fatalf("CW entrance profile = %v; want selected and unrelated values preserved", got.SavedValues)
}
if scene.SavedValues["FSV_370GridFight"] != 0 {
t.Fatal("applying CW freeplay profile mutated shared scene data")
}
got.SavedValues["FSV_370GridFight"] = 99
if scene.SavedValues["FSV_370GridFight"] != 0 {
t.Fatal("returned CW scene shares its saved-value map with the original")
}
}
func TestCurrencyWarsSelectedEnemyBranchUsesSavedOneBasedChoice(t *testing.T) {
first := &goproto.KKBGIPCAGGH{IFIIOIIKJHO: 91}
second := &goproto.KKBGIPCAGGH{IFIIOIIKJHO: 92}
for _, tc := range []struct {
name string
selection uint32
branches []*goproto.KKBGIPCAGGH
want *goproto.KKBGIPCAGGH
wantErr string
}{
{name: "select first offered branch", selection: 1, branches: []*goproto.KKBGIPCAGGH{first, second}, want: first},
{name: "select second offered branch", selection: 2, branches: []*goproto.KKBGIPCAGGH{first, second}, want: second},
{name: "selection is required", selection: 0, branches: []*goproto.KKBGIPCAGGH{first}, wantErr: "outside"},
{name: "selection must be offered", selection: 2, branches: []*goproto.KKBGIPCAGGH{first}, wantErr: "outside"},
{name: "selected branch cannot be empty", selection: 1, branches: []*goproto.KKBGIPCAGGH{nil}, wantErr: "is empty"},
{name: "no offered branches", selection: 1, wantErr: "no offered plans"},
} {
t.Run(tc.name, func(t *testing.T) {
got, err := cwSelectedEnemyBranch(&goproto.MDJBBILMHLC{MOADBALHMFD: tc.selection, BPDABHGFBFO: tc.branches})
if tc.wantErr != "" {
if err == nil || !strings.Contains(err.Error(), tc.wantErr) {
t.Fatalf("branch error = %v; want %q", err, tc.wantErr)
}
return
}
if err != nil {
t.Fatalf("resolve selected branch: %v", err)
}
if got != tc.want {
t.Fatalf("selected plan = %p; want %p", got, tc.want)
}
})
}
}
func TestCurrencyWarsBattleAndRefreshRequestsUseTypedHandlers(t *testing.T) {
for _, name := range []string{
"GridFightEnterBattleStageCsReq",
"GridFightRefreshShopCsReq",
} {
t.Run(name, func(t *testing.T) {
id, ok := goproto.CmdIDByName[name]
if !ok {
t.Fatalf("protocol command table is missing %s", name)
}
if typedHandlers[uint16(id)] == nil {
t.Fatalf("implemented CW request %s has no typed handler", name)
}
if rawHandlers[uint16(id)] != nil {
t.Fatalf("CW request %s is registered through an untyped handler", name)
}
if _, ok := dummyResponses[uint16(id)]; ok {
t.Fatalf("CW request %s unexpectedly has a dummy response", name)
}
if !isDangerCmd(uint16(id)) {
t.Fatalf("CW request %s is not protected from dispatcher auto-replies", name)
}
if _, ok := goproto.RspIDByReqID[uint32(id)]; !ok {
t.Fatalf("test request %s has no paired response to protect", name)
}
})
}
getDataID, ok := goproto.CmdIDByName["GridFightGetDataCsReq"]
if !ok || typedHandlers[uint16(getDataID)] == nil {
t.Fatal("the verified CW bootstrap request must keep its typed handler")
}
}
func TestCurrencyWarsStartGameplayUsesCurrentTypedRequestHandler(t *testing.T) {
id := uint16(goproto.CmdIDByName["GridFightStartGamePlayCsReq"])
if typedHandlers[id] == nil {
t.Fatal("CW start request must have a typed gameplay handler")
}
if rawHandlers[id] != nil {
t.Fatal("CW start request must not use an untyped mode-observation handler")
}
if _, ok := dummyResponses[id]; ok {
t.Fatal("CW start request must not receive an empty response")
}
if !isDangerCmd(id) {
t.Fatal("CW start request must remain protected from dispatcher auto-replies")
}
if _, ok := goproto.RspIDByReqID[uint32(id)]; !ok {
t.Fatal("CW start request has no paired response in the current protocol table")
}
}
func TestCurrencyWarsEntranceRejectsUnsupportedOrUnmetConditions(t *testing.T) {
tests := []struct {
name string
value int32
comparison string
wantErr string
}{
{name: "condition unmet", value: 0, comparison: "GreaterEqual", wantErr: "condition is not satisfied"},
{name: "unsupported comparison", value: 2, comparison: "Equal", wantErr: "unsupported CW room comparison"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
scene := &common.LevelOutputConfig{SavedValues: map[string]int32{"condition": 0}}
group := common.CurrencyWarsRoomGroup{GroupID: 1}
group.Condition.Name = "condition"
group.Condition.Comparison = tt.comparison
group.Condition.Value = 1
entrance := common.CurrencyWarsEntrance{
RoomGroups: []common.CurrencyWarsRoomGroup{group},
SavedValues: map[string]int32{"condition": tt.value},
}
if _, err := currencyWarsEntranceScene(scene, entrance); err == nil || !strings.Contains(err.Error(), tt.wantErr) {
t.Fatalf("entrance error = %v; want %q", err, tt.wantErr)
}
if scene.SavedValues["condition"] != 0 {
t.Fatal("rejected CW profile mutated shared scene data")
}
})
}
}