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

618 lines
17 KiB
Go

package gameserver
import (
"os"
"testing"
"gac-hkrpg-go/internal/common"
pb "gac-hkrpg-go/protocol/proto"
"google.golang.org/protobuf/proto"
)
func TestCurrencyWarsTalentsHandlers(t *testing.T) {
origPersistent, _ := os.ReadFile("persistent")
if origPersistent != nil {
t.Cleanup(func() {
_ = os.WriteFile("persistent", origPersistent, 0644)
})
}
session := &Session{Token: 12345}
catalog, err := common.LoadCurrencyWarsCatalog()
if err != nil {
t.Fatalf("load catalog: %v", err)
}
data, rev, err := readCurrencyWarsData(catalog)
if err != nil || data == nil {
data, err = newCurrencyWarsData(catalog)
if err != nil {
t.Fatalf("new data: %v", err)
}
rev = 0
}
session.currencyWarsRevision = rev
if err := session.saveCurrencyWarsData(data, catalog); err != nil {
t.Fatalf("save initial data: %v", err)
}
// 1. Enable Permanent Talent
permReq := &pb.GridFightPermanentTalentEnableCsReq{CKHCLKOALKE: 101}
permCmdID, ok := pb.CmdIDByName["GridFightPermanentTalentEnableCsReq"]
if !ok || typedHandlers[uint16(permCmdID)] == nil {
t.Fatal("handler GridFightPermanentTalentEnable not registered")
}
rawPerm, err := proto.Marshal(permReq)
if err != nil {
t.Fatalf("marshal permReq: %v", err)
}
if err := typedHandlers[uint16(permCmdID)](session, rawPerm); err != nil {
t.Fatalf("call GridFightPermanentTalentEnable: %v", err)
}
saved, _, err := readCurrencyWarsData(catalog)
if err != nil || saved == nil {
t.Fatalf("read saved data: %v", err)
}
if saved.LFLCDNGALME == nil || saved.LFLCDNGALME.PKELAIPIHMF == nil || saved.LFLCDNGALME.PKELAIPIHMF.MHFHLBHFIBC == nil {
t.Fatal("permanent talent container missing")
}
found := false
for _, id := range saved.LFLCDNGALME.PKELAIPIHMF.MHFHLBHFIBC.BDGMNPIJDDL {
if id == 101 {
found = true
break
}
}
if !found {
t.Errorf("permanent talent 101 was not persisted: %v", saved.LFLCDNGALME.PKELAIPIHMF.MHFHLBHFIBC.BDGMNPIJDDL)
}
// 2. Enable Season Talent
seasonReq := &pb.GridFightSeasonTalentEnableCsReq{CKHCLKOALKE: 202}
seasonCmdID, ok := pb.CmdIDByName["GridFightSeasonTalentEnableCsReq"]
if !ok || typedHandlers[uint16(seasonCmdID)] == nil {
t.Fatal("handler GridFightSeasonTalentEnable not registered")
}
rawSeason, err := proto.Marshal(seasonReq)
if err != nil {
t.Fatalf("marshal seasonReq: %v", err)
}
if err := typedHandlers[uint16(seasonCmdID)](session, rawSeason); err != nil {
t.Fatalf("call GridFightSeasonTalentEnable: %v", err)
}
saved, _, _ = readCurrencyWarsData(catalog)
if saved.LFLCDNGALME == nil || saved.LFLCDNGALME.HFPEJIDDDJG == nil || saved.LFLCDNGALME.HFPEJIDDDJG.MHFHLBHFIBC == nil {
t.Fatal("season talent container missing")
}
foundSeason := false
for _, id := range saved.LFLCDNGALME.HFPEJIDDDJG.MHFHLBHFIBC.BDGMNPIJDDL {
if id == 202 {
foundSeason = true
break
}
}
if !foundSeason {
t.Errorf("season talent 202 was not persisted: %v", saved.LFLCDNGALME.HFPEJIDDDJG.MHFHLBHFIBC.BDGMNPIJDDL)
}
// 3. Reset Talents
resetCmdID, ok := pb.CmdIDByName["GridFightPermanentTalentResetCsReq"]
if ok && typedHandlers[uint16(resetCmdID)] != nil {
rawReset, _ := proto.Marshal(&pb.GridFightPermanentTalentResetCsReq{})
_ = typedHandlers[uint16(resetCmdID)](session, rawReset)
}
saved, _, _ = readCurrencyWarsData(catalog)
if len(saved.LFLCDNGALME.PKELAIPIHMF.MHFHLBHFIBC.BDGMNPIJDDL) != 0 {
t.Errorf("expected permanent talents to be empty after reset, got %v", saved.LFLCDNGALME.PKELAIPIHMF.MHFHLBHFIBC.BDGMNPIJDDL)
}
}
func TestCurrencyWarsArchiveHandlers(t *testing.T) {
record := &pb.OEDHMAHDECC{
Reason: pb.PEIJNEFOCHI_PEIJNEFOCHI_JOJPGBCECLN,
IsOverlock: false,
}
entry := cwRecordArchive(record, 2, 1)
if entry == nil || entry.BAAGDILLLME == 0 {
t.Fatal("archive record failed")
}
session := &Session{Token: 54321}
// Toggle Favour
favourReq := &pb.FavourArchiveCsReq{BAAGDILLLME: entry.BAAGDILLLME, IECKKCHJMLP: true}
favourCmdID, ok := pb.CmdIDByName["FavourArchiveCsReq"]
if !ok || typedHandlers[uint16(favourCmdID)] == nil {
t.Fatal("FavourArchive handler missing")
}
rawFavour, err := proto.Marshal(favourReq)
if err != nil {
t.Fatalf("marshal favourReq: %v", err)
}
if err := typedHandlers[uint16(favourCmdID)](session, rawFavour); err != nil {
t.Fatalf("call FavourArchive: %v", err)
}
foundFavour := false
globalCWArchive.mu.RLock()
for _, e := range globalCWArchive.entries {
if e.BAAGDILLLME == entry.BAAGDILLLME && e.IECKKCHJMLP {
foundFavour = true
break
}
}
globalCWArchive.mu.RUnlock()
if !foundFavour {
t.Errorf("expected archive %d to be favoured", entry.BAAGDILLLME)
}
// Get Archive
getArchiveCmdID, ok := pb.CmdIDByName["GridFightGetArchiveCsReq"]
if !ok || typedHandlers[uint16(getArchiveCmdID)] == nil {
t.Fatal("GridFightGetArchive handler missing")
}
rawGetArchive, _ := proto.Marshal(&pb.GridFightGetArchiveCsReq{Type: pb.MLJIILDNDPL_MLJIILDNDPL_FAGECPGMPEH})
if err := typedHandlers[uint16(getArchiveCmdID)](session, rawGetArchive); err != nil {
t.Fatalf("call GridFightGetArchive: %v", err)
}
}
func TestCurrencyWarsCalculateBoardTraits(t *testing.T) {
catalog, err := common.LoadCurrencyWarsCatalog()
if err != nil {
t.Fatalf("load catalog: %v", err)
}
// Role 100402 (Welt) in Season 2 has TraitList: [ 100502, 200302 ]
run := &pb.MPIMAEMCEFH{
NAGDJGMMCBE: []*pb.GridFightGameInfo{
{
LEOIAOFBBFA: &pb.GridFightGameInfo_EJIPINIKNCK{
EJIPINIKNCK: &pb.HKFECLCHBFJ{
GridGameRoleList: []*pb.CDEAABODKCP{
{Id: 100402, RoleStar: 1, Pos: 1, UniqueId: 101},
{Id: 100402, RoleStar: 1, Pos: 2, UniqueId: 102},
},
},
},
},
},
}
traits := cwCalculateBoardTraits(catalog, run)
if len(traits) == 0 {
t.Fatal("expected computed traits for deployed roles")
}
foundTrait := false
for _, tr := range traits {
if tr.TraitId == 100502 || tr.TraitId == 200302 {
foundTrait = true
break
}
}
if !foundTrait {
t.Errorf("expected Welt traits to be computed, got %v", traits)
}
}
func TestCurrencyWarsOrbDropsAndClaim(t *testing.T) {
origPersistent, _ := os.ReadFile("persistent")
if origPersistent != nil {
t.Cleanup(func() {
_ = os.WriteFile("persistent", origPersistent, 0644)
})
}
catalog, err := common.LoadCurrencyWarsCatalog()
if err != nil {
t.Fatalf("load catalog: %v", err)
}
// 1. Test cwRollNodeOrbs for various stages in Route 2801
// 1-1: 900 -> 4 rolls
orbs1_1, err := cwRollNodeOrbs(catalog, nil, 2801, 1, 1, 100)
if err != nil {
t.Fatalf("cwRollNodeOrbs 1-1: %v", err)
}
if len(orbs1_1) != 4 {
t.Errorf("expected 4 orbs from 1-1, got %d", len(orbs1_1))
}
// Progress 50% on 1-1: (4*50 + 99)/100 = 2 orbs
orbs1_1_half, err := cwRollNodeOrbs(catalog, nil, 2801, 1, 1, 50)
if err != nil {
t.Fatalf("cwRollNodeOrbs 1-1 half: %v", err)
}
if len(orbs1_1_half) != 2 {
t.Errorf("expected 2 orbs from 1-1 at 50%% progress, got %d", len(orbs1_1_half))
}
// 1-5 (monster 902 in route 2801): 8 rolls
orbs1_5, err := cwRollNodeOrbs(catalog, nil, 2801, 1, 5, 100)
if err != nil {
t.Fatalf("cwRollNodeOrbs 1-5: %v", err)
}
if len(orbs1_5) != 8 {
t.Errorf("expected 8 orbs from 1-5, got %d", len(orbs1_5))
}
// 2-4 (monster 903): 10 rolls
orbs2_4, err := cwRollNodeOrbs(catalog, nil, 2801, 2, 4, 100)
if err != nil {
t.Fatalf("cwRollNodeOrbs 2-4: %v", err)
}
if len(orbs2_4) != 10 {
t.Errorf("expected 10 orbs from 2-4, got %d", len(orbs2_4))
}
// 3-4 (monster 904): 6 rolls
orbs3_4, err := cwRollNodeOrbs(catalog, nil, 2801, 3, 4, 100)
if err != nil {
t.Fatalf("cwRollNodeOrbs 3-4: %v", err)
}
if len(orbs3_4) != 6 {
t.Errorf("expected 6 orbs from 3-4, got %d", len(orbs3_4))
}
// Non-combat / Supply node (1-3): 0 orbs
orbs1_3, err := cwRollNodeOrbs(catalog, nil, 2801, 1, 3, 100)
if err != nil {
t.Fatalf("cwRollNodeOrbs 1-3: %v", err)
}
if len(orbs1_3) != 0 {
t.Errorf("expected 0 orbs from supply node 1-3, got %d", len(orbs1_3))
}
// 2. Test cwUseOrbs with Orb 305 and multiple orbs
session := &Session{Token: 99887}
orbBag := &pb.DBONCGFHHDN{
MDKGCIKOMLI: []*pb.IBPDPCOGDEP{
{UniqueId: 5001, LDBNOMPCOKK: 102},
{UniqueId: 5002, LDBNOMPCOKK: 103},
{UniqueId: 5003, LDBNOMPCOKK: 305},
},
}
data, err := newCurrencyWarsData(catalog)
if err != nil {
t.Fatalf("new data: %v", err)
}
testRun := &pb.MPIMAEMCEFH{
UniqueId: 99887,
LJNGPCKPJLF: catalog.ActiveSeasonID,
DivisionId: data.LFLCDNGALME.HFPEJIDDDJG.DivisionId,
NAGDJGMMCBE: []*pb.GridFightGameInfo{
{
LEOIAOFBBFA: &pb.GridFightGameInfo_KLCPAAHDPJF{
KLCPAAHDPJF: &pb.BEEFLEJAOCN{
DCAHJELNJNP: 1,
SectionId: 1,
EMOHLJHPOPC: 2801,
},
},
},
{
LEOIAOFBBFA: &pb.GridFightGameInfo_FAOCFBOFCJP{
FAOCFBOFCJP: &pb.NIHNGPKHEPC{
Gold: 100,
MICEOJBAKGN: 4,
},
},
},
{
LEOIAOFBBFA: &pb.GridFightGameInfo_EJIPINIKNCK{
EJIPINIKNCK: &pb.HKFECLCHBFJ{},
},
},
{
LEOIAOFBBFA: &pb.GridFightGameInfo_GCKLIONKMJC{
GCKLIONKMJC: &pb.CONONEALNOF{},
},
},
{
LEOIAOFBBFA: &pb.GridFightGameInfo_EBHDIOGALIE{
EBHDIOGALIE: &pb.POCJDMMANBM{},
},
},
{
LEOIAOFBBFA: &pb.GridFightGameInfo_DBAGKJNPDOL{
DBAGKJNPDOL: orbBag,
},
},
},
}
data.DHFOFLFCLAH = testRun
_, rev, _ := readCurrencyWarsData(catalog)
session.currencyWarsRevision = rev
if err := session.saveCurrencyWarsData(data, catalog); err != nil {
t.Fatalf("save initial data with orbs: %v", err)
}
// Claim all orbs
useReq := &pb.IKMOCJBMGOJ{IsGetAll: true}
if err := cwUseOrbs(session, useReq); err != nil {
t.Fatalf("cwUseOrbs with IsGetAll: %v", err)
}
// Verify bag in saved data is now empty
saved, _, err := readCurrencyWarsData(catalog)
if err != nil || saved == nil || saved.DHFOFLFCLAH == nil {
t.Fatalf("read saved data: %v", err)
}
var savedBag *pb.DBONCGFHHDN
for _, c := range saved.DHFOFLFCLAH.NAGDJGMMCBE {
if b := c.GetDBAGKJNPDOL(); b != nil {
savedBag = b
break
}
}
if savedBag != nil && len(savedBag.MDKGCIKOMLI) != 0 {
t.Errorf("expected 0 orbs left in bag, got %d", len(savedBag.MDKGCIKOMLI))
}
// 3. Test idempotency: clicking already consumed or ghost orbs must NOT return error (avoids 6900 error toast)
ghostReq := &pb.IKMOCJBMGOJ{CBMGDBJHLHO: []uint32{5001, 5002, 5003}}
if err := cwUseOrbs(session, ghostReq); err != nil {
t.Fatalf("cwUseOrbs on already consumed orbs returned error: %v", err)
}
// Calling IsGetAll when bag is already empty must also succeed cleanly
if err := cwUseOrbs(session, &pb.IKMOCJBMGOJ{IsGetAll: true}); err != nil {
t.Fatalf("cwUseOrbs with IsGetAll on empty bag returned error: %v", err)
}
}
func TestCurrencyWarsTerminalSettlementLogic(t *testing.T) {
catalog, err := common.LoadCurrencyWarsCatalog()
if err != nil {
t.Fatalf("load catalog: %v", err)
}
run := &pb.MPIMAEMCEFH{
UniqueId: 999,
LJNGPCKPJLF: catalog.ActiveSeasonID,
DivisionId: 1,
NAGDJGMMCBE: []*pb.GridFightGameInfo{
{
LEOIAOFBBFA: &pb.GridFightGameInfo_KLCPAAHDPJF{
KLCPAAHDPJF: &pb.BEEFLEJAOCN{
DCAHJELNJNP: 3,
SectionId: 5,
EMOHLJHPOPC: 2801,
},
},
},
{
LEOIAOFBBFA: &pb.GridFightGameInfo_FAOCFBOFCJP{
FAOCFBOFCJP: &pb.NIHNGPKHEPC{
Gold: 500,
GridFightLineupHp: 100,
PPKKHHNEOHB: 100,
GDAKCOICIDA: 7,
},
},
},
{
LEOIAOFBBFA: &pb.GridFightGameInfo_EJIPINIKNCK{
EJIPINIKNCK: &pb.HKFECLCHBFJ{
GridGameRoleList: []*pb.CDEAABODKCP{
{Id: 1001, UniqueId: 1, Pos: 1, RoleStar: 3},
},
},
},
},
},
}
keepWin, settle, err := currencyWarsTerminalSettlement(catalog, nil, run, pb.PEIJNEFOCHI_PEIJNEFOCHI_JOJPGBCECLN, true, nil)
if err != nil {
t.Fatalf("currencyWarsTerminalSettlement failed: %v", err)
}
if keepWin == nil || settle == nil {
t.Fatal("expected non-nil keepWin and settle notifications")
}
if settle.BFLLJJBHLDF == nil || settle.BFLLJJBHLDF.Reason != pb.PEIJNEFOCHI_PEIJNEFOCHI_JOJPGBCECLN {
t.Errorf("expected victory reason in settlement, got %v", settle.BFLLJJBHLDF)
}
if settle.BFLLJJBHLDF.NDLIKNEKDLL.DCAHJELNJNP != 3 || settle.BFLLJJBHLDF.NDLIKNEKDLL.SectionId != 5 {
t.Errorf("expected 3-5 stage in settlement, got %d-%d", settle.BFLLJJBHLDF.NDLIKNEKDLL.DCAHJELNJNP, settle.BFLLJJBHLDF.NDLIKNEKDLL.SectionId)
}
}
func TestCurrencyWarsBonfireSelectHeal(t *testing.T) {
origPersistent, _ := os.ReadFile("persistent")
if origPersistent != nil {
t.Cleanup(func() {
_ = os.WriteFile("persistent", origPersistent, 0644)
})
}
catalog, err := common.LoadCurrencyWarsCatalog()
if err != nil {
t.Fatalf("load catalog: %v", err)
}
offer := &pb.NCEPOLNLNKH{
Phase: cwBonfireChoosing,
EIFICNFGGHI: 10,
AHJJJNBOMPG: 2,
DANCNIMJOLE: 3,
KFNMIEMBJGD: []*pb.MHCOIBONODA{
{EventId: 10200601, HAFOKMHCGFM: 1},
},
}
run := &pb.MPIMAEMCEFH{
UniqueId: 101,
LJNGPCKPJLF: catalog.ActiveSeasonID,
DivisionId: 1,
FMIGOKIDKJL: &pb.CHCHAJIANIM{
QueuePosition: 795,
FGLIODAJFML: &pb.CHCHAJIANIM_IIDBLCFAAPC{
IIDBLCFAAPC: offer,
},
},
NAGDJGMMCBE: []*pb.GridFightGameInfo{
{
LEOIAOFBBFA: &pb.GridFightGameInfo_KLCPAAHDPJF{
KLCPAAHDPJF: &pb.BEEFLEJAOCN{
DCAHJELNJNP: 1,
SectionId: 5,
EMOHLJHPOPC: 2801,
BossInfo: &pb.KGMCLFHAJNJ{NCGHKPEMPDP: []*pb.AHKDPCIIELA{{MonsterId: 1004016, RoleStar: 1, LOGHLGKIFEN: 1}}},
HEKMEJLMGFF: []*pb.LHNCMINFONJ{{LOGHLGKIFEN: 1}},
OOMMIIKPHBC: []*pb.BCCIJHPLJOJ{
{DCAHJELNJNP: 1, SectionId: 6, DisplayValue: &pb.OHMDHKGAEMM{StageId: 70000009}},
},
},
},
},
{
LEOIAOFBBFA: &pb.GridFightGameInfo_FAOCFBOFCJP{
FAOCFBOFCJP: &pb.NIHNGPKHEPC{
Gold: 50,
GridFightLineupHp: 60,
PPKKHHNEOHB: 100,
EHNJDIDABHD: 1,
NDAINFLOLPJ: 10,
},
},
},
{
LEOIAOFBBFA: &pb.GridFightGameInfo_EJIPINIKNCK{
EJIPINIKNCK: &pb.HKFECLCHBFJ{},
},
},
{
LEOIAOFBBFA: &pb.GridFightGameInfo_EBHDIOGALIE{
EBHDIOGALIE: &pb.POCJDMMANBM{},
},
},
},
}
// 1. Test Select Heal HP
healReq := &pb.CAMIICJDOBO{
FFBBOFAGGDI: &pb.CAMIICJDOBO_NCGOOEMOBIC{
NCGOOEMOBIC: &pb.MLAENDOFBCK{
FFBBOFAGGDI: &pb.MLAENDOFBCK_LGAJNMNOAEM{
LGAJNMNOAEM: true,
},
},
},
}
notify, _, err := cwSelectBonfire(catalog, run, healReq)
if err != nil {
t.Fatalf("cwSelectBonfire heal failed: %v", err)
}
if notify == nil {
t.Fatal("expected notify from heal")
}
if offer.Phase != cwBonfireSelected {
t.Errorf("expected phase %v, got %v", cwBonfireSelected, offer.Phase)
}
player, _, _ := cwComponents(run)
if player.GridFightLineupHp != 80 {
t.Errorf("expected 80 HP after heal, got %d", player.GridFightLineupHp)
}
// 2. Test Take Bonfire rewards and advance node
takeReq := &pb.CAMIICJDOBO{
FFBBOFAGGDI: &pb.CAMIICJDOBO_ECDACDNDHGO{
ECDACDNDHGO: &pb.EFIEAIJBHIB{},
},
}
takeNotify, _, err := cwSelectBonfire(catalog, run, takeReq)
if err != nil {
t.Fatalf("cwSelectBonfire take failed: %v", err)
}
if takeNotify == nil {
t.Fatal("expected notify from take")
}
stage, _ := cwStage(run)
if stage.SectionId != 6 {
t.Errorf("expected node advance to section 6, got %d", stage.SectionId)
}
}
func TestPrintSaveRoles(t *testing.T) {
catalog, err := common.LoadCurrencyWarsCatalog()
if err != nil {
t.Fatalf("catalog: %v", err)
}
data, _, err := readCurrencyWarsData(catalog)
if err != nil || data == nil || data.DHFOFLFCLAH == nil {
t.Skip("no active run in persistent snapshot")
}
run := data.DHFOFLFCLAH
_, team, _ := cwComponents(run)
if team == nil {
t.Fatal("team nil")
}
for _, r := range team.GridGameRoleList {
v, _ := catalog.CapturedRoleVariant(r.Id, r.RoleStar)
t.Logf("Role ID=%d Star=%d Pos=%d AvatarID=%d BABNHPDGNGB=%v Props=%v ValInit=%v", r.Id, r.RoleStar, r.Pos, v.Config.AvatarID, r.BABNHPDGNGB, r.ConvertPropertyToFixpoint, r.GridFightValueInitComponent)
}
runtime, _ := cwBonfireRuntime(catalog, run)
if runtime != nil {
t.Logf("Runtime rules: %v", runtime.Rules)
}
}
func TestCurrencyWarsGrantRandomOrb(t *testing.T) {
catalog, err := common.LoadCurrencyWarsCatalog()
if err != nil {
t.Fatalf("load catalog: %v", err)
}
run := &pb.MPIMAEMCEFH{
UniqueId: 999,
NAGDJGMMCBE: []*pb.GridFightGameInfo{
{
LEOIAOFBBFA: &pb.GridFightGameInfo_DBAGKJNPDOL{
DBAGKJNPDOL: &pb.DBONCGFHHDN{},
},
},
{
LEOIAOFBBFA: &pb.GridFightGameInfo_FAOCFBOFCJP{
FAOCFBOFCJP: &pb.NIHNGPKHEPC{
Gold: 100,
},
},
},
},
}
cmd := common.CurrencyWarsCommand{
Field: "orb",
Value: 0,
Quantity: 3,
}
notify, err := cwApplyCommand(catalog, run, cmd)
if err != nil {
t.Fatalf("cwApplyCommand random orb: %v", err)
}
if notify == nil || len(notify.GCPNIHHBGGD) == 0 {
t.Fatal("expected notify with update bundle")
}
if len(notify.GCPNIHHBGGD[0].JKPJFKCBIEM) != 3 {
t.Fatalf("expected 3 notify items, got %d", len(notify.GCPNIHHBGGD[0].JKPJFKCBIEM))
}
var bag *pb.DBONCGFHHDN
for _, c := range run.NAGDJGMMCBE {
if b := c.GetDBAGKJNPDOL(); b != nil {
bag = b
break
}
}
if bag == nil || len(bag.MDKGCIKOMLI) != 3 {
t.Fatalf("expected 3 orbs in bag, got %v", bag)
}
// Verify that all granted orbs have valid, implemented IDs
recipes, err := common.LoadCurrencyWarsOrbRecipes()
if err != nil {
t.Fatalf("load recipes: %v", err)
}
implemented := make(map[uint32]bool)
for _, r := range recipes.Recipes {
if r.Status == "Implemented" && r.Engine == "Orb" {
implemented[r.ID] = true
}
}
for i, orb := range bag.MDKGCIKOMLI {
if !implemented[orb.LDBNOMPCOKK] {
t.Errorf("orb[%d] id %d is not an implemented orb", i, orb.LDBNOMPCOKK)
}
}
}