Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,3 +251,9 @@ Opening another preset or restarting at New Game will reset back to awaiting a `
2. Place your additional autosplitters in this directory. These should be formatted the same way `settings.json` is, but they can be named however you'd like as long as they end in `.json`.

Note: Time and State settings will be derived from the main `settings.json` autosplitter.

## (Optional) Dependencies

A **Reset Splits** hotkey is also optionally included, which can be relevant in cases where you need a `"start"` condition to fire without relying on loading a preset or starting at New Game.

To make use of this, you'll need to install [input-api](https://github.com/CCDirectLink/input-api).
73 changes: 50 additions & 23 deletions timer/eventManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,35 +18,70 @@ export class EventManager {
this._activeConfig = JSON.parse(localStorage.getItem(activeConfigKey));
this._awaitingStart = false;

Hook.onTitlescreen(() => this._cancelAwaitStart());
Hook.newGameButton(() => this._onStart('newGame'));
Hook.startPresetButton((preset, slotIndex) => this._onStart('preset', preset.slots[slotIndex].title.value));
Hook.enemyHP((name, hp) => { this._awaitingStart ? this._checkStart('damage', { type: 'damage', name, hp }) : this._check({ type: 'damage', name, hp }) });
Hook.teleport((mapName) => { this._awaitingStart ? this._checkStart('teleport', { type: 'teleport', mapName }) : this._check({ type: 'teleport', mapName }) });
Hook.varChanged(() => {this._awaitingStart ? this._checkStart('vars', { type: 'vars' }) : this._check({ type: 'vars' }) });
Hook.newGameButton(() => this._onNewGameButton());
Hook.startPresetButton((preset, slotIndex) => this._onStartPresetButton(preset, slotIndex));
Hook.update(() => this._update());
Hook.enemyHP((name, hp) => { this._check({ type: 'damage', name, hp }) });
Hook.teleport((mapName) => { this._check({ type: 'teleport', mapName }) });
Hook.varChanged(() => { this._check({ type: 'vars' }) });
window.addEventListener('unload', () => this.onunload());
}

_cancelAwaitStart() {
if(this._awaitingStart) {
this._awaitingStart = false;
console.log('[timer] Cancelled Awaiting Start Condition');
_update() {
if(ig.input.pressed('reset-splits')) {
this._resetAndAwaitStart();
}
}

_onNewGameButton() {
if(sc.options && sc.options.get('resetOnNewGame')) {
this._resetAndAwaitStart();
}

this._check({ type: 'newGame' });
}

_onStartPresetButton(preset, slotIndex) {
if(sc.options && sc.options.get('resetOnPreset')) {
this._resetAndAwaitStart();
}

this._check({ type:'preset', presetName: preset.slots[slotIndex].title.value } );
}

/**
* Called either automatically (by new game / preset if enabled) or manually via a hotkey.
* Resets all configs and tells the timer mod to await a start condition.
*/
_resetAndAwaitStart() {
this._resetConfigs();
this._activeConfig = null;
this._awaitingStart = true;
console.log(`[timer] Resetting Splits and now Awaiting a Start Condition`);
}

_resetConfigs() {
for (const config of this._configs) {
config.reset();
}
}

_checkStart(startType, action) {
/**
* While no config is selected, all events will go through _checkStart() to see if
* they match the start condition for one of the splitters.
*
* If one is found that matches, that splitter will be set as the _activeConfig, and
* splits will now go through _check() for that splitter alone.
*/
_checkStart(action) {
if(this._activeConfig || !this._awaitingStart) return;

for(const config of this._configs) {
for(const event of config.splits) {
if (event.disabled || event.type !== 'start') {
continue;
}
if(!event.on && startType != 'newGame') continue; //empty start condition must be new game
if(!event.on && action.type != 'newGame') continue; //empty start condition must be new game

if (event.on) {
const [start] = this._checkEvent(event.on, action);
Expand All @@ -71,12 +106,13 @@ export class EventManager {
}

/**
* Once an _activeConfig has been assigned, all split events are handled here.
*
* @param {{type: 'damage', name: string, hp: number} | {type: 'teleport', mapName: string}} [action]
*/
_check(action) {
if(!this._activeConfig) {
//console.log(`[timer] Error: Called _check() without active config`);
this._checkStart(action);
return;
}

Expand Down Expand Up @@ -111,14 +147,6 @@ export class EventManager {
}
}

_onStart(type, presetName) {
console.log(`[timer] Awaiting start condition for type: ${type}` + (presetName ? `, preset: ${presetName}` : ''));
this._resetConfigs();
this._activeConfig = null;
this._awaitingStart = true;
this._checkStart(type, { type: 'preset', presetName});
}

/**
*
* @param {{type: 'start' | 'loadmap' | 'eventtriggered' | 'combined' | 'damage', name?: string, once?: boolean, value?: any, conditions?: any[], below?: number, above?: number}} event
Expand Down Expand Up @@ -167,7 +195,7 @@ export class EventManager {
}

for (let i = 0; i < conds.length; i++) {
const [split, once] = this._checkEvent(conds[i]);
const [split, once] = this._checkEvent(conds[i], action);
if (!split) {
return [false, false];
}
Expand All @@ -178,7 +206,6 @@ export class EventManager {
}
}


if (conds.length === 0) {
return [true, true];
}
Expand Down
2 changes: 1 addition & 1 deletion timer/ingameDisplay.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export class IngameDisplay {
if(hour <= 0) {
this.timer.innerHTML = min + ((sec < 10) ? ':0': ':') + sec;
} else {
this.timer.innerHTML = hour + ((min < 9) ? ':0': ':') + min + ((sec < 10) ? ':0': ':') + sec;
this.timer.innerHTML = hour + ((min < 10) ? ':0': ':') + min + ((sec < 10) ? ':0': ':') + sec;
}
}
}
15 changes: 15 additions & 0 deletions timer/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,21 @@ export default class CCTimer extends Plugin {
connection.connect(() => this.setupLivesplit(), () => ingameDisplay.run());
}

prestart() {
if(versions.hasOwnProperty('input-api')) {
sc.OPTIONS_DEFINITION["keys-reset-splits"] = {
cat: sc.OPTION_CATEGORY.CONTROLS,
hasDivider: true,
header: 'ccTimer',
init: {
key1: ig.KEY.SEMICOLON,
key2: void 0
},
type: 'CONTROLS',
};
}
}

async setupLivesplit() {
Utils.log('[timer] Connected to livesplit');
Utils.log('[timer] Loading config..');
Expand Down
22 changes: 22 additions & 0 deletions timer/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ export class Utils {
ig.lang.labels.sc.gui.options['dontResetTimerOnDeath'] = {name: 'Don\'t reset timer on death', description: 'Don\'t reset timer on death. \\c[1]WARNING: This will affect the actual IGT!'};
ig.lang.labels.sc.gui.options['printEvents'] = {name: 'Print all events', description: 'Print all possible events that can be split on. Use "Log level: Default"'};
ig.lang.labels.sc.gui.options['roomTimer'] = {name: 'Display room timer', description: 'Displays a room timer'};
ig.lang.labels.sc.gui.options['resetOnNewGame'] = {name: 'Reset splits on new game', description: 'Will check for split start conditions upon starting a new file.'};
ig.lang.labels.sc.gui.options['resetOnPreset'] = {name: 'Reset splits on preset', description: 'Will check for split start conditions upon starting a preset.'};
ig.lang.labels.sc.gui.options.controls.keys['reset-splits'] = 'Reset Splits';
ig.lang.labels.sc.gui.options.headers['ccTimer'] = 'CCTimer';

sc.OPTIONS_DEFINITION.dontResetTimerOnDeath = {
cat: sc.OPTION_CATEGORY.GENERAL,
hasDivider: true,
Expand All @@ -26,9 +30,27 @@ export class Utils {
restart: true,
type: 'CHECKBOX',
};
sc.OPTIONS_DEFINITION.resetOnNewGame = {
cat: sc.OPTION_CATEGORY.GENERAL,
init: true,
restart: true,
type: 'CHECKBOX',
};
sc.OPTIONS_DEFINITION.resetOnPreset = {
cat: sc.OPTION_CATEGORY.GENERAL,
init: true,
restart: true,
type: 'CHECKBOX',
};
if (sc.options.values.dontResetTimerOnDeath == null) {
sc.options.values.dontResetTimerOnDeath = false;
}
if(sc.options.values.resetOnNewGame == null) {
sc.options.values.resetOnNewGame = true;
}
if(sc.options.values.resetOnPreset == null) {
sc.options.values.resetOnPreset = true;
}

Hook.statsSet((val, stats) => {
if(sc.options.get('dontResetTimerOnDeath')
Expand Down