feat(effects): migrate battle effects to VFX
- add WebP frame-sequence assets for lightning, electric accent, fire, sunbeam, poison, rain, water, and rounded fog effects - replace procedural lightning rendering with VFX frame playback - align lightning impact to the click point - remove the full-screen lightning blur/flash overlay - add electric accent playback for lightning impact dispersion - keep lightning scorch marks as the persistent ground trace after the action effect - consolidate the lightning tool into a single public `Молния` action - replace procedural fire with the VFX fire implementation - remove the separate `Огонь 2` tool and keep the VFX fire under `Огонь` - keep VFX fire brightness fixed at full intensity - preserve fire as a field effect with brush placement and eraser support - replace procedural sunbeam with the Pulse Discharge VFX implementation - remove the separate `Луч света 2` tool and keep the VFX sunbeam under `Луч света` - align sunbeam impact to the click point instead of the raw frame center - remove the full-screen sunbeam flash overlay - add Dust Burst VFX playback for poison cloud - tint poison smoke green - keep the skull glyph rendered above the poison smoke - align poison smoke bottom to the click point - remove the old procedural poison cloud implementation - remove the separate `Яд 2` tool and keep the VFX poison under `Яд` - replace procedural rain strokes with medium-rain VFX frame playback - recolor rain frames to gray/light-gray so rain no longer renders black - preserve rain as a field effect with brush placement and eraser support - replace static water fill with generated animated water VFX frames - render water VFX through the existing stroke mask so water keeps the painted brush shape - preserve water as a field effect with brush placement and eraser support - replace procedural fog texture generation with Smokey Atmosphere VFX frames - generate rounded fog VFX frames with soft alpha falloff at the edges - size each fog VFX stamp to the brush diameter - remove per-stamp fog drift and rotation so fog elements stay fixed in place - keep fog animation limited to internal frame playback - preload VFX frame textures for smoother first use - cache VFX textures per Pixi instance to avoid duplicate decoding - filter expired action instances before node synchronization to stop old effects from flashing back during rapid casts - add brush-stroke erasing for field effects using the same model as scene darkness reveal - keep whole-instance erasing for action effects - update effect hit tests for the consolidated VFX action effects - update effect store pruning for renamed and consolidated action effects - update control-panel buttons for consolidated fire, sunbeam, poison, and VFX-backed field effects - update editor localization for removed and renamed effect tools - add tests for field-effect brush erasing - update eraser hit-test coverage for VFX-backed effects - update effects store lifetime pruning tests for consolidated poison - update control-panel tests for consolidated effect buttons - include the field-effect eraser test in the default test script Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import crypto from 'node:crypto';
|
||||
|
||||
import { applyFieldEffectEraserStroke } from '../../shared/fieldEffectEraser';
|
||||
import type { EffectsEvent, EffectsState, EffectToolState } from '../../shared/types';
|
||||
|
||||
function nowMs(): number {
|
||||
@@ -35,7 +36,7 @@ export class EffectsStore {
|
||||
|
||||
dispatch(event: EffectsEvent): EffectsState {
|
||||
const s = this.state;
|
||||
const next: EffectsState = applyEvent(s, event);
|
||||
const next: EffectsState = applyEvent(s, event, (prefix) => this.makeId(prefix));
|
||||
this.state = next;
|
||||
return next;
|
||||
}
|
||||
@@ -47,13 +48,18 @@ export class EffectsStore {
|
||||
const kept = this.state.instances.filter((i) => {
|
||||
// Пятно льда не истекает по таймеру (только «очистить все» или ластик в UI).
|
||||
if (i.type === 'ice' || i.type === 'shadow') return true;
|
||||
if (i.type === 'lightning' || i.type === 'sunbeam' || i.type === 'poisonCloud' || i.type === 'darkness') {
|
||||
if (
|
||||
i.type === 'lightning' ||
|
||||
i.type === 'sunbeam' ||
|
||||
i.type === 'poisonCloud' ||
|
||||
i.type === 'darkness'
|
||||
) {
|
||||
return now - i.createdAtMs < i.lifetimeMs;
|
||||
}
|
||||
if (i.type === 'scorch') {
|
||||
return now - i.createdAtMs < i.lifetimeMs;
|
||||
}
|
||||
if (i.type === 'fog' || i.type === 'water') {
|
||||
if (i.type === 'fog' || i.type === 'fire' || i.type === 'rain' || i.type === 'water') {
|
||||
if (i.lifetimeMs === null) return true;
|
||||
return now - i.createdAtMs < i.lifetimeMs;
|
||||
}
|
||||
@@ -74,7 +80,11 @@ export class EffectsStore {
|
||||
}
|
||||
}
|
||||
|
||||
function applyEvent(state: EffectsState, event: EffectsEvent): EffectsState {
|
||||
function applyEvent(
|
||||
state: EffectsState,
|
||||
event: EffectsEvent,
|
||||
makeId: (prefix: string) => string,
|
||||
): EffectsState {
|
||||
const bump = (patch: Omit<EffectsState, 'revision' | 'serverNowMs'>): EffectsState => ({
|
||||
...patch,
|
||||
revision: state.revision + 1,
|
||||
@@ -89,6 +99,11 @@ function applyEvent(state: EffectsState, event: EffectsEvent): EffectsState {
|
||||
return bump({ ...state, instances: [...state.instances, event.instance] });
|
||||
case 'instance.remove':
|
||||
return bump({ ...state, instances: state.instances.filter((i) => i.id !== event.id) });
|
||||
case 'field.erase':
|
||||
return bump({
|
||||
...state,
|
||||
instances: applyFieldEffectEraserStroke(state.instances, event.points, event.radiusN, makeId),
|
||||
});
|
||||
default: {
|
||||
// Exhaustiveness
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
|
||||
Reference in New Issue
Block a user