Files
pixelheros/assets/script/game/common/config/CardSet.ts
panFD c59ba45b5b refactor: 统一将游戏内的「波次」术语替换为「回合」
本次提交将全项目中所有与「波次」相关的术语、注释、配置描述、UI文本全部替换为「回合」,包括:
1.  技能卡牌、怪物刷新、经济系统的相关注释与变量名
2.  配置表中的波次相关常量与描述文本
3.  UI预制体中的波次显示文本
4.  所有相关的文档注释与接口说明

统一术语后,游戏的关卡流程描述将更符合常规肉鸽游戏的表达习惯,避免与「波次」字面含义混淆,提升代码可读性与团队协作一致性。
2026-08-09 14:51:48 +08:00

258 lines
9.2 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 * as exp from "constants"
import { HeroInfo, HeroList, HType } from "./heroSet"
import { oops } from "db://oops-framework/core/Oops"
import { SkillOverrides, TGroup } 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, //药水
}
/**
* 卡牌技能触发类型
* - 命名对齐英雄侧 SkillTriggerType便于跨模块认知统一
* - 枚举值从 1 开始,避免 0 的 falsy 坑if (trigger_type) 判断出错)
*/
export enum CardTriggerType {
Instant = 1, // 即时触发:使用后立即生效一次
Interval = 2, // 定时循环:战斗中按 t_inv 间隔重复触发
Field = 3, // 驻场光环(俗称「装备」):被动生效,由 field 数组驱动,支持单卡多词条
FightStart = 4, // 战斗开始时触发
FightEnd = 5, // 战斗结束时触发(每回合结束)
HeroDead = 6, // 场上己方英雄死亡时触发
HeroCall = 7, // 英雄上场时触发(主角召唤 + 技能召唤 + 复活)
}
/** 通用卡牌配置 */
export interface CardConfig {
uuid: number
type: CardType
cost: number
weight: number
kind: CKind
card_lv: number // 卡牌等级(驱动 bg_node 颜色1=green 2=blue 3=purple 4=red 5=yellow
wave?: number // 针对技能卡:仅在指定回合(wave)才能抽到
hero_lv?: number
base_card_lv?: number
// 技能卡扩展属性
skill?: number // 关联的技能 UUID
icon?: string // 图标ID可选优先使用未设置时按 trigger_type 从 SkillSet/FieldSkillSet 自动取)
name?: string // 卡牌名称
info?: string // 卡牌描述信息
is_inst?: boolean // 是否即时起效
t_times?: number // 触发次数
t_inv?: number // 触发间隔(秒)
keep_waves?: number // 维持的回合数(-1表示持续到战斗结束0或undefined表示仅本回合
overrides?: SkillOverrides // 技能参数覆写如自定义伤害ap、buff值、金币数等
field?: number[] // 装备词条数组驻场光环卡trigger_type=Field的属性词条支持单卡多词条如 [7002, 7004, 7005] 同时提供攻击/暴击/暴伤)
/** 触发类型(必填,技能卡专用;功能卡/英雄卡可缺省) */
trigger_type?: CardTriggerType;
/**
* 事件型触发的全局次数上限(仅 FightStart/FightEnd/HeroDead/HeroCall 有效)
* 默认 Infinity达到上限后销毁节点
* 注意:与 t_times 语义不同——t_times 控制每回合内 Interval 的次数
*/
trigger_limit?: number;
/**
* 动态升级卡专属:目标英雄实体 eid运行时唯一
* 仅当 type === CardType.SpecialUpgrade 且该卡由 buildDrawCards 动态生成时使用。
* 使用该卡时精确升级场上对应 eid 的英雄实体。
*/
target_hero_eid?: number;
}
/** 基础卡池(英雄、技能、功能) */
export const CardPoolList: CardConfig[] = [];
// 动态生成英雄卡池:所有英雄统一生成 lv1 卡牌
function getHeroCost(cardLv: number): number {
if (cardLv === 1) return 5;
if (cardLv === 2) return 10;
return 15;
}
HeroList.forEach(uuid => {
const hero = HeroInfo[uuid];
if (!hero) return;
const baseWeight = 25;
CardPoolList.push({
uuid: hero.uuid,
type: CardType.Hero,
cost: getHeroCost(hero.card_lv ?? 1),
weight: baseWeight,
card_lv: hero.card_lv ?? 1,
kind: CKind.Hero,
hero_lv: 1,
base_card_lv: hero.card_lv ?? 1,
});
});
export enum SpecialRefreshHeroType {
Any = 0,
Melee = 1,
Ranged = 2,
}
/** 升级功能卡完整配置(作为动态升级卡的基础模板) */
export interface SpecialUpgradeCardConfig extends CardConfig {
name: string
info: string
/** 模板字段currentLv=0 表示动态卡(实际值由 target_hero_eid 对应的英雄决定) */
currentLv: number
/** 模板字段targetLv=0 表示"+1 级"(实际目标等级 = 当前等级 + 1 */
targetLv: number
}
/** 刷新功能卡完整配置 */
export interface SpecialRefreshCardConfig extends CardConfig {
name: string
info: string
refreshLv: number
refreshHeroType: SpecialRefreshHeroType
}
/**
* 升级卡模板。
* 仅有 7001 一条记录作为动态升级卡的基础配置cost/weight/name/info
* 实际使用时由 MissionCardComp.buildDrawCards 根据场上英雄动态生成 CardConfig
* 通过 target_hero_eid 字段绑定具体英雄实体。
*/
export const SpecialUpgradeCardList: Record<number, SpecialUpgradeCardConfig> = {
7001: {
uuid: 7001, type: CardType.SpecialUpgrade, cost: 10, weight: 18, card_lv: 1, kind: CKind.Card, name: t("scard_name_7001"), info: t("scard_info_7001"),
currentLv: 0, targetLv: 0,
},
}
export const SpecialRefreshCardList: Record<number, SpecialRefreshCardConfig> = {
7101: {
uuid: 7101, type: CardType.SpecialRefresh, cost: 3, weight: 14, card_lv: 1, 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, card_lv: 1, 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, card_lv: 1, kind: CKind.Card, name: t("scard_name_7103"), info: t("scard_info_7103"),
refreshLv: 0, refreshHeroType: SpecialRefreshHeroType.Any,
},
}
// ============ 装备相关语义已迁移至 EquipSet.ts ============
// 装备卡池EquipPoolList独立管理不再混入 CardPoolList
/** 单次按权重抽取一张卡 */
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 张卡,允许重复或通过 unique 剔除重复 */
const pickCards = (cards: CardConfig[], count: number, unique: boolean = false): CardConfig[] => {
if (cards.length === 0 || count <= 0) return []
const selected: CardConfig[] = []
let available = [...cards]
while (selected.length < count) {
if (available.length === 0) break
const pick = weightedPick(available)
if (!pick) break
selected.push(pick)
if (unique) {
available = available.filter(c => c.uuid !== pick.uuid)
}
}
return selected
}
const normalizeTypeFilter = (type: CardType | CardType[]): Set<CardType> => {
const list = Array.isArray(type) ? type : [type]
return new Set<CardType>(list)
}
/**
* 通用按规则抽卡。
* @param options 抽卡选项
*/
export const drawCardsByRule = (
options: {
count?: number
type?: CardType | CardType[]
heroType?: HType
heroLv?: number
wave?: number
unique?: boolean
} = {}
): CardConfig[] => {
const count = Math.max(0, Math.floor(options.count ?? 4))
let pool = CardPoolList
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
})
}
// 如果传入了回合并且是技能卡,则根据 wave 过滤
if (options.wave !== undefined) {
pool = pool.filter(card => {
if (card.type === CardType.Skill) {
return card.wave === options.wave;
}
return true;
})
}
const picked = pickCards(pool, count, options.unique)
return picked
}