151 lines
5.4 KiB
Go
151 lines
5.4 KiB
Go
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
|
|
})
|
|
}
|