import { AttrCards, AttrInfo, CanSelectAttrs, PotionCards, CanSelectPotions } from "./AttrSet"; import { talConf, ItalConf, CanSelectTalents } from "./TalSet"; import { SkillSet, SkillConfig, CanSelectSkills } from "./SkillSet"; import { HeroInfo, heroInfo, CanSelectHeros } from "./heroSet"; import { CardType, CardKind } from "./GameSet"; /** * 获取等级对应的奖励类型 * @param level 当前等级 * @returns 奖励类型 CardType */ export function getLevelRewardType(level: number): CardType { switch (level) { case 2: return CardType.Talent; case 3: return CardType.Attr; case 4: return CardType.Attr; case 5: return CardType.Talent; case 6: return CardType.Attr; case 7: return CardType.Attr; case 8: return CardType.Attr; case 9: return CardType.Attr; case 10: return CardType.Talent; case 11: return CardType.Attr; case 12: return CardType.Attr; case 13: return CardType.Attr; case 14: return CardType.Attr; case 15: return CardType.Talent; case 16: return CardType.Attr; case 17: return CardType.Attr; case 18: return CardType.Attr; case 19: return CardType.Attr; case 20: return CardType.Attr; default: return null } } /** * 统一卡牌信息接口 (用于UI显示和逻辑处理) */ export interface ICardInfo { uuid: number; type: CardType; kind: CardKind; name: string; desc: string; icon: string; weight: number; // 抽取权重 tag?: string; // 标签 (如 "special" 表示特殊属性) payload: any; // 原始配置数据 } /** * 具体卡牌配置项 (内部使用) */ export interface IPoolItem { id: number; // 卡牌UUID weight: number; // 该卡牌在池中的权重 } /** * 等级池配置项 * 仅定义类型和权重,具体卡牌内容由各模块的 CanSelectXXX 配置决定 */ export interface IPoolConfig { type: CardType; // 卡牌类型 poolWeight: number; // 该类型池被选中的概率权重 tag?: string; // 辅助筛选(从全池中筛选带tag的,如 "special") } // 默认单卡权重 const DEFAULT_CARD_WEIGHT = 100; /** * 1-20 级卡牌池配置表 * 定义每个等级可能出现的卡牌类型及其权重 */ export const LevelPoolConfigs: Record = { 1: [{ type: CardType.Skill, poolWeight: 100 }], 2: [{ type: CardType.Attr, poolWeight: 100 }], // 常规属性 3: [{ type: CardType.Talent, poolWeight: 50 }, { type: CardType.Attr, poolWeight: 50, tag: "special" }], // 天赋或特殊属性 4: [{ type: CardType.Attr, poolWeight: 100 }], 5: [{ type: CardType.Talent, poolWeight: 100 }], 6: [{ type: CardType.Partner, poolWeight: 100 }], // 伙伴节点 7: [{ type: CardType.Attr, poolWeight: 80 }, { type: CardType.Skill, poolWeight: 20 }], 8: [{ type: CardType.Attr, poolWeight: 80 }, { type: CardType.Skill, poolWeight: 20 }], 9: [{ type: CardType.Attr, poolWeight: 50, tag: "special" }, { type: CardType.Talent, poolWeight: 50 }], 10: [{ type: CardType.Talent, poolWeight: 100 }], 11: [{ type: CardType.Attr, poolWeight: 70 }, { type: CardType.Skill, poolWeight: 30 }], 12: [{ type: CardType.Attr, poolWeight: 70 }, { type: CardType.Skill, poolWeight: 30 }], 13: [{ type: CardType.Attr, poolWeight: 100 }], 14: [{ type: CardType.Attr, poolWeight: 50, tag: "special" }, { type: CardType.Talent, poolWeight: 50 }], 15: [{ type: CardType.Talent, poolWeight: 100 }], 16: [{ type: CardType.Attr, poolWeight: 60 }, { type: CardType.Skill, poolWeight: 40 }], 17: [{ type: CardType.Attr, poolWeight: 60 }, { type: CardType.Skill, poolWeight: 40 }], 18: [{ type: CardType.Attr, poolWeight: 50, tag: "special" }, { type: CardType.Talent, poolWeight: 50 }], 19: [{ type: CardType.Attr, poolWeight: 100 }], 20: [{ type: CardType.Talent, poolWeight: 100 }], }; // ========== 卡牌池构建逻辑 ========== /** * 获取指定类型的卡牌信息(不含权重,仅基础信息) */ function getCardBaseInfo(type: CardType, uuid: number): ICardInfo | null { let baseInfo: any = null; let name = ""; let desc = ""; let icon = ""; let kind = CardKind.Attr; let tag = undefined; switch (type) { case CardType.Attr: baseInfo = AttrCards[uuid]; if (!baseInfo) return null; name = baseInfo.name || "属性"; desc = baseInfo.desc; icon = baseInfo.icon; kind = CardKind.Attr; tag = baseInfo.isSpecial ? "special" : undefined; break; case CardType.Talent: baseInfo = talConf[uuid]; if (!baseInfo) return null; name = baseInfo.name; desc = baseInfo.desc; icon = baseInfo.icon; kind = baseInfo.kind; break; case CardType.Skill: baseInfo = SkillSet[uuid]; if (!baseInfo) return null; name = baseInfo.name; desc = baseInfo.info; icon = baseInfo.icon; kind = CardKind.Skill; break; case CardType.Partner: baseInfo = HeroInfo[uuid]; if (!baseInfo) return null; name = baseInfo.name; desc = baseInfo.info; icon = baseInfo.icon; kind = CardKind.Partner; break; case CardType.Potion: baseInfo = PotionCards[uuid]; if (!baseInfo) return null; name = baseInfo.name || "药水"; desc = baseInfo.desc; icon = baseInfo.icon; kind = CardKind.Buff; // 药水归类为 Buff 类型的卡片显示 break; } return { uuid, type, kind, name, desc, icon, weight: 0, // 基础信息不包含权重,权重由配置决定 tag, payload: baseInfo }; } /** * 获取指定类型的全量池 * 自动累加所有 <= level 的配置项 * @param type 卡牌类型 * @param level 当前等级 */ function getDefaultPool(type: CardType, level: number = 1): IPoolItem[] { const items: IPoolItem[] = []; let configMap: Record | null = null; let defaultWeight = 100; switch (type) { case CardType.Attr: configMap = CanSelectAttrs; defaultWeight = 100; break; case CardType.Talent: configMap = CanSelectTalents; defaultWeight = 50; break; case CardType.Skill: configMap = CanSelectSkills; defaultWeight = 80; break; case CardType.Partner: configMap = CanSelectHeros; defaultWeight = 100; break; case CardType.Potion: configMap = CanSelectPotions; defaultWeight = 100; break; } if (configMap) { // 1. 找到符合条件的最大等级 Key (<= level 且 != 99) let targetKey = -1; let maxLv = -1; Object.keys(configMap).forEach(lvlStr => { const lv = parseInt(lvlStr); if (lv !== 99 && lv <= level) { if (lv > maxLv) { maxLv = lv; targetKey = lv; } } }); // 2. 如果找到了目标等级配置,则使用该配置 if (targetKey !== -1 && configMap[targetKey]) { const ids = configMap[targetKey]; ids.forEach(id => { items.push({ id, weight: defaultWeight }); }); } // 3. 如果没找到(等级过低),且存在 99 号默认配置,则使用 99 号配置 else if (configMap[99]) { configMap[99].forEach(id => { items.push({ id, weight: defaultWeight }); }); } } else { // 兜底逻辑:如果该类型没有配置表 (理论上不应发生,除非新增类型未配置) // 这里保留原有的全量兜底逻辑作为最后的防线 switch (type) { case CardType.Attr: Object.keys(AttrCards).forEach(key => items.push({ id: Number(key), weight: 100 })); break; case CardType.Talent: Object.keys(talConf).forEach(key => items.push({ id: Number(key), weight: 50 })); break; case CardType.Skill: Object.keys(SkillSet).forEach(key => items.push({ id: Number(key), weight: 80 })); break; case CardType.Partner: Object.keys(HeroInfo).forEach(key => { const id = Number(key); if (id < 5200) items.push({ id, weight: 100 }); }); break; case CardType.Potion: Object.keys(PotionCards).forEach(key => items.push({ id: Number(key), weight: 100 })); break; } } return items; } /** * 根据等级获取随机卡牌选项 * @param level 当前等级 * @param count 选项数量 (默认3个) * @param excludeUuids 排除的卡牌UUID列表 (用于去重或排除已拥有) * @param forcedType 强制指定卡牌类型 (用于特殊获取,如商店、技能书等) * @param preferredAttrs 偏好的属性类型列表 (用于增加权重) */ export function getCardOptions(level: number, count: number = 3, excludeUuids: number[] = [], forcedType?: CardType, preferredAttrs: number[] = []): ICardInfo[] { // 1. 确定类型:强制指定 > 默认为属性 const type = forcedType !== undefined ? forcedType : CardType.Attr; const result: ICardInfo[] = []; const excludeSet = new Set(excludeUuids); // 2. 获取该类型的候选池 const rawItems = getDefaultPool(type, level); // 3. 构建候选列表(包含完整信息) const candidates: ICardInfo[] = []; for (const item of rawItems) { if (excludeSet.has(item.id)) continue; const info = getCardBaseInfo(type, item.id); if (info) { info.weight = item.weight; // 如果是属性卡,且该属性在偏好列表中,增加权重 if (type === CardType.Attr && preferredAttrs.length > 0) { // AttrCards 的 payload 就是 AttrInfo,包含 attr 字段 if (info.payload && preferredAttrs.includes(info.payload.attr)) { // 增加权重,这里设置为 2 倍 info.weight *= 2; } } candidates.push(info); } } // 4. 抽取 for (let i = 0; i < count; i++) { if (candidates.length === 0) break; const card = weightedRandomCard(candidates); if (card) { result.push(card); // 移除已选,防止重复 const idx = candidates.indexOf(card); if (idx > -1) candidates.splice(idx, 1); } } // 5. 兜底逻辑:如果数量不足,且当前类型不是属性,则用属性卡补齐 if (result.length < count && type !== CardType.Attr) { const attrItems = getDefaultPool(CardType.Attr); // 兜底可以用全量属性池 for (const item of attrItems) { if (result.length >= count) break; // 检查是否已存在(ID去重) if (excludeSet.has(item.id) || result.some(r => r.uuid === item.id)) continue; const info = getCardBaseInfo(CardType.Attr, item.id); if (info) { info.weight = 100; result.push(info); } } } shuffleArray(result); return result; } // ========== 工具函数 ========== function weightedRandomPool(configs: IPoolConfig[]): IPoolConfig | null { if (!configs || configs.length === 0) return null; const totalWeight = configs.reduce((sum, item) => sum + item.poolWeight, 0); let randomVal = Math.random() * totalWeight; for (const config of configs) { randomVal -= config.poolWeight; if (randomVal <= 0) { return config; } } return configs[configs.length - 1]; } function weightedRandomCard(cards: ICardInfo[]): ICardInfo | null { if (!cards || cards.length === 0) return null; const totalWeight = cards.reduce((sum, item) => sum + item.weight, 0); let randomVal = Math.random() * totalWeight; for (const card of cards) { randomVal -= card.weight; if (randomVal <= 0) { return card; } } return cards[cards.length - 1]; } function shuffleArray(array: any[]) { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } }