refactor(hero): 移除天赋系统和相关属性,简化英雄架构
- 删除 SCDSystem、HeroAttrSystem 等独立系统,将功能整合到现有组件 - 移除 TalComp 天赋组件及相关配置(TalSet、AttrSet、CardSet) - 清理 HeroAttrs 中未使用的属性枚举,保留核心战斗属性 - 简化 Hero 实体创建逻辑,不再为主角挂载天赋组件 - 移除 SingletonModuleComp 中与天赋、经验、收集相关的数据管理
This commit is contained in:
@@ -1,372 +0,0 @@
|
||||
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<number, IPoolConfig[]> = {
|
||||
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<number, number[]> | 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]];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user