import { AttrCards, AttrInfo, CanSelectAttrs } from "./AttrSet"; import { talConf, ItalConf, CanSelectTalents } from "./TalSet"; import { SkillSet, SkillConfig, CanSelectSkills } from "./SkillSet"; import { HeroInfo, heroInfo, CanSelectHeros } from "./heroSet"; import { CardType } from "./GameSet"; /** * 统一卡牌信息接口 (用于UI显示和逻辑处理) */ export interface ICardInfo { uuid: number; type: CardType; 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") } export enum CardKind { Atk = 1, Atted = 2, Buff = 3, Attr = 4, Skill = 5, Hp = 6, Dead = 7, Partner = 8, } // 默认单卡权重 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 tag = undefined; switch (type) { case CardType.Attr: baseInfo = AttrCards[uuid]; if (!baseInfo) return null; name = baseInfo.desc.split(" ")[0] || "属性"; desc = baseInfo.desc; icon = baseInfo.icon; 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; break; case CardType.Skill: baseInfo = SkillSet[uuid]; if (!baseInfo) return null; name = baseInfo.name; desc = baseInfo.info; icon = baseInfo.icon; break; case CardType.Partner: baseInfo = HeroInfo[uuid]; if (!baseInfo) return null; name = baseInfo.name; desc = baseInfo.info; icon = baseInfo.path; break; } return { uuid, type, name, desc, icon, weight: 0, // 基础信息不包含权重,权重由配置决定 tag, payload: baseInfo }; } /** * 获取默认的全量池 (当配置未指定items时使用) * @param type 卡牌类型 * @param level 当前等级(可选,用于从各模块的CanSelect配置中获取) */ function getDefaultPool(type: CardType, level: number = 1): IPoolItem[] { const items: IPoolItem[] = []; switch (type) { case CardType.Attr: // 优先使用 CanSelectAttrs 中的配置 if (CanSelectAttrs[level]) { CanSelectAttrs[level].forEach(id => items.push({ id, weight: 100 })); } else if (CanSelectAttrs[99]) { // 默认池 CanSelectAttrs[99].forEach(id => items.push({ id, weight: 100 })); } else { // 全量兜底 Object.keys(AttrCards).forEach(key => items.push({ id: Number(key), weight: 100 })); } break; case CardType.Talent: // 优先使用 CanSelectTalents 中的配置 if (CanSelectTalents[level]) { CanSelectTalents[level].forEach(id => items.push({ id, weight: 50 })); } else if (CanSelectTalents[99]) { // 默认池 CanSelectTalents[99].forEach(id => items.push({ id, weight: 50 })); } else { // 全量兜底 Object.keys(talConf).forEach(key => items.push({ id: Number(key), weight: 50 })); } break; case CardType.Skill: // 优先使用 CanSelectSkills 中的配置 if (CanSelectSkills[level]) { CanSelectSkills[level].forEach(id => items.push({ id, weight: 80 })); } else if (CanSelectSkills[99]) { // 默认池 CanSelectSkills[99].forEach(id => items.push({ id, weight: 80 })); } else { // 全量兜底 Object.keys(SkillSet).forEach(key => items.push({ id: Number(key), weight: 80 })); } break; case CardType.Partner: // 优先使用 CanSelectHeros 中的配置 if (CanSelectHeros[level]) { CanSelectHeros[level].forEach(id => items.push({ id, weight: 100 })); } else if (CanSelectHeros[99]) { // 默认池 CanSelectHeros[99].forEach(id => items.push({ id, weight: 100 })); } else { // 全量兜底 (排除怪物) Object.keys(HeroInfo).forEach(key => { const id = Number(key); if (id < 5200) items.push({ id, weight: 100 }); }); } break; } return items; } /** * 根据等级获取随机卡牌选项 * @param level 当前等级 * @param count 选项数量 (默认3个) * @param excludeUuids 排除的卡牌UUID列表 (用于去重或排除已拥有) * @param forcedType 强制指定卡牌类型 (用于特殊获取,如商店、技能书等) */ export function getCardOptions(level: number, count: number = 3, excludeUuids: number[] = [], forcedType?: CardType): ICardInfo[] { // 1. 获取该等级的池配置 // 如果强制指定类型,则构造一个只包含该类型的配置 const initialPoolConfigs = forcedType ? [{ type: forcedType, poolWeight: 100 }] : (LevelPoolConfigs[level] || [{ type: CardType.Attr, poolWeight: 100 }]); const result: ICardInfo[] = []; const excludeSet = new Set(excludeUuids); // 循环获取 count 张卡牌 for (let i = 0; i < count; i++) { // 在每一轮获取中,我们使用一个临时的配置列表 // 这样如果某个类型池空了,我们可以从临时列表中移除它,避免重复选中 let currentConfigs = [...initialPoolConfigs]; let cardFound = false; while (currentConfigs.length > 0) { // 2.1 随机决定本次抽取的类型池 const selectedConfig = weightedRandomPool(currentConfigs); if (!selectedConfig) break; // 理论上不会发生 // 2.2 获取该类型的所有候选卡牌 // 直接使用默认全池 (传入level以获取该等级特定的默认配置) const rawCandidates = getDefaultPool(selectedConfig.type, level); // 2.3 过滤与构建完整信息 const validCandidates: ICardInfo[] = []; for (const item of rawCandidates) { // 排除全局排除项 if (excludeSet.has(item.id)) continue; // 排除本轮已选项 if (result.find(r => r.uuid === item.id)) continue; // 获取详情 const info = getCardBaseInfo(selectedConfig.type, item.id); if (!info) continue; // Tag 过滤 if (selectedConfig.tag && info.tag !== selectedConfig.tag) { continue; } // 赋予配置的权重 info.weight = item.weight; validCandidates.push(info); } // 2.4 检查该类型是否有可用卡牌 if (validCandidates.length > 0) { // 有卡!随机抽取一张 const card = weightedRandomCard(validCandidates); if (card) { result.push(card); cardFound = true; break; // 成功获取一张,跳出内层循环,进行下一张的获取 } } else { // 没卡!从当前配置中移除这个类型,重试 const index = currentConfigs.indexOf(selectedConfig); if (index > -1) { currentConfigs.splice(index, 1); } // 继续 while 循环,重新随机类型 } } // 2.5 如果尝试了所有类型都没找到卡 (极少见兜底) if (!cardFound) { // 尝试从属性池硬拿一个不重复的 const attrItems = getDefaultPool(CardType.Attr); for (const item of attrItems) { if (excludeSet.has(item.id) || result.find(r => r.uuid === item.id)) continue; const info = getCardBaseInfo(CardType.Attr, item.id); if (info) { info.weight = 100; result.push(info); cardFound = true; break; } } } // 如果连兜底都找不到(比如所有属性都拿完了),那也没办法了,可能返回少于 count 张 if (!cardFound) { console.warn(`[CardSet] 无法为等级 ${level} 找到足够的卡牌选项,当前已选: ${result.length}/${count}`); break; } } // 3. 最终结果洗牌 (虽然逻辑上已经是随机的,但洗牌可以打乱类型顺序) 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]]; } }