package gameserver import ( "crypto/rand" "fmt" "maps" "math/big" "sort" "gac-hkrpg-go/internal/common" pb "gac-hkrpg-go/protocol/proto" ) // Grant transactions operate on the detached Preparation snapshot, then the // caller saves before sending the same acquisition updates used by Supply/shop. func cwApplyCommand(c *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, cmd common.CurrencyWarsCommand, rt ...*common.CurrencyWarsRuntimeState) (*pb.GridFightSyncUpdateResultScNotify, error) { player, team, shop := cwComponents(run) if player == nil { return nil, fmt.Errorf("CW player missing") } switch cmd.Field { case "gold": player.Gold = cmd.Value return cwNotify(cwGoldSync(player.Gold)), nil case "tbpower", "tb", "trailblaze": tbCat, err := common.LoadCurrencyWarsTrailblaze() if err != nil { return nil, err } var runtime *common.CurrencyWarsRuntimeState if len(rt) > 0 && rt[0] != nil { runtime = rt[0] } else { runtime, _ = cwBonfireRuntime(c, run) } if runtime == nil { runtime = &common.CurrencyWarsRuntimeState{RunID: run.UniqueId} } if runtime.Rules == nil { runtime.Rules = make(map[string]uint32) } layer := cwGetTrailblazeLayer(c, run) tier := tbCat.Tier(layer) if runtime.Rules["TbOpened"] == 0 { runtime.Rules["TbOpened"] = 1 stage, _ := cwStage(run) if stage != nil { runtime.Rules["TbNode"] = stage.DCAHJELNJNP*1000 + stage.SectionId } cwTrailblazeRoll(tbCat, run, runtime, tier) if tbBoughtCount(runtime, tbCat.InitialGoodID) == 0 { runtime.Rules["TbGood/0"] = tbCat.InitialGoodID } } runtime.Rules["TbPower"] = cmd.Value if cmd.Star > 0 { runtime.Rules["TbCosted"] = cmd.Star - 1 if runtime.Rules["TbCosted"] >= tbCat.PomThreshold && !tbOffered(runtime, tbCat.PomGoodID) { slotToPut := 0 for s := 0; s < cwTrailblazeSlots; s++ { if runtime.Rules[fmt.Sprintf("TbGood/%d", s)] == 0 { slotToPut = s break } } runtime.Rules[fmt.Sprintf("TbGood/%d", slotToPut)] = tbCat.PomGoodID } } cwSyncTraitComponent(c, run, runtime) bundle := &pb.ABPLALCHNFA{ GridUpdateSrc: pb.JJDFGMMMCMI_JJDFGMMMCMI_GLKAGBGLENM, OOEOCGLLHFO: []uint32{cwTrailblazeTrait, cwTrailblazeEffect}, JKPJFKCBIEM: []*pb.KFNCAIHIHCM{ { MNLCHEJKDHO: &pb.KFNCAIHIHCM_HKHJJCMKKJE{ HKHJJCMKKJE: &pb.JLKCBOIFJKC{ TraitId: cwTrailblazeTrait, ENPLACDLJGA: cwTrailblazeTraitEffectProto(tbCat, runtime), }, }, }, { MNLCHEJKDHO: &pb.KFNCAIHIHCM_GOOCHGKBIPN{ GOOCHGKBIPN: cwTrailblazeModifierProto(tbCat, runtime), }, }, }, } return &pb.GridFightSyncUpdateResultScNotify{ GCPNIHHBGGD: []*pb.ABPLALCHNFA{bundle}, }, nil case "hp": if cmd.Value == 0 || cmd.Value > player.PPKKHHNEOHB { return nil, fmt.Errorf("HP must be 1..%d", player.PPKKHHNEOHB) } player.GridFightLineupHp = cmd.Value return cwNotify(&pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_GridFightLineupHp{GridFightLineupHp: &pb.FMJDDIHBELC{GridFightLineupHp: cmd.Value, PPKKHHNEOHB: player.PPKKHHNEOHB}}}), nil case "exp": if shop == nil { return nil, fmt.Errorf("CW shop missing") } if err := cwApplyBaseExperience(c, player, shop, cmd.Value); err != nil { return nil, err } return cwNotify(cwCapacitySync(player), cwShopSync(shop), &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_PLPHEIBLPDL{PLPHEIBLPDL: &pb.PIHKLPJGEPM{Level: player.EHNJDIDABHD, Exp: player.GridFightCurLevelExp, HFMKLAPEKFA: player.NDAINFLOLPJ}}}), nil case "equip": items, err := common.LoadCurrencyWarsEquipment(c) if err != nil { return nil, err } if _, ok := items[cmd.Value]; !ok { return nil, fmt.Errorf("equipment %d not captured", cmd.Value) } bag := cwEquipmentBag(run) if bag == nil { return nil, fmt.Errorf("CW equipment bag missing") } quantity := cmd.Quantity if quantity == 0 { quantity = 1 } // Existing scalar queue format. if uint64(quantity) > uint64(^uint32(0))-uint64(len(bag.GridFightEquipmentList)) { return nil, fmt.Errorf("equipment count overflow") } added := &pb.AOFOEOIJEHF{} for i := uint32(0); i < quantity; i++ { uid, e := cwNextEntityID(run) if e != nil { return nil, e } // Source zero is protocol Default. Do not impersonate a Supply reward. gear := &pb.IJDLENJJBHJ{UniqueId: uid, GridFightEquipmentId: cmd.Value} bag.GridFightEquipmentList = append(bag.GridFightEquipmentList, gear) added.GridFightEquipmentList = append(added.GridFightEquipmentList, gear) } return cwNotify(&pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_LMOLELDIKMA{LMOLELDIKMA: added}}), nil case "orb": rewards, err := common.LoadCurrencyWarsRewards(c) if err != nil { return nil, err } var pool []uint32 if cmd.Value == 0 { recipes, err := common.LoadCurrencyWarsOrbRecipes() if err != nil { return nil, err } for _, r := range recipes.Recipes { if r.Status == "Implemented" && r.Engine == "Orb" { if _, ok := rewards.Orbs[r.ID]; ok { pool = append(pool, r.ID) } } } if len(pool) == 0 { return nil, fmt.Errorf("no implemented CW orbs found") } sort.Slice(pool, func(i, j int) bool { return pool[i] < pool[j] }) } else { if _, ok := rewards.Orbs[cmd.Value]; !ok { return nil, fmt.Errorf("Orb %d not captured", cmd.Value) } } var orbs *pb.DBONCGFHHDN for _, component := range run.NAGDJGMMCBE { if x := component.GetDBAGKJNPDOL(); x != nil { orbs = x } } if orbs == nil { orbs = &pb.DBONCGFHHDN{} run.NAGDJGMMCBE = append(run.NAGDJGMMCBE, &pb.GridFightGameInfo{ LEOIAOFBBFA: &pb.GridFightGameInfo_DBAGKJNPDOL{ DBAGKJNPDOL: orbs, }, }) } quantity := cmd.Quantity if quantity == 0 { quantity = 1 } notify := cwNotify() for i := uint32(0); i < quantity; i++ { orbID := cmd.Value if orbID == 0 { idx, err := rand.Int(rand.Reader, big.NewInt(int64(len(pool)))) if err != nil { return nil, err } orbID = pool[idx.Int64()] } uid, e := cwNextEntityID(run) if e != nil { return nil, e } orbs.MDKGCIKOMLI = append(orbs.MDKGCIKOMLI, &pb.IBPDPCOGDEP{UniqueId: uid, LDBNOMPCOKK: orbID}) notify.GCPNIHHBGGD[0].JKPJFKCBIEM = append(notify.GCPNIHHBGGD[0].JKPJFKCBIEM, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_AFILIPBHMOO{AFILIPBHMOO: &pb.MMEDLCAENAL{UniqueId: uid, LDBNOMPCOKK: orbID}}}) } return notify, nil case "consumable": rewards, err := common.LoadCurrencyWarsRewards(c) if err != nil { return nil, err } if _, ok := rewards.Consumables[cmd.Value]; !ok { return nil, fmt.Errorf("consumable %d not captured", cmd.Value) } bag := cwEquipmentBag(run) if bag == nil { return nil, fmt.Errorf("CW bag missing") } quantity := cmd.Quantity if quantity == 0 { quantity = 1 } if quantity > uint32(^uint32(0)>>1) { return nil, fmt.Errorf("consumable delta exceeds protocol int32") } var row *pb.LALKBPJANIH var group uint32 for _, x := range bag.IPMKOMEHBMP { if x.GroupId > group { group = x.GroupId } if x.ItemId == cmd.Value { row = x } } fresh := row == nil if fresh { if group == ^uint32(0) { return nil, fmt.Errorf("consumable groups exhausted") } row = &pb.LALKBPJANIH{GroupId: group + 1, ItemId: cmd.Value} bag.IPMKOMEHBMP = append(bag.IPMKOMEHBMP, row) } if uint64(row.Num)+uint64(quantity) > uint64(^uint32(0)) { return nil, fmt.Errorf("consumable stack overflow") } row.Num += quantity info := &pb.AOFOEOIJEHF{PFMDKHIDDLB: []*pb.FLGMGNEOFBH{{GroupId: row.GroupId, ItemId: row.ItemId, Num: row.Num, RoundCnt: row.RoundCnt, HFNNNGHBHGG: int32(quantity)}}} update := &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_DDBKEOPKPFI{DDBKEOPKPFI: info}} if fresh { update.MNLCHEJKDHO = &pb.KFNCAIHIHCM_LMOLELDIKMA{LMOLELDIKMA: info} } return cwNotify(update), nil case "forge": quantity := cmd.Quantity if quantity == 0 { quantity = 1 } notify := cwNotify() for i := uint32(0); i < quantity; i++ { update, err := cwGrantForge(c, run, cmd.Value) if err != nil { return nil, err } notify.GCPNIHHBGGD[0].JKPJFKCBIEM = append(notify.GCPNIHHBGGD[0].JKPJFKCBIEM, update) } return notify, nil case "role": if team == nil || len(team.GridGameRoleList) == 0 { return nil, fmt.Errorf("CW role property template missing") } if _, err := c.CapturedRoleVariant(cmd.Value, cmd.Star); err != nil { return nil, err } scales := team.GridGameRoleList[0].ConvertPropertyToFixpoint for _, r := range team.GridGameRoleList { if !maps.Equal(scales, r.ConvertPropertyToFixpoint) { return nil, fmt.Errorf("role property templates differ") } } front, err := c.IntegerRule("GridFight_Front_AvatarMaxNum") if err != nil { return nil, err } back, err := c.IntegerRule("GridFight_Back_AvatarMaxNum") if err != nil { return nil, err } quantity := cmd.Quantity if quantity == 0 { quantity = 1 } notify := &pb.GridFightSyncUpdateResultScNotify{} for i := uint32(0); i < quantity; i++ { occupied := map[uint32]bool{} for _, r := range team.GridGameRoleList { occupied[r.Pos] = true } for _, r := range team.KCOMPKMJCMC { occupied[r.Pos] = true } for _, f := range team.PMEOPPDAMLM { occupied[f.Pos] = true } var pos uint32 for p := uint64(front) + uint64(back) + 1; p <= uint64(front)+uint64(back)+uint64(player.MICEOJBAKGN) && p <= uint64(^uint32(0)); p++ { if !occupied[uint32(p)] { pos = uint32(p) break } } uid, e := cwNextEntityID(run) if e != nil { return nil, e } role := &pb.CDEAABODKCP{Id: cmd.Value, RoleStar: cmd.Star, UniqueId: uid, Pos: pos, ConvertPropertyToFixpoint: maps.Clone(scales)} team.GridGameRoleList = append(team.GridGameRoleList, role) notify.GCPNIHHBGGD = append(notify.GCPNIHHBGGD, cwNotify(&pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_PIDDDHKPDDM{PIDDDHKPDDM: role}}).GCPNIHHBGGD...) merged, e := cwMergeRoles(c, run, uid) if e != nil { return nil, e } notify.GCPNIHHBGGD = append(notify.GCPNIHHBGGD, merged...) for _, r := range team.GridGameRoleList { if r.Pos == 0 { return nil, fmt.Errorf("CW bench full; entire grant cancelled") } } } return notify, nil } return nil, fmt.Errorf("unknown CW command %q", cmd.Field) }