Files
pixelheros/assets/script/game/common/config/CardSet.ts
panw 0a8f2056c3 refactor(game): 优化属性卡牌配置与UI更新逻辑
- 将AttrSet中的`note`字段统一重命名为`name`,提高字段语义清晰度
- 在CardSet中修正属性卡和药水卡名称的获取逻辑,使用新的`name`字段
- 调整HeroViewComp的health方法,增加血量变化小于等于99时不显示特效的优化
- 将HeroAttrsSystem中的自然回复计时器从1秒改为5秒,并更新相关注释
- 移除HeroViewComp中已注释的冗余UI更新代码,保持代码整洁
2026-02-04 16:23:09 +08:00

331 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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";
/**
* 统一卡牌信息接口 (用于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 强制指定卡牌类型 (用于特殊获取,如商店、技能书等)
*/
export function getCardOptions(level: number, count: number = 3, excludeUuids: number[] = [], forcedType?: CardType): 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;
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]];
}
}