var glob = require('glob'), fs = require('fs'), crypto = require('crypto'); session = { "updateRate": 3000, "players": {}, "rooms": {}, "mobiles": {}, "mobileIds": 1, "items": {}, "itemIds": 1 }; builder = require('./builder'); admin = require('./admin'); commands = require('./commands'); rooms = require(config.worldDataPath + '/rooms'); items = require(config.worldDataPath + '/items'); mobiles = require(config.worldDataPath + '/mobiles'); class World { constructor() { this.time = 0; this.blacklist = ["constructor"]; this.blacklistPrefix = "mud_"; this.timerId = -1; } mud_load() { if(this.timerId != -1 ) { clearInterval(this.timerId); } session.updateRate = 3000; session.rooms = {}; session.items = {}; session.mobiles = {}; session.players = {}; session.mobileIds = 1; session.itemIds = 1; //populate rooms for(var roomName in rooms) { var room = builder.mud_spawnRoom(roomName); if(typeof room.mud_init == 'function') { room.mud_init(); } } //populate mobiles for(var mobileName in mobiles) { if(mobileName == "PlayerMobile") { continue; } var mobile = builder.mud_spawnMobile(mobileName); if(typeof mobile.mud_init == 'function') { mobile.mud_init(mobileName); } else { world.mud_destroyMobile(mobile.id); } } //populate items for(var itemName in items) { var item = builder.mud_spawnItem(itemName); if(typeof item.mud_init == 'function') { item.mud_init(); } else { world.mud_destroyItem(item.id); } } this.timerId = setInterval(function() { world.mud_tick(Date.now()); }, session.updateRate); } mud_handleInput(roomName, mobileId, input) { var room = this.mud_getRoom(roomName); var mobile = this.mud_getMobile(mobileId); var command = input.shift(); if(command.indexOf(this.blacklistPrefix) != -1) { libs.Output.toMobile(mobile.id, "I didn't understand that."); return true; } if(this.blacklist.indexOf(command) != -1) { libs.Output.toMobile(mobile.id, "I didn't understand that."); return true; } if (world.mud_mobileHasRole(mobile.id, "admin") && typeof admin[command] == 'function') { try { if(admin[command](room, mobile, input)) { return true; } } catch (error) { libs.Output.toServerError("Exception in admin tools - ", error); libs.Output.toMobile(mobile.id, "The administration tools are misbehaving."); if(world.mud_mobileHasRole(mobile.id, "admin")) { libs.Output.toMobile(mobile.id, error.code + " " + error.stack + "\n"); } } } if (world.mud_mobileHasRole(mobile.id, "builder") && typeof builder[command] == 'function') { try { if(builder[command](room, mobile, input)) { return true; } } catch (error) { libs.Output.toServerError("Exception in builder tools - ", error); libs.Output.toMobile(mobile.id, "The build tools are misbehaving."); if(world.mud_mobileHasRole(mobile.id, "admin")) { libs.Output.toMobile(mobile.id, error.code + " " + error.stack + "\n"); } } } for (var itemIndex in mobile.items) { var otherItem = this.mud_getItem(mobile.items[itemIndex]); if(typeof otherItem[command] == 'function') { try { if(otherItem[command](room, mobile, input)) { return true; } } catch (error) { libs.Output.toServerError("Exception in mobile item: " + mobile.constructor.name + ":" +mobile.id+","+otherItem.constructor.name+":"+otherItem.id+" - ", error); libs.Output.toMobile(mobile.id, otherItem.name + " you are holding {error}glitches{/error} for a second."); if(world.mud_mobileHasRole(mobile.id, "builder")) { libs.Output.toMobile(mobile.id, error.code + " " + error.stack + "\n"); } } } } if(typeof mobile[command] == 'function') { try { if(mobile[command](room, mobile, input)) { return true; } } catch (error) { libs.Output.toServerError("Exception in mobile: " + mobile.constructor.name + ":" +mobile.id+" - ", error); libs.Output.toMobile(mobile.id, "You feel funny and {error}not quite right{/error}."); if(world.mud_mobileHasRole(mobile.id, "builder")) { libs.Output.toMobile(mobile.id, error.code + " " + error.stack + "\n"); } } } for (var itemIndex in room.items) { var otherItem = this.mud_getItem(room.items[itemIndex]); if(typeof otherItem[command] == 'function') { try { if(otherItem[command](room, mobile, input)) { return true; } } catch (error) { libs.Output.toServerError("Exception in room item: " + room.constructor.name + ","+otherItem.constructor.name+":"+otherItem.id+" - ", error); libs.Output.toMobile(mobile.id, otherItem.name + " here {error}glitches{/error} for a second."); if(world.mud_mobileHasRole(mobile.id, "builder")) { libs.Output.toMobile(mobile.id, error.code + " " + error.stack + "\n"); } } } } for (var mobileIndex in room.mobiles) { var otherMobile = this.mud_getMobile(room.mobiles[mobileIndex]); if(typeof otherMobile[command] == 'function') { try { if(otherMobile[command](room, mobile, input)) { return true; } } catch (error) { libs.Output.toServerError("Exception in room mobile: " + room.constructor.name + "," + otherMobile.constructor.name +":"+otherMobile.id+" - ", error); libs.Output.toMobile(mobile.id, otherMobile.name + " {error}staggers{/error} in and out of reality."); if(world.mud_mobileHasRole(mobile.id, "builder")) { libs.Output.toMobile(mobile.id, error.code + " " + error.stack + "\n"); } } } } if(typeof room[command] == 'function') { try { if(room[command](room, mobile, input)) { return true; } } catch (error) { libs.Output.toServerError("Exception in room: " + room.id + " - ", error); libs.Output.toMobile(mobile.id, "You see strange {error}glitch errors{/error} in the surrounding area."); if(world.mud_mobileHasRole(mobile.id, "builder")) { libs.Output.toMobile(mobile.id, error.code + " " + error.stack + "\n"); } } } for(var commanderClass in commands) { var commander = new commands[commanderClass](); if (typeof commander[command] == 'function') { try { if(commander[command](room, mobile, input)) { return true; } } catch (error) { libs.Output.toServerError("Exception in command: " + commanderClass + " - ", error); libs.Output.toMobile(mobile.id, commander.mud_getErrorMessage()); if(world.mud_mobileHasRole(mobile.id, "builder")) { libs.Output.toMobile(mobile.id, error.code + " " + error.stack + "\n"); } } } } libs.Output.toMobile(mobile.id, "I didn't understand that."); return true; } mud_tick(time) { try { for(var roomName in session.rooms) { var room = this.mud_getRoom(roomName); if(typeof room.mud_tick == 'function') { room.mud_tick(time); } } } catch (error) { libs.Output.worldToMobiles("The flow of time seems to {error}stutter{/error} a second."); libs.Output.toBuildersError("Room tick error", error); libs.Output.toServerError("Room tick error", error); } try { for(var mobileId in session.mobiles) { var mobile = this.mud_getMobile(mobileId); if(mobile == null) { console.log("null mobile", mobile); continue; } if(typeof mobile.mud_tick == 'function') { mobile.mud_tick(time); } } } catch (error) { libs.Output.worldToMobiles("Lifeforms in the universe {error}skip{/error} a unified heartbeat."); libs.Output.toBuildersError("Mobile tick error", error); libs.Output.toServerError("Mobile tick error", error); } try { for(var itemId in session.items) { var item = this.mud_getItem(itemId); if(item == null) { console.log("null item", item); continue; } if(typeof item.mud_tick == 'function') { item.mud_tick(time); } } } catch (error) { libs.Output.worldToMobiles("Items everywhere {error}flicker{/error} out of reality for a second."); libs.Output.toBuildersError("Item tick error", error); libs.Output.toServerError("Item tick error", error); } this.time++; this.time %= 640; switch(this.time) { case 0: //text must include dawn libs.Output.worldToMobiles("{global}Dawn breaks as the sun begins to rise.{/global}"); this.mud_allPlayersSave(); break; case 160: //text must include midday libs.Output.worldToMobiles("{global}The sky is a clear, crisp cerulean to hold the bright midday sun.{/global}"); this.mud_allPlayersSave(); break; case 320: //text must include twilight libs.Output.worldToMobiles("{global}Twilight creeps in slowly as the sun begins to set.{/global}"); this.mud_allPlayersSave(); break; case 480: //text must include midnight libs.Output.worldToMobiles("{global}Stars twinkle in the midnight sky.{/global}"); this.mud_allPlayersSave(); break; } } mud_moveMobile(mobileId, sourceRoom, destinationRoom, outDirection, inDirection) { if(session.rooms[destinationRoom] == null) { libs.Output.toMobile(mobileId, destinationRoom + " doesn't seem to exist yet."); return; } this.mud_moveMobileFromRoom(sourceRoom, mobileId, outDirection); this.mud_moveMobileToRoom(destinationRoom, mobileId, inDirection); } mud_moveMobileToRoom(roomName, mobileId, direction) { var mobileObj = this.mud_getMobile(mobileId); if(typeof mobileObj.mud_enterRoom == "function") { libs.Output.toRoom(roomName, mobileObj.mud_enterRoom(direction), mobileObj); } else { if(direction != null) { libs.Output.toRoom(roomName, mobileObj.mud_getName() + " " + direction + "."); } else { libs.Output.toRoom(roomName, mobileObj.mud_getName() + " arrives.", mobileObj); } } this.mud_addMobileToRoom(roomName, mobileId); this.mud_handleInput(roomName, mobileId, ["look"]); } mud_moveMobileFromRoom(roomName, mobileId, direction) { var mobileObj = this.mud_getMobile(mobileId); this.mud_removeMobileFromRoom(roomName, mobileId); if(typeof mobileObj.mud_leaveRoom == "function") { libs.Output.toRoom(roomName, mobileObj.mud_leaveRoom(direction), mobileObj); } else { if(direction != null) { libs.Output.toRoom(roomName, mobileObj.mud_getName() + " " + direction + "."); } else { libs.Output.toRoom(roomName, mobileObj.mud_getName() + " leaves.", mobileObj); } } } mud_getItem(itemId) { var item = session.items[itemId]; if(item == null) { throw new Error("Item '"+itemId+"' not defined."); } return item; } mud_getMobile(mobileId) { var mobile = session.mobiles[mobileId]; if(mobile == null) { throw new Error("Mobile '"+mobileId+"' not defined."); } return mobile; } mud_getRoom(roomName) { var room = session.rooms[roomName]; if(room == null) { throw new Error("Room '"+roomName+"' not defined."); } return room; } mud_addMobileToRoom(roomName, mobileId) { var mobileObj = this.mud_getMobile(mobileId); var roomObj = this.mud_getRoom(roomName); mobileObj.roomId = roomName; roomObj.mobiles.push(mobileId); } mud_removeMobileFromRoom(roomName, mobileId) { var room = this.mud_getRoom(roomName); room.mobiles.splice(room.mobiles.indexOf(mobileId), 1); } mud_addItemToRoom(roomName, itemId) { var room = this.mud_getRoom(roomName); if(room.items.indexOf(itemId) == -1) { room.items.push(itemId); var item = world.mud_getItem(itemId); item.inRoom = roomName; } } mud_addItemToMobile(mobileId, itemId) { var mobile = this.mud_getMobile(mobileId); if(mobile.items.indexOf(itemId) == -1) { mobile.items.push(itemId); var item = world.mud_getItem(itemId); item.heldBy = mobileId; } } mud_removeItemFromMobile(mobileId, itemId) { var mobile = session.mobiles[mobileId]; for(var slotIndex in mobile.itemSlots) { var slot = mobile.itemSlots[slotIndex]; if(slot == itemId) { mobile.itemSlots[slotIndex] = null; var item = world.mud_getItem(itemId); item.isWorn = -1; item.heldBy = -1; } } if(mobile.items.indexOf(itemId) != -1) { mobile.items.splice(mobile.items.indexOf(itemId), 1); } } mud_removeItemFromRoom(roomId, itemId) { var room = session.rooms[roomId]; room.items.splice(room.items.indexOf(itemId), 1); var item = world.mud_getItem(itemId); item.inRoom = -1; } mud_destroyItem(itemId) { delete session.items[itemId]; } mud_destroyMobile(mobileId) { delete session.mobiles[mobileId]; } mud_mobileHasRole(mobileId, role) { var mobile = session.mobiles[mobileId]; if(mobile.roles != null) { return mobile.roles.indexOf(role) != -1; } return false; } mud_generateHash(input) { return crypto.createHash('sha256').update(input).digest("base64"); } mud_getUserData(username) { var playerFilename = config.playerDataPath+ "/"+username.toLowerCase().trim()+".json"; var files = glob.sync(playerFilename, {} ); if(files.length == 1) { var fileData = fs.readFileSync(files[0], "utf8"); return JSON.parse(fileData); } return null; } mud_isValidLogin(userData, password) { var hash = crypto.createHash('sha256').update(userData.username.toLowerCase().trim() + password).digest("base64"); if(userData.password == hash) { return true; } return false; } mud_playerLoad(login, userData) { for(var activePlayerId in session.players) { var activePlayer = session.players[activePlayerId]; if(activePlayer.password == userData.password) { libs.Output.toMobile(activePlayer.id, "You are being booted by another login."); var oldLogin = activePlayer.login.reconnect(login); login.player = activePlayer; login.setConnectionTimeout(activePlayer.connectionTimeout); return; } } var player = builder.mud_spawnMobile("PlayerMobile"); player.login = login; player.connectionTimeout = userData.connectionTimeout || 0; player.login.setConnectionTimeout(player.connectionTimeout); player.name = userData.username; player.description = userData.description || "A player"; player.password = userData.password; player.roomId = userData.roomName || "ZebedeeTemple"; player.hintColor = userData.hintColor || "FgMagenta"; player.terminalWidthPreference = userData.terminalWidthPreference || 78; player.connectionTimeout = userData.connectionTimeout || 0; player.keywords.push(player.name.toLowerCase()); player.roles = userData.roles || []; player.title = userData.title || "the Player"; player.pubKeys = userData.pubKeys || []; player.account = parseInt(userData.account) || 0; for(var itemIdIndex in userData.items) { var loadedItemData = userData.items[itemIdIndex]; var loadedItem = builder.mud_spawnItem(loadedItemData.classname); if(typeof loadedItem.mud_loadItem == "function") { loadedItem.mud_loadItem(loadedItemData); } this.mud_addItemToMobile(player.id, loadedItem.id); } player.aliases = userData.aliases; for(var aliasKey in userData.aliases) { player.mud_setAlias(aliasKey, userData.aliases[aliasKey]); } login.player = player; world.mud_attachPlayer(player); } mud_attachPlayer(player) { this.mud_addMobileToRoom(player.roomId, player.id); session.players[player.id] = player; var joinMessage = player.name + " joined the server."; libs.Output.toServer(joinMessage); libs.Output.worldToMobiles(joinMessage, player); this.mud_handleInput(player.roomId, player.id, ["look"]); } mud_playerSave(player) { var playerFilename = config.playerDataPath+ "/"+player.name.toLowerCase().trim()+".json"; var userData = {}; userData.username = player.name; userData.password = player.password, userData.roomName = session.rooms[player.roomId].constructor.name; userData.hintColor = player.hintColor; userData.description = player.description; userData.terminalWidthPreference = player.terminalWidthPreference; userData.connectionTimeout = player.connectionTimeout; userData.roles = player.roles; userData.title = player.title; userData.pubKeys = player.pubKeys; userData.account = player.account; var items = []; for(var itemIdIndex in player.items) { var savedItem = {}; if(typeof session.items[player.items[itemIdIndex]].mud_saveItem == "function") { savedItem = session.items[player.items[itemIdIndex]].mud_saveItem(); } savedItem.classname = session.items[player.items[itemIdIndex]].constructor.name; items.push(savedItem); } userData.items = items; userData.aliases = player.aliases; fs.writeFile(playerFilename, JSON.stringify(userData, null, "\t"), (err) => { if (err) { throw err; } libs.Output.toServer("saved player data " + playerFilename); }); } mud_allPlayersSave() { for(var index in session.players) { var player = session.players[index]; this.mud_playerSave(player); } } mud_playerQuit(player) { this.mud_playerSave(player); var playerId = player.id; for(var itemIdIndex in player.items) { var item = session.items[player.items[itemIdIndex]]; this.mud_removeItemFromMobile(player.id, item.id); this.mud_destroyItem(item.id); } this.mud_removeMobileFromRoom(player.roomId, player.id); delete session.players[player.id]; this.mud_destroyMobile(playerId); libs.Output.toServer(player.mud_getName() + " left the server."); libs.Output.worldToMobiles(player.mud_getName() + " left the server.", player); return true; } mud_rollRandom(probabilitySet) { var rollValue = parseInt(Math.random() * 100) + 1; var probabilityTotal = 0; var sortedSet = probabilitySet.sort(function(a, b) { return (a.probability < b.probability) ? 1 : ((a.probability > b.probability) ? -1 : 0); }); for(var index in sortedSet) { var row = sortedSet[index]; probabilityTotal += row.probability; if(rollValue <= probabilityTotal) { return row.value; } } } mud_replaceRoomItem(roomId, itemName) { var isFound = false; var room = this.mud_getRoom(roomId); for(var i in room.items) { var itemId = room.items[i]; if(this.mud_getItem(itemId).constructor.name == itemName) { isFound = true; } } if(!isFound) { world.mud_addItemToRoom(roomId, builder.mud_spawnItem(itemName).id); } } } module.exports = new World();