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

164 lines
6.5 KiB
Go

// SPDX-License-Identifier: AGPL-3.0-only
// Adapted from Currency-War-SR by xaodev (2026); see third_party/currency-war-sr.
package gameserver
import (
"crypto/sha256"
"encoding/binary"
"fmt"
"math"
"sort"
"gac-hkrpg-go/internal/common"
pb "gac-hkrpg-go/protocol/proto"
"google.golang.org/protobuf/proto"
)
// Client difficulty source keys from GridFightStage.GetBaseDiffAddLv and
// GetAllDiffAddLvForBranchPreview; these are protocol semantics, not game IDs.
const (
cwDifficultySourceDivision uint32 = 1
cwDifficultySourceBranch uint32 = 2
)
func cwXaodevNodeKey(stage *pb.BEEFLEJAOCN) string {
return fmt.Sprintf("XaodevEncounter/%d/%d/%d", stage.EMOHLJHPOPC, stage.DCAHJELNJNP, stage.SectionId)
}
func cwXaodevGenerated(run *pb.MPIMAEMCEFH, runtime *common.CurrencyWarsRuntimeState) bool {
stage, _ := cwStage(run)
return stage != nil && runtime != nil && runtime.RunID == run.UniqueId && runtime.Rules[cwXaodevNodeKey(stage)] == 1
}
// Equivalent to upstream's unprofiled EliteBranch encounter policy. The
// upstream also calls this a local policy, not the official server wave roll.
// Select only within the chapter camp already present in this run; no fresh
// boss/camp roll and no substitution of a captured first-node battle.
func cwGenerateXaodevEncounter(c *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, display *pb.OHMDHKGAEMM) (*pb.NCKOKDHNJDC, error) {
stage, template, err := cwReferenceCurrentTemplate(c, run)
if err != nil {
return nil, err
}
if run.UniqueId == 0 || stage.DCAHJELNJNP == 0 || display == nil || template.NodeType != "EliteBranch" || display.StageId != template.StageID || len(template.ParamList) == 0 || uint64(stage.DCAHJELNJNP) > uint64(len(stage.HEKMEJLMGFF)) {
return nil, fmt.Errorf("CW xaodev Encounter template/camp missing")
}
data, err := common.LoadCurrencyWarsXaodev(c)
if err != nil {
return nil, err
}
policy, err := cwLoadReferenceEnemies(c)
if err != nil {
return nil, err
}
penalties, err := common.LoadCurrencyWarsReferencePenalties(c)
if err != nil {
return nil, err
}
if _, ok := penalties[template.PenaltyBonusRuleID]; !ok {
return nil, fmt.Errorf("CW xaodev Encounter penalty missing")
}
campID := stage.HEKMEJLMGFF[stage.DCAHJELNJNP-1].GetLOGHLGKIFEN()
camp, err := c.MonsterCamp(run.LJNGPCKPJLF, campID)
if err != nil {
return nil, err
}
var pool []uint32
for _, id := range camp.Season.MonsterList {
if policy.MonsterTiers[id] == data.Policy.MonsterTier {
pool = append(pool, id)
}
}
if len(pool) == 0 {
return nil, fmt.Errorf("CW xaodev Encounter camp has no configured monster tier")
}
sort.Slice(pool, func(i, j int) bool { return pool[i] < pool[j] })
// A stable per-node seed; no global RNG state is consumed by re-preview.
var identity [16]byte
binary.LittleEndian.PutUint32(identity[0:4], run.UniqueId)
binary.LittleEndian.PutUint32(identity[4:8], stage.EMOHLJHPOPC)
binary.LittleEndian.PutUint32(identity[8:12], stage.DCAHJELNJNP)
binary.LittleEndian.PutUint32(identity[12:16], stage.SectionId)
seed := sha256.Sum256(identity[:])
roll := binary.LittleEndian.Uint64(seed[:8])
quality := data.Policy.PowerfulQualities[roll%uint64(len(data.Policy.PowerfulQualities))]
wave := &pb.PNAKHANCNGK{OCPEPEMNNEK: 1}
for i := uint32(0); i < data.Policy.MonsterCount; i++ {
wave.PBLBBDBDLEK = append(wave.PBLBBDBDLEK, &pb.AHKDPCIIELA{MonsterId: pool[(roll%uint64(len(pool))+uint64(i))%uint64(len(pool))], RoleStar: min(data.Policy.StarCap, stage.DCAHJELNJNP), LOGHLGKIFEN: campID})
}
layer := &pb.MDJBBILMHLC{LNDKCPBJDKH: campID} // zero selection opens the pending page
for _, q := range []uint32{data.Policy.NormalQuality, quality} {
rule, err := data.Branch(template.ParamList[0], q)
if err != nil {
return nil, err
}
if _, err = data.BranchDifficulty(run.DivisionId, rule.ID); err != nil {
return nil, err
}
layer.BPDABHGFBFO = append(layer.BPDABHGFBFO, &pb.KKBGIPCAGGH{
CJADMFDOBAI: rule.ID, IFIIOIIKJHO: template.PenaltyBonusRuleID,
MonsterWaveList: []*pb.PNAKHANCNGK{proto.Clone(wave).(*pb.PNAKHANCNGK)},
BOAMLLHPNEC: &pb.GridFightDropInfo{NMGIABHIJJB: []*pb.BCCONKJHDJL{{FKKGLFOHGGA: pb.MJJIFGINJAJ_MJJIFGINJAJ_item_value, Num: display.PICMDONCKCO}}},
})
}
if display.PICMDONCKCO == 0 {
return nil, fmt.Errorf("CW xaodev Encounter base reward missing")
}
return &pb.NCKOKDHNJDC{EFECLPJFOCM: &pb.NCKOKDHNJDC_MPOBBIPMNNJ{MPOBBIPMNNJ: layer}}, nil
}
// Preserve all other difficulty sources and replace just the selected branch.
// Selection, difficulty and the pending acknowledgement commit together.
func cwXaodevDifficulty(c *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, add uint32) (*pb.KFNCAIHIHCM, error) {
stage, _ := cwStage(run)
if stage == nil {
return nil, fmt.Errorf("CW xaodev difficulty stage missing")
}
data, err := common.LoadCurrencyWarsXaodev(c)
if err != nil {
return nil, err
}
division, err := c.CapturedStage(run.DivisionId)
if err != nil {
return nil, err
}
info := &pb.KOMPNJAMLPM{PIJBPDPEODG: division.EnemyHardLevel, PIFNOKAMBLL: map[uint32]int32{cwDifficultySourceDivision: int32(division.EnemyHardLevel)}}
if stage.ANJBLBEOJND != nil {
info = proto.Clone(stage.ANJBLBEOJND).(*pb.KOMPNJAMLPM)
}
if info.PIFNOKAMBLL == nil {
info.PIFNOKAMBLL = map[uint32]int32{}
}
if base, ok := info.PIFNOKAMBLL[cwDifficultySourceDivision]; ok && (base < 0 || uint32(base) != division.EnemyHardLevel) {
return nil, fmt.Errorf("CW xaodev difficulty base differs")
}
info.PIFNOKAMBLL[cwDifficultySourceDivision] = int32(division.EnemyHardLevel)
total := int64(info.PIJBPDPEODG) - int64(info.PIFNOKAMBLL[cwDifficultySourceBranch]) + int64(add)
if total < 0 || total > math.MaxInt32 || add > math.MaxInt32 || !data.SupportsDifficulty(stage.DCAHJELNJNP, uint32(total)) {
return nil, fmt.Errorf("CW xaodev difficulty outside Client table")
}
info.PIJBPDPEODG = uint32(total)
info.PIFNOKAMBLL[cwDifficultySourceBranch] = int32(add)
stage.ANJBLBEOJND = info
return &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_CEDMLOEDPEH{CEDMLOEDPEH: info}}, nil
}
func cwXaodevSelectionDifficulty(c *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, runtime *common.CurrencyWarsRuntimeState) (*pb.KFNCAIHIHCM, error) {
if !cwXaodevGenerated(run, runtime) {
return nil, nil
}
stage, _ := cwStage(run)
branch, err := cwSelectedEnemyBranch(stage.GetIMPNAHBCADN().GetMPOBBIPMNNJ())
if err != nil {
return nil, err
}
data, err := common.LoadCurrencyWarsXaodev(c)
if err != nil {
return nil, err
}
add, err := data.BranchDifficulty(run.DivisionId, branch.CJADMFDOBAI)
if err != nil {
return nil, err
}
return cwXaodevDifficulty(c, run, add)
}