1. 扩展CardLV枚举至LV5,将卡池等级上限提升至5级 2. 重构技能卡牌配置逻辑,按波次分组管理并自动匹配对应卡池等级 3. 整理并新增多阶段技能卡牌数据,适配更高等级游戏内容
400 lines
17 KiB
TypeScript
400 lines
17 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, 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, //药水
|
||
}
|
||
|
||
/** 卡池等级定义 */
|
||
export enum CardLV {
|
||
LV1 = 1,
|
||
LV2 = 2,
|
||
LV3 = 3,
|
||
LV4 = 4,
|
||
LV5 = 5,
|
||
}
|
||
|
||
/** 通用卡牌配置 */
|
||
export interface CardConfig {
|
||
uuid: number
|
||
type: CardType
|
||
cost: number
|
||
weight: number
|
||
kind: CKind
|
||
pool_lv: CardLV
|
||
wave?: number // 针对技能卡:仅在指定波次(wave)才能抽到
|
||
hero_lv?: number
|
||
card_lv?: number
|
||
base_pool_lv?: number
|
||
|
||
// 技能卡扩展属性
|
||
skill?: number // 关联的技能 UUID
|
||
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[] // 驻场技能 UUID 数组,表示该卡牌提供驻场属性加成
|
||
}
|
||
export const CardsUpSet: Record<number, number> = {
|
||
1: 50,
|
||
2: 100,
|
||
3: 150,
|
||
4: 200,
|
||
5: 250,
|
||
}
|
||
|
||
/**初始coin数 */
|
||
export const CardInitCoins = 4
|
||
/** 卡池升级每波减免金额 */
|
||
export const CARD_POOL_UPGRADE_DISCOUNT_PER_WAVE = 10
|
||
/** 卡池默认初始等级 */
|
||
export const CARD_POOL_INIT_LEVEL = CardLV.LV1
|
||
/** 卡池等级上限 */
|
||
export const CARD_POOL_MAX_LEVEL = CardLV.LV5
|
||
/** 英雄最高等级限制 */
|
||
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 = FightSet.BASE_COST;
|
||
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;
|
||
|
||
// 【修改开始】永远只刷 lv1 等级的英雄卡牌,不再出现某英雄的 lv2 等级卡牌
|
||
// 设置为 true 则开启该限制。保留原有代码逻辑以便后续有变直接引用。
|
||
const ONLY_SPAWN_LV1_HERO = true;
|
||
if (ONLY_SPAWN_LV1_HERO && targetHeroLv > 1) {
|
||
break;
|
||
}
|
||
// 【修改结束】
|
||
|
||
// 英雄的最高等级 是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
|
||
});
|
||
}
|
||
});
|
||
|
||
// 添加非英雄卡牌 (技能、功能卡)
|
||
const waveToPoolLv: Record<number, number> = {
|
||
1: 1,
|
||
5: 2,
|
||
10: 3,
|
||
15: 4,
|
||
20: 5
|
||
};
|
||
|
||
const SkillCardData: any[] = [
|
||
// === 1波技能 ===
|
||
{ uuid: 8301, skill: 6301, wave: 1, name: "护盾", info: "为伙伴/自己添加护盾,可抵挡3次伤害", is_inst: true, keep_waves: 15 },
|
||
{ uuid: 8302, skill: 6302, wave: 1, name: "治疗", info: "治疗伙伴/自己", is_inst: true, keep_waves: 15 },
|
||
{ uuid: 8705, skill: 0, wave: 1, name: "金币收益", info: "每回合金币收益+1", is_inst: false, keep_waves: -1, field: [7005] },
|
||
{ uuid: 8706, skill: 0, wave: 1, name: "出售强化", info: "卖出英雄金币+1", is_inst: false, keep_waves: -1, field: [7006] },
|
||
{ uuid: 8707, skill: 0, wave: 1, name: "战后恢复", info: "战斗结束生命回复量+10%", is_inst: false, keep_waves: -1, field: [7007] },
|
||
|
||
// === 5波技能 ===
|
||
{ uuid: 8303, skill: 6303, wave: 5, name: "获取金币", info: "增加一定数量的金币", is_inst: true, keep_waves: 15 },
|
||
{ uuid: 8401, skill: 6401, wave: 5, name: "攻击强化", info: "全体友方攻击力提升5点,持续1次", is_inst: true, keep_waves: 15 },
|
||
{ uuid: 8402, skill: 6402, wave: 5, name: "生命强化", info: "全体友方最大生命值提升20点,持续1次", is_inst: true, keep_waves: 15 },
|
||
{ uuid: 8403, skill: 6403, wave: 5, name: "暴击强化", info: "全体友方暴击率提升10%,持续1次", is_inst: true, keep_waves: 15 },
|
||
{ uuid: 8404, skill: 6404, wave: 5, name: "暴伤强化", info: "全体友方暴击伤害提升20%,持续1次", is_inst: true, keep_waves: 15 },
|
||
{ uuid: 8405, skill: 6405, wave: 5, name: "击晕强化", info: "全体友方击晕概率提升10%,持续1次", is_inst: true, keep_waves: 15 },
|
||
{ uuid: 8408, skill: 6408, wave: 5, name: "穿刺强化", info: "全体友方穿透概率提升20%,持续1次", is_inst: true, keep_waves: 15 },
|
||
{ uuid: 8409, skill: 6409, wave: 5, name: "风怒强化", info: "全体友方风怒次数提升1次,持续1次", is_inst: true, keep_waves: 15 },
|
||
{ uuid: 8501, skill: 6501, wave: 5, name: "复活", info: "ap 代表复活的生命值百分比", is_inst: true, keep_waves: 15 },
|
||
|
||
// === 10波技能 ===
|
||
{ uuid: 8708, skill: 0, wave: 10, name: "攻击加成", info: "英雄攻击力+10%", is_inst: false, keep_waves: -1, field: [7008] },
|
||
{ uuid: 8709, skill: 0, wave: 10, name: "击晕加成", info: "英雄击晕概率+10%", is_inst: false, keep_waves: -1, field: [7009] },
|
||
{ uuid: 8710, skill: 0, wave: 10, name: "暴击加成", info: "英雄暴击率+10%", is_inst: false, keep_waves: -1, field: [7010] },
|
||
{ uuid: 8711, skill: 0, wave: 10, name: "暴伤加成", info: "英雄暴击伤害+20%", is_inst: false, keep_waves: -1, field: [7011] },
|
||
{ uuid: 8712, skill: 0, wave: 10, name: "攻速加成", info: "英雄攻击速度+10%", is_inst: false, keep_waves: -1, field: [7012] },
|
||
{ uuid: 8713, skill: 0, wave: 10, name: "购买优惠", info: "购买卡牌费用-1金币", is_inst: false, keep_waves: -1, field: [7013] },
|
||
{ uuid: 8714, skill: 0, wave: 10, name: "刷新优惠", info: "刷新卡牌费用-1金币", is_inst: false, keep_waves: -1, field: [7014] },
|
||
{ uuid: 8716, skill: 0, wave: 10, name: "生命加成", info: "英雄最大生命+10%", is_inst: false, keep_waves: -1, field: [7016] },
|
||
{ uuid: 8717, skill: 0, wave: 10, name: "风怒加成", info: "英雄风怒概率+10%", is_inst: false, keep_waves: -1, field: [7017] },
|
||
{ uuid: 8718, skill: 0, wave: 10, name: "穿刺加成", info: "英雄穿刺概率+10%", is_inst: false, keep_waves: -1, field: [7018] },
|
||
|
||
// === 15波技能 ===
|
||
{ uuid: 8701, skill: 0, wave: 15, name: "召唤强化", info: "召唤触发技能次数+1", is_inst: false, keep_waves: -1, field: [7001] },
|
||
{ uuid: 8702, skill: 0, wave: 15, name: "死亡强化", info: "死亡触发技能次数+1", is_inst: false, keep_waves: -1, field: [7002] },
|
||
{ uuid: 8703, skill: 0, wave: 15, name: "开场强化", info: "战斗开始触发技能次数+1", is_inst: false, keep_waves: -1, field: [7003] },
|
||
{ uuid: 8704, skill: 0, wave: 15, name: "结束强化", info: "战斗结束触发技能次数+1", is_inst: false, keep_waves: -1, field: [7004] },
|
||
|
||
// === 20波技能 ===
|
||
{ uuid: 8201, skill: 6201, wave: 20, name: "雷墙", info: "召唤雷墙阻挡敌人,有概率击晕", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1 },
|
||
{ uuid: 8202, skill: 6202, wave: 20, name: "火墙", info: "召唤火墙阻挡敌人,有概率击晕", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1 },
|
||
{ uuid: 8203, skill: 6203, wave: 20, name: "飓风", info: "召唤飓风攻击敌人,有概率击晕", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1 },
|
||
{ uuid: 8204, skill: 6204, wave: 20, name: "水墙", info: "召唤水墙阻挡敌人,有概率击晕", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1 },
|
||
{ uuid: 8205, skill: 6205, wave: 20, name: "风墙", info: "召唤风墙困住敌人,有概率击晕", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1 },
|
||
{ uuid: 8206, skill: 6206, wave: 20, name: "陨石术", info: "召唤陨石范围攻击敌人,有概率击晕", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1 },
|
||
];
|
||
|
||
SkillCardData.forEach(data => {
|
||
CardPoolList.push({
|
||
uuid: data.uuid,
|
||
skill: data.skill || undefined,
|
||
type: CardType.Skill,
|
||
cost: 0,
|
||
weight: 10,
|
||
pool_lv: waveToPoolLv[data.wave] as CardLV,
|
||
wave: data.wave,
|
||
kind: CKind.Skill,
|
||
card_lv: 1,
|
||
name: data.name,
|
||
info: data.info,
|
||
is_inst: data.is_inst,
|
||
t_times: data.t_times || (data.is_inst ? 1 : 999),
|
||
t_inv: data.t_inv || 0,
|
||
keep_waves: data.keep_waves,
|
||
field: data.field
|
||
});
|
||
});
|
||
|
||
|
||
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
|
||
wave?: 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))
|
||
}
|
||
|
||
// 如果强制筛选后池子为空(比如开启了 ONLY_SPAWN_LV1_HERO 导致没有高等级英雄卡),
|
||
// 且需要抽取英雄,则兜底降级回 pool_lv 为 1 的卡池,保证系统不会卡死
|
||
if (pool.length === 0) {
|
||
pool = CardPoolList.filter(card => card.pool_lv === 1);
|
||
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) {
|
||
// 只有 wave 值严格等于当前 wave 的技能卡才会留在池中
|
||
return card.wave === options.wave;
|
||
}
|
||
return true;
|
||
})
|
||
}
|
||
|
||
const picked = pickCards(pool, count)
|
||
return picked
|
||
}
|