You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
AdLike2048/assets/script/manager/battleBuff/BattleBuffSelectService.ts

303 lines
11 KiB

4 days ago
import { ConstNumType } from '../../game/GameDataEnums';
import { TableNames } from '../../game/ConfigTableData';
import { WeightedPick } from '../../tools/WeightedPick';
import type { DailyShopParsed } from '../DailyShopTypes';
import { BattleBuffCarryStore } from './BattleBuffCarryStore';
/** 品质:1绿 2蓝 3紫 4橙 */
const QUALITY_COUNT = 4;
/** 每次三选一选项数 */
const OPTION_COUNT = 3;
/** 单次抽取结果的选项 */
export interface BattleBuffSelectOption {
/** Battlebuff.id */
id: number;
/** 品质 */
quality: number;
/** 表行 */
row: ITableBattlebuff;
}
/** 玩家本局三选一会话状态 */
interface BattleBuffSelectSession {
/** playerId */
playerId: number;
/** 本局已弹出次数(含当前进行中) */
popupOrdinal: number;
/** 免费刷新剩余 */
freeLeft: number;
/** 广告刷新剩余 */
adLeft: number;
/** 当前选项 */
options: BattleBuffSelectOption[];
}
/**
* 局内 Battlebuff 三选一:品质权重 → 子池权重 → 选项。
* 每玩家独立会话;开战需 reset。
*/
export class BattleBuffSelectService {
private static readonly _sessions: BattleBuffSelectSession[] = [];
/** 开战清空全部会话 */
static resetAll(): void {
this._sessions.length = 0;
}
/**
* 开战初始化某玩家刷新次数。
* @param playerId 槽位
*/
static ensurePlayer(playerId: number): void {
const pid = Math.max(0, Math.floor(Number(playerId) || 0));
if (this._find(pid)) return;
this._sessions.push({
playerId: pid,
popupOrdinal: 0,
freeLeft: Math.max(0, Math.floor(gg.data.table.getConst(ConstNumType.BattleBuffFreeRefreshCount) || 0)),
adLeft: Math.max(0, Math.floor(gg.data.table.getConst(ConstNumType.BattleBuffAdRefreshCount) || 0)),
options: [],
});
}
/**
* 开始一次弹窗(自动刷新权重),生成 3 选项。
* @param playerId 槽位
*/
static beginPopup(playerId: number): BattleBuffSelectOption[] {
this.ensurePlayer(playerId);
const session = this._find(Math.max(0, Math.floor(Number(playerId) || 0)));
if (!session) {
console.warn('[BattleBuffSelect] beginPopup 无会话', playerId);
return [];
}
session.popupOrdinal += 1;
session.options = this._rollOptions(session.playerId, session.popupOrdinal, false);
return session.options.slice();
}
/**
* 主动刷新:消耗免费或广告次数后重抽(广告权重)。
* @param playerId 槽位
* @param useAd 是否按广告次数扣减(false=免费)
*/
static refreshOptions(playerId: number, useAd: boolean): BattleBuffSelectOption[] | null {
const session = this._find(Math.max(0, Math.floor(Number(playerId) || 0)));
if (!session) {
console.warn('[BattleBuffSelect] refresh 无会话', playerId);
return null;
}
if (useAd) {
if (session.adLeft <= 0) {
console.warn('[BattleBuffSelect] 广告刷新次数已尽', playerId);
return null;
}
session.adLeft -= 1;
} else {
if (session.freeLeft <= 0) {
console.warn('[BattleBuffSelect] 免费刷新次数已尽', playerId);
return null;
}
session.freeLeft -= 1;
}
session.options = this._rollOptions(session.playerId, session.popupOrdinal, true);
return session.options.slice();
}
/**
* 当前是否应优先免费刷新(免费剩余>0)。
* @param playerId 槽位
*/
static shouldUseFreeRefresh(playerId: number): boolean {
const session = this._find(Math.max(0, Math.floor(Number(playerId) || 0)));
return !!session && session.freeLeft > 0;
}
/**
* 是否还能主动刷新。
* @param playerId 槽位
*/
static canRefresh(playerId: number): boolean {
const session = this._find(Math.max(0, Math.floor(Number(playerId) || 0)));
if (!session) return false;
return session.freeLeft > 0 || session.adLeft > 0;
}
/**
* 剩余刷新次数合计(展示用)。
* @param playerId 槽位
*/
static remainRefreshTotal(playerId: number): number {
const session = this._find(Math.max(0, Math.floor(Number(playerId) || 0)));
if (!session) return 0;
return session.freeLeft + session.adLeft;
}
/** 当前弹窗选项 */
static currentOptions(playerId: number): BattleBuffSelectOption[] {
const session = this._find(Math.max(0, Math.floor(Number(playerId) || 0)));
return session ? session.options.slice() : [];
}
/**
* 确认选择某一选项 id。
* @param playerId 槽位
* @param buffId 选项 id
*/
static confirmPick(playerId: number, buffId: number): DailyShopParsed | null {
const session = this._find(Math.max(0, Math.floor(Number(playerId) || 0)));
if (!session) {
console.warn('[BattleBuffSelect] confirm 无会话', playerId);
return null;
}
let hit = false;
for (let i = 0; i < session.options.length; i++) {
if (session.options[i].id === buffId) {
hit = true;
break;
}
}
if (!hit) {
console.warn('[BattleBuffSelect] confirm 非当前选项', playerId, buffId);
return null;
}
const parsed = BattleBuffCarryStore.appendBuffId(playerId, buffId);
session.options = [];
return parsed;
}
/**
* AI:开始弹窗并自动选中第一项(若有)。
* @param playerId AI playerId
*/
static autoPickOne(playerId: number): DailyShopParsed | null {
const options = this.beginPopup(playerId);
if (!options.length) {
console.warn('[BattleBuffSelect] AI 无可选项', playerId);
return null;
}
return this.confirmPick(playerId, options[0].id);
}
/**
10 hours ago
* @deprecated 原 Const27 解锁次数门闸;开挖宝箱每次都弹三选一,勿再调用。
* @param unlockCount 忽略
4 days ago
*/
static isTriggerUnlockCount(unlockCount: number): boolean {
10 hours ago
console.warn('[BattleBuffSelect] isTriggerUnlockCount 已废弃,开挖宝箱不再读 Const27', unlockCount);
4 days ago
return false;
}
private static _find(playerId: number): BattleBuffSelectSession | null {
for (let i = 0; i < this._sessions.length; i++) {
if (this._sessions[i].playerId === playerId) return this._sessions[i];
}
return null;
}
/**
* @param playerId 槽位
* @param popupOrdinal 弹窗序号
* @param activeRefresh true=主动刷新权重
*/
private static _rollOptions(playerId: number, popupOrdinal: number, activeRefresh: boolean): BattleBuffSelectOption[] {
const qualities = this._rollQualities(popupOrdinal, activeRefresh);
const pickedIds: number[] = [];
const out: BattleBuffSelectOption[] = [];
for (let i = 0; i < qualities.length; i++) {
const q = qualities[i];
const row = this._pickRowByQuality(playerId, q, pickedIds);
if (!row) {
console.warn('[BattleBuffSelect] 品质池为空', q, 'player', playerId);
continue;
}
pickedIds.push(row.id);
out.push({ id: row.id, quality: q, row });
}
return out;
}
private static _rollQualities(popupOrdinal: number, activeRefresh: boolean): number[] {
const weights = this._qualityWeights(popupOrdinal, activeRefresh);
const out: number[] = [];
for (let i = 0; i < OPTION_COUNT; i++) {
const entries: { quality: number; weight: number }[] = [];
for (let q = 1; q <= QUALITY_COUNT; q++) {
entries.push({ quality: q, weight: weights[q - 1] });
}
const hit = WeightedPick.pick(entries);
if (!hit) {
console.warn('[BattleBuffSelect] 品质权重全 0');
out.push(1);
continue;
}
out.push(hit.quality);
}
return out;
}
private static _qualityWeights(popupOrdinal: number, activeRefresh: boolean): number[] {
const baseId = activeRefresh ? ConstNumType.BattleBuffAdWeight : ConstNumType.BattleBuffFreeWeight;
const purpleId = activeRefresh ? ConstNumType.BattleBuffAdPurpleBonus : ConstNumType.BattleBuffFreePurpleBonus;
const orangeId = activeRefresh ? ConstNumType.BattleBuffAdOrangeBonus : ConstNumType.BattleBuffFreeOrangeBonus;
const base = gg.data.table.getConstArray(baseId);
const weights = [0, 0, 0, 0];
for (let i = 0; i < QUALITY_COUNT; i++) {
weights[i] = Math.max(0, Math.floor(Number(base[i]) || 0));
}
if (base.length < QUALITY_COUNT) {
console.warn('[BattleBuffSelect] 品质基础权重不足 4 项', baseId, base);
}
this._applyQualityBonus(weights, 2, purpleId, popupOrdinal);
this._applyQualityBonus(weights, 3, orangeId, popupOrdinal);
return weights;
}
/**
* @param weights 绿蓝紫橙
* @param index 0~3
* @param bonusConstId 次数,加值
* @param popupOrdinal 弹窗序号
*/
private static _applyQualityBonus(weights: number[], index: number, bonusConstId: number, popupOrdinal: number): void {
const arr = gg.data.table.getConstArray(bonusConstId);
if (arr.length < 2) {
console.warn('[BattleBuffSelect] 品质加值配置非法', bonusConstId, arr);
return;
}
const need = Math.floor(arr[0]);
const add = Math.floor(arr[1]);
if (need <= 0) return;
if (popupOrdinal >= need) {
weights[index] = Math.max(0, weights[index] + add);
}
}
private static _pickRowByQuality(playerId: number, quality: number, excludeIds: readonly number[]): ITableBattlebuff | null {
const list = gg.data.table.getTableList<ITableBattlebuff>(TableNames.Battlebuff) ?? [];
const pool: ITableBattlebuff[] = [];
for (let i = 0; i < list.length; i++) {
const row = list[i];
if (Math.floor(Number(row.quality) || 0) !== quality) continue;
const id = Math.floor(Number(row.id) || 0);
if (id <= 0) continue;
let excluded = false;
for (let e = 0; e < excludeIds.length; e++) {
if (excludeIds[e] === id) {
excluded = true;
break;
}
}
if (excluded) continue;
const limit = Math.floor(Number(row.limit) || 0);
if (limit > 0 && BattleBuffCarryStore.getObtainCount(playerId, id) >= limit) continue;
const w = Number(row.weight) || 0;
if (w <= 0) continue;
pool.push(row);
}
if (!pool.length) return null;
return WeightedPick.pick(pool);
}
}