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