From 4a506555ba8260d3ad56e0813bd0f248cd7c883d Mon Sep 17 00:00:00 2001 From: panw Date: Wed, 14 Jan 2026 17:29:02 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=8D=A1=E7=89=8C=E7=B3=BB=E7=BB=9F):=20?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E5=8D=A1=E7=89=8C=E6=B1=A0=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=8F=8A=E9=9A=8F=E6=9C=BA=E9=80=89=E6=8B=A9=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加卡牌类型枚举和统一卡牌信息接口 实现等级池配置表及卡牌池缓存机制 提供根据等级获取随机卡牌选项的功能 包含权重随机选择算法和数组洗牌工具函数 --- assets/script/game/common/config/CardSet.ts | 251 ++++++++++++++++++++ 1 file changed, 251 insertions(+) diff --git a/assets/script/game/common/config/CardSet.ts b/assets/script/game/common/config/CardSet.ts index e69de29b..1f464762 100644 --- a/assets/script/game/common/config/CardSet.ts +++ b/assets/script/game/common/config/CardSet.ts @@ -0,0 +1,251 @@ +import { AttrCards, AttrInfo } from "./AttrSet"; +import { talConf, ItalConf } from "./TalSet"; +import { SkillSet, CanSelectSkills, SkillConfig } from "./SkillSet"; +import { HeroInfo, CanSelectHeros, heroInfo } from "./heroSet"; + +/** + * 卡牌类型枚举 + */ +export enum CardType { + Skill = 1, // 技能 + Talent = 2, // 天赋 + Attr = 3, // 属性 + Hero = 4 // 英雄(伙伴) +} + +/** + * 统一卡牌信息接口 (用于UI显示和逻辑处理) + */ +export interface ICardInfo { + uuid: number; + type: CardType; + name: string; + desc: string; + icon: string; + weight: number; // 抽取权重 + tag?: string; // 标签 (如 "special" 表示特殊属性) + payload: any; // 原始配置数据 +} + +/** + * 等级池配置项 + */ +export interface IPoolWeight { + type: CardType; + weight: number; + tag?: string; // 如果指定,只从该类型中包含此tag的卡牌中抽取 +} + +/** + * 1-20 级卡牌池配置表 + * 定义每个等级可能出现的卡牌类型及其权重 + */ +export const LevelPoolConfigs: Record = { + 1: [{ type: CardType.Skill, weight: 100 }], + 2: [{ type: CardType.Attr, weight: 100 }], // 常规属性 + 3: [{ type: CardType.Talent, weight: 50 }, { type: CardType.Attr, weight: 50, tag: "special" }], // 天赋或特殊属性 + 4: [{ type: CardType.Attr, weight: 100 }], + 5: [{ type: CardType.Talent, weight: 100 }], + 6: [{ type: CardType.Hero, weight: 100 }], // 伙伴节点 + 7: [{ type: CardType.Attr, weight: 80 }, { type: CardType.Skill, weight: 20 }], + 8: [{ type: CardType.Attr, weight: 80 }, { type: CardType.Skill, weight: 20 }], + 9: [{ type: CardType.Attr, weight: 50, tag: "special" }, { type: CardType.Talent, weight: 50 }], + 10: [{ type: CardType.Talent, weight: 100 }], + 11: [{ type: CardType.Attr, weight: 70 }, { type: CardType.Skill, weight: 30 }], + 12: [{ type: CardType.Attr, weight: 70 }, { type: CardType.Skill, weight: 30 }], + 13: [{ type: CardType.Attr, weight: 100 }], + 14: [{ type: CardType.Attr, weight: 50, tag: "special" }, { type: CardType.Talent, weight: 50 }], + 15: [{ type: CardType.Talent, weight: 100 }], + 16: [{ type: CardType.Attr, weight: 60 }, { type: CardType.Skill, weight: 40 }], + 17: [{ type: CardType.Attr, weight: 60 }, { type: CardType.Skill, weight: 40 }], + 18: [{ type: CardType.Attr, weight: 50, tag: "special" }, { type: CardType.Talent, weight: 50 }], + 19: [{ type: CardType.Attr, weight: 100 }], + 20: [{ type: CardType.Talent, weight: 100 }], +}; + +// ========== 卡牌池缓存 ========== +let _cachedPools: Map = new Map(); + +/** + * 初始化并获取指定类型的完整卡牌池 + */ +function getFullPool(type: CardType): ICardInfo[] { + if (_cachedPools.has(type)) { + return _cachedPools.get(type)!; + } + + const pool: ICardInfo[] = []; + + switch (type) { + case CardType.Attr: + // 转换属性配置 + Object.values(AttrCards).forEach(cfg => { + pool.push({ + uuid: cfg.uuid, + type: CardType.Attr, + name: cfg.desc.split(" ")[0] || "属性强化", // 简单处理名称 + desc: cfg.desc, + icon: cfg.icon, + weight: 100, // 属性默认权重 100 + tag: cfg.isSpecial ? "special" : undefined, + payload: cfg + }); + }); + break; + + case CardType.Talent: + // 转换天赋配置 + Object.values(talConf).forEach(cfg => { + pool.push({ + uuid: cfg.uuid, + type: CardType.Talent, + name: cfg.name, + desc: cfg.desc, + icon: cfg.icon, + weight: 50, // 天赋默认权重 50 + payload: cfg + }); + }); + break; + + case CardType.Skill: + // 转换技能配置 (仅包含 CanSelectSkills) + CanSelectSkills.forEach(uuid => { + const cfg = SkillSet[uuid]; + if (cfg) { + pool.push({ + uuid: cfg.uuid, + type: CardType.Skill, + name: cfg.name, + desc: cfg.info, + icon: cfg.icon, + weight: 80, // 技能默认权重 80 + payload: cfg + }); + } + }); + break; + + case CardType.Hero: + // 转换英雄配置 (仅包含 CanSelectHeros) + CanSelectHeros.forEach(uuid => { + const cfg = HeroInfo[uuid]; + if (cfg) { + pool.push({ + uuid: cfg.uuid, + type: CardType.Hero, + name: cfg.name, + desc: cfg.info, + icon: cfg.path, // 使用 path 作为图标引用 + weight: 1000, // 英雄权重极高(如果池子里有的话) + payload: cfg + }); + } + }); + break; + } + + _cachedPools.set(type, pool); + return pool; +} + +/** + * 根据等级获取随机卡牌选项 + * @param level 当前等级 + * @param count 选项数量 (默认3个) + * @param excludeUuids 排除的卡牌UUID列表 (用于去重或排除已拥有) + */ +export function getCardOptions(level: number, count: number = 3, excludeUuids: number[] = []): ICardInfo[] { + // 1. 获取该等级的池配置,如果没有配置,默认给属性 + const poolConfigs = LevelPoolConfigs[level] || [{ type: CardType.Attr, weight: 100 }]; + + const result: ICardInfo[] = []; + const excludeSet = new Set(excludeUuids); + + // 循环抽取 count 次 + for (let i = 0; i < count; i++) { + // 2.1 随机决定本次抽取的类型池 + const selectedPoolConfig = weightedRandomPool(poolConfigs); + if (!selectedPoolConfig) continue; + + // 2.2 获取该类型的所有卡牌 + let candidates = getFullPool(selectedPoolConfig.type); + + // 2.3 过滤 (Tag过滤 + 排除列表 + 已选中过滤) + candidates = candidates.filter(card => { + // Tag 匹配 + if (selectedPoolConfig.tag && card.tag !== selectedPoolConfig.tag) return false; + // 排除列表 + if (excludeSet.has(card.uuid)) return false; + // 当前轮次已选中去重 + if (result.find(r => r.uuid === card.uuid)) return false; + return true; + }); + + // 2.4 如果该池子空了 (比如技能都学完了),尝试从属性池兜底 + if (candidates.length === 0) { + candidates = getFullPool(CardType.Attr).filter(c => !result.find(r => r.uuid === c.uuid)); + } + + if (candidates.length > 0) { + // 2.5 按卡牌权重随机抽取一张 + const card = weightedRandomCard(candidates); + if (card) { + result.push(card); + } + } + } + + // 3. 最终结果洗牌 (避免顺序固定) + shuffleArray(result); + + return result; +} + +// ========== 工具函数 ========== + +/** + * 权重随机选择池配置 + */ +function weightedRandomPool(configs: IPoolWeight[]): IPoolWeight | null { + if (!configs || configs.length === 0) return null; + + const totalWeight = configs.reduce((sum, item) => sum + item.weight, 0); + let randomVal = Math.random() * totalWeight; + + for (const config of configs) { + randomVal -= config.weight; + 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]; +} + +/** + * 数组洗牌 (Fisher-Yates) + */ +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]]; + } +}