From 7ed02021426f1c5472185f73e45068c6d496c5ff Mon Sep 17 00:00:00 2001 From: Nevernown Date: Sun, 16 Mar 2025 15:02:23 +0100 Subject: [PATCH] From the archive --- .gitignore | 2 + backbone/cache.js | 64 +++++ backbone/setup.js | 26 ++ backbone/state.js | 90 ++++++ backbone/util.js | 7 + game.js | 10 + game/alias.json | 39 +++ game/commands/alias.js | 29 ++ game/commands/help.js | 49 ++++ game/commands/me.js | 110 ++++++++ game/commands/ping.js | 23 ++ game/commands/scene.js | 38 +++ game/context.js | 78 ++++++ game/process.js | 20 ++ game/scenario/scenario.js | 80 ++++++ game/scenario/storylines/zeppelin.js | 28 ++ game/scenario/storylines/zeppelin/items.json | 1 + game/scenario/storylines/zeppelin/map.json | 265 ++++++++++++++++++ .../scenario/storylines/zeppelin/objects.json | 106 +++++++ package.json | 15 + state/517775880119123973.json | 1 + 21 files changed, 1081 insertions(+) create mode 100644 .gitignore create mode 100644 backbone/cache.js create mode 100644 backbone/setup.js create mode 100644 backbone/state.js create mode 100644 backbone/util.js create mode 100644 game.js create mode 100644 game/alias.json create mode 100644 game/commands/alias.js create mode 100644 game/commands/help.js create mode 100644 game/commands/me.js create mode 100644 game/commands/ping.js create mode 100644 game/commands/scene.js create mode 100644 game/context.js create mode 100644 game/process.js create mode 100644 game/scenario/scenario.js create mode 100644 game/scenario/storylines/zeppelin.js create mode 100644 game/scenario/storylines/zeppelin/items.json create mode 100644 game/scenario/storylines/zeppelin/map.json create mode 100644 game/scenario/storylines/zeppelin/objects.json create mode 100644 package.json create mode 100644 state/517775880119123973.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..504afef --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +package-lock.json diff --git a/backbone/cache.js b/backbone/cache.js new file mode 100644 index 0000000..f228c17 --- /dev/null +++ b/backbone/cache.js @@ -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 +} diff --git a/backbone/setup.js b/backbone/setup.js new file mode 100644 index 0000000..04ea735 --- /dev/null +++ b/backbone/setup.js @@ -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`); + } + }); + + }) +} diff --git a/backbone/state.js b/backbone/state.js new file mode 100644 index 0000000..ffc1c01 --- /dev/null +++ b/backbone/state.js @@ -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 +} diff --git a/backbone/util.js b/backbone/util.js new file mode 100644 index 0000000..9c2dd9e --- /dev/null +++ b/backbone/util.js @@ -0,0 +1,7 @@ +function c(o) { + return JSON.parse(JSON.stringify(o)); +} + +module.exports = { + clone: c +} diff --git a/game.js b/game.js new file mode 100644 index 0000000..4a210fd --- /dev/null +++ b/game.js @@ -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)) +})); diff --git a/game/alias.json b/game/alias.json new file mode 100644 index 0000000..848c257 --- /dev/null +++ b/game/alias.json @@ -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" + } +} diff --git a/game/commands/alias.js b/game/commands/alias.js new file mode 100644 index 0000000..9d981ae --- /dev/null +++ b/game/commands/alias.js @@ -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; +}; diff --git a/game/commands/help.js b/game/commands/help.js new file mode 100644 index 0000000..5a6ed44 --- /dev/null +++ b/game/commands/help.js @@ -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 \` +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 \` 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; +}; diff --git a/game/commands/me.js b/game/commands/me.js new file mode 100644 index 0000000..925e227 --- /dev/null +++ b/game/commands/me.js @@ -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; +}; diff --git a/game/commands/ping.js b/game/commands/ping.js new file mode 100644 index 0000000..ac50dad --- /dev/null +++ b/game/commands/ping.js @@ -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; +}; diff --git a/game/commands/scene.js b/game/commands/scene.js new file mode 100644 index 0000000..dcb6772 --- /dev/null +++ b/game/commands/scene.js @@ -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 \` 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; +}; diff --git a/game/context.js b/game/context.js new file mode 100644 index 0000000..a8d4a04 --- /dev/null +++ b/game/context.js @@ -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 +} diff --git a/game/process.js b/game/process.js new file mode 100644 index 0000000..00e5f88 --- /dev/null +++ b/game/process.js @@ -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); + } + } +} diff --git a/game/scenario/scenario.js b/game/scenario/scenario.js new file mode 100644 index 0000000..c5d6023 --- /dev/null +++ b/game/scenario/scenario.js @@ -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 +} diff --git a/game/scenario/storylines/zeppelin.js b/game/scenario/storylines/zeppelin.js new file mode 100644 index 0000000..36023fb --- /dev/null +++ b/game/scenario/storylines/zeppelin.js @@ -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 + } +} diff --git a/game/scenario/storylines/zeppelin/items.json b/game/scenario/storylines/zeppelin/items.json new file mode 100644 index 0000000..60b0742 --- /dev/null +++ b/game/scenario/storylines/zeppelin/items.json @@ -0,0 +1 @@ +[] diff --git a/game/scenario/storylines/zeppelin/map.json b/game/scenario/storylines/zeppelin/map.json new file mode 100644 index 0000000..8b9a774 --- /dev/null +++ b/game/scenario/storylines/zeppelin/map.json @@ -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": "" + } + } + ] +} diff --git a/game/scenario/storylines/zeppelin/objects.json b/game/scenario/storylines/zeppelin/objects.json new file mode 100644 index 0000000..35753b7 --- /dev/null +++ b/game/scenario/storylines/zeppelin/objects.json @@ -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" + } +] diff --git a/package.json b/package.json new file mode 100644 index 0000000..5a2b446 --- /dev/null +++ b/package.json @@ -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" + } +} diff --git a/state/517775880119123973.json b/state/517775880119123973.json new file mode 100644 index 0000000..bc09871 --- /dev/null +++ b/state/517775880119123973.json @@ -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"}}} \ No newline at end of file