UNPKG

8.79 kBJavaScriptView Raw
1/*
2The MIT License (MIT)
3
4Copyright (c) 2014 Nathan Wittstock
5
6Permission is hereby granted, free of charge, to any person obtaining a copy
7of this software and associated documentation files (the "Software"), to deal
8in the Software without restriction, including without limitation the rights
9to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10copies of the Software, and to permit persons to whom the Software is
11furnished to do so, subject to the following conditions:
12
13The above copyright notice and this permission notice shall be included in
14all copies or substantial portions of the Software.
15
16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22THE SOFTWARE.
23*/
24
25/**
26 * TODO Items:
27 *
28 * [ ] Win Condition
29 * [ ] Death Condition
30 * [x] Separation into require'd library
31 * [ ] Error checking and recovery
32 */
33
34
35var mongo = require('mongodb').MongoClient;
36var natural = require('natural');
37var i18n = new (require('i18n-2'))({ locales: ['en']});
38var _ = require('underscore');
39var tokenizer = new natural.WordTokenizer();
40
41var advtxt = {};
42
43exports = module.exports = advtxt.Server = function(connectionString) {
44 var self = this;
45 self.mongodb = null;
46
47 console.info('Connecting to MongoDB database…');
48 // now connect to mongo
49 mongo.connect(connectionString, function(err, db) {
50 if (err) throw err;
51
52 if (db) {
53 console.info('Connected.');
54 self.mongodb = db;
55
56 }
57 else {
58 console.error('Connected, but failed to get a database…');
59 process.exit(1);
60 }
61 });
62}
63
64/**
65 * Moves the player's coordinates to another room, and then passes that off to
66 * the function that actually gets the room.
67 *
68 * @since 0.0.1
69 * @param {command} command - The command object.
70 * @param {string} direction - A string representation of the direction we're
71 * moving in.
72 */
73advtxt.Server.prototype.movePlayer = function(command, direction) {
74 var self = this;
75
76 var move = [];
77 // right now we only process cardinal directions
78 if (direction === i18n.__('north') || direction === 'n')
79 move = [0, -1];
80 else if (direction === i18n.__('south') || direction === 's')
81 move = [0, 1];
82 else if (direction === i18n.__('east') || direction === 'e')
83 move = [1, 0];
84 else if (direction === i18n.__('west') || direction === 'w')
85 move = [-1, 0];
86 else
87 move = [0, 0];
88
89 // now we apply those moves to the player object
90 command.player.x += move[0];
91 command.player.y += move[1];
92
93 // now we need to update the player in the database
94 var playerCollection = self.mongodb.collection('player');
95 playerCollection.update({_id: command.player._id},
96 {$set: {x: command.player.x, y: command.player.y}}, {w:1},
97 function(err, success) {
98 if (err) throw err;
99
100 // if we were successful, lets update their location and send a response
101 if (success) {
102 self.getCurrentLocation(command, true);
103 }
104 });
105}
106
107
108/**
109 * Updates the player's item collection in the database
110 *
111 * @since 0.0.1
112 * @param {player} player - The player object to be updated.
113 * @param {updatePlayerCallback} next - The optional callback to be executed
114 * after the update.
115 */
116advtxt.Server.prototype.updatePlayerItems = function(player, next) {
117 var self = this;
118
119 var playerCollection = self.mongodb.collection('player');
120 playerCollection.update({_id: player._id},
121 {$set: {items: player.items}}, {w:1},
122 function(err, success) {
123 if (err) throw err;
124
125 // the player is saved, do nothing unless there's an error
126 if (!success) {
127 console.error("Couldn't save player: " + player.username);
128 console.error(player);
129 }
130 if (typeof next !== 'undefined' && next) next(success);
131 });
132}
133
134/**
135 * This callback runs after the player is updated.
136 *
137 * @callback updatePlayerCallback
138 * @param {number} playerUpdated
139 */
140
141
142/**
143 * Process a command that was received from the player.
144 *
145 * @since 0.0.1
146 * @param {command} command - The command object
147 */
148advtxt.Server.prototype.doCommand = function(command) {
149 var self = this;
150 command.command = tokenizer.tokenize(command.command.toLowerCase());
151
152 // make things less verbose
153 var verb = command.command[0];
154 var obj = command.command[1];
155 var player = command.player;
156 var room = player.room;
157
158 if (verb === i18n.__('get')) {
159 // would try to get an item
160 if (typeof room.items[obj] !== 'undefined') {
161 var available = room.items[obj].available(player);
162 if (available === true) {
163 // that item was available. get the item
164 player.items[obj] = room.items[obj].name;
165 command.reply(room.items[obj].get(player));
166 // update the player
167 self.updatePlayerItems(player);
168 }
169 // that item wasn't available
170 else {
171 command.reply(available);
172 }
173 }
174 // there wasn't an item by that name
175 else {
176 command.reply(i18n.__('You can\'t find a "%s" in this room!', obj));
177 }
178 }
179 else if (verb === i18n.__('go')) {
180 // would try to move in a direction
181 if (typeof room.exits[obj] !== 'undefined') {
182 var available = room.exits[obj].available(player);
183 if (available === true) {
184 // that direction was available, play the "go" message, then move them
185 command.reply(room.exits[obj].go(player));
186 // move the player
187 self.movePlayer(command, obj);
188 }
189 // that direction wasn't available; give the reason
190 else {
191 command.reply(available);
192 }
193 }
194 // there wasn't a direction by that name
195 else {
196 // TODO give customized replies for actual directions
197 command.reply(i18n.__('You can\'t go "%s", it just doesn\'t work.', obj));
198 }
199 }
200 // otherwise, try to run the command from our possible ones
201 else if (typeof room.commands[verb] !== 'undefined') {
202 command.reply(room.commands[verb](player));
203 }
204 // if they asked for the exits, list them
205 else if (verb === i18n.__('exits')) {
206 var exits = [];
207 for (var key in room.exits) {
208 exits.push(room.exits[key].name);
209 }
210
211 var exitNames = i18n.__('Available exits: ');
212 for (var i = 0; i < exits.length; i++) {
213 exitNames += exits[i];
214 if (i !== exits.length - 1) exitNames += i18n.__(", ");
215 }
216 command.reply(exitNames);
217 }
218 else {
219 command.reply(i18n.__('Sorry, I don\'t know how to "%s" in this room.', verb));
220 }
221}
222
223/**
224 * Gets the room a player is in, and inserts that room into the command object.
225 * Responds to the player if they just entered the room.
226 *
227 * @since 0.0.1
228 * @param {command} command - The command object.
229 * @param {boolean} enterRoom - If the player has just entered the room, we need
230 * to play them an entrance message.
231 */
232advtxt.Server.prototype.getCurrentLocation = function(command, enterRoom) {
233 var self = this;
234
235 var roomCollection = self.mongodb.collection('room');
236 roomCollection.findOne({x: command.player.x, y: command.player.y, map: command.player.map}, function (err, room) {
237 if (err) throw err;
238
239 if (room) {
240 // we assign the room into the command object, so we remember everything
241 // about it
242 command.player.room = room;
243 // then we get the player into a local var, all of the commands that we
244 // eval use "player.room.xxxxx" as their namespace, so this will ensure
245 // they're assigned properly
246 var player = command.player;
247
248 // now we need to eval what was in the db
249 eval(player.room.commands);
250 eval(player.room.items);
251 eval(player.room.exits);
252
253 // this is a special case for a new player, who wouldn't see the info
254 // about the first room as they were placed here programatically, not by
255 // their own command. So we send that response.
256 if (enterRoom) {
257 command.reply(player.room.description);
258 }
259 // otherwise, process the command that was given
260 else {
261 self.doCommand(command);
262 }
263 }
264 });
265}
266
267/**
268 * Get the user that is addressed in the command, and inserts that information
269 * into the command.
270 *
271 * @since 0.0.1
272 * @param {command} command - The command object.
273 */
274advtxt.Server.prototype.processCommand = function(command) {
275 var self = this;
276
277 var playerCollection = self.mongodb.collection('player');
278 playerCollection.findOne({username: command.player, map: "default"}, function(err, player) {
279 if (err) throw err;
280
281 if (player) {
282 command.player = player;
283 self.getCurrentLocation(command);
284 }
285 else {
286 playerCollection.insert({
287 username: command.player,
288 map: "default",
289 x: 0,
290 y: 0,
291 room: {},
292 items: {}
293 }, function(err, player) {
294 if (err) throw err;
295
296 command.player = player[0];
297 self.getCurrentLocation(command, true);
298 });
299 }
300 });
301}
302