ayut
parent
d4c008a5e1
commit
dc39db5b18
|
|
@ -1,30 +0,0 @@
|
||||||
import { registerAction } from '@actions';
|
|
||||||
import { Game } from '@game';
|
|
||||||
import { ItemState } from '@items';
|
|
||||||
import { TaskState } from '@tasks';
|
|
||||||
import { SelectItem } from '../../../src/ui/SelectItem.js';
|
|
||||||
import { FLINT_NORMAL } from '../items/CoreItems.js';
|
|
||||||
import { GATHER_FLINT, MAKE_ARROWHEAD } from '../tasks/CoreTasks.js';
|
|
||||||
|
|
||||||
// registerAction('Gather Slate', (qty) => {
|
|
||||||
// Game.current.board.addTask({
|
|
||||||
// taskId: 'core:gather-slate',
|
|
||||||
// options: {}
|
|
||||||
// })
|
|
||||||
// });
|
|
||||||
|
|
||||||
registerAction('Gather Flint', () => {
|
|
||||||
const taskState = new TaskState(GATHER_FLINT);
|
|
||||||
Game.current.board.addTask(taskState);
|
|
||||||
});
|
|
||||||
|
|
||||||
registerAction('Create Arrowhead', () => {
|
|
||||||
// const rock = new ItemState(FLINT_NORMAL, 1, null);
|
|
||||||
const item = SelectItem.show()
|
|
||||||
const task = new TaskState(MAKE_ARROWHEAD, {
|
|
||||||
baseMaterial: rock
|
|
||||||
});
|
|
||||||
|
|
||||||
Game.current.board.addTask(task);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
@ -56,7 +56,7 @@ export const SANDSTONE_PEBBLE = new Item()
|
||||||
.setName("Sandstone Pebble")
|
.setName("Sandstone Pebble")
|
||||||
.setId('core:sandstone-pebble')
|
.setId('core:sandstone-pebble')
|
||||||
|
|
||||||
export const SLATE_NORMAL = new Item()
|
export const SLATE = new Item()
|
||||||
.setName("Slate")
|
.setName("Slate")
|
||||||
.setId('core:slate')
|
.setId('core:slate')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,42 @@
|
||||||
import { Task } from "@tasks";
|
import { Task } from "@tasks";
|
||||||
import { Game } from '@game';
|
import { Game } from '@game';
|
||||||
import { ARROWHEAD, FLINT_NORMAL, SLATE_NORMAL } from '../items/CoreItems.js';
|
import { ARROWHEAD, FLINT_NORMAL, SLATE, STICK } from '../items/CoreItems.js';
|
||||||
import { ItemState } from "@items";
|
import { ItemState } from "@items";
|
||||||
import { Popup } from "../../../src/ui/Popup.js";
|
import { Popup } from "../../../src/ui/Popup.js";
|
||||||
import { inspect } from 'util';
|
import { inspect } from 'util';
|
||||||
|
import { Place, ResourceNode } from "@world";
|
||||||
|
|
||||||
export const GATHER_FLINT = new Task('core:gather-flint')
|
// export const GATHER_FLINT = new Task('core:gather-flint')
|
||||||
.setName('Gather Flint')
|
// .setName('Gather Flint')
|
||||||
.setStatus('SCAVENGING')
|
// .setStatus('SCAVENGING')
|
||||||
.setWork(1000)
|
// .setWork(1000)
|
||||||
.setTasklistVisibility(true)
|
// .setTasklistVisibility(true)
|
||||||
.setCategory("work")
|
// .setCategory("work")
|
||||||
.setCompletionEvent(() => {
|
// .setCompletionEvent(() => {
|
||||||
const qty = Math.floor(Math.random() * 5) + 1;
|
// const qty = Math.floor(Math.random() * 5) + 1;
|
||||||
Game.current.inv.add(new ItemState(FLINT_NORMAL, 1, null));
|
// Game.current.inv.add(new ItemState(FLINT_NORMAL, 1, null));
|
||||||
});
|
// });
|
||||||
|
|
||||||
export const MAKE_ARROWHEAD = new Task<{
|
// export const MAKE_ARROWHEAD = new Task<{
|
||||||
baseMaterial: ItemState<any>
|
// baseMaterial: ItemState<any>
|
||||||
}>('core:gather-slate')
|
// }>('core:gather-slate')
|
||||||
.setName('Craft Arrowhead')
|
// .setName('Craft Arrowhead')
|
||||||
.setStatus('CRAFTING')
|
// .setStatus('CRAFTING')
|
||||||
.setWork(1000)
|
// .setWork(1000)
|
||||||
.setTasklistVisibility(true)
|
// .setTasklistVisibility(true)
|
||||||
.setCategory("craft")
|
// .setCategory("craft")
|
||||||
.setCompletionEvent((data) => {
|
// .setCompletionEvent((data) => {
|
||||||
const itemState = new ItemState(ARROWHEAD, 1, {
|
// const itemState = new ItemState(ARROWHEAD, 1, {
|
||||||
baseMaterial: data.baseMaterial
|
// baseMaterial: data.baseMaterial
|
||||||
});
|
// });
|
||||||
Game.current.inv.add(itemState);
|
// Game.current.inv.add(itemState);
|
||||||
});
|
// });
|
||||||
|
|
||||||
|
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,12 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>df-idle</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="module" src="./out/index.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -5,6 +5,7 @@ const moduleAliases = {
|
||||||
"@actions": "./out/src/registries/Actions.js",
|
"@actions": "./out/src/registries/Actions.js",
|
||||||
"@tasks": "./out/src/registries/Tasks.js",
|
"@tasks": "./out/src/registries/Tasks.js",
|
||||||
"@items": "./out/src/registries/Items.js",
|
"@items": "./out/src/registries/Items.js",
|
||||||
|
"@world": "./out/src/World.js",
|
||||||
"@ui": "./out/src/ui/UI.js",
|
"@ui": "./out/src/ui/UI.js",
|
||||||
"@game": "./out/src/Game.js"
|
"@game": "./out/src/Game.js"
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
"@types/mocha": "^8.2.2",
|
"@types/mocha": "^8.2.2",
|
||||||
"@types/uuid": "^8.3.0",
|
"@types/uuid": "^8.3.0",
|
||||||
"@types/ws": "^7.4.5",
|
"@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",
|
||||||
|
|
@ -42,6 +43,7 @@
|
||||||
"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",
|
||||||
"profile": "node --inspect --no-warnings --loader ./lib/aliases.mjs --enable-source-maps out/src/index.js"
|
"web": "web-dev-server --open index.html --node-resolve --watch",
|
||||||
|
"profile": "node --inspect-brk --no-warnings --loader ./lib/aliases.mjs --enable-source-maps out/src/index.js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import Time, { Tickable } from './Time.js';
|
||||||
import { render, Renderable, setTitle, start } from './ui/UI.js';
|
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: Game = null;
|
||||||
|
|
||||||
|
|
@ -19,6 +20,7 @@ export class Game extends Frigid implements Tickable, Renderable {
|
||||||
menu: Menu;
|
menu: Menu;
|
||||||
clock: Time;
|
clock: Time;
|
||||||
name: string;
|
name: string;
|
||||||
|
world: World;
|
||||||
|
|
||||||
[DEBUG] = true;
|
[DEBUG] = true;
|
||||||
|
|
||||||
|
|
@ -57,6 +59,7 @@ export class Game extends Frigid implements Tickable, Renderable {
|
||||||
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.menu = new Menu();
|
||||||
|
|
@ -71,7 +74,7 @@ export class Game extends Frigid implements Tickable, Renderable {
|
||||||
}
|
}
|
||||||
|
|
||||||
static serializationDependencies() {
|
static serializationDependencies() {
|
||||||
return [ Pawn, Inventory, TaskList, Time ];
|
return [ Pawn, Inventory, TaskList, Time, World ];
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
||||||
82
src/Pawn.ts
82
src/Pawn.ts
|
|
@ -1,19 +1,12 @@
|
||||||
import { Serializable } from 'frigid';
|
import { Serializable } from 'frigid';
|
||||||
import faker from 'faker';
|
import faker from 'faker';
|
||||||
import { TaskState } from './registries/Tasks.js';
|
import { Task, TaskState } 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 } from './ui/UI.js';
|
import { render } from './ui/UI.js';
|
||||||
import { Memory } from './Memory.js';
|
import { Memory } from './Memory.js';
|
||||||
import { getTheme } from '@themes';
|
import { getTheme } from '@themes';
|
||||||
|
|
||||||
// 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
|
||||||
|
|
||||||
export class Pawn extends Serializable implements Tickable {
|
export class Pawn extends Serializable implements Tickable {
|
||||||
|
|
@ -21,54 +14,18 @@ export class Pawn extends Serializable implements Tickable {
|
||||||
first: string,
|
first: string,
|
||||||
last: string
|
last: string
|
||||||
};
|
};
|
||||||
job: TaskState<any>;
|
|
||||||
awake: boolean;
|
|
||||||
sex: number;
|
sex: number;
|
||||||
|
|
||||||
energy: number;
|
|
||||||
fun: number;
|
|
||||||
|
|
||||||
age: number;
|
age: number;
|
||||||
|
|
||||||
memories: Memory[];
|
memories: Memory[];
|
||||||
|
|
||||||
|
job: TaskState<unknown>;
|
||||||
|
|
||||||
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 && this.awake;
|
return !this.job;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctor() {
|
ctor() {
|
||||||
|
|
@ -80,8 +37,6 @@ 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));
|
||||||
|
|
@ -96,30 +51,29 @@ 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.stopJob();
|
// this.job.unclaim(this);
|
||||||
this.job = null;
|
this.job = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assignJob(task: TaskState<any>) {
|
|
||||||
this.job?.stopJob()
|
|
||||||
this.job = task;
|
|
||||||
this.job.claim(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
get status() {
|
get status() {
|
||||||
if(this.job) {
|
return 'SEMETHING';
|
||||||
return this.job.task.status;
|
// if(this.job) {
|
||||||
} else {
|
// return this.job.task.status;
|
||||||
return this.awake ? getTheme().status.idle('IDLE') : getTheme().status.self('RESTING')
|
// } else {
|
||||||
}
|
// return this.awake ? getTheme().status.idle('IDLE') : getTheme().status.self('RESTING')
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
static serializationDependencies() {
|
static serializationDependencies() {
|
||||||
|
|
@ -134,3 +88,5 @@ export class Pawn extends Serializable implements Tickable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// const task =
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
|
import { getTheme } from '@themes';
|
||||||
import { Serializable } from 'frigid';
|
import { Serializable } from 'frigid';
|
||||||
import { Task, TaskState } from './registries/Tasks.js';
|
import { Task, TaskState } from './registries/Tasks.js';
|
||||||
import { render, Renderable, panels } from './ui/UI.js';
|
import { render, Renderable, panels } from './ui/UI.js';
|
||||||
|
|
||||||
const taskTypes = {};
|
|
||||||
|
|
||||||
export class TaskList extends Serializable implements Renderable {
|
export class TaskList extends Serializable implements Renderable {
|
||||||
tasks: TaskState<any>[] = [];
|
tasks: TaskState<unknown, unknown>[] = [];
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
for(const task of this.tasks) {
|
for(const task of this.tasks) {
|
||||||
|
|
@ -13,27 +12,34 @@ export class TaskList extends Serializable implements Renderable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static serializationDependencies() {
|
static serializationDependencies(): any[] {
|
||||||
return [TaskState]
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
addTask(task: TaskState<any>) {
|
// TODO assign task dependant on pawn skills
|
||||||
|
getUnclaimedTask(): TaskState<unknown, unknown> | null {
|
||||||
|
// const availableTasks = this.tasks.filter(task => !task.claimed);
|
||||||
|
// if(availableTasks.length > 0) {
|
||||||
|
// return availableTasks[0]
|
||||||
|
// } else return null;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
addTask(task: TaskState<unknown, unknown>) {
|
||||||
this.tasks = [...this.tasks, task];
|
this.tasks = [...this.tasks, task];
|
||||||
}
|
}
|
||||||
|
|
||||||
removeTask(task: TaskState<any>) {
|
removeTask(task: TaskState<unknown, unknown>) {
|
||||||
this.tasks = this.tasks.filter(v => v !== task);
|
this.tasks = this.tasks.filter(v => v !== task);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
// const width = tasksPanel.width;
|
// const width = tasksPanel.width;
|
||||||
panels.left.setContent(`${this.tasks.map(task => {
|
panels.left.setContent(`${this.tasks.map(task => `${
|
||||||
return task.toString();
|
getTheme().normal(task.toString())
|
||||||
}).join('\n')}`);
|
} ${
|
||||||
|
getTheme().dimmed(task.worker?.toString() ?? '')
|
||||||
|
}`).join('\n')}`);
|
||||||
// return this.tasks.map(task => task.toString()).join('\n');
|
// return this.tasks.map(task => task.toString()).join('\n');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// export function registerTask(name, clazz) {
|
|
||||||
// taskTypes[name] = clazz;
|
|
||||||
// }
|
|
||||||
138
src/World.ts
138
src/World.ts
|
|
@ -1,5 +1,143 @@
|
||||||
|
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";
|
||||||
|
|
||||||
export class World extends Serializable {
|
export class World extends Serializable {
|
||||||
|
places: PlaceState[] = [];
|
||||||
|
|
||||||
|
distanceExplored = 0; // km ish
|
||||||
|
explorationConstant = 0.001; // km ish
|
||||||
|
|
||||||
|
home: PlaceState = null;
|
||||||
|
|
||||||
|
static serializationDependencies() {
|
||||||
|
return [PlaceState];
|
||||||
|
}
|
||||||
|
|
||||||
|
explore() {
|
||||||
|
for(const [id, place] of places) {
|
||||||
|
const threshold = (this.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 += this.explorationConstant;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctor() {
|
||||||
|
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
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
21
src/index.ts
21
src/index.ts
|
|
@ -8,6 +8,7 @@ import { Game } from '@game';
|
||||||
import { isStarted, stop } from './ui/UI.js';
|
import { isStarted, stop } from './ui/UI.js';
|
||||||
import { writeFileSync } from 'fs';
|
import { writeFileSync } from 'fs';
|
||||||
|
|
||||||
|
console.clear();
|
||||||
|
|
||||||
function gracefulShutdown() {
|
function gracefulShutdown() {
|
||||||
if(isStarted()) {
|
if(isStarted()) {
|
||||||
|
|
@ -34,19 +35,27 @@ console.log('df-idle: Loading extensions');
|
||||||
const extensionsPath = resolve(parse(fileURLToPath(import.meta.url)).dir, '../content');
|
const extensionsPath = resolve(parse(fileURLToPath(import.meta.url)).dir, '../content');
|
||||||
|
|
||||||
const extensions = walkSync(extensionsPath)
|
const extensions = walkSync(extensionsPath)
|
||||||
.map(path => resolve(extensionsPath, path))
|
.map(path => [path, resolve(extensionsPath, path)])
|
||||||
.filter(path => lstatSync(path).isFile())
|
.filter(path => lstatSync(path[1]).isFile())
|
||||||
.filter(path => parse(path).ext === '.js');
|
.filter(path => parse(path[1]).ext === '.js');
|
||||||
// .map(path => import(path));
|
|
||||||
|
|
||||||
console.log('found', extensions.length, 'extensions');
|
console.log('found', extensions.length, 'extensions');
|
||||||
|
|
||||||
for(const path of extensions) {
|
for(const path of extensions) {
|
||||||
console.log('=== [', path, '] ===');
|
console.log('=== [', path[0], '] ===');
|
||||||
await import(path);
|
await import(path[1]);
|
||||||
console.log();
|
console.log();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('Setup Complete.');
|
||||||
|
|
||||||
|
|
||||||
|
for(let seconds = 2; seconds > 0; seconds --) {
|
||||||
|
process.stdout.write('Starting DF-Idle in ' + seconds + '\r');
|
||||||
|
await new Promise(res => setTimeout(res, 1000));
|
||||||
|
}
|
||||||
|
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.
|
||||||
|
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
import { getTheme } from "@themes";
|
|
||||||
import { Renderable } from "../ui/UI.js";
|
|
||||||
|
|
||||||
export const actions: Action[] = [];
|
|
||||||
|
|
||||||
export function registerAction(name: string, invoke: () => 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -7,7 +7,7 @@ export type ItemID = string;
|
||||||
const items = new Map<ItemID, Item<any>>();
|
const items = new Map<ItemID, Item<any>>();
|
||||||
|
|
||||||
// ITEMS SHALL BE SINGULAR
|
// ITEMS SHALL BE SINGULAR
|
||||||
export class Item<Data> extends Serializable {
|
export class Item<Data = any> extends Serializable {
|
||||||
|
|
||||||
name = '';
|
name = '';
|
||||||
id: ItemID = '';
|
id: ItemID = '';
|
||||||
|
|
@ -60,7 +60,7 @@ export class ItemState<Data> extends Serializable implements Renderable {
|
||||||
return items.get(this.itemId);
|
return items.get(this.itemId);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(item: Item<Data>, amount: number, data: Data) {
|
constructor(item: Item<Data>, amount: number, data: Data = null) {
|
||||||
super();
|
super();
|
||||||
this.qty = amount;
|
this.qty = amount;
|
||||||
this.itemId = item.id;
|
this.itemId = item.id;
|
||||||
|
|
|
||||||
|
|
@ -6,119 +6,94 @@ import { Game } from '../Game.js';
|
||||||
import { panels } from '../ui/UI.js';
|
import { panels } from '../ui/UI.js';
|
||||||
import { progressbar, ProgressbarStyle } from '../Progressbar.js';
|
import { progressbar, ProgressbarStyle } from '../Progressbar.js';
|
||||||
|
|
||||||
export const tasks: Map<string, Task<any>> = new Map();
|
const tasks: Map<string, Task<unknown, unknown>> = new Map();
|
||||||
export type TaskCategory = "self" | "work" | "craft" | "idle";
|
type WorkFunction<Data, State> = (taskState: TaskState<Data, State>, dtime: number) => void;
|
||||||
|
type InitFunction<Data, State> = (data: Data) => State;
|
||||||
|
|
||||||
export class Task<Data> {
|
export class Task<Data, State> {
|
||||||
id: string;
|
id: string;
|
||||||
work: number;
|
fn: WorkFunction<Data, State>;
|
||||||
name: string | ((data: Data) => string);
|
init: InitFunction<Data, State>;
|
||||||
status: string;
|
name: string;
|
||||||
tasklistVisibility: boolean;
|
toString: (data: Data, state: State) => string;
|
||||||
category: TaskCategory;
|
|
||||||
completionEvent: (data: Data) => void;
|
|
||||||
|
|
||||||
setTasklistVisibility(b: boolean) {
|
constructor(id: string) {
|
||||||
this.tasklistVisibility = b;
|
this.id = id;
|
||||||
|
tasks.set(id, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
setInitiate(init: InitFunction<Data, State>) {
|
||||||
|
this.init = init;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setName(name: string | ((data: Data) => string)) {
|
setFunction(fn: WorkFunction<Data, State>) {
|
||||||
|
this.fn = fn;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
setName(name: string) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setStatus(s: string) {
|
setToString(fn: (data: Data, state: State) => string) {
|
||||||
this.status = s;
|
this.toString = fn;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setCategory(c: TaskCategory) {
|
|
||||||
this.category = c;
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setWork(n: number) {
|
export class TaskState<Data, State> extends Serializable {
|
||||||
this.work = n;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setCompletionEvent(fn: (data: Data) => void) {
|
|
||||||
this.completionEvent = fn;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(id: string) {
|
|
||||||
this.id = id;
|
|
||||||
this.tasklistVisibility = true;
|
|
||||||
this.category = "work";
|
|
||||||
tasks.set(this.id, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class TaskState<T> extends Serializable {
|
|
||||||
taskId: string;
|
taskId: string;
|
||||||
progress: number;
|
workFn: WorkFunction<Data, State>;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
data: Data;
|
||||||
|
completed: boolean = false;
|
||||||
worker: Pawn;
|
worker: Pawn;
|
||||||
data: T;
|
state: State;
|
||||||
|
|
||||||
ctor() {
|
constructor(task: Task<Data, State>, data: Data) {
|
||||||
// retest completion when loaded, JUST IN CASE
|
|
||||||
this.testCompletion();
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(task: Task<T>, data: T = {} as T) {
|
|
||||||
super();
|
super();
|
||||||
this.taskId = task.id;
|
|
||||||
this.progress = 0;
|
|
||||||
this.worker = null;
|
|
||||||
// preset the data to nothing JIC
|
|
||||||
this.data = data;
|
this.data = data;
|
||||||
|
this.taskId = task.id;
|
||||||
|
this.workFn = this.task.fn.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
stopJob() {
|
setLocation(x: number, y: number) {
|
||||||
this.worker = null;
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
doWork(work = 1, pawn: Pawn) {
|
get task(): Task<Data, State> {
|
||||||
this.worker = pawn;
|
// casting because the id this is associated with
|
||||||
this.progress += work;
|
// should have the datatype of the task it was
|
||||||
this.testCompletion();
|
// created with, but it stored as unknown
|
||||||
|
return tasks.get(this.taskId) as Task<Data, State>;
|
||||||
}
|
}
|
||||||
|
|
||||||
testCompletion() {
|
get name() {
|
||||||
if (this.taskId && this.completed) {
|
return this.task.name;
|
||||||
this.task.completionEvent(this.data);
|
|
||||||
Game.current.board.removeTask(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
free() {
|
work(dtime: number) {
|
||||||
this.worker = null;
|
this.workFn(this, dtime);
|
||||||
}
|
}
|
||||||
|
|
||||||
claim(pawn: Pawn) {
|
claim(pawn: Pawn) {
|
||||||
this.worker = pawn;
|
this.worker = pawn;
|
||||||
}
|
}
|
||||||
|
|
||||||
get completion() {
|
unclaim() {
|
||||||
return Math.min(1, this.progress / this.task.work);
|
this.worker = null;
|
||||||
}
|
|
||||||
|
|
||||||
get task() {
|
|
||||||
return tasks.get(this.taskId);
|
|
||||||
}
|
|
||||||
|
|
||||||
get completed() {
|
|
||||||
return this.completion >= 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
// HACK magic number 2 here, is the border
|
return this.task.toString(this.data, this.state)
|
||||||
// of the panel
|
|
||||||
const width = panels.left.width - 2;
|
|
||||||
const left = ' ' + this.task.name + ' ' + (this.worker?.toString() || chalk.bold.black('Queued'));
|
|
||||||
const bar = width - 2;
|
|
||||||
return `${left}\n ${progressbar(this.completion, bar, ProgressbarStyle.progress)}\n`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// export interface TaskProvider {
|
||||||
|
// hasTask(): boolean;
|
||||||
|
// getTask(): TaskState<unknown, unknown>;
|
||||||
|
// }
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ 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 { ActionsView } from './view/ActionsView.js';
|
import { ActionsView } from './view/ActionsView.js';
|
||||||
import { tasks } from '@tasks';
|
import WorldResourcesView from './view/WorldView.js';
|
||||||
|
|
||||||
const clamp = (min: number, max: number, value: number) => Math.min(Math.max(value, min), max);
|
const clamp = (min: number, max: number, value: number) => Math.min(Math.max(value, min), max);
|
||||||
|
|
||||||
|
|
@ -28,7 +28,8 @@ export class Menu implements Renderable {
|
||||||
new PawnsView(),
|
new PawnsView(),
|
||||||
new InventoryView(),
|
new InventoryView(),
|
||||||
new MultiplayerView(),
|
new MultiplayerView(),
|
||||||
new ActionsView()
|
new ActionsView(),
|
||||||
|
new WorldResourcesView()
|
||||||
]
|
]
|
||||||
|
|
||||||
get view() {
|
get view() {
|
||||||
|
|
@ -59,8 +60,6 @@ export class Menu implements Renderable {
|
||||||
} else if (key.full === '1') {
|
} else if (key.full === '1') {
|
||||||
Popup.show(inspect(stats));
|
Popup.show(inspect(stats));
|
||||||
} else if (key.full === '2') {
|
} else if (key.full === '2') {
|
||||||
Popup.show(inspect(tasks));
|
|
||||||
} else if (key.full === '3') {
|
|
||||||
Popup.show(inspect(stats));
|
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());
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ 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(): void;
|
abstract render(): string;
|
||||||
abstract keypress(key: { full: string; }): void;
|
abstract keypress(key: { full: string; }): void;
|
||||||
|
|
||||||
name: string;
|
name: string;
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ export default class PawnsView extends View {
|
||||||
let str = '';
|
let str = '';
|
||||||
if(selected) {
|
if(selected) {
|
||||||
str += ` ${getTheme().bright(` ❯ ${pawn.toString()}`)}{|}${pawn.status} \n`;
|
str += ` ${getTheme().bright(` ❯ ${pawn.toString()}`)}{|}${pawn.status} \n`;
|
||||||
str += ` ${getTheme().normal('Energy')}{|}${progressbar(pawn.energy / 100, (panels.right.width - 4) / 2)} \n`;
|
// str += ` ${getTheme().normal('Energy')}{|}${progressbar(pawn.energy / 100, (panels.right.width - 4) / 2)} \n`;
|
||||||
} else {
|
} else {
|
||||||
str += ` ${getTheme().normal(pawn.toString())}{|}${pawn.status} `;
|
str += ` ${getTheme().normal(pawn.toString())}{|}${pawn.status} `;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
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.node.resources.item.name + ' from ' + data.node.place.place.name;
|
||||||
|
})
|
||||||
|
.setInitiate((data: Data) => {
|
||||||
|
return {
|
||||||
|
workCounter: 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
"@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/ui/UI"],
|
"@ui": ["./src/ui/UI"],
|
||||||
"@game": ["./src/Game"]
|
"@game": ["./src/Game"]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue