176 lines
6.7 KiB
Go
176 lines
6.7 KiB
Go
package gameserver
|
|
|
|
import (
|
|
"fmt"
|
|
"gac-hkrpg-go/internal/common"
|
|
pb "gac-hkrpg-go/protocol/proto"
|
|
"google.golang.org/protobuf/proto"
|
|
"math"
|
|
)
|
|
|
|
// cwAdvanceRouteNode follows gfPendingAdvanceNode -> gfFxHEnterNode in the
|
|
// reference workflow. The caller owns the detached snapshot and its single CAS
|
|
// commit; this helper grants no rewards and never reads/writes a player save.
|
|
// Saved route displays retain run-specific Tea Break/spotlight substitutions.
|
|
func cwAdvanceRouteNode(catalog *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, runtime *common.CurrencyWarsRuntimeState) (*pb.GridFightSyncUpdateResultScNotify, error) {
|
|
if run == nil || run.FMIGOKIDKJL == nil {
|
|
return nil, fmt.Errorf("CW advance requires current pending queue")
|
|
}
|
|
queue := run.FMIGOKIDKJL.QueuePosition
|
|
if queue == math.MaxUint32 {
|
|
return nil, fmt.Errorf("CW pending queue exhausted")
|
|
}
|
|
stage, _ := cwStage(run)
|
|
if stage == nil {
|
|
return nil, fmt.Errorf("CW advance stage missing")
|
|
}
|
|
route, err := catalog.RouteNodes(stage.EMOHLJHPOPC)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
index := -1
|
|
for i, row := range route {
|
|
if row.ChapterID == stage.DCAHJELNJNP && row.SectionID == stage.SectionId {
|
|
index = i
|
|
break
|
|
}
|
|
}
|
|
if index < 0 || index+1 >= len(route) {
|
|
return nil, fmt.Errorf("CW route has no next node")
|
|
}
|
|
next := route[index+1]
|
|
var display *pb.OHMDHKGAEMM
|
|
for _, node := range stage.OOMMIIKPHBC {
|
|
if node != nil && node.DCAHJELNJNP == next.ChapterID && node.SectionId == next.SectionID {
|
|
if display != nil {
|
|
return nil, fmt.Errorf("CW duplicate next route display")
|
|
}
|
|
display = node.DisplayValue
|
|
}
|
|
}
|
|
if display == nil {
|
|
return nil, fmt.Errorf("CW next route display missing")
|
|
}
|
|
refs, err := common.LoadCurrencyWarsReferenceNodes(catalog)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
template, err := refs.Template(next.NodeTemplateID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if template.StageID != display.StageId {
|
|
return nil, fmt.Errorf("CW next template/display stage differs")
|
|
}
|
|
// Build against a temporary run at the destination: the enemy constructor
|
|
// selects that chapter's saved boss/camp, leaving the source untouched if a
|
|
// recipe fails validation. Dynamic non-enemy nodes need their own factory.
|
|
shadow := proto.Clone(run).(*pb.MPIMAEMCEFH)
|
|
shadowStage, _ := cwStage(shadow)
|
|
shadowStage.DCAHJELNJNP = next.ChapterID
|
|
shadowStage.SectionId = next.SectionID
|
|
shadowStage.IMPNAHBCADN = nil
|
|
var layer *pb.NCKOKDHNJDC
|
|
var difficulty *pb.KFNCAIHIHCM
|
|
if template.NodeType == "Supply" {
|
|
layer, err = cwCapturedSupplyLayer(catalog)
|
|
} else if template.NodeType == "EliteBranch" {
|
|
if runtime == nil || runtime.RunID != run.UniqueId {
|
|
return nil, fmt.Errorf("CW generated Encounter requires matching runtime transaction")
|
|
}
|
|
layer, err = cwGenerateXaodevEncounter(catalog, shadow, display)
|
|
} else {
|
|
layer, err = cwGenerateReferenceEnemyLayer(catalog, shadow, display)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if layer == nil {
|
|
return nil, fmt.Errorf("CW generated next node layer missing")
|
|
}
|
|
hasBranchDiff := stage.ANJBLBEOJND != nil && stage.ANJBLBEOJND.PIFNOKAMBLL != nil && stage.ANJBLBEOJND.PIFNOKAMBLL[cwDifficultySourceBranch] != 0
|
|
if template.NodeType == "EliteBranch" || cwXaodevGenerated(run, runtime) || hasBranchDiff {
|
|
difficulty, err = cwXaodevDifficulty(catalog, shadow, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
pending := &pb.CHCHAJIANIM{QueuePosition: queue + 1, FGLIODAJFML: &pb.CHCHAJIANIM_NNDGEJFLGGF{NNDGEJFLGGF: &pb.DPHPAKFLNOD{}}}
|
|
stage.DCAHJELNJNP = next.ChapterID
|
|
stage.SectionId = next.SectionID
|
|
stage.IMPNAHBCADN = layer
|
|
stage.ANJBLBEOJND = shadowStage.ANJBLBEOJND
|
|
run.FMIGOKIDKJL = pending
|
|
if template.NodeType == "EliteBranch" {
|
|
if runtime.Rules == nil {
|
|
runtime.Rules = map[string]uint32{}
|
|
}
|
|
runtime.Rules[cwXaodevNodeKey(stage)] = 1
|
|
}
|
|
cwTrailblazeNodeEntered(catalog, run, runtime)
|
|
cwSyncTraitComponent(catalog, run, runtime)
|
|
notify := cwNotify(cwFinish(queue), &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_OOICHLAMNIG{OOICHLAMNIG: &pb.AINFNFIDGFF{DCAHJELNJNP: stage.DCAHJELNJNP, SectionId: stage.SectionId, IMPNAHBCADN: layer}}})
|
|
if difficulty != nil {
|
|
notify.GCPNIHHBGGD[0].JKPJFKCBIEM = append(notify.GCPNIHHBGGD[0].JKPJFKCBIEM, difficulty)
|
|
}
|
|
if tbCat, err := common.LoadCurrencyWarsTrailblaze(); err == nil && (cwGetTrailblazeLayer(catalog, run) > 0 || (runtime != nil && runtime.Rules["TbOpened"] != 0)) {
|
|
notify.GCPNIHHBGGD[0].JKPJFKCBIEM = append(notify.GCPNIHHBGGD[0].JKPJFKCBIEM,
|
|
&pb.KFNCAIHIHCM{
|
|
MNLCHEJKDHO: &pb.KFNCAIHIHCM_HKHJJCMKKJE{
|
|
HKHJJCMKKJE: &pb.JLKCBOIFJKC{
|
|
TraitId: cwTrailblazeTrait,
|
|
ENPLACDLJGA: cwTrailblazeTraitEffectProto(tbCat, runtime),
|
|
},
|
|
},
|
|
},
|
|
&pb.KFNCAIHIHCM{
|
|
MNLCHEJKDHO: &pb.KFNCAIHIHCM_GOOCHGKBIPN{
|
|
GOOCHGKBIPN: cwTrailblazeModifierProto(tbCat, runtime),
|
|
},
|
|
},
|
|
)
|
|
}
|
|
notify.GCPNIHHBGGD[0].JKPJFKCBIEM = append(notify.GCPNIHHBGGD[0].JKPJFKCBIEM, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_FMIGOKIDKJL{FMIGOKIDKJL: pending}})
|
|
return notify, nil
|
|
}
|
|
|
|
// cwGeneratedRoundPreparation handles the same RoundBegin -> shop refresh ->
|
|
// ReturnPreparation transition as the captured initial rounds, without replaying
|
|
// an unrelated node's opaque wire or its fixed queue. Restrict this fallback to
|
|
// constructed enemy layers; other node families keep their typed handlers.
|
|
func cwGeneratedRoundPreparation(catalog *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH) (*pb.CHCHAJIANIM, error) {
|
|
if run == nil || run.GetFMIGOKIDKJL().GetNNDGEJFLGGF() == nil {
|
|
return nil, fmt.Errorf("CW generated round is not RoundBegin")
|
|
}
|
|
stage, display := cwStage(run)
|
|
if stage == nil || display == nil || stage.GetIMPNAHBCADN().GetMPOBBIPMNNJ() == nil {
|
|
return nil, fmt.Errorf("CW generated RoundBegin enemy layer missing")
|
|
}
|
|
queue := run.FMIGOKIDKJL.QueuePosition
|
|
if queue == math.MaxUint32 {
|
|
return nil, fmt.Errorf("CW pending queue exhausted")
|
|
}
|
|
name, err := catalog.NodeTypeName(display.CJCCMAACJDB)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if name == "Supply" {
|
|
offer, err := cwGenerateSupplyOffer(catalog, run)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &pb.CHCHAJIANIM{QueuePosition: queue + 1, FGLIODAJFML: &pb.CHCHAJIANIM_FIKAGBAEOAP{FIKAGBAEOAP: offer}}, nil
|
|
}
|
|
if display.HPNNLMBHOPF {
|
|
offer, err := cwSpotlightChoices(catalog, run)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &pb.CHCHAJIANIM{QueuePosition: queue + 1, FGLIODAJFML: &pb.CHCHAJIANIM_EEKMIAMGHFL{EEKMIAMGHFL: offer}}, nil
|
|
}
|
|
if name == "EliteBranch" && stage.IMPNAHBCADN.GetMPOBBIPMNNJ().MOADBALHMFD == 0 {
|
|
return &pb.CHCHAJIANIM{QueuePosition: queue + 1, FGLIODAJFML: &pb.CHCHAJIANIM_BAKAKODKECM{BAKAKODKECM: &pb.HNPHLAHJPOB{}}}, nil
|
|
}
|
|
return &pb.CHCHAJIANIM{QueuePosition: queue + 1, FGLIODAJFML: &pb.CHCHAJIANIM_LGNPHHBGKFH{LGNPHHBGKFH: &pb.HJPDALHDJHC{}}}, nil
|
|
}
|