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 = { 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 = { 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 => { const list = Array.isArray(type) ? type : [type] return new Set(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 }