163 lines
6.5 KiB
Go
163 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)
|
||
|
|
}
|