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