Files
pixelheros/assets/script/game/common/config/CardSet.ts
pan e76cba7933 feat(map): 新增固定波次技能三选一弹窗系统
1.  新增MSkillBoxComp弹窗组件,实现固定波次触发的技能卡选择功能
2.  新增SkillBoxCardConfig配置与SkillBoxPool技能池,支持按波次配置技能
3.  重构MissionCardComp,将技能卡抽取改为固定波次弹窗触发
4.  扩展SingletonModuleComp与MissionComp,添加技能刷新次数持久化逻辑
5.  优化MissSkillsComp,新增SkillBox专属技能加载流程
6.  修复SkillBoxComp,支持自定义技能参数覆盖
7.  调整UIConfig与CardSet配置,适配新的技能卡流程
2026-06-03 16:36:22 +08:00

487 lines
19 KiB
TypeScript

import * as exp from "constants"
import { HeroInfo, HeroList, HType } from "./heroSet"
import { FightSet } from "./GameSet"
import { oops } from "db://oops-framework/core/Oops"
import { SkillOverrides } from "./SkillSet"
class I18nString {
constructor(private key: string, private params?: any[]) { }
private getTranslated(): string {
let str = oops.language.getLangByID(this.key) || this.key;
if (this.params && this.params.length > 0) {
for (let i = 0; i < this.params.length; i++) {
str = str.replace(`{${i}}`, String(this.params[i]));
}
}
return str;
}
toString() { return this.getTranslated(); }
valueOf() { return this.getTranslated(); }
toJSON() { return this.getTranslated(); }
get length() { return this.toString().length; }
}
const t = (key: string, ...params: any[]) => new I18nString(key, params) as unknown as string;
/** 卡牌大类定义 */
export enum CardType {
Hero = 1,
Skill = 2,
SpecialUpgrade = 3,
SpecialRefresh = 4,
}
/** 卡牌大类定义 */
export enum CKind {
Hero = 1, //英雄
Skill = 2, //技能
Card = 3, //卡牌
Potion = 4, //药水
}
/** 卡池等级定义 */
export enum CardLV {
LV1 = 1,
LV2 = 2,
LV3 = 3,
}
/** 通用卡牌配置 */
export interface CardConfig {
uuid: number
type: CardType
cost: number
weight: number
kind: CKind
pool_lv: CardLV
hero_lv?: number
card_lv?: number
base_pool_lv?: number
// 技能卡扩展属性
name?: string // 卡牌名称
info?: string // 卡牌描述信息
is_inst?: boolean // 是否即时起效
t_times?: number // 触发次数
t_inv?: number // 触发间隔(秒)
}
/**
* 技能卡池专用卡牌配置(MSkillBoxComp 三选一弹窗使用)
*
* 在普通 CardConfig 基础上扩展:
* - s_uuid : 实际生效的 SkillSet 条目 uuid
* - t_num : 触发次数(可选,默认从 SkillSet 读取)
* - overrides : 参数覆盖,参考 heroSet 中触发技能的 overrides 字段格式
*
* uuid 范围 9000+ 用于和普通 CardPoolList 区分,MissSkillsComp 据此识别。
*/
export interface SkillBoxCardConfig extends CardConfig {
/** 实际生效的 SkillSet 技能 uuid */
s_uuid: number
/** 触发次数(可选,默认 1) */
t_num?: number
/** 技能参数覆盖,参考 SkillOverrides */
overrides?: SkillOverrides
}
export const CardsUpSet: Record<number, number> = {
1: 50,
2: 100,
3: 150,
4: 200,
5: 250,
}
/**初始coin数 */
export const CardInitCoins = 10
/** 卡池升级每波减免金额 */
export const CARD_POOL_UPGRADE_DISCOUNT_PER_WAVE = 10
/** 卡池默认初始等级 */
export const CARD_POOL_INIT_LEVEL = CardLV.LV1
/** 卡池等级上限 */
export const CARD_POOL_MAX_LEVEL = CardLV.LV3
/** 英雄最高等级限制 */
export const CARD_HERO_MAX_LEVEL = 1
/** 基础卡池(英雄、技能、功能) */
export const CardPoolList: CardConfig[] = [];
// 动态生成英雄卡池
HeroList.forEach(uuid => {
const hero = HeroInfo[uuid];
if (!hero) return;
const basePoolLv = hero.pool_lv || 1;
const baseHeroLv = hero.lv || 1;
const baseCost = 5;
const baseWeight = 25;
// 生成从 basePoolLv 到 CARD_POOL_MAX_LEVEL 的卡牌
for (let pLv = basePoolLv; pLv <= CARD_POOL_MAX_LEVEL; pLv++) {
const offset = pLv - basePoolLv;
const targetHeroLv = baseHeroLv + offset;
// 英雄的最高等级 是MERGE_MAX-1
if (targetHeroLv > FightSet.MERGE_MAX - 1) {
break;
}
// cost = baseCost * 3^(lv-1): Lv1=5, Lv2=15, Lv3=45
let cost = baseCost;
if (targetHeroLv > 1) {
cost = baseCost * Math.pow(FightSet.MERGE_NEED, targetHeroLv - 1);
}
CardPoolList.push({
uuid: hero.uuid,
type: CardType.Hero,
cost: cost,
weight: baseWeight,
pool_lv: pLv as CardLV,
kind: CKind.Hero,
hero_lv: targetHeroLv,
base_pool_lv: basePoolLv
});
}
});
// 添加非英雄卡牌 (技能、功能卡)
CardPoolList.push(
// 技能卡牌 (以增益/辅助为主,因为在备战期没有敌人)
{ uuid: 6401, type: CardType.Skill, cost: 8, weight: 20, pool_lv: 1, kind: CKind.Skill, card_lv: 1, name: t("skill_name_6401"), info: t("skill_info_6401"), is_inst: true, t_times: 1, t_inv: 0 },
{ uuid: 6402, type: CardType.Skill, cost: 8, weight: 20, pool_lv: 1, kind: CKind.Skill, card_lv: 1, name: t("skill_name_6402"), info: t("skill_info_6402"), is_inst: true, t_times: 1, t_inv: 0 },
{ uuid: 6403, type: CardType.Skill, cost: 8, weight: 20, pool_lv: 1, kind: CKind.Skill, card_lv: 1, name: t("skill_name_6403"), info: t("skill_info_6403"), is_inst: true, t_times: 1, t_inv: 0 },
{ uuid: 6404, type: CardType.Skill, cost: 8, weight: 20, pool_lv: 1, kind: CKind.Skill, card_lv: 1, name: t("skill_name_6404"), info: t("skill_info_6404"), is_inst: true, t_times: 1, t_inv: 0 },
{ uuid: 6405, type: CardType.Skill, cost: 8, weight: 20, pool_lv: 2, kind: CKind.Skill, card_lv: 1, name: t("skill_name_6405"), info: t("skill_info_6405"), is_inst: true, t_times: 1, t_inv: 0 },
{ uuid: 6406, type: CardType.Skill, cost: 8, weight: 20, pool_lv: 2, kind: CKind.Skill, card_lv: 1, name: t("skill_name_6406"), info: t("skill_info_6406"), is_inst: true, t_times: 1, t_inv: 0 },
{ uuid: 6304, type: CardType.Skill, cost: 8, weight: 20, pool_lv: 3, kind: CKind.Skill, card_lv: 1, name: t("skill_name_6304"), info: t("skill_info_6304"), is_inst: true, t_times: 1, t_inv: 0 },
{ uuid: 6305, type: CardType.Skill, cost: 8, weight: 20, pool_lv: 3, kind: CKind.Skill, card_lv: 1, name: t("skill_name_6305"), info: t("skill_info_6305"), is_inst: true, t_times: 1, t_inv: 0 },
);
export enum SpecialRefreshHeroType {
Any = 0,
Melee = 1,
Ranged = 2,
}
/** 升级功能卡完整配置 */
export interface SpecialUpgradeCardConfig extends CardConfig {
name: string
info: string
currentLv: number
targetLv: number
}
/** 刷新功能卡完整配置 */
export interface SpecialRefreshCardConfig extends CardConfig {
name: string
info: string
refreshLv: number
refreshHeroType: SpecialRefreshHeroType
}
/** 功能卡定义表 */
export const SpecialUpgradeCardList: Record<number, SpecialUpgradeCardConfig> = {
7001: {
uuid: 7001, type: CardType.SpecialUpgrade, cost: 10, weight: 16, pool_lv: CardLV.LV1, kind: CKind.Card, name: t("scard_name_7001"), info: t("scard_info_7001"),
currentLv: 1, targetLv: 2,
},
7002: {
uuid: 7002, type: CardType.SpecialUpgrade, cost: 28, weight: 14, pool_lv: CardLV.LV2, kind: CKind.Card, name: t("scard_name_7002"), info: t("scard_info_7002"),
currentLv: 2, targetLv: 3,
},
}
export const SpecialRefreshCardList: Record<number, SpecialRefreshCardConfig> = {
7101: {
uuid: 7101, type: CardType.SpecialRefresh, cost: 3, weight: 14, pool_lv: CardLV.LV1, kind: CKind.Card, name: t("scard_name_7101"), info: t("scard_info_7101"),
refreshLv: 0, refreshHeroType: SpecialRefreshHeroType.Melee,
},
7102: {
uuid: 7102, type: CardType.SpecialRefresh, cost: 3, weight: 14, pool_lv: CardLV.LV1, kind: CKind.Card, name: t("scard_name_7102"), info: t("scard_info_7102"),
refreshLv: 0, refreshHeroType: SpecialRefreshHeroType.Ranged,
},
7103: {
uuid: 7103, type: CardType.SpecialRefresh, cost: 4, weight: 12, pool_lv: CardLV.LV2, kind: CKind.Card, name: t("scard_name_7103"), info: t("scard_info_7103"),
refreshLv: 3, refreshHeroType: SpecialRefreshHeroType.Any,
},
}
/** 规范等级到合法区间 [LV1, LV6] */
const clampCardLv = (lv: number): CardLV => {
const value = Math.floor(lv)
if (value < CARD_POOL_INIT_LEVEL) return CARD_POOL_INIT_LEVEL
if (value > CARD_POOL_MAX_LEVEL) return CARD_POOL_MAX_LEVEL
return value as CardLV
}
/** 单次按权重抽取一张卡 */
const weightedPick = (cards: CardConfig[]): CardConfig | null => {
if (cards.length === 0) return null
const totalWeight = cards.reduce((total, card) => total + card.weight, 0)
let random = Math.random() * totalWeight
for (const card of cards) {
random -= card.weight
if (random <= 0) return card
}
return cards[cards.length - 1]
}
/** 连续抽取 count 张卡,允许重复 */
const pickCards = (cards: CardConfig[], count: number): CardConfig[] => {
if (cards.length === 0 || count <= 0) return []
const selected: CardConfig[] = []
while (selected.length < count) {
const pick = weightedPick(cards)
if (!pick) break
selected.push(pick)
}
return selected
}
/** 获取指定等级可出现的基础卡池 */
export const getCardPoolByLv = (lv: number, onlyCurrentLv: boolean = false): CardConfig[] => {
const cardLv = clampCardLv(lv)
if (onlyCurrentLv) {
return CardPoolList.filter(card => card.pool_lv === cardLv)
}
return CardPoolList.filter(card => card.pool_lv <= cardLv)
}
const normalizeTypeFilter = (type: CardType | CardType[]): Set<CardType> => {
const list = Array.isArray(type) ? type : [type]
return new Set<CardType>(list)
}
/** 常规发牌:前 3 英雄 + 后 1 其他;支持按类型和等级模式过滤 */
export const getCardsByLv = (
lv: number,
type?: CardType | CardType[],
onlyCurrentLv: boolean = false
): CardConfig[] => {
const pool = getCardPoolByLv(lv, onlyCurrentLv)
if (type !== undefined) {
const typeSet = normalizeTypeFilter(type)
const filteredPool = pool.filter(card => typeSet.has(card.type))
return pickCards(filteredPool, 4)
}
const heroPool = pool.filter(card => card.type === CardType.Hero)
const otherPool = pool.filter(card => card.type !== CardType.Hero)
const heroes = pickCards(heroPool, 3)
const others = pickCards(otherPool, 1)
return [...heroes, ...others]
}
export const drawCardsByRule = (
lv: number,
options: {
count?: number
onlyCurrentLv?: boolean
type?: CardType | CardType[]
heroType?: HType
heroLv?: number
targetPoolLv?: number
} = {}
): CardConfig[] => {
const count = Math.max(0, Math.floor(options.count ?? 4))
const onlyCurrentLv = options.onlyCurrentLv ?? false
let pool = getCardPoolByLv(lv, onlyCurrentLv)
if (options.type !== undefined) {
const typeSet = normalizeTypeFilter(options.type)
pool = pool.filter(card => typeSet.has(card.type))
}
if (options.targetPoolLv !== undefined) {
// 如果指定了目标卡池等级,则强制从所有配置中筛选该等级的卡牌,无视当前的卡池等级限制
pool = CardPoolList.filter(card => card.pool_lv === options.targetPoolLv)
if (options.type !== undefined) {
const typeSet = normalizeTypeFilter(options.type)
pool = pool.filter(card => typeSet.has(card.type))
}
}
if (options.heroType !== undefined || options.heroLv !== undefined) {
pool = pool.filter(card => {
if (card.type !== CardType.Hero) return false
const hero = HeroInfo[card.uuid]
if (!hero) return false
if (options.heroType !== undefined && hero.type !== options.heroType) return false
if (options.heroLv !== undefined && card.hero_lv !== options.heroLv) return false
return true
})
}
const picked = pickCards(pool, count)
return picked
}
/**
* 固定波次弹出技能三选一弹窗(MissionCardComp 触发)
* 5 个波次:1 / 5 / 10 / 15 / 20
*/
export const SKILL_BOX_TRIGGER_WAVES: readonly number[] = [1, 5, 10, 15, 20] as const
/**
* 技能卡池(MSkillBoxComp 三选一)
*
* 5 个硬编码独立池,key 对应触发波次,value 是该波次可刷出的 SkillBoxCardConfig 列表。
* 每条配置:
* - uuid 唯一且 >= 9000,用于 MissSkillsComp 识别
* - s_uuid 指向 SkillSet 中实际生效的技能
* - overrides 参考 heroSet 触发技能格式,对技能参数做具体覆盖
* - cost 强制 0(免费领取)
* - is_inst / t_times / t_inv 控制技能使用后行为
*/
export const SkillBoxPool: Record<number, SkillBoxCardConfig[]> = {
// 第 1 波:基础技能(伤害/治疗/护盾基础数值)
1: [
{
uuid: 9001, type: CardType.Skill, cost: 0, weight: 0, pool_lv: CardLV.LV1, kind: CKind.Skill, card_lv: 1,
name: t("skillbox_name_9001"), info: t("skillbox_info_9001"),
is_inst: true, t_times: 1, t_inv: 0,
s_uuid: 6401, t_num: 1, overrides: { ap: 120 }
},
{
uuid: 9002, type: CardType.Skill, cost: 0, weight: 0, pool_lv: CardLV.LV1, kind: CKind.Skill, card_lv: 1,
name: t("skillbox_name_9002"), info: t("skillbox_info_9002"),
is_inst: true, t_times: 1, t_inv: 0,
s_uuid: 6402, t_num: 1, overrides: { ap: 150 }
},
{
uuid: 9003, type: CardType.Skill, cost: 0, weight: 0, pool_lv: CardLV.LV1, kind: CKind.Skill, card_lv: 1,
name: t("skillbox_name_9003"), info: t("skillbox_info_9003"),
is_inst: true, t_times: 1, t_inv: 0,
s_uuid: 6403, t_num: 1, overrides: { ap: 100, hit_count: 2 }
},
{
uuid: 9004, type: CardType.Skill, cost: 0, weight: 0, pool_lv: CardLV.LV1, kind: CKind.Skill, card_lv: 1,
name: t("skillbox_name_9004"), info: t("skillbox_info_9004"),
is_inst: true, t_times: 1, t_inv: 0,
s_uuid: 6404, t_num: 1, overrides: { ap: 110 }
},
],
// 第 5 波:中级技能(伤害提升,带附加效果)
5: [
{
uuid: 9005, type: CardType.Skill, cost: 0, weight: 0, pool_lv: CardLV.LV2, kind: CKind.Skill, card_lv: 1,
name: t("skillbox_name_9005"), info: t("skillbox_info_9005"),
is_inst: true, t_times: 1, t_inv: 0,
s_uuid: 6405, t_num: 1, overrides: { ap: 130, crt: 10 }
},
{
uuid: 9006, type: CardType.Skill, cost: 0, weight: 0, pool_lv: CardLV.LV2, kind: CKind.Skill, card_lv: 1,
name: t("skillbox_name_9006"), info: t("skillbox_info_9006"),
is_inst: true, t_times: 1, t_inv: 0,
s_uuid: 6406, t_num: 1, overrides: { ap: 140, hit_count: 2 }
},
{
uuid: 9007, type: CardType.Skill, cost: 0, weight: 0, pool_lv: CardLV.LV2, kind: CKind.Skill, card_lv: 1,
name: t("skillbox_name_9007"), info: t("skillbox_info_9007"),
is_inst: true, t_times: 1, t_inv: 0,
s_uuid: 6304, t_num: 1, overrides: { ap: 160 }
},
{
uuid: 9008, type: CardType.Skill, cost: 0, weight: 0, pool_lv: CardLV.LV2, kind: CKind.Skill, card_lv: 1,
name: t("skillbox_name_9008"), info: t("skillbox_info_9008"),
is_inst: false, t_times: 3, t_inv: 2,
s_uuid: 6401, t_num: 3, overrides: { ap: 100 }
},
],
// 第 10 波:高级技能(高伤害/多段/持续)
10: [
{
uuid: 9009, type: CardType.Skill, cost: 0, weight: 0, pool_lv: CardLV.LV3, kind: CKind.Skill, card_lv: 1,
name: t("skillbox_name_9009"), info: t("skillbox_info_9009"),
is_inst: true, t_times: 1, t_inv: 0,
s_uuid: 6305, t_num: 1, overrides: { ap: 200 }
},
{
uuid: 9010, type: CardType.Skill, cost: 0, weight: 0, pool_lv: CardLV.LV3, kind: CKind.Skill, card_lv: 1,
name: t("skillbox_name_9010"), info: t("skillbox_info_9010"),
is_inst: true, t_times: 1, t_inv: 0,
s_uuid: 6405, t_num: 1, overrides: { ap: 220, hit_count: 3, crt: 15 }
},
{
uuid: 9011, type: CardType.Skill, cost: 0, weight: 0, pool_lv: CardLV.LV3, kind: CKind.Skill, card_lv: 1,
name: t("skillbox_name_9011"), info: t("skillbox_info_9011"),
is_inst: false, t_times: 4, t_inv: 1.5,
s_uuid: 6402, t_num: 4, overrides: { ap: 130, frz: 10 }
},
{
uuid: 9012, type: CardType.Skill, cost: 0, weight: 0, pool_lv: CardLV.LV3, kind: CKind.Skill, card_lv: 1,
name: t("skillbox_name_9012"), info: t("skillbox_info_9012"),
is_inst: true, t_times: 1, t_inv: 0,
s_uuid: 6406, t_num: 1, overrides: { ap: 240, bck: 20 }
},
],
// 第 15 波:精英技能(高额暴击/多段)
15: [
{
uuid: 9013, type: CardType.Skill, cost: 0, weight: 0, pool_lv: CardLV.LV3, kind: CKind.Skill, card_lv: 2,
name: t("skillbox_name_9013"), info: t("skillbox_info_9013"),
is_inst: true, t_times: 1, t_inv: 0,
s_uuid: 6304, t_num: 1, overrides: { ap: 280, hit_count: 3 }
},
{
uuid: 9014, type: CardType.Skill, cost: 0, weight: 0, pool_lv: CardLV.LV3, kind: CKind.Skill, card_lv: 2,
name: t("skillbox_name_9014"), info: t("skillbox_info_9014"),
is_inst: true, t_times: 1, t_inv: 0,
s_uuid: 6305, t_num: 1, overrides: { ap: 320, crt: 25 }
},
{
uuid: 9015, type: CardType.Skill, cost: 0, weight: 0, pool_lv: CardLV.LV3, kind: CKind.Skill, card_lv: 2,
name: t("skillbox_name_9015"), info: t("skillbox_info_9015"),
is_inst: false, t_times: 5, t_inv: 1.2,
s_uuid: 6406, t_num: 5, overrides: { ap: 200, hit_count: 2, bck: 30 }
},
],
// 第 20 波:终极技能(全屏/极限数值)
20: [
{
uuid: 9016, type: CardType.Skill, cost: 0, weight: 0, pool_lv: CardLV.LV3, kind: CKind.Skill, card_lv: 3,
name: t("skillbox_name_9016"), info: t("skillbox_info_9016"),
is_inst: true, t_times: 1, t_inv: 0,
s_uuid: 6304, t_num: 1, overrides: { ap: 400, hit_count: 4, crt: 30 }
},
{
uuid: 9017, type: CardType.Skill, cost: 0, weight: 0, pool_lv: CardLV.LV3, kind: CKind.Skill, card_lv: 3,
name: t("skillbox_name_9017"), info: t("skillbox_info_9017"),
is_inst: true, t_times: 1, t_inv: 0,
s_uuid: 6305, t_num: 1, overrides: { ap: 500, crt: 40, frz: 20 }
},
{
uuid: 9018, type: CardType.Skill, cost: 0, weight: 0, pool_lv: CardLV.LV3, kind: CKind.Skill, card_lv: 3,
name: t("skillbox_name_9018"), info: t("skillbox_info_9018"),
is_inst: true, t_times: 1, t_inv: 0,
s_uuid: 6406, t_num: 1, overrides: { ap: 600, hit_count: 5, bck: 50 }
},
],
}
/**
* 从指定波次的技能池中抽取 N 张卡(加权随机,去重,不足循环补齐)
*
* @param wave 触发波次(1/5/10/15/20),若该波次无池则返回空数组
* @param count 期望返回卡牌数(默认 3)
* @returns 抽中的 SkillBoxCardConfig 列表(长度 = count)
*/
export function getSkillBoxCards(wave: number, count: number = 3): SkillBoxCardConfig[] {
const pool = SkillBoxPool[wave] || [];
if (pool.length === 0) return [];
const result: SkillBoxCardConfig[] = [];
const usedIndexes = new Set<number>();
for (let i = 0; i < count; i++) {
const available: number[] = [];
for (let j = 0; j < pool.length; j++) {
if (!usedIndexes.has(j)) available.push(j);
}
// 池内卡片不足时,允许重复
const candidates = available.length > 0 ? available : Array.from({ length: pool.length }, (_, k) => k);
const pick = candidates[Math.floor(Math.random() * candidates.length)];
usedIndexes.add(pick);
result.push(pool[pick]);
}
return result;
}