an experiment in putting together a wiki and an object-oriented mud.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
the-forest/server/interpreter.js

200 lines
6.3 KiB

const sqlite = require('better-sqlite3');
const db = new sqlite('the_big_db.db', { verbose: console.log });
var sockets = new Map();
function interpret(context, player, command) {
// interpret and execute a command entered by a player.
//
// context: the location the player has entered the command in from.
// 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(' ');
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 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.
executeVerb(player, verbs.get(second), first, third, ...rest);
} else {
// 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 findVerb(word, location, actor) {
// returns the number of a verb which matches the requested word.
// check for verbs on actor
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) {
const fullVerb = lookUpObject(verbId);
if (!fullVerb) {
return sockets.get(outputObject)?.send(`verb not found`);
}
sockets.get(outputObject)?.send(`verb found as ${fullVerb.name}`);
const func = fullVerb.fn;
if (!func) {
return sockets.get(outputObject)?.send(`verb was found but didn't have a fn attribute`);
}
return fullVerb.fn(outputObject, subject, object, ...rest);
}
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 == 29) return {
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 {
title: "warp",
verb: true,
description: "this built-in function changes the player's location, as well as telling their client to move",
fn: (logReceiver, subject, object, ...rest) => {
const objectNum = verifyObjectReference(object);
const subjectNum = verifyObjectReference(subject);
setAttribute(subjectNum, "location", objectNum);
sockets.get(logReceiver)?.send(`location change to: ${object}`);
}
}
}
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 };