finally working!
parent
327b703201
commit
ac510b651e
|
|
@ -61,8 +61,11 @@ export class Game extends Frigid implements Tickable {
|
||||||
this.inventory ??= new Inventory();
|
this.inventory ??= new Inventory();
|
||||||
this.inventory.validate();
|
this.inventory.validate();
|
||||||
this.clock ??= new Time();
|
this.clock ??= new Time();
|
||||||
this.clock.thing = this;
|
this.clock.start(this);
|
||||||
this.clock.start();
|
this.pawns = [];
|
||||||
|
if(this.pawns.length === 0) {
|
||||||
|
for(let i = 0; i < 3; i ++) this.pawns.push(new Pawn());
|
||||||
|
}
|
||||||
ready(this.name);
|
ready(this.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import chalk from 'chalk';
|
||||||
import EventEmitter from 'events';
|
import EventEmitter from 'events';
|
||||||
import ipc from 'node-ipc';
|
import ipc from 'node-ipc';
|
||||||
import {
|
import {
|
||||||
|
|
@ -9,27 +10,31 @@ import {
|
||||||
} from './Constants.js';
|
} from './Constants.js';
|
||||||
|
|
||||||
let connected = false;
|
let connected = false;
|
||||||
|
const oldConsoleLog = console.log;
|
||||||
|
|
||||||
|
const patchLog = () => console.log = console.log.bind(console, chalk.cyan('[CLIENT]'));
|
||||||
|
const restoreLog = () => console.log = oldConsoleLog;
|
||||||
|
|
||||||
|
// const log = (...args: any[]) => console.log(chalk.cyan('[CLIENT]'), ...args);
|
||||||
|
|
||||||
class ProcessManagerClass extends EventEmitter {
|
class ProcessManagerClass extends EventEmitter {
|
||||||
quit() {
|
quit() {
|
||||||
if (connected) {
|
this.emit('shutdown');
|
||||||
console.log('client sending quit event')
|
process.exit(0);
|
||||||
ipc.of[name].emit(IPC_QUIT_EVENT);
|
|
||||||
process.exit(0);
|
|
||||||
} else {
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
restart() {
|
restart() {
|
||||||
|
this.emit('shutdown');
|
||||||
if (connected) {
|
if (connected) {
|
||||||
console.log('client emitting ipc restart')
|
|
||||||
ipc.of[name].emit(IPC_RESTART_EVENT);
|
ipc.of[name].emit(IPC_RESTART_EVENT);
|
||||||
process.exit(0);
|
|
||||||
} else {
|
|
||||||
console.log('eh?! not connected to tower... closing')
|
|
||||||
process.exit(0);
|
|
||||||
}
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
process.exit(0);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
get connected() {
|
||||||
|
return connected;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,11 +45,23 @@ ipc.config.appspace = IPC_CLIENT_APPSAPCE;
|
||||||
ipc.config.silent = true;
|
ipc.config.silent = true;
|
||||||
|
|
||||||
ipc.connectTo(name, () => {
|
ipc.connectTo(name, () => {
|
||||||
ipc.of[name].on('connect', () => connected = true);
|
ipc.of[name].on('connect', () => {
|
||||||
ipc.of[name].on('disconnect', () => connected = false);
|
connected = true;
|
||||||
|
patchLog();
|
||||||
|
});
|
||||||
|
ipc.of[name].on('disconnect', () => {
|
||||||
|
connected = false
|
||||||
|
restoreLog();
|
||||||
|
});
|
||||||
ipc.of[name].on(IPC_REQUEST_RESTART, () => {
|
ipc.of[name].on(IPC_REQUEST_RESTART, () => {
|
||||||
|
console.log('received restart request');
|
||||||
|
// ProcessManager.restart();
|
||||||
ProcessManager.emit('reload');
|
ProcessManager.emit('reload');
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
/////////////
|
process.on('SIGKILL', () => ProcessManager.quit());
|
||||||
|
process.on('SIGTERM', () => ProcessManager.quit());
|
||||||
|
process.on('SIGINT', () => ProcessManager.quit());
|
||||||
|
|
||||||
|
///
|
||||||
79
src/Time.ts
79
src/Time.ts
|
|
@ -18,8 +18,9 @@ const months: AbbreviatedMonthName[] = [
|
||||||
'Oct', 'Nov', 'Dec'
|
'Oct', 'Nov', 'Dec'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// TODO split ticker util and calendar util...
|
||||||
export default class Time extends Serializable {
|
export default class Time extends Serializable {
|
||||||
rate: number;
|
targetTPS: number;
|
||||||
paused = true;
|
paused = true;
|
||||||
|
|
||||||
thing: Tickable;
|
thing: Tickable;
|
||||||
|
|
@ -30,6 +31,12 @@ export default class Time extends Serializable {
|
||||||
hour: number;
|
hour: number;
|
||||||
minute: number;
|
minute: number;
|
||||||
|
|
||||||
|
ticksInSecond: number;
|
||||||
|
lastTPSCheckpoint: number;
|
||||||
|
tps: number;
|
||||||
|
|
||||||
|
_boundTick: Function;
|
||||||
|
|
||||||
constructor(timestamp: number = 0) {
|
constructor(timestamp: number = 0) {
|
||||||
super();
|
super();
|
||||||
this.minute = timestamp;
|
this.minute = timestamp;
|
||||||
|
|
@ -67,16 +74,34 @@ export default class Time extends Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
return `${this.hour}:${Math.floor(this.minute).toString().padStart(2, '0')} ${months[this.month]} ${this.day + 1}, ${this.normalizedYear}`
|
return `${
|
||||||
|
this.hour
|
||||||
|
}:${
|
||||||
|
Math.floor(this.minute).toString().padStart(2, '0')
|
||||||
|
} ${
|
||||||
|
months[this.month]
|
||||||
|
} ${
|
||||||
|
this.day + 1
|
||||||
|
}, ${
|
||||||
|
this.normalizedYear
|
||||||
|
} [${
|
||||||
|
this.tps
|
||||||
|
} / ${
|
||||||
|
this.targetTPS
|
||||||
|
}]`;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctor() {
|
ctor() {
|
||||||
this.rate = 60;
|
this.targetTPS = 2000;
|
||||||
this.minute ??= 0;
|
this.minute ??= 0;
|
||||||
this.hour ??= 0;
|
this.hour ??= 0;
|
||||||
this.day ??= 0;
|
this.day ??= 0;
|
||||||
this.month ??= 0;
|
this.month ??= 0;
|
||||||
this.year ??= 0;
|
this.year ??= 0;
|
||||||
|
this.tps ??= 0;
|
||||||
|
this.lastTPSCheckpoint = ms4();
|
||||||
|
this.ticksInSecond = 0;
|
||||||
|
this._boundTick = this.doTick.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
get second() {
|
get second() {
|
||||||
|
|
@ -112,7 +137,13 @@ export default class Time extends Serializable {
|
||||||
this.paused = true;
|
this.paused = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
resume() {
|
||||||
|
this.paused = false;
|
||||||
|
setTimeout(this.doTick.bind(this), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
start(tickable: Tickable) {
|
||||||
|
this.thing = tickable;
|
||||||
this.paused = false;
|
this.paused = false;
|
||||||
setTimeout(this.doTick.bind(this), 0);
|
setTimeout(this.doTick.bind(this), 0);
|
||||||
}
|
}
|
||||||
|
|
@ -130,8 +161,6 @@ export default class Time extends Serializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
normalize() {
|
normalize() {
|
||||||
// while(t)
|
// while(t)
|
||||||
while(this.minute >= 60) {
|
while(this.minute >= 60) {
|
||||||
|
|
@ -173,20 +202,46 @@ export default class Time extends Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
async doTick() {
|
async doTick() {
|
||||||
|
|
||||||
this.advanceTime(1);
|
this.advanceTime(1);
|
||||||
const timeout = 1000 / this.rate;
|
const timeout = 1000 / this.targetTPS;
|
||||||
const start = new Date().getTime();
|
// const start = ms4()
|
||||||
|
const start = ms4();
|
||||||
if(this.thing) {
|
if(this.thing) {
|
||||||
await this.thing.tick();
|
await this.thing.tick();
|
||||||
}
|
}
|
||||||
const elapsed = new Date().getTime() - start;
|
const end = ms4()
|
||||||
const wait = Math.max(timeout - elapsed, 0);
|
const elapsed = end - start;
|
||||||
|
const wait = timeout - elapsed;
|
||||||
|
const normalizedWait = Math.floor(Math.max(wait, 0));
|
||||||
|
|
||||||
|
// process.stdout.write(`tick took ${elapsed} waiting ${normalizedWait}\n`);
|
||||||
|
|
||||||
|
if(wait < 0) {
|
||||||
|
const ticksOver = (-wait / timeout) + 1;
|
||||||
|
console.log(chalk.yellow('Can\'t keep up! Tick took ' + ticksOver.toFixed(2) + ' ticks (' + (timeout - wait).toFixed(4) + 'ms)'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.ticksInSecond ++;
|
||||||
|
|
||||||
|
if(end > this.lastTPSCheckpoint + 1000) {
|
||||||
|
this.lastTPSCheckpoint = end;
|
||||||
|
this.tps = this.ticksInSecond;
|
||||||
|
this.ticksInSecond = 0;
|
||||||
|
}
|
||||||
|
|
||||||
if(this.paused) return;
|
if(this.paused) return;
|
||||||
setTimeout(this.doTick.bind(this), wait)
|
setTimeout(this._boundTick, normalizedWait)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface Tickable {
|
export interface Tickable {
|
||||||
tick: () => Promise<void>
|
tick: () => Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
function ms4() {
|
||||||
|
const a = process.hrtime()
|
||||||
|
return a[0]*10e2 + a[1]/1000000;
|
||||||
}
|
}
|
||||||
|
|
@ -3,6 +3,8 @@ import { parse, resolve } from 'path';
|
||||||
import walkSync from 'walk-sync';
|
import walkSync from 'walk-sync';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { APPLICATION_NAME } from './Constants.js';
|
import { APPLICATION_NAME } from './Constants.js';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
|
||||||
|
|
||||||
export function osrsNumber(x: number): string {
|
export function osrsNumber(x: number): string {
|
||||||
if(x < 10_000) return '' + x;
|
if(x < 10_000) return '' + x;
|
||||||
|
|
@ -11,7 +13,7 @@ export function osrsNumber(x: number): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadExtensions() {
|
export async function loadExtensions() {
|
||||||
console.log(APPLICATION_NAME + ': Loading extensions');
|
console.log('Loading extensions');
|
||||||
const extensionsPath = resolve(parse(fileURLToPath(import.meta.url)).dir, '../content');
|
const extensionsPath = resolve(parse(fileURLToPath(import.meta.url)).dir, '../content');
|
||||||
|
|
||||||
const extensions = walkSync(extensionsPath)
|
const extensions = walkSync(extensionsPath)
|
||||||
|
|
@ -28,6 +30,4 @@ export async function loadExtensions() {
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Setup Complete.');
|
console.log('Setup Complete.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// export function
|
|
||||||
102
src/hot-index.ts
102
src/hot-index.ts
|
|
@ -6,83 +6,85 @@ import {
|
||||||
IPC_REQUEST_RESTART
|
IPC_REQUEST_RESTART
|
||||||
} from './Constants.js';
|
} from './Constants.js';
|
||||||
import { spawn, ChildProcess } from 'child_process';
|
import { spawn, ChildProcess } from 'child_process';
|
||||||
import watch from 'watch';
|
|
||||||
import chokidar from 'chokidar';
|
import chokidar from 'chokidar';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
|
ipc.config.silent = true;
|
||||||
|
|
||||||
// ipc.config.silent = true;
|
|
||||||
|
|
||||||
const exec = 'qode' + (process.platform === "win32" ? '.cmd' : '');
|
// should be obtained from process spawn args, but whatever!
|
||||||
|
const exec = 'qode' +
|
||||||
|
(process.platform === "win32" ? '.cmd' : '');
|
||||||
const args = [
|
const args = [
|
||||||
'bin/app.bundle.cjs'
|
'bin/app.bundle.cjs'
|
||||||
]
|
];
|
||||||
|
|
||||||
ipc.serve(IPC_PATH, () => {
|
const log = console.log.bind(console, chalk.green('[TOWER]'));
|
||||||
ipc.server.on(IPC_QUIT_EVENT, async () => {
|
|
||||||
await killProcess();
|
|
||||||
ipc.server.stop();
|
|
||||||
process.exit(0);
|
|
||||||
});
|
|
||||||
ipc.server.on(IPC_RESTART_EVENT, restart)
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('started ipc tower server!')
|
|
||||||
ipc.server.start();
|
|
||||||
|
|
||||||
|
// varying state data
|
||||||
|
let connected = 0;
|
||||||
let proc: ChildProcess = null;
|
let proc: ChildProcess = null;
|
||||||
|
let restartTimer: NodeJS.Timeout = null;
|
||||||
|
|
||||||
|
function ensureAlive() {
|
||||||
|
if(proc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
function startProcess() {
|
|
||||||
proc = spawn(exec, args, {
|
proc = spawn(exec, args, {
|
||||||
stdio: 'inherit'
|
stdio: 'inherit'
|
||||||
});
|
});
|
||||||
console.log(`[${proc.pid}] ${chalk.grey(`${exec} ${args.join(' ')}`)}`);
|
proc.once('exit', () => {
|
||||||
proc.on('exit', () => {
|
|
||||||
console.log('process died');
|
|
||||||
proc = null;
|
proc = null;
|
||||||
})
|
});
|
||||||
|
log(`[${
|
||||||
|
proc.pid
|
||||||
|
}] ${
|
||||||
|
chalk.grey(`${
|
||||||
|
exec
|
||||||
|
} ${
|
||||||
|
args.join(' ')
|
||||||
|
}`)
|
||||||
|
}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function killProcess() {
|
async function ensureDead() {
|
||||||
if(proc) {
|
if(!proc) {
|
||||||
console.log('killing process...');
|
return;
|
||||||
const killedPromise = new Promise((res) => {
|
|
||||||
proc.on('exit', (code, sig) => {
|
|
||||||
res(code || sig);
|
|
||||||
})
|
|
||||||
})
|
|
||||||
proc.kill();
|
|
||||||
console.log('process died with code', await killedPromise);
|
|
||||||
proc = null;
|
|
||||||
console.log()
|
|
||||||
}
|
}
|
||||||
|
const killedPromise =
|
||||||
|
new Promise(res => proc.once('exit', res));
|
||||||
|
proc.kill(9);
|
||||||
|
await killedPromise;
|
||||||
|
proc = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function restart() {
|
async function restart() {
|
||||||
console.log('received restart event');
|
await ensureDead();
|
||||||
await killProcess();
|
ensureAlive();
|
||||||
console.log('')
|
|
||||||
startProcess();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
startProcess();
|
|
||||||
|
|
||||||
let restartTimer: NodeJS.Timeout = null;
|
|
||||||
|
|
||||||
function fileChange() {
|
function fileChange() {
|
||||||
// appendFileSync('log.log', evt + ' ' + path + '\n');
|
|
||||||
// console.log(cluster.isMaster, evt, path);
|
|
||||||
if(restartTimer) clearTimeout(restartTimer)
|
if(restartTimer) clearTimeout(restartTimer)
|
||||||
restartTimer = setTimeout(() => {
|
restartTimer = setTimeout(() => {
|
||||||
console.log('changes detected');
|
ensureAlive();
|
||||||
if(proc) {
|
ipc.server.broadcast(IPC_REQUEST_RESTART);
|
||||||
ipc.server.broadcast(IPC_REQUEST_RESTART);
|
|
||||||
} else {
|
|
||||||
startProcess();
|
|
||||||
}
|
|
||||||
restartTimer = null;
|
restartTimer = null;
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
chokidar.watch('./out').on('all', fileChange);
|
|
||||||
// watch.watchTree('./bin', fileChange);
|
|
||||||
|
|
||||||
|
// start the server, connect events
|
||||||
|
ipc.serve(IPC_PATH, () => {
|
||||||
|
ipc.server.on(IPC_QUIT_EVENT, ensureDead);
|
||||||
|
ipc.server.on(IPC_RESTART_EVENT, restart);
|
||||||
|
ipc.server.on('connect', () => connected ++);
|
||||||
|
ipc.server.on('disconnect', () => connected --);
|
||||||
|
});
|
||||||
|
ipc.server.start();
|
||||||
|
|
||||||
|
// open the process
|
||||||
|
ensureAlive();
|
||||||
|
|
||||||
|
//begin watching for files, ignore changes on boot.
|
||||||
|
chokidar.watch('./out', {
|
||||||
|
ignoreInitial: true
|
||||||
|
}).on('all', fileChange);
|
||||||
35
src/index.ts
35
src/index.ts
|
|
@ -16,25 +16,25 @@ import ansi from 'sisteransi';
|
||||||
import './../content/content.js';
|
import './../content/content.js';
|
||||||
import { loadExtensions } from './Util.js';
|
import { loadExtensions } from './Util.js';
|
||||||
import { APPLICATION_NAME } from './Constants.js';
|
import { APPLICATION_NAME } from './Constants.js';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import { ProcessManager } from './ProcessManager.js';
|
||||||
|
|
||||||
// console.clear();
|
// console.clear();
|
||||||
|
|
||||||
function gracefulShutdown() {
|
ProcessManager.on('shutdown', gracefulShutdown);
|
||||||
if (isStarted()) {
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
console.log('shutting down gracefully...');
|
|
||||||
if (Game.current) {
|
|
||||||
console.log('saving world...');
|
|
||||||
Game.current.sync();
|
|
||||||
}
|
|
||||||
console.log('exitting');
|
|
||||||
process.stdout.write(ansi.cursor.show);
|
|
||||||
|
|
||||||
process.exit(0);
|
function gracefulShutdown() {
|
||||||
|
// if (isStarted()) {
|
||||||
|
// stop();
|
||||||
|
// }
|
||||||
|
if (Game.current) {
|
||||||
|
console.log(chalk.cyan('Saving world...'));
|
||||||
|
Game.current.sync();
|
||||||
|
console.log(chalk.cyan('World Saved!'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
process.on('exit', gracefulShutdown);
|
// process.on('exit', gracefulShutdown);
|
||||||
|
|
||||||
const saveFile = process.argv[2] || 'data/world01.json';
|
const saveFile = process.argv[2] || 'data/world01.json';
|
||||||
|
|
||||||
|
|
@ -42,10 +42,11 @@ ensureDirSync(parse(saveFile).dir);
|
||||||
|
|
||||||
// loadExtensions();
|
// loadExtensions();
|
||||||
|
|
||||||
for (let seconds = 0; seconds > 0; seconds --) {
|
// TODO replace with splash screen
|
||||||
process.stdout.write('Starting ' + APPLICATION_NAME + ' in ' + seconds + '\r');
|
// for (let seconds = 0; seconds > 0; seconds --) {
|
||||||
}
|
// process.stdout.write('Starting ' + APPLICATION_NAME + ' in ' + seconds + '\r');
|
||||||
process.stdout.write('Starting ' + APPLICATION_NAME + ' in ' + 0 + '\n');
|
// }
|
||||||
|
// process.stdout.write('Starting ' + APPLICATION_NAME + ' in ' + 0 + '\n');
|
||||||
// console.clear();
|
// console.clear();
|
||||||
|
|
||||||
// TODO move render logic into game, so that the ui doesnt exist until the game does...
|
// TODO move render logic into game, so that the ui doesnt exist until the game does...
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import * as uuid from 'uuid';
|
||||||
import faker from 'faker';
|
import faker from 'faker';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { Item } from '../registries/Items.js';
|
import { Item } from '../registries/Items.js';
|
||||||
import WebSocket from 'ws';
|
import WebSocket, { EventEmitter } from 'ws';
|
||||||
import { Popup } from '@ui';
|
import { Popup } from '@ui';
|
||||||
import { inspect } from 'util'
|
import { inspect } from 'util'
|
||||||
import { Pawn } from '../Pawn.js';
|
import { Pawn } from '../Pawn.js';
|
||||||
|
|
@ -18,11 +18,11 @@ const mdns = bonjour();
|
||||||
const ID = uuid.v4();
|
const ID = uuid.v4();
|
||||||
let devices: Player[] = [];
|
let devices: Player[] = [];
|
||||||
|
|
||||||
const network = {
|
const network = new (class Network extends EventEmitter {
|
||||||
get players() {
|
get players() {
|
||||||
return devices;
|
return devices;
|
||||||
}
|
}
|
||||||
}
|
})();
|
||||||
|
|
||||||
export type GiftMessage = {
|
export type GiftMessage = {
|
||||||
pawns: string[],
|
pawns: string[],
|
||||||
|
|
@ -61,11 +61,13 @@ export async function ready(name: string) {
|
||||||
mdns.find({
|
mdns.find({
|
||||||
type: MDNS_TYPE
|
type: MDNS_TYPE
|
||||||
}, (service) => {
|
}, (service) => {
|
||||||
|
network.emit('change');
|
||||||
const p = new Player();
|
const p = new Player();
|
||||||
p.name = service.name;
|
p.name = service.name;
|
||||||
p.host = service.host;
|
p.host = service.host;
|
||||||
p.port = service.port;
|
p.port = service.port;
|
||||||
devices.push(p);
|
devices.push(p);
|
||||||
}).on("down", (service) => {
|
}).on("down", (service) => {
|
||||||
|
network.emit('change');
|
||||||
// TODO remove player from MP
|
// TODO remove player from MP
|
||||||
})
|
})
|
||||||
|
|
@ -1,18 +1,19 @@
|
||||||
import { Game } from '@game';
|
import { Game } from '@game';
|
||||||
|
import { ItemState } from '@items';
|
||||||
import {
|
import {
|
||||||
QLabel,
|
QLabel,
|
||||||
QTabWidget,
|
QTabWidget,
|
||||||
QWidget,
|
QWidget,
|
||||||
QIcon,
|
QIcon,
|
||||||
FlexLayout,
|
|
||||||
QGridLayout,
|
QGridLayout,
|
||||||
FocusPolicy,
|
FocusPolicy,
|
||||||
WidgetEventTypes,
|
|
||||||
AlignmentFlag,
|
AlignmentFlag,
|
||||||
QListView,
|
QBoxLayout,
|
||||||
QListWidget,
|
Direction,
|
||||||
QListWidgetItem
|
QScrollArea,
|
||||||
} from '@nodegui/nodegui';
|
} from '@nodegui/nodegui';
|
||||||
|
import network from '../multiplayer/mDNS.js';
|
||||||
|
import { Player } from '../multiplayer/Player.js';
|
||||||
import { Pawn } from '../Pawn.js';
|
import { Pawn } from '../Pawn.js';
|
||||||
import { View } from './View.js';
|
import { View } from './View.js';
|
||||||
|
|
||||||
|
|
@ -33,9 +34,6 @@ export class GameView extends View {
|
||||||
|
|
||||||
this.title.setText(this.game.name);
|
this.title.setText(this.game.name);
|
||||||
|
|
||||||
this.left.setInlineStyle('border: 1px solid white;')
|
|
||||||
this.right.setInlineStyle('border: 1px solid white;')
|
|
||||||
|
|
||||||
this.layout.addWidget(this.title, 0, 0);
|
this.layout.addWidget(this.title, 0, 0);
|
||||||
this.layout.addWidget(this.timeLabel, 0, 1);
|
this.layout.addWidget(this.timeLabel, 0, 1);
|
||||||
this.layout.addWidget(this.left, 1, 0);
|
this.layout.addWidget(this.left, 1, 0);
|
||||||
|
|
@ -46,7 +44,8 @@ export class GameView extends View {
|
||||||
this.layout.setColumnStretch(1, 2);
|
this.layout.setColumnStretch(1, 2);
|
||||||
|
|
||||||
this.right.addTab(new PawnPageWidget(), new QIcon(), 'Pawns');
|
this.right.addTab(new PawnPageWidget(), new QIcon(), 'Pawns');
|
||||||
this.right.addTab(new InventoryWidget(), new QIcon(), 'Inventory')
|
this.right.addTab(new InventoryPageWidget(), new QIcon(), 'Inventory');
|
||||||
|
this.right.addTab(new MultiplayerPageWidget(), new QIcon(), 'Multiplayer');
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(game: Game) {
|
constructor(game: Game) {
|
||||||
|
|
@ -55,52 +54,154 @@ export class GameView extends View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PawnWidget extends QWidget {
|
class GridItem extends QWidget {
|
||||||
constructor(pawn: Pawn) {
|
|
||||||
|
rootLayout: QGridLayout;
|
||||||
|
|
||||||
|
get layout(): QGridLayout {
|
||||||
|
return this.rootLayout;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
super();
|
super();
|
||||||
let layout: QGridLayout;
|
|
||||||
this.setLayout(layout = new QGridLayout());
|
|
||||||
// this.setInlineStyle(`
|
|
||||||
// margin-bottom: 4px;
|
|
||||||
// `);
|
|
||||||
|
|
||||||
const nameLabel = new QLabel();
|
this.rootLayout = new QGridLayout()
|
||||||
nameLabel.setText(pawn.name.first + ' ' + pawn.name.last);
|
this.setLayout(this.rootLayout);
|
||||||
nameLabel.setAlignment(AlignmentFlag.AlignLeft | AlignmentFlag.AlignTop);
|
this.setInlineStyle(`
|
||||||
|
width: \'100%\';
|
||||||
const activityLabel = new QLabel();
|
background: coral;
|
||||||
activityLabel.setText(pawn.status);
|
margin: 0px;
|
||||||
activityLabel.setAlignment(AlignmentFlag.AlignRight | AlignmentFlag.AlignTop);
|
padding: 0px;
|
||||||
|
`);
|
||||||
this.layout.addWidget(nameLabel, 0, 0, 1, 1);
|
|
||||||
this.layout.addWidget(activityLabel, 0, 1, 1, 1);
|
this.setFocusPolicy(FocusPolicy.ClickFocus);
|
||||||
layout.setColumnStretch(0, 1);
|
|
||||||
layout.setColumnStretch(1, 1);
|
|
||||||
layout.setRowStretch(0, 1);
|
|
||||||
// this.setFocusPolicy(FocusPolicy.ClickFocus);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PawnPageWidget extends QListWidget {
|
function addSplitText(layout: QGridLayout, left: string, right: string, row: number) {
|
||||||
|
layout.setSpacing(0);
|
||||||
|
|
||||||
|
const nameLabel = new QLabel();
|
||||||
|
nameLabel.setText(left);
|
||||||
|
nameLabel.setAlignment(AlignmentFlag.AlignLeft | AlignmentFlag.AlignTop);
|
||||||
|
const activityLabel = new QLabel();
|
||||||
|
activityLabel.setText(right);
|
||||||
|
activityLabel.setAlignment(AlignmentFlag.AlignRight | AlignmentFlag.AlignTop);
|
||||||
|
|
||||||
|
layout.addWidget(nameLabel, row, 0, 1, 1);
|
||||||
|
layout.addWidget(activityLabel, row, 1, 1, 1);
|
||||||
|
layout.setRowStretch(row, 1);
|
||||||
|
|
||||||
|
// in theory this is redundant, calling this
|
||||||
|
// function on the same layout multiple times...
|
||||||
|
layout.setColumnStretch(0, 1);
|
||||||
|
layout.setColumnStretch(1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
class PawnWidget extends GridItem {
|
||||||
|
constructor(pawn: Pawn) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
addSplitText(
|
||||||
|
this.layout,
|
||||||
|
pawn.name.first + ' ' + pawn.name.last,
|
||||||
|
pawn.status,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ItemWidget extends GridItem {
|
||||||
|
constructor(itemState: ItemState<unknown>) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
addSplitText(
|
||||||
|
this.layout,
|
||||||
|
itemState.name,
|
||||||
|
'' + (itemState.qty),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class ScrollPanel extends QScrollArea {
|
||||||
|
centralWidget: QWidget;
|
||||||
|
vLayout: QBoxLayout;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
// this.setLayout(new FlexLayout());
|
this.setInlineStyle(`
|
||||||
this.setInlineStyle('background: purple;');
|
background: rgba(0, 0, 0, 0);
|
||||||
// this.layout.addWidget(new PawnWidget(Game.current.pawns[0]));
|
border: none;
|
||||||
|
`)
|
||||||
|
this.centralWidget = new QWidget();
|
||||||
|
this.centralWidget.setInlineStyle(`
|
||||||
|
background: rgba(0, 0, 0, 0);
|
||||||
|
`)
|
||||||
|
// this.setVerticalScrollBarPolicy(ScrollBarPolicy.ScrollBarAlwaysOn);
|
||||||
|
this.setWidgetResizable(true);
|
||||||
|
this.setWidget(this.centralWidget);
|
||||||
|
this.vLayout = new QBoxLayout(Direction.TopToBottom);
|
||||||
|
this.centralWidget.setLayout(this.vLayout);
|
||||||
|
|
||||||
|
this.fill();
|
||||||
|
|
||||||
|
this.vLayout.addStretch(1);
|
||||||
|
|
||||||
|
// for(let i = 0; i < 100; i ++) {
|
||||||
|
// const button = new QPushButton();
|
||||||
|
// button.setText('' + i);
|
||||||
|
// this.vLayout.addWidget(button);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
refill() {
|
||||||
|
for(const a of this.nodeChildren) {
|
||||||
|
console.log(a);
|
||||||
|
}
|
||||||
|
this.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
addWidget(widget: QWidget) {
|
||||||
|
this.vLayout.addWidget(widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fill(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
class PawnPageWidget extends ScrollPanel {
|
||||||
|
fill() {
|
||||||
for(const pawn of Game.current.pawns) {
|
for(const pawn of Game.current.pawns) {
|
||||||
// const label = new QLabel();
|
this.addWidget(new PawnWidget(pawn));
|
||||||
// label.setText(pawn.name.first);
|
|
||||||
// this.layout.addWidget(label);
|
|
||||||
// this.layout.addWidget(new PawnWidget(pawn));
|
|
||||||
const item = new QListWidgetItem();
|
|
||||||
this.addItem(item);
|
|
||||||
this.setItemWidget(item, new PawnWidget(pawn));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// class PawnPageWidget extends QListWidget {
|
||||||
|
// constructor() {
|
||||||
|
// super();
|
||||||
|
// for (const pawn of Game.current.pawns) {
|
||||||
|
// const pawnWidget = new PawnWidget(pawn);
|
||||||
|
// const item = new QListWidgetItem();
|
||||||
|
// this.addItem(item);
|
||||||
|
// this.setItemWidget(item, pawnWidget);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// class PawnPageWidget extends QWidget {
|
||||||
|
|
||||||
|
// constructor() {
|
||||||
|
// super();
|
||||||
|
// this.setLayout(new QBoxLayout(Direction.TopToBottom));
|
||||||
|
// // this.setLayout(new FlexLayout());
|
||||||
|
|
||||||
|
// for(const pawn of Game.current.pawns) {
|
||||||
|
// this.layout.addWidget(new PawnWidget(pawn));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
class TimeWidget extends QLabel {
|
class TimeWidget extends QLabel {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
@ -112,6 +213,38 @@ class TimeWidget extends QLabel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InventoryWidget extends QWidget {
|
|
||||||
|
|
||||||
|
|
||||||
|
class InventoryPageWidget extends ScrollPanel {
|
||||||
|
fill() {
|
||||||
|
for(const itemState of Game.current.inv.items) {
|
||||||
|
this.addWidget(new ItemWidget(itemState))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MultiplayerPlayerWidget extends GridItem {
|
||||||
|
constructor(player: Player) {
|
||||||
|
super();
|
||||||
|
addSplitText(
|
||||||
|
this.layout,
|
||||||
|
player.name,
|
||||||
|
player.host + ':' + player.port,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MultiplayerPageWidget extends ScrollPanel {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
network.on('change', () => {
|
||||||
|
this.refill();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fill(): void {
|
||||||
|
for(const player of network.players) {
|
||||||
|
this.addWidget(new MultiplayerPlayerWidget(player))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -20,10 +20,14 @@ win.setWindowTitle(APPLICATION_NAME);
|
||||||
win.show();
|
win.show();
|
||||||
(global as any).win = win;
|
(global as any).win = win;
|
||||||
win.addEventListener(WidgetEventTypes.Paint, _ => _);
|
win.addEventListener(WidgetEventTypes.Paint, _ => _);
|
||||||
win.addEventListener('customContextMenuRequested', console.log)
|
win.addEventListener('customContextMenuRequested', console.log);
|
||||||
win.addEventListener('objectNameChanged', console.log)
|
win.addEventListener('objectNameChanged', console.log);
|
||||||
win.addEventListener('windowIconChanged', console.log)
|
win.addEventListener('windowIconChanged', console.log);
|
||||||
win.addEventListener('windowTitleChanged', console.log)
|
win.addEventListener('windowTitleChanged', console.log);
|
||||||
|
win.addEventListener(
|
||||||
|
WidgetEventTypes.Close,
|
||||||
|
() => ProcessManager.quit()
|
||||||
|
);
|
||||||
|
|
||||||
setView(new LoadingView());
|
setView(new LoadingView());
|
||||||
|
|
||||||
|
|
@ -54,7 +58,6 @@ export function isStarted() {
|
||||||
|
|
||||||
ProcessManager.on('reload', () => {
|
ProcessManager.on('reload', () => {
|
||||||
RequestReloadPopup.show();
|
RequestReloadPopup.show();
|
||||||
//
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -62,6 +65,6 @@ ProcessManager.on('reload', () => {
|
||||||
function f() {
|
function f() {
|
||||||
win.repaint();
|
win.repaint();
|
||||||
win.update();
|
win.update();
|
||||||
setTimeout(f, 100);
|
setTimeout(f, 0);
|
||||||
}
|
}
|
||||||
f();
|
f();
|
||||||
|
|
@ -36,7 +36,7 @@ export class Item<Data = any> {
|
||||||
|
|
||||||
register(force = true) {
|
register(force = true) {
|
||||||
if((!this.id || !this.name) && !force) return;
|
if((!this.id || !this.name) && !force) return;
|
||||||
console.log('Added item', (this.name.singular ?? "[No Name]").padStart(20, ' '), `| (${this.id})`)
|
// console.log('Added item', (this.name.singular ?? "[No Name]").padStart(20, ' '), `| (${this.id})`)
|
||||||
items.set(this.id, this);
|
items.set(this.id, this);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
@ -63,6 +63,10 @@ export class ItemState<Data> extends Serializable {
|
||||||
return new ItemState<Data>(this.item, qty, this.data);
|
return new ItemState<Data>(this.item, qty, this.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get name() {
|
||||||
|
return this.qty === 1 ? this.item.name.singular : this.item.name.plural;
|
||||||
|
}
|
||||||
|
|
||||||
get item() {
|
get item() {
|
||||||
if(!items.has(this.itemId))
|
if(!items.has(this.itemId))
|
||||||
throw new Error('unknown item: ' + this.itemId);
|
throw new Error('unknown item: ' + this.itemId);
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ let currentTheme = backupTheme;
|
||||||
const themes: Map<ThemeName, Theme> = new Map();
|
const themes: Map<ThemeName, Theme> = new Map();
|
||||||
|
|
||||||
export function registerTheme(name: ThemeName, theme: Partial<Theme>) {
|
export function registerTheme(name: ThemeName, theme: Partial<Theme>) {
|
||||||
console.log('Registered theme', name);
|
// console.log('Registered theme', name);
|
||||||
themes.set(name, merge(backupTheme, theme));
|
themes.set(name, merge(backupTheme, theme));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ export class EscapeMenu {
|
||||||
} else if (key.full === 'enter') {
|
} else if (key.full === 'enter') {
|
||||||
switch(this.selected) {
|
switch(this.selected) {
|
||||||
case 0: {
|
case 0: {
|
||||||
Game.current.clock.start();
|
Game.current.clock.resume();
|
||||||
panels.screen.remove(this.box);
|
panels.screen.remove(this.box);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -54,7 +54,7 @@ export class EscapeMenu {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if(key.full === 'escape') {
|
} else if(key.full === 'escape') {
|
||||||
Game.current.clock.start();
|
Game.current.clock.resume();
|
||||||
panels.screen.remove(this.box);
|
panels.screen.remove(this.box);
|
||||||
}
|
}
|
||||||
this.render();
|
this.render();
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ export class GiftPopup {
|
||||||
if(key.full === 'enter') {
|
if(key.full === 'enter') {
|
||||||
this.send();
|
this.send();
|
||||||
} if(key.full === 'escape' || key.full === 'enter') {
|
} if(key.full === 'escape' || key.full === 'enter') {
|
||||||
Game.current.clock.start();
|
Game.current.clock.resume();
|
||||||
panels.screen.remove(this.box);
|
panels.screen.remove(this.box);
|
||||||
} else if (key.full === 'up') {
|
} else if (key.full === 'up') {
|
||||||
this.selected --;
|
this.selected --;
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ export class PawnDetails {
|
||||||
});
|
});
|
||||||
this.box.on('keypress', (evt: {}, key: {full: string}) => {
|
this.box.on('keypress', (evt: {}, key: {full: string}) => {
|
||||||
if(key.full === 'escape' || key.full === 'enter') {
|
if(key.full === 'escape' || key.full === 'enter') {
|
||||||
Game.current.clock.start();
|
Game.current.clock.resume();
|
||||||
panels.screen.remove(this.box);
|
panels.screen.remove(this.box);
|
||||||
} else if (key.full === 'up') {
|
} else if (key.full === 'up') {
|
||||||
// this.selected --;
|
// this.selected --;
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ export class Popup {
|
||||||
});
|
});
|
||||||
this.box.on('keypress', (evt: {}, key: {full: string}) => {
|
this.box.on('keypress', (evt: {}, key: {full: string}) => {
|
||||||
if(key.full === 'escape' || key.full === 'enter') {
|
if(key.full === 'escape' || key.full === 'enter') {
|
||||||
Game.current.clock.start();
|
Game.current.clock.resume();
|
||||||
panels.screen.remove(this.box);
|
panels.screen.remove(this.box);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ export class SelectItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
private close() {
|
private close() {
|
||||||
Game.current.clock.start();
|
Game.current.clock.resume();
|
||||||
panels.screen.remove(this.box);
|
panels.screen.remove(this.box);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,7 @@ export function start() {
|
||||||
export function stop() {
|
export function stop() {
|
||||||
screen.destroy();
|
screen.destroy();
|
||||||
// process.stdout.write('\x1b[?1049l');
|
// process.stdout.write('\x1b[?1049l');
|
||||||
|
process.stdout.write(ansi.cursor.show);
|
||||||
}
|
}
|
||||||
|
|
||||||
// move to some debugging shit, idk
|
// move to some debugging shit, idk
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue