Skip to content

Commit

Permalink
refactored splits to support live mode and animation can now follow l…
Browse files Browse the repository at this point in the history
…ive mode, without it canceling playback
  • Loading branch information
OliverGrack committed Feb 21, 2025
1 parent 195e419 commit ba841f8
Show file tree
Hide file tree
Showing 17 changed files with 574 additions and 371 deletions.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@
".*Class",
".*ClassName"
],
"cSpell.words": ["Arrayish"]
"cSpell.words": ["Arrayish", "inperformant"]
}
166 changes: 83 additions & 83 deletions src/lib/parser/player-data/abilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,101 +2,101 @@ import { type PlayerDataField } from './player-data';

export type AbilityType = 'ability' | 'item';
export interface AbilityOrItem {
name: string;
spriteName: string;
type: AbilityType;
name: string;
spriteName: string;
type: AbilityType;
}

export const abilitiesAndItems: Record<
PlayerDataField['name'] &
(
| `has${string}`
| 'gotLurkerKey'
| 'slySimpleKey'
| 'simpleKeys'
| 'xunFlowerBroken'
| 'salubraBlessing'
| 'ore'
),
AbilityOrItem | undefined
PlayerDataField['name'] &
(
| `has${string}`
| 'gotLurkerKey'
| 'slySimpleKey'
| 'simpleKeys'
| 'xunFlowerBroken'
| 'salubraBlessing'
| 'ore'
),
AbilityOrItem | undefined
> = {
// nail arts
hasAllNailArts: undefined,
hasNailArt: undefined,
hasCyclone: { name: 'Cyclone Slash', spriteName: 'Inv_0023_inv_whirlwind_skill', type: 'ability' },
hasDashSlash: { name: 'Dash Slash', spriteName: 'Inv_0022_inv_uppercut_skill', type: 'ability' },
hasUpwardSlash: { name: 'Great Slash', spriteName: 'Inv_0021_inv_dash_strike_skill', type: 'ability' },
// nail arts
hasAllNailArts: undefined,
hasNailArt: undefined,
hasCyclone: { name: 'Cyclone Slash', spriteName: 'Inv_0023_inv_whirlwind_skill', type: 'ability' },
hasDashSlash: { name: 'Dash Slash', spriteName: 'Inv_0022_inv_uppercut_skill', type: 'ability' },
hasUpwardSlash: { name: 'Great Slash', spriteName: 'Inv_0021_inv_dash_strike_skill', type: 'ability' },

// spells
hasSpell: undefined,
// TODO: all spells
// spells
hasSpell: undefined,
// TODO: all spells

// map
hasMarker: undefined,
hasMarker_b: undefined,
hasMarker_r: undefined,
hasMarker_w: undefined,
hasMarker_y: undefined,
hasPin: undefined,
hasPinShop: undefined,
hasPinBench: undefined,
hasPinStag: undefined,
hasPinBlackEgg: undefined,
hasPinCocoon: undefined,
hasPinDreamPlant: undefined,
hasPinGhost: undefined,
hasPinGrub: undefined,
hasPinGuardian: undefined,
hasPinSpa: undefined,
hasPinTram: undefined,
// map
hasMarker: undefined,
hasMarker_b: undefined,
hasMarker_r: undefined,
hasMarker_w: undefined,
hasMarker_y: undefined,
hasPin: undefined,
hasPinShop: undefined,
hasPinBench: undefined,
hasPinStag: undefined,
hasPinBlackEgg: undefined,
hasPinCocoon: undefined,
hasPinDreamPlant: undefined,
hasPinGhost: undefined,
hasPinGrub: undefined,
hasPinGuardian: undefined,
hasPinSpa: undefined,
hasPinTram: undefined,

// abilities
hasDash: { name: 'Mothwing Cloak', spriteName: 'Inv_0015_dash-cloak', type: 'ability' },
hasWalljump: { name: 'Mantis Claw', spriteName: 'items__0003_mantis-claw', type: 'ability' },
hasSuperDash: { name: 'Crystal Heart', spriteName: 'items__0000_crystal-heart', type: 'ability' },
hasDoubleJump: { name: 'Monarch Wings', spriteName: 'items__0002_emperor-wings', type: 'ability' },
hasAcidArmour: { name: "Isma's Tear", spriteName: 'items__0001_acid-armour', type: 'ability' },
hasShadowDash: { name: 'Shade Cloak', spriteName: 'items__0004_shade_cloak', type: 'ability' },
hasDreamNail: { name: 'Dream Nail', spriteName: 'dream_nail_0003_1', type: 'ability' },
// TODO awoken dream nail
// TODO world sense
hasDreamGate: { name: 'Dreamgate', spriteName: 'Dream_Gate_Pin_0000_3', type: 'ability' },
// abilities
hasDash: { name: 'Mothwing Cloak', spriteName: 'Inv_0015_dash-cloak', type: 'ability' },
hasWalljump: { name: 'Mantis Claw', spriteName: 'items__0003_mantis-claw', type: 'ability' },
hasSuperDash: { name: 'Crystal Heart', spriteName: 'items__0000_crystal-heart', type: 'ability' },
hasDoubleJump: { name: 'Monarch Wings', spriteName: 'items__0002_emperor-wings', type: 'ability' },
hasAcidArmour: { name: "Isma's Tear", spriteName: 'items__0001_acid-armour', type: 'ability' },
hasShadowDash: { name: 'Shade Cloak', spriteName: 'items__0004_shade_cloak', type: 'ability' },
hasDreamNail: { name: 'Dream Nail', spriteName: 'dream_nail_0003_1', type: 'ability' },
// TODO awoken dream nail
// TODO world sense
hasDreamGate: { name: 'Dreamgate', spriteName: 'Dream_Gate_Pin_0000_3', type: 'ability' },

// items https://hollowknight.wiki/w/Category:Items_(Hollow_Knight)
// keys (maybe add later)?
hasMenderKey: undefined, // i think used var
hasWaterwaysKey: undefined, // i think used var
hasSpaKey: undefined, // i think used var
gotLurkerKey: undefined, // { name: 'Simple Key (Pale Lurker)', spriteName: 'inv_item__00014_graveyard_key', type: 'item' },
slySimpleKey: undefined, // { name: 'Simple Key (Sly)', spriteName: 'inv_item__00014_graveyard_key', type: 'item' },
// items https://hollowknight.wiki/w/Category:Items_(Hollow_Knight)
// keys (maybe add later)?
hasMenderKey: undefined, // i think used var
hasWaterwaysKey: undefined, // i think used var
hasSpaKey: undefined, // i think used var
gotLurkerKey: undefined, // { name: 'Simple Key (Pale Lurker)', spriteName: 'inv_item__00014_graveyard_key', type: 'item' },
slySimpleKey: undefined, // { name: 'Simple Key (Sly)', spriteName: 'inv_item__00014_graveyard_key', type: 'item' },

simpleKeys: { name: 'Simple Key', spriteName: 'inv_item__00014_graveyard_key', type: 'item' },
hasWhiteKey: { name: 'Elegant Key', spriteName: 'Elegant_Key', type: 'item' },
hasLoveKey: { name: 'Love Key', spriteName: 'inv_Love_Key', type: 'item' },
hasSlykey: { name: "Shopkeeper's Key", spriteName: 'inv_item__0002_storeroom_key', type: 'item' },
hasCityKey: { name: 'City Crest', spriteName: 'inv_item_city_key', type: 'item' },
hasKingsBrand: { name: "King's Brand", spriteName: 'inv_white_brand', type: 'item' },
simpleKeys: { name: 'Simple Key', spriteName: 'inv_item__00014_graveyard_key', type: 'item' },
hasWhiteKey: { name: 'Elegant Key', spriteName: 'Elegant_Key', type: 'item' },
hasLoveKey: { name: 'Love Key', spriteName: 'inv_Love_Key', type: 'item' },
hasSlykey: { name: "Shopkeeper's Key", spriteName: 'inv_item__0002_storeroom_key', type: 'item' },
hasCityKey: { name: 'City Crest', spriteName: 'inv_item_city_key', type: 'item' },
hasKingsBrand: { name: "King's Brand", spriteName: 'inv_white_brand', type: 'item' },

// exploration and quest
hasTramPass: { name: 'Tram Pass', spriteName: 'inv_item__0001_tram_pass', type: 'item' },
hasLantern: { name: 'Lumafly Lantern', spriteName: 'Lumafly_Lantern0000', type: 'item' },
hasMap: undefined, // { name: 'Map', spriteName: 'inv_item__0008_jar_col_map', type: 'item' },
hasQuill: { name: 'Quill', spriteName: 'inv_item_map_quill_combined', type: 'item' },
hasJournal: { name: "Hunter's Journal", spriteName: 'Journal_Prompt', type: 'item' },
hasHuntersMark: { name: "Hunter's Mark", spriteName: 'bestiary_hunter_mark_f', type: 'item' },
// exploration and quest
hasTramPass: { name: 'Tram Pass', spriteName: 'inv_item__0001_tram_pass', type: 'item' },
hasLantern: { name: 'Lumafly Lantern', spriteName: 'Lumafly_Lantern0000', type: 'item' },
hasMap: undefined, // { name: 'Map', spriteName: 'inv_item__0008_jar_col_map', type: 'item' },
hasQuill: { name: 'Quill', spriteName: 'inv_item_map_quill_combined', type: 'item' },
hasJournal: { name: "Hunter's Journal", spriteName: 'Journal_Prompt', type: 'item' },
hasHuntersMark: { name: "Hunter's Mark", spriteName: 'bestiary_hunter_mark_f', type: 'item' },

hasXunFlower: undefined, // { name: 'Delicate Flower', spriteName: 'White_Flower_Full', type: 'item' },
xunFlowerBroken: undefined, //{ name: 'Ruined Flower', spriteName: 'White_Flower_Half', type: 'item' },
hasGodfinder: { name: 'Godtuner', spriteName: 'GG_IU_Godfinder0009_complete', type: 'item' },
salubraBlessing: { name: "Salubra's Blessing", spriteName: 'shop_blessing', type: 'item' },
hasCharm: undefined,
hasXunFlower: undefined, // { name: 'Delicate Flower', spriteName: 'White_Flower_Full', type: 'item' },
xunFlowerBroken: undefined, //{ name: 'Ruined Flower', spriteName: 'White_Flower_Half', type: 'item' },
hasGodfinder: { name: 'Godtuner', spriteName: 'GG_IU_Godfinder0009_complete', type: 'item' },
salubraBlessing: { name: "Salubra's Blessing", spriteName: 'shop_blessing', type: 'item' },
hasCharm: undefined,

// tradables
ore: { name: 'Pale Ore', spriteName: 'inv_item__0009_ore', type: 'item' },
// tradables
ore: { name: 'Pale Ore', spriteName: 'inv_item__0009_ore', type: 'item' },
};

export function isPlayerDataAbilityOrItemField(
field: PlayerDataField,
): field is PlayerDataField & { name: keyof typeof abilitiesAndItems } {
return field.name in abilitiesAndItems;
export type AbilityOrItemField = PlayerDataField & { name: keyof typeof abilitiesAndItems };

export function isPlayerDataAbilityOrItemField(field: PlayerDataField): field is AbilityOrItemField {
return field.name in abilitiesAndItems;
}
3 changes: 3 additions & 0 deletions src/lib/parser/recording-files/combine-recordings.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { scale } from '../map-data/scaling';
import { getDefaultValue, playerDataFields, type PlayerDataField } from '../player-data/player-data';
import { isVersionBefore1_4_0 } from '../recording-file-version';
import { raise } from '../util';
Expand Down Expand Up @@ -255,6 +256,7 @@ function internalCombineRecordingEvents(
event.mapDistanceToPrevious = ctx.previousPlayerPositionEventWithMapPosition.mapPosition.distanceTo(
event.mapPosition,
);
event.isJump = (event.mapDistanceToPrevious ?? 0) > scale(1.5);
}
if (event.mapPosition != null) {
ctx.previousPlayerPositionEventWithMapPosition = event;
Expand Down Expand Up @@ -367,6 +369,7 @@ export function combineRecordings(recordings: ParsedRecording[]): CombinedRecord
ctx,
// todo make not live when not live?
true,
false,
);
}

Expand Down
50 changes: 37 additions & 13 deletions src/lib/parser/recording-files/events/player-data-event.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,44 @@
import { type PlayerDataField, type PlayerDataFieldValue } from '../../player-data/player-data';
import { AbilityOrItemField, isPlayerDataAbilityOrItemField } from '../../player-data/abilities';
import {
isPlayerDataDefeatedField,
isPlayerDataKilledField,
PlayerDataDefeatedField,
PlayerDataKilledField,
type PlayerDataField,
type PlayerDataFieldValue,
} from '../../player-data/player-data';
import { type PlayerPositionEvent } from './player-position-event';
import { RecordingEventBase, type RecordingEventBaseOptions } from './recording-event-base';

export type PlayerDataEventOptions<TField extends PlayerDataField> = RecordingEventBaseOptions &
Pick<PlayerDataEvent<TField>, 'field' | 'value' | 'previousPlayerPositionEvent' | 'previousPlayerDataEventOfField'>;
Pick<PlayerDataEvent<TField>, 'field' | 'value' | 'previousPlayerPositionEvent' | 'previousPlayerDataEventOfField'>;
export class PlayerDataEvent<TField extends PlayerDataField> extends RecordingEventBase {
public previousPlayerPositionEvent: PlayerPositionEvent | null;
public previousPlayerDataEventOfField: PlayerDataEvent<TField> | null;
public field: TField;
public value: PlayerDataFieldValue<TField>;
public previousPlayerPositionEvent: PlayerPositionEvent | null;
public previousPlayerDataEventOfField: PlayerDataEvent<TField> | null;
public field: TField;
public value: PlayerDataFieldValue<TField>;

constructor(options: PlayerDataEventOptions<TField>) {
super(options);
this.previousPlayerPositionEvent = options.previousPlayerPositionEvent;
this.field = options.field;
this.value = options.value;
this.previousPlayerDataEventOfField = options.previousPlayerDataEventOfField;
}
constructor(options: PlayerDataEventOptions<TField>) {
super(options);
this.previousPlayerPositionEvent = options.previousPlayerPositionEvent;
this.field = options.field;
this.value = options.value;
this.previousPlayerDataEventOfField = options.previousPlayerDataEventOfField;
}

public isOfField<TFieldCheck extends PlayerDataField>(field: TFieldCheck): this is PlayerDataEvent<TFieldCheck> {
return (this.field as unknown) === field;
}

public isOfDefeatedField(): this is PlayerDataEvent<PlayerDataDefeatedField> {
return isPlayerDataDefeatedField(this.field);
}

public isOfKilledField(): this is PlayerDataEvent<PlayerDataKilledField> {
return isPlayerDataKilledField(this.field);
}

public isOfAbilityOrItemField(): this is PlayerDataEvent<AbilityOrItemField> {
return isPlayerDataAbilityOrItemField(this.field);
}
}
29 changes: 15 additions & 14 deletions src/lib/parser/recording-files/events/player-position-event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,22 @@ import { type SceneEvent } from './scene-event';

type PlayerPositionEventOptions = RecordingEventBaseOptions & Pick<PlayerPositionEvent, 'position' | 'sceneEvent'>;
export class PlayerPositionEvent extends RecordingEventBase {
public position: Vector2;
public sceneEvent: SceneEvent;
public previousPlayerPositionEvent: PlayerPositionEvent | null = null;
public position: Vector2;
public sceneEvent: SceneEvent;
public previousPlayerPositionEvent: PlayerPositionEvent | null = null;

public mapPosition: Vector2 | null = null;
public previousPlayerPositionEventWithMapPosition: PlayerPositionEvent | null = null;
public mapDistanceToPrevious: number | null = null;
public mapPosition: Vector2 | null = null;
public previousPlayerPositionEventWithMapPosition: PlayerPositionEvent | null = null;
public mapDistanceToPrevious: number | null = null;
public isJump: boolean = true;

constructor(options: PlayerPositionEventOptions) {
super(options);
this.position = options.position;
this.sceneEvent = options.sceneEvent;
}
constructor(options: PlayerPositionEventOptions) {
super(options);
this.position = options.position;
this.sceneEvent = options.sceneEvent;
}

calcMapPosition() {
this.mapPosition = playerPositionToMapPosition(this.position, this.sceneEvent) ?? null;
}
calcMapPosition() {
this.mapPosition = playerPositionToMapPosition(this.position, this.sceneEvent) ?? null;
}
}
1 change: 0 additions & 1 deletion src/lib/parser/recording-files/recording-file-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import {
} from './events';
import {
HeroStateEvent,
ParsedRecording,
type RecordingEvent,
RecordingFileVersionEvent,
SpellDownEvent,
Expand Down
Loading

0 comments on commit ba841f8

Please sign in to comment.