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

564 lines
15 KiB
Go
Raw Normal View History

package gameserver
import (
"bytes"
"os"
"testing"
"gac-hkrpg-go/internal/common"
pb "gac-hkrpg-go/protocol/proto"
"google.golang.org/protobuf/proto"
)
func TestCurrencyWarsTalentsHandlers(t *testing.T) {
before, err := os.ReadFile("persistent")
if err != nil {
t.Fatal(err)
}
s := &Session{Token: 12345}
requests := map[string]proto.Message{
"GridFightPermanentTalentEnableCsReq": &pb.GridFightPermanentTalentEnableCsReq{CKHCLKOALKE: ^uint32(0)},
"GridFightSeasonTalentEnableCsReq": &pb.GridFightSeasonTalentEnableCsReq{CKHCLKOALKE: ^uint32(0)},
"GridFightPermanentTalentResetCsReq": &pb.GridFightPermanentTalentResetCsReq{},
"GridFightSeasonTalentResetCsReq": &pb.GridFightSeasonTalentResetCsReq{},
}
for name, req := range requests {
raw, _ := proto.Marshal(req)
if err := typedHandlers[uint16(pb.CmdIDByName[name])](s, raw); err != nil {
t.Fatal(err)
}
after, err := os.ReadFile("persistent")
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(before, after) {
t.Fatalf("unsupported talent request changed persistent: %s", name)
}
}
}
func TestCurrencyWarsArchiveHandlers(t *testing.T) {
c, err := common.LoadCurrencyWarsCatalog()
if err != nil {
t.Fatal(err)
}
data, revision, err := readCurrencyWarsData(c)
if err != nil {
t.Fatal(err)
}
if data == nil {
data, err = newCurrencyWarsData(c)
if err != nil {
t.Fatal(err)
}
}
session := &Session{Token: 54321, currencyWarsRevision: revision}
record := &pb.OEDHMAHDECC{Reason: pb.PEIJNEFOCHI_PEIJNEFOCHI_JOJPGBCECLN}
if err := session.saveCurrencyWarsTerminal(data, c, record); err != nil {
t.Fatal(err)
}
snapshot, err := cwArchiveSnapshot(c)
if err != nil {
t.Fatal(err)
}
entries, err := cwReadArchives(snapshot)
if err != nil || len(entries) == 0 {
t.Fatalf("read archive: %v", err)
}
entry := entries[len(entries)-1]
raw, _ := proto.Marshal(&pb.FavourArchiveCsReq{BAAGDILLLME: entry.BAAGDILLLME, IECKKCHJMLP: true})
if err := typedHandlers[uint16(pb.CmdIDByName["FavourArchiveCsReq"])](session, raw); err != nil {
t.Fatal(err)
}
// A new read/session has no in-memory archive state to rely on.
snapshot, err = cwArchiveSnapshot(c)
if err != nil {
t.Fatal(err)
}
entries, err = cwReadArchives(snapshot)
if err != nil {
t.Fatal(err)
}
if !entries[len(entries)-1].IECKKCHJMLP {
t.Fatal("favourite was not persisted")
}
stale := &Session{currencyWarsRevision: revision}
if err := stale.saveCurrencyWarsTerminal(data, c, record); err == nil {
t.Fatal("stale settlement appended archive")
}
again, err := cwArchiveSnapshot(c)
if err != nil {
t.Fatal(err)
}
if len(again.Archives) != len(snapshot.Archives) {
t.Fatal("failed transaction changed archives")
}
if err := session.saveCurrencyWarsData(data, c); err != nil {
t.Fatal(err)
}
again, err = cwArchiveSnapshot(c)
if err != nil {
t.Fatal(err)
}
if len(again.Archives) != len(snapshot.Archives) {
t.Fatal("ordinary save lost archives")
}
}
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)
}
}
}