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) } } }