feat(卡牌系统): 重构卡牌选择逻辑并增加等级分段配置
重构卡牌选择系统,将原有的简单数组配置改为按等级分段的字典结构 - 为技能、英雄、天赋和属性分别添加 CanSelectXXX 配置 - 优化卡牌池构建逻辑,支持按等级筛选可用卡牌 - 改进权重随机算法,增加兜底机制 - 分离卡牌基础信息和权重配置,提高可维护性
This commit is contained in:
@@ -25,4 +25,15 @@ import { Attrs } from "./HeroAttrs";
|
|||||||
2012:{uuid:2012, icon:"2001", attr: Attrs.SLOW_CHANCE, value: 10, showValue: 10, desc: "减速概率 +10%", isSpecial: true, note: "上限50%" },
|
2012:{uuid:2012, icon:"2001", attr: Attrs.SLOW_CHANCE, value: 10, showValue: 10, desc: "减速概率 +10%", isSpecial: true, note: "上限50%" },
|
||||||
2013:{uuid:2013, icon:"2001", attr: Attrs.LIFESTEAL, value: 10, showValue: 10, desc: "吸血比例 +10%", isSpecial: true, note: "上限50%" },
|
2013:{uuid:2013, icon:"2001", attr: Attrs.LIFESTEAL, value: 10, showValue: 10, desc: "吸血比例 +10%", isSpecial: true, note: "上限50%" },
|
||||||
2014:{uuid:2014, icon:"2001", attr: Attrs.MANASTEAL, value: 10, showValue: 10, desc: "吸蓝比例 +10%", isSpecial: true, note: "上限50%" },
|
2014:{uuid:2014, icon:"2001", attr: Attrs.MANASTEAL, value: 10, showValue: 10, desc: "吸蓝比例 +10%", isSpecial: true, note: "上限50%" },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const CanSelectAttrs: Record<number, number[]> = {
|
||||||
|
// 基础属性
|
||||||
|
2: [2001, 2002, 2003, 2004],
|
||||||
|
// 混合
|
||||||
|
4: [2001, 2002, 2003, 2004],
|
||||||
|
// 进阶属性
|
||||||
|
7: [2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2013],
|
||||||
|
// 默认全开
|
||||||
|
99: [2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014]
|
||||||
|
};
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { AttrCards, AttrInfo } from "./AttrSet";
|
import { AttrCards, AttrInfo, CanSelectAttrs } from "./AttrSet";
|
||||||
import { talConf, ItalConf } from "./TalSet";
|
import { talConf, ItalConf, CanSelectTalents } from "./TalSet";
|
||||||
import { SkillSet, CanSelectSkills, SkillConfig } from "./SkillSet";
|
import { SkillSet, SkillConfig, CanSelectSkills } from "./SkillSet";
|
||||||
import { HeroInfo, CanSelectHeros, heroInfo } from "./heroSet";
|
import { HeroInfo, heroInfo, CanSelectHeros } from "./heroSet";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 卡牌类型枚举
|
* 卡牌类型枚举
|
||||||
@@ -28,125 +28,170 @@ export interface ICardInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 等级池配置项
|
* 具体卡牌配置项 (内部使用)
|
||||||
*/
|
*/
|
||||||
export interface IPoolWeight {
|
export interface IPoolItem {
|
||||||
type: CardType;
|
id: number; // 卡牌UUID
|
||||||
weight: number;
|
weight: number; // 该卡牌在池中的权重
|
||||||
tag?: string; // 如果指定,只从该类型中包含此tag的卡牌中抽取
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 等级池配置项
|
||||||
|
* 仅定义类型和权重,具体卡牌内容由各模块的 CanSelectXXX 配置决定
|
||||||
|
*/
|
||||||
|
export interface IPoolConfig {
|
||||||
|
type: CardType; // 卡牌类型
|
||||||
|
poolWeight: number; // 该类型池被选中的概率权重
|
||||||
|
tag?: string; // 辅助筛选(从全池中筛选带tag的,如 "special")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认单卡权重
|
||||||
|
const DEFAULT_CARD_WEIGHT = 100;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 1-20 级卡牌池配置表
|
* 1-20 级卡牌池配置表
|
||||||
* 定义每个等级可能出现的卡牌类型及其权重
|
* 定义每个等级可能出现的卡牌类型及其权重
|
||||||
*/
|
*/
|
||||||
export const LevelPoolConfigs: Record<number, IPoolWeight[]> = {
|
export const LevelPoolConfigs: Record<number, IPoolConfig[]> = {
|
||||||
1: [{ type: CardType.Skill, weight: 100 }],
|
1: [{ type: CardType.Skill, poolWeight: 100 }],
|
||||||
2: [{ type: CardType.Attr, weight: 100 }], // 常规属性
|
2: [{ type: CardType.Attr, poolWeight: 100 }], // 常规属性
|
||||||
3: [{ type: CardType.Talent, weight: 50 }, { type: CardType.Attr, weight: 50, tag: "special" }], // 天赋或特殊属性
|
3: [{ type: CardType.Talent, poolWeight: 50 }, { type: CardType.Attr, poolWeight: 50, tag: "special" }], // 天赋或特殊属性
|
||||||
4: [{ type: CardType.Attr, weight: 100 }],
|
4: [{ type: CardType.Attr, poolWeight: 100 }],
|
||||||
5: [{ type: CardType.Talent, weight: 100 }],
|
5: [{ type: CardType.Talent, poolWeight: 100 }],
|
||||||
6: [{ type: CardType.Hero, weight: 100 }], // 伙伴节点
|
6: [{ type: CardType.Hero, poolWeight: 100 }], // 伙伴节点
|
||||||
7: [{ type: CardType.Attr, weight: 80 }, { type: CardType.Skill, weight: 20 }],
|
7: [{ type: CardType.Attr, poolWeight: 80 }, { type: CardType.Skill, poolWeight: 20 }],
|
||||||
8: [{ type: CardType.Attr, weight: 80 }, { type: CardType.Skill, weight: 20 }],
|
8: [{ type: CardType.Attr, poolWeight: 80 }, { type: CardType.Skill, poolWeight: 20 }],
|
||||||
9: [{ type: CardType.Attr, weight: 50, tag: "special" }, { type: CardType.Talent, weight: 50 }],
|
9: [{ type: CardType.Attr, poolWeight: 50, tag: "special" }, { type: CardType.Talent, poolWeight: 50 }],
|
||||||
10: [{ type: CardType.Talent, weight: 100 }],
|
10: [{ type: CardType.Talent, poolWeight: 100 }],
|
||||||
11: [{ type: CardType.Attr, weight: 70 }, { type: CardType.Skill, weight: 30 }],
|
11: [{ type: CardType.Attr, poolWeight: 70 }, { type: CardType.Skill, poolWeight: 30 }],
|
||||||
12: [{ type: CardType.Attr, weight: 70 }, { type: CardType.Skill, weight: 30 }],
|
12: [{ type: CardType.Attr, poolWeight: 70 }, { type: CardType.Skill, poolWeight: 30 }],
|
||||||
13: [{ type: CardType.Attr, weight: 100 }],
|
13: [{ type: CardType.Attr, poolWeight: 100 }],
|
||||||
14: [{ type: CardType.Attr, weight: 50, tag: "special" }, { type: CardType.Talent, weight: 50 }],
|
14: [{ type: CardType.Attr, poolWeight: 50, tag: "special" }, { type: CardType.Talent, poolWeight: 50 }],
|
||||||
15: [{ type: CardType.Talent, weight: 100 }],
|
15: [{ type: CardType.Talent, poolWeight: 100 }],
|
||||||
16: [{ type: CardType.Attr, weight: 60 }, { type: CardType.Skill, weight: 40 }],
|
16: [{ type: CardType.Attr, poolWeight: 60 }, { type: CardType.Skill, poolWeight: 40 }],
|
||||||
17: [{ type: CardType.Attr, weight: 60 }, { type: CardType.Skill, weight: 40 }],
|
17: [{ type: CardType.Attr, poolWeight: 60 }, { type: CardType.Skill, poolWeight: 40 }],
|
||||||
18: [{ type: CardType.Attr, weight: 50, tag: "special" }, { type: CardType.Talent, weight: 50 }],
|
18: [{ type: CardType.Attr, poolWeight: 50, tag: "special" }, { type: CardType.Talent, poolWeight: 50 }],
|
||||||
19: [{ type: CardType.Attr, weight: 100 }],
|
19: [{ type: CardType.Attr, poolWeight: 100 }],
|
||||||
20: [{ type: CardType.Talent, weight: 100 }],
|
20: [{ type: CardType.Talent, poolWeight: 100 }],
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== 卡牌池缓存 ==========
|
// ========== 卡牌池构建逻辑 ==========
|
||||||
let _cachedPools: Map<CardType, ICardInfo[]> = new Map();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化并获取指定类型的完整卡牌池
|
* 获取指定类型的卡牌信息(不含权重,仅基础信息)
|
||||||
*/
|
*/
|
||||||
function getFullPool(type: CardType): ICardInfo[] {
|
function getCardBaseInfo(type: CardType, uuid: number): ICardInfo | null {
|
||||||
if (_cachedPools.has(type)) {
|
let baseInfo: any = null;
|
||||||
return _cachedPools.get(type)!;
|
let name = "";
|
||||||
}
|
let desc = "";
|
||||||
|
let icon = "";
|
||||||
const pool: ICardInfo[] = [];
|
let tag = undefined;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case CardType.Attr:
|
case CardType.Attr:
|
||||||
// 转换属性配置
|
baseInfo = AttrCards[uuid];
|
||||||
Object.values(AttrCards).forEach(cfg => {
|
if (!baseInfo) return null;
|
||||||
pool.push({
|
name = baseInfo.desc.split(" ")[0] || "属性";
|
||||||
uuid: cfg.uuid,
|
desc = baseInfo.desc;
|
||||||
type: CardType.Attr,
|
icon = baseInfo.icon;
|
||||||
name: cfg.desc.split(" ")[0] || "属性强化", // 简单处理名称
|
tag = baseInfo.isSpecial ? "special" : undefined;
|
||||||
desc: cfg.desc,
|
|
||||||
icon: cfg.icon,
|
|
||||||
weight: 100, // 属性默认权重 100
|
|
||||||
tag: cfg.isSpecial ? "special" : undefined,
|
|
||||||
payload: cfg
|
|
||||||
});
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CardType.Talent:
|
case CardType.Talent:
|
||||||
// 转换天赋配置
|
baseInfo = talConf[uuid];
|
||||||
Object.values(talConf).forEach(cfg => {
|
if (!baseInfo) return null;
|
||||||
pool.push({
|
name = baseInfo.name;
|
||||||
uuid: cfg.uuid,
|
desc = baseInfo.desc;
|
||||||
type: CardType.Talent,
|
icon = baseInfo.icon;
|
||||||
name: cfg.name,
|
|
||||||
desc: cfg.desc,
|
|
||||||
icon: cfg.icon,
|
|
||||||
weight: 50, // 天赋默认权重 50
|
|
||||||
payload: cfg
|
|
||||||
});
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CardType.Skill:
|
case CardType.Skill:
|
||||||
// 转换技能配置 (仅包含 CanSelectSkills)
|
baseInfo = SkillSet[uuid];
|
||||||
CanSelectSkills.forEach(uuid => {
|
if (!baseInfo) return null;
|
||||||
const cfg = SkillSet[uuid];
|
name = baseInfo.name;
|
||||||
if (cfg) {
|
desc = baseInfo.info;
|
||||||
pool.push({
|
icon = baseInfo.icon;
|
||||||
uuid: cfg.uuid,
|
|
||||||
type: CardType.Skill,
|
|
||||||
name: cfg.name,
|
|
||||||
desc: cfg.info,
|
|
||||||
icon: cfg.icon,
|
|
||||||
weight: 80, // 技能默认权重 80
|
|
||||||
payload: cfg
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CardType.Hero:
|
case CardType.Hero:
|
||||||
// 转换英雄配置 (仅包含 CanSelectHeros)
|
baseInfo = HeroInfo[uuid];
|
||||||
CanSelectHeros.forEach(uuid => {
|
if (!baseInfo) return null;
|
||||||
const cfg = HeroInfo[uuid];
|
name = baseInfo.name;
|
||||||
if (cfg) {
|
desc = baseInfo.info;
|
||||||
pool.push({
|
icon = baseInfo.path;
|
||||||
uuid: cfg.uuid,
|
|
||||||
type: CardType.Hero,
|
|
||||||
name: cfg.name,
|
|
||||||
desc: cfg.info,
|
|
||||||
icon: cfg.path, // 使用 path 作为图标引用
|
|
||||||
weight: 1000, // 英雄权重极高(如果池子里有的话)
|
|
||||||
payload: cfg
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
_cachedPools.set(type, pool);
|
return {
|
||||||
return pool;
|
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列表 (用于去重或排除已拥有)
|
* @param excludeUuids 排除的卡牌UUID列表 (用于去重或排除已拥有)
|
||||||
*/
|
*/
|
||||||
export function getCardOptions(level: number, count: number = 3, excludeUuids: number[] = []): ICardInfo[] {
|
export function getCardOptions(level: number, count: number = 3, excludeUuids: number[] = []): ICardInfo[] {
|
||||||
// 1. 获取该等级的池配置,如果没有配置,默认给属性
|
// 1. 获取该等级的池配置
|
||||||
const poolConfigs = LevelPoolConfigs[level] || [{ type: CardType.Attr, weight: 100 }];
|
// 必须复制一份,因为我们可能需要修改它(比如移除空的池子)
|
||||||
|
const initialPoolConfigs = LevelPoolConfigs[level] || [{ type: CardType.Attr, poolWeight: 100 }];
|
||||||
|
|
||||||
const result: ICardInfo[] = [];
|
const result: ICardInfo[] = [];
|
||||||
const excludeSet = new Set(excludeUuids);
|
const excludeSet = new Set(excludeUuids);
|
||||||
|
|
||||||
// 循环抽取 count 次
|
// 循环获取 count 张卡牌
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
// 2.1 随机决定本次抽取的类型池
|
// 在每一轮获取中,我们使用一个临时的配置列表
|
||||||
const selectedPoolConfig = weightedRandomPool(poolConfigs);
|
// 这样如果某个类型池空了,我们可以从临时列表中移除它,避免重复选中
|
||||||
if (!selectedPoolConfig) continue;
|
let currentConfigs = [...initialPoolConfigs];
|
||||||
|
let cardFound = false;
|
||||||
|
|
||||||
// 2.2 获取该类型的所有卡牌
|
while (currentConfigs.length > 0) {
|
||||||
let candidates = getFullPool(selectedPoolConfig.type);
|
// 2.1 随机决定本次抽取的类型池
|
||||||
|
const selectedConfig = weightedRandomPool(currentConfigs);
|
||||||
|
if (!selectedConfig) break; // 理论上不会发生
|
||||||
|
|
||||||
// 2.3 过滤 (Tag过滤 + 排除列表 + 已选中过滤)
|
// 2.2 获取该类型的所有候选卡牌
|
||||||
candidates = candidates.filter(card => {
|
// 直接使用默认全池 (传入level以获取该等级特定的默认配置)
|
||||||
// Tag 匹配
|
const rawCandidates = getDefaultPool(selectedConfig.type, level);
|
||||||
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 如果该池子空了 (比如技能都学完了),尝试从属性池兜底
|
// 2.3 过滤与构建完整信息
|
||||||
if (candidates.length === 0) {
|
const validCandidates: ICardInfo[] = [];
|
||||||
candidates = getFullPool(CardType.Attr).filter(c => !result.find(r => r.uuid === c.uuid));
|
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 如果尝试了所有类型都没找到卡 (极少见兜底)
|
||||||
// 2.5 按卡牌权重随机抽取一张
|
if (!cardFound) {
|
||||||
const card = weightedRandomCard(candidates);
|
// 尝试从属性池硬拿一个不重复的
|
||||||
if (card) {
|
const attrItems = getDefaultPool(CardType.Attr);
|
||||||
result.push(card);
|
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);
|
shuffleArray(result);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -204,17 +296,14 @@ export function getCardOptions(level: number, count: number = 3, excludeUuids: n
|
|||||||
|
|
||||||
// ========== 工具函数 ==========
|
// ========== 工具函数 ==========
|
||||||
|
|
||||||
/**
|
function weightedRandomPool(configs: IPoolConfig[]): IPoolConfig | null {
|
||||||
* 权重随机选择池配置
|
|
||||||
*/
|
|
||||||
function weightedRandomPool(configs: IPoolWeight[]): IPoolWeight | null {
|
|
||||||
if (!configs || configs.length === 0) return null;
|
if (!configs || configs.length === 0) return null;
|
||||||
|
|
||||||
const totalWeight = configs.reduce((sum, item) => sum + item.weight, 0);
|
const totalWeight = configs.reduce((sum, item) => sum + item.poolWeight, 0);
|
||||||
let randomVal = Math.random() * totalWeight;
|
let randomVal = Math.random() * totalWeight;
|
||||||
|
|
||||||
for (const config of configs) {
|
for (const config of configs) {
|
||||||
randomVal -= config.weight;
|
randomVal -= config.poolWeight;
|
||||||
if (randomVal <= 0) {
|
if (randomVal <= 0) {
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
@@ -222,9 +311,6 @@ function weightedRandomPool(configs: IPoolWeight[]): IPoolWeight | null {
|
|||||||
return configs[configs.length - 1];
|
return configs[configs.length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 权重随机选择卡牌
|
|
||||||
*/
|
|
||||||
function weightedRandomCard(cards: ICardInfo[]): ICardInfo | null {
|
function weightedRandomCard(cards: ICardInfo[]): ICardInfo | null {
|
||||||
if (!cards || cards.length === 0) return null;
|
if (!cards || cards.length === 0) return null;
|
||||||
|
|
||||||
@@ -240,9 +326,6 @@ function weightedRandomCard(cards: ICardInfo[]): ICardInfo | null {
|
|||||||
return cards[cards.length - 1];
|
return cards[cards.length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 数组洗牌 (Fisher-Yates)
|
|
||||||
*/
|
|
||||||
function shuffleArray(array: any[]) {
|
function shuffleArray(array: any[]) {
|
||||||
for (let i = array.length - 1; i > 0; i--) {
|
for (let i = array.length - 1; i > 0; i--) {
|
||||||
const j = Math.floor(Math.random() * (i + 1));
|
const j = Math.floor(Math.random() * (i + 1));
|
||||||
|
|||||||
@@ -292,4 +292,11 @@ export const EAnmConf: Record<number, IEndAnm> = {
|
|||||||
9001:{uuid:9001,path:"atked",loop:false,time:0},
|
9001:{uuid:9001,path:"atked",loop:false,time:0},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CanSelectSkills = [6002, 6004, 6003, 6100];
|
export const CanSelectSkills: Record<number, number[]> = {
|
||||||
|
1: [6002],
|
||||||
|
2: [6004],
|
||||||
|
3: [6003],
|
||||||
|
4: [6100],
|
||||||
|
// 默认
|
||||||
|
99: [6002, 6004, 6003, 6100]
|
||||||
|
};
|
||||||
|
|||||||
@@ -135,4 +135,17 @@ export const talConf: Record<number, ItalConf> = {
|
|||||||
desc:"每升1级,永久增加2%的风怒概率"},
|
desc:"每升1级,永久增加2%的风怒概率"},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const CanSelectTalents: Record<number, number[]> = {
|
||||||
|
// 3级开放攻击类天赋
|
||||||
|
3: [7001, 7003, 7005, 7008],
|
||||||
|
// 5级必出防御类
|
||||||
|
5: [7101, 7102, 7103],
|
||||||
|
// 9级混合
|
||||||
|
9: [7001, 7003, 7005, 7008, 7101, 7102, 7103],
|
||||||
|
// 20级终极天赋
|
||||||
|
20: [7301, 7302],
|
||||||
|
// 默认全开
|
||||||
|
99: [7001, 7003, 7004, 7005, 7006, 7007, 7008, 7009, 7010, 7101, 7102, 7103, 7104, 7201, 7301, 7302]
|
||||||
|
};
|
||||||
|
|
||||||
// ========== 工具函数 ==========
|
// ========== 工具函数 ==========
|
||||||
|
|||||||
@@ -122,7 +122,16 @@ export interface heroInfo {
|
|||||||
info: string; // 描述文案
|
info: string; // 描述文案
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CanSelectHeros = [5001, 5002, 5003, 5004, 5005, 5006, 5007];
|
export const CanSelectHeros: Record<number, number[]> = {
|
||||||
|
1: [5001, 5002],
|
||||||
|
2: [5003],
|
||||||
|
3: [5004],
|
||||||
|
4: [5005],
|
||||||
|
5: [5006],
|
||||||
|
6: [5007],
|
||||||
|
// 默认全开(或根据需要留空)
|
||||||
|
99: [5001, 5002, 5003, 5004, 5005, 5006, 5007]
|
||||||
|
};
|
||||||
|
|
||||||
export const HeroInfo: Record<number, heroInfo> = {
|
export const HeroInfo: Record<number, heroInfo> = {
|
||||||
// ========== 英雄角色 ==========
|
// ========== 英雄角色 ==========
|
||||||
|
|||||||
Reference in New Issue
Block a user