diff --git a/.gitignore b/.gitignore
index 2c192b5..ef6a5f2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -74,4 +74,7 @@ build/
 bin/akashi
 bin/config/
 bin_tests/
+bin/logs/
+bin/core.lib
+bin/libcore.a
 doxygen/html
diff --git a/akashi/main.cpp b/akashi/main.cpp
index 03e8d90..08b8d6f 100644
--- a/akashi/main.cpp
+++ b/akashi/main.cpp
@@ -39,40 +39,29 @@ int main(int argc, char* argv[])
     QCoreApplication::setApplicationVersion("banana");
     std::atexit(cleanup);
 
-    ConfigManager config_manager;
-    if (config_manager.initConfig()) {
-        // Config is sound, so proceed with starting the server
-        // Validate some of the config before passing it on
-        ConfigManager::server_settings settings;
-        bool config_valid = config_manager.loadServerSettings(&settings);
-        if (!config_valid) {
-            qCritical() << "config.ini is invalid!";
-            qCritical() << "Exiting server due to configuration issue.";
-            exit(EXIT_FAILURE);
-            QCoreApplication::quit();
-        }
-
-        else {
-            if (settings.advertise_server) {
-                advertiser =
-                    new Advertiser(settings.ms_ip, settings.ms_port,
-                                   settings.ws_port, settings.port,
-                                   settings.name, settings.description);
-                advertiser->contactMasterServer();
-            }
-
-            server = new Server(settings.port, settings.ws_port);
-
-            if (advertiser != nullptr) {
-                QObject::connect(server, &Server::reloadRequest, advertiser, &Advertiser::reloadRequested);
-            }
-            server->start();
-        }
-    } else {
+    // Verify server configuration is sound.
+    if (!ConfigManager::verifyServerConfig()) {
+        qCritical() << "config.ini is invalid!";
         qCritical() << "Exiting server due to configuration issue.";
         exit(EXIT_FAILURE);
         QCoreApplication::quit();
     }
+    else {
+        if (ConfigManager::advertiseServer()) {
+            advertiser =
+                new Advertiser(ConfigManager::masterServerIP(), ConfigManager::masterServerPort(),
+                               ConfigManager::webaoPort(), ConfigManager::serverPort(),
+                               ConfigManager::serverName(), ConfigManager::serverDescription());
+            advertiser->contactMasterServer();
+        }
+
+        server = new Server(ConfigManager::serverPort(), ConfigManager::webaoPort());
+
+        if (advertiser != nullptr) {
+            QObject::connect(server, &Server::reloadRequest, advertiser, &Advertiser::reloadRequested);
+        }
+        server->start();
+    }
 
     return app.exec();
 }
diff --git a/bin/config_sample/config.ini b/bin/config_sample/config.ini
index 55b65f0..c3c4621 100644
--- a/bin/config_sample/config.ini
+++ b/bin/config_sample/config.ini
@@ -1,42 +1,104 @@
-[Info]
-version=1
-
 [Options]
+; Whether or not the server should appear on the master server.
 advertise=true
+
+; The maximum number of players that can join the server at once.
 max_players=100
+
+; The IP address of the master server. Unless something happens to the default one, you shouldn't change this.
 ms_ip=master.aceattorneyonline.com
+
+; The port of the master server. Unless something happens to the default one, you shouldn't change this.
 ms_port=27016
+
+; The TCP port to listen for incoming connections on.
 port=27016
+
+; The server description that will appear on the master server.
 server_description=This is a placeholder server description. Tell the world of AO who you are here!
+
+; The server's name. This appears both on the master server, and in messages sent to users by the server.
 server_name=An Unnamed Server
+
+; The server's Message of the Day. This will be sent in OOC to joining users.
 motd=MOTD is not set.
+
+; Whether the server should accept WebAO connections or not.
 webao_enable=true
+
+; The TCP port to listen for WebAO connections on. This must be different than the server port.
 webao_port=27017
+
+; The authorization level of the server. You shouldn't touch this, use /changeauth on the server instead.
 auth=simple
+
+; The moderator password used with simple authorization. Change this to something unique and secure.
 modpass=changeme
+
+; The amount of logged messages an area should store. Once this limit is reached, older messages will be overrwritten. 
+; This is only used for modcall logging, or if the webhook_sendfile is enabled.
 logbuffer=500
+
+; The server logging type. Valid values here are "modcall" and "full". 
+; Modcall logging will only save an area log file if a modcall is sent. 
+; Full logging will log every event in every area, and will output to a new file every day.
 logging=modcall
+
+; The maximum number of statements that can be recorded in the testimony recorder.
 maximum_statements=10
+
+; The maximum number of connections that will be accepted from the same IP address.
+; Multiclienting is generally used for casing/RPing, so the default value is fine in most cases.
 multiclient_limit=15
+
+; The maximum number of characters that an IC/OOC message can contain.
 maximum_characters=256
+
+; The minimum time between IC messages, in miliseconds. The default value is fine for most cases.
 message_floodguard=250
-asset_url=Your WebAO asset url here.
+
+; The URL of the server's remote repository, sent to the client during their initial handshake. Used by WebAO users for custom content.
+asset_url=
 
 [Dice]
+; The maximum number of sides dice can be rolled with.
 max_value=100
+
+; The maximum number of dice that can be rolled at once.
 max_dice=100
 
 [Discord]
+; Whether the discord webhook is enabled or not.
+; The server will send messages to this webhook whenever a modcall is sent.
 webhook_enabled=false
-webhook_url=Your webhook url here.
+
+; The URL of the discord webhook to send messages to. Must contain the webhook ID and token.
+webhook_url=
+
+; Whether to attach a file containing the area log when a modcall message is sent to the webhook.
 webhook_sendfile=false
+
+; Additional text to send with the webhook message. Usually for adding tags for roles, like @Administrator
 webhook_content=
 
 [Password]
+; Whether or not to enforce password requirements. Only applicable under advanced authorization.
 password_requirements = true
+
+; The minimum length passwords must be.
 pass_min_length = 8
+
+; The maximum length passwords can be. Set to 0 for unlimited length passwords.
 pass_max_length = 0
+
+; Whether passwords must contain both uppercase and lowercase letters.
 pass_required_mix_case = true
+
+; Whether passwords must contain at least one number.
 pass_required_numbers = true
+
+; Whether passwords must contain at least one special character.
 pass_required_special = true
+
+; Whether passwords can contain the username inside them.
 pass_can_contain_username = false
diff --git a/core/core.pro b/core/core.pro
index d46856e..ea3e4ec 100644
--- a/core/core.pro
+++ b/core/core.pro
@@ -52,6 +52,7 @@ HEADERS += include/advertiser.h \
     include/aopacket.h \
     include/area_data.h \
     include/config_manager.h \
+    include/data_types.h \
     include/db_manager.h \
     include/discord.h \
     include/logger.h \
diff --git a/core/include/aoclient.h b/core/include/aoclient.h
index b3191b0..9aefdf5 100644
--- a/core/include/aoclient.h
+++ b/core/include/aoclient.h
@@ -230,6 +230,7 @@ class AOClient : public QObject {
         {"SAVETEST",        1ULL << 12},
         {"FORCE_CHARSELECT",1ULL << 13},
         {"BYPASS_LOCKS",    1ULL << 14},
+        {"IGNORE_BGLIST",   1ULL << 15},
         {"SUPER",          ~0ULL      }
     };
 
@@ -963,6 +964,15 @@ class AOClient : public QObject {
     */
     void cmdJudgeLog(int argc, QStringList argv);
 
+    /**
+     * @brief Toggles whether the BG list is ignored in an area.
+     *
+     * @details No arguments.
+     *
+     * @iscommand
+     */
+    void cmdIgnoreBgList(int argc, QStringList argv);
+
     ///@}
 
     /**
@@ -1937,7 +1947,7 @@ class AOClient : public QObject {
         {"login",              {ACLFlags.value("NONE"),         0, &AOClient::cmdLogin}},
         {"getareas",           {ACLFlags.value("NONE"),         0, &AOClient::cmdGetAreas}},
         {"getarea",            {ACLFlags.value("NONE"),         0, &AOClient::cmdGetArea}},
-        {"ban",                {ACLFlags.value("BAN"),          2, &AOClient::cmdBan}},
+        {"ban",                {ACLFlags.value("BAN"),          3, &AOClient::cmdBan}},
         {"kick",               {ACLFlags.value("KICK"),         2, &AOClient::cmdKick}},
         {"changeauth",         {ACLFlags.value("SUPER"),        0, &AOClient::cmdChangeAuth}},
         {"rootpass",           {ACLFlags.value("SUPER"),        1, &AOClient::cmdSetRootPass}},
@@ -2055,6 +2065,8 @@ class AOClient : public QObject {
         {"updateban",          {ACLFlags.value("BAN"),          3, &AOClient::cmdUpdateBan}},
         {"update_ban",         {ACLFlags.value("BAN"),          3, &AOClient::cmdUpdateBan}},
         {"changepass",         {ACLFlags.value("NONE"),         1, &AOClient::cmdChangePassword}},
+        {"ignorebglist",       {ACLFlags.value("IGNORE_BGLIST"),0, &AOClient::cmdIgnoreBgList}},
+        {"ignore_bglist",      {ACLFlags.value("IGNORE_BGLIST"),0, &AOClient::cmdIgnoreBgList}}
     };
 
     /**
diff --git a/core/include/area_data.h b/core/include/area_data.h
index f2d777d..ca436e5 100644
--- a/core/include/area_data.h
+++ b/core/include/area_data.h
@@ -20,6 +20,7 @@
 
 #include "logger.h"
 #include "aopacket.h"
+#include "config_manager.h"
 
 #include <QMap>
 #include <QString>
@@ -777,7 +778,7 @@ class AreaData : public QObject {
      * @brief Logs a moderator login attempt.
      *
      * @details This is not a duplicated function! When a client uses the `/login` command to log in, the command call
-     * itself is logged with log(), but the outcome of that call is logged here.
+     * itself is logged with logCmd(), but the outcome of that call is logged here.
      *
      * If there was a way to login *without* the command, only this would be logged.
      *
@@ -788,6 +789,18 @@ class AreaData : public QObject {
      */
     void logLogin(const QString &f_clientName_r, const QString &f_clientIpid_r, bool f_success, const QString& f_modname_r) const;
 
+    /**
+     * @brief Logs a command in the area logger.
+     *
+     * @details When a client sends any packet containing `/`, it is sent to this function instead of log().
+     *
+     * @param f_clientName_r The showname of the command sender's character.
+     * @param f_clientIpid_r The IPID of the command sender.
+     * @param f_command_r The command that was sent.
+     * @param f_cmdArgs_r The arguments of the command
+     */
+    void logCmd(const QString& f_clientName_r, const QString& f_clientIpid_r, const QString& f_command_r, const QStringList& f_cmdArgs_r) const;
+
     /**
      * @brief Convenience function over Logger::flush().
      */
@@ -797,9 +810,23 @@ class AreaData : public QObject {
      * @brief Returns a copy of the underlying logger's buffer.
      *
      * @return See short description.
+     *
+     * @see #m_ignoreBgList
      */
     QQueue<QString> buffer() const;
 
+    /**
+     * @brief Returns whether the BG list is ignored in this araa.
+     *
+     * @return See short description.
+     */
+    bool ignoreBgList();
+
+    /**
+     * @brief Toggles whether the BG list is ignored in this area.
+     */
+    void toggleIgnoreBgList();
+
 private:
     /**
      * @brief The list of timers available in the area.
@@ -983,6 +1010,11 @@ private:
      * @brief Whether or not music is allowed in this area. If false, only CMs can change the music.
      */
     bool m_toggleMusic;
+
+    /**
+     * @brief Whether or not to ignore the server defined background list. If true, any background can be set in an area.
+     */
+    bool m_ignoreBgList;
 };
 
 #endif // AREA_DATA_H
diff --git a/core/include/config_manager.h b/core/include/config_manager.h
index f073385..852e28b 100644
--- a/core/include/config_manager.h
+++ b/core/include/config_manager.h
@@ -20,85 +20,354 @@
 
 #define CONFIG_VERSION 1
 
+#include "data_types.h"
+
 #include <QDebug>
 #include <QDir>
 #include <QFile>
 #include <QFileInfo>
 #include <QSettings>
+#include <QUrl>
+#include <QMetaEnum>
 
 /**
  * @brief The config file handler class.
  */
 class ConfigManager {
+
   public:
     /**
-     * @brief An empty constructor for ConfigManager.
+     * @brief Verifies the server configuration, confirming all required files/directories exist and are valid.
+     *
+     * @return True if the server configuration was verified, false otherwise.
      */
-    ConfigManager() {};
+    static bool verifyServerConfig();
 
     /**
-     * @brief Performs some preliminary checks for the various configuration files used by the server.
+     * @brief Returns true if the server should advertise to the master server.
      *
-     * @return True if the config file exists, is up-to-date, and valid, false otherwise.
+     * @return See short description.
      */
-    bool initConfig();
+    static bool advertiseServer();
 
     /**
-     * @brief Updates the config file's version to the one used by the server currently.
+     * @brief Returns the maximum number of players the server will allow.
      *
-     * @details The function can return false if the server's expected config version is 0
-     * (doesn't actually exist), or negative (nonsense).
-     * If the current config file lags more than one version behind the expected, all intermediate
-     * updates are also performed on the config file.
-     *
-     * @param current_version The current configuration version expected by the server.
-     *
-     * @return True if a version update took place, false otherwise.
+     * @return See short description.
      */
-    bool updateConfig(int current_version);
+    static int maxPlayers();
 
     /**
-     * @brief The collection of server-specific settings.
+     * @brief Returns the IP of the master server to advertise to.
+     *
+     * @return See short description.
      */
-    struct server_settings {
-        QString ms_ip; //!< The IP address of the master server to establish connection to.
-        int port; //!< The TCP port the server will accept client connections through.
-        int ws_port; //!< The WebSocket port the server will accept client connections through.
-        int ms_port; //!< The port of the master server to establish connection to.
-        QString name; //!< The name of the server as advertised on the server browser.
-        QString description; //!< The description of the server as advertised on the server browser.
-        bool advertise_server; //!< The server will only be announced to the master server (and thus appear on the master server list) if this is true.
+    static QString masterServerIP();
+
+    /**
+     * @brief Returns the port of the master server to advertise to.
+     *
+     * @return See short description.
+     */
+    static int masterServerPort();
+
+    /**
+     * @brief Returns the port to listen for connections on.
+     *
+     * @return See short description.
+     */
+    static int serverPort();
+
+    /**
+     * @brief Returns the server description.
+     *
+     * @return See short description.
+     */
+    static QString serverDescription();
+
+    /**
+     * @brief Returns the server name.
+     *
+     * @return See short description.
+     */
+    static QString serverName();
+
+    /**
+     * @brief Returns the server's Message of the Day.
+     *
+     * @return See short description.
+     */
+    static QString motd();
+
+    /**
+     * @brief Returns true if the server should accept webAO connections.
+     *
+     * @return See short description.
+     */
+    static bool webaoEnabled();
+
+    /**
+     * @brief Returns the port to listen for webAO connections on.
+     *
+     * @return See short description.
+     */
+    static int webaoPort();
+
+    /**
+     * @brief Returns the server's authorization type.
+     *
+     * @return See short description.
+     */
+    static DataTypes::AuthType authType();
+
+    /**
+     * @brief Returns the server's moderator password.
+     *
+     * @return See short description.
+     */
+    static QString modpass();
+
+    /**
+     * @brief Returns the server's log buffer length.
+     *
+     * @return See short description.
+     */
+    static int logBuffer();
+
+    /**
+     * @brief Returns the server's logging type.
+     *
+     * @return See short description.
+     */
+    static DataTypes::LogType loggingType();
+
+    /**
+     * @brief Returns true if the server should advertise to the master server.
+     *
+     * @return See short description.
+     */
+    static int maxStatements();
+
+    /**
+     * @brief Returns the maximum number of permitted connections from the same IP.
+     *
+     * @return See short description.
+     */
+    static int multiClientLimit();
+
+    /**
+     * @brief Returns the maximum number of characters a message can contain.
+     *
+     * @return See short description.
+     */
+    static int maxCharacters();
+
+    /**
+     * @brief Returns the duration of the message floodguard.
+     *
+     * @return See short description.
+     */
+    static int messageFloodguard();
+
+    /**
+     * @brief Returns the URL where the server should retrieve remote assets from.
+     *
+     * @return See short description.
+     */
+    static QUrl assetUrl();
+
+    /**
+     * @brief Returns the maximum number of sides dice can have.
+     *
+     * @return See short description.
+     */
+    static int diceMaxValue();
+
+    /**
+     * @brief Returns the maximum number of dice that can be rolled at once.
+     *
+     * @return See short description.
+     */
+    static int diceMaxDice();
+
+    /**
+     * @brief Returns true if the discord webhook is enabled.
+     *
+     * @return See short description.
+     */
+    static bool discordWebhookEnabled();
+
+    /**
+     * @brief Returns the discord webhook URL.
+     *
+     * @return See short description.
+     */
+    static QString discordWebhookUrl();
+
+    /**
+     * @brief Returns the discord webhook content.
+     *
+     * @return See short description.
+     */
+    static QString discordWebhookContent();
+
+    /**
+     * @brief Returns true if the discord webhook should send log files.
+     *
+     * @return See short description.
+     */
+    static bool discordWebhookSendFile();
+
+    /**
+     * @brief Returns true if password requirements should be enforced.
+     *
+     * @return See short description.
+     */
+    static bool passwordRequirements();
+
+    /**
+     * @brief Returns the minimum length passwords must be.
+     *
+     * @return See short description.
+     */
+    static int passwordMinLength();
+
+    /**
+     * @brief Returns the maximum length passwords can be, or `0` for unlimited length.
+     *
+     * @return See short description.
+     */
+    static int passwordMaxLength();
+
+    /**
+     * @brief Returns true if passwords must be mixed case.
+     *
+     * @return See short description.
+     */
+    static bool passwordRequireMixCase();
+
+    /**
+     * @brief Returns true is passwords must contain one or more numbers.
+     *
+     * @return See short description.
+     */
+    static bool passwordRequireNumbers();
+
+    /**
+     * @brief Returns true if passwords must contain one or more special characters..
+     *
+     * @return See short description.
+     */
+    static bool passwordRequireSpecialCharacters();
+
+    /**
+     * @brief Returns true if passwords can contain the username.
+     *
+     * @return See short description.
+     */
+    static bool passwordCanContainUsername();
+
+    /**
+     * @brief Returns the duration before a client is considered AFK.
+     *
+     * @return See short description.
+     */
+    static int afkTimeout();
+
+    /**
+     * @brief Returns a list of magic 8 ball answers.
+     *
+     * @return See short description.
+     */
+    static QStringList magic8BallAnswers();
+
+    /**
+     * @brief Returns a list of praises.
+     *
+     * @return See short description.
+     */
+    static QStringList praiseList();
+
+    /**
+     * @brief Returns a list of reprimands.
+     *
+     * @return See short description.
+     */
+    static QStringList reprimandsList();
+
+    /**
+     * @brief Returns the server gimp list.
+     *
+     * @return See short description.
+     */
+    static QStringList gimpList();
+
+    /**
+     * @brief Sets the server's authorization type.
+     *
+     * @param f_auth The auth type to set.
+     */
+    static void setAuthType(const DataTypes::AuthType f_auth);
+
+    /**
+     * @brief Sets the server's Message of the Day.
+     *
+     * @param f_motd The MOTD to set.
+     */
+    static void setMotd(const QString f_motd);
+
+    /**
+     * @brief Reload the server configuration.
+     */
+    static void reloadSettings();
+
+private:
+    /**
+     * @brief Checks if a file exists and is valid.
+     *
+     * @param file The file to check.
+     *
+     * @return True if the file exists and is valid, false otherwise.
+     */
+    static bool fileExists(const QFileInfo& file);
+
+    /**
+     * @brief Checks if a directory exists and is valid.
+     *
+     * @param file The directory to check.
+     *
+     * @return True if the directory exists and is valid, false otherwise.
+     */
+    static bool dirExists(const QFileInfo& dir);
+
+    /**
+     * @brief A struct for storing QStringLists loaded from command configuration files.
+     */
+    struct CommandSettings {
+        QStringList magic_8ball; //!< Contains answers for /8ball, found in config/text/8ball.txt
+        QStringList praises; //!< Contains command praises, found in config/text/praises.txt
+        QStringList reprimands; //!< Contains command reprimands, found in config/text/reprimands.txt
+        QStringList gimps; //!< Contains phrases for /gimp, found in config/text/gimp.txt
     };
 
     /**
-     * @brief Loads the server settings into the given struct from the config file.
-     *
-     * @param[out] settings Pointer to a server_settings file to be filled with data.
-     *
-     * @return False if any of the ports (the master server connection port,
-     * the TCP port used by clients, or the WebSocket port used by WebAO) failed
-     * to be read in from the settings correctly, true otherwise.
-     *
-     * @pre initConfig() must have been run beforehand to check for the config file's existence.
+     * @brief Contains the settings required for various commands.
      */
-    bool loadServerSettings(server_settings* settings);
-
-  private:
-    /**
-     * @brief Convenience function to check if the object exists, and is a file.
-     *
-     * @param file The object to check.
-     *
-     * @return See brief description.
-     */
-    bool fileExists(QFileInfo *file);
+    static CommandSettings* m_commands;
 
     /**
-     * @brief Verifies the existence of the command configuration files found in config/text/.
-     *
-     * @return True if the config files exist, and are files. False otherwise.
+     * @brief Stores all server configuration values.
      */
-    bool verifyCommandConfig();
+    static QSettings* m_settings;
+
+    /**
+     * @brief Returns a stringlist with the contents of a .txt file from config/text/.
+     *
+     * @param Name of the file to load.
+     */
+    static QStringList loadConfigFile(const QString filename);
 };
 
+
+
 #endif // CONFIG_MANAGER_H
diff --git a/core/include/data_types.h b/core/include/data_types.h
new file mode 100644
index 0000000..e485c8d
--- /dev/null
+++ b/core/include/data_types.h
@@ -0,0 +1,59 @@
+//////////////////////////////////////////////////////////////////////////////////////
+//    akashi - a server for Attorney Online 2                                       //
+//    Copyright (C) 2020  scatterflower                                             //
+//                                                                                  //
+//    This program is free software: you can redistribute it and/or modify          //
+//    it under the terms of the GNU Affero General Public License as                //
+//    published by the Free Software Foundation, either version 3 of the            //
+//    License, or (at your option) any later version.                               //
+//                                                                                  //
+//    This program is distributed in the hope that it will be useful,               //
+//    but WITHOUT ANY WARRANTY; without even the implied warranty of                //
+//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                 //
+//    GNU Affero General Public License for more details.                           //
+//                                                                                  //
+//    You should have received a copy of the GNU Affero General Public License      //
+//    along with this program.  If not, see <https://www.gnu.org/licenses/>.        //
+//////////////////////////////////////////////////////////////////////////////////////
+#ifndef DATA_TYPES_H
+#define DATA_TYPES_H
+
+#include <QDebug>
+/**
+ * @brief A class for handling several custom data types.
+ */
+class DataTypes
+{
+    Q_GADGET
+
+public:
+    /**
+     * @brief Custom type for authorization types.
+     */
+    enum class AuthType {
+            SIMPLE,
+            ADVANCED
+        };
+    Q_ENUM(AuthType);
+
+    /**
+     * @brief Custom type for logging types.
+     */
+    enum class LogType {
+        MODCALL,
+        FULL
+    };
+    Q_ENUM(LogType)
+};
+
+template<typename T>
+T toDataType(const QString& f_string){
+    return QVariant(f_string).value<T>();
+}
+
+template<typename T>
+QString fromDataType(const T& f_t){
+    return QVariant::fromValue(f_t).toString();
+}
+
+#endif // DATA_TYPES_H
diff --git a/core/include/logger.h b/core/include/logger.h
index a35fde1..2866f47 100644
--- a/core/include/logger.h
+++ b/core/include/logger.h
@@ -23,6 +23,7 @@
 #include <QString>
 #include <QQueue>
 #include <QDateTime>
+#include "data_types.h"
 
 /**
  * @brief A class associated with an AreaData class to log various events happening inside the latter.
@@ -35,7 +36,7 @@ public:
      *
      * @param f_max_length The maximum amount of entries the Logger can store at once.
      */
-    Logger(QString f_area_name, int f_max_length, const QString& f_logType_r) :
+    Logger(QString f_area_name, int f_max_length, const DataTypes::LogType& f_logType_r) :
         m_areaName(f_area_name), m_maxLength(f_max_length), m_logType(f_logType_r) {};
 
     /**
@@ -80,9 +81,10 @@ public slots:
      *
      * @param f_charName_r The character name of the client who sent the command.
      * @param f_ipid_r The IPID of the aforementioned client.
-     * @param f_oocMessage_r The text of the OOC message. Passed to logOOC() if the command is not 'special' (see details).
+     * @param f_command_r The command being logged.
+     * @param f_cmdArgs_r The command arguments being logged.
      */
-    void logCmd(const QString& f_charName_r, const QString& f_ipid_r, const QString& f_oocMessage_r);
+    void logCmd(const QString& f_charName_r, const QString& f_ipid_r, const QString& f_command_r, const QStringList& f_cmdArgs_r);
 
     /**
      * @brief Logs a login attempt.
@@ -132,7 +134,7 @@ private:
      * @details This largely influences the resulting log file's name, and in case of a `"full"` setup,
      * the in-memory buffer is auto-dumped to said file if full.
      */
-    QString m_logType;
+    DataTypes::LogType m_logType;
 };
 
 #endif // LOGGER_H
diff --git a/core/include/server.h b/core/include/server.h
index 3fd6157..2a14857 100644
--- a/core/include/server.h
+++ b/core/include/server.h
@@ -24,6 +24,7 @@
 #include "include/ws_proxy.h"
 #include "include/db_manager.h"
 #include "include/discord.h"
+#include "include/config_manager.h"
 
 #include <QCoreApplication>
 #include <QDebug>
@@ -183,126 +184,14 @@ class Server : public QObject {
      */
     DBManager* db_manager;
 
-    /**
-     * @brief The max amount of players on the server.
-     */
-    QString max_players;
-    /**
-     * @brief The user-facing server name.
-     */
-    QString server_name;
-
-    /**
-     * @brief The server description.
-     */
-    QString server_desc;
-
-    /**
-     * @brief The Message Of The Day of the server, shown upon entry to the server and on request.
-     */
-    QString MOTD;
-
-   /**
-    * @brief The Maximum amounts of IC-Messages an area is allowed to store.
-    */
-    int maximum_statements;
-
-    /**
-     * @brief The authorization type of the server.
-     *
-     * @details In simple mode, the modpass stored in config.ini is used for moderator logins. In advanced mode, logins found in the database are used.
-     */
-    QString auth_type;
-
-    /**
-     * @brief The modpass for moderator login with simple auth_type.
-     */
-    QString modpass;
-
-    /**
-     * @brief The highest value dice can have.
-     */
-    int dice_value;
-
-    /**
-     * @brief The max amount of dice that can be rolled at once.
-     */
-    int max_dice;
-
-    /**
-     * @brief The amount of time in seconds to wait before marking a user AFK.
-     */
-    int afk_timeout;
-
-    /**
-     * @brief Whether discord webhooks are enabled on this server.
-     */
-    bool webhook_enabled;
-
-    /**
-     * @brief Requires an https Webhook link, including both ID and Token in the link.
-     */
-    QString webhook_url;
-
-    /**
-     * @brief If the modcall buffer is sent as a file.
-     */
-    bool webhook_sendfile;
-
     /**
      * @brief The server-wide global timer.
      */
     QTimer* timer;
 
-    /**
-     * @brief Loads values from config.ini.
-     */
-    void loadServerConfig();
-
-    /**
-     * @brief Loads the configuration files for commands into stringlists.
-     */
-    void loadCommandConfig();
-
-    /**
-     * @brief Returns a stringlist with the contents of a .txt file from config/text/.
-     *
-     * @param Name of the file to load.
-     */
-    QStringList loadConfigFile(QString filename);
 
     QStringList getCursedCharsTaken(AOClient* client, QStringList chars_taken);
 
-    /**
-     * @brief List holding the contents of 8ball.txt, used by /8ball.
-     */
-    QStringList magic_8ball_answers;
-
-    /**
-     * @brief List holding the contents of praise.txt, used by AOClient::getReprimand.
-     */
-    QStringList praise_list;
-
-    /**
-     * @brief List holding the contents of reprimands.txt, used by AOClient::getReprimand.
-     */
-    QStringList reprimands_list;
-
-    /**
-     * @brief List holding the contents of gimp.txt, used by AOClient::cmdGimp.
-     */
-    QStringList gimp_list;
-
-    /**
-     * @brief Integer representing the maximum number of clients allowed to connect from the same IP
-     */
-    int multiclient_limit;
-
-    /**
-     * @brief Integer representing the maximum amount of characters an IC or OOC message can contain.
-     */
-    int max_chars;
-
     /**
      * @brief Timer until the next IC message can be sent.
      */
@@ -313,56 +202,6 @@ class Server : public QObject {
      */
     bool can_send_ic_messages = true;
 
-    /**
-     * @brief The minimum time between IC messages, in milliseconds.
-     */
-    int message_floodguard;
-
-    /**
-     * @brief Whether password requirements are enabled.
-     */
-    bool password_requirements = true;
-
-    /**
-     * @brief The minimum length passwords can be.
-     */
-    int password_minimum_length;
-
-    /**
-     * @brief The maximum length passwords can be.
-     */
-    int password_maximum_length;
-
-    /**
-     * @brief Whether passwords must be mixed case.
-     */
-    bool password_require_mixed_case = true;
-
-    /**
-     * @brief Whether passwords must contain numbers.
-     */
-    bool password_require_numbers = true;
-
-    /**
-     * @brief Whether passwords must contain special characters.
-     */
-    bool password_require_special_characters = true;
-
-    /**
-     * @brief Whether passwords can contain the associated username.
-     */
-    bool password_can_contain_username = false;
-
-    /**
-     * @brief URL send to the client during handshake to set the remote repository URL.
-     */
-    QUrl asset_url;
-
-    /**
-     * @brief Opional text to be send with the Discord embeed. Can be used to configure pings.
-     */
-    QString webhook_content;
-
   public slots:
     /**
      * @brief Handles a new connection.
diff --git a/core/src/aoclient.cpp b/core/src/aoclient.cpp
index 8371e11..8bd6461 100644
--- a/core/src/aoclient.cpp
+++ b/core/src/aoclient.cpp
@@ -88,7 +88,7 @@ void AOClient::handlePacket(AOPacket packet)
         if (is_afk)
             sendServerMessage("You are no longer AFK.");
         is_afk = false;
-        afk_timer->start(server->afk_timeout * 1000);
+        afk_timer->start(ConfigManager::afkTimeout() * 1000);
     }
 
     if (packet.contents.length() < info.minArgs) {
@@ -291,17 +291,17 @@ void AOClient::calculateIpid()
 
 void AOClient::sendServerMessage(QString message)
 {
-    sendPacket("CT", {server->server_name, message, "1"});
+    sendPacket("CT", {ConfigManager::serverName(), message, "1"});
 }
 
 void AOClient::sendServerMessageArea(QString message)
 {
-    server->broadcast(AOPacket("CT", {server->server_name, message, "1"}), current_area);
+    server->broadcast(AOPacket("CT", {ConfigManager::serverName(), message, "1"}), current_area);
 }
 
 void AOClient::sendServerBroadcast(QString message)
 {
-    server->broadcast(AOPacket("CT", {server->server_name, message, "1"}));
+    server->broadcast(AOPacket("CT", {ConfigManager::serverName(), message, "1"}));
 }
 
 bool AOClient::checkAuth(unsigned long long acl_mask)
@@ -318,12 +318,14 @@ bool AOClient::checkAuth(unsigned long long acl_mask)
         else if (!authenticated) {
             return false;
         }
-        if (server->auth_type == "advanced") {
+        switch (ConfigManager::authType()) {
+        case DataTypes::AuthType::SIMPLE:
+            return authenticated;
+            break;
+        case DataTypes::AuthType::ADVANCED:
             unsigned long long user_acl = server->db_manager->getACL(moderator_name);
             return (user_acl & acl_mask) != 0;
-        }
-        else if (server->auth_type == "simple") {
-            return authenticated;
+            break;
         }
     }
     return true;
diff --git a/core/src/area_data.cpp b/core/src/area_data.cpp
index 17dd7b8..38daf76 100644
--- a/core/src/area_data.cpp
+++ b/core/src/area_data.cpp
@@ -47,13 +47,10 @@ AreaData::AreaData(QString p_name, int p_index) :
     m_forceImmediate = areas_ini.value("force_immediate", "false").toBool();
     m_toggleMusic = areas_ini.value("toggle_music", "true").toBool();
     m_shownameAllowed = areas_ini.value("shownames_allowed", "true").toBool();
+    m_ignoreBgList = areas_ini.value("ignore_bglist", "false").toBool();
     areas_ini.endGroup();
-    QSettings config_ini("config/config.ini", QSettings::IniFormat);
-    config_ini.setIniCodec("UTF-8");
-    config_ini.beginGroup("Options");
-    int log_size = config_ini.value("logbuffer", 50).toInt();
-    QString l_logType = config_ini.value("logger","modcall").toString();
-    config_ini.endGroup();
+    int log_size = ConfigManager::logBuffer();
+    DataTypes::LogType l_logType = ConfigManager::loggingType();
     if (log_size == 0)
         log_size = 500;
     m_logger = new Logger(m_name, log_size, l_logType);
@@ -296,7 +293,7 @@ void AreaData::log(const QString &f_clientName_r, const QString &f_clientIpid_r,
     if (l_header == "MS") {
         m_logger->logIC(f_clientName_r, f_clientIpid_r, f_packet_r.contents.at(4));
     } else if (l_header == "CT") {
-        m_logger->logCmd(f_clientName_r, f_clientIpid_r, f_packet_r.contents.at(1));
+        m_logger->logOOC(f_clientName_r, f_clientIpid_r, f_packet_r.contents.at(1));
     } else if (l_header == "ZZ") {
         m_logger->logModcall(f_clientName_r, f_clientIpid_r, f_packet_r.contents.at(0));
     }
@@ -307,6 +304,11 @@ void AreaData::logLogin(const QString &f_clientName_r, const QString &f_clientIp
     m_logger->logLogin(f_clientName_r, f_clientIpid_r, f_success, f_modname_r);
 }
 
+void AreaData::logCmd(const QString &f_clientName_r, const QString &f_clientIpid_r, const QString &f_command_r, const QStringList &f_cmdArgs_r) const
+{
+    m_logger->logCmd(f_clientName_r, f_clientIpid_r, f_command_r, f_cmdArgs_r);
+}
+
 void AreaData::flushLogs() const
 {
     m_logger->flush();
@@ -534,3 +536,13 @@ QString AreaData::background() const
 {
     return m_background;
 }
+
+bool AreaData::ignoreBgList()
+{
+    return m_ignoreBgList;
+}
+
+void AreaData::toggleIgnoreBgList()
+{
+    m_ignoreBgList = !m_ignoreBgList;
+}
diff --git a/core/src/commands/area.cpp b/core/src/commands/area.cpp
index 0a2219c..e454072 100644
--- a/core/src/commands/area.cpp
+++ b/core/src/commands/area.cpp
@@ -239,7 +239,7 @@ void AOClient::cmdSetBackground(int argc, QStringList argv)
 {
     AreaData* area = server->areas[current_area];
     if (authenticated || !area->bgLocked()) {
-        if (server->backgrounds.contains(argv[0])) {
+        if (server->backgrounds.contains(argv[0]) || area->ignoreBgList() == true) {
             area->background() = argv[0];
             server->broadcast(AOPacket("BN", {argv[0]}), current_area);
             sendServerMessageArea(current_char + " changed the background to " + argv[0]);
@@ -261,7 +261,7 @@ void AOClient::cmdBgLock(int argc, QStringList argv)
         area->toggleBgLock();
     };
 
-    server->broadcast(AOPacket("CT", {server->server_name, current_char + " locked the background.", "1"}), current_area);
+    server->broadcast(AOPacket("CT", {ConfigManager::serverName(), current_char + " locked the background.", "1"}), current_area);
 }
 
 void AOClient::cmdBgUnlock(int argc, QStringList argv)
@@ -272,7 +272,7 @@ void AOClient::cmdBgUnlock(int argc, QStringList argv)
         area->toggleBgLock();
     };
 
-    server->broadcast(AOPacket("CT", {server->server_name, current_char + " unlocked the background.", "1"}), current_area);
+    server->broadcast(AOPacket("CT", {ConfigManager::serverName(), current_char + " unlocked the background.", "1"}), current_area);
 }
 
 void AOClient::cmdStatus(int argc, QStringList argv)
@@ -282,7 +282,7 @@ void AOClient::cmdStatus(int argc, QStringList argv)
 
     if (area->changeStatus(arg)) {
         arup(ARUPType::STATUS, true);
-        server->broadcast(AOPacket("CT", {server->server_name, current_char + " changed status to " + arg.toUpper(), "1"}), current_area);
+        server->broadcast(AOPacket("CT", {ConfigManager::serverName(), current_char + " changed status to " + arg.toUpper(), "1"}), current_area);
     } else {
         sendServerMessage("That does not look like a valid status. Valid statuses are " + AreaData::map_statuses.keys().join(", "));
     }
@@ -305,3 +305,11 @@ void AOClient::cmdJudgeLog(int argc, QStringList argv)
         sendServerMessage(filteredmessage);
     }
 }
+
+void AOClient::cmdIgnoreBgList(int argc, QStringList argv)
+{
+    AreaData* area = server->areas[current_area];
+    area->toggleIgnoreBgList();
+    QString state = area->ignoreBgList() ? "ignored." : "enforced.";
+    sendServerMessage("BG list in this area is now " + state);
+}
diff --git a/core/src/commands/authentication.cpp b/core/src/commands/authentication.cpp
index 2d789fb..1fe40fa 100644
--- a/core/src/commands/authentication.cpp
+++ b/core/src/commands/authentication.cpp
@@ -26,8 +26,9 @@ void AOClient::cmdLogin(int argc, QStringList argv)
         sendServerMessage("You are already logged in!");
         return;
     }
-    if (server->auth_type == "simple") {
-        if (server->modpass == "") {
+    switch (ConfigManager::authType()) {
+    case DataTypes::AuthType::SIMPLE:
+        if (ConfigManager::modpass() == "") {
             sendServerMessage("No modpass is set. Please set a modpass before logging in.");
             return;
         }
@@ -36,17 +37,18 @@ void AOClient::cmdLogin(int argc, QStringList argv)
             is_logging_in = true;
             return;
         }
-    }
-    else if (server->auth_type == "advanced") {
+        break;
+    case DataTypes::AuthType::ADVANCED:
         sendServerMessage("Entering login prompt.\nPlease enter your username and password.");
         is_logging_in = true;
         return;
+        break;
     }
 }
 
 void AOClient::cmdChangeAuth(int argc, QStringList argv)
 {
-    if (server->auth_type == "simple") {
+    if (ConfigManager::authType() == DataTypes::AuthType::SIMPLE) {
         change_auth_started = true;
         sendServerMessage("WARNING!\nThis command will change how logging in as a moderator works.\nOnly proceed if you know what you are doing\nUse the command /rootpass to set the password for your root account.");
     }
@@ -64,10 +66,7 @@ void AOClient::cmdSetRootPass(int argc, QStringList argv)
 
     sendServerMessage("Changing auth type and setting root password.\nLogin again with /login root [password]");
     authenticated = false;
-    QSettings settings("config/config.ini", QSettings::IniFormat);
-    settings.beginGroup("Options");
-    settings.setValue("auth", "advanced");
-    server->auth_type = "advanced";
+    ConfigManager::setAuthType(DataTypes::AuthType::ADVANCED);
 
 #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
     qsrand(QDateTime::currentMSecsSinceEpoch());
diff --git a/core/src/commands/casing.cpp b/core/src/commands/casing.cpp
index 0568c29..78e3c4f 100644
--- a/core/src/commands/casing.cpp
+++ b/core/src/commands/casing.cpp
@@ -170,7 +170,7 @@ void AOClient::cmdPauseTestimony(int argc, QStringList argv)
 
 void AOClient::cmdAddStatement(int argc, QStringList argv)
 {
-    if (server->areas[current_area]->statement() < server->maximum_statements) {
+    if (server->areas[current_area]->statement() < ConfigManager::maxStatements()) {
         server->areas[current_area]->setTestimonyRecording(AreaData::TestimonyRecording::ADD);
         sendServerMessage("The next IC-Message will be inserted into the testimony.");
     }
@@ -248,7 +248,7 @@ void AOClient::cmdLoadTestimony(int argc, QStringList argv)
     QTextStream in(&file);
     in.setCodec("UTF-8");
     while (!in.atEnd()) {
-        if (testimony_lines <= server->maximum_statements) {
+        if (testimony_lines <= ConfigManager::maxStatements()) {
             QString line = in.readLine();
             QStringList packet = line.split("#");
             area->addStatement(area->testimony().size(), packet);
diff --git a/core/src/commands/command_helper.cpp b/core/src/commands/command_helper.cpp
index 5d5e046..ee5d2b6 100644
--- a/core/src/commands/command_helper.cpp
+++ b/core/src/commands/command_helper.cpp
@@ -80,9 +80,9 @@ void AOClient::diceThrower(int argc, QStringList argv, bool p_roll)
     int dice = 1;
     QStringList results;
     if (argc >= 1)
-        sides = qBound(1, argv[0].toInt(), server->dice_value);
+        sides = qBound(1, argv[0].toInt(), ConfigManager::diceMaxValue());
     if (argc == 2)
-        dice = qBound(1, argv[1].toInt(), server->max_dice);
+        dice = qBound(1, argv[1].toInt(), ConfigManager::diceMaxDice());
     for (int i = 1; i <= dice; i++) {
         results.append(QString::number(AOClient::genRand(1, sides)));
     }
@@ -163,44 +163,44 @@ long long AOClient::parseTime(QString input)
 QString AOClient::getReprimand(bool positive)
 {
     if (positive) {
-        return server->praise_list[genRand(0, server->praise_list.size() - 1)];
+        return ConfigManager::praiseList()[genRand(0, ConfigManager::praiseList().size() - 1)];
         }
     else {
-        return server->reprimands_list[genRand(0, server->reprimands_list.size() - 1)];
+        return ConfigManager::reprimandsList()[genRand(0, ConfigManager::reprimandsList().size() - 1)];
         }
 }
 
 bool AOClient::checkPasswordRequirements(QString username, QString password)
 {
     QString decoded_password = decodeMessage(password);
-    if (!server->password_requirements)
+    if (!ConfigManager::passwordRequirements())
         return true;
 
-    if (server->password_minimum_length > decoded_password.length())
+    if (ConfigManager::passwordMinLength() > decoded_password.length())
         return false;
 
-    if (server->password_maximum_length < decoded_password.length() && server->password_maximum_length != 0)
+    if (ConfigManager::passwordMaxLength() < decoded_password.length() && ConfigManager::passwordMaxLength() != 0)
         return false;
 
-    else if (server->password_require_mixed_case) {
+    else if (ConfigManager::passwordRequireMixCase()) {
         if (decoded_password.toLower() == decoded_password)
             return false;
         if (decoded_password.toUpper() == decoded_password)
             return false;
     }
-    else if (server->password_require_numbers) {
+    else if (ConfigManager::passwordRequireNumbers()) {
         QRegularExpression regex("[0123456789]");
         QRegularExpressionMatch match = regex.match(decoded_password);
         if (!match.hasMatch())
             return false;
     }
-    else if (server->password_require_special_characters) {
+    else if (ConfigManager::passwordRequireSpecialCharacters()) {
         QRegularExpression regex("[~!@#$%^&*_-+=`|\\(){}\[]:;\"'<>,.?/]");
         QRegularExpressionMatch match = regex.match(decoded_password);
         if (!match.hasMatch())
             return false;
     }
-    else if (!server->password_can_contain_username) {
+    else if (!ConfigManager::passwordCanContainUsername()) {
         if (decoded_password.contains(username))
             return false;
     }
diff --git a/core/src/commands/moderation.cpp b/core/src/commands/moderation.cpp
index ebad420..1582151 100644
--- a/core/src/commands/moderation.cpp
+++ b/core/src/commands/moderation.cpp
@@ -30,11 +30,6 @@ void AOClient::cmdBan(int argc, QStringList argv)
 
     DBManager::BanInfo ban;
 
-    if (argc < 3) {
-        sendServerMessage("Invalid syntax. Usage:\n/ban <ipid> <duration> <reason>");
-        return;
-    }
-
     long long duration_seconds = 0;
     if (argv[1] == "perma")
         duration_seconds = -2;
@@ -53,11 +48,13 @@ void AOClient::cmdBan(int argc, QStringList argv)
     bool ban_logged = false;
     int kick_counter = 0;
 
-    if (server->auth_type == "advanced") {
-        ban.moderator = moderator_name;
-    }
-    else {
+    switch (ConfigManager::authType()) {
+    case DataTypes::AuthType::SIMPLE:
         ban.moderator = "moderator";
+        break;
+    case DataTypes::AuthType::ADVANCED:
+        ban.moderator = moderator_name;
+        break;
     }
 
     for (AOClient* client : server->getClientsByIpid(ban.ipid)) {
@@ -121,7 +118,7 @@ void AOClient::cmdMods(int argc, QStringList argv)
     for (AOClient* client : server->clients) {
         if (client->authenticated) {
             entries << "---";
-            if (server->auth_type != "simple")
+            if (ConfigManager::authType() != DataTypes::AuthType::SIMPLE)
                 entries << "Moderator: " + client->moderator_name;
             entries << "OOC name: " + client->ooc_name;
             entries << "ID: " + QString::number(client->id);
@@ -152,12 +149,12 @@ void AOClient::cmdHelp(int argc, QStringList argv)
 void AOClient::cmdMOTD(int argc, QStringList argv)
 {
     if (argc == 0) {
-        sendServerMessage("=== MOTD ===\r\n" + server->MOTD + "\r\n=============");
+        sendServerMessage("=== MOTD ===\r\n" + ConfigManager::motd() + "\r\n=============");
     }
     else if (argc > 0) {
         if (checkAuth(ACLFlags.value("MOTD"))) {
             QString MOTD = argv.join(" ");
-            server->MOTD = MOTD;
+            ConfigManager::setMotd(MOTD);
             sendServerMessage("MOTD has been changed.");
         }
         else {
@@ -413,9 +410,8 @@ void AOClient::cmdBanInfo(int argc, QStringList argv)
 
 void AOClient::cmdReload(int argc, QStringList argv)
 {
-    server->loadServerConfig();
-    server->loadCommandConfig();
-    emit server->reloadRequest(server->server_name, server->server_desc);
+    ConfigManager::reloadSettings();
+    emit server->reloadRequest(ConfigManager::serverName(), ConfigManager::serverDescription());
     sendServerMessage("Reloaded configurations");
 }
 
diff --git a/core/src/commands/roleplay.cpp b/core/src/commands/roleplay.cpp
index d291d98..8806f94 100644
--- a/core/src/commands/roleplay.cpp
+++ b/core/src/commands/roleplay.cpp
@@ -161,12 +161,12 @@ void AOClient::cmdNoteCardReveal(int argc, QStringList argv)
 
 void AOClient::cmd8Ball(int argc, QStringList argv)
 {
-    if (server->magic_8ball_answers.isEmpty()) {
+    if (ConfigManager::magic8BallAnswers().isEmpty()) {
         qWarning() << "8ball.txt is empty!";
         sendServerMessage("8ball.txt is empty.");
         }
     else {
-        QString response = server->magic_8ball_answers[(genRand(1, server->magic_8ball_answers.size() - 1))];
+        QString response = ConfigManager::magic8BallAnswers()[(genRand(1, ConfigManager::magic8BallAnswers().size() - 1))];
         QString sender_name = ooc_name;
         QString sender_message = argv.join(" ");
 
diff --git a/core/src/config_manager.cpp b/core/src/config_manager.cpp
index 8256862..76244c7 100644
--- a/core/src/config_manager.cpp
+++ b/core/src/config_manager.cpp
@@ -17,179 +17,368 @@
 //////////////////////////////////////////////////////////////////////////////////////
 #include "include/config_manager.h"
 
-// Validate and set up the config
-bool ConfigManager::initConfig()
+QSettings* ConfigManager::m_settings = new QSettings("config/config.ini", QSettings::IniFormat);
+ConfigManager::CommandSettings* ConfigManager::m_commands = new CommandSettings();
+
+bool ConfigManager::verifyServerConfig()
 {
-    QSettings config("config/config.ini", QSettings::IniFormat);
-    config.setIniCodec("UTF-8");
-    QFileInfo config_dir_info("config/");
-    if (!config_dir_info.exists() || !config_dir_info.isDir()) {
-        qCritical() << "Config directory doesn't exist!";
-        return false;
+    // Verify directories
+    QStringList l_directories{"config/", "config/text/"};
+    for (QString l_directory : l_directories) {
+        if (!dirExists(QFileInfo(l_directory))) {
+                qCritical() << l_directory + " does not exist!";
+                return false;
+        }
     }
 
-    // Check that areas, characters, and music lists all exist
-    QFileInfo areas_info("config/areas.ini");
-    QFileInfo characters_info("config/characters.txt");
-    QFileInfo music_info("config/music.txt");
-    QFileInfo backgrounds_info("config/backgrounds.txt");
-
-    if (!fileExists(&areas_info)) {
-        qCritical() << "areas.ini doesn't exist!";
-        return false;
-    }
-    else {
-        QSettings areas_ini("config/areas.ini", QSettings::IniFormat);
-        areas_ini.setIniCodec("UTF-8");
-        if (areas_ini.childGroups().length() < 1) {
-            qCritical() << "areas.ini is invalid!";
+    // Verify config files
+    QStringList l_config_files{"config/config.ini", "config/areas.ini", "config/backgrounds.txt", "config/characters.txt", "config/music.txt",
+                              "config/text/8ball.txt", "config/text/gimp.txt", "config/text/praise.txt", "config/text/reprimands.txt"};
+    for (QString l_file : l_config_files) {
+        if (!fileExists(QFileInfo(l_file))) {
+            qCritical() << l_file + " does not exist!";
             return false;
         }
     }
-    if (!fileExists(&characters_info)) {
-        qCritical() << "characters.txt doesn't exist!";
-        return false;
-    }
-    if (!fileExists(&music_info)) {
-        qCritical() << "music.txt doesn't exist!";
-        return false;
-    }
-    if (!fileExists(&backgrounds_info)) {
-        qCritical() << "backgrounds.txt doesn't exist!";
+
+    // Verify areas
+    QSettings l_areas_ini("config/areas.ini", QSettings::IniFormat);
+    l_areas_ini.setIniCodec("UTF-8");
+    if (l_areas_ini.childGroups().length() < 1) {
+        qCritical() << "areas.ini is invalid!";
         return false;
     }
 
-    config.beginGroup("Info");
-    QString config_version = config.value("version", "none").toString();
-    config.endGroup();
-    if (config_version == "none") {
-        QFileInfo check_file("config/config.ini");
-        if (!fileExists(&check_file)) {
-            qCritical() << "config.ini doesn't exist!";
-        }
-        else {
-            qCritical() << "config.ini is invalid!";
-        }
+    // Verify config settings
+    m_settings->beginGroup("Options");
+    bool ok;
+    m_settings->value("ms_port", 27016).toInt(&ok);
+    if (!ok) {
+        qCritical("ms_port is not a valid port!");
         return false;
     }
-    else if (config_version != QString::number(CONFIG_VERSION)) {
-        bool version_number_is_valid;
-        int current_version = config_version.toInt(&version_number_is_valid);
-        if (version_number_is_valid) {
-            if (updateConfig(current_version))
-                qWarning() << "config.ini was out of date, and has been updated. Please review the changes, and restart the server.";
-            else
-                qCritical() << "config.ini is invalid!";
-        }
-        else
-            qCritical() << "config.ini is invalid!"; // Version number isn't a number at all
-                                                     // This means the config is invalid
+    m_settings->value("port", 27016).toInt(&ok);
+    if (!ok) {
+        qCritical("port is not a valid port!");
         return false;
     }
-    config.beginGroup("Options");
-    QString auth_type = config.value("auth", "simple").toString();
-    config.endGroup();
-    if (!(auth_type == "simple" || auth_type == "advanced")) {
-        qCritical() << "config.ini is invalid!";
-        return false;
-    }
-    if (!(verifyCommandConfig())) {
-        return false;
-    }
-
-    else {
-        // Config is valid and up to date, so let's go ahead
-        return true;
-    }
-}
-
-// Ensure version continuity with config versions
-bool ConfigManager::updateConfig(int current_version)
-{
-    QSettings config("config/config.ini", QSettings::IniFormat);
-    config.setIniCodec("UTF-8");
-    if (current_version > CONFIG_VERSION) {
-        // Config version is newer than the latest version, and the config is
-        // invalid This could also mean the server is out of date, and the user
-        // should be shown a relevant message Regardless, regen the config
-        // anyways
-        return false;
-    }
-    else if (current_version < 0) {
-        // Negative version number? Invalid!
-        return false;
+    bool web_ao = m_settings->value("webao_enable", false).toBool();
+    if (!web_ao) {
+        m_settings->setValue("webao_port", -1);
     }
     else {
-        // Update the config as needed using a switch. This is nice because we
-        // can fall through as we go up the version ladder.
-        switch (current_version) {
-        case 0: // Version 0 doesn't actually exist, but we should check for it
-                // just in case
-        case 1:
-            config.beginGroup("Info");
-            config.setValue("version", CONFIG_VERSION);
-            config.endGroup();
-            break; // This is the newest version, and nothing more needs to be
-                   // done
-        }
-        return true;
-    }
-}
-
-// Validate and retriever settings related to advertising and the server
-bool ConfigManager::loadServerSettings(server_settings* settings)
-{
-    QSettings config("config/config.ini", QSettings::IniFormat);
-    config.setIniCodec("UTF-8");
-    bool port_conversion_success;
-    bool ws_port_conversion_success;
-    bool local_port_conversion_success;
-    config.beginGroup("Options");
-    settings->ms_ip =
-        config.value("ms_ip", "master.aceattorneyonline.com").toString();
-    settings->port =
-        config.value("port", "27016").toInt(&port_conversion_success);
-    settings->ws_port =
-        config.value("webao_port", "27017").toInt(&ws_port_conversion_success);
-    settings->ms_port =
-        config.value("ms_port", "27016").toInt(&local_port_conversion_success);
-    settings->name = config.value("server_name", "My First Server").toString();
-    settings->description =
-        config.value("server_description", "This is my flashy new server")
-            .toString();
-    config.endGroup();
-    if (!port_conversion_success || !ws_port_conversion_success ||
-        !local_port_conversion_success) {
-        return false;
-    }
-    else {
-        config.beginGroup("Options");
-        // Will be true of false depending on the key
-        settings->advertise_server =
-            (config.value("advertise", "true").toString() == "true");
-
-        if (config.value("webao_enable", "true").toString() != "true")
-            settings->ws_port = -1;
-        config.endGroup();
-
-        return true;
-    }
-}
-
-bool ConfigManager::fileExists(QFileInfo* file)
-{
-    return (file->exists() && file->isFile());
-}
-
-bool ConfigManager::verifyCommandConfig()
-{
-    QStringList filelist = {"8ball", "praise", "reprimands", "gimp"};
-    foreach (QString filename, filelist) {
-        QFileInfo file("config/text/" + filename + ".txt");
-        if (!(fileExists(&file))) {
-            qCritical() << (filename + ".txt doesn't exist!");
+        m_settings->value("webao_port", 27017).toInt(&ok);
+        if (!ok) {
+            qCritical("webao_port is not a valid port!");
             return false;
         }
     }
+    QString l_auth = m_settings->value("auth", "simple").toString().toLower();
+    if (!(l_auth == "simple" || l_auth == "advanced")) {
+        qCritical("auth is not a valid auth type!");
+        return false;
+    }
+    m_settings->endGroup();
+    m_commands->magic_8ball = (loadConfigFile("8ball"));
+    m_commands->praises = (loadConfigFile("praise"));
+    m_commands->reprimands = (loadConfigFile("reprimands"));
+    m_commands->gimps = (loadConfigFile("gimp"));
+
     return true;
 }
+
+void ConfigManager::reloadSettings()
+{
+    m_settings->sync();
+}
+
+QStringList ConfigManager::loadConfigFile(const QString filename)
+{
+    QStringList stringlist;
+    QFile file("config/text/" + filename + ".txt");
+    file.open(QIODevice::ReadOnly | QIODevice::Text);
+    while (!(file.atEnd())) {
+        stringlist.append(file.readLine().trimmed());
+    }
+    file.close();
+    return stringlist;
+}
+
+bool ConfigManager::advertiseServer()
+{
+    return m_settings->value("Options/advertise", true).toBool();
+}
+
+int ConfigManager::maxPlayers()
+{
+    bool ok;
+    int l_players = m_settings->value("Options/max_players", 100).toInt(&ok);
+    if (!ok) {
+        qWarning("max_players is not an int!");
+        l_players = 100;
+    }
+    return l_players;
+}
+
+QString ConfigManager::masterServerIP()
+{
+    return m_settings->value("Options/ms_ip", "master.aceattorneyonline.com").toString();
+}
+
+int ConfigManager::masterServerPort()
+{
+    return m_settings->value("Options/ms_port", 27016).toInt();
+}
+
+int ConfigManager::serverPort()
+{
+    return m_settings->value("Options/port", 27016).toInt();
+}
+
+QString ConfigManager::serverDescription()
+{
+    return m_settings->value("Options/server_description", "This is my flashy new server!").toString();
+}
+
+QString ConfigManager::serverName()
+{
+    return m_settings->value("Options/server_name", "An Unnamed Server").toString();
+}
+
+QString ConfigManager::motd()
+{
+    return m_settings->value("Options/motd", "MOTD not set").toString();
+}
+
+bool ConfigManager::webaoEnabled()
+{
+    return m_settings->value("Options/webao_enable", false).toBool();
+}
+
+int ConfigManager::webaoPort()
+{
+    return m_settings->value("Options/webao_port", 27017).toInt();
+}
+
+DataTypes::AuthType ConfigManager::authType()
+{
+    QString l_auth = m_settings->value("Options/auth", "simple").toString();
+    return toDataType<DataTypes::AuthType>(l_auth);
+}
+
+QString ConfigManager::modpass()
+{
+    return m_settings->value("Options/modpass", "changeme").toString();
+}
+
+int ConfigManager::logBuffer()
+{
+    bool ok;
+    int l_buffer = m_settings->value("Options/logbuffer", 500).toInt(&ok);
+    if (!ok) {
+        qWarning("logbuffer is not an int!");
+        l_buffer = 500;
+    }
+    return l_buffer;
+}
+
+DataTypes::LogType ConfigManager::loggingType()
+{
+    QString l_log = m_settings->value("Options/logging", "modcall").toString();
+    return toDataType<DataTypes::LogType>(l_log);
+}
+
+int ConfigManager::maxStatements()
+{
+    bool ok;
+    int l_max = m_settings->value("Options/maximum_statements", 10).toInt(&ok);
+    if (!ok) {
+        qWarning("maximum_statements is not an int!");
+        l_max = 10;
+    }
+    return l_max;
+}
+int ConfigManager::multiClientLimit()
+{
+    bool ok;
+    int l_limit = m_settings->value("Options/multiclient_limit", 15).toInt(&ok);
+    if (!ok) {
+        qWarning("multiclient_limit is not an int!");
+        l_limit = 15;
+    }
+    return l_limit;
+}
+
+int ConfigManager::maxCharacters()
+{
+    bool ok;
+    int l_max = m_settings->value("Options/maximum_characters", 256).toInt(&ok);
+    if (!ok) {
+        qWarning("maximum_characters is not an int!");
+        l_max = 256;
+    }
+    return l_max;
+}
+
+int ConfigManager::messageFloodguard()
+{
+    bool ok;
+    int l_flood = m_settings->value("Options/message_floodguard", 250).toInt(&ok);
+    if (!ok) {
+        qWarning("message_floodguard is not an int!");
+        l_flood = 250;
+    }
+    return l_flood;
+}
+
+QUrl ConfigManager::assetUrl()
+{
+    QByteArray l_url = m_settings->value("Options/asset_url", "").toString().toUtf8();
+    if (QUrl(l_url).isValid()) {
+        return QUrl(l_url);
+    }
+    else {
+        qWarning("asset_url is not a valid url!");
+        return QUrl(NULL);
+    }
+}
+
+int ConfigManager::diceMaxValue()
+{
+    bool ok;
+    int l_value = m_settings->value("Dice/max_value", 100).toInt(&ok);
+    if (!ok) {
+        qWarning("max_value is not an int!");
+        l_value = 100;
+    }
+    return l_value;
+}
+
+int ConfigManager::diceMaxDice()
+{
+    bool ok;
+    int l_dice = m_settings->value("Dice/max_dice", 100).toInt(&ok);
+    if (!ok) {
+        qWarning("max_dice is not an int!");
+        l_dice = 100;
+    }
+    return l_dice;
+}
+
+bool ConfigManager::discordWebhookEnabled()
+{
+    return m_settings->value("Discord/webhook_enabled", false).toBool();
+}
+
+QString ConfigManager::discordWebhookUrl()
+{
+    return m_settings->value("Discord/webhook_url", "").toString();
+}
+
+QString ConfigManager::discordWebhookContent()
+{
+    return m_settings->value("Discord/webhook_content", "").toString();
+}
+
+bool ConfigManager::discordWebhookSendFile()
+{
+    return m_settings->value("Discord/webhook_sendfile", false).toBool();
+}
+
+bool ConfigManager::passwordRequirements()
+{
+    return m_settings->value("Password/password_requirements", true).toBool();
+}
+
+int ConfigManager::passwordMinLength()
+{
+    bool ok;
+    int l_min = m_settings->value("Password/pass_min_length", 8).toInt(&ok);
+    if (!ok) {
+        qWarning("pass_min_length is not an int!");
+        l_min = 8;
+    }
+    return l_min;
+}
+
+int ConfigManager::passwordMaxLength()
+{
+    bool ok;
+    int l_max = m_settings->value("Password/pass_max_length", 0).toInt(&ok);
+    if (!ok) {
+        qWarning("pass_max_length is not an int!");
+        l_max = 0;
+    }
+    return l_max;
+}
+
+bool ConfigManager::passwordRequireMixCase()
+{
+    return m_settings->value("Password/pass_required_mix_case", true).toBool();
+}
+
+bool ConfigManager::passwordRequireNumbers()
+{
+    return m_settings->value("Password/pass_required_numbers", true).toBool();
+}
+
+bool ConfigManager::passwordRequireSpecialCharacters()
+{
+    return m_settings->value("Password/pass_required_special", true).toBool();
+}
+
+bool ConfigManager::passwordCanContainUsername()
+{
+    return m_settings->value("Password/pass_can_contain_username", false).toBool();
+}
+
+int ConfigManager::afkTimeout()
+{
+    bool ok;
+    int l_afk = m_settings->value("Options/afk_timeout", 300).toInt(&ok);
+    if (!ok) {
+        qWarning("afk_timeout is not an int!");
+        l_afk = 300;
+    }
+    return l_afk;
+}
+
+void ConfigManager::setAuthType(const DataTypes::AuthType f_auth)
+{
+    m_settings->setValue("Options/auth", fromDataType<DataTypes::AuthType>(f_auth).toLower());
+}
+
+QStringList ConfigManager::magic8BallAnswers()
+{
+    return m_commands->magic_8ball;
+}
+
+QStringList ConfigManager::praiseList()
+{
+    return m_commands->praises;
+}
+
+QStringList ConfigManager::reprimandsList()
+{
+    return m_commands->reprimands;
+}
+
+QStringList ConfigManager::gimpList()
+{
+    return m_commands->gimps;
+}
+
+void ConfigManager::setMotd(const QString f_motd)
+{
+    m_settings->setValue("Options/motd", f_motd);
+}
+
+bool ConfigManager::fileExists(const QFileInfo &f_file)
+{
+    return (f_file.exists() && f_file.isFile());
+}
+
+bool ConfigManager::dirExists(const QFileInfo &f_dir)
+{
+    return (f_dir.exists() && f_dir.isDir());
+}
diff --git a/core/src/discord.cpp b/core/src/discord.cpp
index de97e20..c0cf97d 100644
--- a/core/src/discord.cpp
+++ b/core/src/discord.cpp
@@ -19,12 +19,13 @@
 
 void Discord::postModcallWebhook(QString name, QString reason, int current_area)
 {
-    if (!QUrl (server->webhook_url).isValid()) {
+    if (!QUrl (ConfigManager::discordWebhookUrl()).isValid()) {
         qWarning() << "Invalid webhook url!";
         return;
     }
 
-    QNetworkRequest request(QUrl (server->webhook_url));
+    QNetworkRequest request;
+    request.setUrl(QUrl (ConfigManager::discordWebhookUrl()));
     QNetworkAccessManager* nam = new QNetworkAccessManager();
     connect(nam, &QNetworkAccessManager::finished,
             this, &Discord::onFinish);
@@ -42,12 +43,12 @@ void Discord::postModcallWebhook(QString name, QString reason, int current_area)
     };
     jsonArray.append(jsonObject);
     json["embeds"] = jsonArray;
-    if (!server->webhook_content.isEmpty())
-      json["content"] = server->webhook_content;
+    if (!ConfigManager::discordWebhookContent().isEmpty())
+      json["content"] = ConfigManager::discordWebhookContent();
 
     nam->post(request, QJsonDocument(json).toJson());
 
-    if (server->webhook_sendfile) {
+    if (ConfigManager::discordWebhookSendFile()) {
         QHttpMultiPart* construct = new QHttpMultiPart();
         request.setHeader(QNetworkRequest::ContentTypeHeader, "multipart/form-data; boundary=" + construct->boundary());
 
diff --git a/core/src/logger.cpp b/core/src/logger.cpp
index 260acb7..55203a7 100644
--- a/core/src/logger.cpp
+++ b/core/src/logger.cpp
@@ -35,27 +35,22 @@ void Logger::logModcall(const QString& f_charName_r, const QString& f_ipid_r, co
     addEntry(f_charName_r, f_ipid_r, "MODCALL", f_modcallReason_r);
 }
 
-void Logger::logCmd(const QString& f_charName_r, const QString& f_ipid_r, const QString& f_oocMessage_r)
+void Logger::logCmd(const QString& f_charName_r, const QString& f_ipid_r, const QString& f_command_r, const QStringList& f_cmdArgs_r)
 {
-    // I don't like this, but oh well.
-    auto l_cmdArgs = f_oocMessage_r.split(" ", QString::SplitBehavior::SkipEmptyParts);
-    auto l_cmd = l_cmdArgs.at(0).trimmed().toLower();
-    l_cmd = l_cmd.right(l_cmd.length() - 1);
-    l_cmdArgs.removeFirst();
-
     // Some commands contain sensitive data, like passwords
     // These must be filtered out
-    if (l_cmd == "login") {
+    if (f_command_r == "login") {
         addEntry(f_charName_r, f_ipid_r, "LOGIN", "Attempted login");
     }
-    else if (l_cmd == "rootpass") {
+    else if (f_command_r == "rootpass") {
         addEntry(f_charName_r, f_ipid_r, "USERS", "Root password created");
     }
-    else if (l_cmd == "adduser" && !l_cmdArgs.isEmpty()) {
-        addEntry(f_charName_r, f_ipid_r, "USERS", "Added user " + l_cmdArgs.at(0));
+    else if (f_command_r == "adduser" && !f_cmdArgs_r.isEmpty()) {
+        addEntry(f_charName_r, f_ipid_r, "USERS", "Added user " + f_cmdArgs_r.at(0));
     }
     else {
-        logOOC(f_charName_r, f_ipid_r, f_oocMessage_r);
+        QString message = "/" + f_command_r + f_cmdArgs_r.join(" ");
+        logOOC(f_charName_r, f_ipid_r, message);
     }
 }
 
@@ -79,7 +74,7 @@ void Logger::addEntry(
     if (m_buffer.length() < m_maxLength) {
         m_buffer.enqueue(l_logEntry);
 
-        if (m_logType == "full") {
+        if (m_logType == DataTypes::LogType::FULL) {
            flush();
         }
     }
@@ -98,14 +93,13 @@ void Logger::flush()
 
     QFile l_logfile;
 
-    if (m_logType == "modcall") {
+    switch (m_logType) {
+    case DataTypes::LogType::MODCALL:
         l_logfile.setFileName(QString("logs/report_%1_%2.log").arg(m_areaName, (QDateTime::currentDateTime().toString("yyyy-MM-dd_hhmmss"))));
-    }
-    else if (m_logType == "full") {
+        break;
+    case DataTypes::LogType::FULL:
         l_logfile.setFileName(QString("logs/%1.log").arg(QDate::currentDate().toString("yyyy-MM-dd")));
-    }
-    else {
-        qCritical("Invalid logger set!");
+        break;
     }
 
     if (l_logfile.open(QIODevice::WriteOnly | QIODevice::Append)) {
diff --git a/core/src/packets.cpp b/core/src/packets.cpp
index f2512d1..dfa2e89 100644
--- a/core/src/packets.cpp
+++ b/core/src/packets.cpp
@@ -62,11 +62,11 @@ void AOClient::pktSoftwareId(AreaData* area, int argc, QStringList argv, AOPacke
         version.minor = match.captured(3).toInt();
     }
 
-    sendPacket("PN", {QString::number(server->player_count), server->max_players});
+    sendPacket("PN", {QString::number(server->player_count), QString::number(ConfigManager::maxPlayers())});
     sendPacket("FL", feature_list);
 
-    if (server->asset_url.isValid()) {
-    QByteArray asset_url = server->asset_url.toEncoded(QUrl::EncodeSpaces);
+    if (ConfigManager::assetUrl().isValid()) {
+    QByteArray asset_url = ConfigManager::assetUrl().toEncoded(QUrl::EncodeSpaces);
     sendPacket("ASS", {asset_url});
     }
 }
@@ -116,7 +116,7 @@ void AOClient::pktLoadingDone(AreaData* area, int argc, QStringList argv, AOPack
     sendPacket("DONE");
     sendPacket("BN", {area->background()});
   
-    sendServerMessage("=== MOTD ===\r\n" + server->MOTD + "\r\n=============");
+    sendServerMessage("=== MOTD ===\r\n" + ConfigManager::motd() + "\r\n=============");
 
     fullArup(); // Give client all the area data
     if (server->timer->isActive()) {
@@ -179,7 +179,7 @@ void AOClient::pktIcChat(AreaData* area, int argc, QStringList argv, AOPacket pa
     area->updateLastICMessage(validated_packet.contents);
 
     server->can_send_ic_messages = false;
-    server->next_message_timer.start(server->message_floodguard);
+    server->next_message_timer.start(ConfigManager::messageFloodguard());
 }
 
 void AOClient::pktOocChat(AreaData* area, int argc, QStringList argv, AOPacket packet)
@@ -190,7 +190,7 @@ void AOClient::pktOocChat(AreaData* area, int argc, QStringList argv, AOPacket p
     }
 
     ooc_name = dezalgo(argv[0]).replace(QRegExp("\\[|\\]|\\{|\\}|\\#|\\$|\\%|\\&"), ""); // no fucky wucky shit here
-    if (ooc_name.isEmpty() || ooc_name == server->server_name) // impersonation & empty name protection
+    if (ooc_name.isEmpty() || ooc_name == ConfigManager::serverName()) // impersonation & empty name protection
         return;
 
     if (ooc_name.length() > 30) {
@@ -204,7 +204,7 @@ void AOClient::pktOocChat(AreaData* area, int argc, QStringList argv, AOPacket p
     }
     
     QString message = dezalgo(argv[1]);
-    if (message.length() == 0 || message.length() > server->max_chars)
+    if (message.length() == 0 || message.length() > ConfigManager::maxCharacters())
         return;
     AOPacket final_packet("CT", {ooc_name, message, "0"});
     if(message.at(0) == '/') {
@@ -215,6 +215,8 @@ void AOClient::pktOocChat(AreaData* area, int argc, QStringList argv, AOPacket p
         int cmd_argc = cmd_argv.length();
 
         handleCommand(command, cmd_argc, cmd_argv);
+        area->logCmd(current_char, ipid, command, cmd_argv);
+        return;
     }
     else {
         server->broadcast(final_packet, current_area);
@@ -335,7 +337,7 @@ void AOClient::pktWebSocketIp(AreaData* area, int argc, QStringList argv, AOPack
                 multiclient_count++;
         }
 
-        if (multiclient_count > server->multiclient_limit) {
+        if (multiclient_count > ConfigManager::multiClientLimit()) {
             socket->close();
             return;
         }
@@ -350,7 +352,7 @@ void AOClient::pktModCall(AreaData* area, int argc, QStringList argv, AOPacket p
     }
     area->log(current_char, ipid, packet);
 
-    if (server->webhook_enabled) {
+    if (ConfigManager::discordWebhookEnabled()) {
         QString name = ooc_name;
         if (ooc_name.isEmpty())
             name = current_char;
@@ -519,11 +521,11 @@ AOPacket AOClient::validateIcPacket(AOPacket packet)
     args.append(incoming_args[1].toString());
 
     // char name
-    if (current_char != incoming_args[2].toString()) {
+    if (current_char.toLower() != incoming_args[2].toString().toLower()) {
         // Selected char is different from supplied folder name
         // This means the user is INI-swapped
         if (!area->iniswapAllowed()) {
-            if (!server->characters.contains(incoming_args[2].toString()))
+            if (!server->characters.contains(incoming_args[2].toString(), Qt::CaseInsensitive))
                 return invalid;
         }
         qDebug() << "INI swap detected from " << getIpid();
@@ -538,7 +540,7 @@ AOPacket AOClient::validateIcPacket(AOPacket packet)
     args.append(emote);
 
     // message text
-    if (incoming_args[4].toString().size() > server->max_chars)
+    if (incoming_args[4].toString().size() > ConfigManager::maxCharacters())
         return invalid;
 
     QString incoming_msg = dezalgo(incoming_args[4].toString().trimmed());
@@ -553,7 +555,7 @@ AOPacket AOClient::validateIcPacket(AOPacket packet)
     }
 
     if (is_gimped) {
-        QString gimp_message = server->gimp_list[(genRand(1, server->gimp_list.size() - 1))];
+        QString gimp_message = ConfigManager::gimpList()[(genRand(1, ConfigManager::gimpList().size() - 1))];
         incoming_msg = gimp_message;
     }
 
@@ -883,8 +885,9 @@ QString AOClient::decodeMessage(QString incoming_message)
 
 void AOClient::loginAttempt(QString message)
 {
-    if (server->auth_type == "simple") {
-        if (message == server->modpass) {
+    switch (ConfigManager::authType()) {
+    case DataTypes::AuthType::SIMPLE:
+        if (message == ConfigManager::modpass()) {
             sendPacket("AUTH", {"1"}); // Client: "You were granted the Disable Modcalls button."
             sendServerMessage("Logged in as a moderator."); // pre-2.9.1 clients are hardcoded to display the mod UI when this string is sent in OOC
             authenticated = true;
@@ -894,8 +897,8 @@ void AOClient::loginAttempt(QString message)
             sendServerMessage("Incorrect password.");
         }
         server->areas.value(current_area)->logLogin(current_char, ipid, authenticated, "moderator");
-    }
-    else if (server->auth_type == "advanced") {
+        break;
+    case DataTypes::AuthType::ADVANCED:
         QStringList login = message.split(" ");
         if (login.size() < 2) {
             sendServerMessage("You must specify a username and a password");
@@ -918,11 +921,8 @@ void AOClient::loginAttempt(QString message)
             sendServerMessage("Incorrect password.");
         }
         server->areas.value(current_area)->logLogin(current_char, ipid, authenticated, username);
+        break;
     }
-    else {
-            qWarning() << "config.ini has an unrecognized auth_type!";
-            sendServerMessage("Config.ini contains an invalid auth_type, please check your config.");
-        }
     sendServerMessage("Exiting login prompt.");
     is_logging_in = false;
     return;
diff --git a/core/src/server.cpp b/core/src/server.cpp
index 87fe428..84c5717 100644
--- a/core/src/server.cpp
+++ b/core/src/server.cpp
@@ -51,10 +51,7 @@ void Server::start()
         qDebug() << "Server listening on" << port;
     }
 
-    loadServerConfig();
-    loadCommandConfig();
-
-    if (webhook_enabled) {
+    if (ConfigManager::discordWebhookEnabled()) {
         discord = new Discord(this, this);
         connect(this, &Server::webhookRequest,
                 discord, &Discord::postModcallWebhook);
@@ -133,7 +130,7 @@ void Server::clientConnected()
             multiclient_count++;
     }
 
-    if (multiclient_count > multiclient_limit && !client->remote_ip.isLoopback()) // TODO: make this configurable
+    if (multiclient_count > ConfigManager::multiClientLimit() && !client->remote_ip.isLoopback()) // TODO: make this configurable
         is_at_multiclient_limit = true;
 
     if (is_banned) {
@@ -246,96 +243,6 @@ int Server::getCharID(QString char_name)
     return -1; // character does not exist
 }
 
-void Server::loadCommandConfig()
-{
-    magic_8ball_answers = (loadConfigFile("8ball"));
-    praise_list = (loadConfigFile("praise"));
-    reprimands_list = (loadConfigFile("reprimands"));
-    gimp_list = (loadConfigFile("gimp"));
-}
-
-QStringList Server::loadConfigFile(QString filename)
-{
-    QStringList stringlist;
-    QFile file("config/text/" + filename + ".txt");
-    file.open(QIODevice::ReadOnly | QIODevice::Text);
-    while (!(file.atEnd())) {
-        stringlist.append(file.readLine().trimmed());
-    }
-    file.close();
-    return stringlist;
-}
-
-void Server::loadServerConfig()
-{
-    QSettings config("config/config.ini", QSettings::IniFormat);
-    config.beginGroup("Options");
-    //Load config.ini values
-    max_players = config.value("max_players","100").toString();
-    server_name = config.value("server_name","An Unnamed Server").toString();
-    server_desc = config.value("server_description","This is a placeholder server description. Tell the world of AO who you are here!").toString();
-    MOTD = config.value("motd","MOTD is not set.").toString();
-    auth_type = config.value("auth","simple").toString();
-    modpass = config.value("modpass","").toString();
-    bool maximum_statements_conversion_success;
-    maximum_statements = config.value("maximustatement()s", "10").toInt(&maximum_statements_conversion_success);
-    if (!maximum_statements_conversion_success)
-        maximum_statements = 10;
-    bool afk_timeout_conversion_success;
-    afk_timeout = config.value("afk_timeout", "300").toInt(&afk_timeout_conversion_success);
-    if (!afk_timeout_conversion_success)
-        afk_timeout = 300;
-    bool multiclient_limit_conversion_success;
-    multiclient_limit = config.value("multiclient_limit", "15").toInt(&multiclient_limit_conversion_success);
-    if (!multiclient_limit_conversion_success)
-        multiclient_limit = 15;
-    bool max_char_conversion_success;
-    max_chars = config.value("maximum_characters", "256").toInt(&max_char_conversion_success);
-    if (!max_char_conversion_success)
-        max_chars = 256;
-    bool message_floodguard_conversion_success;
-    message_floodguard = config.value("message_floodguard", "250").toInt(&message_floodguard_conversion_success);
-    if (!message_floodguard_conversion_success)
-        message_floodguard = 30;
-    asset_url = config.value("asset_url","").toString().toUtf8();
-    if (!asset_url.isValid())
-        asset_url = NULL;
-    config.endGroup();
-
-    //Load dice values
-    config.beginGroup("Dice");
-    dice_value = config.value("value_type", "100").toInt();
-    max_dice = config.value("max_dice","100").toInt();
-    config.endGroup();
-
-    //Load discord webhook
-    config.beginGroup("Discord");
-    webhook_enabled = config.value("webhook_enabled", "false").toBool();
-    webhook_url = config.value("webhook_url", "Your webhook url here.").toString();
-    webhook_sendfile = config.value("webhook_sendfile", false).toBool();
-    webhook_content = config.value("webhook_content", "").toString();
-    config.endGroup();
-
-    //Load password configuration
-    config.beginGroup("Password");
-    password_requirements = config.value("password_requirements", "false").toBool();
-    if (password_requirements) {
-        bool password_minimum_length_conversion_success;
-        password_minimum_length = config.value("pass_min_length", "8").toInt(&password_minimum_length_conversion_success);
-        if (!password_minimum_length_conversion_success)
-            password_minimum_length = 8;
-        bool password_maximum_length_conversion_success;
-        password_maximum_length = config.value("pass_max_length", "16").toInt(&password_maximum_length_conversion_success);
-        if (!password_minimum_length_conversion_success)
-            password_maximum_length = 16;
-        password_require_mixed_case = config.value("pass_require_mix_case", "true").toBool();
-        password_require_numbers = config.value("pass_require_numbers", "true").toBool();
-        password_require_special_characters = config.value("pass_require_special", "true").toBool();
-        password_can_contain_username = config.value("pass_can_contain_username", "false").toBool();
-    }
-    config.endGroup();
-}
-
 void Server::allowMessage()
 {
     can_send_ic_messages = true;
diff --git a/core/src/testimony_recorder.cpp b/core/src/testimony_recorder.cpp
index 88bc074..45ad35d 100644
--- a/core/src/testimony_recorder.cpp
+++ b/core/src/testimony_recorder.cpp
@@ -25,7 +25,7 @@ void AOClient::addStatement(QStringList packet)
     int c_statement = area->statement();
     if (c_statement >= -1) {
         if (area->testimonyRecording() == AreaData::TestimonyRecording::RECORDING) {
-            if (c_statement <= server->maximum_statements) {
+            if (c_statement <= ConfigManager::maxStatements()) {
                 if (c_statement == -1)
                     packet[14] = "3";
                 else