package gameserver import ( "fmt" "slices" "gac-hkrpg-go/internal/common" pb "gac-hkrpg-go/protocol/proto" ) // These discriminators are GridFightEquipmentType / GridFightEquipDressType // definitions, mirrored by the reference equipment rule switch. Item IDs, // limits, role restrictions and properties always come from Client resources. const ( cwEquipNormal uint32 = iota cwEquipImplant ) const ( cwDressNotUnique uint32 = iota cwDressUnique cwDressEmpty cwDressLeader cwDressTraitOnly cwDressUniqueExclusiveTrait cwDressRoleOnly ) func cwEquipmentBag(run *pb.MPIMAEMCEFH) *pb.CONONEALNOF { for _, component := range run.NAGDJGMMCBE { if bag := component.GetGCKLIONKMJC(); bag != nil { return bag } } return nil } func cwCanDressEquipment(c *common.CurrencyWarsCatalog, equipment map[uint32]common.CurrencyWarsEquipment, bag *pb.CONONEALNOF, role *pb.CDEAABODKCP, gear *pb.IJDLENJJBHJ) error { row, ok := equipment[gear.GridFightEquipmentId] if !ok { return fmt.Errorf("equipment configuration is not captured") } config, err := c.CapturedRole(role.Id) if err != nil { return err } limitName := "GridFight_AvatarDressEquipNum" if row.EquipType == cwEquipImplant { limitName = "GridFight_AvatarDressImplantsNum" } else if row.EquipType != cwEquipNormal { return fmt.Errorf("equipment type is unsupported") } limit, err := c.IntegerRule(limitName) if err != nil || limit < 0 { return fmt.Errorf("equipment slot limit is missing") } var sameType int64 unique := true for _, uid := range role.PNJIPHJJONG { index := slices.IndexFunc(bag.GridFightEquipmentList, func(x *pb.IJDLENJJBHJ) bool { return x != nil && x.UniqueId == uid }) if index < 0 { return fmt.Errorf("equipped item is absent from inventory") } other, exists := equipment[bag.GridFightEquipmentList[index].GridFightEquipmentId] if !exists { return fmt.Errorf("equipped item configuration is missing") } if other.EquipType == row.EquipType { sameType++ } if other.ItemID == row.ItemID { unique = false } if row.EquipType == cwEquipNormal && other.EquipType == cwEquipNormal && other.DressRule == cwDressEmpty { return fmt.Errorf("existing equipment requires all other slots empty") } } if row.NeedEquipSlot && sameType >= limit { return fmt.Errorf("equipment slots are full") } allowed := false switch row.DressRule { case cwDressNotUnique: allowed = true case cwDressUnique: allowed = unique case cwDressEmpty: allowed = sameType == 0 case cwDressLeader: allowed = role.Pos == 1 case cwDressTraitOnly: for _, trait := range row.DressRuleParamList { if slices.Contains(config.Config.TraitList, trait) { allowed = true } } case cwDressUniqueExclusiveTrait: allowed = unique for _, trait := range row.DressRuleParamList { if slices.Contains(config.Config.TraitList, trait) { allowed = false } } case cwDressRoleOnly: allowed = slices.Contains(row.DressRuleParamList, role.Id) } if !allowed { return fmt.Errorf("equipment dress restriction does not match target") } return nil } func init() { cwHandle("GridFightEquipDress", func(s *Session, req *pb.GridFightEquipDressCsReq, rsp *pb.GridFightEquipDressScRsp) error { rsp.Retcode = uint32(pb.Retcode_RET_GRID_FIGHT_PARAM_NOT_MATCH) catalog, data, err := cwPreparingData(s) if err != nil { return nil } run := data.DHFOFLFCLAH _, team, _ := cwComponents(run) bag := cwEquipmentBag(run) if team == nil || bag == nil || req.PEBCBLBLMPH == 0 || req.MDNAGANGNKK == 0 { return nil } if _, ok := pb.DMGOKGLBNJJ_name[int32(req.CPFCNOAJCGK)]; !ok { return nil } // All entities have run-global unique IDs. CPFCNOAJCGK is the operation // source (None/Manual/Recommend), not the target's entity kind. var role *pb.CDEAABODKCP for _, x := range team.GridGameRoleList { if x.UniqueId == req.MDNAGANGNKK { role = x break } } if role == nil { return nil } var gear *pb.IJDLENJJBHJ for _, x := range bag.GridFightEquipmentList { if x.UniqueId == req.PEBCBLBLMPH { gear = x break } } if gear == nil || slices.Contains(role.PNJIPHJJONG, gear.UniqueId) { return nil } equipment, err := common.LoadCurrencyWarsEquipment(catalog) if err != nil { PacketLog("CW_EQUIP_DRESS rejected: " + err.Error()) return nil } // Dressing a component onto an existing component crafts the pair on // that role. Resolve the pair through the captured current recipe table. recipes, err := common.LoadCurrencyWarsCrafts(catalog) if err != nil { PacketLog("CW_EQUIP_DRESS rejected: " + err.Error()) return nil } for _, equippedUID := range role.PNJIPHJJONG { var other *pb.IJDLENJJBHJ for _, x := range bag.GridFightEquipmentList { if x.UniqueId == equippedUID { other = x break } } if other == nil { return nil } recipe, recipeErr := cwCraftRecipe(recipes, 0, []uint32{gear.GridFightEquipmentId, other.GridFightEquipmentId}) if recipeErr != nil { continue } // Auto-craft cannot take a component from another wearer. for _, x := range team.GridGameRoleList { if x.UniqueId != role.UniqueId && slices.Contains(x.PNJIPHJJONG, gear.UniqueId) { return nil } } notify, craftErr := cwCraftEquipmentOnRole(catalog, run, []uint32{gear.UniqueId, other.UniqueId}, recipe.CraftEquipID, role) if craftErr != nil { PacketLog("CW_EQUIP_DRESS auto-craft rejected: " + craftErr.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_EQUIP_DRESS auto-crafted role=%d target=%d revision=%d", role.UniqueId, recipe.CraftEquipID, s.currencyWarsRevision)) return nil } if err = cwCanDressEquipment(catalog, equipment, bag, role, gear); err != nil { PacketLog("CW_EQUIP_DRESS rejected: " + err.Error()) return nil } var changed []*pb.KFNCAIHIHCM for _, other := range team.GridGameRoleList { if slices.Contains(other.PNJIPHJJONG, gear.UniqueId) { other.PNJIPHJJONG = slices.DeleteFunc(other.PNJIPHJJONG, func(id uint32) bool { return id == gear.UniqueId }) changed = append(changed, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_PIDDDHKPDDM{PIDDDHKPDDM: other}}) } } // Moving from NPC equipment needs the verified NPC-update sync arm. for _, npc := range team.GridGameNpcList { if slices.Contains(npc.PNJIPHJJONG, gear.UniqueId) { return nil } } role.PNJIPHJJONG = append(role.PNJIPHJJONG, gear.UniqueId) changed = append(changed, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_PIDDDHKPDDM{PIDDDHKPDDM: role}}) if err = s.saveCurrencyWarsData(data, catalog); err != nil { return err } if err = cwSend(s, cwNotify(changed...)); err != nil { return err } rsp.Retcode = 0 PacketLog(fmt.Sprintf("CW_EQUIP_DRESS saved role=%d equipment=%d revision=%d", role.UniqueId, gear.UniqueId, s.currencyWarsRevision)) return nil }) }