where('r.direction', $direction) ->field('r.id,r.tier,r.direction,r.end_index,r.weight,r.grid_number,r.start_index,r.ui_text,r.real_ev,r.remark,r.type,r.create_time,r.update_time') ->order($orderField, $orderType) ->order('r.end_index', 'asc'); if ($tier !== '') { $query->where('r.tier', $tier); } $paginator = $query->paginate($limit, false, ['page' => $page]); $arr = $paginator->toArray(); $data = isset($arr['data']) ? $arr['data'] : $arr['records'] ?? []; $total = (int) ($arr['total'] ?? 0); $perPage = (int) ($arr['per_page'] ?? $limit); $currentPage = (int) ($arr['current_page'] ?? $page); foreach ($data as $i => $row) { if (isset($row['id']) && $row['id'] !== '' && $row['id'] !== null) { $data[$i]['id'] = (int) $row['id']; } else { $data[$i]['id'] = isset($row['end_index']) ? (int) $row['end_index'] : 0; } $data[$i]['start_index'] = isset($row['start_index']) && $row['start_index'] !== '' && $row['start_index'] !== null ? (int) $row['start_index'] : 0; } return [ 'total' => $total, 'per_page' => $perPage, 'current_page' => $currentPage, 'data' => $data, ]; } /** * 按单方向批量更新权重(仅更新当前方向的 weight,并刷新缓存) * @param int $direction 0=顺时针 1=逆时针 * @param array $items id 为 end_index(DiceRewardConfig.id) */ public function batchUpdateWeightsByDirection(int $direction, array $items): void { if (empty($items)) { return; } foreach ($items as $item) { if (!is_array($item)) { continue; } $id = isset($item['id']) ? (int) $item['id'] : 0; $weight = isset($item['weight']) ? (int) $item['weight'] : self::WEIGHT_MIN; if ($id <= 0) { throw new ApiException('存在无效的配置ID'); } $weight = max(self::WEIGHT_MIN, min(self::WEIGHT_MAX, $weight)); $tier = DiceRewardConfig::where('id', $id)->value('tier'); if ($tier === null || $tier === '') { throw new ApiException('配置ID ' . $id . ' 不存在或档位为空'); } $tier = (string) $tier; $affected = DiceReward::where('tier', $tier)->where('direction', $direction)->where('end_index', $id)->update(['weight' => $weight]); if ($affected === 0) { $m = new DiceReward(); $m->tier = $tier; $m->direction = $direction; $m->end_index = $id; $m->weight = $weight; $m->save(); } } DiceReward::refreshCache(); } /** * 按档位+单方向返回列表(用于权重编辑弹窗:当前方向下按档位分组的配置+权重) * @param int $direction 0=顺时针 1=逆时针 * @return array 键 T1|T2|...|BIGWIN,值为该档位下带 weight 的行数组 */ public function getListGroupedByTierForDirection(int $direction): array { $configInstance = DiceRewardConfig::getCachedInstance(); $byTier = $configInstance['by_tier'] ?? []; $rewardInstance = DiceReward::getCachedInstance(); $byTierDirection = $rewardInstance['by_tier_direction'] ?? []; $result = []; foreach (self::TIER_KEYS as $tier) { $result[$tier] = []; $rows = $byTier[$tier] ?? []; $dirRows = $byTierDirection[$tier][$direction] ?? []; $weightMap = []; foreach ($dirRows as $r) { $eid = isset($r['end_index']) ? (int) $r['end_index'] : 0; $weightMap[$eid] = isset($r['weight']) ? max(self::WEIGHT_MIN, min(self::WEIGHT_MAX, (int) $r['weight'])) : 1; } foreach ($rows as $row) { $id = isset($row['id']) ? (int) $row['id'] : 0; $result[$tier][] = [ 'id' => $id, 'grid_number' => $row['grid_number'] ?? 0, 'ui_text' => $row['ui_text'] ?? '', 'real_ev' => $row['real_ev'] ?? 0, 'remark' => $row['remark'] ?? '', 'tier' => $tier, 'weight' => $weightMap[$id] ?? 1, ]; } } return $result; } /** * 按档位+方向返回 DiceReward 列表(用于权重配比弹窗),直接读 dice_reward 表,不依赖 config * 每行含 reward_id(DiceReward 主键,用于按 id 更新权重)、id(end_index 展示用)、grid_number、ui_text、real_ev、remark、weight * * @return array */ public function getListGroupedByTierWithDirection(): array { $rewardInstance = DiceReward::getCachedInstance(); $byTierDirection = $rewardInstance['by_tier_direction'] ?? []; $result = []; foreach (self::TIER_KEYS as $tier) { $result[$tier] = [0 => [], 1 => []]; foreach ([0, 1] as $direction) { $rows = $byTierDirection[$tier][$direction] ?? []; foreach ($rows as $r) { $result[$tier][$direction][] = [ 'reward_id' => isset($r['id']) ? (int) $r['id'] : 0, 'id' => isset($r['end_index']) ? (int) $r['end_index'] : 0, 'grid_number' => isset($r['grid_number']) ? (int) $r['grid_number'] : 0, 'ui_text' => (string) ($r['ui_text'] ?? ''), 'real_ev' => $r['real_ev'] ?? 0, 'remark' => (string) ($r['remark'] ?? ''), 'weight' => isset($r['weight']) ? max(self::WEIGHT_MIN, min(self::WEIGHT_MAX, (int) $r['weight'])) : self::WEIGHT_MIN, ]; } } } return $result; } /** * 批量更新权重:直接按 DiceReward 主键 id 更新 weight,不依赖 direction/grid_number * * @param array $items 每项 id 为 dice_reward 表主键,weight 为 1-10000 * @throws ApiException */ public function batchUpdateWeights(array $items): void { if (empty($items)) { return; } foreach ($items as $item) { if (!is_array($item)) { continue; } $id = isset($item['id']) ? (int) $item['id'] : 0; $weight = isset($item['weight']) ? (int) $item['weight'] : self::WEIGHT_MIN; if ($id <= 0) { throw new ApiException('存在无效的 DiceReward id'); } $weight = max(self::WEIGHT_MIN, min(self::WEIGHT_MAX, $weight)); DiceReward::where('id', $id)->update(['weight' => $weight]); } DiceReward::refreshCache(); } /** BIGWIN 权重范围:0=0% 中奖,10000=100% 中奖;grid_number=5/30 固定 100% 不可改 */ private const BIGWIN_WEIGHT_MAX = 10000; /** * 按 grid_number 获取 BIGWIN 档位权重(取顺时针方向,用于编辑展示) * 若 DiceReward 无该点数则 5/30 返回 10000,其余返回 0 */ public function getBigwinWeightByGridNumber(int $gridNumber): int { $inst = DiceReward::getCachedInstance(); $rows = $inst['by_tier_direction']['BIGWIN'][DiceReward::DIRECTION_CLOCKWISE] ?? []; foreach ($rows as $row) { if ((int) ($row['grid_number'] ?? 0) === $gridNumber) { return min(self::BIGWIN_WEIGHT_MAX, (int) ($row['weight'] ?? self::BIGWIN_WEIGHT_MAX)); } } return in_array($gridNumber, [5, 30], true) ? self::BIGWIN_WEIGHT_MAX : 0; } /** * 更新 BIGWIN 档位某点数的权重(顺/逆时针同时更新);0=0% 中奖,10000=100% 中奖 */ public function updateBigwinWeight(int $gridNumber, int $weight): void { $weight = min(self::BIGWIN_WEIGHT_MAX, max(0, $weight)); foreach ([DiceReward::DIRECTION_CLOCKWISE, DiceReward::DIRECTION_COUNTERCLOCKWISE] as $direction) { // 优先更新已存在记录 $affected = DiceReward::where('tier', 'BIGWIN') ->where('direction', $direction) ->where('grid_number', $gridNumber) ->update(['weight' => $weight]); // 若不存在 BIGWIN 记录,则按当前 BIGWIN 配置懒加载创建一条 dice_reward 记录 if ($affected === 0) { $config = DiceRewardConfig::where('tier', 'BIGWIN') ->where('grid_number', $gridNumber) ->find(); if ($config) { $m = new DiceReward(); $m->tier = 'BIGWIN'; $m->direction = $direction; $m->grid_number = (int) $gridNumber; // 对于 BIGWIN,仅需保证 real_ev、weight、grid_number,start_index/end_index 取当前配置 id 即可 $m->start_index = (int) $config->id; $m->end_index = (int) $config->id; $m->ui_text = (string) ($config->ui_text ?? ''); $m->real_ev = (float) ($config->real_ev ?? 0); $m->remark = (string) ($config->remark ?? ''); $m->type = $config->type ?? null; $m->weight = $weight > 0 ? $weight : self::WEIGHT_MIN; $m->save(); } } } DiceReward::refreshCache(); } /** 盘面格数(用于顺时针/逆时针计算 end_index) */ private const BOARD_SIZE = 26; /** 点数摇取范围:5-30,顺时针与逆时针均需创建 */ private const GRID_NUMBER_MIN = 5; private const GRID_NUMBER_MAX = 30; /** * 创建奖励对照:先清空 dice_reward 表,再按两种方向为点数 5-30 生成记录。 * * DiceReward 记录数据规则(与 config 通过 end_index 关联): * - 方向:direction = 0(顺时针)/ 1(逆时针) * - 摇取点数:grid_number * - 起始索引:start_index = DiceRewardConfig::where('grid_number', $grid_number)->first()->id * - 结束索引(顺时针):end_index = ($start_index + $grid_number) % 26(对 26 取余) * - 结束索引(逆时针):end_index = ($start_index - $grid_number >= 0) ? ($start_index - $grid_number) : (26 + $start_index - $grid_number) * - 奖励档位:tier = DiceRewardConfig::where('id', $end_index)->first()->tier * - 显示ui:ui_text = DiceRewardConfig::where('id', $end_index)->first()->ui_text * - 实际中奖:real_ev = DiceRewardConfig::where('id', $end_index)->first()->real_ev * - 备注:remark = DiceRewardConfig::where('id', $end_index)->first()->remark * - 类型:type = DiceRewardConfig::where('id', $end_index)->first()->type(-2=唯一惩罚,-1=抽水,0=回本,1=再来一次,2=小赚,3=大奖格) * - weight 默认 1,后续在权重编辑弹窗设置 * * 例如顺时针摇取点数为 5 时:start_index = 配置中 grid_number=5 对应格位的 id, * 结束位置 = (起始位置 + grid_number) % 26,再取该位置的 config 的 id 作为 end_index。 * 使用「按 id 排序后的盘面位置 0-25」做环形计算,避免 config.id 非连续时取模结果找不到; * 唯一键为 (direction, grid_number),保证每个点数、每个方向各一条记录,不因 end_index 相同而覆盖。 * * @return array{created_clockwise: int, created_counterclockwise: int, updated_clockwise: int, updated_counterclockwise: int, skipped: int} * @throws ApiException */ public function createRewardReferenceFromConfig(): array { $list = DiceRewardConfig::order('id', 'asc')->select()->toArray(); if (empty($list)) { throw new ApiException('奖励配置为空,请先维护 dice_reward_config'); } $configCount = count($list); if ($configCount < self::BOARD_SIZE) { throw new ApiException( '奖励配置需覆盖 26 个格位(id 0-25 或 1-26),当前仅 ' . $configCount . ' 条,无法完整生成 5-30 共26个点数、顺时针与逆时针的奖励对照' ); } $table = (new DiceReward())->getTable(); Db::execute('DELETE FROM `' . $table . '`'); DiceReward::refreshCache(); // 按 id 排序后,盘面位置 0..25 对应 $list[$pos],避免 config.id 非 0-25/1-26 时取模结果找不到 $gridToPosition = []; foreach ($list as $pos => $row) { $gn = isset($row['grid_number']) ? (int) $row['grid_number'] : 0; if ($gn >= self::GRID_NUMBER_MIN && $gn <= self::GRID_NUMBER_MAX && !isset($gridToPosition[$gn])) { $gridToPosition[$gn] = $pos; } } $createdCw = 0; $createdCcw = 0; $updatedCw = 0; $updatedCcw = 0; $skipped = 0; for ($gridNumber = self::GRID_NUMBER_MIN; $gridNumber <= self::GRID_NUMBER_MAX; $gridNumber++) { if (!isset($gridToPosition[$gridNumber])) { $skipped++; continue; } $startPos = $gridToPosition[$gridNumber]; $startRow = $list[$startPos]; $startId = isset($startRow['id']) ? (int) $startRow['id'] : 0; $endPosCw = ($startPos + $gridNumber) % self::BOARD_SIZE; $endPosCcw = $startPos - $gridNumber >= 0 ? $startPos - $gridNumber : self::BOARD_SIZE + $startPos - $gridNumber; $configCw = $list[$endPosCw] ?? null; $configCcw = $list[$endPosCcw] ?? null; $endIdCw = $configCw !== null && isset($configCw['id']) ? (int) $configCw['id'] : 0; $endIdCcw = $configCcw !== null && isset($configCcw['id']) ? (int) $configCcw['id'] : 0; if ($configCw !== null) { $tier = isset($configCw['tier']) ? trim((string) $configCw['tier']) : ''; if ($tier !== '') { $payloadCw = [ 'tier' => $tier, 'weight' => self::WEIGHT_MIN, 'grid_number' => $gridNumber, 'start_index' => $startId, 'end_index' => $endIdCw, 'ui_text' => $configCw['ui_text'] ?? '', 'real_ev' => $configCw['real_ev'] ?? null, 'remark' => $configCw['remark'] ?? '', 'type' => isset($configCw['type']) ? (int) $configCw['type'] : 0, ]; $existing = DiceReward::where('direction', DiceReward::DIRECTION_CLOCKWISE)->where('grid_number', $gridNumber)->find(); if ($existing) { DiceReward::where('id', $existing->id)->update($payloadCw); $updatedCw++; } else { $m = new DiceReward(); $m->tier = $tier; $m->direction = DiceReward::DIRECTION_CLOCKWISE; $m->end_index = $endIdCw; $m->weight = self::WEIGHT_MIN; $m->grid_number = $gridNumber; $m->start_index = $startId; $m->ui_text = $configCw['ui_text'] ?? ''; $m->real_ev = $configCw['real_ev'] ?? null; $m->remark = $configCw['remark'] ?? ''; $m->type = isset($configCw['type']) ? (int) $configCw['type'] : 0; $m->save(); $createdCw++; } } } if ($configCcw !== null) { $tier = isset($configCcw['tier']) ? trim((string) $configCcw['tier']) : ''; if ($tier !== '') { $payloadCcw = [ 'tier' => $tier, 'weight' => self::WEIGHT_MIN, 'grid_number' => $gridNumber, 'start_index' => $startId, 'end_index' => $endIdCcw, 'ui_text' => $configCcw['ui_text'] ?? '', 'real_ev' => $configCcw['real_ev'] ?? null, 'remark' => $configCcw['remark'] ?? '', 'type' => isset($configCcw['type']) ? (int) $configCcw['type'] : 0, ]; $existing = DiceReward::where('direction', DiceReward::DIRECTION_COUNTERCLOCKWISE)->where('grid_number', $gridNumber)->find(); if ($existing) { DiceReward::where('id', $existing->id)->update($payloadCcw); $updatedCcw++; } else { $m = new DiceReward(); $m->tier = $tier; $m->direction = DiceReward::DIRECTION_COUNTERCLOCKWISE; $m->end_index = $endIdCcw; $m->weight = self::WEIGHT_MIN; $m->grid_number = $gridNumber; $m->start_index = $startId; $m->ui_text = $configCcw['ui_text'] ?? ''; $m->real_ev = $configCcw['real_ev'] ?? null; $m->remark = $configCcw['remark'] ?? ''; $m->type = isset($configCcw['type']) ? (int) $configCcw['type'] : 0; $m->save(); $createdCcw++; } } } } DiceReward::refreshCache(); return [ 'created_clockwise' => $createdCw, 'created_counterclockwise' => $createdCcw, 'updated_clockwise' => $updatedCw, 'updated_counterclockwise' => $updatedCcw, 'skipped' => $skipped, ]; } }