text: // .env Variables
  require('dotenv').config({path: '../.env'});
  
  // Node Modules
  const Discord = require('discord.js');
  const client = new Discord.Client();
  const cron = require('node-cron');
  
  // Bot Modules
  const dataRequest = require('../modules/dataRequest');
  const calcRandom = require('../modules/calcRandom');
  const PlayerClass = require('./playerClass');
  const DungeonClass = require('./dungeonClass');
  
  // JSON
  const rooms = require('./rooms.json');
  
  // Playing dialogs
  const nothingMessage = "with locks."
  
  // State Machine
  var BotEnumState = {
      NOTHING: 0,
      WAITING_FOR_PLAYERS: 1,
      ACTIVE: 2
  }
  
  // Directions
  var Directions = {
      NORTH: 0,
      EAST: 1,
      SOUTH: 2,
      WEST: 3
  }
  
  // Dungeon State
  var DungeonState = {
      WAITING_FOR_USERS: 0,
      INSIDE_DUNGEON: 1,
      FIGHTING: 2,
      BOSS_BATTLE: 3
  }
  
  // Modes (these better not be final, me.)
  var DungeonModes = {
      HEIRARCHY: 0,   // Uses heirarchy to determine movement
      ANARCHY: 1,     // Anyone can determine movement
      DEMOCRACY: 2    // Voting system
  }
  
  // Dungeons
  class DungeonRaidInstance {
      constructor(room) {
          this.room = room;
          this.mode = DungeonModes.DEMOCRACY;
          this.players = [];
          this.location;
          this.state = DungeonState.WAITING_FOR_USERS;
          this.dialogObj;
          this.reroutedRooms = [];
          this.items = [];
          this.isTyping;
          this.timer;
          this.startTimer;
          this.crystalsGained = 0;
          this.directionalCollector;
          this.directionalMessageID;
      }
  }
  
  // Dungeon collections;
  var dungeonCollection = [];
  
  // The ready event is vital, it means that your bot will only start reacting to information
  // from Discord _after_ ready is emitted
  client.on('ready', async () => {
      // Generates invite link
      try {
          let link = await client.generateInvite(["ADMINISTRATOR"]);
          console.log("Invite Link: " + link);
      } catch(e) {
          console.log(e.stack);
      }
  
      // You can set status to 'online', 'invisible', 'away', or 'dnd' (do not disturb)
      client.user.setStatus('invisible');
  
      console.log(`Connected! \
      \nLogged in as: ${client.user.username} - (${client.user.id})`);
  
      //client.guilds.get(process.env.SANCTUM_ID).members.get(client.user.id).setNickname("");
  });
  
  // Create an event listener for messages
  client.on('message', async message => {
      // Ignores ALL bot messages
      if (message.author.bot) return;
      // Message has to be in test or hell's gate
      if (!(message.channel.id !== process.env.TEST_CHANNEL_ID || message.channel.id !== process.env.HELLS_GATE_CHANNEL_ID)) {
              const args = message.content.slice(process.env.PREFIX.length).trim().split(/ +/g);
              const command = args.shift().toLowerCase();
              if (command !== "party") return;
      };
      // Has to be (prefix)command
      if (message.content.indexOf(process.env.PREFIX) !== 0) return;
  
      // "This is the best way to define args. Trust me."
      // - Some tutorial dude on the internet
      const args = message.content.slice(process.env.PREFIX.length).trim().split(/ +/g);
      const command = args.shift().toLowerCase();
  
      switch (command) {
          case "ping":
              if (isAdmin(message.author.id)) {
                  return message.reply("Pong!");
              }
          break;
          case "summonr":
              if (isAdmin(message.author.id)) {
                  return NewDungeonSequence(process.env.TEST_CHANNEL_ID);
              }
          break;
          case "vanishr":
              if (isAdmin(message.author.id) && getDungeonPlayer(message.author.id).playerDungeon !== undefined) {
                  return BotActive(getDungeonPlayer(message.author.id).playerDungeon);
              }
          break;
          case "help":
              if (args[0] && message.mentions.members.first() !== undefined) {
                  if (message.mentions.members.first().id !== client.user.id) break;
                  let tempVar = '';
                  tempVar += "Here are the commands you can do."
                  tempVar += "\n\n"
                  tempVar += "!map (!m)\n```Checks your high-tech map about the dungeon.```"
                  tempVar += "\n"
                  tempVar += "!party (!p)\n```Lists all members in your party.```"
                  tempVar += "\n"
                  tempVar += "!promote (!pr)\n```If you're the leader, you can promote others to be a party leader with you in order to move around.```"
                  tempVar += "\n"
                  tempVar += "!demote (!de)\n```If you're the leader, you can demote others if needed if they are a commander.```"
                  return sendMessage(message.channel.id, tempVar);
              }
          break;
          case "leave":
              leaveMessage(message);
          break;
      }
  
      // Scans if player is in the raid collection
      const dungeonPlayer = getDungeonPlayer(message.author.id);
      var isLeader = dungeonPlayer.isLeader;              // Gets if author is leader of raid
      var isCommander = dungeonPlayer.isCommander;        // Gets if userID is commander
      var playerID = dungeonPlayer.playerID;              // Grabs message.author if this is part of raid
      var playerDungeon = dungeonPlayer.playerDungeon;    // Grab's the player's dungeon if joined
  
      // If user has not joined a party
      /*
      if (!playerID) {
          switch (command) {
              case "join":
                  joinMessage(message, getRoomName(args[0]));
              break;
              case "j":
                  joinMessage(message, getRoomName(args[0]));
              break;
          }
      }
      */
  
      // Party member commands only
      if (!playerID) return;
  
      // Party commands
      switch (command) {
          case "check":
              return sendMessage(message.author.id, message.channel.id, "Please use **!map** for checking the map.");
          case "members":
              if (args[0] !== undefined) {
                  var num = Math.floor(parseInt(args[0]));       
                  if (Number.isSafeInteger(num) && Math.sign(num) > 0) {
                      return partyMembers(playerDungeon, num);
                  }
              } else {
                  return partyMembers(playerDungeon);
              }
          break;
          case "party":
              if (args[0] !== undefined) {
                  var num = Math.floor(parseInt(args[0]));       
                  if (Number.isSafeInteger(num) && Math.sign(num) > 0) {
                      return partyMembers(playerDungeon, num);
                  }
              } else {
                  return partyMembers(playerDungeon);
              }
          break;
          case "p":
              if (args[0] !== undefined) {
                  var num = Math.floor(parseInt(args[0]));       
                  if (Number.isSafeInteger(num) && Math.sign(num) > 0) {
                      return partyMembers(playerDungeon, num);
                  }
              } else {
                  return partyMembers(playerDungeon);
              }
          break;
          case "promote":
              return promoteCommander(message.author, message.mentions.members.first(), isLeader, message.channel.id);
          case "pr":
              return promoteCommander(message.author, message.mentions.members.first(), isLeader, message.channel.id);
          case "demote":
              return demoteCommander(message.author, message.mentions.members.first(), isLeader, message.channel.id);
          case "de":
              return demoteCommander(message.author, message.mentions.members.first(), isLeader, message.channel.id);
          case "transferleadership":
              return transferLeader(message, message.mentions.members.first());
          case "giveleadership":
              return transferLeader(message, message.mentions.members.first());
          case "map":
              return sendEmbedLocation(playerDungeon);
          case "m":
              return sendEmbedLocation(playerDungeon);
          case "location":
              return sendEmbedLocation(playerDungeon);
          case "l":
              return sendEmbedLocation(playerDungeon);
          case "locationtext":
              return sendLocation(playerDungeon, message.author);
          case "lt":
              return sendLocation(playerDungeon, message.author);
          case "pinventory":
              if (args[0] !== undefined) {
                  var num = Math.floor(parseInt(args[0]));       
                  if (Number.isSafeInteger(num) && Math.sign(num) > 0) {
                      return sendInventory(playerDungeon, num);
                  }
              } else {
                  return sendInventory(playerDungeon);
              }
          case "pinv":
              if (args[0] !== undefined) {
                  var num = Math.floor(parseInt(args[0]));       
                  if (Number.isSafeInteger(num) && Math.sign(num) > 0) {
                      return sendInventory(playerDungeon, num);
                  }
              } else {
                  return sendInventory(playerDungeon);
              }
          case "pi":
              if (args[0] !== undefined) {
                  var num = Math.floor(parseInt(args[0]));       
                  if (Number.isSafeInteger(num) && Math.sign(num) > 0) {
                      return sendInventory(playerDungeon, num);
                  }
              } else {
                  return sendInventory(playerDungeon);
              }
          case "timer":
              return sendTimerInChat(playerDungeon);
          case "cleardialog":
              console.log("Clearing player dungeon by command trigger...");
              playerDungeon.dialogObj = undefined;
              return;
          case "debug":
              console.log(playerDungeon.reroutedRooms);
              return;
      }
  
      // The dungeon has to be active now
      if (playerDungeon.state === DungeonState.WAITING_FOR_USERS) {
          //return sendMessage(message.author.id, message.channel.id, "**Command denied.** Please wait for until the dungeon closes.");
          return;
      }
  
      // Custom commands from JSON 
      if (playerDungeon.dialogObj === undefined) {
          var newCommand = playerDungeon.location[command];
          customCommands(newCommand, playerDungeon, message.author, command, args);
      // More custom commands that are children from other custom commands
      } else {
          var newCommand = playerDungeon.dialogObj[command];
          customCommands(newCommand, playerDungeon, message.author, command, args);
          return;
      }
  
      // Leader or commander only commands (movement)
      return;
      switch (command) {
          case "north":
              move(Directions.NORTH, playerDungeon, (isLeader || isCommander), message.author);
          break;
          case "n":
              move(Directions.NORTH, playerDungeon, (isLeader || isCommander), message.author);
          break;
          case "east":
              move(Directions.EAST, playerDungeon, (isLeader || isCommander), message.author);
          break;
          case "e":
              move(Directions.EAST, playerDungeon, (isLeader || isCommander), message.author);
          break;
          case "south":
              move(Directions.SOUTH, playerDungeon, (isLeader || isCommander), message.author);
          break;
          case "s":
              move(Directions.SOUTH, playerDungeon, (isLeader || isCommander), message.author);
          break;
          case "west":
              move(Directions.WEST, playerDungeon, (isLeader || isCommander), message.author);
          break;
          case "w":
              move(Directions.WEST, playerDungeon, (isLeader || isCommander), message.author);
          break;
          case "mode":
              
          break;
          case "m":
  
          break;
      }
  });
  
  client.on('error', console.error);
  client.on('warn', console.warn);
  
  // Gets if user has an Overseers rank
  function isAdmin(userID) {
      var guild = client.guilds.get(process.env.SANCTUM_ID);
      return guild.members.get(userID).roles.find(role => role.name === "Overseers");
  }
  
  // Creates an array that creates multiple different arrays inside of that array -> [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
  // http://www.frontcoded.com/splitting-javascript-array-into-chunks.html
  var createGroupedArray = function(arr, chunkSize) {
      var groups = [], i;
      for (i = 0; i < arr.length; i += chunkSize) {
          groups.push(arr.slice(i, i + chunkSize));
      }
      return groups;
  }
  
  // Gets the user's role by member
  function getFactionID(member) {
      var playerRole;
      var isGroupA = member.roles.has(process.env.GROUP_A_ROLE);
      var isGroupB = member.roles.has(process.env.GROUP_B_ROLE);
      var isGroupC = member.roles.has(process.env.GROUP_C_ROLE);
  
      if (isGroupA) playerRole = process.env.GROUP_A_ROLE;
      else if (isGroupB) playerRole = process.env.GROUP_B_ROLE;
      else if (isGroupC) playerRole = process.env.GROUP_C_ROLE;
      else playerRole = "none";
  
      return playerRole;
  }
    
  // Gets the Combat Class name by role
  function getCombatClassName(member) {
      var combatClass;
      var isTank      = member.roles.find(role => role.name === "🛡️ Tank");
      var isRogue     = member.roles.find(role => role.name === "🗡️ Rogue");
      var isDPSMelee  = member.roles.find(role => role.name === "⚔️ DPS Melee");
      var isDPSRange  = member.roles.find(role => role.name === "🏹 DPS Range");
      var isSupport   = member.roles.find(role => role.name === "❤️ Support");
  
      if (isTank)             combatClass = "Tank";
      else if (isRogue)       combatClass = "Rogue";
      else if (isDPSMelee)    combatClass = "DPS Melee";
      else if (isDPSRange)    combatClass = "DPS Range";
      else if (isSupport)     combatClass = "Support";
      else                    combatClass = "None";
  
      return combatClass;
  }
  
  // Returns JSON room data by room name
  function getRoomName(name) {
      if (name !== undefined) {
          if (name.toLowerCase() === "hellsgate") 
              return rooms.hellsgate;
      } else {
          name = "";
      }
  
      return { "undefined": name };
  }
  
  // Updates Player data in Dungeon Collection
  function updatePlayers() {
      // Scans if player is in the raid collection
      // Totally seemingly not intensive test of for loops (Wish I knew)
      if (dungeonCollection !== undefined) {
          // Loops through every dungeon active
          for (let index = 0; index < dungeonCollection.length; index++) {
              const dungeonElement = dungeonCollection[index];
  
              // Loops through every player in that dungeon
              for (let index = 0; index < dungeonElement.players.length; index++) {
                  const playerElement = dungeonElement.players[index];
                  const thisGuild = client.guilds.get(process.env.SANCTUM_ID);
                  const newMember = thisGuild.members.get(playerElement.userID);
                  // If user is in one of the dungeon
                  if (newMember) {
                      // Start updating
                      playerElement.factionID = getFactionID(newMember);
                      playerElement.combatClass = getCombatClassName(newMember);
                      //playerElement.badgeEmote;
                  }
              }         
          }
      }
  }
  
  // Collects usable username for !party
  function playerName(userID) {
      const server = client.guilds.get(process.env.SANCTUM_ID);
      const sizeLimit = 19;
      var name = server.members.get(userID).displayName;
  
      if (name.length > sizeLimit) {
          name = truncate(name, sizeLimit);
      }
      return name;
  }
  
  // Truncates super long text that is way too lon...
  function truncate(string, amount) {
      if (string.length > amount)
          return string.substring(0, amount - 3) + '...';
      else
          return string;
  }; 
  
  // Gets dungeon player by userID
  function getDungeonPlayer(userID) {
      // Scans if player is in the raid collection
      var isLeader = false;       // Gets if author is leader of raid
      var isCommander = false;    // Gets if user is commander
      var playerID;               // Grabs userID if this is part of raid
      var playerDungeon;          // Grabs the player's dungeon if joined
      var player;                 // Gets player class
  
      // Totally seemingly not intensive test of for loops (Wish I knew)
      if (dungeonCollection !== undefined) {
          // Loops through every dungeon active
          for (let index = 0; index < dungeonCollection.length; index++) {
              const dungeonElement = dungeonCollection[index];
              //console.log("dungeonElement | " + dungeonElement);
  
              // Loops through every player in that dungeon
              for (let index = 0; index < dungeonElement.players.length; index++) {
                  const playerElement = dungeonElement.players[index];
                  //console.log("playerElement | " + playerElement);
                  
                  // If user is in one of the dungeon
                  if (userID === playerElement.userID) {
                      playerID = userID;
                      player = playerElement;
                      playerDungeon = dungeonElement;     // Sets player's dungeon to this
                      
                      if (playerElement.leader) {
                          isLeader = true;
                      }
  
                      if (playerElement.commander) {
                          isCommander = true;
                      }
                  }
              }         
          }
      } else { console.log("UNDEFINED DUNGEON COLLECTION" + dungeonCollection) }
  
      return { isLeader, isCommander, playerID, playerDungeon, player };
  }
  
  // Async Waiting
  function sleep(time) {
      return new Promise((resolve, reject) => {
          setTimeout(resolve, time);
      });
  }
  
  // Checks for if custom command is valid
  async function customCommands(newCommand, playerDungeon, author, command, args) {
      function items() {
          var array = [];
          newCommand.pickup.forEach(element => {
              array.push(element);
          });
      }
  
      // Grabs the object if it exists
      console.log(`[Custom Commands] Type I  | newCommand exists: ${newCommand !== undefined}`)
      if (newCommand !== undefined && newCommand.command) {
          if (playerDungeon.state === DungeonState.WAITING_FOR_USERS) return;
          console.log(`[Custom Commands] Type II | internal: ${newCommand.internal} | command: ${command}`);
          // If command is meant to be an internal, inscript command
          if (newCommand.internal) {
              if (newCommand.pickup)
                  pickupItem(playerDungeon, newCommand, author, newCommand.pickup);
          } else {
              playerDungeon.dialogObj = newCommand;
              typingDialog(playerDungeon, newCommand, author);
          }        
      }
  }
  
  // Executes custom commands by emote text
  function customCommandEmoteProcessor(playerDungeon, newCommand, author, returnElement, newMessage) {
      //console.log("[Emote Processor] Emote reaction custom command: " + JSON.stringify(returnElement, null, 4));
      //console.log("[Emote Processor] newCommand: " + JSON.stringify(newCommand, null, 4));
      console.log("[Emote Processor] newCommand.type = " + newCommand.type);
      switch (newCommand.type) {
          case "chest":
              switch (returnElement.emote) {
                  // JSON : chest.options.unlock
                  case "🔓":
                      openChest(playerDungeon, newCommand[returnElement.command], author, false, newMessage, newCommand.embed)
                      break;
                  case "🔐":
                      openChest(playerDungeon, newCommand[returnElement.command], author, true, newMessage, newCommand.embed);
                      break;
                  case "🔏":
                      lockpickChest(playerDungeon, newCommand, author, newMessage, newCommand.embed);
                      break;
                  case "❌":
                      leaveObject(playerDungeon, newCommand[returnElement.command], author);
                      break;
              }            
              break;
      
          case "door":
              switch (returnElement.emote) {
                  case "🔐":
                      openDoor(playerDungeon, newCommand[returnElement.command], author, returnElement, newMessage, newCommand.embed);
                      break;
                  case "🔏":
                      lockpickDoor(playerDungeon, newCommand, author, newMessage, newCommand.embed);
                      break;
                  case "❌":
                      leaveObject(playerDungeon, newCommand[returnElement.command], author);
                      break;
              }
              break;
      }
  }
  
  // Adds item to inventory
  function addItem(playerDungeon, item, amount) {
      // If item exists in inventory
      console.log("[Add Item] " + amount);
      var i = playerDungeon.items.findIndex(i => i.name === item.name);
      console.log("[Item Amount Item Exists] i Equals " + i);
  
      // Item is internally handled
      if (item.internal) {
          switch (item.name.toLowerCase()) {
              case "crystals":
                  addCrystalsToPartyMember(playerDungeon, amount);
                  break;
          
              default:
                  break;
          }
      }
  
      // If there is an item that exists in inventory
      if (i !== -1) {
          console.log("[Item Amount Start] " + playerDungeon.items[i].amount);
          playerDungeon.items[i].amount += item.amount;    // Increments amount value
          console.log("[Item Amount Finish] " + playerDungeon.items[i].amount);
      // Disallows adding objects such as crystals
      } else if (!item.internal) {
          console.log("[Item Amount Wait] Created item for the first time.");
          var clonedItem = JSON.parse(JSON.stringify(item));  // Clones item in order to not actually increment the rooms.js item JSON data
          playerDungeon.items.push(clonedItem);
      }
  }
  
  // Removes item from party inventory (TODO: multi item stacks & only take one item)
  function removeItem(playerDungeon, item, amount) {
      let i = playerDungeon.items.findIndex( i => i.name === item.name );
      console.log("[Remove Item] i Equals " + i + ` | For item: ${item.name}`);
      if (i !== -1) {
          if (playerDungeon.items[i].amount > 2) {
              console.log("[Item Amount Start] " + playerDungeon.items[i].amount);
              playerDungeon.items[i].amount -= item.amount;   // Decrements amount value
              console.log("[Item Amount Finish] " + playerDungeon.items[i].amount);
          }
          else {
              console.log("[Item Amount Wait] Destroying item:\n" + playerDungeon.items.length);
              playerDungeon.items.splice(i, 1);
              console.log("[Item Amount Done] Finished Destroying item:\n" + playerDungeon.items.length);
          }
  
          console.log("[Remove Item] Successfully removed item " + item.name + " from " + playerDungeon.room.name)
          return true;
      }
      console.log("[Remove Item] Couldn't remove item!");
      return false;
  }
  
  // Adds crystals to EACH player dungeon players (GAMBLE IS BEING USED TO ADD)
  function addCrystalsToPartyMember(playerDungeon, amount) {
      playerDungeon.players.forEach(element => {
          dataRequest.sendServerData("gambleWon", amount, element.userID); 
          playerDungeon.crystalsGained += amount;
      });
  }
  
  // Pickup Item
  async function pickupItem(playerDungeon, newCommand, author, items) { 
      // Loops through all the items in array, and adds them into the item string
      var itemString = "";
      console.log("[Pickup Item] JSON Data:\n" + JSON.stringify(items, null, 4));
      for (let i = 0; i < items.length; i++) {
          const element = items[i].item;
          const item = rooms.items[element];  
          console.log("[Pickup Item] " + element + "\nJSON:\n" + JSON.stringify(item, null, 4));
          // Emote processing
          var emoteText = "";
          if (item.emote !== undefined && item.emote !== "") emoteText = `${item.emote} `;
  
          // Adds to item string & inventory
          var amount = 1;
          // Generates random number if array
          if (Array.isArray(items[i].amount))
              amount = calcRandom.random(items[i].amount[0], items[i].amount[1]);
          // Treats it as an int instead
          else {
              // If amount variables exists, set it to so
              if (items[i].amount) amount = items[i].amount;
          }
  
          var newAmount;
          switch (element) {
              // Meant to have each player get a set amount of crystals
              case "crystals":
                  newAmount = amount * playerDungeon.players.length;
                  break;
              // Normal handling
              default:
                  newAmount = amount;
                  break;
          }
  
          itemString += `> **${emoteText}${item.name}** [${newAmount}x]\n`;
          addItem(playerDungeon, item, amount);
      }
  
      // Sends embed
      const embed = new Discord.RichEmbed()
          .setAuthor("Ghost", client.user.avatarURL)
          .setColor(playerDungeon.room.color)
          .setTitle("Item Get!")
          .setDescription(`You picked up...\n${itemString}You can find it in the party's **!pinventory**.\nAny collected crystals or materials will be distributed evenly.`)
      
      client.channels.get(playerDungeon.room.channel).send({embed});
  
      // Debug
      // console.log("DEBUG JSON DATA: " + JSON.stringify(item. null, 4));
  
      await sleep(2500);
      playerDungeon.dialogObj = newCommand;
      playerDungeon.reroutedRooms.push([newCommand.moveFrom, newCommand.moveTo]);
      move(newCommand.moveTo, playerDungeon, true, author);
      playerDungeon.dialogObj = undefined;
  }
  
  // Lock picks a chest
  async function lockpickChest(playerDungeon, newCommand, author, newMessage, embedData) {
      var random = calcRandom.gamble(50);
      var channel = client.channels.get(playerDungeon.room.channel);
      var successMessage = `:lock_with_ink_pen: **You have successfully lockpicked the chest!** The party has saved a key.`;
      var failedMessage  = `:lock_with_ink_pen: **The unlock has failed.** Better luck next time!`;
      if (random) {
          if (!embedData) {
              if (!newMessage)
                  channel.send(successMessage);
          } else {
              var dungeonName = embedData.setAuthor.title.replace("${dungeon.room.name}", playerDungeon.room.name);
              var newImage = embedData.setAuthor.image.replace("${profile_pic}", client.user.avatarURL);
              const embed = new Discord.RichEmbed()
                  .setAuthor("Ghost", newImage)
                  .setColor(playerDungeon.room.color)
                  .setTitle(embedData.setTitle)
                  .setDescription("...")
  
              await newMessage.edit({embed});
              embed.description = successMessage;
              newMessage.edit({embed});
          }
          await sleep(2500);
          var dungeonName = embedData.setAuthor.title.replace("${dungeon.room.name}", playerDungeon.room.name);
          var newImage = embedData.setAuthor.image.replace("${profile_pic}", client.user.avatarURL);
          const embed = new Discord.RichEmbed()
              .setAuthor("Ghost", newImage)
              .setColor(playerDungeon.room.color)
              .setTitle(embedData.setTitle)
              .setDescription("...")
  
          await newMessage.edit({embed});
          lootChest(playerDungeon, newCommand.unlock, author, newMessage, embedData);
      } else {
          if (!embedData)
              channel.send(failedMessage);
          else {
              var dungeonName = embedData.setAuthor.title.replace("${dungeon.room.name}", playerDungeon.room.name);
              var newImage = embedData.setAuthor.image.replace("${profile_pic}", client.user.avatarURL);
              const embed = new Discord.RichEmbed()
                  .setAuthor("Ghost", newImage)
                  .setColor(playerDungeon.room.color)
                  .setTitle(embedData.setTitle)
                  .setDescription("...")
  
              await newMessage.edit({embed});
              embed.description = failedMessage;
              newMessage.edit({embed});
          }
          await sleep(2500);
  
          playerDungeon.dialogObj = newCommand.lockpick;
          move(newCommand.lockpick.moveToFail, playerDungeon, true, author);
          playerDungeon.dialogObj = undefined;
      }
  }
  
  // Opens the chest
  async function openChest(playerDungeon, newCommand, author, useKey, newMessage, embedData) {
      var successMessage = `${author} used the **:key: Key** to open the chest.`;
      var failedMessage  = "The party does not have a **:key: Key** to :closed_lock_with_key: unlock this chest.";
  
      console.log("[Open Chest] " + successMessage + " | " + failedMessage);
  
      if (useKey && playerDungeon.dialogObj.removeItem) {
          // Checks for key in inventory
          let removeKey = playerDungeon.items.findIndex( i => i.name === "Key" );
          console.log("[Remove Key] " + removeKey);
  
          // If key exist
          if (removeKey !== -1) {
              removeItem(playerDungeon, rooms.items["key"]);
              if (!embedData)
                  sendMessage(author.id, playerDungeon.room.channel, successMessage)
              else {
                  var dungeonName = embedData.setAuthor.title.replace("${dungeon.room.name}", playerDungeon.room.name);
                  var newImage = embedData.setAuthor.image.replace("${profile_pic}", client.user.avatarURL);
                  const embed = new Discord.RichEmbed()
                      .setAuthor("Ghost", newImage)
                      .setColor(playerDungeon.room.color)
                      .setTitle(embedData.setTitle + ": Unlocking")
                      .setDescription("...")
                  
                  await newMessage.edit({embed});
                  embed.description = successMessage;
                  newMessage.edit({embed});
                  await sleep(2500);
              }
          } else {
              if (!embedData)
                  sendMessage(author.id, playerDungeon.room.channel, failedMessage);
              else {
                  var dungeonName = embedData.setAuthor.title.replace("${dungeon.room.name}", playerDungeon.room.name);
                  var newImage = embedData.setAuthor.image.replace("${profile_pic}", client.user.avatarURL);
                  const embed = new Discord.RichEmbed()
                      .setAuthor("Ghost", newImage)
                      .setColor(playerDungeon.room.color)
                      .setTitle(embedData.setTitle)
                      .setDescription("...")
              
                  await newMessage.edit({embed});
                  embed.description = failedMessage;
                  newMessage.edit({embed});
                  await sleep(2500);
              }
  
              return typingDialog(playerDungeon, playerDungeon.dialogObj, author);
          }
      } else {
          var dungeonName = embedData.setAuthor.title.replace("${dungeon.room.name}", playerDungeon.room.name);
          var newImage = embedData.setAuthor.image.replace("${profile_pic}", client.user.avatarURL);
          const embed = new Discord.RichEmbed()
              .setAuthor("Ghost", newImage)
              .setColor(playerDungeon.room.color)
              .setTitle(embedData.setTitle)
              .setDescription("...")
  
          await newMessage.edit({embed});
      }
  
      // Open chest
      lootChest(playerDungeon, newCommand, author, newMessage, embedData);
  }
  
  // Lockpicks door (lockpickChest IS VERY SIMILAR)
  async function lockpickDoor(playerDungeon, newCommand, author, newMessage, embedData) {
      var random = calcRandom.gamble(50);
      var channel = client.channels.get(playerDungeon.room.channel);
      var successMessage = `:lock_with_ink_pen: **You have successfully lockpicked the door!** The party has saved a key.`;
      var failedMessage  = `:lock_with_ink_pen: **The unlock has failed.** Better luck next time!`;
      if (random) {
          if (!embedData) {
              if (!newMessage)
                  channel.send(successMessage);
          } else {
              var dungeonName = embedData.setAuthor.title.replace("${dungeon.room.name}", playerDungeon.room.name);
              var newImage = embedData.setAuthor.image.replace("${profile_pic}", client.user.avatarURL);
              const embed = new Discord.RichEmbed()
                  .setAuthor("Ghost", newImage)
                  .setColor(playerDungeon.room.color)
                  .setTitle(embedData.setTitle)
                  .setDescription("...")
                  
  
              await newMessage.edit({embed});
              embed.description = successMessage;
              newMessage.edit({embed});
          }
          await sleep(2500);
          var dungeonName = embedData.setAuthor.title.replace("${dungeon.room.name}", playerDungeon.room.name);
          var newImage = embedData.setAuthor.image.replace("${profile_pic}", client.user.avatarURL);
          const embed = new Discord.RichEmbed()
              .setAuthor("Ghost", newImage)
              .setColor(playerDungeon.room.color)
              .setTitle(embedData.setTitle)
              .setDescription("...")
              
  
          await newMessage.edit({embed});
          actualOpenDoor(playerDungeon, newCommand.unlock, author, newMessage, embedData);
      } else {
          if (!embedData)
              channel.send(failedMessage);
          else {
              var dungeonName = embedData.setAuthor.title.replace("${dungeon.room.name}", playerDungeon.room.name);
              var newImage = embedData.setAuthor.image.replace("${profile_pic}", client.user.avatarURL);
              const embed = new Discord.RichEmbed()
                  .setAuthor("Ghost", newImage)
                  .setColor(playerDungeon.room.color)
                  .setTitle(embedData.setTitle)
                  .setDescription("...")
                  
  
              await newMessage.edit({embed});
              embed.description = failedMessage;
              newMessage.edit({embed});
          }
          await sleep(2500);
  
          playerDungeon.dialogObj = newCommand.lockpick;
          move(newCommand.lockpick.moveToFail, playerDungeon, true, author);
          playerDungeon.dialogObj = undefined;
      }
  }
  
  // Opens the door (TODO: ADD NO EMOTE COMPATIBILITY,    AND REMOVE KEY)
  async function openDoor(playerDungeon, newCommand, author, returnElement, newMessage, embedData) {
      var hasAllItems = true;
      const noneString = "**> [None]**\n";
      var itemsHasString = noneString;
      var itemsNotHaveString = noneString;
      console.log("[Open Door] JSON Data: " + JSON.stringify(newCommand, null, 4) + "\nreturnElement: " + JSON.stringify(returnElement, null, 4));
      // Loops over all items
      for (let i = 0; i < newCommand.required.length; i++) {
          // Element = item object
          const objData = newCommand.required[i];     // Contains data from rooms.json
          const item = rooms.items[objData.item];
      
          // Checks for key in inventory
          let itemRemove = playerDungeon.items.findIndex( i => i.name === item.name );
          console.log("[Remove Item Door] " + "itemRemove = " + itemRemove);
  
          // If item exist
          var failed;
          if (itemRemove !== -1) {
              if (removeItem(playerDungeon, item, objData.amount)) {
                  if (itemsHasString === noneString) itemsHasString = ""; // Erases None string
                  itemsHasString += `**> ${item.emote} ${item.name} [${objData.amount}x]**\n`;
              }
              else {
                  console.log(`[Remove Item Door] Remove fail at removeItem().`) 
                  failed = true;
              }
          } else {
              console.log(`[Remove Item Door] Couldn't find item name.`) 
              failed = true;
          }
  
          if (failed) {
              hasAllItems = false;
              if (itemsNotHaveString === noneString) itemsNotHaveString = ""; // Erases None string
              itemsNotHaveString += `**> ${item.emote} ${item.name} [${objData.amount}x]**\n`;
          }
      }
  
      // Variables        
      var successMessage = `${author} used:\n${itemsHasString} to open the door.`;
      var failedMessage  = `The party does have:\n${itemsHasString}However, the party does not have:\n${itemsNotHaveString}\n...in order to :closed_lock_with_key: unlock the door.`;
      console.log("[Open Door] " + successMessage + " | " + failedMessage);
      
      // If no items needed
      if (hasAllItems) {
          var dungeonName = embedData.setAuthor.title.replace("${dungeon.room.name}", playerDungeon.room.name);
          var newImage = embedData.setAuthor.image.replace("${profile_pic}", client.user.avatarURL);
          const embed = new Discord.RichEmbed()
              .setAuthor("Ghost", newImage)
              .setColor(playerDungeon.room.color)
              .setTitle(embedData.setTitle + ": Unlocking")
              .setDescription("...")
          
          await newMessage.edit({embed});
          embed.description = successMessage;
          newMessage.edit({embed});
          await sleep(2500);
      } else {
          // Send fail message
          if (!embedData)
              sendMessage(author.id, playerDungeon.room.channel, failedMessage);
          else {
              var dungeonName = embedData.setAuthor.title.replace("${dungeon.room.name}", playerDungeon.room.name);
              var newImage = embedData.setAuthor.image.replace("${profile_pic}", client.user.avatarURL);
              const embed = new Discord.RichEmbed()
                  .setAuthor("Ghost", newImage)
                  .setColor(playerDungeon.room.color)
                  .setTitle(embedData.setTitle)
                  .setDescription("...")
          
              await newMessage.edit({embed});
              embed.description = failedMessage;
              newMessage.edit({embed});
              await sleep(2500);
          }
  
          return typingDialog(playerDungeon, playerDungeon.dialogObj, author);
      }
  
      actualOpenDoor(playerDungeon, newCommand, author, newMessage, embedData);
  }
  
  function actualOpenDoor(playerDungeon, newCommand, author, newMessage, embedData) {
      if (newMessage) {
          var dungeonName = embedData.setAuthor.title.replace("${dungeon.room.name}", playerDungeon.room.name);
          var newImage = embedData.setAuthor.image.replace("${profile_pic}", client.user.avatarURL);
          const embed = new Discord.RichEmbed()
              .setAuthor("Ghost", newImage)
              .setColor(playerDungeon.room.color)
              .setTitle(embedData.setTitle + ": Unlocking")
              .setDescription(`:unlock: The door has been opened!`)
      
          newMessage.edit({embed}); 
      }
  
      playerDungeon.dialogObj = newCommand;
      playerDungeon.reroutedRooms.push([newCommand.moveFrom, newCommand.moveTo]);
      move(newCommand.moveTo, playerDungeon, true, author);
      playerDungeon.dialogObj = undefined;
  }
  
  // Loots the chest (TODO: REMOVE GAMBLWON ON SENDSERVERDATA)
  async function lootChest(playerDungeon, newCommand, author, newMessage, embedData) {
      var channel = client.channels.get(playerDungeon.room.channel);
      var content = `:lock: **Opening chest.`;
      var openMessage = `:unlock: **You obtained :wind_blowing_face: Air!** This item will be distributed through the party at random.`;
      // Generates a random item drop group
      //console.log(JSON.stringify(newCommand, null, 4));
      var randomDropGroup = calcRandom.random(0, newCommand.objects.length - 1);
      var randomItemGroup = calcRandom.random(0, newCommand.objects[randomDropGroup].length - 1)
  
      /*
      console.log("newCommand.objects: " + JSON.stringify(newCommand.objects, null, 4));
      console.log("newCommand.objects.length: " + newCommand.objects.length);
      console.log("newCommand.objects[randomDropGroup]: " + JSON.stringify(newCommand.objects[randomDropGroup], null, 4));=
      console.log("newCommand.objects[0][0]: " + JSON.stringify(newCommand.objects[0][0], null, 4));
      console.log("IF THIS DOESNT WORK, AHHH: " + newCommand.objects[0][0].name)
      */
  
      console.log("[RNG] " + randomDropGroup + " " + randomItemGroup + " | " + newCommand.objects[randomDropGroup].length);
      var randomItem = newCommand.objects[randomDropGroup][randomItemGroup];
  
      switch (randomItem.name) {
          case "crystals":
              if (randomItem["amount"]) {
                  var amount = calcRandom.random(randomItem["amount"][0], randomItem["amount"][1]);
                  openMessage = `:unlock: **You obtained <:crystals:460974340247257089> ${amount * playerDungeon.players.length} Crystals!** This item will be distributed through the party evenly.`;
                  addCrystalsToPartyMember(playerDungeon, amount);
              }
              break;
      
          case "materials":
              openMessage = `:unlock: **You obtained materials that I have implemented!**`;
              break;
  
          default:
              console.warn("Message not in switch statement: " + randomItem.name)
              break;
      }
  
      if (!embedData || !newMessage) {
          var message = await channel.send(content + "**").catch(console.error);
      } else {
          var dungeonName = embedData.setAuthor.title.replace("${dungeon.room.name}", playerDungeon.room.name);
          var newImage = embedData.setAuthor.image.replace("${profile_pic}", client.user.avatarURL);
          const embed = new Discord.RichEmbed()
              .setAuthor("Ghost", newImage)
              .setColor(playerDungeon.room.color)
              .setTitle(embedData.setTitle + ": Unlocking")
              .setDescription(content + "**")
  
          newMessage.edit({embed});
      }
  
      for (let index = 1; index < 3; index++) {
          var stringOfDots = "";
          
          for (let i = 0; i < index; i++) {
              console.log("[Loot Chest] | Number of dots: " + index);
              stringOfDots += ".";            
          }
          
          await sleep(750);  
          console.log("[Loot Chest] " + content + stringOfDots + " | " + index);
          if (!embedData)
              await message.edit(content + stringOfDots + "**");
          else {
              var dungeonName = embedData.setAuthor.title.replace("${dungeon.room.name}", playerDungeon.room.name);
              var newImage = embedData.setAuthor.image.replace("${profile_pic}", client.user.avatarURL);
              const embed = new Discord.RichEmbed()
                  .setAuthor("Ghost", newImage)
                  .setColor(playerDungeon.room.color)
                  .setTitle(embedData.setTitle + ": Unlocking")
                  .setDescription(content + stringOfDots + "**")
  
              await newMessage.edit({embed})
          }
      }
  
      await sleep(750);
      if (!embedData)
          message.edit(openMessage);
      else {
          var dungeonName = embedData.setAuthor.title.replace("${dungeon.room.name}", playerDungeon.room.name);
          var newImage = embedData.setAuthor.image.replace("${profile_pic}", client.user.avatarURL);
          const embed = new Discord.RichEmbed()
              .setAuthor("Ghost", newImage)
              .setColor(playerDungeon.room.color)
              .setTitle(embedData.setTitle + ": Unlocked")
              .setDescription(openMessage)
  
          newMessage.edit({embed});
      }
      await sleep(2500);
      
      // Moves user to empty chest room
      playerDungeon.dialogObj = newCommand;
      //console.log(JSON.stringify(newCommand, null, 4));
      playerDungeon.reroutedRooms.push([newCommand.moveFrom, newCommand.moveTo]);
      move(newCommand.moveTo, playerDungeon, true, author);
      playerDungeon.dialogObj = undefined;
  }
  
  // Leaves the chest alone
  async function leaveObject(playerDungeon, newCommand, author) {
      playerDungeon.dialogObj = newCommand;
      move(newCommand.moveTo, playerDungeon, true, author);
      playerDungeon.dialogObj = undefined;
  }
  
  // Passes all the user IDs
  function passDungeonUserIDs(playerDungeon) {
      var userIDArray = [];
      playerDungeon.players.forEach(element => {
          userIDArray.push(element.userID);
      });
      console.log(userIDArray);
      return userIDArray;
  }
  
  // https://stackoverflow.com/questions/3733227/javascript-seconds-to-minutes-and-seconds
  function fmtMSS(s){   // accepts seconds as Number or String. Returns m:ss
      return( s -         // take value s and subtract (will try to convert String to Number)
              ( s %= 60 ) // the new value of s, now holding the remainder of s divided by 60 
                          // (will also try to convert String to Number)
            ) / 60 + (    // and divide the resulting Number by 60 
                          // (can never result in a fractional value = no need for rounding)
                          // to which we concatenate a String (converts the Number to String)
                          // who's reference is chosen by the conditional operator:
              9 < s       // if    seconds is larger than 9
              ? ':'       // then  we don't need to prepend a zero
              : ':0'      // else  we do need to prepend a zero
            ) + s ;       // and we add Number s to the string (converting it to String as well)
  }
  
  // Creates new dungeon, inits BotWaitingPlayers()
  async function NewDungeonSequence(channel) {
      console.log('Attempting to start a raid!');
  
      // If bot summoning for a dungeon is free
      var newRoomID = rooms.rooms[calcRandom.random(0, rooms.rooms.length - 1)];  // calcRandom.random is inclusive for low + high
      console.log(newRoomID);
      var newRoom = rooms[newRoomID] 
  
      var newDungeon;
      dungeonCollection.forEach(element => {
          if (element.room.name === newRoom.name) {
              return console.log("Raid already happening.");
          }
      });
  
      // Decides on new dungeon
      newDungeon = new DungeonRaidInstance(newRoom);
      newDungeon.location = newDungeon.room.entrance;
      dungeonCollection.push(newDungeon);
  
      // Minutes
      var sleepTime = 2 * 60;
      BotWaitingPlayers(channel, newDungeon, sleepTime);
  }
  
  // Shows the join message for the specified dungeon
  async function BotWaitingPlayers(channel, dungeon, timer) {
      dungeon.startTimer = timer;
  
      var decrementTime = 1; // Seconds
      var newMessage;
      var textTimer = `⏰ ${fmtMSS(dungeon.startTimer)} min. left before leaving`;
      var crystalCost = 50;
      const botDialog = `*Psst.* Hey travelers, **${dungeon.room.name}** doesn't have any Ravagers outside guarding it. ` +
                          `I could lockpick it, but it's gunna cost you **<:crystals:460974340247257089> 50**, for each of you.`;
      const description = `In order to join the dungeon, you have to pay **<:crystals:460974340247257089> ${crystalCost}**. \nReact with <:crystals:460974340247257089> to enter, and the crystals will be subtracted from your account.`;
  
      client.user.setStatus('online');
      client.user.setActivity(`${textTimer} ${dungeon.room.name}.`)
      dungeon.state = DungeonState.WAITING_FOR_USERS;
  
      const embed = new Discord.RichEmbed()
          .setAuthor("Ghost", client.user.avatarURL)
          .setColor(dungeon.room.color)
          .setDescription(description)
          .setFooter(textTimer  + ".")               
      newMessage = await client.channels.get(channel).send(botDialog, {embed});  
  
      await newMessage.react('460974340247257089');
      
      // Collector for emotes
      const collector = newMessage.createReactionCollector(
          (reaction, user) => reaction.emoji.name === 'crystals' && user.id !== client.user.id , { time: timer * 1000 });
  
      // Removes crystals
      collector.on("collect", async reaction => {
          var user = reaction.users.last();
          //console.log(reaction.emoji.name)
          //console.log("Added new user: " + user.username)
  
          var attacker = String(dataRequest.loadServerData("userStats", user.id));
          var attackerWallet = parseFloat(attacker.split(",")[6]);
          
          if (attackerWallet >= crystalCost) {
              dataRequest.sendServerData("buyDrink", crystalCost, user.id);
              joinMessage(user, channel, dungeon.room);
          } else {
              newMessage.channel.send(`:x: ${user} You don't have enough crystals to pay me. Come back later, when you do. I ain't doing this for free.`);
          }
      });
  
      // Clears reactions
      collector.once("end", async collecter => {
          console.log("[Reaction Options] Ended collector, clearing emotes and sending timing out message if needed.");
          newMessage.clearReactions();
      });
      
      // Creates description and displays it (join, no-join)
      function timerFunction() {
          setTimeout(async () => {
              if (dungeon.state !== DungeonState.WAITING_FOR_USERS) return;
              const timeInveral = 30;
              var quarterMinuteRemainder = dungeon.startTimer % timeInveral;
              console.log("[Timer Function] timer: " + dungeon.startTimer + " => " + (dungeon.startTimer - decrementTime) + " | " + quarterMinuteRemainder)
   
              // If it's been 30 seconds (divisible without any remainder, & not exactly starting time)
              if (quarterMinuteRemainder == 0 && dungeon.startTimer !== timer) {
                  var descriptionString = "";
                  var finished = `The travelers entered the dungeon. You can no longer join.`
                  var textTimer = `⏰ ${fmtMSS(dungeon.startTimer)} min. left before leaving`;
                  var footerText = textTimer + ".";
      
                  if (dungeon.startTimer > 0) {
                      descriptionString = description;
                      client.user.setActivity(`${textTimer} ${dungeon.room.name}.`);
                  } else {
                      descriptionString = finished;
                      footerText = "⏰ You can no longer join."
                  }
      
                  const embed = new Discord.RichEmbed()
                      .setAuthor("Ghost", client.user.avatarURL)
                      .setColor(dungeon.room.color)
                      .setDescription(descriptionString)
                      .setFooter(`${footerText}`)
      
                  newMessage.edit(botDialog, embed);
              } else if (dungeon.startTimer === timer) {
                  console.log("[Debug] " + dungeon.startTimer + " | " + timer);
              }
              dungeon.startTimer -= decrementTime;
              if (dungeon.startTimer > -1) timerFunction(); else {
                  if (dungeon.state === DungeonState.WAITING_FOR_USERS && dungeon.players.length > 0) {
                      console.log(dungeon.players.length);
                      BotActive(dungeon);
                  } else {
                      BotInactive(dungeon);
                  }
              }
          }, decrementTime * 1000);
      }
   
      timerFunction();
  }
  
  // Sends closing dungeon messages
  async function BotActive(dungeon) {
      //sendMessage(channel, `${dungeon.room.name} has closed, trapping the travelers inside the dungeon. \
      //\n\n***YOU NO LONGER CAN !JOIN***`);
      
      if (dungeon.players.length > 0) {
          await sendEmbedLocation(dungeon);
  
          client.channels.get(dungeon.room.channel).send(`**<#${dungeon.room.channel}>** got closed behind us. I guess we should start venturing deeper.` +
              `\nUse **!help ${client.user}** to get all the commands you are able to use here, if you need anythin'.`);
  
          client.user.setStatus('away');
          
          dungeon.state = DungeonState.INSIDE_DUNGEON;
      } else {
          BotInactive(dungeon);
      }
      
      // Starts timer
      var decrementTime = 1000;              // 1 every second
  
      dungeon.timer = 10 * 60 * 1000;
      sendTimerInChat(dungeon);
  
      // Decrements dungeon timer and displays it in chat.
      function displayTimer() {
          setTimeout(async () => {
              // Makes sure dungeon is still there
              if (dungeon) {
                  if (dungeon.state === DungeonState.INSIDE_DUNGEON) {
                      dungeon.timer -= decrementTime;
  
                      // Timers
                      if (dungeon.timer > 0) {
                          // Events
                          var oneMinuteRemainder = dungeon.timer % (1 * 60 * 1000);
      
                          if (oneMinuteRemainder == 0) {
                              sendTimerInChat(dungeon);
                              client.user.setActivity(`⏰ ${fmtMSS(dungeon.timer / 1000)} min. left | ${dungeon.room.name}`).then(presence => console.log(`Activity set to ${presence.game ? presence.game.name : 'none'}`))
                                  .catch(console.error);
                          }
      
                          // Recursion
                          displayTimer();
                      // Leaves dungeon
                      } else {
                          await sendTimerInChat(dungeon);
                          await client.channels.get(dungeon.room.channel).send("An overwhelming amount of Ravagers came back in," + 
                              " and the party escaped, however not completing the dungeon.\n\n***THE DUNGEON HAS FAILED TO BE COMPLETED IN TIME.***");
                          await client.channels.get(process.env.TEST_CHANNEL_ID).send("Alright travelers, we didn't make it in time, but we managed to get out alive, I guess. Better luck next time.");
                          BotInactive(dungeon);
                      }
                  }
              }
          }, decrementTime);
      }
  
      displayTimer();
  }
  
  // Inactive
  async function BotInactive(dungeon) {
      // Removes roles
      const guild = client.guilds.get(process.env.SANCTUM_ID);
      if (dungeon.players.length > 0) {
          for (let i = 0; i < dungeon.players.length; i++) {
              const element = dungeon.players[i];
  
              // Deletes role
              let role = client.guilds.get(process.env.SANCTUM_ID).roles.find(role => role.name === `${dungeon.room.name}: Raid Party`);
              await guild.members.get(element.userID).removeRole(role);
          }
      }
  
      // Removes dungeon by index
      let removeDungeon = dungeonCollection.findIndex( d => d.room.name === dungeon.room.name );
      dungeonCollection.splice(removeDungeon);
  
      if (dungeonCollection.length < 1)
          client.user.setStatus('invisible');
  }
  
  // Sends timer message
  function sendTimerInChat(newDungeon) {
      console.log("[Timer] " + newDungeon.timer + " | " + newDungeon.startTimer);
      const guild = client.guilds.get(process.env.SANCTUM_ID);
      var role = guild.roles.find(role => role.name === `${newDungeon.room.name}: Raid Party`);
  
      if (newDungeon.state !== DungeonState.WAITING_FOR_USERS)
          return client.channels.get(newDungeon.room.channel).send(`:alarm_clock: ${role} has ${fmtMSS(newDungeon.timer / 1000)} min. left to complete the dungeon.`);
      else
          return client.channels.get(newDungeon.room.channel).send(`:alarm_clock: ${role} has ${fmtMSS(newDungeon.startTimer)} min. left before the dungeon starts.`);
  }
  
  // Ends dialog by pausing then sending your location again.
  async function endDialog(newCommand, playerDungeon, overrideLastCommand) {
      if (newCommand.lastCommand || overrideLastCommand) {
          console.log("[End Dialog] Doing End Dialog Sequence.")
          playerDungeon.dialogObj = undefined;
          await sleep(1000);
  
          if (newCommand.lastCommand) sendEmbedLocation(playerDungeon);
      }
  }
  
  // Does typing dialog for the NPC
  async function typingDialog(playerDungeon, newCommand, author, newMessage) {
      if (playerDungeon.isTyping) { console.log("isTyping true, cancelling"); return false };
      console.log("[Typing Dialog]\n" + JSON.stringify(newCommand, null, 4));
      playerDungeon.isTyping = true;
      var firstTime;
      var range = 250;
      var channel = client.channels.get(playerDungeon.room.channel);
  
      // Types out all the dialog
      if (newCommand.descriptions) {
          for (let index = 0; index < newCommand.descriptions.length; index++) {
              var temp = "";
              const element = newCommand.descriptions[index];
              if (element.text !== undefined) {
                  var title = ""
                  if (!firstTime) {
                      title = `**${playerDungeon.dialogObj.name}**\n`; 
                      firstTime = true;
                  }
                  temp = element.text.replace("${leader}", author);
                  var waitTimerRange = calcRandom.random(element.waitBegin - range, element.waitBegin + range);
  
                  // Debug purposes only
                  //waitTimerRange = 10;
  
                  sendTypingMessage(channel, title + temp, waitTimerRange);
                  await sleep(waitTimerRange + element.waitEnd);
                  
                  endDialog(newCommand, playerDungeon);
              }
          }
      }
      
      playerDungeon.isTyping = false;
      reactionOptions(playerDungeon, newCommand, author, newMessage);
  }
  
  // Does reactions on a message, then uses them as options
  async function reactionOptions(playerDungeon, newCommand, author, newMessage) {
      var channel = client.channels.get(playerDungeon.room.channel);
      var range = 250;
      var returnElement = false;
  
      // Reactions (if they exist via text or have an options JSON field)
      if (newCommand.optionsDescription || newCommand.options) {
          var waitTimerRange;
          var isEmbed = false;
          waitTimerRange = 10;
  
          // Embed and text varients
          if (!newCommand.embed) {
              waitTimerRange = calcRandom.random(newCommand.optionsDescription.waitBegin - range, newCommand.optionsDescription.waitBegin + range);
              newMessage = await sendTypingMessage(channel, newCommand.optionsDescription.text, waitTimerRange)
          }
          else {
              await sleep(newCommand.embed.waitBegin);
  
              // Specific formattings
              if (newCommand.embed.formatting === "author_title_image_desc") {
                  var newImage = client.user.avatarURL;
                  const embed = new Discord.RichEmbed()
                      .setAuthor("Ghost", newImage)
                      .setColor(playerDungeon.room.color)
                      .setTitle(newCommand.embed.setTitle)
                      .setDescription(newCommand.embed.setDescription)
  
                  newMessage = await channel.send({embed});
                  channel.stopTyping(true);
              } else {
                  console.error("[Reaction Options] Embed was tried, but no vaid formatting!");
              }
  
              await sleep(newCommand.embed.waitEnd)
          }
  
          // Reacts
          for (let i = 0; i < newCommand.options.length; i++) {
              const element = newCommand.options[i];
              console.log("[Reaction Options] Emote: " + element.emote + "  | newMessage: " + newMessage);
              await newMessage.react(element.emote);
          }
  
          // Collects emotes and reacts upon the reaction (120 seconds)
          const collector = newMessage.createReactionCollector(
              (reaction, user) => (getDungeonPlayer(user.id).isLeader || getDungeonPlayer(user.id).isCommander) && 
              (newCommand.options.some(option => option.emote === reaction.emoji.name)), { time: 120 * 1000 });
          var endedOnReact = false;
  
          // Collect
          collector.once("collect", async reaction => {
              // Solely for players
              if (playerDungeon.dialogObj) {
                  const chosen = reaction.emoji.name;
                  newCommand.options.forEach(element => {
                      if (element.emote === chosen) {
                          returnElement = element;
  
                          // Grabs the object if it exists
                          if (playerDungeon.dialogObj[element.command] && playerDungeon.dialogObj[element.command].command) {
                              typingDialog(playerDungeon, playerDungeon.dialogObj[element.command], author, newMessage);
                              console.log("Using grab object if exists");
                              hasAlreadyTriggered = true;
                          }
                      }
                  });
              }
              customCommandEmoteProcessor(playerDungeon, newCommand, author, returnElement, newMessage, reaction);
              endedOnReact = true;
              collector.stop();
  
              /*
              console.log(newMessage.reactions);
              newMessage.reactions.forEach(element => {
                  if (element.users.id !== client.user.id) newMessage.reactions.remove(element);
              });
              */
              
              // Debug
              //console.log("[Reaction Options] Return emote from reactionOptions: " + returnElement.emote);
              //console.log("[Reaction Options] newCommand @ reactions function: " + JSON.stringify(newCommand));
              //console.log("[Reaction Options] returnElement: " + JSON.stringify(returnElement));
          });
  
          // End trigger
          collector.once("end", collecter => {
              console.log("[Reaction Options] Ended collector, clearing emotes and sending timing out message if needed.");
              newMessage.edit(newMessage.content);
              newMessage.clearReactions();
              if (!endedOnReact) {
                  sendMessage(playerDungeon.room.channel, ":x: **Timed Out** The emote reaction request timed out after 2 minutes.");
                  endDialog(newCommand, playerDungeon, true);
              }
          });
          
      }
  }
  
  // Sends messages in periods of time
  async function sendTypingMessage(channel, message, waitTimer) {
      channel.startTyping()
      await sleep(waitTimer);
      channel.stopTyping(true);
      
      return channel.send(message);
  }
  
  // Just here for placeholder, do not use
  async function sendEmbedMessage(channel, embedData, waitTimer) {
      channel.startTyping();
      await sleep(waitTimer);
      channel.stopTyping(true);
  
      const embed = new Discord.RichEmbed();
  
      return channel.send(embed);
  }
  
  // Send message handler
  function sendMessage(userID, channelID, message) {
      // Handle optional first argument (so much for default arugments in node)
      if (message === undefined) {
          message = channelID;
          channelID = userID;
          userID = null;
      }
  
      // Utility trick (@userID with an optional argument)
      if (userID != null) {
          message = "<@" + userID + "> " + message;
      }
      
      // Sends message (needs client var, therefore I think external script won't work)
      client.channels.get(channelID).send(message);
  }
  
  // You may use cron normally
  cron.schedule('30 */3 * * *', function() {
      NewDungeonSequence(process.env.TEST_CHANNEL_ID);
  });
  
  // Join Message
  function joinMessage(user, channel, room) {
      var newDungeon;
      var firstUserGiveLeader = false;
      dungeonCollection.forEach(element => {
          if (element.room === room) newDungeon = element;
      });
  
      // If dungeon doesn't exist
      if (!newDungeon) {
          if (room.undefined != undefined) {
              if (room.undefined == "") 
                  return sendMessage(user.id, channel.id, `:x: You need to specify what dungeon you wish to join.`);
              return sendMessage(user.id, channel.id, `:x: There is no dungeon named ${room.undefined}.`);
          }
          return sendMessage(user.id, channel.id, ":x: This dungeon does not exist.");
      } else {
          if (newDungeon.players.length == 0) {
              firstUserGiveLeader = true;
          }
      }
  
      if (newDungeon.state !== DungeonState.WAITING_FOR_USERS) {
          return sendMessage(user.id, channel.id, ":x: This dungeon's closed currently.");
      }
  
      // Find if user is in the Raid Party
      var userExists = newDungeon.players.find( player => player.userID === user.id );
  
      // If user doesn't exist in the list, join message
      if (!userExists) {
          let member = client.guilds.get(process.env.SANCTUM_ID).members.get(user.id);
  
          // Adds role to use
          let role = client.guilds.get(process.env.SANCTUM_ID).roles.find(role => role.name === `${newDungeon.room.name}: Raid Party`);
          member.addRole(role);
  
          // Creates new player for array
          var playerRole = getFactionID(member);
          var className = getCombatClassName(member);
          var newPlayer = new PlayerClass(user.id, playerRole, className, ":new:", firstUserGiveLeader);
          
          newDungeon.players.push(newPlayer);
  
          // Sends messages confirming it
          client.channels.get(newDungeon.room.channel).send(`<@${user.id}> welcome to the ${newDungeon.room.name} dungeon raid! Use **!party** to check the members.`);
          //message.channel.send(`<@${user.id}> you have **!join**ed the ${newDungeon.room.name} dungeon raid! Use **!party** to check the members.`);
      // Else send error already joined
      } else {
          //client.channels.get(newDungeon).send(`<@${user.id}> you already **!join**ed the ${newDungeon.room.name} dungeon raid. Use **!party** to check the members.`);
      }
  }
  
  // Leave Message
  async function leaveMessage(message) {
      var dungeonPlayer = getDungeonPlayer(message.author.id);
  
      // Sends messages confirming it
      if (dungeonPlayer.playerDungeon === undefined) {
          var confirmMessage = await message.channel.send(`<@${message.author.id}> you aren't in a dungeon raid in my books, but are you sure you want to force leave all dungeon raids?\n*This will remove all dungeon raid roles.*`);
  
          // Reacts
          await confirmMessage.react('✅');
          await confirmMessage.react('❌');
  
          const collector = confirmMessage.createReactionCollector(
              (reaction, user) => (reaction.emoji.name === '✅' || reaction.emoji.name === '❌') && user.id === message.author.id , { time: 15 * 1000 });
          var react = "";
  
          // Collect
          collector.once("collect", async reaction => {
              endedOnReact = true;
              react = reaction.emoji.name;
              collector.stop();
          });
  
          // End trigger
          collector.once("end", async collecter => {
              console.log("[Reaction Options] Ended collector, clearing emotes and sending timing out message if needed.");
              confirmMessage.edit(confirmMessage.content);
              confirmMessage.clearReactions();
              if (!endedOnReact) return message.channel.send(":x: **Timed Out**: The emote reaction request timed out after 15 seconds.");
              if (react === '❌') return confirmMessage.edit(`${message.author} Cancelled removing dungeon raid roles.`);
  
              // Force deletes all dungeons that exist
              for (let i = 0; i < rooms.rooms.length; i++) {
                  const element = rooms.rooms[i];
                  const name = rooms[element].name;
                  let role = client.guilds.get(process.env.SANCTUM_ID).roles.find(role => role.name === `${name}: Raid Party`);
                  await message.member.removeRole(role);
              } 
  
              await confirmMessage.edit(`${message.author} Removed all dungeon raid roles.`)
          });
      } else {
          var confirmMessage = await message.channel.send(`<@${message.author.id}> are you sure you want to leave the ${dungeonPlayer.playerDungeon.room.name} dungeon raid?`);
  
          // Reacts
          await confirmMessage.react('✅');
          await confirmMessage.react('❌');
          
          // Collects emotes and reacts upon the reaction (15 seconds)
          const collector = confirmMessage.createReactionCollector(
              (reaction, user) => (reaction.emoji.name === '✅' || reaction.emoji.name === '❌') && user.id === message.author.id , { time: 15 * 1000 });
          var endedOnReact = false;
          var react = "";
  
          // Collect
          collector.once("collect", async reaction => {
              // Solely for players
              endedOnReact = true;
              react = reaction.emoji.name;
              collector.stop();
          });
  
          // End trigger
          collector.once("end", collecter => {
              console.log("[Reaction Options] Ended collector, clearing emotes and sending timing out message if needed.");
              confirmMessage.edit(confirmMessage.content);
              confirmMessage.clearReactions();
              if (!endedOnReact) return sendMessage(message.channel, ":x: **Timed Out**: The emote reaction request timed out after 15 seconds.");
              if (react === '❌') return confirmMessage.edit(`${message.author} Cancelled leaving the ${dungeonPlayer.playerDungeon.room.name}.`);
              
              // Sends confirmation message
              message.channel.send(`${message.author} has left the dungeon.`)
  
              // Deletes role
              let role = client.guilds.get(process.env.SANCTUM_ID).roles.find(role => role.name === `${dungeonPlayer.playerDungeon.room.name}: Raid Party`);
              message.member.removeRole(role);
  
              // Removes user from dungeon
              dungeonPlayer.playerDungeon.players = dungeonPlayer.playerDungeon.players.filter(obj => obj.userID !== message.author.id)
  
              // Chooses random leader if leader left
              console.log("Is there a leader: " + dungeonPlayer.playerDungeon.players.find(l => l.leader) === true);
              if (dungeonPlayer.playerDungeon.players.find(l => l.leader) === undefined) {
                  for (let i = 0; i < dungeonPlayer.playerDungeon.players.length; i++) {
                      const element = dungeonPlayer.playerDungeon.players[i];
                      console.log(element);
                      if (element.commander) {
                          element.commander = false;
                          element.leader = true;
                          message.channel.send(`<@${element.userID}> has been promoted to leader!`);
                          break;
                      }
                  }
              
                  // If there is no commander, promote first person on list
                  for (let i = 0; i < dungeonPlayer.playerDungeon.players.length; i++) {
                      const element = dungeonPlayer.playerDungeon.players[i];
                      element.commander = false;
                      element.leader = true;
                      message.channel.send(`<@${element.userID}> has been promoted to leader!`);
                      break;
                  } 
              }
  
              // Destroy dungeon if last player
              if (dungeonPlayer.playerDungeon.players.length === 0) {
                  BotInactive(dungeonPlayer.playerDungeon);
              }
          });
      }
  }
  
  // Checks if direction can be done (skips anything other than Directions.XXXX)
  function checkValidMovement(direction, dungeon, silent) {
      var directionFailure;
      switch (direction) {
          case Directions.NORTH:
              if (dungeon.location.connections.north === undefined) {
                  directionFailure = "**north**";
              }
          break;
          case Directions.EAST:
              if (dungeon.location.connections.east === undefined) {
                  directionFailure = "**east**";
              }
          break;
          case Directions.SOUTH:
              if (dungeon.location.connections.south === undefined) {
                  directionFailure = "**south**";
              }
          break;
          case Directions.WEST:
              if (dungeon.location.connections.west === undefined) {
                  directionFailure = "**west**";
              }
          break;
      }
  
      if (directionFailure) {
          if (!silent) {
              var deniedMessage = `:x: ${user} You can't go ${directionFailure}, please choose a valid path.`;
              client.channels.get(dungeon.room.channel).send(deniedMessage);
          }
          return false;
      }
      return true;
  }
  
  // Moves in the dungeon
  // TODO ITEM
  async function move(direction, dungeon, isPartyLeader, user, moveToOverride) {
      if (dungeon === undefined) return console.log("Dungeon doesn't exist!");
      
      var channel = client.channels.get(dungeon.room.channel);
      var newRoom;
  
      if (!isPartyLeader) return channel.send(`:x: ${user} You need to be a Party Leader or be **!promoted** to Commander in order to move.`);
      if (!checkValidMovement(direction, dungeon)) return;
  
      // Says something if command can be done
      switch (direction) {
          case Directions.NORTH:
              if (dungeon.location.connections.north !== undefined) {
                  newRoom = dungeon.location.connections.north;
                  //channel.send(":white_check_mark: **Command accepted.** Following the party **north**.");
              }
          break;
          case Directions.EAST:
              if (dungeon.location.connections.east !== undefined) {
                  newRoom = dungeon.location.connections.east;
                  //channel.send(":white_check_mark: **Command accepted.** Following the party **east**.");
              }
          break;
          case Directions.SOUTH:
              if (dungeon.location.connections.south !== undefined) {
                  newRoom = dungeon.location.connections.south;
                  //channel.send(":white_check_mark: **Command accepted.** Following the party **south**.");
              }
          break;
          case Directions.WEST:
              if (dungeon.location.connections.west !== undefined) {
                  newRoom = dungeon.location.connections.west;
                  //channel.send(":white_check_mark: **Command accepted.** Following the party **west**.");
              }
          break;
          default:
              // If uses a custom moveTo statement
              if (moveToOverride) {
                  newRoom = moveToOverride;
              // Else if no override
              } else if (dungeon.dialogObj.moveTo) {
                  newRoom = dungeon.dialogObj.moveTo;
              }
          break;
      }
  
      // Reroutes rooms
      dungeon.reroutedRooms.forEach(element => {
          console.log("[Move Reroute Rooms] " + element[0] + " " + element[1] + " " + newRoom + " " + (element[0] === newRoom));
          if (element[0] === newRoom) newRoom = element[1];
      });
  
      console.log("[New Room]: " + newRoom + " | [Old Room]: " + dungeon.location.name);
      switch (newRoom) {
          case "__END":
              await client.channels.get(process.env.TEST_CHANNEL_ID).send(`The **${dungeon.room.name}** dungeon has been completed!`);
              await channel.send(`Congratulations! The party completed the **${dungeon.room.name}** dungeon with **${fmtMSS(dungeon.timer / 1000)} min. left**!`);
              BotInactive(dungeon);
              break;
  
          default:
              dungeon.location = dungeon.room[newRoom];
              sendEmbedLocation(dungeon);
              break;
      }
      console.log("[New Location]: " + dungeon.location.name);
  }
  
  // Movement option text
  function movementOptions(dungeon) {
      var temp = "";
  
      if (dungeon.location.northtext  !== "" && dungeon.location.northtext)
          temp += `:arrow_up: ${dungeon.location.northtext}\n`;
      if (dungeon.location.easttext   !== "" && dungeon.location.easttext)
          temp += `:arrow_right: ${dungeon.location.easttext}\n`;
      if (dungeon.location.southtext  !== "" && dungeon.location.southtext)
          temp += `:arrow_down: ${dungeon.location.southtext}\n`;
      if (dungeon.location.westtext   !== "" && dungeon.location.westtext)
          temp += `:arrow_left: ${dungeon.location.westtext}\n`;
      return temp;
  }
  
  async function sendInventory(playerDungeon, pageNum) {
      var items = "";
  
      // Creates an array that seperates 10 items into seperate arrays each
      var groupedArr = createGroupedArray(playerDungeon.items, 5);
  
      // Sets a default page num, or makes it human readable
      if (pageNum === undefined) pageNum = 0; else {
          pageNum--;
          if (pageNum < 0) pageNum = 0;
          if (groupedArr.length < pageNum) pageNum = groupedArr.length;
      }
      
      // Checks if page number is valid
      if (pageNum + 1 > groupedArr.length) {
          // If it's longer than actual length, but isn't just an empty inventory
          if (!groupedArr.length === 0) return;  
      }
  
      // Grabs item in loop, parses it, then adds it to "items" variable
      if (groupedArr[pageNum]) {
          for (let index = 0; index < groupedArr[pageNum].length; index++) {
              // Grabs an item, from a page index
              const element = groupedArr[pageNum][index];
  
              // Makes if there is an emote, it'll add an extra space
              var emoteText = "";
              if (element.emote) emoteText = element.emote + " ";
  
              // Adds it in
              items += `> ${emoteText}**${element.name}** [${element.amount}x] ${element.info}\n`; 
          }  
      }
  
      // No items message to fill field
      if (items === "") items = "There are no items in the party's inventory.";
  
      // To make the message of "Page 1/0" with no items in !pinventory not happen
      var moddedLength = groupedArr.length;
      if (moddedLength < 1) moddedLength = 1;
  
      const embed = new Discord.RichEmbed()
          .setAuthor("Ghost", client.user.avatarURL)
          .setColor(playerDungeon.room.color)
          .addField(`Party Inventory (Page ${pageNum + 1}/${moddedLength})`, items, true)
          //.setDescription(`${items}`)
  
      client.channels.get(playerDungeon.room.channel).send({embed});
  }
  
  // Sends location into chat (non-embed)
  function sendLocation(dungeon, author) {
      var temp = `Current Room: **${dungeon.location.name}**\n\n`;
      temp += location(dungeon);
      temp += "\n\n";
      temp += movementOptions(dungeon);
  
      // Sends message
      var channel = client.channels.get(dungeon.room.channel);
      channel.send(author + " " + temp);
  }
  
  // Sends location into chat (embed)
  async function sendEmbedLocation(dungeon) {
      if (dungeon.directionalMessageID) {
          dungeon.directionalCollector.stop();
      }
      var channel = client.channels.get(dungeon.room.channel);
  
      // Does image for background (future map)
      const imageEmbed = new Discord.RichEmbed()
          //.setAuthor("Ghost", client.user.avatarURL)
          .setColor(dungeon.room.color)
          .setImage(dungeon.location.image_url)
      await channel.send({embed: imageEmbed});
  
      // Does text dialog
      const textEmbed = new Discord.RichEmbed()
          .setAuthor("Ghost", client.user.avatarURL)
          .setTitle(`${dungeon.location.name}`)
          .setColor(dungeon.room.color)
          .setDescription(`${location(dungeon)}\n\n${movementOptions(dungeon)}`)
      var newMessage = await channel.send({embed: textEmbed});
  
      // Collects emotes and reacts upon the reaction (120 seconds)
      var options = ['⬆', '⬇', '⬅', '➡']
      const collector = newMessage.createReactionCollector(
          (reaction, user) => options.includes(reaction.emoji.name) && user.id !== client.user.id);
  
      // Reacts
      for (let i = 0; i < options.length; i++) {
          const element = options[i];
          console.log("[Reaction Options] Emote: " + element + "  | newMessage: " + newMessage);
          await newMessage.react(element);
      }
  
      // Collect
      collector.once("collect", async reaction => {
          const user = reaction.users.last();
          var moveSuccessful;
          switch (reaction.emoji.name) {
              case '⬆':
                  moveSuccessful = move(Directions.NORTH, dungeon, (getDungeonPlayer(user.id).isLeader || getDungeonPlayer(user.id).isCommander), user, false);
                  break;
  
              case '⬇':
                  moveSuccessful = move(Directions.SOUTH, dungeon, (getDungeonPlayer(user.id).isLeader || getDungeonPlayer(user.id).isCommander), user, false);
                  break;
              
              case '⬅':
                  moveSuccessful = move(Directions.WEST, dungeon, (getDungeonPlayer(user.id).isLeader || getDungeonPlayer(user.id).isCommander), user, false);
                  break;
  
              case '➡':
                  moveSuccessful = move(Directions.EAST, dungeon, (getDungeonPlayer(user.id).isLeader || getDungeonPlayer(user.id).isCommander), user, false);
                  break;
          }
          collector.stop();
          
      });
  
      // End trigger
      collector.once("end", collecter => {
          console.log("[Reaction Options] [sendEmbedLocation] Ended collector, clearing emotes and sending timing out message if needed.");
          newMessage.edit(newMessage.content);
      });
  }
  
  // Sends location descriptions (Text)
  function location(dungeon) {
      var temp = "";
  
      for (let index = 0; index < dungeon.location.descriptions.length; index++) {
          const element = dungeon.location.descriptions[index];
          if (element.text !== undefined) {
              temp += element.text + " ";
          }
      }
  
      //console.log(temp);
      return temp;
  }
  
  // Sends party members (Embed)
  function partyMembers(dungeon, pageNum) {
      // Updates players
      updatePlayers();
      var playerString = "";
      var userInfo = "";
  
      //console.log("0: " + pageNum);
      if (pageNum === undefined)
          pageNum = 0;
      else
          pageNum--;
      
      //console.log("1: " + pageNum)
      var groupedArr = createGroupedArray(dungeon.players, 9);
      //console.log(groupedArr);
      //console.log("\n\nGROUPEDARR[PAGENUM]: " + groupedArr[pageNum] + "\n\n")
  
      if (pageNum + 1 > groupedArr.length) return;    // If it's longer than actual length
      if (groupedArr[pageNum] !== undefined) {
          for (let index = 0; index < groupedArr[pageNum].length; index++) {
              //const user = dungeon.players[index];
              const element = groupedArr[pageNum][index];
   
              playerString += `${element.factionEmote}${element.badge} ${playerName(element.userID)}\n`; 
              userInfo += `${element.combatClassEmote} ${element.combatClass} ${element.isLeaderText}\n`;
              //console.log("playerString:\n" + playerString);
              //console.log("userInfo:\n " + userInfo);
          }
      }
  
      if (playerString == "") playerString = "None.";
      if (userInfo == "") userInfo = "None.";
  
      const embed = new Discord.RichEmbed()
          .setAuthor("Ghost", client.user.avatarURL)
          //.setTitle("Hell's Gate: Dungeon Raid")
          /*
          * Alternatively, use "#00AE86", [0, 174, 134] or an integer number.
          */
          .setColor(dungeon.room.color)
          .setTitle(`${dungeon.location.name}`)
          .setDescription(`The **!party** is currently on floor ${dungeon.location.floor}. The leader can **!promote** users so they can interact with the dungeon.`)
          //.setImage("https://i.imgur.com/WyI8YuR.png")
          //.setThumbnail("https://i.imgur.com/BZgLV7w.png")
          //.setThumbnail("https://i.imgur.com/BlQTi94.jpg")
  
          .addField(`Party Members (Page ${pageNum + 1}/${groupedArr.length})`, playerString, true)
          .addField("User Info", `${userInfo}`, true)
  
      client.channels.get(dungeon.room.channel).send({embed});
  }
  
  // Promote user to become leader
  function promoteCommander(author, mentionedMember, leader, channelID) {
      if (mentionedMember !== undefined ) {
          if (leader) {
              var dungeonPlayer = getDungeonPlayer(mentionedMember.id);
              if (dungeonPlayer.player) {
                  if (author.id !== mentionedMember.id) {
                      if (!dungeonPlayer.isCommander) {
                          sendMessage(channelID, `:white_check_mark: ${author} has been **!promote**d ${mentionedMember} to commander status!`);
                          dungeonPlayer.player.commander = true;
                      } else {
                          sendMessage(channelID, `:x: ${author} **Command denied.** User already has been **!promote**d.`);
                      }
                  } else {
                      sendMessage(channelID, `:x: ${author} **Command denied.** You cannot promote yourself.`);
                  }
              }
              else
                  sendMessage(channelID, `:x: ${author} **Command denied.** The user being promoted has to be in the party.`);
          }
          else 
              sendMessage(channelID, `:x: ${author} **Command denied.** You need to be a leader in order to **!promote** others.`);
      } else {
          temp = author + " !promote (!pr)\n```If you're the leader, you can promote others to be a commander with you in order to move around.```"
          sendMessage(channelID, temp);
      }
  }
  
  // Promote user to become leader
  function demoteCommander(author, mentionedMember, leader, channelID) {
      if (mentionedMember !== undefined ) {
          if (leader) {
              var dungeonPlayer = getDungeonPlayer(mentionedMember.id);
              if (dungeonPlayer.player) {
                  if (author.id !== mentionedMember.id) {
                      if (dungeonPlayer.isCommander) {
                          sendMessage(channelID, `:white_check_mark: ${author} has **!demote**d ${mentionedMember} to normal status.`);
                          dungeonPlayer.player.commander = false;
                      } else {
                          sendMessage(channelID, `:x: ${author} **Command denied.** User already has been **!demote**d.`);
                      }
                  } else {
                      sendMessage(channelID, `:x: ${author} **Command denied.** You cannot promote yourself.`);
                  }               
              }
              else
                  sendMessage(channelID, `:x: ${author} **Command denied.** The user being demoted has to be in the party.`);
          }
          else 
              sendMessage(channelID, `:x: ${author} **Command denied.** You need to be a leader in order to **!demote** others.`);
      } else {
          temp = author + " !demote (!de)\n```If you're the leader, you can demote others if needed if they are a commander.```"
          sendMessage(channelID, temp);
      }
  }
  
  // Transfer party leadership
  async function transferLeader(message, toUser) {
      // From and to user transfer
      if (toUser === undefined) return message.channel.send(`${message.author} I don't know that user, please try again with someone I can recognize, thanks.`);
  
      fromUser = getDungeonPlayer(message.author.id);
      newUser = getDungeonPlayer(toUser.id);
  
      if (newUser.playerDungeon !== undefined && fromUser.playerDungeon !== undefined 
          && newUser.playerDungeon === fromUser.playerDungeon && fromUser.player.leader
          && message.author.id !== toUser.id) {
  
          // Sends messages confirming it
          var confirmMessage = await message.channel.send(`:warning: <@${message.author.id}> are you sure you want to give leadership to ${toUser}?\n*You'll be given Commander status afterwards.*`);
  
          // Reacts
          await confirmMessage.react('✅');
          await confirmMessage.react('❌');
  
          const collector = confirmMessage.createReactionCollector(
              (reaction, user) => (reaction.emoji.name === '✅' || reaction.emoji.name === '❌') && user.id === message.author.id , { time: 15 * 1000 });
          var react = "";
          var endedOnReact;
  
          // Collect
          collector.once("collect", async reaction => {
              endedOnReact = true;
              react = reaction.emoji.name;
              collector.stop();
          });
  
          // End trigger
          collector.once("end", async collecter => {
              console.log("[Reaction Options] Ended collector, clearing emotes and sending timing out message if needed.");
              confirmMessage.edit(confirmMessage.content);
              confirmMessage.clearReactions();
              if (!endedOnReact) return sendMessage(message.channel, ":x: **Timed Out**: The emote reaction request timed out after 15 seconds.");
              if (react === '❌') return confirmMessage.edit(`:x: ${message.author} Cancelled giving leadership to ${toUser}.`);
  
              fromUser.player.leader = false;
              fromUser.player.commander = true;
              newUser.player.leader = true;
              newUser.player.commander = false;
  
              await confirmMessage.edit(`:white_check_mark: ${message.author} has transferred leadership tp ${toUser}!`)
          });
      } else {
          if (newUser.playerDungeon !== fromUser.playerDungeon) {
              return message.channel.send(`:x: ${message.author} The traveler must be in the party, or else I can't do anything about it.`);
          }
  
          if (!fromUser.player.leader) {
              return message.channel.send(`:x: ${message.author} You need to be a leader, in order to transfer your leadership.`)
          }
  
          if (message.author.id === toUser.id) {
              return message.channel.send(`:x: ${message.author} You cannot give yourself leadership if you are already the leader.`)
          }
      }
  }
  
  // Log our bot in (change the token by looking into the .env file)
  client.login(process.env.GHOST_TOKEN);