Files
GaCy-GO/internal/gameserver/currency_wars_test.go
T

193 lines
6.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")
}
})
}
}
func TestInspectUserCurrentState(t *testing.T) {
catalog, err := common.LoadCurrencyWarsCatalog()
if err != nil {
t.Fatal(err)
}
saved, rev, err := readCurrencyWarsData(catalog)
if err != nil {
t.Fatal(err)
}
t.Logf("revision=%d", rev)
run := saved.DHFOFLFCLAH
if run == nil {
t.Skip("no active run in persistent snapshot")
}
stage, display := cwStage(run)
t.Logf("stage: %d/%d display: stage_id=%d type=%d", stage.DCAHJELNJNP, stage.SectionId, display.StageId, display.CJCCMAACJDB)
t.Logf("pending: %+v", run.FMIGOKIDKJL)
bag := cwEquipmentBag(run)
if bag != nil {
t.Logf("bag items (%d):", len(bag.GridFightEquipmentList))
for _, eq := range bag.GridFightEquipmentList {
t.Logf(" eq uid=%d id=%d source=%d", eq.UniqueId, eq.GridFightEquipmentId, eq.Source)
}
}
_, team, _ := cwComponents(run)
if team != nil {
t.Logf("team roles (%d):", len(team.GridGameRoleList))
for _, r := range team.GridGameRoleList {
t.Logf(" role uid=%d id=%d star=%d pos=%d eq=%v", r.UniqueId, r.Id, r.RoleStar, r.Pos, r.PNJIPHJJONG)
}
}
}