package gameserver import ( "crypto/rand" "fmt" "gac-hkrpg-go/internal/common" pb "gac-hkrpg-go/protocol/proto" "google.golang.org/protobuf/proto" "math/big" "sort" ) func cwSpotlights(run *pb.MPIMAEMCEFH) *pb.GNOBLNOCEHA { for _, c := range run.NAGDJGMMCBE { if v := c.GetLEGMAJLHBLA(); v != nil { return v } } return nil } func cwEnsureSpotlights(run *pb.MPIMAEMCEFH) bool { if cwSpotlights(run) != nil { return false } run.NAGDJGMMCBE = append(run.NAGDJGMMCBE, &pb.GridFightGameInfo{LEOIAOFBBFA: &pb.GridFightGameInfo_LEGMAJLHBLA{LEGMAJLHBLA: &pb.GNOBLNOCEHA{}}}) return true } func cwSpotlightChoices(c *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH) (*pb.BDLBHJGNCFM, error) { rows, policy, e := c.SpotlightData() if e != nil { return nil, e } _, team, _ := cwComponents(run) if team == nil { return nil, fmt.Errorf("CW Spotlight team missing") } owned := map[uint32]bool{} for _, r := range team.GridGameRoleList { owned[r.Id] = true } used := map[uint32]bool{} if v := cwSpotlights(run); v != nil { for _, r := range v.FNNEPMAPOOF { used[r.RoleId] = true } } var ids, safeIDs []uint32 for id, row := range rows { if run.IsOverlock && !row.IsOCEffective { continue } compatible, hasUsedRole := false, false for _, role := range row.SuitableRoles { if owned[role] && !used[role] { compatible = true } if owned[role] && used[role] { hasUsedRole = true } } if compatible { ids = append(ids, id) if !hasUsedRole { safeIDs = append(safeIDs, id) } } } // The Client can offer every compatible owned role, including one with an // existing binding. Prefer options without such roles when available so a // valid-looking selection cannot be rejected as a duplicate. This is a // server offer policy, not a claim about the original selection algorithm. if len(safeIDs) > 0 { ids = safeIDs } sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] }) if len(ids) == 0 { return nil, fmt.Errorf("CW no compatible Spotlight choices for owned roles") } // Explicit resource policy, not an invented claim about native weights. count := int(policy.OfferCount) if count > len(ids) { count = len(ids) } out := &pb.BDLBHJGNCFM{BKOGIFHEKHH: true, ACKOKJBBPKH: true, DOAHJPPPMGM: policy.FreeRefreshCount} for i := 0; i < count; i++ { n, e := rand.Int(rand.Reader, big.NewInt(int64(len(ids)-i))) if e != nil { return nil, e } j := i + int(n.Int64()) ids[i], ids[j] = ids[j], ids[i] out.HPKFKBKOADP = append(out.HPKFKBKOADP, &pb.FGEOPPMIHJM{IHHNLLAJAGB: ids[i]}) } return out, nil } func cwSelectSpotlight(c *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, selection *pb.NIJPHAAONFB) (*pb.GridFightSyncUpdateResultScNotify, error) { if selection == nil || run.FMIGOKIDKJL == nil || run.FMIGOKIDKJL.GetEEKMIAMGHFL() == nil { return nil, fmt.Errorf("CW Spotlight is not pending") } pending := run.FMIGOKIDKJL offer := pending.GetEEKMIAMGHFL() if !offer.BKOGIFHEKHH || offer.GFGJOKJGGCO != 0 { return nil, fmt.Errorf("CW Spotlight replacement is not a grant") } offered := false for _, v := range offer.HPKFKBKOADP { offered = offered || v.IHHNLLAJAGB == selection.IHHNLLAJAGB } if !offered { return nil, fmt.Errorf("CW Spotlight choice not offered") } rows, _, e := c.SpotlightData() if e != nil { return nil, e } row, ok := rows[selection.IHHNLLAJAGB] if !ok { return nil, fmt.Errorf("CW Spotlight configuration missing") } compatible := false for _, id := range row.SuitableRoles { compatible = compatible || id == selection.RoleId } if !compatible { return nil, fmt.Errorf("CW Spotlight role is incompatible") } player, team, _ := cwComponents(run) if player == nil || team == nil { return nil, fmt.Errorf("CW Spotlight player or team missing") } var owned *pb.CDEAABODKCP for _, r := range team.GridGameRoleList { if r.Id == selection.RoleId && (owned == nil || r.RoleStar > owned.RoleStar || r.RoleStar == owned.RoleStar && r.UniqueId < owned.UniqueId) { owned = r } } if owned == nil { return nil, fmt.Errorf("CW Spotlight role not owned") } cwEnsureSpotlights(run) state := cwSpotlights(run) for _, v := range state.FNNEPMAPOOF { if v.RoleId == selection.RoleId { return nil, fmt.Errorf("CW role already has Spotlight") } } uid, e := cwNextEntityID(run) if e != nil { return nil, e } // AddOrUpdate shuffles proto fields into the binding record; MDNAGANGNKK // becomes RoleUniqueID, RoleId remains the configuration ID. Native // GetActiveSpotLightBindingByRoleId + binding constructor corroborate it. item := &pb.FMBACDOFCAL{UniqueId: uid, IHHNLLAJAGB: row.ID, RoleId: selection.RoleId, MDNAGANGNKK: owned.UniqueId} state.FNNEPMAPOOF = append(state.FNNEPMAPOOF, item) stage, node := cwStage(run) if stage == nil || node == nil || !node.HPNNLMBHOPF { return nil, fmt.Errorf("CW current node has no unclaimed Spotlight") } node.HPNNLMBHOPF = false var routeNode *pb.BCCIJHPLJOJ for _, v := range stage.OOMMIIKPHBC { if v.DCAHJELNJNP == stage.DCAHJELNJNP && v.SectionId == stage.SectionId { routeNode = v break } } if routeNode == nil { return nil, fmt.Errorf("CW current route node missing") } run.FMIGOKIDKJL = nil player.GAJGBBFELEL = &pb.GMBMNFLMKPI{} return cwNotify(&pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_OHHBBOGFDON{OHHBBOGFDON: proto.Clone(item).(*pb.FMBACDOFCAL)}}, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_KBHOBGHKIBA{KBHOBGHKIBA: &pb.BAAGMMIDHDB{DBIIPEEDBHI: proto.Clone(routeNode).(*pb.BCCIJHPLJOJ)}}}, cwFinish(pending.QueuePosition), cwUnlock()), nil } func init() { cwHandle("GridFightSpotLightEnter", func(s *Session, _ *pb.GridFightSpotLightEnterCsReq, rsp *pb.GridFightSpotLightEnterScRsp) error { rsp.Retcode = uint32(pb.Retcode_RET_GRID_FIGHT_CONF_MISS) c, data, e := cwPreparingData(s) if e != nil { PacketLog("CW_SPOTLIGHT_ENTER rejected: " + e.Error()) return nil } run := data.DHFOFLFCLAH _, node := cwStage(run) if node == nil || !node.HPNNLMBHOPF { rsp.Retcode = uint32(pb.Retcode_RET_GRID_FIGHT_PARAM_NOT_MATCH) return nil } if s.currencyWarsRevision >= uint64(^uint32(0)) { return nil } offer, e := cwSpotlightChoices(c, run) if e != nil { PacketLog("CW_SPOTLIGHT_ENTER rejected: " + e.Error()) return nil } cwEnsureSpotlights(run) pending := &pb.CHCHAJIANIM{QueuePosition: uint32(s.currencyWarsRevision + 1), FGLIODAJFML: &pb.CHCHAJIANIM_EEKMIAMGHFL{EEKMIAMGHFL: offer}} run.FMIGOKIDKJL = pending if e = s.saveCurrencyWarsData(data, c); e != nil { return nil } if e = cwSend(s, cwNotify(&pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_FMIGOKIDKJL{FMIGOKIDKJL: pending}})); e != nil { return e } rsp.Retcode = 0 PacketLog(fmt.Sprintf("CW_SPOTLIGHT_ENTER offered=%d queue=%d revision=%d", len(offer.HPKFKBKOADP), pending.QueuePosition, s.currencyWarsRevision)) return nil }) }