package gameserver import ( "fmt" "slices" "gac-hkrpg-go/internal/common" pb "gac-hkrpg-go/protocol/proto" ) // GridFightAddItemSourceType: CraftEquip=5 in the reference enum/builder. const cwEquipmentSourceCraft uint32 = 5 func cwCraftRecipe(recipes []common.CurrencyWarsCraft, target uint32, materialIDs []uint32) (*common.CurrencyWarsCraft, error) { if len(materialIDs) != 2 { return nil, fmt.Errorf("CW craft requires two material instances") } ids := slices.Clone(materialIDs) slices.Sort(ids) var found *common.CurrencyWarsCraft for i := range recipes { r := &recipes[i] if target != 0 && r.CraftEquipID != target { continue } cost := slices.Clone(r.CostEquipList) slices.Sort(cost) if slices.Equal(cost, ids) { if found != nil && found.CraftEquipID != r.CraftEquipID { return nil, fmt.Errorf("CW craft result is ambiguous") } found = r } } if found == nil { return nil, fmt.Errorf("CW material pair has no matching current recipe") } return found, nil } func cwCraftEquipment(catalog *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, uids []uint32, target uint32) (*pb.GridFightSyncUpdateResultScNotify, error) { return cwCraftEquipmentOnRole(catalog, run, uids, target, nil) } func cwCraftEquipmentOnRole(catalog *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, uids []uint32, target uint32, receiver *pb.CDEAABODKCP) (*pb.GridFightSyncUpdateResultScNotify, error) { if len(uids) != 2 || uids[0] == 0 || uids[0] == uids[1] { return nil, fmt.Errorf("CW craft material identities differ") } bag := cwEquipmentBag(run) _, team, _ := cwComponents(run) if bag == nil || team == nil { return nil, fmt.Errorf("CW craft inventory/team missing") } var materials []*pb.IJDLENJJBHJ var ids []uint32 for _, uid := range uids { var gear *pb.IJDLENJJBHJ for _, item := range bag.GridFightEquipmentList { if item.UniqueId == uid { gear = item break } } if gear == nil { return nil, fmt.Errorf("CW material does not belong to this run") } for _, role := range team.GridGameRoleList { if slices.Contains(role.PNJIPHJJONG, uid) && (receiver == nil || receiver.UniqueId != role.UniqueId) { return nil, fmt.Errorf("CW manual craft material is equipped") } } for _, npc := range team.GridGameNpcList { if slices.Contains(npc.PNJIPHJJONG, uid) { return nil, fmt.Errorf("CW manual craft material is equipped by NPC") } } materials = append(materials, gear) ids = append(ids, gear.GridFightEquipmentId) } recipes, err := common.LoadCurrencyWarsCrafts(catalog) if err != nil { return nil, err } recipe, err := cwCraftRecipe(recipes, target, ids) if err != nil { return nil, err } equipment, err := common.LoadCurrencyWarsEquipment(catalog) if err != nil { return nil, err } if _, ok := equipment[recipe.CraftEquipID]; !ok { return nil, fmt.Errorf("CW craft result configuration %d not captured", recipe.CraftEquipID) } uid, err := cwNextEntityID(run) if err != nil { return nil, err } gear := &pb.IJDLENJJBHJ{UniqueId: uid, GridFightEquipmentId: recipe.CraftEquipID, Source: cwEquipmentSourceCraft} if receiver != nil { receiver.PNJIPHJJONG = slices.DeleteFunc(receiver.PNJIPHJJONG, func(id uint32) bool { return slices.Contains(uids, id) }) if err = cwCanDressEquipment(catalog, equipment, bag, receiver, gear); err != nil { return nil, err } receiver.PNJIPHJJONG = append(receiver.PNJIPHJJONG, gear.UniqueId) } bag.GridFightEquipmentList = slices.DeleteFunc(bag.GridFightEquipmentList, func(x *pb.IJDLENJJBHJ) bool { return slices.Contains(uids, x.UniqueId) }) bag.GridFightEquipmentList = append(bag.GridFightEquipmentList, gear) // Current reference admin acquisition proves LMOLELDIKMA is Add. The // source/Client sync family maps PENGEDECBNF to Remove, DDBKEOPKPFI to // Update; never send the acquisition using the remove arm. notify := cwNotify(&pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_PENGEDECBNF{PENGEDECBNF: &pb.AOFOEOIJEHF{GridFightEquipmentList: materials}}}, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_LMOLELDIKMA{LMOLELDIKMA: &pb.AOFOEOIJEHF{GridFightEquipmentList: []*pb.IJDLENJJBHJ{gear}}}}) if receiver != nil { notify.GCPNIHHBGGD[0].JKPJFKCBIEM = append(notify.GCPNIHHBGGD[0].JKPJFKCBIEM, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_PIDDDHKPDDM{PIDDDHKPDDM: receiver}}) } return notify, nil } func init() { cwHandle("GridFightEquipCraft", func(s *Session, req *pb.GridFightEquipCraftCsReq, rsp *pb.GridFightEquipCraftScRsp) error { rsp.Retcode = uint32(pb.Retcode_RET_GRID_FIGHT_PARAM_NOT_MATCH) catalog, data, err := cwPreparingData(s) if err != nil { return nil } if _, ok := pb.ACNPGGHPDJB_name[int32(req.Source)]; !ok { return nil } if req.JCNLCNFHHDG == 0 { return nil } // Request offset +0x24 is target equipment (current wire tag 10), // +0x28 is optional track ID (tag 13). Isolated current-Client wire // evidence ev_6340e59770f2b6839244f07ec5054aefd568e58d9723650e3bbe1cdf1a7c2a25. notify, err := cwCraftEquipment(catalog, data.DHFOFLFCLAH, req.EHDAKFEHPMF, req.JCNLCNFHHDG) if err != nil { PacketLog("CW_CRAFT rejected: " + err.Error()) return nil } if err = s.saveCurrencyWarsData(data, catalog); err != nil { return err } if err = cwSend(s, notify); err != nil { return err } rsp.Retcode = 0 PacketLog(fmt.Sprintf("CW_CRAFT saved materials=%v target=%d track=%d revision=%d", req.EHDAKFEHPMF, req.JCNLCNFHHDG, req.NNFECNICMOD, s.currencyWarsRevision)) return nil }) }