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
File renamed without changes.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
package-lock.json
plugin.js
src/new
File renamed without changes.
14 changes: 14 additions & 0 deletions ccmod.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"id": "timer",
"version": "3.0.0",
"title": "CCTimer",
"description": "A speedrun timer for CrossCode.",
"icons": {
"24": "icon.png"
},
"plugin": "plugin.js",
"dependencies": {
"ccloader": "^2.19.0",
"Simplify": "^2.9.0"
}
}
Binary file added icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "timer",
"plugin": "plugin.js",
"homepage": "https://github.com/CCDirectLink/CCTimer",
"description": "A speedrun timer for CrossCode.",
"scripts": {
"start": "esbuild --target=es2018 --format=esm --platform=node --bundle --sourcemap=inline --outfile=plugin.js src/plugin.js",
"watch": "esbuild --target=es2018 --format=esm --platform=node --bundle --sourcemap=inline --watch --outfile=plugin.js src/plugin.js"
},
"version": "2.6.0",
"ccmodDependencies": {
"ccloader": "^2.19.0",
"Simplify": "^2.9.0"
},
"devDependencies": {
"esbuild": "^0.19.1",
"ws": "^8.13.0"
}
}
File renamed without changes.
File renamed without changes.
9 changes: 5 additions & 4 deletions timer/connectionManager.js → src/connectionManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ export class ConnectionManager {
this.net = require('net');
}

connect(onconnect, onerror) {
connect(onconnect, ondisconnect) {
onconnect = onconnect || (() => {});
onerror = onerror || (() => {});
ondisconnect = ondisconnect || (() => {});

ondisconnect()

this.livesplit = this.net.connect(12346);
this.livesplit.on('connect', () => onconnect());
this.livesplit.on('disconnect', () => this.connect());
this.livesplit.on('error', () => onerror());
}

sendStart() {
Expand Down Expand Up @@ -63,4 +64,4 @@ export class ConnectionManager {
this.livesplit.write('5\n');
}
}
}
}
File renamed without changes.
File renamed without changes.
5 changes: 4 additions & 1 deletion timer/ingameDisplay.js → src/ingameDisplay.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ export class IngameDisplay {
if(!t || (!this.isRoomTimer && sc.options.get('roomTimer'))) {
return this.timer.innerHTML = '';
}
if (typeof t === 'string') {
return this.timer.innerHTML = t;
}
const hour = parseInt(t / 60 / 60);
const min = parseInt(t / 60) - hour * 60;
const sec = Math.floor((t - (min + hour * 60) * 60 ) * 1000) / 1000;
Expand All @@ -40,4 +43,4 @@ export class IngameDisplay {
this.timer.innerHTML = hour + ((min < 10) ? ':0': ':') + min + ((sec < 10) ? ':0': ':') + sec;
}
}
}
}
88 changes: 88 additions & 0 deletions src/livesplitone.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { Utils } from './utils.js';

export class LiveSplitOneServer {
constructor() {
this.ws = require('ws')

this.server = null
this.wsList = null
this.serverRunning = false
}

connect(onconnect, ondisconnect) {
onconnect = onconnect || (() => {});
ondisconnect = ondisconnect || (() => {});

ondisconnect()


this.serverRunning = true

window.addEventListener('beforeunload', () => {
if (this.serverRunning) {
this.serverRunning = false
this.server.close()
}
});

const host = 'localhost'
const port = 5000

this.server = new this.ws.Server({ host, port });
this.wsList = new Set();

this.server.on('connection', (ws) => {
onconnect()
this.wsList.add(ws);

ws.on('close', () => {
this.wsList.delete(ws);
if (this.wsList.size == 0) {
ondisconnect()
}
});
});
}

_sendCommand(name) {
if (this.wsList) {
for (const ws of Array.from(this.wsList)) {
ws.send(name);
}
}
}

sendStart() {
this._sendCommand('reset');
this._sendCommand('start')

Utils.log('Sent start');
}

/**
*
* @param {number} value
*/
sendIgt(value) {
console.log('sendIgt:', value)
this._sendCommand(`setgametime ${value}`)
}

sendSplit() {
this._sendCommand('split')

Utils.log('Sent split');
}

/**
*
* @param {boolean} paused
*/
sendPaused(paused) {
if (paused) {
this._sendCommand('pausegametime')
} else {
this._sendCommand('resumegametime')
}
}
}
64 changes: 36 additions & 28 deletions timer/plugin.js → src/plugin.js
Original file line number Diff line number Diff line change
@@ -1,53 +1,61 @@
import { IngameDisplay } from './ingameDisplay.js';
import { ConnectionManager } from './connectionManager.js';
import { LiveSplitOneServer } from './livesplitone.js';
import { Config } from './config.js';
import { EventManager } from './eventManager.js';
import { Utils } from './utils.js';
import { StateManager } from './stateManager.js';
import { RoomtimeDisplay } from './roomtimeDisplay.js';
import { Hook } from './hooks.js';

export default class CCTimer extends Plugin {
export default class CCTimer {
constructor(mod) {
super();
this.mod = mod;
this.splitsDir = 'autosplitters/';
}

main() {
const utils = this.utils = new Utils();
utils.addOptions();
utils.printEvents();

const ingameDisplay = this.ingameDisplay = new IngameDisplay(() => sc.stats.getMap('player', 'playtime'));
ingameDisplay.initialize();
if (!window.require) {
ingameDisplay.run();
return;
}
this.utils.addOptions();
this.utils.printEvents();

this.mode = sc.options.get('timerMode')

if (this.mode == sc.TIMER_MODE.Off) { return }

if (this.mode == sc.TIMER_MODE.InGame) {
this.ingameDisplay = new IngameDisplay(() => sc.stats.getMap('player', 'playtime'));

} else if (this.mode == sc.TIMER_MODE.LiveSplit) {
this.ingameDisplay = new IngameDisplay(() => this.connected ? '' : 'LiveSplit not connected');
this.connection = new ConnectionManager()

} else if (this.mode == sc.TIMER_MODE.LiveSplitOne) {
this.ingameDisplay = new IngameDisplay(() => this.connected ? '' : 'Connect at one.livesplit.org with adress ws://localhost:5000');
this.connection = new LiveSplitOneServer()
}

if (this.connection) {
this.connection.connect(() => {
this.setupLivesplit()
this.connected = true
}, () => {
this.connected = false
})
}

if (this.ingameDisplay) {
this.ingameDisplay.initialize();
this.ingameDisplay.run();
}
this.roomDisplay = new RoomtimeDisplay();
this.roomDisplay.initialize();
Hook.loadMap(() => this.roomDisplay.start());
Hook.update(() => this.roomDisplay.update());

const connection = this.connection = new ConnectionManager();
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',
};
}
this.utils = new Utils();
this.utils.addOptionsPrestart()
}

async setupLivesplit() {
Expand Down Expand Up @@ -97,4 +105,4 @@ export default class CCTimer extends Plugin {
events.start(configs);
Utils.log('[timer] Hooked events');
}
}
}
4 changes: 3 additions & 1 deletion timer/roomtimeDisplay.js → src/roomtimeDisplay.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export class RoomtimeDisplay extends IngameDisplay {
update() {
if (sc.options.get('roomTimer')) {
this._update();
} else {
this.timer.innerHTML = ''
}
}
}
}
File renamed without changes.
57 changes: 46 additions & 11 deletions timer/utils.js → src/utils.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,36 @@
import { Hook } from './hooks.js';

export class Utils {
addOptions() {
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 = {
addOptionsPrestart() {
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',
};
}
sc.TIMER_MODE = {
Off: 0,
InGame: 1,
LiveSplit: 2,
LiveSplitOne: 3,
}
sc.OPTIONS_DEFINITION.timerMode = {
cat: sc.OPTION_CATEGORY.GENERAL,
header: 'ccTimer',
hasDivider: true,
data: sc.TIMER_MODE,
init: sc.TIMER_MODE.InGame,
restart: true,
type: 'BUTTON_GROUP',
};
sc.OPTIONS_DEFINITION.dontResetTimerOnDeath = {
cat: sc.OPTION_CATEGORY.GENERAL,
header: 'ccTimer',
init: true,
restart: true,
Expand Down Expand Up @@ -42,6 +60,8 @@ export class Utils {
restart: true,
type: 'CHECKBOX',
};

/*
if (sc.options.values.dontResetTimerOnDeath == null) {
sc.options.values.dontResetTimerOnDeath = false;
}
Expand All @@ -51,6 +71,21 @@ export class Utils {
if(sc.options.values.resetOnPreset == null) {
sc.options.values.resetOnPreset = true;
}
*/
}

addOptions() {
ig.lang.labels.sc.gui.options['timerMode'] = {
name: 'Timer mode', description: 'Set the timer mode. \\c[1]Restart Required',
group: [ 'Off', 'In-Game', 'LiveSplit', 'LiveSplit One' ],
};
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';

Hook.statsSet((val, stats) => {
if(sc.options.get('dontResetTimerOnDeath')
Expand Down Expand Up @@ -108,4 +143,4 @@ Utils.log = (...args) => {
if (window.logTimer) {
console.log(...args);
}
};
};
11 changes: 0 additions & 11 deletions timer/package.json

This file was deleted.