- 🌳
+ 🌳
{number}.
+ onClick={linkClick} />
)
:
"..."
diff --git a/server/db_scripts/dump_24-10-10.js b/server/db_scripts/dump_24-10-10.js
new file mode 100644
index 00000000..149d449c
--- /dev/null
+++ b/server/db_scripts/dump_24-10-10.js
@@ -0,0 +1,11 @@
+const sqlite = require("better-sqlite3");
+const db = new sqlite('the_big_db.db', { verbose: console.log });
+
+const fs = require('node:fs');
+
+const dump = {
+ pages: db.prepare('select * from pages').all(),
+ users: db.prepare('select * from users').all()
+};
+
+fs.writeFile(`dump_${new Date().toISOString()}.json`, JSON.stringify(dump), (err) => console.log(err) );
\ No newline at end of file
diff --git a/server/db_scripts/initialize_db-24-10-10.1.js b/server/db_scripts/initialize_db-24-10-10.1.js
new file mode 100644
index 00000000..9be4ca9f
--- /dev/null
+++ b/server/db_scripts/initialize_db-24-10-10.1.js
@@ -0,0 +1,187 @@
+// wipe and load the database from a dump file created by dump_blahblah.js
+const hljs = require('highlight.js/lib/core');
+hljs.registerLanguage('lua', require('highlight.js/lib/languages/lua'));
+
+const fs = require('node:fs');
+const sqlite = require("better-sqlite3");
+const showdown = require("showdown");
+const db = new sqlite('the_big_db.db', { verbose: console.log });
+
+const converter = new showdown.Converter();
+
+const dump_file_name = process.argv[2]
+//get argument out of 'node db_scripts/initialize_db-blah-blah.js dumpfile'
+
+let data = "{}";
+try {
+ data = fs.readFileSync(dump_file_name, 'utf8');
+} catch (error) {
+ console.log(error);
+ return;
+}
+//open that file as text (unicode i guess?)
+console.log("opened file");
+const filejson = JSON.parse(data);
+//parse it as json
+
+
+const pages = filejson["pages"];
+
+const createPages = db.prepare(`
+create table if not exists pages (
+ id integer primary key,
+ number integer,
+
+ title varchar(255),
+
+ description text,
+ html text,
+ lua text,
+
+ time timestamp default current_timestamp,
+ author integer,
+
+ type integer
+)
+`);
+/* an object has:
+ id = primary key
+ number = object identifier number
+
+ title / NAME = a short display name for an object, or the title of a page
+ description / TEXT? = the markdown description of an object, or the lua script of the source code of a verb
+
+ html / HTML_MARKDOWN? = the markdown for that object, rendered into html for display as a page
+ HTML_LUA? = the lua source code of the object, rendered into html with syntax highlighting?
+
+ time = when this edit was saved
+ author = the user id of whoever authored this revision
+
+ TYPE = whether this represents an object or a verb
+*/
+
+function migratePages(pages) {
+ console.log("moving old table to a temporary");
+ console.log(db.prepare(`alter table pages rename to old_pages`).run())
+
+ console.log("creating new page table")
+ console.log(createPages.run());
+
+ console.log('iterating over dump pages');
+
+ const insertPage = db.prepare(`insert into pages
+ (id, number, title, description, html, lua, time, author, type)
+ values (:id, :number, :title, :description, :html, :lua, :time, :author, :type)`);
+
+ pages.forEach((pageData) => {
+ let {id, number, description} = pageData;
+ console.log(`rendering page number ${number}.${id}`)
+ description = description.replace(/ /g, "\n");
+
+ const renderedPage = converter.makeHtml(description);
+
+ const highlightedLua = hljs.highlight(description, {language: 'lua'}).value;
+
+ args = { ...pageData, description: description, html: renderedPage, lua: highlightedLua, type: "noun"};
+
+ console.log("inserting args", args);
+ insertPage.run(args);
+ });
+
+ console.log("getting rid of old table");
+ console.log(db.prepare("drop table old_pages").run());
+
+ //throw new Error("blahhh i'm a dracula");
+}
+try {
+ db.transaction(migratePages)(filejson["pages"]);
+} catch (error) {
+ console.log(error);
+}
+
+const createAttributesQuery = db.prepare(`
+create table if not exists attributes (
+ number integer unique,
+ contents json
+)
+`);
+/* an attribute store has:
+ number = the object number it's attached to
+ contents = a `text` object holding a json store of attributes defined on object #number, containing:
+ LOCATION? = the object which contains this object? should this be in the DB or in the json store?
+ PROTOTYPE = the number of the prototype for this object, to which we should look for attributes not found here
+ ATTRIBUTES? = json object of attached, script-controlled attributes.
+
+ CONTENTS = json array of objectss contained inside this one
+ VERBS = json array of objecsts which are verbs on this one
+*/
+function createAttributes() {
+ console.log("creating new attribute table")
+ console.log(createAttributesQuery.run());
+
+ console.log('iterating over all pages');
+
+ const insertAttribute = db.prepare(`insert into attributes
+ (number, contents) values (:number, :contents)`);
+
+ db.prepare(`select * from pages`).all().forEach((pageData) => {
+ let { number } = pageData;
+
+ let args = {contents: JSON.stringify({}), number: number};
+
+ console.log("inserting args", args);
+ insertAttribute.run(args);
+ });
+
+ //throw new Error("blahhh i'm a dracula");
+}
+
+try {
+ db.transaction(createAttributes)();
+} catch (error) {
+ console.log(error);
+}
+
+
+
+const createUsers = db.prepare(`
+create table if not exists users (
+ id integer primary key,
+
+ name varchar(64) unique,
+ password varchar(128),
+
+ character integer
+)
+`);
+/* a user has:
+ id = primary key
+ name = name string
+ password = argon2 hash of their password
+
+ character = object in the game world representing your character
+*/
+
+
+
+function migrateUsers(users) {
+ console.log("moving old users");
+ console.log(db.prepare("alter table users rename to old_users").run());
+
+ console.log("creating new users table");
+ console.log(createUsers.run());
+
+ const insertUser = db.prepare("insert into users (name, password) values (:name, :password)")
+ users.forEach((user) => {
+ console.log(insertUser.run(user));
+ });
+
+ console.log("clearing old table");
+ console.log(db.prepare("drop table old_users").run());
+ //throw new Error("i'm dracula 2");
+}
+try {
+ db.transaction(migrateUsers)(filejson["users"]);
+} catch (error) {
+ console.log(error);
+}
\ No newline at end of file
diff --git a/server/db_scripts/initialize_db.js b/server/db_scripts/initialize_db.js
index c0f3ca73..9f879d9c 100644
--- a/server/db_scripts/initialize_db.js
+++ b/server/db_scripts/initialize_db.js
@@ -22,6 +22,34 @@ create table if not exists pages (
author integer
)
`);
+/* an object has:
+ id = primary key
+ number = object identifier number
+
+ title / NAME = a short display name for an object, or the title of a page
+ description / TEXT? = the markdown description of an object, or the lua script of the source code of a verb
+
+ html / HTML_MARKDOWN? = the markdown for that object, rendered into html for display as a page
+ HTML_LUA? = the lua source code of the object, rendered into html with syntax highlighting?
+
+ time = when this edit was saved
+ author = the user id of whoever authored this revision
+
+ TYPE = whether this represents an object or a verb
+
+*/
+
+/* an attribute store has:
+ number = the object number it's attached to
+ contents = a `text` object holding a json store of attributes defined on object #number, containing:
+ LOCATION? = the object which contains this object? should this be in the DB or in the json store?
+ PROTOTYPE = the number of the prototype for this object, to which we should look for attributes not found here
+ ATTRIBUTES? = json object of attached, script-controlled attributes.
+
+ CONTENTS = json array of objectss contained inside this one
+ VERBS = json array of objecsts which are verbs on this one
+*/
+
const createUsers = db.prepare(`
create table if not exists users (
@@ -31,7 +59,13 @@ create table if not exists users (
password varchar(128)
)
`);
+/* a user has:
+ id = primary key
+ name = name string
+ password = argon2 hash of their password
+ character = object in the game world representing your character
+*/
function migratePages() {
console.log("moving old table to a temporary");
diff --git a/server/interpreter.js b/server/interpreter.js
index 69c64abf..7f6e04ef 100644
--- a/server/interpreter.js
+++ b/server/interpreter.js
@@ -10,19 +10,27 @@ function interpret(context, player, command) {
// may be null if it's executing from a script?
// player: the id of the player object who typed in the command.
// may? be null if it's executing from a script? used for output.
+ // so arguably
// command: the full string typed in by the player
const socket = sockets.get(player)
const words = command.trim().split(' ');
- socket?.send(`interpreting and executing the received command: ${command}`);
+ try {
+ socket?.send(`interpreting and executing the received command: ${command}`);
+
+ //for the moment we'll only allow statements of the form "go north" with implicit subject: the player
+ const [first, second, third, ...rest] = words;
- const verbs = findAvailableVerbs(context, player, command);
- socket?.send(`found these verbs: ${Array.from(verbs.keys()).join(', ')}`)
-
- // first word is either a subject or a verb. either way there must be a verb.
- const [first, second, third, ...rest] = words;
+ const verb = findVerb(first, context, player);
+
+ console.log(`executing verb ${verb} towards ${player} on ${player} ${second} ${third}`);
+ executeVerb(player, verb, player, second, third, ...rest)
+ } catch (error) {
+ socket?.send(`got an error! ${error.mesage}`);
+ }
+ /*
// if the second word is a verb, but the first word is also a verb, what do we do?
if (second in verbs) {
// for now, assume that if the second word is a verb, it's the verb we want.
@@ -31,16 +39,54 @@ function interpret(context, player, command) {
// if the second word is not a verb, then the first word is the verb, and the player is the implied subject.
executeVerb(player, verbs.get(first), player, second, third, ...rest)
}
+ */
}
-function findAvailableVerbs(location, actor, command) {
+function findVerb(word, location, actor) {
+ // returns the number of a verb which matches the requested word.
// check for verbs on actor
- // check for verbs on objects in location
- // check for verbs on location
- const out = new Map();
- out.set("look", 29);
- out.set("warp", 31);
- return out;
+ let verb = findVerbOnObject(word, actor);
+ if (verb) return verb;
+
+ const actorContents = getAttribute(actor, "contents");
+ for (const obj of (actorContents || [])) {
+ verb = findVerbOnObject(word, obj);
+ if (verb) return verb;
+ }
+
+ const locationContents = getAttribute(location, "contents");
+ for (const obj of (locationContents || [])) {
+ verb = findVerbOnObject(word, obj);
+ if (verb) return verb;
+ }
+
+ verb = findVerbOnObject(word, location);
+ if (verb) return verb;
+
+ return undefined;
+}
+
+function findVerbOnObject(word, object) {
+ // check for verbs on a single object, and its chain of parents. return a matching verbId, or undefined
+ if (!word) return undefined;
+
+ let focus = object;
+ while (focus) {
+ const focusVerbs = getAttribute(focus, "verbs");
+ for (const verbId of (focusVerbs || [])) {
+ const fullVerb = lookUpObject(verbId);
+
+ if (!fullVerb) continue;
+
+ // test our word against
+ if (word.toLowerCase() == fullVerb.title.toLowerCase()) {
+ return verbId;
+ }
+ }
+ focus = getAttribute(focus, "parent");
+ }
+
+ return undefined;
}
function executeVerb(outputObject, verbId, subject, object, ...rest) {
@@ -56,28 +102,99 @@ function executeVerb(outputObject, verbId, subject, object, ...rest) {
return fullVerb.fn(outputObject, subject, object, ...rest);
}
-const objectQuery = db.prepare('select * from pages where id=?');
-function lookUpObject(id) {
+const objectQuery = db.prepare('select * from pages where number=? order by time desc');
+function lookUpObject(number) {
+ return hardCodedObjects(number) || objectQuery.get(number);
+}
+
+const attributeQuery = db.prepare(`select * from attributes where number=?`);
+function lookUpObjectAttributes(number) {
+ return JSON.parse(attributeQuery.get(number).contents);
+}
+
+
+function hardCodedObjects(id) {
// return objectQuery.get(id);
- if (id == 30) return {name: "shoofle", contents: "this is a shoofle", location: 1};
if (id == 29) return {
- name: "look",
- verb: false,
- contents: "send description of direct object to subject's socket",
+ title: "look",
+ verb: true,
+ description: "send description of direct object to subject's socket",
fn: (logReceiver, subject, object, ...rest) => {
sockets.get(logReceiver)?.send(`you looked around! subject ${subject} object: ${object} args: ${rest}`);
console.log(`${subject} looked at ${object} with args ${rest}, and output was directed to ${logReceiver}`);
}
}
if (id == 31) return {
- name: "warp",
+ title: "warp",
verb: true,
- contents: "this built-in function changes the player's location, as well as telling their client to move",
+ description: "this built-in function changes the player's location, as well as telling their client to move",
fn: (logReceiver, subject, object, ...rest) => {
- // TODO: change subject's location
+ const objectNum = verifyObjectReference(object);
+ const subjectNum = verifyObjectReference(subject);
+ setAttribute(subjectNum, "location", objectNum);
+
sockets.get(logReceiver)?.send(`location change to: ${object}`);
}
}
}
-module.exports = { interpret, sockets, lookUpObject };
\ No newline at end of file
+
+
+
+const pullAttribute = db.prepare(`select * from attributes where number=?`);
+function getAttribute(obj, attributeName) {
+ const attributeStore = pullAttribute.get(verifyObjectReference(obj));
+ const contents = JSON.parse(attributeStore.contents);
+
+ if (contents.hasOwnProperty(attributeName)) {
+ return contents[attributeName];
+ }
+
+ if (contents.hasOwnProperty("parent")) {
+ return getAttribute(contents.parent, attributeName);
+ }
+
+ return undefined;
+}
+
+function hasOwnAttribute(obj, attributeName) {
+ const attributeStore = pullAttribute.get(verifyObjectReference(obj));
+ const contents = JSON.parse(attributeStore.contents);
+
+ return contents.hasOwnProperty(attributeName);
+}
+
+const insertAttribute = db.prepare(`update or replace attributes set contents=:contents where number=:number`);
+function setAttribute(obj, attributeName, value) {
+ const attributeStore = pullAttribute.get(verifyObjectReference(obj));
+ const contents = JSON.parse(attributeStore.contents);
+
+ contents[attributeName] = value;
+
+ insertAttribute.run({...attributeStore, contents: JSON.stringify(contents)});
+}
+
+function deleteAttribute(obj, attributeName) {
+ const attributeStore = pullAttribute.get(verifyObjectReference(obj));
+ const contents = JSON.parse(attributeStore.contents);
+
+ delete contents["attributeName"];
+
+ insertAttribute.run({...attributeStore, contents: JSON.stringify(contents)});
+}
+
+function verifyObjectReference(obj) {
+ try {
+ if (typeof(obj) === "string") {
+ return Number(obj.replace("#", ""));
+ } else if (typeof(obj) === "number") {
+ return obj;
+ } else if (typeof(obj.number) == "number") {
+ return obj.number;
+ }
+ } catch (error) {
+ throw new Error("Tried to get an attribute from something which wasn't an object");
+ }
+}
+
+module.exports = { interpret, sockets, lookUpObject, getAttribute, setAttribute, deleteAttribute, hasOwnAttribute };
\ No newline at end of file
diff --git a/server/package-lock.json b/server/package-lock.json
index d02dd7af..55e812b9 100644
--- a/server/package-lock.json
+++ b/server/package-lock.json
@@ -21,6 +21,7 @@
"graphology-layout": "^0.6.1",
"graphology-layout-force": "^0.2.4",
"graphology-layout-forceatlas2": "^0.10.1",
+ "highlight.js": "^11.10.0",
"jsdom": "^25.0.0",
"nodemon": "^3.1.5",
"showdown": "^2.1.0"
@@ -1128,6 +1129,14 @@
"node": ">= 0.4"
}
},
+ "node_modules/highlight.js": {
+ "version": "11.10.0",
+ "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.10.0.tgz",
+ "integrity": "sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ==",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/html-encoding-sniffer": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
diff --git a/server/package.json b/server/package.json
index 8a3139fd..5dcb0f60 100644
--- a/server/package.json
+++ b/server/package.json
@@ -26,6 +26,7 @@
"graphology-layout": "^0.6.1",
"graphology-layout-force": "^0.2.4",
"graphology-layout-forceatlas2": "^0.10.1",
+ "highlight.js": "^11.10.0",
"jsdom": "^25.0.0",
"nodemon": "^3.1.5",
"showdown": "^2.1.0"
diff --git a/server/routes/sockets.js b/server/routes/sockets.js
index 78a852c7..48325eb9 100644
--- a/server/routes/sockets.js
+++ b/server/routes/sockets.js
@@ -7,7 +7,8 @@ const db = new sqlite('the_big_db.db', { verbose: console.log });
const { loginRequired } = require('../authStuff.js');
-const { interpret, sockets, lookUpObject } = require('../interpreter.js');
+const { interpret, sockets, lookUpObject,
+ getAttribute, setAttribute, hasOwnAttribute, deleteAttribute } = require('../interpreter.js');
@@ -15,6 +16,7 @@ app.ws('/embody', (ws, req) => {
// const { playerObject } = db.prepare('select playerObject from users where id=?').get(req.session.userId)
const playerObjectId = 30; // mocked out for now!
sockets.set(playerObjectId, ws);
+ ws.send(`location change to: #${getAttribute(playerObjectId, "location")}`);
ws.on('message', (msg) => {
const location = lookUpObject(playerObjectId).location;