From the archive
This commit is contained in:
commit
7ed0202142
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
node_modules/
|
||||||
|
package-lock.json
|
||||||
64
backbone/cache.js
Normal file
64
backbone/cache.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
const _ = require('underscore');
|
||||||
|
const state = require('./state.js');
|
||||||
|
|
||||||
|
let cache = {};
|
||||||
|
|
||||||
|
function i (id) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
state.create(id);
|
||||||
|
state.read(id)
|
||||||
|
.then(data => {
|
||||||
|
cache[id] = data;
|
||||||
|
resolve();
|
||||||
|
})
|
||||||
|
.catch(err => reject(err));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function c (id) {
|
||||||
|
if(_.has(cache, id)) {
|
||||||
|
state.update(id, cache[id]);
|
||||||
|
console.info(`[${id}] Committed new state`);
|
||||||
|
} else {
|
||||||
|
console.error(`[${id}] Could not commit nonexistant state`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function r (id) {
|
||||||
|
state.read(id)
|
||||||
|
.then(data => {
|
||||||
|
cache[id] = data
|
||||||
|
console.info(`[${id}] Reverted state`);
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
}
|
||||||
|
function e (id) {
|
||||||
|
if(_.has(cache, id)) {
|
||||||
|
delete cache[id];
|
||||||
|
console.info(`[${id}] Erased state`);
|
||||||
|
state.delete(id);
|
||||||
|
} else {
|
||||||
|
console.error(`[${id}] Could not erase nonexistant state`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function s (id) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if(_.has(cache, id)) {
|
||||||
|
resolve(cache[id]);
|
||||||
|
} else {
|
||||||
|
state.read(id)
|
||||||
|
.then(data => {
|
||||||
|
cache[id] = data;
|
||||||
|
console.info(`[${id}] read and cached state`);
|
||||||
|
resolve(cache[id]);
|
||||||
|
})
|
||||||
|
.catch(err => reject(err));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init: i,
|
||||||
|
commit: c,
|
||||||
|
revert: r,
|
||||||
|
erase: e,
|
||||||
|
state: s
|
||||||
|
}
|
||||||
26
backbone/setup.js
Normal file
26
backbone/setup.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
const cache = require('./cache.js');
|
||||||
|
|
||||||
|
module.exports = client => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
console.info(`Logged in as ${client.user.tag}!`);
|
||||||
|
|
||||||
|
console.info(`[BOOT] Initiating state manager...`);
|
||||||
|
Promise.all(client.guilds.cache.map(guild => cache.init(guild.id)))
|
||||||
|
.then(() => resolve())
|
||||||
|
.catch(err => console.error(err));
|
||||||
|
|
||||||
|
console.info(`[BOOT] Locating game channels...`);
|
||||||
|
client.guilds.cache.each(guild => {
|
||||||
|
let channel = guild.channels.cache.find(channel => channel.name === 'machine');
|
||||||
|
if(channel) {
|
||||||
|
console.info(`[${guild.id}] Found machine channel`);
|
||||||
|
channel.send(`\`[${guild.id}] NeverSteam Engine v2 startup successful\``)
|
||||||
|
.then(() => console.info(`[${guild.id}] Sent startup notification`))
|
||||||
|
.catch(console.error);
|
||||||
|
} else {
|
||||||
|
console.info(`[${guild.id}] Could not find machine channel`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
90
backbone/state.js
Normal file
90
backbone/state.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const location = './state'
|
||||||
|
|
||||||
|
if(!fs.existsSync(location)) {
|
||||||
|
fs.mkdir(location, err => {
|
||||||
|
if(err) { console.error(err); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function l (id) { return `${location}/${id}.json` }
|
||||||
|
|
||||||
|
function c (id) {
|
||||||
|
let path = l(id);
|
||||||
|
if(fs.existsSync(path)) {
|
||||||
|
console.info(`[${id}] State already exists`);
|
||||||
|
} else {
|
||||||
|
fs.writeFile(path, JSON.stringify({}), err => {
|
||||||
|
if(err) {
|
||||||
|
console.error(err);
|
||||||
|
} else {
|
||||||
|
console.info(`[${id}] Created new state`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function r (id) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let path = l(id);
|
||||||
|
if(fs.existsSync(path)) {
|
||||||
|
console.info(`[${id}] Reading existing state`);
|
||||||
|
fs.readFile(path, (err, data) => {
|
||||||
|
if(err) {
|
||||||
|
console.error(err);
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
console.info(`[${id}] Read state`);
|
||||||
|
try {
|
||||||
|
let state = JSON.parse(data);
|
||||||
|
console.info(`[${id}] Deserialized state`);
|
||||||
|
resolve(state);
|
||||||
|
} catch(err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error(`[${id}] Failed to locate state`);
|
||||||
|
reject(new Error('State not found.'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function u (id, state) {
|
||||||
|
let path = l(id);
|
||||||
|
if(fs.existsSync(path)) {
|
||||||
|
fs.writeFile(path, JSON.stringify(state), err => {
|
||||||
|
if(err) {
|
||||||
|
console.error(err);
|
||||||
|
} else {
|
||||||
|
console.info(`[${id}] Updated state`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error(`[${id}] Failed to locate state`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function d (id) {
|
||||||
|
let path = l(id);
|
||||||
|
if(fs.existsSync(path)) {
|
||||||
|
fs.unlink(path, err=> {
|
||||||
|
if(err) {
|
||||||
|
console.error(err);
|
||||||
|
} else {
|
||||||
|
console.info(`[${id}] Removed state`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error(`[${id}] Failed to locate state`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
create: c,
|
||||||
|
read: r,
|
||||||
|
update: u,
|
||||||
|
delete: d
|
||||||
|
}
|
||||||
7
backbone/util.js
Normal file
7
backbone/util.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
function c(o) {
|
||||||
|
return JSON.parse(JSON.stringify(o));
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
clone: c
|
||||||
|
}
|
||||||
10
game.js
Normal file
10
game.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
const Discord = require('discord.js');
|
||||||
|
const setup = require('./backbone/setup.js');
|
||||||
|
const process = require('./game/process.js');
|
||||||
|
|
||||||
|
const client = new Discord.Client();
|
||||||
|
client.login('NjcyNTI5MDE2MjI5NzI0MTkw.XjMz0g.XhdRL-wSCFkYmsNBC7j5HE3XUSI');
|
||||||
|
|
||||||
|
client.on('ready', () => setup(client).then(() => {
|
||||||
|
client.on('message', msg => process(msg, client))
|
||||||
|
}));
|
||||||
39
game/alias.json
Normal file
39
game/alias.json
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"scn": {
|
||||||
|
"desc": "Alternate to `!scenario`",
|
||||||
|
"command": "scenario"
|
||||||
|
},
|
||||||
|
"h": {
|
||||||
|
"desc": "Alternate to `!help`",
|
||||||
|
"command": "help"
|
||||||
|
},
|
||||||
|
"up": {
|
||||||
|
"desc": "Alternate to `!ping`",
|
||||||
|
"command": "ping"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"desc": "Alternate to `!me --name`, sets the name of your character (use `!help me` for more information)",
|
||||||
|
"command": "me",
|
||||||
|
"arg": "-n "
|
||||||
|
},
|
||||||
|
"age": {
|
||||||
|
"desc": "Alternate to `!me --age`, sets the age of your character (use `!help me` for more information)",
|
||||||
|
"command": "me",
|
||||||
|
"arg": "-a "
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"desc": "Alternate to `!me --title`, sets the title of your character (use `!help me` for more information)",
|
||||||
|
"command": "me",
|
||||||
|
"arg": "-t "
|
||||||
|
},
|
||||||
|
"story": {
|
||||||
|
"desc": "Alternate to `!me --story`, sets the background story of your character (use `!help me` for more information)",
|
||||||
|
"command": "me",
|
||||||
|
"arg": "-s "
|
||||||
|
},
|
||||||
|
"done": {
|
||||||
|
"desc": "Alternate to `!me --done`, set your character to ready-to-play (use `!help me` for more information)",
|
||||||
|
"command": "me",
|
||||||
|
"arg": "-d"
|
||||||
|
}
|
||||||
|
}
|
||||||
29
game/commands/alias.js
Normal file
29
game/commands/alias.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
const scenarios = require('../scenario/scenario.js');
|
||||||
|
const _ = require('underscore');
|
||||||
|
|
||||||
|
const helptext = `The \`!alias\` command can be used to find alternative commands that cannot be found with \`!help\``;
|
||||||
|
|
||||||
|
function alias(context) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if(context.isHelpRequest) {
|
||||||
|
context.message.reply(helptext)
|
||||||
|
.then(() => {
|
||||||
|
console.info(`[${context.id}] ? alias`);
|
||||||
|
resolve();
|
||||||
|
}).catch(err => reject(err));
|
||||||
|
} else {
|
||||||
|
let text = 'Listing aliases\n```';
|
||||||
|
_.each(_.keys(context.alias), key => text += `\n${key}`);
|
||||||
|
text += '\n```'
|
||||||
|
context.message.reply(text)
|
||||||
|
.then(() => {
|
||||||
|
console.info(`[${context.id}] List alias -> [${context.message.author.id}]`)
|
||||||
|
resolve();
|
||||||
|
}).catch(err => reject(err));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = commands => {
|
||||||
|
commands.alias = alias;
|
||||||
|
};
|
||||||
49
game/commands/help.js
Normal file
49
game/commands/help.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
const _ = require('underscore');
|
||||||
|
const helptext = `The \`!help\` command can be used to get a list of commands, or to get specific help text using \`!help <command>\`
|
||||||
|
Also see the \`!alias\` command for extra simplifications.`
|
||||||
|
|
||||||
|
const ex = /^[\w\d]+$/
|
||||||
|
function help(context) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if(context.isHelpRequest) {
|
||||||
|
context.message.reply(helptext)
|
||||||
|
.then(() => {
|
||||||
|
console.info(`[${context.id}] ? help`);
|
||||||
|
resolve();
|
||||||
|
}).catch(err => reject(err));
|
||||||
|
} else if(!context.args) {
|
||||||
|
let text = 'Listing commands, use \`!help <command>\` for detailed information. Use \`!alias\` to find easy shortcut commands\n```';
|
||||||
|
_.each(_.keys(context.commands), key => text += `\n${key}`);
|
||||||
|
text += '\n```'
|
||||||
|
context.message.reply(text)
|
||||||
|
.then(() => {
|
||||||
|
console.info(`[${context.id}] List commands -> [${context.pid}]`)
|
||||||
|
resolve();
|
||||||
|
}).catch(err => reject(err));
|
||||||
|
} else {
|
||||||
|
let match = ex.exec(context.args);
|
||||||
|
if(match && _.has(context.commands, context.args)) {
|
||||||
|
console.info(`[${context.id}] Passing help request to <${context.args}>`);
|
||||||
|
context.isHelpRequest = true;
|
||||||
|
context.commands[context.args](context);
|
||||||
|
} else if (match && _.has(context.alias, context.args)) {
|
||||||
|
console.info(`[${context.id}] Alias help request to <${context.command}>`);
|
||||||
|
context.message.reply(`\`!${context.args}\`: ${context.alias[context.args].desc}`)
|
||||||
|
.then(() => {
|
||||||
|
console.info(`[${context.id}] Alias -> [${context.pid}]`);
|
||||||
|
resolve();
|
||||||
|
}).catch(err => reject(err));
|
||||||
|
} else {
|
||||||
|
context.message.reply(`No such command <${context.args}>`)
|
||||||
|
.then(() => {
|
||||||
|
console.info(`[${context.id}] No command -> [${context.pid}]`)
|
||||||
|
resolve();
|
||||||
|
}).catch(err => reject(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = commands => {
|
||||||
|
commands.help = help;
|
||||||
|
};
|
||||||
110
game/commands/me.js
Normal file
110
game/commands/me.js
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
const _ = require('underscore');
|
||||||
|
const helptext = `The \`!me\` command can be used to begin setting up your player character. \`!me\` shows your character, \`!me --done\` finishes.
|
||||||
|
\`!me\` shows current information
|
||||||
|
\`!me --done\` sets your character as ready to play (but you can still adjust it until game start) {\`!me -d \`}
|
||||||
|
\`!me --name <...>\` set name (Max. 48 characters) {\`!me -n <...>\`}
|
||||||
|
\`!me --age N\` set age (0 < N < 1000) {\`!me -a N\`}
|
||||||
|
\`!me --title <...>\` set title name (Max. 48 characters, e.g. Major General, Doctor, Lady) {\`!me -t <...>\`}
|
||||||
|
\`!me --story <...>\` give a background story (32-320 characters) {\`!me -s <...>\`}
|
||||||
|
You can combine these for speed, examples:
|
||||||
|
\`!me -n Johnny Smooth -a 23 -t Presentator\`
|
||||||
|
\`!me --name Scotty Love --age 24 --title Village Idiot\`
|
||||||
|
Mix and match long form and shorthand:
|
||||||
|
\`!me --name Mr. Eaten --age 271 -s <...>\` (<...> omitted for brevity)
|
||||||
|
Finalize on-the-go (be sure to include all options):
|
||||||
|
\`!me -n Speedy Gonzales -a 15 -t Insurgent -s <...> -d\` (<...> omitted for brevity)
|
||||||
|
`
|
||||||
|
|
||||||
|
let expressions = {};
|
||||||
|
expressions.name = {};
|
||||||
|
expressions.name.pt = /((?:--name)|(?:-n))/;
|
||||||
|
expressions.name.ex = /(?:(?:--name)|(?:-n)\s)(.{1,48}?)\s?(?:-|$)/;
|
||||||
|
expressions.age = {};
|
||||||
|
expressions.age.pt = /((?:--age)|(?:-a))/;
|
||||||
|
expressions.age.ex = /(?:(?:--age)|(?:-a)\s)(\d{1,3})\s?(?:-|$)/;
|
||||||
|
expressions.title = {};
|
||||||
|
expressions.title.pt = /((?:--title)|(?:-t))/;
|
||||||
|
expressions.title.ex = /(?:(?:--title)|(?:-t)\s)(.{1,48}?)\s?(?:-|$)/;
|
||||||
|
expressions.story = {};
|
||||||
|
expressions.story.pt = /((?:--story)|(?:-s))/;
|
||||||
|
expressions.story.ex = /(?:(?:--story)|(?:-s)\s)(.{32,320}?)\s?(?:-|$)/;
|
||||||
|
const exDone = /(?:(?:--done)|(?:-d))/
|
||||||
|
|
||||||
|
function p(flag, context) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let flagged = expressions[flag].pt.exec(context.args);
|
||||||
|
if(flagged) {
|
||||||
|
let match = expressions[flag].ex.exec(context.args);
|
||||||
|
if(match) {
|
||||||
|
context.state.player[context.pid][flag] = match[1];
|
||||||
|
console.info(`[${context.id}] set ${flag} for [${context.pid}]`);
|
||||||
|
resolve(`Set \`${flag}\``);
|
||||||
|
} else {
|
||||||
|
console.warn(`[${context.id}] could not parse arguments for flag <${flagged[1]}>, use \`!help me\` to check requirements`);
|
||||||
|
resolve(`Could not parse arguments for flag \`${flag}\``);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resolve(`Flag for \`${flag}\` not present.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function me(context) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if(!context.state.player) { context.state.player = {}; }
|
||||||
|
if(context.isHelpRequest) {
|
||||||
|
context.message.reply(helptext)
|
||||||
|
.then(() => {
|
||||||
|
console.info(`[${context.id}] ? me`);
|
||||||
|
resolve();
|
||||||
|
}).catch(err => reject(err));
|
||||||
|
} else if(!context.args) {
|
||||||
|
if(!context.state.player[context.pid]) {
|
||||||
|
context.message.reply(`[${context.pid}] You do not have a character yet, use the \`!help me\` command to find out how to create one.`)
|
||||||
|
.then(() => resolve())
|
||||||
|
.catch(err => reject(err));
|
||||||
|
} else {
|
||||||
|
let player = context.state.player[context.pid];
|
||||||
|
let text = '```\n';
|
||||||
|
text += player.title ? `${player.title} ` : '[missing title] ';
|
||||||
|
text += player.name ? `${player.name}, ` : '[missing name], ';
|
||||||
|
text += player.age ? `${player.age} years old.\n` : '[missing age]\n';
|
||||||
|
text += player.story ? player.story : '[missing background story]';
|
||||||
|
text += '\n```';
|
||||||
|
context.message.reply(text)
|
||||||
|
.then(() => resolve())
|
||||||
|
.catch(err => reject(err));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(!context.state.player[context.pid]) { context.state.player[context.pid] = {}}
|
||||||
|
Promise.all([
|
||||||
|
p("name", context),
|
||||||
|
p("age", context),
|
||||||
|
p("title", context),
|
||||||
|
p("story", context)
|
||||||
|
]).then(values => {
|
||||||
|
let result = _.reduce(values, (c, v) => { return `${c}\n${v}`});
|
||||||
|
if(exDone.test(context.args)) {
|
||||||
|
let player = context.state.player[context.pid];
|
||||||
|
if(player.name && player.age && player.title && player.story) {
|
||||||
|
player.complete = true;
|
||||||
|
console.info(`[${context.id}] completed [${context.pid}]`);
|
||||||
|
result += `\nCompleted basic player setup for [${context.pid}]`;
|
||||||
|
} else {
|
||||||
|
console.info(`[${context.id}] incomplete [${context.pid}]`);
|
||||||
|
result += `\nMissing information for [${context.pid}]. \nUse \`!me\` and \`!help me\` for more information`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
context.message.reply(result)
|
||||||
|
.then(() => {
|
||||||
|
context.stateChanged = true;
|
||||||
|
resolve(context);
|
||||||
|
}).catch(err => reject(err));
|
||||||
|
}).catch(err => reject(err));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = commands => {
|
||||||
|
commands.me = me;
|
||||||
|
};
|
||||||
23
game/commands/ping.js
Normal file
23
game/commands/ping.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
const helptext = `The \`!ping\` command can be used to check if NeverSteam v2 is running, it ignores any arguments given.`;
|
||||||
|
|
||||||
|
function ping(context) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if(context.isHelpRequest) {
|
||||||
|
context.message.reply(helptext)
|
||||||
|
.then(() => {
|
||||||
|
console.info(`[${context.id}] ? ping`)
|
||||||
|
resolve();
|
||||||
|
}).catch(err => reject(err));
|
||||||
|
} else {
|
||||||
|
context.message.reply(`[${context.id}] ECHO`)
|
||||||
|
.then(() => {
|
||||||
|
console.info(`[${context.id}] PING -> ECHO [${context.pid}]`)
|
||||||
|
resolve();
|
||||||
|
}).catch(err => reject(err));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = commands => {
|
||||||
|
commands.ping = ping;
|
||||||
|
};
|
||||||
38
game/commands/scene.js
Normal file
38
game/commands/scene.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
const scenarios = require('../scenario/scenario.js');
|
||||||
|
const _ = require('underscore');
|
||||||
|
|
||||||
|
const helptext = `The \`!scenario\` command can be used to select a scenario to play. use \`!scenario\` to list all options, and \`!scenario <name>\` to select a particular scenario.`;
|
||||||
|
|
||||||
|
function s(context) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if(context.isHelpRequest) {
|
||||||
|
context.message.reply(helptext)
|
||||||
|
.then(() => {
|
||||||
|
console.info(`[${context.id}] ? scene`);
|
||||||
|
resolve();
|
||||||
|
}).catch(err => reject(err));
|
||||||
|
} else if(context.args) {
|
||||||
|
context.storyline = context.args;
|
||||||
|
scenarios.init(context)
|
||||||
|
.then(context => {
|
||||||
|
console.info(`[${context.id}] Initiated scenario ${context.args}`);
|
||||||
|
resolve();
|
||||||
|
})
|
||||||
|
.catch(err => reject(err));
|
||||||
|
} else {
|
||||||
|
let text = '```\n';
|
||||||
|
_.each(scenarios.list(context), scenario => text += `${scenario}\n`);
|
||||||
|
text += '```';
|
||||||
|
context.message.reply(text)
|
||||||
|
.then(() => {
|
||||||
|
console.info(`[${context.id}] Listed scenarios`);
|
||||||
|
resolve();
|
||||||
|
})
|
||||||
|
.catch(err => reject(err));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = commands => {
|
||||||
|
commands.scenario = s;
|
||||||
|
};
|
||||||
78
game/context.js
Normal file
78
game/context.js
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const _ = require('underscore');
|
||||||
|
const cache = require('../backbone/cache.js');
|
||||||
|
const alias = require('./alias.json');
|
||||||
|
|
||||||
|
let commands = {};
|
||||||
|
fs.readdirSync('./game/commands').forEach(file => {
|
||||||
|
require(`./commands/${file}`)(commands);
|
||||||
|
});
|
||||||
|
|
||||||
|
function a(context) {
|
||||||
|
try {
|
||||||
|
let cmd = context.alias[context.command].command;
|
||||||
|
if(context.alias[context.command].arg) {
|
||||||
|
let args = alias[context.command].arg + context.args;
|
||||||
|
context.args = args;
|
||||||
|
}
|
||||||
|
context.command = cmd;
|
||||||
|
} catch {
|
||||||
|
console.info(`[${context.id}] Not an alias <${context.command}>`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ex = /^!(?:([\w\d]+)|(?:([\w\d]+?)\s(.+)))$/
|
||||||
|
function c (context) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let match = ex.exec(context.message.content);
|
||||||
|
if (match) {
|
||||||
|
context.command = match[2] ? match[2] : match[1];
|
||||||
|
context.args = match[3];
|
||||||
|
context.alias = alias;
|
||||||
|
a(context);
|
||||||
|
context.commands = commands;
|
||||||
|
context.pid = context.message.author.id;
|
||||||
|
cache.state(context.id)
|
||||||
|
.then(data => {
|
||||||
|
context.state = data;
|
||||||
|
resolve(context);
|
||||||
|
}).catch(err => reject(err));
|
||||||
|
} else { resolve(false); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function p(message) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let context = { message: message, id: message.guild.id };
|
||||||
|
c(context)
|
||||||
|
.then(context => resolve(context))
|
||||||
|
.catch(err => reject(err));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function x(context) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if(context) {
|
||||||
|
if(_.has(commands, context.command)) {
|
||||||
|
console.info(`[${context.id}] Execute command <${context.command}>`);
|
||||||
|
commands[context.command](context)
|
||||||
|
.then(() => {
|
||||||
|
if(context.stateChanged){
|
||||||
|
cache.commit(context.id);
|
||||||
|
} else {
|
||||||
|
console.info(`[${context.id}] No change to commit (Context.stateChanged == false)`);
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
})
|
||||||
|
.catch(err => reject(err));
|
||||||
|
} else {
|
||||||
|
console.warn(`[${context.id}] Unknown command <${context.command}>`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
parse: p,
|
||||||
|
execute: x
|
||||||
|
}
|
||||||
20
game/process.js
Normal file
20
game/process.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
const Discord = require('discord.js');
|
||||||
|
const context = require('./context.js')
|
||||||
|
|
||||||
|
function process(message) {
|
||||||
|
context.parse(message)
|
||||||
|
.then(context.execute)
|
||||||
|
.catch(console.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = (message, client) => {
|
||||||
|
if(message.author && message.author.id !== client.user.id){
|
||||||
|
if(message.channel instanceof Discord.TextChannel) {
|
||||||
|
process(message);
|
||||||
|
} else {
|
||||||
|
message.reply(`NeverSteam v2 is not available in DM`)
|
||||||
|
.then(() => console.warn('[WARN] Sent DM warning'))
|
||||||
|
.catch(console.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
80
game/scenario/scenario.js
Normal file
80
game/scenario/scenario.js
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const _ = require('underscore');
|
||||||
|
|
||||||
|
let storylines = {};
|
||||||
|
fs.readdirSync('./game/scenario/storylines').forEach(file => {
|
||||||
|
require(`./storylines/${file}`)(storylines);
|
||||||
|
});
|
||||||
|
|
||||||
|
function l(context) {
|
||||||
|
return _.keys(storylines);
|
||||||
|
}
|
||||||
|
|
||||||
|
function v(context) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let ok = true;
|
||||||
|
console.info(`[${context.id}] Verifying map integrity...`);
|
||||||
|
let uniques = _.chain(context.scenario.map.locations)
|
||||||
|
.map(location => location.id)
|
||||||
|
.uniq().value();
|
||||||
|
if(uniques.length !== context.scenario.map.locations.length) {
|
||||||
|
console.error(`[${context.id}] ID collision detected`);
|
||||||
|
reject(new Error("ID Collision in map data"));
|
||||||
|
}
|
||||||
|
_.each(context.scenario.map.connections, connection => {
|
||||||
|
if(!_.contains(uniques, connection.locations[0])) {
|
||||||
|
console.error(`[${context.id}] Invalid connection detected [${connection.locations[0]}]`);
|
||||||
|
ok = false;
|
||||||
|
} else if(!_.contains(uniques, connection.locations[1])) {
|
||||||
|
console.error(`[${context.id}] Invalid connection detected [${connection.locations[1]}]`);
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if(ok) {
|
||||||
|
console.info(`[${context.id}] ... Verified map integrity`);
|
||||||
|
resolve(context);
|
||||||
|
} else {
|
||||||
|
reject(new Error("Invalid map data"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function i(context) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if(!context.scenario) {
|
||||||
|
if(context.storyline) {
|
||||||
|
if(_.has(storylines, context.storyline)) {
|
||||||
|
storylines[context.storyline].initialize(context)
|
||||||
|
.then(context => {
|
||||||
|
context.state.scenario = context.scenario;
|
||||||
|
v(context)
|
||||||
|
.then(context => {
|
||||||
|
context.message.reply(`Initiated scenario \`[${context.storyline}]\``)
|
||||||
|
.then(() => {
|
||||||
|
context.stateChanged = true;
|
||||||
|
resolve(context);
|
||||||
|
})
|
||||||
|
.catch(err => reject(err))
|
||||||
|
})
|
||||||
|
.catch(err => reject(err));
|
||||||
|
})
|
||||||
|
.catch(err => reject(err));
|
||||||
|
} else {
|
||||||
|
console.warn(`[${context.id}] No such storyline named ${context.storyline}.`)
|
||||||
|
reject(new Error(`No such storyline named ${context.storyline}.`));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error(`[${context.id}] Missing storyline context`);
|
||||||
|
reject(new Error(`Invalid context`));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn(`[${context.id}] Scenario already initiated.`);
|
||||||
|
reject(new Error(`Scenario already initiated.`));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init: i,
|
||||||
|
list: l
|
||||||
|
}
|
||||||
28
game/scenario/storylines/zeppelin.js
Normal file
28
game/scenario/storylines/zeppelin.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
let map = require(`./zeppelin/map.json`);
|
||||||
|
const _ = require('underscore');
|
||||||
|
|
||||||
|
function i(context) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
//Build from here.
|
||||||
|
let scenario = {};
|
||||||
|
console.info(`[${context.id}] Loading zeppelin map information`);
|
||||||
|
scenario.map = map;
|
||||||
|
console.info(`[${context.id}] Setting auxiliary context`);
|
||||||
|
scenario.timer = 0;
|
||||||
|
console.info(`[${context.id}] Succesfully loaded zeppelin scenario`);
|
||||||
|
context.scenario = scenario;
|
||||||
|
resolve(context);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function t(context) {
|
||||||
|
//populate with triggers.js
|
||||||
|
//later, calm down.
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = storylines => {
|
||||||
|
storylines['zeppelin'] = {
|
||||||
|
initialize: i,
|
||||||
|
trigger: t
|
||||||
|
}
|
||||||
|
}
|
||||||
1
game/scenario/storylines/zeppelin/items.json
Normal file
1
game/scenario/storylines/zeppelin/items.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
[]
|
||||||
265
game/scenario/storylines/zeppelin/map.json
Normal file
265
game/scenario/storylines/zeppelin/map.json
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
{
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"id": "FRP",
|
||||||
|
"name": "The freight platform",
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"id": "CRATE",
|
||||||
|
"count": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "CRATE_2",
|
||||||
|
"count": 17
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "CANNISTER",
|
||||||
|
"count": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "WTR",
|
||||||
|
"name": "The waiting room",
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"id": "BENCH",
|
||||||
|
"count": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "PSP",
|
||||||
|
"name": "The passenger platform",
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"id": "BENCH",
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ENG_L",
|
||||||
|
"name": "The lower engine room",
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"id": "ENGINE",
|
||||||
|
"count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "RACK",
|
||||||
|
"count": 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ENT",
|
||||||
|
"name": "The entrance hallway",
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"id": "WARDROBE",
|
||||||
|
"count": 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "DNH",
|
||||||
|
"name": "The dining hall",
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"id": "TABLE",
|
||||||
|
"count": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "CHAIR",
|
||||||
|
"count": 24
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "LAMP",
|
||||||
|
"count": 6
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "SLN",
|
||||||
|
"name": "The outlook salon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "GLY",
|
||||||
|
"name": "The galley"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "FST",
|
||||||
|
"name": "The food storage"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "WHL",
|
||||||
|
"name": "The wheelhouse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ENG_U",
|
||||||
|
"name": "The upper engine room"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "GHL",
|
||||||
|
"name": "The left balloon gangway"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "GHR",
|
||||||
|
"name": "The right balloon gangway"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "CBG",
|
||||||
|
"name": "The cabin gangway"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "FRC",
|
||||||
|
"name": "The freight compartiment"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "RYL",
|
||||||
|
"name": "The royal suite"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "FCL_1",
|
||||||
|
"name": "First class suite 1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "FCL_2",
|
||||||
|
"name": "First class suite 2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "BNK_1",
|
||||||
|
"name": "Bunkbed cabin 1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "BNK_2",
|
||||||
|
"name": "Bunkbed cabin 2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "CRW_1",
|
||||||
|
"name": "Crew compartiments 1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "CRW_2",
|
||||||
|
"name": "Crew compartiments 2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "STR",
|
||||||
|
"name": "Storage closet"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"connections": [
|
||||||
|
{
|
||||||
|
"locations": ["PSP", "ENT"],
|
||||||
|
"type": "Steel sealed rolling door",
|
||||||
|
"blocked": {
|
||||||
|
"reason": "The rolling door is closed and sealed shut."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"locations": ["FRP", "FRC"],
|
||||||
|
"type": "Steel sealed rolling door",
|
||||||
|
"blocked": {
|
||||||
|
"reason": "The rolling door is closed and sealed shut."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"locations": ["PSP", "FRP"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"locations": ["WTR", "PSP"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"locations": ["CBG", "ENT"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"locations": ["FST", "ENT"],
|
||||||
|
"type": "Reinforced door",
|
||||||
|
"blocked": {
|
||||||
|
"reason": "It's locked.",
|
||||||
|
"key": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"locations": ["GLY", "ENT"],
|
||||||
|
"type": "Reinforced door"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"locations": ["DNH", "ENT"],
|
||||||
|
"type": "Reinforced door"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"locations": ["DNH", "SLN"],
|
||||||
|
"type": "Stairs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"locations": ["WHL", "SLN"],
|
||||||
|
"type": "Stairs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"locations": ["WHL", "GHR"],
|
||||||
|
"type": "Reinforced door"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"locations": ["WHL", "GHL"],
|
||||||
|
"type": "Reinforced door"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"locations": ["ENG_U", "GHR"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"locations": ["ENG_U", "GHL"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"locations": ["ENG_U", "ENG_L"],
|
||||||
|
"type": "welded steel ladder"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"locations": ["ENG_L", "FRC"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"locations": ["FRC", "CBG"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"locations": ["RYL", "CBG"],
|
||||||
|
"type": "Reinforced door",
|
||||||
|
"blocked": {
|
||||||
|
"reason": "It's locked.",
|
||||||
|
"key": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"locations": ["FCL_1", "CBG"],
|
||||||
|
"type": "Reinforced door"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"locations": ["FCL_2", "CBG"],
|
||||||
|
"type": "Reinforced door"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"locations": ["BNK_1", "CBG"],
|
||||||
|
"type": "Reinforced door"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"locations": ["BNK_2", "CBG"],
|
||||||
|
"type": "Reinforced door"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"locations": ["CRW_1", "CBG"],
|
||||||
|
"type": "Reinforced door"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"locations": ["CRW_2", "CBG"],
|
||||||
|
"type": "Reinforced door"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"locations": ["STR", "CBG"],
|
||||||
|
"type": "Reinforced door",
|
||||||
|
"blocked": {
|
||||||
|
"reason": "It's locked.",
|
||||||
|
"key": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
106
game/scenario/storylines/zeppelin/objects.json
Normal file
106
game/scenario/storylines/zeppelin/objects.json
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "TABLE",
|
||||||
|
"name": "table",
|
||||||
|
"desc": "a regular table"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "TABLE_2",
|
||||||
|
"name": "coffee table",
|
||||||
|
"desc": "a low table typically used to place drinks on"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "CHAIR",
|
||||||
|
"name": "chair",
|
||||||
|
"desc": "a wooden chair"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "SOFA",
|
||||||
|
"name": "sofa",
|
||||||
|
"desc": "a soft, fancy two-person chair"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "CHAIR_2",
|
||||||
|
"name": "chair",
|
||||||
|
"desc": "a richly colored, soft chair"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "BED",
|
||||||
|
"name": "bed",
|
||||||
|
"desc": "a place to sleep for one person"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "BED_2",
|
||||||
|
"name": "bed",
|
||||||
|
"desc": "a place to sleep for two people"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "BUNKBED",
|
||||||
|
"name": "bunkbed",
|
||||||
|
"desc": "a place to sleep for two single people"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ENGINE",
|
||||||
|
"name": "engine",
|
||||||
|
"desc": "without this, the zeppelin can only float"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "CANNISTER",
|
||||||
|
"name": "spare cannister",
|
||||||
|
"desc": "holds extra gas to replenish the zeppelin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "CRATE",
|
||||||
|
"name": "crate",
|
||||||
|
"desc": "a big box"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "CRATE_2",
|
||||||
|
"name": "small crate",
|
||||||
|
"desc": "a small box"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "VASE",
|
||||||
|
"name": "vase",
|
||||||
|
"desc": "Glued in place to survive turbulence"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "SAFE",
|
||||||
|
"name": "safe",
|
||||||
|
"desc": "a place to sleep for two people",
|
||||||
|
"inv": {
|
||||||
|
"locked": true,
|
||||||
|
"key": "SAFE_KEY_01"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "WHEEL",
|
||||||
|
"name": "steering wheel",
|
||||||
|
"desc": "controls the rudder at the back of the zeppelin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "WINDOW",
|
||||||
|
"name": "window",
|
||||||
|
"desc": "the reinforced glass is not quite reassuring"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "BENCH",
|
||||||
|
"name": "wooden bench",
|
||||||
|
"desc": "not quite first-class"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "RACK",
|
||||||
|
"name": "tool rack",
|
||||||
|
"desc": "holds tools for quick use"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "WARDROBE",
|
||||||
|
"name": "reinforced wardrobe",
|
||||||
|
"desc": "keeps clothing safe during the flight"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "LAMP",
|
||||||
|
"name": "oil lamp",
|
||||||
|
"desc": "as safe as it gets, gives off some light"
|
||||||
|
}
|
||||||
|
]
|
||||||
15
package.json
Normal file
15
package.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "neversteam2",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "NeverSteam Game manager",
|
||||||
|
"main": "game.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "Nevernown",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"discord.js": "^12.1.1",
|
||||||
|
"underscore": "^1.10.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
state/517775880119123973.json
Normal file
1
state/517775880119123973.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"scenario":{"map":{"locations":[{"id":"FRP","name":"The freight platform"},{"id":"WTR","name":"The waiting room"},{"id":"PSP","name":"The passenger platform"},{"id":"ENG_L","name":"The lower engine room"},{"id":"ENT","name":"The entrance hallway"},{"id":"DNH","name":"The dining hall"},{"id":"SLN","name":"The outlook salon"},{"id":"GLY","name":"The galley"},{"id":"FST","name":"The food storage"},{"id":"WHL","name":"The wheelhouse"},{"id":"ENG_U","name":"The upper engine room"},{"id":"GHL","name":"The left balloon gangway"},{"id":"GHR","name":"The right balloon gangway"},{"id":"CBG","name":"The cabin gangway"},{"id":"FRC","name":"The freight compartiment"},{"id":"RYL","name":"The royal suite"},{"id":"FCL_1","name":"First class suite 1"},{"id":"FCL_2","name":"First class suite 2"},{"id":"BNK_1","name":"Bunkbed cabin 1"},{"id":"BNK_2","name":"Bunkbed cabin 2"},{"id":"CRW_1","name":"Crew compartiments 1"},{"id":"CRW_2","name":"Crew compartiments 2"},{"id":"STR","name":"Storage closet"}],"connections":[{"locations":["PSP","ENT"],"type":"Steel sealed rolling door","blocked":{"reason":"The rolling door is closed and sealed shut."}},{"locations":["FRP","FRC"],"type":"Steel sealed rolling door","blocked":{"reason":"The rolling door is closed and sealed shut."}},{"locations":["PSP","FRP"]},{"locations":["WTR","PSP"]},{"locations":["CBG","ENT"]},{"locations":["FST","ENT"],"type":"Reinforced door","blocked":{"reason":"It's locked.","key":""}},{"locations":["GLY","ENT"],"type":"Reinforced door"},{"locations":["DNH","ENT"],"type":"Reinforced door"},{"locations":["DNH","SLN"],"type":"Stairs"},{"locations":["WHL","SLN"],"type":"Stairs"},{"locations":["WHL","GHR"],"type":"Reinforced door"},{"locations":["WHL","GHL"],"type":"Reinforced door"},{"locations":["ENG_U","GHR"]},{"locations":["ENG_U","GHL"]},{"locations":["ENG_U","ENG_L"],"type":"welded steel ladder"},{"locations":["ENG_L","FRC"]},{"locations":["FRC","CBG"]},{"locations":["RYL","CBG"],"type":"Reinforced door","blocked":{"reason":"It's locked.","key":""}},{"locations":["FCL_1","CBG"],"type":"Reinforced door"},{"locations":["FCL_2","CBG"],"type":"Reinforced door"},{"locations":["BNK_1","CBG"],"type":"Reinforced door"},{"locations":["BNK_2","CBG"],"type":"Reinforced door"},{"locations":["CRW_1","CBG"],"type":"Reinforced door"},{"locations":["CRW_2","CBG"],"type":"Reinforced door"},{"locations":["STR","CBG"],"type":"Reinforced door","blocked":{"reason":"It's locked.","key":""}}]},"timer":0},"player":{"261931520292421634":{"name":"Baron Black","title":"Detective","age":"17","story":"Baron is an old raven, who picked up a trick or two in his time, nowadays he spends most time going after hackers and other digital scum","complete":true},"197071757021020160":{"name":"stom","age":"1","title":"1"}}}
|
||||||
Loading…
x
Reference in New Issue
Block a user