@@ -1,7 +1,7 @@
import { AttrCards , AttrInfo } from "./AttrSet" ;
import { talConf , ItalConf } from "./TalSet" ;
import { SkillSet , CanSelectSkills , SkillConfig } from "./SkillSet" ;
import { HeroInfo , CanSelectHeros, heroInfo } from "./heroSet" ;
import { AttrCards , AttrInfo , CanSelectAttrs } from "./AttrSet" ;
import { talConf , ItalConf , CanSelectTalents } from "./TalSet" ;
import { SkillSet , SkillConfig , CanSelectSkills } from "./SkillSet" ;
import { HeroInfo , heroInfo , CanSelectHeros } from "./heroSet" ;
/**
* 卡牌类型枚举
@@ -28,125 +28,170 @@ export interface ICardInfo {
}
/**
* 等级池配置项
* 具体卡牌配置项 (内部使用)
*/
export interface IPoolWeight {
type : CardType ;
weight : number ;
tag? : string ; // 如果指定, 只从该类型中包含此tag的卡牌中抽取
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 < number , IPoolWeight [ ] > = {
1 : [ { type : CardType . Skill , w eight : 100 } ] ,
2 : [ { type : CardType . Attr , w eight : 100 } ] , // 常规属性
3 : [ { type : CardType . Talent , w eight : 50 } , { type : CardType . Attr , w eight : 50 , tag : "special" } ] , // 天赋或特殊属性
4 : [ { type : CardType . Attr , w eight : 100 } ] ,
5 : [ { type : CardType . Talent , w eight : 100 } ] ,
6 : [ { type : CardType . Hero , w eight : 100 } ] , // 伙伴节点
7 : [ { type : CardType . Attr , w eight : 80 } , { type : CardType . Skill , w eight : 20 } ] ,
8 : [ { type : CardType . Attr , w eight : 80 } , { type : CardType . Skill , w eight : 20 } ] ,
9 : [ { type : CardType . Attr , w eight : 50 , tag : "special" } , { type : CardType . Talent , w eight : 50 } ] ,
10 : [ { type : CardType . Talent , w eight : 100 } ] ,
11 : [ { type : CardType . Attr , w eight : 70 } , { type : CardType . Skill , w eight : 30 } ] ,
12 : [ { type : CardType . Attr , w eight : 70 } , { type : CardType . Skill , w eight : 30 } ] ,
13 : [ { type : CardType . Attr , w eight : 100 } ] ,
14 : [ { type : CardType . Attr , w eight : 50 , tag : "special" } , { type : CardType . Talent , w eight : 50 } ] ,
15 : [ { type : CardType . Talent , w eight : 100 } ] ,
16 : [ { type : CardType . Attr , w eight : 60 } , { type : CardType . Skill , w eight : 40 } ] ,
17 : [ { type : CardType . Attr , w eight : 60 } , { type : CardType . Skill , w eight : 40 } ] ,
18 : [ { type : CardType . Attr , w eight : 50 , tag : "special" } , { type : CardType . Talent , w eight : 50 } ] ,
19 : [ { type : CardType . Attr , w eight : 100 } ] ,
20 : [ { type : CardType . Talent , w eight : 100 } ] ,
export const LevelPoolConfigs : Record < number , IPoolConfig [ ] > = {
1 : [ { type : CardType . Skill , poolW eight : 100 } ] ,
2 : [ { type : CardType . Attr , poolW eight : 100 } ] , // 常规属性
3 : [ { type : CardType . Talent , poolW eight : 50 } , { type : CardType . Attr , poolW eight : 50 , tag : "special" } ] , // 天赋或特殊属性
4 : [ { type : CardType . Attr , poolW eight : 100 } ] ,
5 : [ { type : CardType . Talent , poolW eight : 100 } ] ,
6 : [ { type : CardType . Hero , poolW eight : 100 } ] , // 伙伴节点
7 : [ { type : CardType . Attr , poolW eight : 80 } , { type : CardType . Skill , poolW eight : 20 } ] ,
8 : [ { type : CardType . Attr , poolW eight : 80 } , { type : CardType . Skill , poolW eight : 20 } ] ,
9 : [ { type : CardType . Attr , poolW eight : 50 , tag : "special" } , { type : CardType . Talent , poolW eight : 50 } ] ,
10 : [ { type : CardType . Talent , poolW eight : 100 } ] ,
11 : [ { type : CardType . Attr , poolW eight : 70 } , { type : CardType . Skill , poolW eight : 30 } ] ,
12 : [ { type : CardType . Attr , poolW eight : 70 } , { type : CardType . Skill , poolW eight : 30 } ] ,
13 : [ { type : CardType . Attr , poolW eight : 100 } ] ,
14 : [ { type : CardType . Attr , poolW eight : 50 , tag : "special" } , { type : CardType . Talent , poolW eight : 50 } ] ,
15 : [ { type : CardType . Talent , poolW eight : 100 } ] ,
16 : [ { type : CardType . Attr , poolW eight : 60 } , { type : CardType . Skill , poolW eight : 40 } ] ,
17 : [ { type : CardType . Attr , poolW eight : 60 } , { type : CardType . Skill , poolW eight : 40 } ] ,
18 : [ { type : CardType . Attr , poolW eight : 50 , tag : "special" } , { type : CardType . Talent , poolW eight : 50 } ] ,
19 : [ { type : CardType . Attr , poolW eight : 100 } ] ,
20 : [ { type : CardType . Talent , poolW eight : 100 } ] ,
} ;
// ========== 卡牌池缓存 ==========
let _cachedPools : Map < CardType , ICardInfo [ ] > = new Map ( ) ;
// ========== 卡牌池构建逻辑 ==========
/**
* 初始化并 获取指定类型的完整卡牌池
* 获取指定类型的卡牌信息(不含权重,仅基础信息)
*/
function getFullPool ( type : CardType ) : ICardInfo [ ] {
if ( _cachedPools . has ( type ) ) {
return _cachedPools . get ( type ) ! ;
}
const pool : ICardInfo [ ] = [ ] ;
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 :
// 转换属性配置
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
} ) ;
} ) ;
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 :
// 转换天赋配置
Object . values ( talCo nf) . forEach ( cfg = > {
pool . push ( {
uuid : cfg.uuid ,
type : CardType . Talent ,
name : cfg.name ,
desc : cfg.desc ,
icon : cfg.icon ,
weight : 50 , // 天赋默认权重 50
payload : cfg
} ) ;
} ) ;
baseInfo = talConf [ uuid ] ;
if ( ! baseI nfo ) return null ;
name = baseInfo . name ;
desc = baseInfo . desc ;
icon = baseInfo . icon ;
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
} ) ;
}
} ) ;
baseInfo = SkillSet [ uuid ] ;
if ( ! baseInfo ) return null ;
name = baseInfo . name ;
desc = baseInfo . info ;
icon = baseInfo . icon ;
break ;
case CardType.Hero :
// 转换英雄配置 (仅包含 CanSelectHeros)
CanSelectHeros . forEach ( uuid = > {
const cfg = Hero Info[ uuid ] ;
if ( cfg ) {
pool . pus h ( {
uuid : cfg.uuid ,
type : CardType . Hero ,
name : cfg.name ,
desc : cfg.info ,
icon : cfg.path , // 使用 path 作为图标引用
weight : 1000 , // 英雄权重极高(如果池子里有的话)
payload : cfg
} ) ;
}
} ) ;
baseInfo = HeroInfo [ uuid ] ;
if ( ! baseInfo ) return null ;
name = base Info. name ;
desc = baseInfo . info ;
icon = baseInfo . pat h ;
break ;
}
_cachedPools . set ( type , pool ) ;
return pool ;
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 . Hero :
// 优先使用 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 ;
}
/**
@@ -156,47 +201,94 @@ function getFullPool(type: CardType): ICardInfo[] {
* @param excludeUuids 排除的卡牌UUID列表 (用于去重或排除已拥有)
*/
export function getCardOptions ( level : number , count : number = 3 , excludeUuids : number [ ] = [ ] ) : ICardInfo [ ] {
// 1. 获取该等级的池配置,如果没有配置,默认给属性
const poolConfigs = LevelPoolConfigs [ level ] || [ { type : CardType . Attr , weight : 100 } ] ;
// 1. 获取该等级的池配置
// 必须复制一份,因为我们可能需要修改它(比如移除空的池子)
const initialPoolConfigs = LevelPoolConfigs [ level ] || [ { type : CardType . Attr , poolWeight : 100 } ] ;
const result : ICardInfo [ ] = [ ] ;
const excludeSet = new Set ( excludeUuids ) ;
// 循环抽 取 count 次
// 循环获 取 count 张卡牌
for ( let i = 0 ; i < count ; i ++ ) {
// 2.1 随机决定本次抽取的类型池
const selectedPoolConfig = weightedRandomPool ( poolConfigs ) ;
if ( ! selected PoolConfig) continue ;
// 在每一轮获取中,我们使用一个临时的配置列表
// 这样如果某个类型池空了,我们可以从临时列表中移除它,避免重复选中
let currentConfigs = [ . . . initial PoolConfigs ] ;
let cardFound = false ;
// 2.2 获取该类型的所有卡牌
let candidates = getFullPool ( selectedPoolConfig . type ) ;
while ( currentConfigs . length > 0 ) {
// 2.1 随机决定本次抽取的类型池
const selectedConfig = weightedRandomPool ( currentConfigs ) ;
if ( ! selectedConfig ) break ; // 理论上不会发生
// 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.2 获取该类型的所有候选卡牌
// 直接使用默认全池 (传入level以获取该等级特定的默认配置)
const rawCandidates = getDefaultPool ( selectedConfig . type , level ) ;
// 2.4 如果该池子空了 (比如技能都学完了),尝试从属性池兜底
if ( candidates . length === 0 ) {
candidates = getFullPool ( CardType . Attr ) . filter ( c = > ! result . find ( r = > r . uuid === c . uuid ) ) ;
// 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 循环,重新随机类型
}
}
if ( candidates . length > 0 ) {
// 2.5 按卡牌权重随机抽取一张
const card = weightedRandomCard ( candidates ) ;
if ( c ard) {
result . push ( card ) ;
// 2.5 如果尝试了所有类型都没找到卡 (极少见兜底)
if ( ! cardFound ) {
// 尝试从属性池硬拿一个不重复的
const attrItems = getDefaultPool ( C ardType . 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. 最终结果洗牌 (避免顺序固定 )
// 3. 最终结果洗牌 (虽然逻辑上已经是随机的,但洗牌可以打乱类型顺序 )
shuffleArray ( result ) ;
return result ;
@@ -204,17 +296,14 @@ export function getCardOptions(level: number, count: number = 3, excludeUuids: n
// ========== 工具函数 ==========
/**
* 权重随机选择池配置
*/
function weightedRandomPool ( configs : IPoolWeight [ ] ) : IPoolWeight | null {
function weightedRandomPool ( configs : IPoolConfig [ ] ) : IPoolConfig | null {
if ( ! configs || configs . length === 0 ) return null ;
const totalWeight = configs . reduce ( ( sum , item ) = > sum + item . w eight, 0 ) ;
const totalWeight = configs . reduce ( ( sum , item ) = > sum + item . poolW eight, 0 ) ;
let randomVal = Math . random ( ) * totalWeight ;
for ( const config of configs ) {
randomVal -= config . w eight;
randomVal -= config . poolW eight;
if ( randomVal <= 0 ) {
return config ;
}
@@ -222,9 +311,6 @@ function weightedRandomPool(configs: IPoolWeight[]): IPoolWeight | null {
return configs [ configs . length - 1 ] ;
}
/**
* 权重随机选择卡牌
*/
function weightedRandomCard ( cards : ICardInfo [ ] ) : ICardInfo | null {
if ( ! cards || cards . length === 0 ) return null ;
@@ -240,9 +326,6 @@ function weightedRandomCard(cards: ICardInfo[]): ICardInfo | null {
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 ) ) ;