package gameserver import ( "fmt" "gac-hkrpg-go/internal/common" pb "gac-hkrpg-go/protocol/proto" "maps" ) // 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) (*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 "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 } 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 { return nil, fmt.Errorf("CW Orb component missing") } quantity := cmd.Quantity if quantity == 0 { quantity = 1 } notify := cwNotify() for i := uint32(0); i < quantity; i++ { uid, e := cwNextEntityID(run) if e != nil { return nil, e } orbs.MDKGCIKOMLI = append(orbs.MDKGCIKOMLI, &pb.IBPDPCOGDEP{UniqueId: uid, LDBNOMPCOKK: cmd.Value}) notify.GCPNIHHBGGD[0].JKPJFKCBIEM = append(notify.GCPNIHHBGGD[0].JKPJFKCBIEM, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_AFILIPBHMOO{AFILIPBHMOO: &pb.MMEDLCAENAL{UniqueId: uid, LDBNOMPCOKK: cmd.Value}}}) } 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) }