完成以下核心变更: 1. 重构英雄卡牌消耗逻辑,按等级分档定价 2. 更新现有英雄数值规则,统一永久属性与概率buff效果 3. 新增10名新英雄并完善配置 4. 补充冰冻强化技能与对应计时buff 5. 更新英雄池列表与设计文档
258 lines
9.2 KiB
TypeScript
258 lines
9.2 KiB
TypeScript
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
|
||
}
|