package gameserver import ( "fmt" "gac-hkrpg-go/internal/common" pb "gac-hkrpg-go/protocol/proto" "google.golang.org/protobuf/proto" "maps" "sort" ) // Basic identical-role merge, following the captured remove/add sync. Existing // equipment stays in the bag; only the retained role's attachment list moves. // Switch-role groups and Spotlight star locks need their separately mapped rules. func cwMergeRoles(c *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, preferred uint32) ([]*pb.ABPLALCHNFA, error) { _, team, _ := cwComponents(run) if team == nil { return nil, fmt.Errorf("CW team missing") } rules, e := c.MergeRules() if e != nil { return nil, e } front, e := c.IntegerRule("GridFight_Front_AvatarMaxNum") if e != nil { return nil, e } back, e := c.IntegerRule("GridFight_Back_AvatarMaxNum") if e != nil { return nil, e } active := func(r *pb.CDEAABODKCP) bool { return r.Pos > 0 && uint64(r.Pos) <= uint64(front+back) } better := func(a, b *pb.CDEAABODKCP) bool { if active(a) != active(b) { return active(a) } if len(a.PNJIPHJJONG) != len(b.PNJIPHJJONG) { return len(a.PNJIPHJJONG) > len(b.PNJIPHJJONG) } return a.UniqueId < b.UniqueId } var groups []*pb.ABPLALCHNFA for { candidates := map[[2]uint32][]*pb.CDEAABODKCP{} for _, r := range team.GridGameRoleList { if r.RoleStar < rules.OrdinaryMaxStar { key := [2]uint32{r.Id, r.RoleStar} candidates[key] = append(candidates[key], r) } } keys := make([][2]uint32, 0, len(candidates)) for k, v := range candidates { if len(v) >= int(rules.Copies) { keys = append(keys, k) } } sort.Slice(keys, func(i, j int) bool { if keys[i][1] != keys[j][1] { return keys[i][1] < keys[j][1] } return keys[i][0] < keys[j][0] }) var selected []*pb.CDEAABODKCP for _, key := range keys { if _, err := c.CapturedRoleVariant(key[0], key[1]+1); err != nil { continue } selected = candidates[key] sort.Slice(selected, func(i, j int) bool { if (selected[i].UniqueId == preferred) != (selected[j].UniqueId == preferred) { return selected[i].UniqueId == preferred } return better(selected[i], selected[j]) }) selected = selected[:int(rules.Copies)] break } if len(selected) == 0 { return groups, nil } sort.Slice(selected, func(i, j int) bool { return better(selected[i], selected[j]) }) anchor := selected[0] for _, r := range selected { if !maps.Equal(anchor.ConvertPropertyToFixpoint, r.ConvertPropertyToFixpoint) || !maps.Equal(anchor.BABNHPDGNGB, r.BABNHPDGNGB) || !maps.Equal(anchor.GridFightValueInitComponent, r.GridFightValueInitComponent) { return nil, fmt.Errorf("CW merge role modifiers need an explicit transfer rule") } } id, err := cwNextEntityID(run) if err != nil { return nil, err } merged := proto.Clone(anchor).(*pb.CDEAABODKCP) merged.UniqueId = id merged.RoleStar++ removed := map[uint32]bool{} group := &pb.ABPLALCHNFA{GridUpdateSrc: pb.JJDFGMMMCMI_JJDFGMMMCMI_NJBMBFFLNGM} for _, r := range selected { removed[r.UniqueId] = true group.JKPJFKCBIEM = append(group.JKPJFKCBIEM, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_HFCBOIOAECK{HFCBOIOAECK: r.UniqueId}}) } kept := make([]*pb.CDEAABODKCP, 0, len(team.GridGameRoleList)-len(selected)+1) for _, r := range team.GridGameRoleList { if !removed[r.UniqueId] { kept = append(kept, r) } } team.GridGameRoleList = append(kept, merged) group.JKPJFKCBIEM = append(group.JKPJFKCBIEM, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_PIDDDHKPDDM{PIDDDHKPDDM: proto.Clone(merged).(*pb.CDEAABODKCP)}}) if state := cwSpotlights(run); state != nil { for _, binding := range state.FNNEPMAPOOF { if removed[binding.MDNAGANGNKK] { binding.MDNAGANGNKK = merged.UniqueId group.JKPJFKCBIEM = append(group.JKPJFKCBIEM, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_OHHBBOGFDON{OHHBBOGFDON: proto.Clone(binding).(*pb.FMBACDOFCAL)}}) } } } groups = append(groups, group) preferred = id } }