d-bot
Version:
A quirky Discord bot made for single, small, private servers
239 lines (223 loc) • 9.58 kB
JavaScript
'use strict';
// The great RPG
var util = require('./util.js');
var jsonfile = require('jsonfile');
jsonfile.spaces = 4;
var rpgMap = require('./rpg-map.js');
var wordListJSON = require('word-list-json');
var wordListTrim = wordListJSON.slice(6963,270633);
var sendMsg, messages, members; // Vars from bot.js
var rpg, ready;
var unknownNames;
var playerInitValues = {
state: 'new', hp: [10,10], level: 1, xp: 0, atk: 1, def: 1, evd: 1, gold: 10,
weapon: { name: 'small dagger', dmg: 1, value: 1 }
};
jsonfile.readFile('rpg.json',function(err,obj) {
if(err) { console.log(err); return; }
//console.log(JSON.stringify(obj, null, 4));
rpg = obj;
if(!rpg.users) rpg.users = {};
// Fill in missing user init values
for(var uKey in rpg.users) { if(!rpg.users.hasOwnProperty(uKey)) continue;
rpg.users[uKey] = new Player(rpg.users[uKey]);
}
save();
});
var save = function() {jsonfile.writeFile('rpg.json',rpg,function(err){if(err)console.error(err)});};
var initUser = function(user,name) {
rpg.users[user] = JSON.parse(JSON.stringify(playerInitValues));
rpg.users[user].name = name;
save();
};
var describeUser = function(user) {
var messages = [];
var rpgUser = rpg.users[user];
messages.push('**'+rpgUser.name+'**, you are **level '+rpgUser.level+'** with **'+rpgUser.gold+' gold**. ' +
'Your weapon is a **'+rpgUser.weapon.name+'**.');
return messages;
};
var newEncounter = function(cb) {
console.log(new Date(),'generating encounter');
var monster = unknownNames[util.randomInt(unknownNames.length-1)];
monster = { master: monster[1], name: monster[0], hp: [3,3], atk: 1, def: 1, evd: 1 };
messages.find({ user:monster.master,
$where: function () { return !util.contains(this.content,'http') && !util.contains(this.content,'<@'); } },
function(err,msgs) {
if (err) { console.log(err); return; }
monster.message = msgs[util.randomInt(msgs.length-1)].content;
cb(monster);
}
);
};
var getMonsterXP = function(monster) {
return monster.hp[1] + (monster.atk || 0)*3 + (monster.def || 0)*2 + (monster.evd || 0)*2;
};
class Player {
constructor(data) {
for(var iKey in playerInitValues) { if(!playerInitValues.hasOwnProperty(iKey)) continue;
this[iKey] = playerInitValues[iKey]; // Fill init values as defaults
}
for(var dKey in data) { if(!data.hasOwnProperty(dKey)) continue;
this[dKey] = data[dKey]; // Fill input data
}
if(this.module) {
this.module = new Module(this.module);
}
}
sendMessage(msg) {
sendMsg(this.id,[msg]);
}
takeItem(item) {
if(!this.hasOwnProperty('inventory')) {
this.inventory = [item];
} else {
this.inventory.push(item);
}
}
doAction(cmd) {
var result = this.module.doAction(cmd);
if(result) {
if(result.msg) this.sendMessage(result.msg);
if(result.pickup) this.takeItem(result.pickup);
} else {
this.sendMessage('You must enter one of the action words.');
}
}
getPrompt() {
return this.module.getPrompt();
}
}
class Module {
constructor(data) {
if(modules.hasOwnProperty(data)) { // If data param is a module name, initialize a new one
data = { module: data, room: modules[data].startingRoom };
}
for(var pKey in data) { if(!data.hasOwnProperty(pKey)) continue;
this[pKey] = data[pKey]; // Fill in module properties from saved data
}
}
doAction(cmd) {
var monster = modules[this.module].rooms[this.room].monster;
if(monster) { this.monster = monster; return monster; }
var actions = modules[this.module].rooms[this.room].actions;
if(actions[cmd]) { this.room = actions[cmd].goto; return actions[cmd]; }
return false;
}
getPrompt() {
return modules[this.module].rooms[this.room].prompt;
}
}
var modules = {
tutorial: {
startingRoom: '1',
rooms: {
'1': {
prompt: 'Ye find yeself in yon dungeon. You can `walk` forward ' +
'through the narrow torch-lit hall, or `look` around.\n_Type an `action` to do something_',
actions: {
'walk': {goto:'2'},
'look': {goto:'1-helmet'}
}
},
'1-helmet': {
prompt: 'Ever cautious, you examine your surroundings. ' +
'There is an object hidden in shadow and cobwebs. ' +
'You pick it up and brush off the decrepitude. It\'s an **iron skullcap**! ' +
'You can `take` it, or ignore it and `walk` down the hallway.',
actions: {
'walk': {goto:'2'},
'take': {goto:'2', msg: 'You don the old helmet, and its weighty yet sturdy presence on ' +
'your head is reassuring.', pickup:{ name:'iron skullcap', def:1, value:5 }}
}
},
'2': {
prompt: 'You walk for a time down winding corridors until you\'re suddenly ' +
'interrupted by a wretched gasp. Quickly turning toward the source of the inhuman ' +
'sound, dagger at the ready, you see before you an imp. It creeps toward you in ' +
'a crouch, brandishing a jagged shard of steel with cloth wraps for a handle.\n\n' +
'You\'re in combat now! Type `attack` to strike your foe!',
monster: { name: 'imp', hp: [3,3], atk: 1, def: 1, evd: 1 }
}
}
}
};
for(var mKey in modules) { if(!modules.hasOwnProperty(mKey)) continue;
modules[mKey].module = mKey;
}
class Monster {
constructor(data) {
for(var dKey in data) { if(!data.hasOwnProperty(dKey)) continue;
this[dKey] = data[dKey]; // Fill input data
}
}
}
// TODO: Combat system like the one in 100% orange juice
// TODO: Enemies are minions of other people in Discord, they say a random message upon encountering
module.exports = {
init: function(sm,msg,mem) {
sendMsg = sm; messages = msg; members = mem;
messages.find({},function(err,msgs) {
if (err) { console.log(err); return; }
var dictionary = {};
var lettersRx = /^[a-z]*$/;
for(var m = 0; m < msgs.length; m++) {
if(['/','!'].indexOf(msgs[m].content[0]) >= 0) continue;
if(msgs[m].user == '103730024510685184') continue;
var words = msgs[m].content.split(' ');
if(!words || words.length == 0) continue;
for(var w = 0; w < words.length; w++) {
var thisWord = words[w].toLowerCase();
if(thisWord.length < 5) continue;
if(thisWord.length > 14) continue;
if(['shouldnt','shouldve','wouldnt','wouldve','couldnt','couldve','youre','didnt','theyre',
'thats','theyve','youve','havent'].indexOf(thisWord) >= 0) continue; // Typos
if(!thisWord.match(lettersRx)) continue;
if(wordListTrim.indexOf(thisWord) >= 0) continue;
dictionary[thisWord] = msgs[m].user;
}
}
unknownNames = [];
for(var wKey in dictionary) { if(!dictionary.hasOwnProperty(wKey)) continue;
unknownNames.push([wKey,dictionary[wKey]]);
}
console.log(new Date(),'RPG initialized');
ready = true;
//console.log(unknownWords);
//newEncounter(function(monster) {
// console.log(new Date(),monster);
//});
});
},
parse: function(msg,user,name,channel) {
if(!rpg || !ready) return;
if(msg.substr(0,4) == '!rpg' || msg.substr(0,4) == '/rpg') {
if(!rpg.users.hasOwnProperty(user)) { // If user doesn't exist
rpg.users[user] = new Player({ id:user, name: name, module: 'tutorial' });
save();
}
sendMsg(channel,describeUser(user));
if(user != channel) { // If not a PM
sendMsg(channel,['To play the RPG, talk to me in private chat: @D-Bot']);
} else {
sendMsg(channel,[rpg.users[user].getPrompt()]);
}
return;
} else if(msg[0] == '!' || msg[0] == '/') {
return; // Don't listen to any other commands
}
if(user != channel) return; // Only proceed if PM
if(!rpg.users.hasOwnProperty(user)) { // If user doesn't exist
sendMsg(channel,['Type !rpg to initialize your character.']); return;
}
var player = rpg.users[user], cmd = msg.toLowerCase();
if(cmd) {
player.doAction(cmd);
}
sendMsg(channel,[player.getPrompt()]);
////////////////////////////////////////////////////////////////
//sendMsg(channel,['The game\'s not ready yet, come back later.']); return;
////////////////////////////////////////////////////////////////
save();
}
};