Compare commits
No commits in common. "master" and "ui-refactor" have entirely different histories.
master
...
ui-refacto
|
|
@ -2,4 +2,3 @@ node_modules
|
||||||
out
|
out
|
||||||
data
|
data
|
||||||
*.log
|
*.log
|
||||||
bin
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"tasks": [
|
"tasks": [
|
||||||
{
|
{
|
||||||
"label": "tsc",
|
"label": "build",
|
||||||
"type": "npm",
|
"type": "npm",
|
||||||
"script": "tsc:watch",
|
"script": "compile:watch",
|
||||||
"isBackground": true,
|
"isBackground": true,
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
|
|
@ -17,76 +17,12 @@
|
||||||
"reveal": "never",
|
"reveal": "never",
|
||||||
"echo": false,
|
"echo": false,
|
||||||
"focus": false,
|
"focus": false,
|
||||||
"panel": "shared",
|
"panel": "dedicated"
|
||||||
"group": "main"
|
|
||||||
},
|
},
|
||||||
"problemMatcher": {
|
"problemMatcher": {
|
||||||
"base": "$tsc-watch",
|
"base": "$tsc-watch",
|
||||||
"applyTo": "allDocuments"
|
"applyTo": "allDocuments"
|
||||||
},
|
}
|
||||||
"dependsOn": [
|
|
||||||
"install deps"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "dev",
|
|
||||||
"type": "npm",
|
|
||||||
"script": "dev",
|
|
||||||
"isBackground": true,
|
|
||||||
"group": "build",
|
|
||||||
"runOptions": {
|
|
||||||
"runOn": "folderOpen"
|
|
||||||
},
|
|
||||||
"presentation": {
|
|
||||||
"reveal": "never",
|
|
||||||
"echo": false,
|
|
||||||
"focus": false,
|
|
||||||
"panel": "shared",
|
|
||||||
"group": "main"
|
|
||||||
},
|
|
||||||
"problemMatcher": [],
|
|
||||||
"dependsOn": [
|
|
||||||
"install deps"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "install deps",
|
|
||||||
"type": "npm",
|
|
||||||
"script": "install",
|
|
||||||
"isBackground": true,
|
|
||||||
"group": "build",
|
|
||||||
"runOptions": {
|
|
||||||
"runOn": "folderOpen"
|
|
||||||
},
|
|
||||||
"presentation": {
|
|
||||||
"reveal": "never",
|
|
||||||
"echo": false,
|
|
||||||
"focus": false,
|
|
||||||
"panel": "shared",
|
|
||||||
"group": "main"
|
|
||||||
},
|
|
||||||
"problemMatcher": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "rollup",
|
|
||||||
"type": "npm",
|
|
||||||
"script": "rollup:watch",
|
|
||||||
"isBackground": true,
|
|
||||||
"group": "build",
|
|
||||||
"runOptions": {
|
|
||||||
"runOn": "folderOpen"
|
|
||||||
},
|
|
||||||
"presentation": {
|
|
||||||
"reveal": "never",
|
|
||||||
"echo": false,
|
|
||||||
"focus": false,
|
|
||||||
"panel": "shared",
|
|
||||||
"group": "main"
|
|
||||||
},
|
|
||||||
"problemMatcher": [],
|
|
||||||
"dependsOn": [
|
|
||||||
"install deps"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
"presets": [
|
|
||||||
[
|
|
||||||
"@babel/preset-env",
|
|
||||||
{
|
|
||||||
"useBuiltIns": "entry"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
import './core/items/CoreItems.js';
|
|
||||||
import './core/tasks/CoreTasks.js';
|
|
||||||
import './core/themes/standard.js';
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { registerAction } from '@actions';
|
||||||
|
import { Game } from '@game';
|
||||||
|
|
||||||
|
registerAction('Gather Flint', (qty) => {
|
||||||
|
Game.current.board.addTask({
|
||||||
|
taskId: 'core:gather-flint',
|
||||||
|
options: {}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
registerAction('Gather Slate', (qty) => {
|
||||||
|
Game.current.board.addTask({
|
||||||
|
taskId: 'core:gather-slate',
|
||||||
|
options: {}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
registerAction('Fetch Sticks', (qty) => {
|
||||||
|
Game.current.board.addTask({
|
||||||
|
taskId: "core:fetch-sticks",
|
||||||
|
options: {}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
import { registerAction } from '@actions';
|
||||||
|
import { Game } from '@game';
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
import { registerAction } from '@actions';
|
||||||
|
import { Game } from '@game';
|
||||||
|
|
@ -1,137 +0,0 @@
|
||||||
import { Item, ItemFilter, ItemProperty, ItemState } from '@items'
|
|
||||||
|
|
||||||
class Material {
|
|
||||||
name: string;
|
|
||||||
hardness: number;
|
|
||||||
|
|
||||||
setName(name: string) {
|
|
||||||
this.name = name;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setHardness(n: number) {
|
|
||||||
this.hardness = n;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// #region properties!
|
|
||||||
export const ROCK = new ItemProperty('core:rock')
|
|
||||||
export const MATERIAL = new ItemProperty('core:material')
|
|
||||||
export const ROCK_SIZE = new ItemProperty('core:rock-size')
|
|
||||||
export const SEDIMENTARY = new ItemProperty('core:sedimentary')
|
|
||||||
export const IGNEOUS = new ItemProperty('core:igneous')
|
|
||||||
export const METAMORPHIC = new ItemProperty('core:metamorphic')
|
|
||||||
// #endregion
|
|
||||||
|
|
||||||
// #region tree-y stuff
|
|
||||||
export const LOG = new Item()
|
|
||||||
.setName("Log")
|
|
||||||
.setId('core:resources/log')
|
|
||||||
|
|
||||||
export const STICK = new Item()
|
|
||||||
.setName("Stick")
|
|
||||||
.plural('Sticks')
|
|
||||||
.setId('core:resources/stick')
|
|
||||||
|
|
||||||
export const PLANT_FIBRES = new Item()
|
|
||||||
.setName("Plant Fibres")
|
|
||||||
.setId('core:plant-fibres')
|
|
||||||
// #endregion
|
|
||||||
|
|
||||||
// #region normal ass rocks
|
|
||||||
export const FLINT_NORMAL = new Item()
|
|
||||||
.setName("Flint")
|
|
||||||
.setId('core:flint')
|
|
||||||
.setProperty(MATERIAL, new Material()
|
|
||||||
.setName('Flint')
|
|
||||||
.setHardness(7)
|
|
||||||
)
|
|
||||||
.setProperty(ROCK, true)
|
|
||||||
.setProperty(IGNEOUS, true)
|
|
||||||
|
|
||||||
export const SANDSTONE_NORMAL = new Item()
|
|
||||||
.setName("Sandstone")
|
|
||||||
.setId('core:sandstone')
|
|
||||||
|
|
||||||
export const SANDSTONE_PEBBLE = new Item()
|
|
||||||
.setName("Sandstone Pebble")
|
|
||||||
.setId('core:sandstone-pebble')
|
|
||||||
|
|
||||||
export const SLATE = new Item()
|
|
||||||
.setName("Slate")
|
|
||||||
.setId('core:slate')
|
|
||||||
|
|
||||||
export const LIMESTONE_NORMAL = new Item()
|
|
||||||
.setName("Limestone")
|
|
||||||
.setId('core:limestone')
|
|
||||||
|
|
||||||
export const LIMESTONE_PEBBLE = new Item()
|
|
||||||
.setName("Limestone Pebble")
|
|
||||||
.setId('core:limestone-pebble')
|
|
||||||
|
|
||||||
export const SHALE_NORMAL = new Item()
|
|
||||||
.setName("Shale")
|
|
||||||
.setId('core:shale')
|
|
||||||
|
|
||||||
export const SHALE_PEBBLE = new Item()
|
|
||||||
.setName("Shale Pebble")
|
|
||||||
.setId('core:shale-pebble')
|
|
||||||
|
|
||||||
export const OBSIDIAN_NORMAL = new Item()
|
|
||||||
.setName("Obsidian")
|
|
||||||
.setId('core:obsidian')
|
|
||||||
|
|
||||||
// #endregion
|
|
||||||
|
|
||||||
// #region tools n shit
|
|
||||||
|
|
||||||
export const FLINT_HATCHET = new Item()
|
|
||||||
.setName("Flint Hatchet")
|
|
||||||
.setId('core:flint-hatchet')
|
|
||||||
|
|
||||||
export const ARROWHEAD = new Item<{
|
|
||||||
baseMaterial: ItemState<any>
|
|
||||||
}>()
|
|
||||||
.setName("Flint Arrowhead")
|
|
||||||
.setId('core:flint-arrowhead')
|
|
||||||
|
|
||||||
export const FLINT_SPEAR = new Item()
|
|
||||||
.setName("Flint Spear")
|
|
||||||
.setId('core:flint-spear')
|
|
||||||
|
|
||||||
export const SLATE_HATCHET = new Item()
|
|
||||||
.setName("Slate Hatchet")
|
|
||||||
.setId('core:slate-hatchet')
|
|
||||||
|
|
||||||
export const SLATE_ARROWHEAD = new Item()
|
|
||||||
.setName("Slate Arrowhead")
|
|
||||||
.setId('core:slate-arrowhead')
|
|
||||||
|
|
||||||
export const SLATE_SPEAR = new Item()
|
|
||||||
.setName("Slate Spear")
|
|
||||||
.setId('core:slate-spear')
|
|
||||||
|
|
||||||
export const OBSIDIAN_HATCHET = new Item()
|
|
||||||
.setName("Obsidian Hatchet")
|
|
||||||
.setId('core:obsidian-hatchet')
|
|
||||||
|
|
||||||
export const OBSIDIAN_ARROWHEAD = new Item()
|
|
||||||
.setName("Obsidian Arrowhead")
|
|
||||||
.setId('core:obsidian-arrowhead')
|
|
||||||
|
|
||||||
export const OBSIDIAN_SPEAR = new Item()
|
|
||||||
.setName("Obsidian Spear")
|
|
||||||
.setId('core:obsidian-spear')
|
|
||||||
|
|
||||||
// #endregion
|
|
||||||
|
|
||||||
export function FILTER_CRAFTABLE_ROCK(itemState: ItemState<any>) {
|
|
||||||
if(!itemState.item.getProperty(MATERIAL)) return false;
|
|
||||||
const mat: Material = itemState.item.getProperty(MATERIAL) as Material;
|
|
||||||
return itemState.item.getProperty(ROCK)
|
|
||||||
&& mat.hardness >= 6
|
|
||||||
&& mat.hardness < 10
|
|
||||||
}
|
|
||||||
|
|
||||||
// tools: plant fibres = rope, flint hatchet
|
|
||||||
// shale - igneous. metamorphasis => slate
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { Item } from '@items';
|
||||||
|
|
||||||
|
export const LOG = new Item().setName("Log").setId('core:resources/log');
|
||||||
|
export const STICK = new Item().setName("Stick").setId('core:resources/stick');
|
||||||
|
export const PLANT_FIBRES = new Item().setName("Plant Fibres").setId('core:plant-fibres');
|
||||||
|
|
||||||
|
export const FLINT_NORMAL = new Item().setName("Flint").setId('core:flint');
|
||||||
|
export const FLINT_FLAKE = new Item().setName("Flint Flake").setId('core:flint-flake');
|
||||||
|
export const SANDSTONE_NORMAL = new Item().setName("Sandstone").setId('core:sandstone');
|
||||||
|
export const SANDSTONE_PEBBLE = new Item().setName("Sandstone Pebble").setId('core:sandstone-pebble');
|
||||||
|
export const SLATE_NORMAL = new Item().setName("Slate").setId('core:slate');
|
||||||
|
export const SLATE_FLAKE = new Item().setName("Slate Flake").setId('core:slate-flake');
|
||||||
|
export const LIMESTONE_NORMAL = new Item().setName("Limestone").setId('core:limestone');
|
||||||
|
export const LIMESTONE_PEBBLE = new Item().setName("Limestone Pebble").setId('core:limestone-pebble');
|
||||||
|
export const SHALE_NORMAL = new Item().setName("Shale").setId('core:shale');
|
||||||
|
export const SHALE_PEBBLE = new Item().setName("Shale Pebble").setId('core:shale-pebble');
|
||||||
|
export const OBSIDIAN_NORMAL = new Item().setName("Obsidian").setId('core:obsidian')
|
||||||
|
export const OBSIDIAN_FLAKE = new Item().setName("Obsidian Flake").setId('core:obsidian-flake');
|
||||||
|
|
||||||
|
export const FLINT_HATCHET = new Item().setName("Flint Hatchet").setId('core:flint-hatchet');
|
||||||
|
export const FLINT_ARROWHEAD = new Item().setName("Flint Arrowhead").setId('core:flint-arrowhead');
|
||||||
|
export const FLINT_SPEAR = new Item().setName("Flint Spear").setId('core:flint-spear');
|
||||||
|
export const SLATE_HATCHET = new Item().setName("Slate Hatchet").setId('core:slate-hatchet');
|
||||||
|
export const SLATE_ARROWHEAD = new Item().setName("Slate Arrowhead").setId('core:slate-arrowhead');
|
||||||
|
export const SLATE_SPEAR = new Item().setName("Slate Spear").setId('core:slate-spear');
|
||||||
|
export const OBSIDIAN_HATCHET = new Item().setName("Obsidian Hatchet").setId('core:obsidian-hatchet');
|
||||||
|
export const OBSIDIAN_ARROWHEAD = new Item().setName("Obsidian Arrowhead").setId('core:obsidian-arrowhead');
|
||||||
|
export const OBSIDIAN_SPEAR = new Item().setName("Obsidian Spear").setId('core:obsidian-spear');
|
||||||
|
|
||||||
|
// tools: plant fibres = rope, flint hatchet
|
||||||
|
// shale - igneous. metamorphasis => slate
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import { Game } from '@game';
|
||||||
|
import { registerTask, Task } from '@tasks';
|
||||||
|
import { LOG } from '../items/Items.js';
|
||||||
|
|
||||||
|
// TODO cleanup imports, use aliases
|
||||||
|
|
||||||
|
class ChopTreeTask extends Task {
|
||||||
|
work = 100;
|
||||||
|
|
||||||
|
reward() {
|
||||||
|
Game.current.inv.add(LOG, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
get title() {
|
||||||
|
return chalk.yellow('Chop Trees');
|
||||||
|
}
|
||||||
|
|
||||||
|
get status() {
|
||||||
|
return chalk.yellow('LOGGING');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
registerTask('core:chop-trees', ChopTreeTask);
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
import { Item, ItemState } from "@items";
|
|
||||||
import { Place, ResourceNode } from "@world";
|
|
||||||
// import { STICK } from "../items/CoreItems.js";
|
|
||||||
|
|
||||||
|
|
||||||
// export const GATHER_FLINT = new Task('core:gather-flint')
|
|
||||||
// .setName('Gather Flint')
|
|
||||||
// .setStatus('SCAVENGING')
|
|
||||||
// .setWork(1000)
|
|
||||||
// .setTasklistVisibility(true)
|
|
||||||
// .setCategory("work")
|
|
||||||
// .setCompletionEvent(() => {
|
|
||||||
// const qty = Math.floor(Math.random() * 5) + 1;
|
|
||||||
// Game.current.inv.add(new ItemState(FLINT_NORMAL, 1, null));
|
|
||||||
// });
|
|
||||||
|
|
||||||
// export const MAKE_ARROWHEAD = new Task<{
|
|
||||||
// baseMaterial: ItemState<any>
|
|
||||||
// }>('core:gather-slate')
|
|
||||||
// .setName('Craft Arrowhead')
|
|
||||||
// .setStatus('CRAFTING')
|
|
||||||
// .setWork(1000)
|
|
||||||
// .setTasklistVisibility(true)
|
|
||||||
// .setCategory("craft")
|
|
||||||
// .setCompletionEvent((data) => {
|
|
||||||
// const itemState = new ItemState(ARROWHEAD, 1, {
|
|
||||||
// baseMaterial: data.baseMaterial
|
|
||||||
// });
|
|
||||||
// Game.current.inv.add(itemState);
|
|
||||||
// });
|
|
||||||
|
|
||||||
|
|
||||||
export const STICK = new Item()
|
|
||||||
.setName("Stick")
|
|
||||||
.plural('Sticks')
|
|
||||||
.setId('core:resources/stick')
|
|
||||||
|
|
||||||
export const Forest = new Place()
|
|
||||||
.setName('Forest')
|
|
||||||
.setId('core:forest')
|
|
||||||
.setFrequency(1)
|
|
||||||
.setHabitable(true)
|
|
||||||
.populateResources(() => [
|
|
||||||
new ResourceNode(new ItemState(STICK, 10_000))
|
|
||||||
])
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { Task, registerTask } from "@tasks";
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import { Game } from '@game';
|
||||||
|
import { LOG, STICK } from '../items/Items.js';
|
||||||
|
|
||||||
|
class FetchSticksTask extends Task {
|
||||||
|
work = 100;
|
||||||
|
reward(): void {
|
||||||
|
Game.current.inv.add(STICK, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
get title() {
|
||||||
|
return chalk.yellow('Chop Trees');
|
||||||
|
}
|
||||||
|
|
||||||
|
get status() {
|
||||||
|
return chalk.yellow('LOGGING');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerTask('core:fetch-sticks', FetchSticksTask);
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { Task, registerTask } from "@tasks";
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import { Game } from '@game';
|
||||||
|
import { FLINT_NORMAL, SLATE_NORMAL } from '../items/Items.js';
|
||||||
|
|
||||||
|
registerTask('core:gather-flint', class GatherFlintTask extends Task {
|
||||||
|
work = 1000;
|
||||||
|
|
||||||
|
reward(): void {
|
||||||
|
Game.current.inv.add(FLINT_NORMAL, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
get title() {
|
||||||
|
return 'Gather Flint';
|
||||||
|
}
|
||||||
|
|
||||||
|
get status() {
|
||||||
|
return 'SCAVENGING';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
registerTask('core:gather-slate', class GatherSlateTask extends Task {
|
||||||
|
work = 1000;
|
||||||
|
|
||||||
|
reward(): void {
|
||||||
|
Game.current.inv.add(SLATE_NORMAL, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
get title() {
|
||||||
|
return 'Gather Slate';
|
||||||
|
}
|
||||||
|
|
||||||
|
get status() {
|
||||||
|
return 'SCAVENGING';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -4,5 +4,5 @@ import chalk from 'chalk'
|
||||||
registerTheme("default", {});
|
registerTheme("default", {});
|
||||||
|
|
||||||
registerTheme("high contrast", {
|
registerTheme("high contrast", {
|
||||||
bright: chalk.ansi256(250).inverse
|
selected: chalk.ansi256(250).inverse
|
||||||
});
|
});
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
const moduleAliases = {
|
||||||
|
"@themes": "./out/src/registries/Themes.js",
|
||||||
|
"@actions": "./out/src/registries/Actions.js",
|
||||||
|
"@tasks": "./out/src/registries/Tasks.js",
|
||||||
|
"@items": "./out/src/registries/Items.js",
|
||||||
|
"@game": "./out/src/Game.js"
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAliases = () => {
|
||||||
|
const base = process.cwd();
|
||||||
|
const aliases = moduleAliases || {};
|
||||||
|
const absoluteAliases = Object.keys(aliases).reduce((acc, key) =>
|
||||||
|
aliases[key][0] === '/'
|
||||||
|
? acc
|
||||||
|
: { ...acc, [key]: 'file:///' + path.join(base, aliases[key]) },
|
||||||
|
aliases)
|
||||||
|
return absoluteAliases;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAliasInSpecifier = (path, alias) => {
|
||||||
|
return path.indexOf(alias) === 0
|
||||||
|
&& (path.length === alias.length || path[alias.length] === '/')
|
||||||
|
}
|
||||||
|
|
||||||
|
const aliases = getAliases();
|
||||||
|
|
||||||
|
export const resolve = (specifier, parentModuleURL, defaultResolve) => {
|
||||||
|
const alias = Object.keys(aliases).find((key) => isAliasInSpecifier(specifier, key));
|
||||||
|
|
||||||
|
const newSpecifier = alias === undefined
|
||||||
|
? specifier
|
||||||
|
: path.join(aliases[alias], specifier.substr(alias.length));
|
||||||
|
|
||||||
|
return defaultResolve(newSpecifier, parentModuleURL);
|
||||||
|
}
|
||||||
38
package.json
38
package.json
|
|
@ -1,32 +1,19 @@
|
||||||
{
|
{
|
||||||
"name": "hadean",
|
"name": "df-idle",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.14.8",
|
|
||||||
"@babel/preset-env": "^7.14.8",
|
|
||||||
"@nodegui/nodegui": "^0.34.0",
|
|
||||||
"@rollup/plugin-alias": "^3.1.4",
|
|
||||||
"@rollup/plugin-babel": "^5.3.0",
|
|
||||||
"@rollup/plugin-json": "^4.1.0",
|
|
||||||
"@types/blessed": "^0.1.17",
|
"@types/blessed": "^0.1.17",
|
||||||
"@types/bonjour": "^3.5.8",
|
"@types/bonjour": "^3.5.8",
|
||||||
"@types/chai": "^4.2.19",
|
"@types/chai": "^4.2.19",
|
||||||
"@types/chokidar": "^2.1.3",
|
|
||||||
"@types/faker": "^5.5.6",
|
"@types/faker": "^5.5.6",
|
||||||
"@types/fs-extra": "^9.0.11",
|
|
||||||
"@types/mocha": "^8.2.2",
|
"@types/mocha": "^8.2.2",
|
||||||
"@types/node-ipc": "^9.1.5",
|
|
||||||
"@types/uuid": "^8.3.0",
|
"@types/uuid": "^8.3.0",
|
||||||
"@types/watch": "^1.0.2",
|
|
||||||
"@types/ws": "^7.4.5",
|
|
||||||
"@web/dev-server": "^0.1.18",
|
|
||||||
"bonjour": "^3.5.0",
|
"bonjour": "^3.5.0",
|
||||||
"chai": "^4.3.4",
|
"chai": "^4.3.4",
|
||||||
"chalk": "^4.1.1",
|
"chalk": "^4.1.1",
|
||||||
"chokidar": "^3.5.2",
|
|
||||||
"deepmerge": "^4.2.2",
|
"deepmerge": "^4.2.2",
|
||||||
"faker": "^5.5.3",
|
"faker": "^5.5.3",
|
||||||
"frigid": "^1.3.13",
|
"frigid": "^1.3.13",
|
||||||
|
|
@ -34,36 +21,21 @@
|
||||||
"get-port": "^5.1.1",
|
"get-port": "^5.1.1",
|
||||||
"mocha": "^9.0.1",
|
"mocha": "^9.0.1",
|
||||||
"module-alias": "^2.2.2",
|
"module-alias": "^2.2.2",
|
||||||
"multiview": "^3.0.1",
|
|
||||||
"neo-blessed": "^0.2.0",
|
"neo-blessed": "^0.2.0",
|
||||||
"node-dev": "^7.0.0",
|
|
||||||
"node-ipc": "^10.0.2",
|
|
||||||
"nodemon": "^2.0.7",
|
|
||||||
"printable-characters": "^1.0.42",
|
"printable-characters": "^1.0.42",
|
||||||
"rollup": "^2.53.3",
|
|
||||||
"sisteransi": "^1.0.5",
|
"sisteransi": "^1.0.5",
|
||||||
"supervisor": "^0.12.0",
|
|
||||||
"typescript": "^4.3.2",
|
"typescript": "^4.3.2",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"walk-sync": "^3.0.0",
|
"walk-sync": "^3.0.0",
|
||||||
"watch": "^1.0.2",
|
|
||||||
"ws": "^7.4.6",
|
"ws": "^7.4.6",
|
||||||
"yarn": "^1.22.10"
|
"yarn": "^1.22.10"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"compile": "yarn tsc & yarn rollup",
|
"compile:watch": "tsc --watch",
|
||||||
"start": "yarn qode bin/app.bundle.cjs",
|
"start": "node --no-warnings --loader ./lib/aliases.mjs --enable-source-maps out/src/index.js",
|
||||||
"dev": "yarn x bin/ipc-tower.bundle.cjs",
|
"dev": "supervisor -w out -n exit -t -k --exec yarn -- start",
|
||||||
"prod": "git fetch && git pull && yarn && tsc && yarn start",
|
"prod": "git fetch && git pull && yarn && tsc && yarn start",
|
||||||
"test": "mocha",
|
"test": "mocha",
|
||||||
"lint": "eslint src/**/*.ts",
|
"lint": "eslint src/**/*.ts"
|
||||||
"web": "web-dev-server --open index.html --node-resolve --watch",
|
|
||||||
"profile": "yarn qode --inspect-brk bin/app.bundle.cjs",
|
|
||||||
"qode": "qode",
|
|
||||||
"x": "node",
|
|
||||||
"tsc": "tsc",
|
|
||||||
"tsc:watch": "yarn tsc --watch",
|
|
||||||
"rollup": "rollup --config rollup.config.js",
|
|
||||||
"rollup:watch": "yarn rollup --watch"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
import alias from '@rollup/plugin-alias';
|
|
||||||
import { dirname, resolve } from 'path';
|
|
||||||
import { fileURLToPath } from 'url';
|
|
||||||
import tsconfig from './tsconfig.json';
|
|
||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
||||||
|
|
||||||
const aliases = Object.entries(tsconfig.compilerOptions.paths).map(([name, [path]]) => {
|
|
||||||
return {
|
|
||||||
find: name,
|
|
||||||
replacement: resolve(__dirname, 'out', path) + '.js'
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const shared = {
|
|
||||||
plugins: [
|
|
||||||
alias({
|
|
||||||
entries: [
|
|
||||||
...aliases,
|
|
||||||
{
|
|
||||||
find: 'frigid',
|
|
||||||
replacement: resolve(__dirname, 'node_modules/frigid/out/index.js')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
find: 'node-ipc',
|
|
||||||
replacement: resolve(__dirname, 'node_modules/node-ipc/node-ipc.js')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
find: 'event-pubsub',
|
|
||||||
replacement: resolve(__dirname, 'node_modules/event-pubsub/index.js')
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
watch: {
|
|
||||||
include: 'out/**/*'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default [
|
|
||||||
{
|
|
||||||
...shared,
|
|
||||||
input: './out/src/index.js',
|
|
||||||
output: {
|
|
||||||
file: 'bin/app.bundle.cjs',
|
|
||||||
format: 'cjs'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
...shared,
|
|
||||||
input: './out/src/hot-index.js',
|
|
||||||
output: {
|
|
||||||
file: 'bin/ipc-tower.bundle.cjs',
|
|
||||||
format: 'cjs'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
export const IPC_PATH = '/tmp/dfi.dev';
|
|
||||||
export const IPC_CLIENT_APPSAPCE = 'dfi.';
|
|
||||||
export const IPC_CLIENT_CONNECT_NAME = 'dev';
|
|
||||||
export const IPC_QUIT_EVENT = 'app.quit';
|
|
||||||
export const IPC_RESTART_EVENT = 'app.restart';
|
|
||||||
export const IPC_REQUEST_RESTART = 'app.request-restart';
|
|
||||||
|
|
||||||
export const MDNS_TYPE = 'hdn';
|
|
||||||
|
|
||||||
export const APPLICATION_NAME = 'Hadean'
|
|
||||||
36
src/Game.ts
36
src/Game.ts
|
|
@ -1,23 +1,26 @@
|
||||||
import { Frigid } from 'frigid';
|
import { Frigid, Serializable } from 'frigid';
|
||||||
|
import { DEBUG } from 'frigid/out/Serializable.js';
|
||||||
import { Pawn } from './Pawn.js';
|
import { Pawn } from './Pawn.js';
|
||||||
import { TaskList } from './TaskList.js';
|
import { TaskList } from './TaskList.js';
|
||||||
import { Inventory } from './Inventory.js';
|
import { Inventory } from './Inventory.js';
|
||||||
|
import { Menu } from './ui/Menu.js';
|
||||||
import Time, { Tickable } from './Time.js';
|
import Time, { Tickable } from './Time.js';
|
||||||
import { setTitle, start, update } from '@ui';
|
import { render, Renderable, setTitle, start } from './ui/UI.js';
|
||||||
import { ready } from './multiplayer/mDNS.js';
|
import { ready } from './multiplayer/mDNS.js';
|
||||||
import faker from 'faker';
|
import faker from 'faker';
|
||||||
import { World } from '@world';
|
|
||||||
|
|
||||||
let game: Game = null;
|
let game = null;
|
||||||
|
|
||||||
export class Game extends Frigid implements Tickable {
|
export class Game extends Frigid implements Tickable, Renderable {
|
||||||
pawns: Pawn[] = [];
|
pawns: Pawn[] = [];
|
||||||
selected: Pawn;
|
selected: Pawn;
|
||||||
inventory: Inventory;
|
inventory: Inventory;
|
||||||
board: TaskList;
|
board: TaskList;
|
||||||
|
menu: Menu;
|
||||||
clock: Time;
|
clock: Time;
|
||||||
name: string;
|
name: string;
|
||||||
world: World;
|
|
||||||
|
[DEBUG] = true;
|
||||||
|
|
||||||
static get current(): Game {
|
static get current(): Game {
|
||||||
if (!game) throw new Error('Somehow called a game before it existed?');
|
if (!game) throw new Error('Somehow called a game before it existed?');
|
||||||
|
|
@ -28,7 +31,7 @@ export class Game extends Frigid implements Tickable {
|
||||||
for(const pawn of this.pawns) {
|
for(const pawn of this.pawns) {
|
||||||
pawn.tick();
|
pawn.tick();
|
||||||
}
|
}
|
||||||
update();
|
render();
|
||||||
}
|
}
|
||||||
|
|
||||||
get inv() { return this.inventory; }
|
get inv() { return this.inventory; }
|
||||||
|
|
@ -44,7 +47,7 @@ export class Game extends Frigid implements Tickable {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
advanceSelection(number: number) {
|
advanceSelection(number) {
|
||||||
let index = this.pawns.indexOf(this.selected);
|
let index = this.pawns.indexOf(this.selected);
|
||||||
this.selected = this.pawns[Math.min(Math.max(index + number, 0), this.pawns.length - 1)];
|
this.selected = this.pawns[Math.min(Math.max(index + number, 0), this.pawns.length - 1)];
|
||||||
}
|
}
|
||||||
|
|
@ -54,22 +57,25 @@ export class Game extends Frigid implements Tickable {
|
||||||
start();
|
start();
|
||||||
this.name ??= faker.address.city();
|
this.name ??= faker.address.city();
|
||||||
setTitle(this.name);
|
setTitle(this.name);
|
||||||
this.world ??= new World();
|
|
||||||
this.pawns ??= [];
|
this.pawns ??= [];
|
||||||
this.selected ??= this.pawns[0] || null;
|
this.selected ??= this.pawns[0] || null;
|
||||||
|
this.menu = new Menu();
|
||||||
this.board ??= new TaskList();
|
this.board ??= new TaskList();
|
||||||
this.inventory ??= new Inventory();
|
this.inventory ??= new Inventory();
|
||||||
this.inventory.validate();
|
this.inventory.validate();
|
||||||
this.clock ??= new Time();
|
this.clock ??= new Time();
|
||||||
this.clock.start(this);
|
this.clock.thing = this;
|
||||||
this.pawns = [];
|
this.clock.start();
|
||||||
if(this.pawns.length === 0) {
|
|
||||||
for(let i = 0; i < 3; i ++) this.pawns.push(new Pawn());
|
|
||||||
}
|
|
||||||
ready(this.name);
|
ready(this.name);
|
||||||
|
render(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
static serializationDependencies() {
|
static serializationDependencies() {
|
||||||
return [ Pawn, Inventory, TaskList, Time, World ];
|
return [ Pawn, Inventory, TaskList, Time ];
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
this.menu.render();
|
||||||
|
this.board.render();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,17 +1,18 @@
|
||||||
import { Serializable } from 'frigid';
|
import { Serializable } from 'frigid';
|
||||||
import { Game } from './Game.js';
|
import { Game } from './Game.js';
|
||||||
import { Item, ItemState } from './registries/Items.js';
|
import { Item, ItemState } from './registries/Items.js';
|
||||||
import { Popup } from '@ui';
|
import { Popup } from './ui/Popup.js';
|
||||||
|
import { Renderable } from './ui/UI.js';
|
||||||
|
|
||||||
export class Inventory extends Serializable {
|
export class Inventory extends Serializable implements Renderable {
|
||||||
items: ItemState<any>[];
|
items: ItemState[];
|
||||||
|
|
||||||
ctor() {
|
ctor() {
|
||||||
this.items ??= [];
|
this.items ??= [];
|
||||||
}
|
}
|
||||||
|
|
||||||
validate() {
|
validate() {
|
||||||
const invalid: ItemState<any>[] = [];
|
const invalid: ItemState[] = [];
|
||||||
for(const itemState of this.items) {
|
for(const itemState of this.items) {
|
||||||
try {
|
try {
|
||||||
itemState.item;
|
itemState.item;
|
||||||
|
|
@ -31,32 +32,25 @@ export class Inventory extends Serializable {
|
||||||
return [ItemState];
|
return [ItemState];
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(itemState: ItemState<any>) {
|
remove(itemState: ItemState) {
|
||||||
this.items = this.items.filter(test => test !== itemState);
|
this.items = this.items.filter(test => test !== itemState);
|
||||||
}
|
}
|
||||||
|
|
||||||
add(itemState: ItemState<any>) {
|
add(item: Item, qty: number = 1) {
|
||||||
this.items.push(itemState);
|
const id = item.id;
|
||||||
this.reduceInv();
|
const existingArr = this.items.filter(itemState => {
|
||||||
}
|
return itemState.itemId === id;
|
||||||
|
});
|
||||||
private reduceInv() {
|
let existing: ItemState = null;
|
||||||
this.items = this.items.reduce((items, itemState) => {
|
if(existingArr.length === 1) {
|
||||||
|
existing = existingArr[0];
|
||||||
// TODO at some point, be able to merge data items?
|
}
|
||||||
|
if(existing) {
|
||||||
const existing = items.find(testItemState => {
|
existing.qty += qty;
|
||||||
return itemState.itemId === testItemState.itemId
|
} else {
|
||||||
&& itemState.data === testItemState.data;
|
this.items.push(new ItemState(item, qty, {}));
|
||||||
});
|
}
|
||||||
|
Game.current.sync();
|
||||||
if(existing) {
|
|
||||||
existing.qty += itemState.qty;
|
|
||||||
} else {
|
|
||||||
items.push(itemState);
|
|
||||||
}
|
|
||||||
return items
|
|
||||||
}, [] as ItemState<any>[])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
import { Game } from "@game";
|
|
||||||
import { Pawn } from "./Pawn.js";
|
|
||||||
|
|
||||||
export function injectTravelMemory(target: Pawn) {
|
|
||||||
return {
|
|
||||||
type: "travel",
|
|
||||||
time: {
|
|
||||||
age: target.age,
|
|
||||||
locale: Game.current.clock.toString()
|
|
||||||
},
|
|
||||||
location: Game.current.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function injectMemory(pawn: Pawn, memory: any) {
|
|
||||||
pawn.memories.push(memory);
|
|
||||||
}
|
|
||||||
|
|
@ -20,10 +20,6 @@ export type BirthMemory = ProtoMemory & {
|
||||||
location: string,
|
location: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AnyMemory =
|
|
||||||
TravelMemory
|
|
||||||
| BirthMemory;
|
|
||||||
|
|
||||||
export function stringify(memory: Memory): string {
|
export function stringify(memory: Memory): string {
|
||||||
switch(memory.type) {
|
switch(memory.type) {
|
||||||
case "birth": {
|
case "birth": {
|
||||||
|
|
|
||||||
118
src/Pawn.ts
118
src/Pawn.ts
|
|
@ -1,11 +1,18 @@
|
||||||
import { Serializable } from 'frigid';
|
import { Serializable } from 'frigid';
|
||||||
import faker from 'faker';
|
import faker from 'faker';
|
||||||
import { Task, TaskState } from './registries/Tasks.js';
|
import { Task } from './registries/Tasks.js';
|
||||||
import Time, { Tickable } from './Time.js';
|
import Time, { Tickable } from './Time.js';
|
||||||
import { Game } from './Game.js';
|
import { Game } from './Game.js';
|
||||||
// import { render, Renderable, RenderMode } from '@ui';
|
import { render } from './ui/UI.js';
|
||||||
import { Memory } from './Memory.js';
|
import { Memory } from './Memory.js';
|
||||||
import { getTheme } from '@themes';
|
import { getTheme } from './registries/Themes.js';
|
||||||
|
|
||||||
|
// const STATUS = {
|
||||||
|
// IDLE: Symbol('IDLE')
|
||||||
|
// }
|
||||||
|
|
||||||
|
const energyScale = 0.1;
|
||||||
|
const MAX_ENERGY = 100;
|
||||||
|
|
||||||
// TODO add stats getter to return % of all stats
|
// TODO add stats getter to return % of all stats
|
||||||
|
|
||||||
|
|
@ -14,18 +21,54 @@ export class Pawn extends Serializable implements Tickable {
|
||||||
first: string,
|
first: string,
|
||||||
last: string
|
last: string
|
||||||
};
|
};
|
||||||
|
job: Task;
|
||||||
|
awake: boolean;
|
||||||
sex: number;
|
sex: number;
|
||||||
age: number;
|
|
||||||
memories: Memory[];
|
|
||||||
|
|
||||||
job: TaskState<unknown, unknown>;
|
energy: number;
|
||||||
|
fun: number;
|
||||||
|
|
||||||
|
age: number;
|
||||||
|
|
||||||
|
memories: Memory[];
|
||||||
|
|
||||||
async tick() {
|
async tick() {
|
||||||
this.age ++;
|
this.age ++;
|
||||||
|
|
||||||
|
this.energy -= energyScale;
|
||||||
|
|
||||||
|
if(this.awake === false) {
|
||||||
|
this.energy += energyScale * 4;
|
||||||
|
if(this.energy >= MAX_ENERGY) {
|
||||||
|
this.awake = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(this.job) {
|
||||||
|
this.job.doWork(1, this);
|
||||||
|
this.energy -= energyScale;
|
||||||
|
if(this.job?.completed) {
|
||||||
|
this.stopWorking();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const inactive = Game.current.board.tasks.filter(task => {
|
||||||
|
return task.worker === null;
|
||||||
|
});
|
||||||
|
if(inactive.length > 0) {
|
||||||
|
const task = inactive[0];
|
||||||
|
// const task = inactive[Math.floor(Math.random() * inactive.length)];
|
||||||
|
this.assignJob(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(this.energy <= 0) {
|
||||||
|
this.stopWorking();
|
||||||
|
this.awake = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get idle() {
|
get idle() {
|
||||||
return !this.job;
|
return !this.job && this.awake;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctor() {
|
ctor() {
|
||||||
|
|
@ -37,6 +80,8 @@ export class Pawn extends Serializable implements Tickable {
|
||||||
this.sex = Math.round(Math.random());
|
this.sex = Math.round(Math.random());
|
||||||
this.name.first = faker.name.firstName(this.sex);
|
this.name.first = faker.name.firstName(this.sex);
|
||||||
}
|
}
|
||||||
|
this.awake ??= true;
|
||||||
|
this.energy ??= MAX_ENERGY;
|
||||||
this.memories ??= [];
|
this.memories ??= [];
|
||||||
if(!this.age) {
|
if(!this.age) {
|
||||||
this.age = Math.floor(525600 * (16 + Math.random() * 9));
|
this.age = Math.floor(525600 * (16 + Math.random() * 9));
|
||||||
|
|
@ -51,54 +96,41 @@ export class Pawn extends Serializable implements Tickable {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// if(this.job?.completed) {
|
if(this.job?.completed) {
|
||||||
// this.stopWorking();
|
this.stopWorking();
|
||||||
// }
|
}
|
||||||
}
|
|
||||||
|
|
||||||
taskCompleted() {
|
|
||||||
this.job = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stopWorking() {
|
stopWorking() {
|
||||||
if(this.job) {
|
if(this.job) {
|
||||||
// this.job.unclaim(this);
|
this.job.stopJob();
|
||||||
this.job = null;
|
this.job = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assignJob(task: Task) {
|
||||||
|
this.job?.stopJob()
|
||||||
|
this.job = task;
|
||||||
|
this.job.claim(this);
|
||||||
|
}
|
||||||
|
|
||||||
get status() {
|
get status() {
|
||||||
return 'SEMETHING';
|
if(this.job) {
|
||||||
// if(this.job) {
|
return this.job.status;
|
||||||
// return this.job.task.status;
|
} else {
|
||||||
// } else {
|
return this.awake ? getTheme().status.idle('IDLE') : getTheme().status.self('RESTING')
|
||||||
// return this.awake ? getTheme().status.idle('IDLE') : getTheme().status.self('RESTING')
|
}
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static serializationDependencies() {
|
static serializationDependencies() {
|
||||||
return [TaskState]
|
return [Task]
|
||||||
}
|
}
|
||||||
|
|
||||||
// toString() {
|
toString() {
|
||||||
// return this.render(RenderMode.ONELINE);
|
if(this.name) {
|
||||||
// }
|
return this.name.first + ' ' + this.name.last;
|
||||||
|
} else {
|
||||||
// render(mode: RenderMode): string {
|
return '[Object Pawn]';
|
||||||
// if(mode === RenderMode.ONELINE) {
|
}
|
||||||
// if(this.name) {
|
}
|
||||||
// return this.name.first + ' ' + this.name.last;
|
|
||||||
// } else {
|
|
||||||
// return '[Object Pawn]';
|
|
||||||
// }
|
|
||||||
// } else if (mode === RenderMode.DETAILS) {
|
|
||||||
// return `${
|
|
||||||
// this.toString()
|
|
||||||
// }{|}${
|
|
||||||
// this.status
|
|
||||||
// }\nDETAILS\nDETAILS`
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// const task =
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
import chalk from 'chalk';
|
|
||||||
import EventEmitter from 'events';
|
|
||||||
import ipc from 'node-ipc';
|
|
||||||
import {
|
|
||||||
IPC_CLIENT_CONNECT_NAME,
|
|
||||||
IPC_CLIENT_APPSAPCE,
|
|
||||||
IPC_QUIT_EVENT,
|
|
||||||
IPC_RESTART_EVENT,
|
|
||||||
IPC_REQUEST_RESTART
|
|
||||||
} from './Constants.js';
|
|
||||||
|
|
||||||
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 {
|
|
||||||
quit() {
|
|
||||||
this.emit('shutdown');
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
restart() {
|
|
||||||
this.emit('shutdown');
|
|
||||||
if (connected) {
|
|
||||||
ipc.of[name].emit(IPC_RESTART_EVENT);
|
|
||||||
}
|
|
||||||
setTimeout(() => {
|
|
||||||
process.exit(0);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
get connected() {
|
|
||||||
return connected;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ProcessManager = new ProcessManagerClass();
|
|
||||||
|
|
||||||
const name = IPC_CLIENT_CONNECT_NAME;
|
|
||||||
ipc.config.appspace = IPC_CLIENT_APPSAPCE;
|
|
||||||
ipc.config.silent = true;
|
|
||||||
|
|
||||||
ipc.connectTo(name, () => {
|
|
||||||
ipc.of[name].on('connect', () => {
|
|
||||||
connected = true;
|
|
||||||
patchLog();
|
|
||||||
});
|
|
||||||
ipc.of[name].on('disconnect', () => {
|
|
||||||
connected = false
|
|
||||||
restoreLog();
|
|
||||||
});
|
|
||||||
ipc.of[name].on(IPC_REQUEST_RESTART, () => {
|
|
||||||
console.log('received restart request');
|
|
||||||
// ProcessManager.restart();
|
|
||||||
ProcessManager.emit('reload');
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
process.on('SIGKILL', () => ProcessManager.quit());
|
|
||||||
process.on('SIGTERM', () => ProcessManager.quit());
|
|
||||||
process.on('SIGINT', () => ProcessManager.quit());
|
|
||||||
|
|
||||||
///
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import { getTheme } from "@themes";
|
import { getTheme } from "./registries/Themes.js";
|
||||||
|
|
||||||
export enum ProgressbarStyle {
|
export enum ProgressbarStyle {
|
||||||
indicator = 'indicator',
|
indicator = 'indicator',
|
||||||
|
|
|
||||||
|
|
@ -1,43 +1,54 @@
|
||||||
import { getTheme } from '@themes';
|
|
||||||
import { Serializable } from 'frigid';
|
import { Serializable } from 'frigid';
|
||||||
import { Task, TaskState } from './registries/Tasks.js';
|
import { Task, taskClasses } from './registries/Tasks.js';
|
||||||
|
import { render, Renderable, panels } from './ui/UI.js';
|
||||||
|
|
||||||
export class TaskList extends Serializable {
|
export class TaskList extends Serializable implements Renderable {
|
||||||
tasks: TaskState<unknown, unknown>[] = [];
|
tasks: Task[] = [];
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
for(const task of this.tasks) {
|
for(const task of this.tasks) {
|
||||||
this.removeTask(task);
|
this.removeTask(task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static serializationDependencies(): any[] {
|
static serializationDependencies() {
|
||||||
return [];
|
let a = Array.from(taskClasses.values())
|
||||||
}
|
return a as unknown as (typeof Serializable)[];
|
||||||
|
}
|
||||||
|
|
||||||
// TODO assign task dependant on pawn skills
|
addTask({taskId, options}: TaskOptions) {
|
||||||
getUnclaimedTask(): TaskState<unknown, unknown> | null {
|
if(!taskClasses.has(taskId))
|
||||||
// const availableTasks = this.tasks.filter(task => !task.claimed);
|
throw new Error('unknown task: ' + taskId);
|
||||||
// if(availableTasks.length > 0) {
|
|
||||||
// return availableTasks[0]
|
|
||||||
// } else return null;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
addTask(task: TaskState<unknown, unknown>) {
|
// this is any because TS doesnt understand that
|
||||||
this.tasks = [...this.tasks, task];
|
// static references and constructors are the
|
||||||
}
|
// SAME THING.
|
||||||
|
// in TS, they're MAGICALLY incompatible...
|
||||||
|
const taskClass: any = taskClasses.get(taskId);
|
||||||
|
const task = new taskClass();
|
||||||
|
|
||||||
removeTask(task: TaskState<unknown, unknown>) {
|
this.tasks = [...this.tasks, task];
|
||||||
this.tasks = this.tasks.filter(v => v !== task);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
removeTask(task) {
|
||||||
// panels.left.setContent(`${this.tasks.map(task => `${
|
this.tasks = this.tasks.filter(v => v !== task);
|
||||||
// getTheme().normal(task.toString())
|
}
|
||||||
// } ${
|
|
||||||
// getTheme().dimmed(task.worker?.toString() ?? '')
|
render() {
|
||||||
// }`).join('\n')}`);
|
// const width = tasksPanel.width;
|
||||||
// return '';
|
panels.left.setContent(`${this.tasks.map(task => {
|
||||||
}
|
return task.toString();
|
||||||
|
}).join('\n')}`);
|
||||||
|
// return this.tasks.map(task => task.toString()).join('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const taskTypes = {};
|
||||||
|
export function registerTask(name, clazz) {
|
||||||
|
taskTypes[name] = clazz;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TaskOptions = {
|
||||||
|
taskId: string,
|
||||||
|
options: any
|
||||||
}
|
}
|
||||||
86
src/Time.ts
86
src/Time.ts
|
|
@ -1,6 +1,7 @@
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import { Serializable } from "frigid";
|
import { Serializable } from "frigid";
|
||||||
import { getTheme } from "@themes";
|
import { getTheme } from "./registries/Themes.js";
|
||||||
|
import { Renderable } from "./ui/UI.js";
|
||||||
|
|
||||||
type AbbreviatedMonthName = string;
|
type AbbreviatedMonthName = string;
|
||||||
|
|
||||||
|
|
@ -18,9 +19,8 @@ const months: AbbreviatedMonthName[] = [
|
||||||
'Oct', 'Nov', 'Dec'
|
'Oct', 'Nov', 'Dec'
|
||||||
]
|
]
|
||||||
|
|
||||||
// TODO split ticker util and calendar util...
|
export default class Time extends Serializable implements Renderable {
|
||||||
export default class Time extends Serializable {
|
rate: number;
|
||||||
targetTPS: number;
|
|
||||||
paused = true;
|
paused = true;
|
||||||
|
|
||||||
thing: Tickable;
|
thing: Tickable;
|
||||||
|
|
@ -31,12 +31,6 @@ 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;
|
||||||
|
|
@ -74,34 +68,16 @@ export default class Time extends Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
return `${
|
return `${this.hour}:${Math.floor(this.minute).toString().padStart(2, '0')} ${months[this.month]} ${this.day + 1}, ${this.normalizedYear}`
|
||||||
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.targetTPS = 2000;
|
this.rate = 60;
|
||||||
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() {
|
||||||
|
|
@ -137,18 +113,12 @@ export default class Time extends Serializable {
|
||||||
this.paused = true;
|
this.paused = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
resume() {
|
start() {
|
||||||
this.paused = false;
|
this.paused = false;
|
||||||
setTimeout(this.doTick.bind(this), 0);
|
setTimeout(this.doTick.bind(this), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
start(tickable: Tickable) {
|
advanceTime(seconds) {
|
||||||
this.thing = tickable;
|
|
||||||
this.paused = false;
|
|
||||||
setTimeout(this.doTick.bind(this), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
advanceTime(seconds: number) {
|
|
||||||
this.minute += seconds / 60;
|
this.minute += seconds / 60;
|
||||||
this.normalize()
|
this.normalize()
|
||||||
}
|
}
|
||||||
|
|
@ -161,6 +131,8 @@ export default class Time extends Serializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
normalize() {
|
normalize() {
|
||||||
// while(t)
|
// while(t)
|
||||||
while(this.minute >= 60) {
|
while(this.minute >= 60) {
|
||||||
|
|
@ -202,46 +174,20 @@ export default class Time extends Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
async doTick() {
|
async doTick() {
|
||||||
|
|
||||||
this.advanceTime(1);
|
this.advanceTime(1);
|
||||||
const timeout = 1000 / this.targetTPS;
|
const timeout = 1000 / this.rate;
|
||||||
// const start = ms4()
|
const start = new Date().getTime();
|
||||||
const start = ms4();
|
|
||||||
if(this.thing) {
|
if(this.thing) {
|
||||||
await this.thing.tick();
|
await this.thing.tick();
|
||||||
}
|
}
|
||||||
const end = ms4()
|
const elapsed = new Date().getTime() - start;
|
||||||
const elapsed = end - start;
|
const wait = Math.max(timeout - elapsed, 0);
|
||||||
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._boundTick, normalizedWait)
|
setTimeout(this.doTick.bind(this), wait)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
33
src/Util.ts
33
src/Util.ts
|
|
@ -1,33 +0,0 @@
|
||||||
import { lstatSync } from 'fs';
|
|
||||||
import { parse, resolve } from 'path';
|
|
||||||
import walkSync from 'walk-sync';
|
|
||||||
import { fileURLToPath } from 'url';
|
|
||||||
import { APPLICATION_NAME } from './Constants.js';
|
|
||||||
import chalk from 'chalk';
|
|
||||||
|
|
||||||
|
|
||||||
export function osrsNumber(x: number): string {
|
|
||||||
if(x < 10_000) return '' + x;
|
|
||||||
else if (x < 10_000_000) return Math.floor(x / 1000) + 'K';
|
|
||||||
else return Math.floor(x / 1_000_000) + 'M';
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function loadExtensions() {
|
|
||||||
console.log('Loading extensions');
|
|
||||||
const extensionsPath = resolve(parse(fileURLToPath(import.meta.url)).dir, '../content');
|
|
||||||
|
|
||||||
const extensions = walkSync(extensionsPath)
|
|
||||||
.map(path => [path, resolve(extensionsPath, path)])
|
|
||||||
.filter(path => lstatSync(path[1]).isFile())
|
|
||||||
.filter(path => parse(path[1]).ext === '.js');
|
|
||||||
|
|
||||||
console.log('found', extensions.length, 'extensions');
|
|
||||||
|
|
||||||
for (const path of extensions) {
|
|
||||||
console.log('=== [', path[0], '] ===');
|
|
||||||
await import(path[1]);
|
|
||||||
console.log();
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Setup Complete.');
|
|
||||||
}
|
|
||||||
140
src/World.ts
140
src/World.ts
|
|
@ -1,145 +1,5 @@
|
||||||
import { Game } from "@game";
|
|
||||||
import { ItemFilter, ItemState } from "@items";
|
|
||||||
import { Task, TaskState } from "@tasks";
|
|
||||||
import { Serializable } from "frigid";
|
import { Serializable } from "frigid";
|
||||||
import { RESOURCE_COLLECTION_TASK } from "./world/ResourceCollectionTask.js";
|
|
||||||
|
|
||||||
const explorationConstant = 0.001; // km ish
|
|
||||||
|
|
||||||
export class World extends Serializable {
|
export class World extends Serializable {
|
||||||
places: PlaceState[] = [];
|
|
||||||
|
|
||||||
distanceExplored: number; // km ish
|
|
||||||
|
|
||||||
home: PlaceState = null;
|
|
||||||
|
|
||||||
static serializationDependencies() {
|
|
||||||
return [PlaceState];
|
|
||||||
}
|
|
||||||
|
|
||||||
explore() {
|
|
||||||
for(const [id, place] of places) {
|
|
||||||
const threshold = (explorationConstant * place.frequency);
|
|
||||||
if(Math.random() <= threshold) {
|
|
||||||
const angle = Math.random() * Math.PI * 2
|
|
||||||
const x = Math.sin(angle) * this.distanceExplored;
|
|
||||||
const y = Math.cos(angle) * this.distanceExplored;
|
|
||||||
const newPlaceState = new PlaceState(
|
|
||||||
place,
|
|
||||||
Math.round(x * 1000),
|
|
||||||
Math.round(y * 1000)
|
|
||||||
);
|
|
||||||
if(this.home === null) {
|
|
||||||
this.home = newPlaceState;
|
|
||||||
}
|
|
||||||
this.places.push(newPlaceState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.distanceExplored += explorationConstant;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctor() {
|
|
||||||
this.distanceExplored ??= 0;
|
|
||||||
this.home ??= null;
|
|
||||||
this.places ??= [];
|
|
||||||
if(this.home === null) {
|
|
||||||
let hasHabitablePlacesLoaded = false;
|
|
||||||
for(const [id, place] of places) {
|
|
||||||
if(place.habitable) hasHabitablePlacesLoaded = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if(hasHabitablePlacesLoaded) {
|
|
||||||
while(this.home === null) {
|
|
||||||
this.explore();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error('No habitable places loaded\n'
|
|
||||||
+ 'unable to create home!')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const places: Map<string, Place> = new Map();
|
|
||||||
|
|
||||||
export class PlaceState extends Serializable {
|
|
||||||
resources: ResourceNode[];
|
|
||||||
placeId: string;
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
|
|
||||||
constructor(place: Place, x: number, y: number) {
|
|
||||||
super();
|
|
||||||
this.placeId = place.id;
|
|
||||||
this.resources = this.place.populate();
|
|
||||||
for(const node of this.resources) node.setPlace(this);
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
}
|
|
||||||
|
|
||||||
get place() {
|
|
||||||
return places.get(this.placeId);
|
|
||||||
}
|
|
||||||
|
|
||||||
static serializationDependencies() {
|
|
||||||
return [ResourceNode];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Place {
|
|
||||||
name: string;
|
|
||||||
id: string;
|
|
||||||
frequency: number;
|
|
||||||
habitable: boolean;
|
|
||||||
populate: () => ResourceNode[];
|
|
||||||
|
|
||||||
setName(name: string) {
|
|
||||||
this.name = name;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setId(id: string) {
|
|
||||||
this.id = id;
|
|
||||||
places.set(this.id, this);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
populateResources(fn: () => ResourceNode[]) {
|
|
||||||
this.populate = fn;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setFrequency(frequency: number) {
|
|
||||||
this.frequency = frequency;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setHabitable(habitable: boolean) {
|
|
||||||
this.habitable = habitable;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ResourceNode extends Serializable {
|
|
||||||
resources: ItemState<unknown>;
|
|
||||||
place: PlaceState;
|
|
||||||
|
|
||||||
constructor(resources: ItemState<unknown>) {
|
|
||||||
// collectionRequirements: ItemFilter[] = []
|
|
||||||
super();
|
|
||||||
this.resources = resources;
|
|
||||||
}
|
|
||||||
|
|
||||||
setPlace(place: PlaceState) {
|
|
||||||
this.place = place;
|
|
||||||
}
|
|
||||||
|
|
||||||
request(qty: number) {
|
|
||||||
Game.current.board.addTask(
|
|
||||||
new TaskState(RESOURCE_COLLECTION_TASK, {
|
|
||||||
qty,
|
|
||||||
node: this
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
import ipc from 'node-ipc';
|
|
||||||
import {
|
|
||||||
IPC_PATH,
|
|
||||||
IPC_QUIT_EVENT,
|
|
||||||
IPC_RESTART_EVENT,
|
|
||||||
IPC_REQUEST_RESTART
|
|
||||||
} from './Constants.js';
|
|
||||||
import { spawn, ChildProcess } from 'child_process';
|
|
||||||
import chokidar from 'chokidar';
|
|
||||||
import chalk from 'chalk';
|
|
||||||
ipc.config.silent = true;
|
|
||||||
|
|
||||||
|
|
||||||
// should be obtained from process spawn args, but whatever!
|
|
||||||
const exec = 'qode' +
|
|
||||||
(process.platform === "win32" ? '.cmd' : '');
|
|
||||||
const args = [
|
|
||||||
'bin/app.bundle.cjs'
|
|
||||||
];
|
|
||||||
|
|
||||||
const log = console.log.bind(console, chalk.green('[TOWER]'));
|
|
||||||
|
|
||||||
// varying state data
|
|
||||||
let connected = 0;
|
|
||||||
let proc: ChildProcess = null;
|
|
||||||
let restartTimer: NodeJS.Timeout = null;
|
|
||||||
|
|
||||||
function ensureAlive() {
|
|
||||||
if(proc) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
proc = spawn(exec, args, {
|
|
||||||
stdio: 'inherit'
|
|
||||||
});
|
|
||||||
proc.once('exit', () => {
|
|
||||||
proc = null;
|
|
||||||
});
|
|
||||||
log(`[${
|
|
||||||
proc.pid
|
|
||||||
}] ${
|
|
||||||
chalk.grey(`${
|
|
||||||
exec
|
|
||||||
} ${
|
|
||||||
args.join(' ')
|
|
||||||
}`)
|
|
||||||
}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function ensureDead() {
|
|
||||||
if(!proc) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const killedPromise =
|
|
||||||
new Promise(res => proc.once('exit', res));
|
|
||||||
proc.kill(9);
|
|
||||||
await killedPromise;
|
|
||||||
proc = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function restart() {
|
|
||||||
await ensureDead();
|
|
||||||
ensureAlive();
|
|
||||||
}
|
|
||||||
|
|
||||||
function fileChange() {
|
|
||||||
if(restartTimer) clearTimeout(restartTimer)
|
|
||||||
restartTimer = setTimeout(() => {
|
|
||||||
ensureAlive();
|
|
||||||
ipc.server.broadcast(IPC_REQUEST_RESTART);
|
|
||||||
restartTimer = null;
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
67
src/index.ts
67
src/index.ts
|
|
@ -1,59 +1,36 @@
|
||||||
|
import { render } from './ui/UI.js';
|
||||||
import { ensureDirSync } from 'fs-extra';
|
import { ensureDirSync } from 'fs-extra';
|
||||||
import { parse } from 'path';
|
import { lstatSync } from 'fs';
|
||||||
|
import { parse, resolve } from 'path';
|
||||||
|
import walkSync from 'walk-sync';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
import { Game } from '@game';
|
import { Game } from '@game';
|
||||||
import {
|
|
||||||
isStarted,
|
|
||||||
stop,
|
|
||||||
update,
|
|
||||||
GameView,
|
|
||||||
setView,
|
|
||||||
start
|
|
||||||
} from '@ui';
|
|
||||||
// @ts-ignore
|
|
||||||
import ansi from 'sisteransi';
|
|
||||||
|
|
||||||
// HACK static extension loading
|
|
||||||
import './../content/content.js';
|
|
||||||
import { loadExtensions } from './Util.js';
|
|
||||||
import { APPLICATION_NAME } from './Constants.js';
|
|
||||||
import chalk from 'chalk';
|
|
||||||
import { ProcessManager } from './ProcessManager.js';
|
|
||||||
|
|
||||||
// console.clear();
|
|
||||||
|
|
||||||
ProcessManager.on('shutdown', gracefulShutdown);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
const saveFile = process.argv[2] || 'data/world01.json';
|
const saveFile = process.argv[2] || 'data/world01.json';
|
||||||
|
|
||||||
ensureDirSync(parse(saveFile).dir);
|
ensureDirSync(parse(saveFile).dir);
|
||||||
|
|
||||||
// loadExtensions();
|
|
||||||
|
|
||||||
// TODO replace with splash screen
|
// TODO extract extension loading into separate file
|
||||||
// for (let seconds = 0; seconds > 0; seconds --) {
|
console.log('df-idle: Loading extensions');
|
||||||
// process.stdout.write('Starting ' + APPLICATION_NAME + ' in ' + seconds + '\r');
|
const extensionsPath = resolve(parse(fileURLToPath(import.meta.url)).dir, '../content');
|
||||||
// }
|
|
||||||
// process.stdout.write('Starting ' + APPLICATION_NAME + ' in ' + 0 + '\n');
|
const extensions = walkSync(extensionsPath)
|
||||||
// console.clear();
|
.map(path => resolve(extensionsPath, path))
|
||||||
|
.filter(path => lstatSync(path).isFile())
|
||||||
|
.filter(path => parse(path).ext === '.js');
|
||||||
|
// .map(path => import(path));
|
||||||
|
|
||||||
|
console.log('found', extensions.length, 'extensions');
|
||||||
|
|
||||||
|
for(const path of extensions) {
|
||||||
|
console.log('=== [', path, '] ===');
|
||||||
|
await import(path);
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
|
|
||||||
// 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...
|
||||||
// maybe, i mean, an argument could be made for not that, because the game
|
// maybe, i mean, an argument could be made for not that, because the game
|
||||||
// isnt necessarily the entire thing, its just one instance of a save file.
|
// isnt necessarily the entire thing, its just one instance of a save file.
|
||||||
// But probably the initial menu screens will be their own thing entirely.
|
// But probably the initial menu screens will be their own thing entirely.
|
||||||
const game = Game.create(saveFile);
|
const game = Game.create(saveFile);
|
||||||
start();
|
|
||||||
const gameView = new GameView(game);
|
|
||||||
setView(gameView);
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export class Player {
|
||||||
return this.name;
|
return this.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
send(items: (ItemState<any> | Pawn)[]) {
|
send(items: (ItemState | Pawn)[]) {
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
const pawnJsons: string[] = [];
|
const pawnJsons: string[] = [];
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
|
|
|
||||||
|
|
@ -5,69 +5,72 @@ 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, { EventEmitter } from 'ws';
|
import WebSocket from 'ws';
|
||||||
import { Popup } from '@ui';
|
import { Popup } from '../ui/Popup.js';
|
||||||
import { inspect } from 'util'
|
import { inspect } from 'util'
|
||||||
import { Pawn } from '../Pawn.js';
|
import { Pawn } from '../Pawn.js';
|
||||||
import { Game } from '../Game.js';
|
import { Game } from '../Game.js';
|
||||||
import { Player } from './Player.js';
|
import { Player } from './Player.js';
|
||||||
import { injectTravelMemory } from '../Memories.js';
|
|
||||||
import { MDNS_TYPE } from '../Constants.js';
|
|
||||||
|
|
||||||
const mdns = bonjour();
|
const mdns = bonjour();
|
||||||
const ID = uuid.v4();
|
const ID = uuid.v4();
|
||||||
let devices: Player[] = [];
|
let devices: Player[] = [];
|
||||||
|
|
||||||
const network = new (class Network extends EventEmitter {
|
const network = {
|
||||||
get players() {
|
get players() {
|
||||||
return devices;
|
return devices;
|
||||||
}
|
}
|
||||||
})();
|
}
|
||||||
|
|
||||||
export type GiftMessage = {
|
export type GiftMessage = {
|
||||||
pawns: string[],
|
pawns: string[],
|
||||||
from: string
|
from: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default network;
|
export default network;
|
||||||
|
|
||||||
export async function ready(name: string) {
|
export async function ready(name) {
|
||||||
const port = await getPort({port: getPort.makeRange(52300, 52399)});
|
const port = await getPort({port: getPort.makeRange(52300, 52399)});
|
||||||
mdns.publish({
|
mdns.publish({
|
||||||
type: MDNS_TYPE,
|
type: 'dfi',
|
||||||
name,
|
name,
|
||||||
port: port
|
port: port
|
||||||
});
|
});
|
||||||
const wss = new WebSocket.Server({ port });
|
const wss = new WebSocket.Server({ port });
|
||||||
wss.on('connection', function connection(ws) {
|
wss.on('connection', function connection(ws) {
|
||||||
ws.on('message', function incoming(message) {
|
ws.on('message', function incoming(message) {
|
||||||
const {pawns: pawnJsons, from} = JSON.parse(message.toString());
|
const {pawns: pawnJsons, from} = JSON.parse(message);
|
||||||
const pawns = [];
|
const pawns = [];
|
||||||
for(const pawnJson of pawnJsons) {
|
for(const pawnJson of pawnJsons) {
|
||||||
const pawn: Pawn = Pawn.fromJson(pawnJson);
|
const pawn: Pawn = Pawn.fromJson(pawnJson);
|
||||||
pawns.push(pawn);
|
pawn.memories.push({
|
||||||
injectTravelMemory(pawn);
|
type: "travel",
|
||||||
}
|
time: {
|
||||||
Popup.show(`${(() => {
|
age: pawn.age,
|
||||||
if(pawns.length === 0) return `A care package has arrived from ${from}.`;
|
locale: Game.current.clock.toString()
|
||||||
if(pawns.length === 1) return `A traveler from ${from} named ${pawns[0].toString()} has arrived.`;
|
},
|
||||||
if(pawns.length > 1) return `A caravan of ${pawns.length} people from ${from} has arrived.`
|
location: Game.current.name
|
||||||
})()}`);
|
})
|
||||||
for(const pawn of pawns) Game.current.pawns.push(pawn);
|
pawns.push(pawn);
|
||||||
});
|
}
|
||||||
});
|
Popup.show(`${(() => {
|
||||||
|
if(pawns.length === 0) return `A care package has arrived from ${from}.`;
|
||||||
|
if(pawns.length === 1) return `A traveler from ${from} named ${pawns[0].toString()} has arrived.`;
|
||||||
|
if(pawns.length > 1) return `A caravan of ${pawns.length} people from ${from} has arrived.`
|
||||||
|
})()}`);
|
||||||
|
for(const pawn of pawns) Game.current.pawns.push(pawn);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
mdns.find({
|
mdns.find({
|
||||||
type: MDNS_TYPE
|
type: 'dfi'
|
||||||
}, (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,250 +0,0 @@
|
||||||
import { Game } from '@game';
|
|
||||||
import { ItemState } from '@items';
|
|
||||||
import {
|
|
||||||
QLabel,
|
|
||||||
QTabWidget,
|
|
||||||
QWidget,
|
|
||||||
QIcon,
|
|
||||||
QGridLayout,
|
|
||||||
FocusPolicy,
|
|
||||||
AlignmentFlag,
|
|
||||||
QBoxLayout,
|
|
||||||
Direction,
|
|
||||||
QScrollArea,
|
|
||||||
} from '@nodegui/nodegui';
|
|
||||||
import network from '../multiplayer/mDNS.js';
|
|
||||||
import { Player } from '../multiplayer/Player.js';
|
|
||||||
import { Pawn } from '../Pawn.js';
|
|
||||||
import { View } from './View.js';
|
|
||||||
|
|
||||||
export class GameView extends View {
|
|
||||||
game: Game;
|
|
||||||
title: QLabel;
|
|
||||||
timeLabel: QLabel;
|
|
||||||
left: QWidget;
|
|
||||||
right: QTabWidget;
|
|
||||||
|
|
||||||
addComponents(): void {
|
|
||||||
this.addLayout();
|
|
||||||
|
|
||||||
this.title = new QLabel();
|
|
||||||
this.left = new QWidget();
|
|
||||||
this.right = new QTabWidget();
|
|
||||||
this.timeLabel = new TimeWidget();
|
|
||||||
|
|
||||||
this.title.setText(this.game.name);
|
|
||||||
|
|
||||||
this.layout.addWidget(this.title, 0, 0);
|
|
||||||
this.layout.addWidget(this.timeLabel, 0, 1);
|
|
||||||
this.layout.addWidget(this.left, 1, 0);
|
|
||||||
this.layout.addWidget(this.right, 1, 1);
|
|
||||||
this.layout.setRowStretch(0, 0);
|
|
||||||
this.layout.setRowStretch(1, 1);
|
|
||||||
this.layout.setColumnStretch(0, 3);
|
|
||||||
this.layout.setColumnStretch(1, 2);
|
|
||||||
|
|
||||||
this.right.addTab(new PawnPageWidget(), new QIcon(), 'Pawns');
|
|
||||||
this.right.addTab(new InventoryPageWidget(), new QIcon(), 'Inventory');
|
|
||||||
this.right.addTab(new MultiplayerPageWidget(), new QIcon(), 'Multiplayer');
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(game: Game) {
|
|
||||||
super();
|
|
||||||
this.game = game;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class GridItem extends QWidget {
|
|
||||||
|
|
||||||
rootLayout: QGridLayout;
|
|
||||||
|
|
||||||
get layout(): QGridLayout {
|
|
||||||
return this.rootLayout;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.rootLayout = new QGridLayout()
|
|
||||||
this.setLayout(this.rootLayout);
|
|
||||||
this.setInlineStyle(`
|
|
||||||
width: \'100%\';
|
|
||||||
background: coral;
|
|
||||||
margin: 0px;
|
|
||||||
padding: 0px;
|
|
||||||
`);
|
|
||||||
|
|
||||||
this.setFocusPolicy(FocusPolicy.ClickFocus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
|
||||||
super();
|
|
||||||
this.setInlineStyle(`
|
|
||||||
background: rgba(0, 0, 0, 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) {
|
|
||||||
this.addWidget(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 {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.setAlignment(AlignmentFlag.AlignRight | AlignmentFlag.AlignVCenter);
|
|
||||||
setInterval(() => {
|
|
||||||
this.setText(Game.current.clock.toString());
|
|
||||||
}, 100)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
import { QGridLayout, QLabel, QWidget } from "@nodegui/nodegui";
|
|
||||||
import { View } from "./View.js";
|
|
||||||
|
|
||||||
export class LoadingView extends View {
|
|
||||||
label: QLabel;
|
|
||||||
|
|
||||||
addComponents(): void {
|
|
||||||
this.addLayout();
|
|
||||||
|
|
||||||
this.label = new QLabel();
|
|
||||||
this.label.setText('Loading World...');
|
|
||||||
this.layout.addWidget(this.label);
|
|
||||||
}
|
|
||||||
|
|
||||||
destory() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
export class Popup {
|
|
||||||
static show(content: string) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
import { FocusReason, QGridLayout, QLabel, QMainWindow, QPushButton, QWidget } from "@nodegui/nodegui";
|
|
||||||
import { ProcessManager } from "../ProcessManager";
|
|
||||||
|
|
||||||
export class RequestReloadPopup {
|
|
||||||
static exists = false;
|
|
||||||
|
|
||||||
static show() {
|
|
||||||
if(this.exists) return;
|
|
||||||
this.exists = true;
|
|
||||||
const window = new QMainWindow();
|
|
||||||
window.setFixedSize(200, 100);
|
|
||||||
const root = new QWidget();
|
|
||||||
window.setCentralWidget(root);
|
|
||||||
const layout = new QGridLayout();
|
|
||||||
root.setLayout(layout);
|
|
||||||
const label = new QLabel();
|
|
||||||
label.setText('A reload has been requested');
|
|
||||||
layout.addWidget(label, 0, 0, 4, 3);
|
|
||||||
const reloadButton = new QPushButton();
|
|
||||||
reloadButton.setText('Reload');
|
|
||||||
layout.addWidget(reloadButton, 4, 2, 1, 1);
|
|
||||||
window.show();
|
|
||||||
window.setWindowTitle('Reload?');
|
|
||||||
|
|
||||||
reloadButton.addEventListener('clicked', () => {
|
|
||||||
ProcessManager.restart();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//////
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
import { QGridLayout, QMainWindow, QWidget, WindowState } from "@nodegui/nodegui";
|
|
||||||
import { QEvent } from "@nodegui/nodegui/dist/lib/QtGui/QEvent/QEvent";
|
|
||||||
import { win } from "@ui";
|
|
||||||
|
|
||||||
export abstract class View {
|
|
||||||
root: QWidget;
|
|
||||||
layout: QGridLayout;
|
|
||||||
|
|
||||||
addLayout() {
|
|
||||||
this.root = new QWidget();
|
|
||||||
this.root.setObjectName("root");
|
|
||||||
win.setCentralWidget(this.root);
|
|
||||||
|
|
||||||
this.layout = new QGridLayout();
|
|
||||||
this.root.setLayout(this.layout);
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract addComponents(): void;
|
|
||||||
}
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
import {
|
|
||||||
QMainWindow, WidgetEventTypes
|
|
||||||
} from '@nodegui/nodegui';
|
|
||||||
import { APPLICATION_NAME } from '../Constants.js';
|
|
||||||
import { ProcessManager } from '../ProcessManager.js';
|
|
||||||
import { LoadingView } from './LoadingView.js';
|
|
||||||
import { RequestReloadPopup } from './RequestReloadPopup.js';
|
|
||||||
import { View } from './View.js';
|
|
||||||
export { GameView } from './GameView.js';
|
|
||||||
export { Popup } from './Popup.js';
|
|
||||||
|
|
||||||
export const win = new QMainWindow();
|
|
||||||
win.setFixedSize(800, 600);
|
|
||||||
win.setWindowTitle(APPLICATION_NAME);
|
|
||||||
// win.setStyleSheet(`
|
|
||||||
// QTabWidget::tab {
|
|
||||||
// bottom: 2px;
|
|
||||||
// }
|
|
||||||
// `);
|
|
||||||
win.show();
|
|
||||||
(global as any).win = win;
|
|
||||||
win.addEventListener(WidgetEventTypes.Paint, _ => _);
|
|
||||||
win.addEventListener('customContextMenuRequested', console.log);
|
|
||||||
win.addEventListener('objectNameChanged', console.log);
|
|
||||||
win.addEventListener('windowIconChanged', console.log);
|
|
||||||
win.addEventListener('windowTitleChanged', console.log);
|
|
||||||
win.addEventListener(
|
|
||||||
WidgetEventTypes.Close,
|
|
||||||
() => ProcessManager.quit()
|
|
||||||
);
|
|
||||||
|
|
||||||
setView(new LoadingView());
|
|
||||||
|
|
||||||
export function start() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export function stop() {
|
|
||||||
win.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setView(view: View) {
|
|
||||||
view.addComponents();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setTitle(title: string) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export function update() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isStarted() {
|
|
||||||
return true;
|
|
||||||
return win.isVisible();
|
|
||||||
}
|
|
||||||
|
|
||||||
ProcessManager.on('reload', () => {
|
|
||||||
RequestReloadPopup.show();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// HACK this is bullshit, :)
|
|
||||||
function f() {
|
|
||||||
win.repaint();
|
|
||||||
win.update();
|
|
||||||
setTimeout(f, 0);
|
|
||||||
}
|
|
||||||
f();
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { getTheme } from "@themes";
|
||||||
|
import { Renderable } from "../ui/UI.js";
|
||||||
|
|
||||||
|
export const actions: Action[] = [];
|
||||||
|
|
||||||
|
export function registerAction(name: string, invoke: (qty: number) => void) {
|
||||||
|
console.log('Registered action', name);
|
||||||
|
actions.push(new Action(name, invoke))
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Action {
|
||||||
|
name: string;
|
||||||
|
qty: number;
|
||||||
|
invoke: (qty: number) => void;
|
||||||
|
|
||||||
|
constructor(name: string, done: (qty: number) => void) {
|
||||||
|
this.name = name;
|
||||||
|
this.invoke = done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,33 +1,23 @@
|
||||||
import { Serializable } from 'frigid';
|
import { Serializable } from 'frigid';
|
||||||
import { getTheme } from '@themes';
|
import { getTheme } from './Themes.js';
|
||||||
import { osrsNumber } from '../Util.js';
|
import { Renderable } from '../ui/UI.js';
|
||||||
|
|
||||||
export type ItemID = string;
|
export type ItemID = string;
|
||||||
|
|
||||||
const items = new Map<ItemID, Item<any>>();
|
const items = new Map<ItemID, Item>();
|
||||||
|
|
||||||
// ITEMS SHALL BE SINGULAR
|
// ITEMS SHALL BE SINGULAR
|
||||||
export class Item<Data = any> {
|
export class Item extends Serializable {
|
||||||
|
|
||||||
name = {
|
name = '';
|
||||||
singular: '',
|
|
||||||
plural: ''
|
|
||||||
}
|
|
||||||
id: ItemID = '';
|
id: ItemID = '';
|
||||||
props: Map<string, any> = new Map();
|
|
||||||
|
|
||||||
setName(name: string) {
|
setName(name) {
|
||||||
this.name.singular = name;
|
this.name = name;
|
||||||
this.name.plural = name;
|
|
||||||
this.register(false);
|
this.register(false);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
plural(name: string) {
|
|
||||||
this.name.plural = name;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setId(id: ItemID) {
|
setId(id: ItemID) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.register(false);
|
this.register(false);
|
||||||
|
|
@ -36,36 +26,16 @@ 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 ?? "[No Name]").padStart(20, ' '), `| (${this.id})`)
|
||||||
items.set(this.id, this);
|
items.set(this.id, this);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setProperty(prop: ItemProperty, value: any) {
|
|
||||||
this.props.set(prop.name, value);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
getProperty(prop: ItemProperty) {
|
|
||||||
if(this.props.has(prop.name)) return this.props.get(prop.name);
|
|
||||||
else return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ItemState<Data> extends Serializable {
|
export class ItemState extends Serializable implements Renderable {
|
||||||
qty: number;
|
qty: number;
|
||||||
itemId: ItemID;
|
itemId: ItemID;
|
||||||
data: Data;
|
data: any;
|
||||||
|
|
||||||
take(qty: number) {
|
|
||||||
if(this.qty < qty) throw new Error('cant split more than stack from stack...');
|
|
||||||
this.qty -= qty;
|
|
||||||
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))
|
||||||
|
|
@ -73,7 +43,7 @@ export class ItemState<Data> extends Serializable {
|
||||||
return items.get(this.itemId);
|
return items.get(this.itemId);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(item: Item<Data>, amount: number, data: Data = null) {
|
constructor(item: Item, amount: number, data: any) {
|
||||||
super();
|
super();
|
||||||
this.qty = amount;
|
this.qty = amount;
|
||||||
this.itemId = item.id;
|
this.itemId = item.id;
|
||||||
|
|
@ -81,19 +51,7 @@ export class ItemState<Data> extends Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return getTheme().normal(` ${osrsNumber(this.qty)} ${(() => {
|
return getTheme().normal(` ${this.item.name}{|}${this.qty} `);
|
||||||
if(this.qty === 1) return this.item.name.singular;
|
|
||||||
else return this.item.name.plural;
|
|
||||||
})()} `);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ItemProperty {
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
constructor(name: string) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ItemFilter = (itemState: ItemState<any>) => boolean;
|
|
||||||
|
|
|
||||||
|
|
@ -3,96 +3,72 @@ import EventEmitter from 'events';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { Pawn } from '../Pawn.js';
|
import { Pawn } from '../Pawn.js';
|
||||||
import { Game } from '../Game.js';
|
import { Game } from '../Game.js';
|
||||||
|
import { panels } from '../ui/UI.js';
|
||||||
import { progressbar, ProgressbarStyle } from '../Progressbar.js';
|
import { progressbar, ProgressbarStyle } from '../Progressbar.js';
|
||||||
|
|
||||||
const tasks: Map<string, Task<unknown, unknown>> = new Map();
|
export const taskClasses: Map<string, typeof Task> = new Map();
|
||||||
type WorkFunction<Data, State> = (taskState: TaskState<Data, State>, dtime: number) => void;
|
|
||||||
type InitFunction<Data, State> = (data: Data) => State;
|
|
||||||
|
|
||||||
export class Task<Data, State> {
|
export abstract class Task extends Serializable {
|
||||||
id: string;
|
progress = 0;
|
||||||
fn: WorkFunction<Data, State>;
|
|
||||||
init: InitFunction<Data, State>;
|
|
||||||
name: string;
|
|
||||||
toString: (data: Data, state: State) => string;
|
|
||||||
|
|
||||||
constructor(id: string) {
|
|
||||||
this.id = id;
|
|
||||||
tasks.set(id, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
setInitiate(init: InitFunction<Data, State>) {
|
|
||||||
this.init = init;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setFunction(fn: WorkFunction<Data, State>) {
|
|
||||||
this.fn = fn;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setName(name: string) {
|
|
||||||
this.name = name;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setToString(fn: (data: Data, state: State) => string) {
|
|
||||||
this.toString = fn;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class TaskState<Data, State> extends Serializable {
|
|
||||||
taskId: string;
|
|
||||||
workFn: WorkFunction<Data, State>;
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
data: Data;
|
|
||||||
completed: boolean = false;
|
|
||||||
worker: Pawn;
|
worker: Pawn;
|
||||||
state: State;
|
data: any;
|
||||||
|
|
||||||
constructor(task: Task<Data, State>, data: Data) {
|
ctor() {
|
||||||
super();
|
this.worker ??= null;
|
||||||
this.data = data;
|
this.testCompletion();
|
||||||
this.taskId = task.id;
|
|
||||||
this.workFn = this.task.fn.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setLocation(x: number, y: number) {
|
abstract reward(): void;
|
||||||
this.x = x;
|
abstract work: number;
|
||||||
this.y = y;
|
|
||||||
|
get completed() {
|
||||||
|
return this.completion >= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
get task(): Task<Data, State> {
|
stopJob() {
|
||||||
// casting because the id this is associated with
|
this.worker = null;
|
||||||
// should have the datatype of the task it was
|
|
||||||
// created with, but it stored as unknown
|
|
||||||
return tasks.get(this.taskId) as Task<Data, State>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get name() {
|
doWork(work = 1, pawn: Pawn) {
|
||||||
return this.task.name;
|
this.worker = pawn;
|
||||||
|
this.progress += work;
|
||||||
|
this.testCompletion();
|
||||||
}
|
}
|
||||||
|
|
||||||
work(dtime: number) {
|
testCompletion() {
|
||||||
this.workFn(this, dtime);
|
if (this.progress >= this.work) {
|
||||||
|
this.reward();
|
||||||
|
Game.current.board.removeTask(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
claim(pawn: Pawn) {
|
claim(pawn: Pawn) {
|
||||||
this.worker = pawn;
|
this.worker = pawn;
|
||||||
}
|
}
|
||||||
|
|
||||||
unclaim() {
|
get completion() {
|
||||||
this.worker = null;
|
return Math.min(1, this.progress / this.work);
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
return this.task.toString(this.data, this.state)
|
// HACK magic number 2 here, is the border
|
||||||
|
// of the panel
|
||||||
|
const width = panels.left.width - 2;
|
||||||
|
const left = ' ' + this.title + ' ' + (this.worker?.toString() || chalk.bold.black('Queued'));
|
||||||
|
const bar = width - 2;
|
||||||
|
return `${left}\n ${progressbar(this.completion, bar, ProgressbarStyle.progress)}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
get title() {
|
||||||
|
return chalk.bgRedBright.black('[Abstract Task]');
|
||||||
|
}
|
||||||
|
|
||||||
|
get status() {
|
||||||
|
return chalk.bgRedBright.black('DOING A TASK');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// export interface TaskProvider {
|
export function registerTask(id: string, taskClass: typeof Task) {
|
||||||
// hasTask(): boolean;
|
console.log('Registered task', id);
|
||||||
// getTask(): TaskState<unknown, unknown>;
|
taskClasses.set(id, taskClass)
|
||||||
// }
|
}
|
||||||
|
|
@ -13,9 +13,8 @@ type StyleFunction = (text: string) => string;
|
||||||
export type Theme = {
|
export type Theme = {
|
||||||
header: StyleFunction,
|
header: StyleFunction,
|
||||||
subheader: StyleFunction,
|
subheader: StyleFunction,
|
||||||
bright: StyleFunction,
|
|
||||||
normal: StyleFunction,
|
normal: StyleFunction,
|
||||||
dimmed: StyleFunction,
|
selected: StyleFunction,
|
||||||
hotkey: StyleFunction,
|
hotkey: StyleFunction,
|
||||||
tab: {
|
tab: {
|
||||||
normal: StyleFunction,
|
normal: StyleFunction,
|
||||||
|
|
@ -43,19 +42,18 @@ export type Theme = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const backupTheme: Theme = {
|
export const backupTheme: Theme = {
|
||||||
header: chalk.ansi256(255),
|
header: chalk.ansi256(255).bold,
|
||||||
subheader: chalk.ansi256(250),
|
subheader: chalk.ansi256(243).bold,
|
||||||
bright: chalk.ansi256(255),
|
normal: chalk.ansi256(243),
|
||||||
normal: chalk.ansi256(250),
|
selected: chalk.ansi256(250),
|
||||||
dimmed: chalk.ansi256(245),
|
|
||||||
hotkey: chalk.ansi256(40),
|
hotkey: chalk.ansi256(40),
|
||||||
tab: {
|
tab: {
|
||||||
normal: chalk.ansi256(117),
|
normal: chalk.ansi256(117),
|
||||||
selected: chalk.ansi256(117).inverse
|
selected: chalk.ansi256(117).inverse
|
||||||
},
|
},
|
||||||
border: {
|
border: {
|
||||||
focused: '#00ff00',
|
focused: '#ffffff',
|
||||||
normal: '#888888'
|
normal: '#222222'
|
||||||
},
|
},
|
||||||
progressBar: {
|
progressBar: {
|
||||||
indicator: {
|
indicator: {
|
||||||
|
|
@ -80,7 +78,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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,22 +92,3 @@ export function setTheme(name: ThemeName): void {
|
||||||
export function getTheme(): Theme {
|
export function getTheme(): Theme {
|
||||||
return currentTheme;
|
return currentTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO move this to theme
|
|
||||||
export const boxStyle = () => {
|
|
||||||
return {
|
|
||||||
style: {
|
|
||||||
border: {
|
|
||||||
fg: getTheme().border.normal
|
|
||||||
},
|
|
||||||
focus: {
|
|
||||||
border: {
|
|
||||||
fg: getTheme().border.focused
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
border: {
|
|
||||||
type: 'line'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
import { Game } from "@game";
|
|
||||||
import { boxStyle, getTheme } from "@themes";
|
|
||||||
import { panels } from "./UI.js";
|
|
||||||
import blessed from 'neo-blessed';
|
|
||||||
import { ProcessManager } from "../ProcessManager.js";
|
|
||||||
|
|
||||||
// TODO convert all these popup-y things to be View based
|
|
||||||
// make them be boxes that have a view
|
|
||||||
export class EscapeMenu {
|
|
||||||
|
|
||||||
options = [
|
|
||||||
'RESUME',
|
|
||||||
'RELOAD',
|
|
||||||
'QUIT'
|
|
||||||
];
|
|
||||||
selected = 0;
|
|
||||||
box;
|
|
||||||
|
|
||||||
static show() {
|
|
||||||
new EscapeMenu();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected constructor() {
|
|
||||||
this.box = blessed.box({
|
|
||||||
top: 3,
|
|
||||||
left: 'center',
|
|
||||||
width: 20,
|
|
||||||
height: 'shrink',
|
|
||||||
content: '',
|
|
||||||
tags: true,
|
|
||||||
...boxStyle(),
|
|
||||||
});
|
|
||||||
this.box.on('keypress', (evt: {}, key: {full: string}) => {
|
|
||||||
if(key.full === 'up') {
|
|
||||||
this.selected --;
|
|
||||||
if(this.selected === -1) this.selected = this.options.length - 1;
|
|
||||||
} else if (key.full === 'down') {
|
|
||||||
this.selected ++;
|
|
||||||
if(this.selected === this.options.length) this.selected = 0;
|
|
||||||
} else if (key.full === 'enter') {
|
|
||||||
switch(this.selected) {
|
|
||||||
case 0: {
|
|
||||||
Game.current.clock.resume();
|
|
||||||
panels.screen.remove(this.box);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 1: {
|
|
||||||
ProcessManager.restart();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 2: {
|
|
||||||
Game.current.sync();
|
|
||||||
ProcessManager.quit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if(key.full === 'escape') {
|
|
||||||
Game.current.clock.resume();
|
|
||||||
panels.screen.remove(this.box);
|
|
||||||
}
|
|
||||||
this.render();
|
|
||||||
});
|
|
||||||
panels.screen.append(this.box);
|
|
||||||
this.box.focus();
|
|
||||||
Game.current.clock.pause();
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const space = ' '.repeat(this.box.width / 2 - 4);
|
|
||||||
this.box.setContent(space + 'Paused\n\n' + this.options.map((v, i) => {
|
|
||||||
if(i === this.selected) {
|
|
||||||
return ` ❯ ${getTheme().bright(v)} `;
|
|
||||||
} else {
|
|
||||||
return ` ${getTheme().normal(v)} `;
|
|
||||||
}
|
|
||||||
}).join('\n'));
|
|
||||||
panels.screen.render();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
import { Game } from "@game";
|
|
||||||
import { ItemState } from "@items";
|
|
||||||
import { boxStyle, getTheme } from "@themes";
|
|
||||||
import { panels } from "./UI";
|
|
||||||
import EventEmitter from "events";
|
|
||||||
import blessed from 'neo-blessed';
|
|
||||||
|
|
||||||
type ItemFilterFunction = (itemState: ItemState<any>) => boolean;
|
|
||||||
|
|
||||||
export class SelectItem {
|
|
||||||
box: any;
|
|
||||||
emitter: EventEmitter;
|
|
||||||
qty: number;
|
|
||||||
items: ItemState<any>[];
|
|
||||||
selectedIdx: number;
|
|
||||||
|
|
||||||
static show(filter: ItemFilterFunction, qty: number = 1): Promise<ItemState<any>> {
|
|
||||||
const si = new SelectItem(filter, qty);
|
|
||||||
return new Promise(res => {
|
|
||||||
si.emitter.on('selected', (itemState: ItemState<any>) => {
|
|
||||||
res(itemState);
|
|
||||||
});
|
|
||||||
si.emitter.on('cancel', () => {
|
|
||||||
res(null);
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private open() {
|
|
||||||
panels.screen.append(this.box);
|
|
||||||
this.box.focus();
|
|
||||||
Game.current.clock.pause();
|
|
||||||
}
|
|
||||||
|
|
||||||
private close() {
|
|
||||||
Game.current.clock.resume();
|
|
||||||
panels.screen.remove(this.box);
|
|
||||||
}
|
|
||||||
|
|
||||||
get selectedItem(): ItemState<any> {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private constructor(filter: ItemFilterFunction, qty: number) {
|
|
||||||
this.emitter = new EventEmitter();
|
|
||||||
this.qty = qty;
|
|
||||||
this.box = blessed.box({
|
|
||||||
top: 'center',
|
|
||||||
left: 'center',
|
|
||||||
width: 'shrink',
|
|
||||||
height: 'shrink',
|
|
||||||
tags: true,
|
|
||||||
...boxStyle(),
|
|
||||||
});
|
|
||||||
this.box.on('keypress', (evt: {}, key: {full: string}) => {
|
|
||||||
if(key.full === 'escape') {
|
|
||||||
this.emitter.emit('cancel');
|
|
||||||
this.close();
|
|
||||||
} else if(key.full === 'enter') {
|
|
||||||
this.emitter.emit('selected', this.selectedItem.take(this.qty));
|
|
||||||
this.close();
|
|
||||||
} else if(key.full === 'down') {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.items = Game.current.inv.items.filter(filter);
|
|
||||||
this.update();
|
|
||||||
this.open();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
update() {
|
|
||||||
this.box.setContent('test');
|
|
||||||
panels.screen.render();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
import { Renderable, RenderMode } from './UI';
|
|
||||||
import { KeypressAcceptor } from './Menu.js';
|
|
||||||
import { getTheme } from '@themes';
|
|
||||||
|
|
||||||
export class SelectionBox implements Renderable, KeypressAcceptor {
|
|
||||||
|
|
||||||
selectedIdx = 0;
|
|
||||||
stuff: Renderable[] = [];
|
|
||||||
height: number;
|
|
||||||
offset: number = 0;
|
|
||||||
getData: () => Renderable[];
|
|
||||||
|
|
||||||
// buffer =
|
|
||||||
|
|
||||||
constructor(height: number) {
|
|
||||||
this.height = height;
|
|
||||||
}
|
|
||||||
|
|
||||||
setGetData(fn: () => Renderable[]) {
|
|
||||||
this.getData = fn;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return this.stuff.map((renderable, idx) => {
|
|
||||||
if(idx === this.selectedIdx) {
|
|
||||||
return ` ${getTheme().bright(` ❯ ${renderable.render(RenderMode.DETAILS)}`)} `;
|
|
||||||
} else {
|
|
||||||
return ` ${getTheme().normal(` ${renderable.render(RenderMode.DETAILS)}`)} `;
|
|
||||||
}
|
|
||||||
}).join('\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
up() {
|
|
||||||
this.selectedIdx --;
|
|
||||||
// if(this.selectedIdx === -1) {
|
|
||||||
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
down() {
|
|
||||||
this.selectedIdx ++;
|
|
||||||
}
|
|
||||||
|
|
||||||
keypress(key: { full: string; }) {
|
|
||||||
if(key.full === 'up') {
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
import { getTheme } from "@themes";
|
|
||||||
import { Game } from "../../Game.js";
|
|
||||||
import { progressbar } from "../../Progressbar.js";
|
|
||||||
import { PawnDetails } from "../PawnDetails.js";
|
|
||||||
import { panels } from "../UI";
|
|
||||||
import { View } from "../View.js";
|
|
||||||
|
|
||||||
export default class PawnsView extends View {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.name = 'Pawns';
|
|
||||||
}
|
|
||||||
|
|
||||||
keypress(key: { full: string; }) {
|
|
||||||
if (key.full === 'delete') {
|
|
||||||
Game.current.removePawn(Game.current.selected);
|
|
||||||
} else if (key.full === 'up') {
|
|
||||||
Game.current.advanceSelection(-1);
|
|
||||||
} else if (key.full === 'down') {
|
|
||||||
Game.current.advanceSelection(1);
|
|
||||||
} else if (key.full === 'enter') {
|
|
||||||
new PawnDetails(Game.current.selected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return `${
|
|
||||||
Game.current.pawns.map(pawn => `${(function() {
|
|
||||||
const selected = pawn === Game.current.selected;
|
|
||||||
let str = '';
|
|
||||||
if(selected) {
|
|
||||||
str += ` ${getTheme().bright(` ❯ ${pawn.toString()}`)}{|}${pawn.status} \n`;
|
|
||||||
// str += ` ${getTheme().normal('Energy')}{|}${progressbar(pawn.energy / 100, (panels.right.width - 4) / 2)} \n`;
|
|
||||||
} else {
|
|
||||||
str += ` ${getTheme().normal(pawn.toString())}{|}${pawn.status} `;
|
|
||||||
}
|
|
||||||
return str;
|
|
||||||
})()}`).join('\n')
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
import { Game } from "@game";
|
|
||||||
import { View } from "../View.js";
|
|
||||||
|
|
||||||
export default class WorldResourcesView extends View {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.name = 'World'
|
|
||||||
}
|
|
||||||
render(): string {
|
|
||||||
return `Explored: ${
|
|
||||||
Game.current.world.distanceExplored.toFixed(3)
|
|
||||||
} km\n${
|
|
||||||
Game.current.world.places?.map(place => ` ${
|
|
||||||
place.placeId
|
|
||||||
} (${place.x}, ${place.y})\n${
|
|
||||||
place.resources.map(resourceNode => ` ${
|
|
||||||
resourceNode.resources.render()
|
|
||||||
}`)
|
|
||||||
}`)
|
|
||||||
}`
|
|
||||||
}
|
|
||||||
keypress(key: { full: string; }): void {
|
|
||||||
if(key.full === 'enter') {
|
|
||||||
Game.current.world.home.resources[0].request(10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
declare module "neo-blessed";
|
|
||||||
|
|
@ -4,8 +4,8 @@ import { Game } from '../Game.js';
|
||||||
import { ItemState } from '../registries/Items.js';
|
import { ItemState } from '../registries/Items.js';
|
||||||
import { Player } from "../multiplayer/Player";
|
import { Player } from "../multiplayer/Player";
|
||||||
import { Pawn } from '../Pawn.js';
|
import { Pawn } from '../Pawn.js';
|
||||||
import { getTheme, boxStyle } from '@themes';
|
import { getTheme } from '../registries/Themes.js';
|
||||||
import { panels } from './UI.js';
|
import { boxStyle, panels } from './UI.js';
|
||||||
|
|
||||||
export class GiftPopup {
|
export class GiftPopup {
|
||||||
box;
|
box;
|
||||||
|
|
@ -25,11 +25,11 @@ export class GiftPopup {
|
||||||
tags: true,
|
tags: true,
|
||||||
...boxStyle(),
|
...boxStyle(),
|
||||||
});
|
});
|
||||||
this.box.on('keypress', (evt: {}, key: {full: string}) => {
|
this.box.on('keypress', (evt, key) => {
|
||||||
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.resume();
|
Game.current.clock.start();
|
||||||
panels.screen.remove(this.box);
|
panels.screen.remove(this.box);
|
||||||
} else if (key.full === 'up') {
|
} else if (key.full === 'up') {
|
||||||
this.selected --;
|
this.selected --;
|
||||||
|
|
@ -50,7 +50,7 @@ export class GiftPopup {
|
||||||
}
|
}
|
||||||
|
|
||||||
send() {
|
send() {
|
||||||
const stuffToSend: (Pawn | ItemState<any>)[] = [];
|
const stuffToSend: (Pawn | ItemState)[] = [];
|
||||||
for(const [pawn, qty] of this.pawns) {
|
for(const [pawn, qty] of this.pawns) {
|
||||||
if(qty === 0) continue;
|
if(qty === 0) continue;
|
||||||
stuffToSend.push(pawn);
|
stuffToSend.push(pawn);
|
||||||
|
|
@ -69,7 +69,7 @@ export class GiftPopup {
|
||||||
this.box.setContent(`${(() => {
|
this.box.setContent(`${(() => {
|
||||||
let pawns = [];
|
let pawns = [];
|
||||||
for (const [pawn, qty] of this.pawns.entries()) {
|
for (const [pawn, qty] of this.pawns.entries()) {
|
||||||
const style = i === this.selected ? getTheme().bright : getTheme().normal;
|
const style = i === this.selected ? getTheme().selected : getTheme().normal;
|
||||||
if(qty > 0) {
|
if(qty > 0) {
|
||||||
pawns.push(style(`{|}${pawn.toString()} `))
|
pawns.push(style(`{|}${pawn.toString()} `))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -10,10 +10,9 @@ import PawnsView from './view/PawnsView.js';
|
||||||
import InventoryView from './view/InventoryView.js';
|
import InventoryView from './view/InventoryView.js';
|
||||||
import MultiplayerView from './view/MultiplayerView.js';
|
import MultiplayerView from './view/MultiplayerView.js';
|
||||||
import { View } from './View.js';
|
import { View } from './View.js';
|
||||||
import WorldResourcesView from './view/WorldView.js';
|
import { ActionsView } from './view/ActionsView.js';
|
||||||
import { EscapeMenu } from './EscapeMenu.js';
|
|
||||||
|
|
||||||
const clamp = (min: number, max: number, value: number) => Math.min(Math.max(value, min), max);
|
const clamp = (min, max, value) => Math.min(Math.max(value, min), max);
|
||||||
|
|
||||||
// TODO move KeypressAcceptor to ui something idk
|
// TODO move KeypressAcceptor to ui something idk
|
||||||
export interface KeypressAcceptor {
|
export interface KeypressAcceptor {
|
||||||
|
|
@ -28,7 +27,7 @@ export class Menu implements Renderable {
|
||||||
new PawnsView(),
|
new PawnsView(),
|
||||||
new InventoryView(),
|
new InventoryView(),
|
||||||
new MultiplayerView(),
|
new MultiplayerView(),
|
||||||
new WorldResourcesView()
|
new ActionsView()
|
||||||
]
|
]
|
||||||
|
|
||||||
get view() {
|
get view() {
|
||||||
|
|
@ -46,20 +45,16 @@ export class Menu implements Renderable {
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
panels.right.on('keypress', (evt: {}, key: {full: string}) => {
|
panels.right.on('keypress', (evt, key) => {
|
||||||
|
|
||||||
if (key.full === 'left') {
|
if (key.full === 'left') {
|
||||||
this.regressView();
|
this.regressView();
|
||||||
} else if (key.full === 'right') {
|
} else if (key.full === 'right') {
|
||||||
this.advanceView();
|
this.advanceView();
|
||||||
} else if (key.full === 'escape') {
|
|
||||||
EscapeMenu.show();
|
|
||||||
|
|
||||||
// debugging hotkeys
|
// debugging hotkeys
|
||||||
} else if (key.full === '1') {
|
} else if (key.full === '1') {
|
||||||
Popup.show(inspect(stats));
|
Popup.show(inspect(stats));
|
||||||
} else if (key.full === '2') {
|
|
||||||
Popup.show(inspect(stats));
|
|
||||||
} else if (key.full === 'z') {
|
} else if (key.full === 'z') {
|
||||||
Game.current.pawns.push(new Pawn());
|
Game.current.pawns.push(new Pawn());
|
||||||
} else if (key.full === 'x') {
|
} else if (key.full === 'x') {
|
||||||
|
|
@ -112,6 +107,5 @@ export class Menu implements Renderable {
|
||||||
this.renderView(),
|
this.renderView(),
|
||||||
].join('\n');
|
].join('\n');
|
||||||
panels.right.setContent(content);
|
panels.right.setContent(content);
|
||||||
return '';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,8 +4,7 @@ import { Game } from '../Game.js';
|
||||||
import { stringify } from '../Memory.js';
|
import { stringify } from '../Memory.js';
|
||||||
import { Pawn } from '../Pawn.js';
|
import { Pawn } from '../Pawn.js';
|
||||||
import Time from '../Time.js';
|
import Time from '../Time.js';
|
||||||
import { panels } from './UI.js';
|
import { boxStyle, panels } from './UI.js';
|
||||||
import { boxStyle } from '@themes';
|
|
||||||
|
|
||||||
export class PawnDetails {
|
export class PawnDetails {
|
||||||
box;
|
box;
|
||||||
|
|
@ -21,9 +20,9 @@ export class PawnDetails {
|
||||||
tags: true,
|
tags: true,
|
||||||
...boxStyle(),
|
...boxStyle(),
|
||||||
});
|
});
|
||||||
this.box.on('keypress', (evt: {}, key: {full: string}) => {
|
this.box.on('keypress', (evt, key) => {
|
||||||
if(key.full === 'escape' || key.full === 'enter') {
|
if(key.full === 'escape' || key.full === 'enter') {
|
||||||
Game.current.clock.resume();
|
Game.current.clock.start();
|
||||||
panels.screen.remove(this.box);
|
panels.screen.remove(this.box);
|
||||||
} else if (key.full === 'up') {
|
} else if (key.full === 'up') {
|
||||||
// this.selected --;
|
// this.selected --;
|
||||||
|
|
@ -1,29 +1,29 @@
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import blessed from 'neo-blessed';
|
import blessed from 'neo-blessed';
|
||||||
import { Game } from '../Game.js';
|
import { Game } from '../Game.js';
|
||||||
import { boxStyle, getTheme } from '@themes';
|
import { getTheme } from '@themes';
|
||||||
import { panels } from './UI.js';
|
import { boxStyle, panels } from './UI.js';
|
||||||
|
|
||||||
export class Popup {
|
export class Popup {
|
||||||
box;
|
box;
|
||||||
|
|
||||||
static show(content: string) {
|
static show(content) {
|
||||||
new Popup(content)
|
new Popup(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected constructor(content: string) {
|
private constructor(content) {
|
||||||
this.box = blessed.box({
|
this.box = blessed.box({
|
||||||
top: 'center',
|
top: 'center',
|
||||||
left: 'center',
|
left: 'center',
|
||||||
width: 'shrink',
|
width: '100%',
|
||||||
height: 'shrink',
|
height: 'shrink',
|
||||||
content: getTheme().normal(content) + `\n\n{right}` + getTheme().hotkey('enter') + getTheme().normal(`: Okay `) + '{/right}',
|
content: getTheme().normal(content) + `\n\n{right}` + getTheme().hotkey('enter') + getTheme().normal(`: Okay `) + '{/right}',
|
||||||
tags: true,
|
tags: true,
|
||||||
...boxStyle(),
|
...boxStyle(),
|
||||||
});
|
});
|
||||||
this.box.on('keypress', (evt: {}, key: {full: string}) => {
|
this.box.on('keypress', (evt, key) => {
|
||||||
if(key.full === 'escape' || key.full === 'enter') {
|
if(key.full === 'escape' || key.full === 'enter') {
|
||||||
Game.current.clock.resume();
|
Game.current.clock.start();
|
||||||
panels.screen.remove(this.box);
|
panels.screen.remove(this.box);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -1,14 +1,32 @@
|
||||||
|
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import blessed from 'neo-blessed';
|
import blessed from 'neo-blessed';
|
||||||
import ansi from 'sisteransi';
|
import ansi from 'sisteransi';
|
||||||
import { boxStyle, getTheme } from '@themes';
|
import { getTheme } from '../registries/Themes.js';
|
||||||
export { Popup } from './Popup.js';
|
|
||||||
export { Menu } from './Menu.js'
|
|
||||||
|
|
||||||
export interface Renderable {
|
export interface Renderable {
|
||||||
render(mode?: RenderMode): string
|
render(): void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO move this to theme
|
||||||
|
export const boxStyle = () => {
|
||||||
|
return {
|
||||||
|
style: {
|
||||||
|
border: {
|
||||||
|
fg: getTheme().border.normal
|
||||||
|
},
|
||||||
|
focus: {
|
||||||
|
border: {
|
||||||
|
fg: getTheme().border.focused
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
border: {
|
||||||
|
type: 'line'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
let leftPanel: any;
|
let leftPanel: any;
|
||||||
let rightPanel: any;
|
let rightPanel: any;
|
||||||
let titleBar: any;
|
let titleBar: any;
|
||||||
|
|
@ -30,15 +48,15 @@ export function isStarted() {
|
||||||
|
|
||||||
export const panels = {
|
export const panels = {
|
||||||
get left() {
|
get left() {
|
||||||
assertStarted();
|
assertStarted()
|
||||||
return leftPanel;
|
return leftPanel;
|
||||||
},
|
},
|
||||||
get right() {
|
get right() {
|
||||||
assertStarted();
|
assertStarted()
|
||||||
return rightPanel;
|
return rightPanel;
|
||||||
},
|
},
|
||||||
get screen() {
|
get screen() {
|
||||||
assertStarted();
|
assertStarted()
|
||||||
return screen;
|
return screen;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -58,9 +76,6 @@ export function render(thing?: Renderable) {
|
||||||
|
|
||||||
export function start() {
|
export function start() {
|
||||||
assertNotStarted();
|
assertNotStarted();
|
||||||
|
|
||||||
// process.stdout.write('\x1b[?1049h');
|
|
||||||
|
|
||||||
screen = blessed.screen({
|
screen = blessed.screen({
|
||||||
smartCSR: true,
|
smartCSR: true,
|
||||||
terminal: 'xterm-256color'
|
terminal: 'xterm-256color'
|
||||||
|
|
@ -99,13 +114,12 @@ export function start() {
|
||||||
|
|
||||||
process.stdout.write(ansi.cursor.hide);
|
process.stdout.write(ansi.cursor.hide);
|
||||||
|
|
||||||
// todo make a real menu
|
screen.key(['C-c'], function(ch, key) {
|
||||||
// screen.key(['C-c'], function() {
|
process.stdout.write(ansi.cursor.show);
|
||||||
// process.stdout.write(ansi.cursor.show);
|
setTimeout(_ => {
|
||||||
// setTimeout(_ => {
|
process.exit(0);
|
||||||
// process.exit(0);
|
})
|
||||||
// })
|
});
|
||||||
// });
|
|
||||||
|
|
||||||
screen.key('f2', () => {
|
screen.key('f2', () => {
|
||||||
rightPanel.focus();
|
rightPanel.focus();
|
||||||
|
|
@ -119,13 +133,6 @@ export function start() {
|
||||||
setTitle('');
|
setTitle('');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stop() {
|
|
||||||
screen.destroy();
|
|
||||||
// process.stdout.write('\x1b[?1049l');
|
|
||||||
process.stdout.write(ansi.cursor.show);
|
|
||||||
}
|
|
||||||
|
|
||||||
// move to some debugging shit, idk
|
|
||||||
let ansiTestCard = '{center}';
|
let ansiTestCard = '{center}';
|
||||||
for(let i = 16; i < 34; i ++) ansiTestCard += chalk.bgAnsi256(i).black(` ${i.toString().padStart(3, ' ')} ${(i-15)%6===0?chalk.reset(' '):''}`); ansiTestCard += '\n';
|
for(let i = 16; i < 34; i ++) ansiTestCard += chalk.bgAnsi256(i).black(` ${i.toString().padStart(3, ' ')} ${(i-15)%6===0?chalk.reset(' '):''}`); ansiTestCard += '\n';
|
||||||
for(let i = 52; i < 70; i ++) ansiTestCard += chalk.bgAnsi256(i).black(` ${i.toString().padStart(3, ' ')} ${(i-15)%6===0?chalk.reset(' '):''}`); ansiTestCard += '\n';
|
for(let i = 52; i < 70; i ++) ansiTestCard += chalk.bgAnsi256(i).black(` ${i.toString().padStart(3, ' ')} ${(i-15)%6===0?chalk.reset(' '):''}`); ansiTestCard += '\n';
|
||||||
|
|
@ -147,9 +154,3 @@ ansiTestCard += '{/center}';
|
||||||
export {
|
export {
|
||||||
ansiTestCard
|
ansiTestCard
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum RenderMode {
|
|
||||||
ONELINE,
|
|
||||||
DETAILS,
|
|
||||||
DYNAMIC
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { Renderable } from './UI';
|
import { Renderable } from './UI.js';
|
||||||
import { KeypressAcceptor } from './Menu.js';
|
import { KeypressAcceptor } from './Menu.js';
|
||||||
|
|
||||||
export abstract class View implements Renderable, KeypressAcceptor {
|
export abstract class View implements Renderable, KeypressAcceptor {
|
||||||
abstract render(): string;
|
abstract render(): void;
|
||||||
abstract keypress(key: { full: string; }): void;
|
abstract keypress(key: { full: string; }): void;
|
||||||
|
|
||||||
name: string;
|
name: string;
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { getTheme } from "../../registries/Themes.js";
|
||||||
|
import { Game } from "../../Game.js";
|
||||||
|
import { Renderable } from "../UI.js";
|
||||||
|
import { View } from "../View.js";
|
||||||
|
import { actions } from "@actions";
|
||||||
|
|
||||||
|
export class ActionsView extends View {
|
||||||
|
actionIdx: number = 0;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.name = 'Actions';
|
||||||
|
}
|
||||||
|
|
||||||
|
keypress(key) {
|
||||||
|
if(key.full === 'up') {
|
||||||
|
this.actionIdx --;
|
||||||
|
} else if (key.full === 'down') {
|
||||||
|
this.actionIdx ++;
|
||||||
|
} else if (key.full === 'enter') {
|
||||||
|
actions[this.actionIdx].invoke(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
return actions.map((action, idx) => `${(() => {
|
||||||
|
if(this.actionIdx === idx) {
|
||||||
|
return getTheme().selected(' ❯ ' + action.name);
|
||||||
|
} else {
|
||||||
|
return getTheme().normal(' ' + action.name);
|
||||||
|
}
|
||||||
|
})()}`).join('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -25,7 +25,7 @@ export default class MultiplayerView extends View {
|
||||||
render() {
|
render() {
|
||||||
if(mdns.players.length === 0) return `{center}${getTheme().normal('No friends online')}{/center}`;
|
if(mdns.players.length === 0) return `{center}${getTheme().normal('No friends online')}{/center}`;
|
||||||
return mdns.players.map((player, i) => {
|
return mdns.players.map((player, i) => {
|
||||||
if(i === this.selected) return ' ' + getTheme().bright(' ❯ ' + player.toString());
|
if(i === this.selected) return ' ' + getTheme().selected(' ❯ ' + player.toString());
|
||||||
else return ' ' + getTheme().normal(player.toString());
|
else return ' ' + getTheme().normal(player.toString());
|
||||||
}).join('\n');
|
}).join('\n');
|
||||||
};
|
};
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { getTheme } from "@themes";
|
||||||
|
import { Game } from "../../Game.js";
|
||||||
|
import { progressbar } from "../../Progressbar.js";
|
||||||
|
import { PawnDetails } from "../PawnDetails.js";
|
||||||
|
import { panels } from "../UI.js";
|
||||||
|
import { View } from "../View.js";
|
||||||
|
|
||||||
|
export default class PawnsView extends View {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.name = 'Pawns';
|
||||||
|
}
|
||||||
|
|
||||||
|
keypress(key: { full: string; }) {
|
||||||
|
if (key.full === 'delete') {
|
||||||
|
Game.current.removePawn(Game.current.selected);
|
||||||
|
} else if (key.full === 'up') {
|
||||||
|
Game.current.advanceSelection(-1);
|
||||||
|
} else if (key.full === 'down') {
|
||||||
|
Game.current.advanceSelection(1);
|
||||||
|
} else if (key.full === 'enter') {
|
||||||
|
new PawnDetails(Game.current.selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return `${
|
||||||
|
Game.current.pawns.map(pawn => `${(function() {
|
||||||
|
const selected = pawn === Game.current.selected;
|
||||||
|
let str = '';
|
||||||
|
if(selected) {
|
||||||
|
str += ` ${getTheme().selected(` ❯ ${pawn.toString()}`)}{|}${pawn.status} \n`;
|
||||||
|
str += ` ${getTheme().normal('Energy')}{|}${progressbar(pawn.energy / 100, (panels.right.width - 4) / 2)} \n`;
|
||||||
|
} else {
|
||||||
|
str += ` ${getTheme().normal(pawn.toString())}{|}${pawn.status} `;
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
})()}`).join('\n')
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
import { Task } from "@tasks"
|
|
||||||
import { ResourceNode } from "@world";
|
|
||||||
|
|
||||||
type Data = {
|
|
||||||
qty: number,
|
|
||||||
node: ResourceNode
|
|
||||||
};
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
workCounter: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export const RESOURCE_COLLECTION_TASK =
|
|
||||||
new Task<Data, State>('core:resource-collection-task')
|
|
||||||
.setName('Collect Resources')
|
|
||||||
.setFunction(((taskState, dTime) => {
|
|
||||||
|
|
||||||
}))
|
|
||||||
.setToString((data, state) => {
|
|
||||||
return 'Collect ' +
|
|
||||||
data.qty +
|
|
||||||
' ' +
|
|
||||||
(data.qty === 1 ? data.node.resources.item.name.singular :
|
|
||||||
data.node.resources.item.name.plural) +
|
|
||||||
' from ' +
|
|
||||||
data.node.place.place.name;
|
|
||||||
})
|
|
||||||
.setInitiate((data: Data) => {
|
|
||||||
return {
|
|
||||||
workCounter: 0
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
@ -12,15 +12,11 @@
|
||||||
"@actions": ["./src/registries/Actions"],
|
"@actions": ["./src/registries/Actions"],
|
||||||
"@tasks": ["./src/registries/Tasks"],
|
"@tasks": ["./src/registries/Tasks"],
|
||||||
"@items": ["./src/registries/Items"],
|
"@items": ["./src/registries/Items"],
|
||||||
"@world": ["./src/World"],
|
|
||||||
"@ui": ["./src/qt/index"],
|
|
||||||
"@game": ["./src/Game"]
|
"@game": ["./src/Game"]
|
||||||
},
|
}
|
||||||
"noImplicitAny": true
|
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*.ts",
|
"src/**/*.ts",
|
||||||
"content/**/*.ts",
|
"content/**/*.ts"
|
||||||
"ui-engine/**/*.ts"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
export abstract class Node {
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
w: number;
|
|
||||||
h: number;
|
|
||||||
children: Node[]
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.children = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
layout(w: number, h: number) {
|
|
||||||
this.w = w;
|
|
||||||
this.h = h;
|
|
||||||
|
|
||||||
this.onResize(w, h);
|
|
||||||
|
|
||||||
this.children.forEach(node => node.layout(w, h));
|
|
||||||
}
|
|
||||||
|
|
||||||
append(child: Node) {
|
|
||||||
this.children.push(child);
|
|
||||||
this.layout(this.w, this.h);
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract render(x: number, y: number): [number, string];
|
|
||||||
|
|
||||||
abstract onResize(w: number, h: number): void
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class TextNode extends Node {
|
|
||||||
_content: string;
|
|
||||||
|
|
||||||
constructor(content: string) {
|
|
||||||
super();
|
|
||||||
this.content = content;
|
|
||||||
}
|
|
||||||
|
|
||||||
set content(val: string) {
|
|
||||||
this._content = val
|
|
||||||
this.layout(this.w, this.h);
|
|
||||||
}
|
|
||||||
|
|
||||||
onResize(w: number, h: number): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
render(x: number, y: number): [number, string] {
|
|
||||||
if(y !== this.y) return null;
|
|
||||||
if(x < this.x) return null;
|
|
||||||
if(x > this.x + this.content.length) return null;
|
|
||||||
return [0, this.content[x - this.x]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
import ansi from 'sisteransi';
|
|
||||||
import { Node, TextNode } from './Node.js';
|
|
||||||
|
|
||||||
|
|
||||||
const TERMINAL_HIGH_BUFFER = '\x1b[?1049h';
|
|
||||||
const TERMINAL_LOW_BUFFER = '\x1b[?1049l'
|
|
||||||
|
|
||||||
class Screen extends Node {
|
|
||||||
buffer: ArrayBuffer;
|
|
||||||
bufferView: Uint8Array;
|
|
||||||
paletteBuffer: ArrayBuffer;
|
|
||||||
paletteBufferView: Uint8Array;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
process.stdout.write(TERMINAL_HIGH_BUFFER);
|
|
||||||
process.stdout.write(ansi.cursor.hide);
|
|
||||||
this.layout(process.stdout.columns, process.stdout.rows);
|
|
||||||
}
|
|
||||||
|
|
||||||
onResize(w: number, h: number) {
|
|
||||||
this.buffer = new ArrayBuffer(w * h);
|
|
||||||
this.bufferView = new Uint8Array(this.buffer);
|
|
||||||
this.paletteBuffer = new ArrayBuffer(w * h);
|
|
||||||
this.paletteBufferView = new Uint8Array(this.paletteBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// paletteAt(x: number, y: number) {
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
offset(x: number, y: number) {
|
|
||||||
return y * this.h + x
|
|
||||||
}
|
|
||||||
|
|
||||||
updateTerminal() {
|
|
||||||
process.stdout.write(ansi.cursor.to(0, 0))
|
|
||||||
for(let y = 0; y < this.h; y ++) {
|
|
||||||
for(let x = 0; x < this.w; x ++) {
|
|
||||||
|
|
||||||
}
|
|
||||||
process.stdout.write('\r\n')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render(x: number, y: number): [number, string] {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
process.stdout.write(TERMINAL_LOW_BUFFER);
|
|
||||||
process.stdout.write(ansi.cursor.show);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// class Palette {
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
const screen = new Screen();
|
|
||||||
screen.append(new TextNode('Test'))
|
|
||||||
Loading…
Reference in New Issue