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.
157 lines
5.8 KiB
157 lines
5.8 KiB
|
3 weeks ago
|
import { TableNames } from '../../game/ConfigTableData';
|
||
|
|
import type { SkillPresentationContext } from './SkillPresentationTypes';
|
||
|
|
|
||
|
|
export interface AtkTypeParts {
|
||
|
|
head: number;
|
||
|
|
param: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface WuSpinSkillParts {
|
||
|
|
secPerRev: number;
|
||
|
|
stickCount: number;
|
||
|
|
volumeCells: number;
|
||
|
|
durationSec: number;
|
||
|
|
damageScale: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface WheelSkillParts {
|
||
|
|
secPerRev: number;
|
||
|
|
count: number;
|
||
|
|
volume: number;
|
||
|
|
radiusPx: number;
|
||
|
|
damageScale: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function parseAtkTypeString(raw: string): AtkTypeParts | null {
|
||
|
|
const t = String(raw ?? '').trim();
|
||
|
|
if (!t || t === '0') return null;
|
||
|
|
const parts = t.split(',').map(Number);
|
||
|
|
const head = parts[0];
|
||
|
|
if (!Number.isFinite(head)) return null;
|
||
|
|
const param = parts.length >= 2 && Number.isFinite(parts[1]) ? parts[1] : 0;
|
||
|
|
return { head, param };
|
||
|
|
}
|
||
|
|
|
||
|
|
export function readBaseAtkRow(baseAtkId: number): ITableSkill | null {
|
||
|
|
if (baseAtkId <= 0) return null;
|
||
|
|
return gg.data.table.getTableData<ITableSkill>(TableNames.Skill, baseAtkId) ?? null;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function readAtkTypeParamFromBaseAtkId(
|
||
|
|
baseAtkId: number,
|
||
|
|
expectedHead?: number,
|
||
|
|
): number | null {
|
||
|
|
const row = readBaseAtkRow(baseAtkId);
|
||
|
|
if (!row) return null;
|
||
|
|
const parsed = parseAtkTypeString(row.atk_type);
|
||
|
|
if (!parsed) return null;
|
||
|
|
if (expectedHead != null && parsed.head !== expectedHead) return null;
|
||
|
|
return parsed.param > 0 ? parsed.param : null;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function resolvePresentationAtkTypeParam(
|
||
|
|
ctx: Pick<SkillPresentationContext, 'params' | 'baseAtkId'>,
|
||
|
|
expectedHead?: number,
|
||
|
|
): number | null {
|
||
|
|
const fromParam = Number(ctx.params.atkTypeParam);
|
||
|
|
if (Number.isFinite(fromParam) && fromParam > 0) return fromParam;
|
||
|
|
return readAtkTypeParamFromBaseAtkId(ctx.baseAtkId, expectedHead);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function requirePart(parts: readonly number[], index: number): number | null {
|
||
|
|
if (index < 0 || index >= parts.length) return null;
|
||
|
|
const v = Number(parts[index]);
|
||
|
|
return Number.isFinite(v) ? v : null;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function requirePositivePart(parts: readonly number[], index: number): number | null {
|
||
|
|
const v = requirePart(parts, index);
|
||
|
|
return v != null && v > 0 ? v : null;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function requireNonNegativePart(parts: readonly number[], index: number): number | null {
|
||
|
|
const v = requirePart(parts, index);
|
||
|
|
return v != null && v >= 0 ? v : null;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function parseSkillParts(params: Readonly<Record<string, unknown>>): number[] | null {
|
||
|
|
const rawParts = params.skillParts as number[] | undefined;
|
||
|
|
if (rawParts?.length) return [...rawParts];
|
||
|
|
const raw = String(params.skillRaw ?? '').trim();
|
||
|
|
if (!raw) return null;
|
||
|
|
const parts = raw.split(',').map(Number);
|
||
|
|
return parts.some((n) => !Number.isFinite(n)) ? null : parts;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function parseWuSpinSkillParts(parts: readonly number[]): WuSpinSkillParts | null {
|
||
|
|
if ((parts[0] ?? 0) !== 1 || parts.length < 5) return null;
|
||
|
|
const secPerRev = requirePositivePart(parts, 1);
|
||
|
|
const stickCountRaw = requirePart(parts, 2);
|
||
|
|
const volumeCells = requirePositivePart(parts, 3);
|
||
|
|
const durationSec = requirePositivePart(parts, 4);
|
||
|
|
if (secPerRev == null || stickCountRaw == null || volumeCells == null || durationSec == null) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
const stickCount = Math.floor(stickCountRaw);
|
||
|
|
if (stickCount < 1) return null;
|
||
|
|
const damageScale = parts.length >= 6
|
||
|
|
? requirePositivePart(parts, 5)
|
||
|
|
: requirePositivePart(parts, parts.length - 1);
|
||
|
|
if (damageScale == null) return null;
|
||
|
|
return { secPerRev, stickCount, volumeCells, durationSec, damageScale };
|
||
|
|
}
|
||
|
|
|
||
|
|
export function parseWheelSkillParts(parts: readonly number[]): WheelSkillParts | null {
|
||
|
|
if ((parts[0] ?? 0) !== 7 || parts.length < 6) return null;
|
||
|
|
const secPerRev = requirePositivePart(parts, 1);
|
||
|
|
const countRaw = requirePart(parts, 2);
|
||
|
|
const volume = requirePositivePart(parts, 3);
|
||
|
|
const radiusPx = requirePositivePart(parts, 4);
|
||
|
|
const damageScale = requirePositivePart(parts, 5);
|
||
|
|
if (secPerRev == null || countRaw == null || volume == null || radiusPx == null || damageScale == null) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
const count = Math.floor(countRaw);
|
||
|
|
if (count < 1) return null;
|
||
|
|
return { secPerRev, count, volume, radiusPx, damageScale };
|
||
|
|
}
|
||
|
|
|
||
|
|
export function parseSkill2RadiusParts(
|
||
|
|
parts: readonly number[],
|
||
|
|
rangeAdd = 0,
|
||
|
|
): { radiusPx: number; damageScale: number } | null {
|
||
|
|
if ((parts[0] ?? 0) !== 2 || parts.length < 3) return null;
|
||
|
|
const radiusBase = requirePositivePart(parts, 1);
|
||
|
|
const damageScale = requirePositivePart(parts, 2);
|
||
|
|
if (radiusBase == null || damageScale == null) return null;
|
||
|
|
return { radiusPx: radiusBase + rangeAdd, damageScale };
|
||
|
|
}
|
||
|
|
|
||
|
|
export function parseSkill5AreaParts(parts: readonly number[]): {
|
||
|
|
radiusPx: number;
|
||
|
|
scale: number;
|
||
|
|
intervalSec: number;
|
||
|
|
durationSec: number;
|
||
|
|
} | null {
|
||
|
|
if ((parts[0] ?? 0) !== 5 || parts.length < 5) return null;
|
||
|
|
const radiusPx = requirePositivePart(parts, 1);
|
||
|
|
const scale = requirePositivePart(parts, 2);
|
||
|
|
const intervalSec = requirePositivePart(parts, 3);
|
||
|
|
const durationSec = requirePositivePart(parts, 4);
|
||
|
|
if (radiusPx == null || scale == null || intervalSec == null || durationSec == null) return null;
|
||
|
|
return { radiusPx, scale, intervalSec, durationSec };
|
||
|
|
}
|
||
|
|
|
||
|
|
export function parseSkill6BeamParts(parts: readonly number[]): {
|
||
|
|
scale: number;
|
||
|
|
intervalSec: number;
|
||
|
|
durationSec: number;
|
||
|
|
} | null {
|
||
|
|
if ((parts[0] ?? 0) !== 6 || parts.length < 4) return null;
|
||
|
|
const scale = requirePositivePart(parts, 1);
|
||
|
|
const intervalSec = requirePositivePart(parts, 2);
|
||
|
|
const durationSec = requirePositivePart(parts, 3);
|
||
|
|
if (scale == null || intervalSec == null || durationSec == null) return null;
|
||
|
|
return { scale, intervalSec, durationSec };
|
||
|
|
}
|