2026-10-21 20:40:13 +07:00
|
|
|
package gameserver
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"crypto/rand"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"fmt"
|
|
|
|
|
"math/big"
|
|
|
|
|
|
|
|
|
|
"gac-hkrpg-go/internal/common"
|
|
|
|
|
pb "gac-hkrpg-go/protocol/proto"
|
|
|
|
|
"google.golang.org/protobuf/proto"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Wire names are revision-specific. Mappings were established by the native
|
|
|
|
|
// RoundBegin -> ReturnPreparation -> UpdatePos replay, not opcode aliases.
|
|
|
|
|
func cwComponents(run *pb.MPIMAEMCEFH) (player *pb.NIHNGPKHEPC, team *pb.HKFECLCHBFJ, shop *pb.POCJDMMANBM) {
|
|
|
|
|
for _, c := range run.NAGDJGMMCBE {
|
|
|
|
|
if v := c.GetFAOCFBOFCJP(); v != nil {
|
|
|
|
|
player = v
|
|
|
|
|
}
|
|
|
|
|
if v := c.GetEJIPINIKNCK(); v != nil {
|
|
|
|
|
team = v
|
|
|
|
|
}
|
|
|
|
|
if v := c.GetEBHDIOGALIE(); v != nil {
|
|
|
|
|
shop = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func cwNotify(updates ...*pb.KFNCAIHIHCM) *pb.GridFightSyncUpdateResultScNotify {
|
|
|
|
|
return &pb.GridFightSyncUpdateResultScNotify{GCPNIHHBGGD: []*pb.ABPLALCHNFA{{JKPJFKCBIEM: updates}}}
|
|
|
|
|
}
|
|
|
|
|
func cwFinish(queue uint32) *pb.KFNCAIHIHCM {
|
|
|
|
|
return &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_PMFBIGLPJJA{PMFBIGLPJJA: queue}}
|
|
|
|
|
}
|
|
|
|
|
func cwUnlock() *pb.KFNCAIHIHCM {
|
|
|
|
|
return &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_JOMFJHOCHJP{JOMFJHOCHJP: &pb.GMBMNFLMKPI{}}}
|
|
|
|
|
}
|
|
|
|
|
func cwShopSync(shop *pb.POCJDMMANBM) *pb.KFNCAIHIHCM {
|
|
|
|
|
return &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_MOFNIOJHBBL{MOFNIOJHBBL: &pb.HONNGJEMGGH{
|
|
|
|
|
ABGEKPJHHIA: shop.ABGEKPJHHIA, OPDCMCFOFAA: shop.OPDCMCFOFAA, NFGMOPCCPIC: shop.NFGMOPCCPIC,
|
|
|
|
|
ABBNGCPECAI: shop.ABBNGCPECAI, OGDKJPDFFAI: shop.OGDKJPDFFAI,
|
|
|
|
|
}}}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func cwInitialRound(catalog *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH) (*pb.GridFightSyncUpdateResultScNotify, error) {
|
|
|
|
|
raw, err := common.ReadResourceFile("resources/CurrencyWarsPreparation.json")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
var recipe struct {
|
|
|
|
|
Format uint32 `json:"format_version"`
|
|
|
|
|
Client string `json:"target_client_version"`
|
|
|
|
|
Assembly string `json:"target_assembly_sha256"`
|
|
|
|
|
Pending []byte `json:"return_preparation_wire"`
|
|
|
|
|
Policy common.CurrencyWarsDrawPolicy `json:"shop_draw_policy"`
|
|
|
|
|
}
|
|
|
|
|
if err = json.Unmarshal(raw, &recipe); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if recipe.Format != 1 || recipe.Client != catalog.ClientVersion || recipe.Assembly != catalog.Provenance.GameAssemblySHA256 {
|
|
|
|
|
return nil, fmt.Errorf("CW preparation recipe revision differs")
|
|
|
|
|
}
|
|
|
|
|
pending := new(pb.CHCHAJIANIM)
|
|
|
|
|
if err = proto.Unmarshal(recipe.Pending, pending); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if pending.GetLGNPHHBGKFH() == nil || pending.QueuePosition <= run.FMIGOKIDKJL.QueuePosition {
|
|
|
|
|
return nil, fmt.Errorf("CW preparation recipe has invalid pending action")
|
|
|
|
|
}
|
|
|
|
|
player, team, shop := cwComponents(run)
|
|
|
|
|
if player == nil || team == nil || shop == nil || len(shop.OGDKJPDFFAI) != 0 {
|
|
|
|
|
return nil, fmt.Errorf("CW initial round components differ")
|
|
|
|
|
}
|
|
|
|
|
pool, err := catalog.NewCapturedCardPool()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
// Reserve starter copies from the same finite pool before drawing the shop.
|
|
|
|
|
for _, role := range team.GridGameRoleList {
|
|
|
|
|
r, ok := pool.Roles[role.Id]
|
|
|
|
|
if !ok {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
config, e := catalog.CapturedRole(role.Id)
|
|
|
|
|
if e != nil {
|
|
|
|
|
return nil, e
|
|
|
|
|
}
|
|
|
|
|
if role.RoleStar != config.DefaultStar || r.Remaining == 0 {
|
|
|
|
|
return nil, fmt.Errorf("CW starter pool reservation is not supported")
|
|
|
|
|
}
|
|
|
|
|
r.Remaining--
|
|
|
|
|
pool.Roles[role.Id] = r
|
|
|
|
|
}
|
|
|
|
|
level, err := catalog.PlayerLevel(player.EHNJDIDABHD)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
weights := level.RarityWeights()
|
|
|
|
|
rules, err := catalog.BaseShopRules()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
planned, err := (&common.CurrencyWarsShop{Pool: pool}).PlanEnterNode(catalog, rules, weights[:], recipe.Policy,
|
|
|
|
|
func(upper uint64) (uint64, error) {
|
|
|
|
|
v, e := rand.Int(rand.Reader, new(big.Int).SetUint64(upper))
|
|
|
|
|
if e != nil {
|
|
|
|
|
return 0, e
|
|
|
|
|
}
|
|
|
|
|
return v.Uint64(), nil
|
|
|
|
|
},
|
|
|
|
|
func(id uint32) (uint32, uint32, error) {
|
|
|
|
|
r, e := catalog.CapturedRole(id)
|
|
|
|
|
return r.DefaultStar, r.Price, e
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
for _, offer := range planned.Offers {
|
|
|
|
|
value, e := currencyWarsRoleOfferProto(catalog, offer, pb.FKGIEPHGACD_FKGIEPHGACD_EJKDKAJPDCO)
|
|
|
|
|
if e != nil {
|
|
|
|
|
return nil, e
|
|
|
|
|
}
|
|
|
|
|
shop.OGDKJPDFFAI = append(shop.OGDKJPDFFAI, value)
|
|
|
|
|
}
|
|
|
|
|
shop.NFGMOPCCPIC = rules.RefreshCost
|
|
|
|
|
queue := run.FMIGOKIDKJL.QueuePosition
|
|
|
|
|
run.FMIGOKIDKJL = pending
|
|
|
|
|
player.GAJGBBFELEL = &pb.GMBMNFLMKPI{IBCPAKJAPFF: pb.KMJNPEMPNMC_KMJNPEMPNMC_AEINKINPAFL, LMICDNNABNM: pb.GGNKDLAHAGH_GGNKDLAHAGH_FHOHPFBDNOH}
|
|
|
|
|
return cwNotify(cwShopSync(shop), cwFinish(queue), cwUnlock(),
|
|
|
|
|
&pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_JOMFJHOCHJP{JOMFJHOCHJP: player.GAJGBBFELEL}},
|
|
|
|
|
&pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_FMIGOKIDKJL{FMIGOKIDKJL: pending}}), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func cwReturnPreparation(run *pb.MPIMAEMCEFH) (*pb.GridFightSyncUpdateResultScNotify, error) {
|
|
|
|
|
player, team, shop := cwComponents(run)
|
|
|
|
|
if player == nil || team == nil || shop == nil {
|
|
|
|
|
return nil, fmt.Errorf("CW preparation components missing")
|
|
|
|
|
}
|
|
|
|
|
queue := run.FMIGOKIDKJL.QueuePosition
|
|
|
|
|
run.FMIGOKIDKJL = nil
|
|
|
|
|
player.GAJGBBFELEL = &pb.GMBMNFLMKPI{}
|
|
|
|
|
return cwNotify(cwShopSync(shop), cwFinish(queue), cwUnlock()), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Refresh a node's shop without charging the player. Pool accounting includes
|
|
|
|
|
// owned copies and unsold reservations, and a locked shop keeps its offers.
|
|
|
|
|
func cwRestockForNode(catalog *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH) error {
|
|
|
|
|
player, team, shop := cwComponents(run)
|
|
|
|
|
if player == nil || team == nil || shop == nil {
|
|
|
|
|
return fmt.Errorf("CW node shop components missing")
|
|
|
|
|
}
|
|
|
|
|
pool, err := catalog.NewCapturedCardPool()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
reserve := func(id uint32) error {
|
|
|
|
|
r, ok := pool.Roles[id]
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2026-10-31 20:47:36 +07:00
|
|
|
if r.Remaining > 0 {
|
|
|
|
|
r.Remaining--
|
2026-10-21 20:40:13 +07:00
|
|
|
}
|
|
|
|
|
pool.Roles[id] = r
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
for _, r := range team.GridGameRoleList {
|
|
|
|
|
if e := pool.ReserveOwned(catalog, r.Id, r.RoleStar); e != nil {
|
|
|
|
|
return e
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
current := &common.CurrencyWarsShop{Pool: pool, Locked: shop.ABGEKPJHHIA, FreeRefreshTimes: shop.OPDCMCFOFAA}
|
|
|
|
|
for _, g := range shop.OGDKJPDFFAI {
|
|
|
|
|
r := g.GetBDPIMPJOJBK()
|
|
|
|
|
if r == nil {
|
|
|
|
|
return fmt.Errorf("CW special shop offer not supported")
|
|
|
|
|
}
|
|
|
|
|
if !g.LHGIFDNPIDE {
|
|
|
|
|
if err = reserve(r.RoleId); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
current.Offers = append(current.Offers, common.CurrencyWarsShopOffer{RoleID: r.RoleId, Star: r.RoleStar, Cost: g.KDKPDJNMMCM, Sold: g.LHGIFDNPIDE})
|
|
|
|
|
}
|
|
|
|
|
rules, err := catalog.BaseShopRules()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
level, err := catalog.PlayerLevel(player.EHNJDIDABHD)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
weights := level.RarityWeights()
|
|
|
|
|
next, err := current.PlanEnterNode(catalog, rules, weights[:], common.CurrencyWarsDrawRemainingCopies, func(upper uint64) (uint64, error) {
|
|
|
|
|
v, e := rand.Int(rand.Reader, new(big.Int).SetUint64(upper))
|
|
|
|
|
if e != nil {
|
|
|
|
|
return 0, e
|
|
|
|
|
}
|
|
|
|
|
return v.Uint64(), nil
|
|
|
|
|
}, func(id uint32) (uint32, uint32, error) {
|
|
|
|
|
r, e := catalog.CapturedRole(id)
|
|
|
|
|
return r.DefaultStar, r.Price, e
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
shop.OGDKJPDFFAI = nil
|
|
|
|
|
for _, offer := range next.Offers {
|
|
|
|
|
value, e := currencyWarsRoleOfferProto(catalog, offer, pb.FKGIEPHGACD_FKGIEPHGACD_EJKDKAJPDCO)
|
|
|
|
|
if e != nil {
|
|
|
|
|
return e
|
|
|
|
|
}
|
|
|
|
|
shop.OGDKJPDFFAI = append(shop.OGDKJPDFFAI, value)
|
|
|
|
|
}
|
|
|
|
|
shop.NFGMOPCCPIC = rules.RefreshCost
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func cwNextRound(catalog *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH) (*pb.GridFightSyncUpdateResultScNotify, error) {
|
|
|
|
|
stage, _ := cwStage(run)
|
|
|
|
|
if stage == nil || run.FMIGOKIDKJL == nil {
|
|
|
|
|
return nil, fmt.Errorf("CW next round missing")
|
|
|
|
|
}
|
|
|
|
|
entries, err := cwNodeTransitions(catalog)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
var pending *pb.CHCHAJIANIM
|
|
|
|
|
for _, e := range entries {
|
|
|
|
|
level := new(pb.AINFNFIDGFF)
|
|
|
|
|
before := new(pb.CHCHAJIANIM)
|
|
|
|
|
if err = proto.Unmarshal(e.LevelWire, level); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if err = proto.Unmarshal(e.PendingWire, before); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if e.Route == stage.EMOHLJHPOPC && e.Division == run.DivisionId && e.Overclock == run.IsOverlock && level.DCAHJELNJNP == stage.DCAHJELNJNP && level.SectionId == stage.SectionId && before.QueuePosition == run.FMIGOKIDKJL.QueuePosition {
|
|
|
|
|
if pending != nil {
|
|
|
|
|
return nil, fmt.Errorf("CW next round recipe duplicated")
|
|
|
|
|
}
|
|
|
|
|
pending = new(pb.CHCHAJIANIM)
|
|
|
|
|
if err = proto.Unmarshal(e.RoundWire, pending); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-10-28 18:54:43 +07:00
|
|
|
if pending == nil {
|
|
|
|
|
pending, err = cwGeneratedRoundPreparation(catalog, run)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if pending.QueuePosition <= run.FMIGOKIDKJL.QueuePosition {
|
2026-10-21 20:40:13 +07:00
|
|
|
return nil, fmt.Errorf("CW next round recipe missing")
|
|
|
|
|
}
|
|
|
|
|
if err = cwRestockForNode(catalog, run); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
player, _, shop := cwComponents(run)
|
|
|
|
|
queue := run.FMIGOKIDKJL.QueuePosition
|
|
|
|
|
run.FMIGOKIDKJL = pending
|
2026-10-28 18:54:43 +07:00
|
|
|
aligned, err := cwAlignEncounterCamp(catalog, run)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
updates := []*pb.KFNCAIHIHCM{cwShopSync(shop), cwFinish(queue), cwUnlock(), {MNLCHEJKDHO: &pb.KFNCAIHIHCM_JOMFJHOCHJP{JOMFJHOCHJP: player.GAJGBBFELEL}}}
|
|
|
|
|
if aligned {
|
|
|
|
|
updates = append(updates, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_OOICHLAMNIG{OOICHLAMNIG: &pb.AINFNFIDGFF{DCAHJELNJNP: stage.DCAHJELNJNP, SectionId: stage.SectionId, IMPNAHBCADN: stage.IMPNAHBCADN}}})
|
|
|
|
|
}
|
|
|
|
|
updates = append(updates, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_FMIGOKIDKJL{FMIGOKIDKJL: pending}})
|
|
|
|
|
return cwNotify(updates...), nil
|
2026-10-21 20:40:13 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func init() {
|
2026-10-28 18:54:43 +07:00
|
|
|
cwHandle("GridFightUpdatePos", func(s *Session, req *pb.GridFightUpdatePosCsReq, rsp *pb.GridFightUpdatePosScRsp) error {
|
2026-10-21 20:40:13 +07:00
|
|
|
rsp.Retcode = uint32(pb.Retcode_RET_GRID_FIGHT_PARAM_NOT_MATCH)
|
2026-10-28 18:54:43 +07:00
|
|
|
if _, active := CURRENT_BATTLE.Load(s.Token); active {
|
|
|
|
|
PacketLog("CW_UPDATE_POS rejected reason=battle_active")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2026-10-21 20:40:13 +07:00
|
|
|
catalog, err := common.LoadCurrencyWarsCatalog()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
data, revision, err := readCurrencyWarsData(catalog)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if data == nil || data.DHFOFLFCLAH == nil {
|
|
|
|
|
rsp.Retcode = uint32(pb.Retcode_RET_GRID_FIGHT_NOT_IN_GAMEPLAY)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
if revision != s.currencyWarsRevision {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
run := data.DHFOFLFCLAH
|
|
|
|
|
if run.FMIGOKIDKJL != nil || len(req.AAKDHMHCMLP) == 0 {
|
|
|
|
|
PacketLog("CW_UPDATE_POS rejected reason=not_preparing_or_empty")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
player, team, _ := cwComponents(run)
|
|
|
|
|
if player == nil || team == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
front, err := catalog.IntegerRule("GridFight_Front_AvatarMaxNum")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
back, err := catalog.IntegerRule("GridFight_Back_AvatarMaxNum")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
activeEnd := uint64(front + back)
|
|
|
|
|
overflow, err := catalog.IntegerRule("GridFight_Bench_OverFlow_AvatarNum")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
benchEnd := activeEnd + uint64(player.MICEOJBAKGN) + uint64(overflow)
|
|
|
|
|
changes := map[uint32]uint32{}
|
|
|
|
|
for _, v := range req.AAKDHMHCMLP {
|
|
|
|
|
if v == nil || v.UniqueId == 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
if _, ok := changes[v.UniqueId]; ok {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
if uint64(v.Pos) > benchEnd {
|
|
|
|
|
rsp.Retcode = uint32(pb.Retcode_RET_GRID_FIGHT_POS_NOT_VALID)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
changes[v.UniqueId] = v.Pos
|
|
|
|
|
}
|
|
|
|
|
// Validate final positions together, so swaps need no intermediate empty slot.
|
|
|
|
|
occupied := map[uint32]bool{}
|
|
|
|
|
owned := map[uint32]bool{}
|
|
|
|
|
active := uint32(0)
|
|
|
|
|
activeAvatars := map[uint32]bool{}
|
|
|
|
|
for _, role := range team.GridGameRoleList {
|
|
|
|
|
owned[role.UniqueId] = true
|
|
|
|
|
pos := role.Pos
|
|
|
|
|
if v, ok := changes[role.UniqueId]; ok {
|
|
|
|
|
pos = v
|
|
|
|
|
}
|
|
|
|
|
if pos != 0 {
|
|
|
|
|
if occupied[pos] {
|
|
|
|
|
rsp.Retcode = uint32(pb.Retcode_RET_GRID_FIGHT_POS_EXIST_ROLE)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
occupied[pos] = true
|
|
|
|
|
}
|
|
|
|
|
if pos != 0 && uint64(pos) <= activeEnd {
|
|
|
|
|
config, err := catalog.CapturedRoleVariant(role.Id, role.RoleStar)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if activeAvatars[config.Config.AvatarID] {
|
|
|
|
|
rsp.Retcode = uint32(pb.Retcode_RET_GRID_FIGHT_SAME_ROLE_IN_BATTLE)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
activeAvatars[config.Config.AvatarID] = true
|
|
|
|
|
active++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for _, forge := range team.KCOMPKMJCMC {
|
|
|
|
|
pos := forge.Pos
|
|
|
|
|
if _, changed := changes[forge.UniqueId]; changed {
|
|
|
|
|
return nil
|
2026-10-28 18:54:43 +07:00
|
|
|
} // Projection movement needs its separately verified sync arm.
|
2026-10-21 20:40:13 +07:00
|
|
|
if pos != 0 {
|
|
|
|
|
if occupied[pos] {
|
|
|
|
|
rsp.Retcode = uint32(pb.Retcode_RET_GRID_FIGHT_POS_EXIST_ROLE)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
occupied[pos] = true
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-10-28 18:54:43 +07:00
|
|
|
for _, forge := range team.PMEOPPDAMLM {
|
|
|
|
|
owned[forge.UniqueId] = true
|
|
|
|
|
pos := forge.Pos
|
|
|
|
|
if v, ok := changes[forge.UniqueId]; ok {
|
|
|
|
|
pos = v
|
|
|
|
|
}
|
|
|
|
|
if pos == 0 || uint64(pos) <= activeEnd || uint64(pos) > benchEnd {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
if occupied[pos] {
|
|
|
|
|
rsp.Retcode = uint32(pb.Retcode_RET_GRID_FIGHT_POS_EXIST_ROLE)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
occupied[pos] = true
|
|
|
|
|
}
|
2026-10-21 20:40:13 +07:00
|
|
|
for id := range changes {
|
|
|
|
|
if !owned[id] {
|
|
|
|
|
rsp.Retcode = uint32(pb.Retcode_RET_GRID_FIGHT_ROLE_NOT_EXIST)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if active > player.AFFCFLFIFPH {
|
|
|
|
|
rsp.Retcode = uint32(pb.Retcode_RET_GRID_FIGHT_REGION_FULL)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
var updates []*pb.KFNCAIHIHCM
|
|
|
|
|
for _, role := range team.GridGameRoleList {
|
|
|
|
|
if pos, ok := changes[role.UniqueId]; ok {
|
|
|
|
|
role.Pos = pos
|
|
|
|
|
updates = append(updates, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_CFOCDALNCBP{CFOCDALNCBP: role}})
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-10-28 18:54:43 +07:00
|
|
|
for _, forge := range team.PMEOPPDAMLM {
|
|
|
|
|
if pos, ok := changes[forge.UniqueId]; ok {
|
|
|
|
|
forge.Pos = pos
|
|
|
|
|
updates = append(updates, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_FEKCPKAPHHO{FEKCPKAPHHO: forge}})
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-10-31 20:47:36 +07:00
|
|
|
runtime, _ := cwBonfireRuntime(catalog, run)
|
|
|
|
|
cwSyncTraitComponent(catalog, run, runtime)
|
|
|
|
|
if tbCat, err := common.LoadCurrencyWarsTrailblaze(); err == nil && (cwGetTrailblazeLayer(catalog, run) > 0 || (runtime != nil && runtime.Rules["TbOpened"] != 0)) {
|
|
|
|
|
updates = append(updates,
|
|
|
|
|
&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),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
if err = s.saveCurrencyWarsDataWithRuntime(data, catalog, runtime); err != nil {
|
2026-10-21 20:40:13 +07:00
|
|
|
return err
|
|
|
|
|
}
|
2026-10-28 18:54:43 +07:00
|
|
|
if err = cwSend(s, cwNotify(updates...)); err != nil {
|
2026-10-21 20:40:13 +07:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
rsp.AAKDHMHCMLP = req.AAKDHMHCMLP
|
|
|
|
|
rsp.Retcode = 0
|
|
|
|
|
PacketLog(fmt.Sprintf("CW_UPDATE_POS saved count=%d active=%d revision=%d", len(changes), active, s.currencyWarsRevision))
|
|
|
|
|
return nil
|
|
|
|
|
})
|
2026-10-31 20:47:36 +07:00
|
|
|
|
|
|
|
|
// CmdID 8543 (GEBBMDCOLCH) -> ScRsp 8630 (ALNFEBKHCFC)
|
|
|
|
|
// Protocol revision: 4.6.53 (upstream_cmdids.go).
|
|
|
|
|
// Assumption: Native Client sends GEBBMDCOLCH during preparation / round transition to sync
|
|
|
|
|
// the batch formation role positions (CONJCEMAHGN: MDNAGANGNKK = role UID, JFCJNHNBMCB = pos).
|
|
|
|
|
// Acknowledging with retcode 0 unblocks the Client UI input controller.
|
|
|
|
|
cwHandleNamed("GEBBMDCOLCH", "ALNFEBKHCFC", func(s *Session, req *pb.GEBBMDCOLCH, rsp *pb.ALNFEBKHCFC) error {
|
|
|
|
|
catalog, err := common.LoadCurrencyWarsCatalog()
|
|
|
|
|
if err != nil {
|
|
|
|
|
rsp.Retcode = 0
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
data, _, err := readCurrencyWarsData(catalog)
|
|
|
|
|
if err != nil || data == nil || data.DHFOFLFCLAH == nil {
|
|
|
|
|
rsp.Retcode = 0
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
run := data.DHFOFLFCLAH
|
|
|
|
|
_, team, _ := cwComponents(run)
|
|
|
|
|
if team != nil && len(req.CONJCEMAHGN) > 0 {
|
|
|
|
|
posMap := make(map[uint32]uint32, len(req.CONJCEMAHGN))
|
|
|
|
|
for _, entry := range req.CONJCEMAHGN {
|
|
|
|
|
if entry != nil && entry.MDNAGANGNKK != 0 {
|
|
|
|
|
posMap[entry.MDNAGANGNKK] = entry.JFCJNHNBMCB
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
changed := false
|
|
|
|
|
for _, role := range team.GridGameRoleList {
|
|
|
|
|
if newPos, ok := posMap[role.UniqueId]; ok && role.Pos != newPos {
|
|
|
|
|
role.Pos = newPos
|
|
|
|
|
changed = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if changed {
|
|
|
|
|
runtime, _ := cwBonfireRuntime(catalog, run)
|
|
|
|
|
cwSyncTraitComponent(catalog, run, runtime)
|
|
|
|
|
_ = s.saveCurrencyWarsDataWithRuntime(data, catalog, runtime)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
rsp.Retcode = 0
|
|
|
|
|
PacketLog(fmt.Sprintf("CW_BATCH_UPDATE_POS count=%d revision=%d", len(req.CONJCEMAHGN), s.currencyWarsRevision))
|
|
|
|
|
return nil
|
|
|
|
|
})
|
2026-10-21 20:40:13 +07:00
|
|
|
}
|