diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 69de29a..e9c85fd 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -28,6 +28,12 @@ jobs:
           qmake
           make
           mv bin/config_sample bin/config
+
+      - name: Run tests
+        run: |
+            for test in bin_tests/*; do
+              LD_LIBRARY_PATH=./bin:$LD_LIBRARY_PATH ./$test
+            done;
           
       - name: Upload binary
         uses: actions/upload-artifact@v2
@@ -52,6 +58,13 @@ jobs:
           nmake
           windeployqt bin\akashi.exe --release --no-opengl-sw
           mv bin\config_sample bin\config
+
+      - name: Run tests
+        run: |
+          for test in bin_tests/*; do
+            LD_LIBRARY_PATH=./bin:$LD_LIBRARY_PATH ./$test
+          done;
+        shell: bash
           
       - name: Upload zip
         uses: actions/upload-artifact@v2
diff --git a/.gitignore b/.gitignore
index 554afe6..2c192b5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -73,4 +73,5 @@ Thumbs.db
 build/
 bin/akashi
 bin/config/
+bin_tests/
 doxygen/html
diff --git a/Doxyfile b/Doxyfile
index c68365e..886c80b 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -51,7 +51,7 @@ PROJECT_BRIEF          = "A C++ server for Attorney Online 2"
 # pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
 # the logo to the output directory.
 
-PROJECT_LOGO           = ./resource/icon/32.png
+PROJECT_LOGO           = ./akashi/resource/icon/32.png
 
 # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
 # into which the generated documentation will be written. If a relative path is
@@ -829,8 +829,9 @@ WARN_LOGFILE           =
 # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
 # Note: If this tag is empty the current directory is searched.
 
-INPUT                  = src/ \
-                         include/ \
+INPUT                  = akashi/ \
+                         core/ \
+                         tests/ \
                          README.md
 
 # This tag can be used to specify the character encoding of the source files
@@ -910,7 +911,7 @@ FILE_PATTERNS          = *.c \
 # be searched for input files as well.
 # The default value is: NO.
 
-RECURSIVE              = NO
+RECURSIVE              = YES
 
 # The EXCLUDE tag can be used to specify files and/or directories that should be
 # excluded from the INPUT source files. This way you can easily exclude a
diff --git a/README.md b/README.md
index eeae8e4..a3460d9 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# akashi <img src="https://github.com/AttorneyOnline/akashi/blob/master/resource/icon/256.png" width=30 height=30>
+# akashi <img src="https://github.com/AttorneyOnline/akashi/blob/master/akashi/resource/icon/256.png" width=30 height=30>
 A C++ server for Attorney Online 2
 
 # Build instructions
diff --git a/akashi.pro b/akashi.pro
index ceaa2f0..119f6c0 100644
--- a/akashi.pro
+++ b/akashi.pro
@@ -1,66 +1,13 @@
-QT       += network websockets core sql
-QT       -= gui
-TEMPLATE = app
+TEMPLATE = subdirs
 
-CONFIG += c++11 console
+SUBDIRS += \
+  core \
+  akashi \
+  tests
 
-# The following define makes your compiler emit warnings if you use
-# any Qt feature that has been marked deprecated (the exact warnings
-# depend on your compiler). Please consult the documentation of the
-# deprecated API in order to know how to port your code away from it.
-DEFINES += QT_DEPRECATED_WARNINGS
-
-# You can also make your code fail to compile if it uses deprecated APIs.
-# In order to do so, uncomment the following line.
-# You can also select to disable deprecated APIs only up to a certain version of Qt.
-#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
-
-QMAKE_CXXFLAGS_WARN_OFF -= -Wunused-parameter
-
-DESTDIR = $$PWD/bin
-OBJECTS_DIR = $$PWD/build
-MOC_DIR = $$PWD/build
-
-RC_ICONS = resource/icon/akashi.ico
-
-# Enable this to print network messages tothe console
-#DEFINES += NET_DEBUG
-
-# Enable this to skip all authentication checks
-#DEFINES += SKIP_AUTH
-
-SOURCES += src/advertiser.cpp \
-    src/aoclient.cpp \
-    src/aopacket.cpp \
-    src/area_data.cpp \
-    src/commands/area.cpp \
-    src/commands/authentication.cpp \
-    src/commands/casing.cpp \
-    src/commands/command_helper.cpp \
-    src/commands/messaging.cpp \
-    src/commands/moderation.cpp \
-    src/commands/music.cpp \
-    src/commands/roleplay.cpp \
-    src/config_manager.cpp \
-    src/db_manager.cpp \
-    src/discord.cpp \
-    src/logger.cpp \
-    src/main.cpp \
-    src/packets.cpp \
-    src/server.cpp \
-    src/testimony_recorder.cpp \
-    src/ws_client.cpp \
-    src/ws_proxy.cpp
-
-
-HEADERS += include/advertiser.h \
-    include/aoclient.h \
-    include/aopacket.h \
-    include/area_data.h \
-    include/config_manager.h \
-    include/db_manager.h \
-    include/discord.h \
-    include/logger.h \
-    include/server.h \
-    include/ws_client.h \
-    include/ws_proxy.h
+# Just like how "CONFIG += ordered" is considered harmful a practice for handling
+# internal dependecies, so is qmake considered harmful a tool for handling projects
+# as Qt expects you to handle them.
+#
+# Too bad.
+CONFIG += ordered
diff --git a/akashi/akashi.pro b/akashi/akashi.pro
new file mode 100644
index 0000000..fe91621
--- /dev/null
+++ b/akashi/akashi.pro
@@ -0,0 +1,33 @@
+QT       += network websockets core sql
+QT       -= gui
+TEMPLATE = app
+
+CONFIG += c++11 console
+
+# The following define makes your compiler emit warnings if you use
+# any Qt feature that has been marked deprecated (the exact warnings
+# depend on your compiler). Please consult the documentation of the
+# deprecated API in order to know how to port your code away from it.
+DEFINES += QT_DEPRECATED_WARNINGS
+
+# You can also make your code fail to compile if it uses deprecated APIs.
+# In order to do so, uncomment the following line.
+# You can also select to disable deprecated APIs only up to a certain version of Qt.
+#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
+
+QMAKE_CXXFLAGS_WARN_OFF -= -Wunused-parameter
+
+DESTDIR = $$PWD/../bin
+OBJECTS_DIR = $$PWD/../build
+MOC_DIR = $$PWD/../build
+
+RC_ICONS = resource/icon/akashi.ico
+
+SOURCES += main.cpp
+
+# Include the akashi library
+win32: LIBS += -L$$PWD/../bin/ -lcore
+else:unix: LIBS += -L$$PWD/../bin/ -lcore
+
+INCLUDEPATH += $$PWD/../core
+DEPENDPATH += $$PWD/../core
diff --git a/src/main.cpp b/akashi/main.cpp
similarity index 97%
rename from src/main.cpp
rename to akashi/main.cpp
index ab475d3..03e8d90 100644
--- a/src/main.cpp
+++ b/akashi/main.cpp
@@ -15,9 +15,9 @@
 //    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/>.        //
 //////////////////////////////////////////////////////////////////////////////////////
-#include "include/advertiser.h"
-#include "include/server.h"
-#include "include/config_manager.h"
+#include <include/advertiser.h>
+#include <include/server.h>
+#include <include/config_manager.h>
 
 #include <cstdlib>
 
diff --git a/resource/icon/16.png b/akashi/resource/icon/16.png
similarity index 100%
rename from resource/icon/16.png
rename to akashi/resource/icon/16.png
diff --git a/resource/icon/256.png b/akashi/resource/icon/256.png
similarity index 100%
rename from resource/icon/256.png
rename to akashi/resource/icon/256.png
diff --git a/resource/icon/32.png b/akashi/resource/icon/32.png
similarity index 100%
rename from resource/icon/32.png
rename to akashi/resource/icon/32.png
diff --git a/resource/icon/akashi.ico b/akashi/resource/icon/akashi.ico
similarity index 100%
rename from resource/icon/akashi.ico
rename to akashi/resource/icon/akashi.ico
diff --git a/core/core.pro b/core/core.pro
new file mode 100644
index 0000000..d46856e
--- /dev/null
+++ b/core/core.pro
@@ -0,0 +1,60 @@
+QT += network websockets core sql
+QT -= gui
+
+TEMPLATE = lib
+
+# Apparently, Windows needs a static config to make a dynamic library?
+# Look, I dunno.
+# Linux works just fine with `shared` only.
+CONFIG += shared static c++11
+
+# Needed so that Windows doesn't do `release/` and `debug/` subfolders
+# in the output directory.
+CONFIG -= \
+        copy_dir_files \
+        debug_and_release \
+        debug_and_release_target
+
+DESTDIR = $$PWD/../bin
+
+# You can make your code fail to compile if it uses deprecated APIs.
+# In order to do so, uncomment the following line.
+#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
+
+# Enable this to print network messages tothe console
+#DEFINES += NET_DEBUG
+
+SOURCES += \
+    src/advertiser.cpp \
+    src/aoclient.cpp \
+    src/aopacket.cpp \
+    src/area_data.cpp \
+    src/commands/area.cpp \
+    src/commands/authentication.cpp \
+    src/commands/casing.cpp \
+    src/commands/command_helper.cpp \
+    src/commands/messaging.cpp \
+    src/commands/moderation.cpp \
+    src/commands/music.cpp \
+    src/commands/roleplay.cpp \
+    src/config_manager.cpp \
+    src/db_manager.cpp \
+    src/discord.cpp \
+    src/logger.cpp \
+    src/packets.cpp \
+    src/server.cpp \
+    src/testimony_recorder.cpp \
+    src/ws_client.cpp \
+    src/ws_proxy.cpp
+
+HEADERS += include/advertiser.h \
+    include/aoclient.h \
+    include/aopacket.h \
+    include/area_data.h \
+    include/config_manager.h \
+    include/db_manager.h \
+    include/discord.h \
+    include/logger.h \
+    include/server.h \
+    include/ws_client.h \
+    include/ws_proxy.h
diff --git a/include/advertiser.h b/core/include/advertiser.h
similarity index 100%
rename from include/advertiser.h
rename to core/include/advertiser.h
diff --git a/include/aoclient.h b/core/include/aoclient.h
similarity index 99%
rename from include/aoclient.h
rename to core/include/aoclient.h
index 6fffd27..dcbe926 100644
--- a/include/aoclient.h
+++ b/core/include/aoclient.h
@@ -73,7 +73,7 @@ class AOClient : public QObject {
      *
      * @see #ipid
      */
-    QString getIpid();
+    QString getIpid() const;
 
     /**
      * @brief Calculates the client's IPID based on a hashed version of its IP.
@@ -1867,11 +1867,6 @@ class AOClient : public QObject {
      */
     QStringList updateStatement(QStringList packet);
 
-    /**
-     * @brief Called when area enum is set to PLAYBACK. Sends the IC-Message stored at the current statement.
-     * @return IC-Message stored in the QVector.
-     */
-    QStringList playTestimony();
     ///@}
 
     /**
diff --git a/include/aopacket.h b/core/include/aopacket.h
similarity index 100%
rename from include/aopacket.h
rename to core/include/aopacket.h
diff --git a/core/include/area_data.h b/core/include/area_data.h
new file mode 100644
index 0000000..f2d777d
--- /dev/null
+++ b/core/include/area_data.h
@@ -0,0 +1,988 @@
+//////////////////////////////////////////////////////////////////////////////////////
+//    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 AREA_DATA_H
+#define AREA_DATA_H
+
+#include "logger.h"
+#include "aopacket.h"
+
+#include <QMap>
+#include <QString>
+#include <QSettings>
+#include <QDebug>
+#include <QTimer>
+#include <QElapsedTimer>
+
+class Logger;
+
+/**
+ * @brief Represents an area on the server, a distinct "room" for people to chat in.
+ */
+class AreaData : public QObject {
+  Q_OBJECT
+  public:
+    /**
+     * @brief Constructor for the AreaData class.
+     *
+     * @param p_name The name of the area. This must be in the format of `"X:YYYYYY"`, where `X` is an integer,
+     * and `YYYYYY` is the actual name of the area.
+     * @param p_index The index of the area in the area list.
+     */
+    AreaData(QString p_name, int p_index);
+
+    /**
+     * @brief The data for evidence in the area.
+     */
+    struct Evidence {
+        QString name; //!< The name of the evidence, shown when hovered over clientside.
+        QString description; //!< The longer description of the evidence, when the user opens the evidence window.
+        QString image; //!< A path originating from `base/evidence/` that points to an image file.
+    };
+
+    /**
+     * @brief The status of an area.
+     *
+     * @details This is purely aesthetic, and serves no functional purpose from a gameplay perspective.
+     * It's only benefit is giving the users a rough idea as to what is going on in an area.
+     */
+    enum Status {
+      IDLE, //!< The area is currently not busy with anything, or the area is empty.
+      RP, //!< There is some (non-Ace Attorney-related) roleplay going on in the area.
+      CASING, //!< An Ace Attorney or Danganronpa-styled case is currently being held in the area.
+      LOOKING_FOR_PLAYERS, //!< Something is being planned in the area, but it needs more players.
+      RECESS, //!< The area is currently taking a break from casing, but will continue later.
+      GAMING //!< The users inside the area are playing some game outside of AO, and are using the area to communicate.
+    };
+
+    /// Exposes the metadata of the Status enum.
+    Q_ENUM(Status);
+
+    /**
+     * @brief Determines who may traverse and communicate in the area.
+     */
+    enum LockStatus {
+      FREE,
+      LOCKED,
+      SPECTATABLE
+    };
+
+    /**
+     * @var LockStatus FREE
+     * Anyone may enter the area, and there are no restrictions on communicating in-character.
+     */
+
+    /**
+     * @var LockStatus LOCKED
+     * Only invited clients may enter the area, but those who are invited are free to communicate in-character.
+     *
+     * When an area transitions from FREE to LOCKED, anyone present in the area
+     * at the time of the transition is considered invited.
+     */
+
+    /**
+     * @var LockStatus SPECTATABLE
+     * Anyone may enter the area, but only invited clients may communicate in-character.
+     *
+     * When an area transitions from FREE to SPECTATABLE, anyone present in the area
+     * at the time of the transition is considered invited.
+     */
+
+    /// Exposes the metadata of the LockStatus enum.
+    Q_ENUM(LockStatus);
+
+    /**
+     * @brief The level of "authorisation" needed to be able to modify, add, and remove evidence in the area.
+     */
+    enum EvidenceMod{
+        FFA,
+        MOD,
+        CM,
+        HIDDEN_CM
+    };
+
+    /**
+     * @var EvidenceMod FFA
+     * "Free-for-all" -- anyone can add, remove or modify evidence.
+     */
+
+    /**
+     * @var EvidenceMod MOD
+     * Only mods can add, remove or modify evidence.
+     */
+
+    /**
+     * @var EvidenceMod CM
+     * Only Case Makers and Mods can add, remove or modify evidence.
+     */
+
+    /**
+     * @var EvidenceMod HIDDEN_CM
+     * Only Case Makers and Mods can add, remove or modify evidence.
+     *
+     * CMs can also hide evidence from various sides by putting `<owner=XXX>` into the evidence's description,
+     * where `XXX` is either a position, of a list of positions separated by `,`.
+     */
+
+    /**
+     * @brief The five "states" the testimony recording system can have in an area.
+     */
+    enum TestimonyRecording{
+        STOPPED,
+        RECORDING,
+        UPDATE,
+        ADD,
+        PLAYBACK,
+    };
+
+    /**
+     * @var TestimonyRecording STOPPED
+     * The testimony recorder is inactive and no ic-messages can be played back.
+     * If messages are inside the buffer when its stopped, the messages will remain until the recorder is set to RECORDING
+     */
+
+    /**
+     * @var TestimonyRecording RECORDING
+     * The testimony recorder is active and any ic-message send is recorded for playback.
+     * It does not differentiate between positions, so any message is recorded. Further improvement?
+     * When the recorder is started, it will clear the buffer and will make the first message the title.
+     * To prevent accidental recording by not disabling the recorder, a configurable buffer size can be set in the config.
+     */
+
+    /**
+     * @var TestimonyRecording UPDATE
+     * The testimony recorder is active and replaces the current message at the index with the next ic-message
+     * Once the IC-Message is send the recorder will default back into playback mode to prevent accidental overwriting of messages.
+     */
+
+    /**
+     * @var TestimonyRecording ADD
+     * The testimony recorder is active and inserts the next message after the currently displayed ic-message
+     * This will increase the size by 1.
+     */
+
+    /**
+     * @var TestimonyRecording PLAYBACK
+     * The testimony recorder is inactive and ic-messages in the buffer will be played back.
+     */
+
+    /// Exposes the metadata of the TestimonyRecording enum.
+    Q_ENUM(TestimonyRecording);
+
+    /**
+     * @brief Determines how the testimony progressed after advancement was called in a direction
+     * (Either to next or previous statement).
+     */
+    enum class TestimonyProgress {
+        OK, //!< The expected statement was selected.
+        LOOPED, //!< The "next" statement would have been beyond the testimony's limits, so the first one was selected.
+        STAYED_AT_FIRST, //!< The "previous" statement would have been before the first, so the selection stayed at the first.
+    };
+
+    /**
+     * @brief Determines a side. Self-explanatory.
+     */
+    enum class Side {
+        DEFENCE, //!< Self-explanatory.
+        PROSECUTOR, //!< Self-explanatory.
+    };
+
+    /**
+     * @brief Contains a list of associations between `/status X` calls and what actual status they set the area to.
+     */
+    static const QMap<QString, AreaData::Status> map_statuses;
+
+    /**
+     * @brief A client in the area has left the area.
+     *
+     * @details This function counts down the playercount and removes the character from the list of taken characters.
+     *
+     * @param f_charId The character ID of the client who left. The default value is `-1`. If it is left at that,
+     * the area will not try to remove any character from the list of characters taken.
+     */
+    void clientLeftArea(int f_charId = -1);
+
+    /**
+     * @brief A client in the area joined recently.
+     *
+     * @details This function adds one to the playercount and adds the client's character to the list of taken characters.
+     *
+     * @param f_charId The character ID of the client who joined. The default value is `-1`. If it is left at that,
+     * the area will not add any character to the list of characters taken.
+     */
+    void clientJoinedArea(int f_charId = -1);
+
+    /**
+     * @brief Returns a copy of the list of owners of this area.
+     *
+     * @return The client IDs of the owners.
+     *
+     * @see #m_owners
+     */
+    QList<int> owners() const;
+
+    /**
+     * @brief Adds a client to the list of onwers for the area.
+     *
+     * @details Also automatically adds them to the list of invited people.
+     *
+     * @param f_clientId The client ID of the client who should be added as an owner.
+     *
+     * @see #m_owners
+     */
+    void addOwner(int f_clientId);
+
+    /**
+     * @brief Removes the target client from the list of owners.
+     *
+     * @param f_clientId The ID of the client to remove from the owners.
+     *
+     * @return True if because of this removal, an ARUP message must be sent out about the locks.
+     *
+     * @note This function *does not* imply that the client also left the area, only that they are no longer its owner.
+     * See clientLeftArea() for that.
+     *
+     * @see #m_owners
+     */
+    bool removeOwner(int f_clientId);
+
+    /**
+     * @brief Returns true if blankposting is allowed in the area.
+     *
+     * @return See short description.
+     *
+     * @see #m_blankpostingAllowed
+     */
+    bool blankpostingAllowed() const;
+
+    /**
+     * @brief Swaps between blankposting being allowed and forbidden in the area.
+     *
+     * @see #m_blankpostingAllowed
+     */
+    void toggleBlankposting();
+
+    /**
+     * @brief Returns if the area is protected.
+     *
+     * @return See short description.
+     *
+     * @see #m_isProtected
+     */
+    bool isProtected() const;
+
+    /**
+     * @brief Returns the lock status of the area.
+     *
+     * @return See short description.
+     *
+     * @see #m_locked
+     */
+    LockStatus lockStatus() const;
+
+    /**
+     * @brief Locks the area, setting it to LOCKED.
+     */
+    void lock();
+
+    /**
+     * @brief Unlocks the area, setting it to FREE.
+     */
+    void unlock();
+
+    /**
+     * @brief Sets the area to SPECTATABLE only.
+     */
+    void spectatable();
+
+    /**
+     * @brief Returns the amount of players in the area.
+     *
+     * @return See short description.
+     *
+     * @see #m_playerCount
+     */
+    int playerCount() const;
+
+    /**
+     * @brief Returns a copy of the list of timers in the area.
+     *
+     * @return See short description.
+     *
+     * @see m_timers
+     */
+    QList<QTimer *> timers() const;
+
+    /**
+     * @brief Returns the name of the area.
+     *
+     * @return See short description.
+     */
+    QString name() const;
+
+    /**
+     * @brief Returns the index of the area in the server's area list.
+     *
+     * @return See short description.
+     *
+     * @todo The area probably shouldn't know its own index.
+     */
+    int index() const;
+
+    /**
+     * @brief Returns a copy of the list of characters taken.
+     *
+     * @return A list of character IDs.
+     *
+     * @see #m_charactersTaken
+     */
+    QList<int> charactersTaken() const;
+
+    /**
+     * @brief Adjusts the composition of the list of characters taken, by optionally removing and optionally adding one.
+     *
+     * @details This function can be used to remove a character, to add one, or to replace one with another (like when a client
+     * changes character, hence the name).
+     *
+     * @param f_from A character ID to remove from the list of characters taken -- a character to switch away "from".
+     * Defaults to `-1`. If left at that, no character is removed.
+     * @param f_to A character ID to add to the list of characters taken -- a character to switch "to".
+     * Defaults to `-1`. If left at that, no character is added.
+     *
+     * @return True if and only if a character was successfully added to the list of characters taken.
+     * False if that character already existed in the list of characters taken, or if `f_to` was left at `-1`.
+     * `f_from` does not influence the return value in any way.
+     *
+     * @todo This is godawful, but I'm at my wits end. Needs a bigger refactor later down the line --
+     * the separation should help somewhat already, maybe.
+     */
+    bool changeCharacter(int f_from = -1, int f_to = -1);
+
+    /**
+     * @brief Returns a copy of the list of evidence in the area.
+     *
+     * @return See short description.
+     *
+     * @see #m_evidence
+     */
+    QList<Evidence> evidence() const;
+
+    /**
+     * @brief Changes the location of two pieces of evidence in the evidence list to one another's.
+     *
+     * @param f_eviId1, f_eviId2 The indices of the pieces of evidence to swap.
+     */
+    void swapEvidence(int f_eviId1, int f_eviId2);
+
+    /**
+     * @brief Appends a piece of evidence to the list of evidence.
+     *
+     * @param f_evi_r The evidence to append.
+     */
+    void appendEvidence(const Evidence& f_evi_r);
+
+    /**
+     * @brief Deletes a piece of evidence from the list of evidence.
+     *
+     * @param f_eviId The ID of the evidence to delete.
+     */
+    void deleteEvidence(int f_eviId);
+
+    /**
+     * @brief Replaces a piece of evidence at a given position with the one supplied.
+     *
+     * @param f_eviId The ID of the evidence to replace.
+     * @param f_newEvi_r The new piece of evidence that will replace the aforementioned one.
+     */
+    void replaceEvidence(int f_eviId, const Evidence& f_newEvi_r);
+
+    /**
+     * @brief Returns the status of the area.
+     *
+     * @return See short description.
+     */
+    Status status() const;
+
+    /**
+     * @brief Changes the area of the status to a new one.
+     *
+     * @param f_newStatus_r A string that a client would enter as an argument for the `/status` command.
+     *
+     * @return True if the entered status was valid, and the status changed, false otherwise.
+     *
+     * @see #map_statuses
+     */
+    bool changeStatus(const QString& f_newStatus_r);
+
+    /**
+     * @brief Returns a copy of the list of invited clients.
+     *
+     * @return A list of client IDs.
+     */
+    QList<int> invited() const;
+
+    /**
+     * @brief Invites a client to the area.
+     *
+     * @param f_clientId The client ID of the client to invite.
+     *
+     * @return True if the client was successfully invited. False if they were already in the list of invited people.
+     *
+     * @see LOCKED and SPECTATABLE for more details about being invited.
+     */
+    bool invite(int f_clientId);
+
+    /**
+     * @brief Removes a client from the list of people invited to the area.
+     *
+     * @param f_clientId The client ID of the client to uninvite.
+     *
+     * @return True if the client was successfully uninvited. False if they were never in the list of invited people.
+     */
+    bool uninvite(int f_clientId);
+
+    /**
+     * @brief Returns the name of the background for the area.
+     *
+     * @return See short description.
+     *
+     * @see #m_background
+     */
+    QString background() const;
+
+    /**
+     * @brief Returns if custom shownames are allowed in the area.
+     *
+     * @return See short description.
+     *
+     * @see #m_shownameAllowed
+     */
+    bool shownameAllowed() const;
+
+    /**
+     * @brief Returns if iniswapping is allowed in the area.
+     *
+     * @return See short description.
+     *
+     * @see #m_iniswapAllowed
+     */
+    bool iniswapAllowed() const;
+
+    /**
+     * @brief Toggles whether iniswap is allowed in the area.
+     *
+     * @see #m_iniswapAllowed
+     */
+    void toggleIniswap();
+
+    /**
+     * @brief Returns if backgrounds changing is locked in the area.
+     *
+     * @return See short description.
+     *
+     * @see #m_bgLocked
+     */
+    bool bgLocked() const;
+
+    /**
+     * @brief Toggles whether backgrounds changing is allowed in the area.
+     *
+     * @see #m_bgLocked
+     */
+    void toggleBgLock();
+
+    /**
+     * @brief Returns the document of the area.
+     *
+     * @return See short description.
+     *
+     * @see #m_document
+     */
+    QString document() const;
+
+    /**
+     * @brief Changes the document in the area.
+     *
+     * @param f_newDoc_r The new document.
+     *
+     * @see #m_document
+     */
+    void changeDoc(const QString& f_newDoc_r);
+
+    /**
+     * @brief Returns the value of the Confidence bar for the defence's side.
+     *
+     * @return The value of the Confidence bar in units of 10%.
+     *
+     * @see #m_defHP
+     */
+    int defHP() const;
+
+    /**
+     * @brief Returns the value of the Confidence bar for the prosecution's side.
+     *
+     * @return The value of the Confidence bar in units of 10%.
+     *
+     * @see #m_proHP
+     */
+    int proHP() const;
+
+    /**
+     * @brief Changes the value of the Confidence bar for the given side.
+     *
+     * @param f_side The side whose Confidence bar to change.
+     * @param f_newHP The absolute new value for the Confidence bar.
+     * Will be clamped between 0 and 10, inclusive on both sides.
+     */
+    void changeHP(AreaData::Side f_side, int f_newHP);
+
+    /**
+     * @brief Returns the music currently being played in the area.
+     *
+     * @return See short description.
+     *
+     * @see #m_currentMusic
+     */
+    QString currentMusic() const;
+
+    /**
+     * @brief Returns the showname of the client who played the music in the area.
+     *
+     * @return See short description.
+     *
+     * @see #m_musicPlayedBy
+     */
+    QString musicPlayerBy() const;
+
+    /**
+     * @brief Changes the music being played in the area.
+     *
+     * @param f_source_r The showname of the client who initiated the music change.
+     * @param f_newSong_r The name of the new song that is going to be played in the area.
+     */
+    void changeMusic(const QString& f_source_r, const QString& f_newSong_r);
+
+    /**
+     * @brief Returns the evidence mod in the area.
+     *
+     * @return See short description.
+     *
+     * @see #m_eviMod
+     */
+    EvidenceMod eviMod() const;
+
+    /**
+     * @brief Sets the evidence mod in the area.
+     *
+     * @param f_eviMod_r The new evidence mod.
+     */
+    void setEviMod(const EvidenceMod &f_eviMod_r);
+
+    /**
+     * @brief Adds a notecard to the area.
+     *
+     * @param f_owner_r The showname of the character to whom the notecard should be associated to.
+     * @param f_notecard_r The contents of the notecard.
+     *
+     * @return True if the notecard didn't replace a previous one, false if it did.
+     */
+    bool addNotecard(const QString& f_owner_r, const QString& f_notecard_r);
+
+    /**
+     * @brief Returns the list of notecards recorded in the area.
+     *
+     * @return Returns a QStringList with the format of `name: message`, with newlines at the end
+     * of each message.
+     */
+    QStringList getNotecards();
+
+    /**
+     * @brief Returns the state of the testimony recording process in the area.
+     *
+     * @return See short description.
+     */
+    TestimonyRecording testimonyRecording() const;
+
+    /**
+     * @brief Sets the state of the testimony recording process in the area.
+     *
+     * @param f_testimonyRecording_r The new state for testimony recording.
+     */
+    void setTestimonyRecording(const TestimonyRecording &f_testimonyRecording_r);
+
+    /**
+     * @brief Sets the testimony to the first moment, and the state to TestimonyRecording::PLAYBACK.
+     */
+    void restartTestimony();
+
+    /**
+     * @brief Clears the testimony, sets the state to TestimonyRecording::STOPPED, and the statement
+     * index to -1.
+     */
+    void clearTestimony();
+
+    /**
+     * @brief Returns the contents of the testimony.
+     *
+     * @return A const reference to the testimony.
+     *
+     * @note Unlike most other getters, this one returns a reference, as it is expected to be used frequently.
+     */
+    const QVector<QStringList>& testimony() const;
+
+    /**
+     * @brief Returns the index of the currently examined statement in the testimony.
+     *
+     * @return See short description.
+     */
+    int statement() const;
+
+    /**
+     * @brief Adds a new statement to the end of the testimony, and increases the statement index by one.
+     *
+     * @param f_newStatement_r The IC message packet to append to the testimony vector.
+     */
+    void recordStatement(const QStringList& f_newStatement_r);
+
+    /**
+     * @brief Adds a statement into the testimony to a given position.
+     *
+     * @param f_position The index to insert the statement to.
+     * @param f_newStatement_r The IC message packet to insert.
+     */
+    void addStatement(int f_position, const QStringList& f_newStatement_r);
+
+    /**
+     * @brief Replaces an already existing statement in the testimony in a given position with a new one.
+     *
+     * @param f_position The index of the statement to replace.
+     * @param f_newStatement_r The IC message packet to insert in the old one's stead.
+     */
+    void replaceStatement(int f_position, const QStringList& f_newStatement_r);
+
+    /**
+     * @brief Removes a statement from the testimony at a given position, and moves the statement index one backward.
+     *
+     * @param f_position The index to remove the statement from.
+     */
+    void removeStatement(int f_position);
+
+    /**
+     * @brief Jumps the testimony playback to the given index.
+     *
+     * @details When advancing forward, if the playback would go past the last statement,
+     * it instead returns the first statement.
+     * When advancing backward, if the playback would go before the first statement, it
+     * instead returns the first statement.
+     *
+     * @param f_position The index to jump to.
+     *
+     * @return A pair of values:
+     * * First, a `QStringList` that is the packet of the statement that was advanced to.
+     * * Then, a `TestimonyProgress` value that describes how the advancement happened.
+     */
+    std::pair<QStringList, AreaData::TestimonyProgress> jumpToStatement(int f_position);
+
+    /**
+     * @brief Returns a copy of the judgelog in the area.
+     *
+     * @return See short description.
+     *
+     * @see #m_judgelog
+     */
+    QStringList judgelog() const;
+
+    /**
+     * @brief Appends a new line to the judgelog.
+     *
+     * @details There is a hard limit of 10 lines in the judgelog -- if a new one is inserted
+     * beyond that, the oldest one is cleared.
+     *
+     * @param f_newLog_r The new line to append to the judgelog.
+     */
+    void appendJudgelog(const QString& f_newLog_r);
+
+    /**
+     * @brief Returns the last IC message sent in the area.
+     *
+     * @return See short description.
+     */
+    const QStringList& lastICMessage() const;
+
+    /**
+     * @brief Updates the last IC message sent in the area.
+     *
+     * @param f_lastMessage_r The new last IC message.
+     */
+    void updateLastICMessage(const QStringList& f_lastMessage_r);
+
+    /**
+     * @brief Returns whether ~~non-interrupting~~ immediate messages are forced in the area.
+     *
+     * @return See short description.
+     *
+     * @see #m_forceImmediate
+     */
+    bool forceImmediate() const;
+
+    /**
+     * @brief Toggles whether immediate messages are forced in the area.
+     */
+    void toggleImmediate();
+
+    /**
+     * @brief Returns whether changing music is allowed in the area.
+     *
+     * @return See short description.
+     *
+     * @see #m_toggleMusic
+     */
+    bool isMusicAllowed() const;
+
+    /**
+     * @brief Toggles whether changing music is allowed in the area.
+     */
+    void toggleMusic();
+
+    /**
+     * @brief Logs a packet in the area's logger.
+     *
+     * @details Logs IC, OOC and modcall packets. Anything else is discarded.
+     *
+     * This function is a convenience function over the Logger's log functions.
+     *
+     * If you wish to log a login attempt, use logLogin() instead.
+     *
+     * @param f_clientName_r The showname of the packet sender's character.
+     * @param f_clientIpid_r The IPID of the packet sender.
+     * @param f_packet_r The packet that was sent.
+     */
+    void log(const QString& f_clientName_r, const QString& f_clientIpid_r, const AOPacket& f_packet_r) const;
+
+    /**
+     * @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.
+     *
+     * If there was a way to login *without* the command, only this would be logged.
+     *
+     * @param f_clientName_r The showname of the login attempt sender's character.
+     * @param f_clientIpid_r The IPID of the client attempting login.
+     * @param f_success The outcome of the login attempt.
+     * @param f_modname_r The moderator name the client attempted to log in with.
+     */
+    void logLogin(const QString &f_clientName_r, const QString &f_clientIpid_r, bool f_success, const QString& f_modname_r) const;
+
+    /**
+     * @brief Convenience function over Logger::flush().
+     */
+    void flushLogs() const;
+
+    /**
+     * @brief Returns a copy of the underlying logger's buffer.
+     *
+     * @return See short description.
+     */
+    QQueue<QString> buffer() const;
+
+private:
+    /**
+     * @brief The list of timers available in the area.
+     */
+    QList<QTimer*> m_timers;
+
+    /**
+     * @brief The user-facing and internal name of the area.
+     */
+    QString m_name;
+
+    /**
+     * @brief The index of the area in the server's area list.
+     */
+    int m_index;
+
+    /**
+     * @brief A list of the character IDs of all characters taken.
+     */
+    QList<int> m_charactersTaken;
+
+    /**
+     * @brief A list of Evidence currently available in the area's court record.
+     *
+     * @details This contains *all* evidence, not just the ones a given side can see.
+     *
+     * @see HIDDEN_CM
+     */
+    QList<Evidence> m_evidence;
+
+    /**
+     * @brief The amount of clients inside the area.
+     */
+    int m_playerCount;
+
+    /**
+     * @brief The status of the area.
+     *
+     * @see Status
+     */
+    Status m_status;
+
+    /**
+     * @brief The IDs of all the owners (or Case Makers / CMs) of the area.
+     */
+    QList<int> m_owners;
+
+    /**
+     * @brief The list of clients invited to the area.
+     *
+     * @see LOCKED and SPECTATABLE for the benefits of being invited.
+     */
+    QList<int> m_invited;
+
+    /**
+     * @brief The status of the area's accessibility to clients.
+     *
+     * @see LockStatus
+     */
+    LockStatus m_locked;
+
+    /**
+     * @brief The background of the area.
+     *
+     * @details Represents a directory's name in `base/background/` clientside.
+     */
+    QString m_background;
+
+    /**
+     * @brief If true, nobody may become the CM of this area.
+     */
+    bool m_isProtected;
+
+    /**
+     * @brief If true, clients are allowed to put on "shownames", custom names
+     * in place of their character's normally displayed name.
+     */
+    bool m_shownameAllowed;
+
+    /**
+     * @brief If true, clients are allowed to use the cursed art of iniswapping in the area.
+     */
+    bool m_iniswapAllowed;
+
+    /**
+     * @brief If true, clients are allowed to send empty IC messages
+     */
+    bool m_blankpostingAllowed;
+
+    /**
+     * @brief If true, the background of the area cannot be changed except by a moderator.
+     */
+    bool m_bgLocked;
+
+    /**
+     * @brief The hyperlink to the document of the area.
+     *
+     * @details Documents are generally used for cases or roleplays, where they contain the related game's
+     * rules. #document can also be something like "None" if there is no case or roleplay being run.
+     */
+    QString m_document;
+
+    /**
+     * @brief The Confidence Gauge's value for the Defence side.
+     *
+     * @details Unit is 10%, and the values range from 0 (= 0%) to 10 (= 100%).
+     */
+    int m_defHP;
+
+    /**
+     * @brief The Confidence Gauge's value for the Prosecutor side.
+     *
+     * @copydetails #m_defHP
+     */
+    int m_proHP;
+
+    /**
+     * @brief The title of the music currently being played in the area.
+     *
+     * @details Title is a path to the music file, with the starting point on
+     * `base/sounds/music/` clientside, with file extension.
+     */
+    QString m_currentMusic;
+
+    /**
+     * @brief The name of the client (or client's character) that started the currently playing music.
+     */
+    QString m_musicPlayedBy;
+
+    /**
+     * @brief A pointer to a Logger, used to send requests to log data.
+     */
+    Logger* m_logger;
+
+    /**
+     * @brief The evidence mod of the area.
+     *
+     * @see EvidenceMod
+     */
+    EvidenceMod m_eviMod;
+
+    /**
+     * @brief The list of notecards in the area.
+     *
+     * @details Notecards are plain text messages that can be left secretly in areas.
+     * They can later be revealed all at once with a command call.
+     *
+     * Notecards have a `name: message` format, with the `name` being the recorder client's character's
+     * charname at the time of recording, and `message` being a custom plain text message.
+     */
+    QMap<QString, QString> m_notecards;
+
+    /**
+     * @brief The state of the testimony recording / playback in the area.
+     */
+    TestimonyRecording m_testimonyRecording;
+
+
+    QVector<QStringList> m_testimony; //!< Vector of all statements saved. Index 0 is always the title of the testimony.
+    int m_statement; //!< Keeps track of the currently played statement.
+
+    /**
+    * @brief The judgelog of an area.
+    *
+    * @details This list contains up to 10 recorded packets of the most recent judge actions (WT/CE or penalty updates) in an area.
+    */
+    QStringList m_judgelog;
+
+    /**
+     * @brief The last IC packet sent in an area.
+     */
+    QStringList m_lastICMessage;
+
+
+    /**
+     * @brief Whether or not to force immediate text processing in this area.
+     */
+    bool m_forceImmediate;
+
+    /**
+     * @brief Whether or not music is allowed in this area. If false, only CMs can change the music.
+     */
+    bool m_toggleMusic;
+};
+
+#endif // AREA_DATA_H
diff --git a/include/config_manager.h b/core/include/config_manager.h
similarity index 100%
rename from include/config_manager.h
rename to core/include/config_manager.h
diff --git a/include/db_manager.h b/core/include/db_manager.h
similarity index 100%
rename from include/db_manager.h
rename to core/include/db_manager.h
diff --git a/include/discord.h b/core/include/discord.h
similarity index 100%
rename from include/discord.h
rename to core/include/discord.h
diff --git a/include/logger.h b/core/include/logger.h
similarity index 50%
rename from include/logger.h
rename to core/include/logger.h
index 046dfe6..a35fde1 100644
--- a/include/logger.h
+++ b/core/include/logger.h
@@ -18,19 +18,12 @@
 #ifndef LOGGER_H
 #define LOGGER_H
 
-#include "include/aoclient.h"
-#include "include/aopacket.h"
-#include "include/area_data.h"
-
 #include <QFile>
 #include <QDebug>
 #include <QString>
 #include <QQueue>
 #include <QDateTime>
 
-class AOClient;
-class AreaData;
-
 /**
  * @brief A class associated with an AreaData class to log various events happening inside the latter.
  */
@@ -40,34 +33,44 @@ public:
     /**
      * @brief Constructs a Logger instance.
      *
-     * @param p_max_length The maximum amount of entries the Logger can store at once.
-     * @param p_area The area associated with the Logger from which it should log entries.
+     * @param f_max_length The maximum amount of entries the Logger can store at once.
      */
-    Logger(int p_max_length, AreaData* p_area) : max_length(p_max_length), area(p_area) {};
+    Logger(QString f_area_name, int f_max_length, const QString& f_logType_r) :
+        m_areaName(f_area_name), m_maxLength(f_max_length), m_logType(f_logType_r) {};
 
+    /**
+     *@brief Returns a copy of the logger's buffer.
+     */
+    QQueue<QString> buffer() const;
+
+public slots:
     /**
      * @brief Logs an IC message.
      *
-     * @param client The client who sent the IC message.
-     * @param packet The IC packet itself, used to grab the text of the IC message.
+     * @param f_charName_r The character name of the client who sent the IC message.
+     * @param f_ipid_r The IPID of the aforementioned client.
+     * @param f_message_r The text of the IC message.
      */
-    void logIC(AOClient* client, AOPacket* packet);
+    void logIC(const QString& f_charName_r, const QString& f_ipid_r, const QString& f_message_r);
 
     /**
      * @brief Logs an OOC message.
      *
-     * @param client The client who sent the OOC message.
-     * @param packet The OOC packet itself, used to grab the text of the OOC message.
+     * @param f_areaName_r The name of the area where the event happened.
+     * @param f_charName_r The character name of the client who sent the OOC message.
+     * @param f_ipid_r The IPID of the aforementioned client.
+     * @param f_message_r The text of the OOC message.
      */
-    void logOOC(AOClient* client, AOPacket* packet);
+    void logOOC(const QString& f_charName_r, const QString& f_ipid_r, const QString& f_message_r);
 
     /**
      * @brief Logs a mod call message.
      *
-     * @param client The client who sent the mod call.
-     * @param packet The ZZ packet itself, used to grab the reason field of the modcall.
+     * @param f_charName_r The character name of the client who sent the mod call.
+     * @param f_ipid_r The IPID of the aforementioned client.
+     * @param f_modcallReason_r The reason for the modcall.
      */
-    void logModcall(AOClient* client, AOPacket* packet);
+    void logModcall(const QString& f_charName_r, const QString& f_ipid_r, const QString& f_modcallReason_r);
 
     /**
      * @brief Logs a command called in OOC.
@@ -75,45 +78,32 @@ public:
      * @details If the command is not one of any of the 'special' ones, it defaults to logOOC().
      * The only thing that makes a command 'special' if it is handled differently in here.
      *
-     * @param client The client who sent the command.
-     * @param packet The OOC packet. Passed to logOOC() if the command is not 'special' (see details).
-     * @param cmd The command called in the OOC -- this is the first word after the `/` character.
-     * @param args The arguments interpreted for the command, every word separated by a whitespace.
+     * @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).
      */
-    void logCmd(AOClient* client, AOPacket* packet, QString cmd, QStringList args);
+    void logCmd(const QString& f_charName_r, const QString& f_ipid_r, const QString& f_oocMessage_r);
 
     /**
      * @brief Logs a login attempt.
      *
-     * @param client The client that attempted to login.
+     * @param f_charName_r The character name of the client that attempted to login.
+     * @param f_ipid_r The IPID of the aforementioned client.
      * @param success True if the client successfully authenticated as a mod.
-     * @param modname If the client logged in with a modname, then this is it. Otherwise, it's `"moderator"`.
-     *
-     * @note Why does this exist? logCmd() already does this in part.
+     * @param f_modname_r If the client logged in with a modname, then this is it. Otherwise, it's `"moderator"`.
      */
-    void logLogin(AOClient* client, bool success, QString modname);
+    void logLogin(const QString& f_charName_r, const QString& f_ipid_r, bool success, const QString& f_modname_r);
 
     /**
      * @brief Appends the contents of #buffer into `config/server.log`, emptying the former.
      */
     void flush();
 
-    /**
-     *@brief Returns the current area buffer
-     */
-    QQueue<QString> getBuffer();
-
 private:
     /**
-     * @brief Convenience function to format entries to the acceptable standard for logging.
-     *
-     * @param client The client who 'caused' the source event for the entry to happen.
-     * @param type The type of entry that is being built, something that uniquely identifies entries of similar being.
-     * @param message Any additional information related to the entry.
-     *
-     * @return A formatted string representation of the entry.
+     * @brief Contains entries that have not yet been flushed out into a log file.
      */
-    QString buildEntry(AOClient* client, QString type, QString message);
+    QQueue<QString> m_buffer;
 
     /**
      * @brief Convenience function to add an entry to #buffer.
@@ -121,28 +111,28 @@ private:
      * @details If the buffer's size is equal to #max_length, the first entry in the queue is removed,
      * and the newest entry is added to the end.
      *
-     * @param entry The string representation of the entry to add.
-     *
-     * @pre You would probably call buildEntry() to format the entry before adding it to the buffer.
+     * @param f_charName_r The character name of the client who 'caused' the source event for the entry to happen.
+     * @param f_ipid_r The IPID of the aforementioned client.
+     * @param f_type_r The type of entry that is being built, something that uniquely identifies entries of similar being.
+     * @param f_message_r Any additional information related to the entry.
      */
-    void addEntry(QString entry);
+    void addEntry(const QString& f_charName_r, const QString& f_ipid_r,
+                  const QString& f_type_r,     const QString& f_message_r);
 
     /**
      * @brief The max amount of entries that may be contained in #buffer.
      */
-    int max_length;
+    int m_maxLength;
+
+    QString m_areaName;
 
     /**
-     * @brief Contains entries that have not yet been flushed out into a log file.
-     */
-    QQueue<QString> buffer;
-
-    /**
-     * @brief A pointer to the area this logger is associated with.
+     * @brief Determines what kind of logging happens, `"full"` or `"modcall"`.
      *
-     * @details Used for logging in what area did a given packet event happen.
+     * @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.
      */
-    AreaData* area;
+    QString m_logType;
 };
 
 #endif // LOGGER_H
diff --git a/include/server.h b/core/include/server.h
similarity index 100%
rename from include/server.h
rename to core/include/server.h
diff --git a/include/ws_client.h b/core/include/ws_client.h
similarity index 100%
rename from include/ws_client.h
rename to core/include/ws_client.h
diff --git a/include/ws_proxy.h b/core/include/ws_proxy.h
similarity index 100%
rename from include/ws_proxy.h
rename to core/include/ws_proxy.h
diff --git a/src/advertiser.cpp b/core/src/advertiser.cpp
similarity index 100%
rename from src/advertiser.cpp
rename to core/src/advertiser.cpp
diff --git a/src/aoclient.cpp b/core/src/aoclient.cpp
similarity index 83%
rename from src/aoclient.cpp
rename to core/src/aoclient.cpp
index a8906a8..5470355 100644
--- a/src/aoclient.cpp
+++ b/core/src/aoclient.cpp
@@ -49,23 +49,21 @@ void AOClient::clientDisconnected()
 #endif
     if (joined) {
         server->player_count--;
-        server->areas[current_area]->player_count--;
+        server->areas[current_area]->clientLeftArea(server->getCharID(current_char));
         arup(ARUPType::PLAYER_COUNT, true);
     }
+
     if (current_char != "") {
-        server->areas[current_area]->characters_taken.removeAll(server->getCharID(current_char));
         server->updateCharsTaken(server->areas[current_area]);
     }
-    bool update_locks;
+
+    bool l_updateLocks = false;
+
     for (AreaData* area : server->areas) {
-        area->owners.removeAll(id);
-        area->invited.removeAll(id);
-        if (area->owners.isEmpty() && area->locked != AreaData::FREE) {
-            area->locked = AreaData::FREE;
-            update_locks = true;
-        }
+        l_updateLocks = l_updateLocks || area->removeOwner(id);
     }
-    if (update_locks)
+
+    if (l_updateLocks)
         arup(ARUPType::LOCKED, true);
     arup(ARUPType::CM, true);
 }
@@ -109,34 +107,34 @@ void AOClient::changeArea(int new_area)
         sendServerMessage("You are already in area " + server->area_names[current_area]);
         return;
     }
-    if (server->areas[new_area]->locked == AreaData::LockStatus::LOCKED && !server->areas[new_area]->invited.contains(id) && !checkAuth(ACLFlags.value("BYPASS_LOCKS"))) {
+    if (server->areas[new_area]->lockStatus() == AreaData::LockStatus::LOCKED && !server->areas[new_area]->invited().contains(id) && !checkAuth(ACLFlags.value("BYPASS_LOCKS"))) {
         sendServerMessage("Area " + server->area_names[new_area] + " is locked.");
         return;
     }
 
     if (current_char != "") {
-        server->areas[current_area]->characters_taken.removeAll(server->getCharID(current_char));
+        server->areas[current_area]->charactersTaken().removeAll(server->getCharID(current_char));
         server->updateCharsTaken(server->areas[current_area]);
     }
-    server->areas[new_area]->player_count++;
-    server->areas[current_area]->player_count--;
+    server->areas[new_area]->clientJoinedArea(char_id);
+    server->areas[current_area]->clientLeftArea(char_id);
     current_area = new_area;
     arup(ARUPType::PLAYER_COUNT, true);
     sendEvidenceList(server->areas[new_area]);
-    sendPacket("HP", {"1", QString::number(server->areas[new_area]->def_hp)});
-    sendPacket("HP", {"2", QString::number(server->areas[new_area]->pro_hp)});
-    sendPacket("BN", {server->areas[new_area]->background});
-    if (server->areas[current_area]->characters_taken.contains(server->getCharID(current_char))) {
+    sendPacket("HP", {"1", QString::number(server->areas[new_area]->defHP())});
+    sendPacket("HP", {"2", QString::number(server->areas[new_area]->proHP())});
+    sendPacket("BN", {server->areas[new_area]->background()});
+    if (server->areas[current_area]->charactersTaken().contains(server->getCharID(current_char))) {
         server->updateCharsTaken(server->areas[current_area]);
         current_char = "";
         sendPacket("DONE");
     }
     else {
-        server->areas[current_area]->characters_taken.append(server->getCharID(current_char));
+        server->areas[current_area]->charactersTaken().append(server->getCharID(current_char));
         server->updateCharsTaken(server->areas[current_area]);
     }
-    for (QTimer* timer : server->areas[current_area]->timers) {
-        int timer_id = server->areas[current_area]->timers.indexOf(timer) + 1;
+    for (QTimer* timer : server->areas[current_area]->timers()) {
+        int timer_id = server->areas[current_area]->timers().indexOf(timer) + 1;
         if (timer->isActive()) {
             sendPacket("TI", {QString::number(timer_id), "2"});
             sendPacket("TI", {QString::number(timer_id), "0", QString::number(QTime(0,0).msecsTo(QTime(0,0).addMSecs(timer->remainingTime())))});
@@ -146,7 +144,7 @@ void AOClient::changeArea(int new_area)
         }
     }
     sendServerMessage("You moved to area " + server->area_names[current_area]);
-    if (server->areas[current_area]->locked == AreaData::LockStatus::SPECTATABLE)
+    if (server->areas[current_area]->lockStatus() == AreaData::LockStatus::SPECTATABLE)
         sendServerMessage("Area " + server->area_names[current_area] + " is spectate-only; to chat IC you will need to be invited by the CM.");
 }
 
@@ -161,22 +159,14 @@ bool AOClient::changeCharacter(int char_id)
         return false;
     }
     
-    if (current_char != "") {
-        area->characters_taken.removeAll(server->getCharID(current_char));
-    }
+    bool l_successfulChange = area->changeCharacter(server->getCharID(current_char), char_id);
 
-    if (char_id >= 0) {
+    current_char = "";
+
+    if (l_successfulChange) {
         QString char_selected = server->characters[char_id];
-        bool taken = area->characters_taken.contains(char_id);
-        if (taken || char_selected == "")
-            return false;
-
-        area->characters_taken.append(char_id);
         current_char = char_selected;
     }
-    else {
-        current_char = "";
-    }
 
     pos = "";
 
@@ -216,20 +206,20 @@ void AOClient::arup(ARUPType type, bool broadcast)
     for (AreaData* area : server->areas) {
         switch(type) {
             case ARUPType::PLAYER_COUNT: {
-                arup_data.append(QString::number(area->player_count));
+                arup_data.append(QString::number(area->playerCount()));
                 break;
             }
             case ARUPType::STATUS: {
-                QString area_status = QVariant::fromValue(area->status).toString().replace("_", "-"); // LOOKING_FOR_PLAYERS to LOOKING-FOR-PLAYERS
+                QString area_status = QVariant::fromValue(area->status()).toString().replace("_", "-"); // LOOKING_FOR_PLAYERS to LOOKING-FOR-PLAYERS
                 arup_data.append(area_status);
                 break;
             }
             case ARUPType::CM: {
-                if (area->owners.isEmpty())
+                if (area->owners().isEmpty())
                     arup_data.append("FREE");
                 else {
                     QStringList area_owners;
-                    for (int owner_id : area->owners) {
+                    for (int owner_id : area->owners()) {
                         AOClient* owner = server->getClientByID(owner_id);
                         area_owners.append("[" + QString::number(owner->id) + "] " + owner->current_char);
                     }
@@ -238,7 +228,7 @@ void AOClient::arup(ARUPType type, bool broadcast)
                 break;
             }
             case ARUPType::LOCKED: {
-                QString lock_status = QVariant::fromValue(area->locked).toString();
+                QString lock_status = QVariant::fromValue(area->lockStatus()).toString();
                 arup_data.append(lock_status);
                 break;
             }
@@ -321,7 +311,7 @@ bool AOClient::checkAuth(unsigned long long acl_mask)
     if (acl_mask != ACLFlags.value("NONE")) {
         if (acl_mask == ACLFlags.value("CM")) {
             AreaData* area = server->areas[current_area];
-            if (area->owners.contains(id))
+            if (area->owners().contains(id))
                 return true;
         }
         else if (!authenticated) {
@@ -339,7 +329,7 @@ bool AOClient::checkAuth(unsigned long long acl_mask)
 }
 
 
-QString AOClient::getIpid() { return ipid; }
+QString AOClient::getIpid() const { return ipid; }
 
 Server* AOClient::getServer() { return server; }
 
diff --git a/src/aopacket.cpp b/core/src/aopacket.cpp
similarity index 100%
rename from src/aopacket.cpp
rename to core/src/aopacket.cpp
diff --git a/core/src/area_data.cpp b/core/src/area_data.cpp
new file mode 100644
index 0000000..968b729
--- /dev/null
+++ b/core/src/area_data.cpp
@@ -0,0 +1,533 @@
+//////////////////////////////////////////////////////////////////////////////////////
+//    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/>.        //
+//////////////////////////////////////////////////////////////////////////////////////
+
+#include <algorithm>
+
+#include "include/area_data.h"
+
+AreaData::AreaData(QString p_name, int p_index) :
+    m_index(p_index),
+    m_playerCount(0),
+    m_status(IDLE),
+    m_locked(FREE),
+    m_document("No document."),
+    m_defHP(10),
+    m_proHP(10),
+    m_statement(0),
+    m_judgelog(),
+    m_lastICMessage()
+{
+    QStringList name_split = p_name.split(":");
+    name_split.removeFirst();
+    m_name = name_split.join(":");
+    QSettings areas_ini("config/areas.ini", QSettings::IniFormat);
+    areas_ini.setIniCodec("UTF-8");
+    areas_ini.beginGroup(p_name);
+    m_background = areas_ini.value("background", "gs4").toString();
+    m_isProtected = areas_ini.value("protected_area", "false").toBool();
+    m_iniswapAllowed = areas_ini.value("iniswap_allowed", "true").toBool();
+    m_bgLocked = areas_ini.value("bg_locked", "false").toBool();
+    QString configured_evi_mod = areas_ini.value("evidence_mod", "FFA").toString().toLower();
+    m_blankpostingAllowed = areas_ini.value("blankposting_allowed","true").toBool();
+    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();
+    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();
+    if (log_size == 0)
+        log_size = 500;
+    m_logger = new Logger(m_name, log_size, l_logType);
+    QTimer* timer1 = new QTimer();
+    m_timers.append(timer1);
+    QTimer* timer2 = new QTimer();
+    m_timers.append(timer2);
+    QTimer* timer3 = new QTimer();
+    m_timers.append(timer3);
+    QTimer* timer4 = new QTimer();
+    m_timers.append(timer4);
+
+    if (configured_evi_mod == "cm")
+        m_eviMod = EvidenceMod::CM;
+    else if (configured_evi_mod == "mod")
+        m_eviMod = EvidenceMod::MOD;
+    else if (configured_evi_mod == "hiddencm")
+        m_eviMod = EvidenceMod::HIDDEN_CM;
+    else
+        m_eviMod = EvidenceMod::FFA;
+}
+
+const QMap<QString, AreaData::Status> AreaData::map_statuses = {
+    {"idle",                    AreaData::Status::IDLE                },
+    {"rp",                      AreaData::Status::RP                  },
+    {"casing",                  AreaData::Status::CASING              },
+    {"lfp",                     AreaData::Status::LOOKING_FOR_PLAYERS },
+    {"looking-for-players",     AreaData::Status::LOOKING_FOR_PLAYERS },
+    {"recess",                  AreaData::Status::RECESS              },
+    {"gaming",                  AreaData::Status::GAMING              },
+};
+
+void AreaData::clientLeftArea(int f_charId)
+{
+    --m_playerCount;
+
+    if (f_charId != -1) {
+        m_charactersTaken.removeAll(f_charId);
+    }
+}
+
+void AreaData::clientJoinedArea(int f_charId)
+{
+    ++m_playerCount;
+
+    if (f_charId != -1) {
+        m_charactersTaken.append(f_charId);
+    }
+}
+
+QList<int> AreaData::owners() const
+{
+    return m_owners;
+}
+
+void AreaData::addOwner(int f_clientId)
+{
+    m_owners.append(f_clientId);
+    m_invited.append(f_clientId);
+}
+
+bool AreaData::removeOwner(int f_clientId)
+{
+    m_owners.removeAll(f_clientId);
+    m_invited.removeAll(f_clientId);
+
+    if (m_owners.isEmpty() && m_locked != AreaData::FREE) {
+        m_locked = AreaData::FREE;
+        return true;
+    }
+
+    return false;
+}
+
+bool AreaData::blankpostingAllowed() const
+{
+    return m_blankpostingAllowed;
+}
+
+void AreaData::toggleBlankposting()
+{
+    m_blankpostingAllowed = !m_blankpostingAllowed;
+}
+
+bool AreaData::isProtected() const
+{
+    return m_isProtected;
+}
+
+AreaData::LockStatus AreaData::lockStatus() const
+{
+    return m_locked;
+}
+
+void AreaData::lock()
+{
+    m_locked = LockStatus::LOCKED;
+}
+
+void AreaData::unlock()
+{
+    m_locked = LockStatus::FREE;
+}
+
+void AreaData::spectatable()
+{
+    m_locked = LockStatus::SPECTATABLE;
+}
+
+bool AreaData::invite(int f_clientId)
+{
+    if (m_invited.contains(f_clientId)) {
+        return false;
+    }
+
+    m_invited.append(f_clientId);
+    return true;
+}
+
+bool AreaData::uninvite(int f_clientId)
+{
+    if (m_invited.contains(f_clientId)) {
+        return false;
+    }
+
+    m_invited.removeAll(f_clientId);
+    return true;
+}
+
+int AreaData::playerCount() const
+{
+    return m_playerCount;
+}
+
+QList<QTimer *> AreaData::timers() const
+{
+    return m_timers;
+}
+
+QString AreaData::name() const
+{
+    return m_name;
+}
+
+int AreaData::index() const
+{
+    return m_index;
+}
+
+QList<int> AreaData::charactersTaken() const
+{
+    return m_charactersTaken;
+}
+
+bool AreaData::changeCharacter(int f_from, int f_to)
+{
+    if (f_from != -1) {
+        m_charactersTaken.removeAll(f_from);
+    }
+
+    if (m_charactersTaken.contains(f_to)) {
+        return false;
+    }
+
+    if (f_to != -1) {
+        m_charactersTaken.append(f_to);
+        return true;
+    }
+
+    return false;
+}
+
+QList<AreaData::Evidence> AreaData::evidence() const
+{
+    return m_evidence;
+}
+
+void AreaData::swapEvidence(int f_eviId1, int f_eviId2)
+{
+#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0)
+    //swapItemsAt does not exist in Qt older than 5.13
+    m_evidence.swap(f_eviId1, f_eviId2);
+#else
+    m_evidence.swapItemsAt(f_eviId1, f_eviId2);
+#endif
+}
+
+void AreaData::appendEvidence(const AreaData::Evidence &f_evi_r)
+{
+    m_evidence.append(f_evi_r);
+}
+
+void AreaData::deleteEvidence(int f_eviId)
+{
+    m_evidence.removeAt(f_eviId);
+}
+
+void AreaData::replaceEvidence(int f_eviId, const AreaData::Evidence &f_newEvi_r)
+{
+    m_evidence.replace(f_eviId, f_newEvi_r);
+}
+
+AreaData::Status AreaData::status() const
+{
+    return m_status;
+}
+
+bool AreaData::changeStatus(const QString &f_newStatus_r)
+{
+    if (AreaData::map_statuses.contains(f_newStatus_r)) {
+        m_status = AreaData::map_statuses[f_newStatus_r];
+        return true;
+    }
+
+    return false;
+}
+
+QList<int> AreaData::invited() const
+{
+    return m_invited;
+}
+
+bool AreaData::isMusicAllowed() const
+{
+    return m_toggleMusic;
+}
+
+void AreaData::toggleMusic()
+{
+    m_toggleMusic = !m_toggleMusic;
+}
+
+void AreaData::log(const QString &f_clientName_r, const QString &f_clientIpid_r, const AOPacket &f_packet_r) const
+{
+    auto l_header = f_packet_r.header;
+
+    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));
+    } else if (l_header == "ZZ") {
+        m_logger->logModcall(f_clientName_r, f_clientIpid_r, f_packet_r.contents.at(0));
+    }
+}
+
+void AreaData::logLogin(const QString &f_clientName_r, const QString &f_clientIpid_r, bool f_success, const QString& f_modname_r) const
+{
+    m_logger->logLogin(f_clientName_r, f_clientIpid_r, f_success, f_modname_r);
+}
+
+void AreaData::flushLogs() const
+{
+    m_logger->flush();
+}
+
+void AreaData::setEviMod(const EvidenceMod &f_eviMod_r)
+{
+    m_eviMod = f_eviMod_r;
+}
+
+QQueue<QString> AreaData::buffer() const
+{
+    return m_logger->buffer();
+}
+
+void AreaData::setTestimonyRecording(const TestimonyRecording &f_testimonyRecording_r)
+{
+    m_testimonyRecording = f_testimonyRecording_r;
+}
+
+void AreaData::restartTestimony()
+{
+    m_testimonyRecording = TestimonyRecording::PLAYBACK;
+    m_statement = 0;
+}
+
+void AreaData::clearTestimony()
+{
+    m_testimonyRecording = AreaData::TestimonyRecording::STOPPED;
+    m_statement = -1;
+    m_testimony.clear();
+}
+
+bool AreaData::forceImmediate() const
+{
+    return m_forceImmediate;
+}
+
+void AreaData::toggleImmediate()
+{
+    m_forceImmediate = !m_forceImmediate;
+}
+
+const QStringList& AreaData::lastICMessage() const
+{
+    return m_lastICMessage;
+}
+
+void AreaData::updateLastICMessage(const QStringList &f_lastMessage_r)
+{
+    m_lastICMessage = f_lastMessage_r;
+}
+
+QStringList AreaData::judgelog() const
+{
+    return m_judgelog;
+}
+
+void AreaData::appendJudgelog(const QString &f_newLog_r)
+{
+    if (m_judgelog.size() == 10) {
+        m_judgelog.removeFirst();
+    }
+
+    m_judgelog.append(f_newLog_r);
+}
+
+int AreaData::statement() const
+{
+    return m_statement;
+}
+
+void AreaData::recordStatement(const QStringList &f_newStatement_r)
+{
+    ++m_statement;
+    m_testimony.append(f_newStatement_r);
+}
+
+void AreaData::addStatement(int f_position, const QStringList &f_newStatement_r)
+{
+    m_testimony.insert(f_position, f_newStatement_r);
+}
+
+void AreaData::replaceStatement(int f_position, const QStringList &f_newStatement_r)
+{
+    m_testimony.replace(f_position, f_newStatement_r);
+}
+
+void AreaData::removeStatement(int f_position)
+{
+    m_testimony.remove(f_position);
+    --m_statement;
+}
+
+std::pair<QStringList, AreaData::TestimonyProgress> AreaData::jumpToStatement(int f_position)
+{
+    m_statement = f_position;
+
+    if (m_statement > m_testimony.size() - 1) {
+        m_statement = 0;
+        return {m_testimony.at(m_statement), TestimonyProgress::LOOPED};
+    }
+    if (m_statement <= 0) {
+        m_statement = 0;
+        return {m_testimony.at(m_statement), TestimonyProgress::STAYED_AT_FIRST};
+    }
+    else {
+        return {m_testimony.at(m_statement), TestimonyProgress::OK};
+    }
+}
+
+const QVector<QStringList>& AreaData::testimony() const
+{
+    return m_testimony;
+}
+
+AreaData::TestimonyRecording AreaData::testimonyRecording() const
+{
+    return m_testimonyRecording;
+}
+
+AreaData::EvidenceMod AreaData::eviMod() const
+{
+    return m_eviMod;
+}
+
+bool AreaData::addNotecard(const QString &f_owner_r, const QString &f_notecard_r)
+{
+    m_notecards[f_owner_r] = f_notecard_r;
+
+    if (f_notecard_r.isNull()) {
+        m_notecards.remove(f_owner_r);
+        return false;
+    }
+
+    return true;
+}
+
+QStringList AreaData::getNotecards()
+{
+    QMapIterator<QString, QString> l_noteIter(m_notecards);
+    QStringList l_notecards;
+
+    while (l_noteIter.hasNext()) {
+        l_noteIter.next();
+        l_notecards << l_noteIter.key() << ": " << l_noteIter.value() << "\n";
+    }
+
+    m_notecards.clear();
+
+    return l_notecards;
+}
+
+QString AreaData::musicPlayerBy() const
+{
+    return m_musicPlayedBy;
+}
+
+void AreaData::changeMusic(const QString &f_source_r, const QString &f_newSong_r)
+{
+    m_currentMusic = f_newSong_r;
+    m_musicPlayedBy = f_source_r;
+}
+
+QString AreaData::currentMusic() const
+{
+    return m_currentMusic;
+}
+
+int AreaData::proHP() const
+{
+    return m_proHP;
+}
+
+void AreaData::changeHP(AreaData::Side f_side, int f_newHP)
+{
+    if (f_side == Side::DEFENCE) {
+        m_defHP = std::min(std::max(0, f_newHP), 10);
+    } else if(f_side == Side::PROSECUTOR) {
+        m_proHP = std::min(std::max(0, f_newHP), 10);
+    }
+}
+
+int AreaData::defHP() const
+{
+    return m_defHP;
+}
+
+QString AreaData::document() const
+{
+    return m_document;
+}
+
+void AreaData::changeDoc(const QString &f_newDoc_r)
+{
+    m_document = f_newDoc_r;
+}
+
+bool AreaData::bgLocked() const
+{
+    return m_bgLocked;
+}
+
+void AreaData::toggleBgLock()
+{
+    m_bgLocked = !m_bgLocked;
+}
+
+bool AreaData::iniswapAllowed() const
+{
+    return m_iniswapAllowed;
+}
+
+void AreaData::toggleIniswap()
+{
+    m_iniswapAllowed = !m_iniswapAllowed;
+}
+
+bool AreaData::shownameAllowed() const
+{
+    return m_shownameAllowed;
+}
+
+QString AreaData::background() const
+{
+    return m_background;
+}
diff --git a/src/commands/area.cpp b/core/src/commands/area.cpp
similarity index 80%
rename from src/commands/area.cpp
rename to core/src/commands/area.cpp
index 705c400..6a7e84c 100644
--- a/src/commands/area.cpp
+++ b/core/src/commands/area.cpp
@@ -25,17 +25,16 @@ void AOClient::cmdCM(int argc, QStringList argv)
 {
     QString sender_name = ooc_name;
     AreaData* area = server->areas[current_area];
-    if (area->is_protected) {
+    if (area->isProtected()) {
         sendServerMessage("This area is protected, you may not become CM.");
         return;
     }
-    else if (area->owners.isEmpty()) { // no one owns this area, and it's not protected
-        area->owners.append(id);
-        area->invited.append(id);
+    else if (area->owners().isEmpty()) { // no one owns this area, and it's not protected
+        area->addOwner(id);
         sendServerMessageArea(sender_name + " is now CM in this area.");
         arup(ARUPType::CM, true);
     }
-    else if (!area->owners.contains(id)) { // there is already a CM, and it isn't us
+    else if (!area->owners().contains(id)) { // there is already a CM, and it isn't us
         sendServerMessage("You cannot become a CM in this area.");
     }
     else if (argc == 1) { // we are CM, and we want to make ID argv[0] also CM
@@ -49,7 +48,7 @@ void AOClient::cmdCM(int argc, QStringList argv)
             sendServerMessage("Unable to find client with ID " + argv[0] + ".");
             return;
         }
-        area->owners.append(owner_candidate->id);
+        area->addOwner(owner_candidate->id);
         sendServerMessageArea(owner_candidate->ooc_name + " is now CM in this area.");
         arup(ARUPType::CM, true);
     }
@@ -63,7 +62,7 @@ void AOClient::cmdUnCM(int argc, QStringList argv)
     AreaData* area = server->areas[current_area];
     int uid;
 
-    if (area->owners.isEmpty()) {
+    if (area->owners().isEmpty()) {
         sendServerMessage("There are no CMs in this area.");
         return;
     }
@@ -78,7 +77,7 @@ void AOClient::cmdUnCM(int argc, QStringList argv)
             sendServerMessage("Invalid user ID.");
             return;
         }
-        if (!area->owners.contains(uid)) {
+        if (!area->owners().contains(uid)) {
             sendServerMessage("That user is not CMed.");
             return;
         }
@@ -89,16 +88,12 @@ void AOClient::cmdUnCM(int argc, QStringList argv)
         sendServerMessage("Invalid command.");
         return;
     }
-    area->owners.removeAll(uid);
-    area->invited.removeAll(uid);
-    arup(ARUPType::CM, true);
-    if (area->owners.isEmpty()) {
-        area->invited.clear();
-        if (area->locked != AreaData::FREE) {
-            area->locked = AreaData::FREE;
-            arup(ARUPType::LOCKED, true);
-        }
+
+    if (area->removeOwner(uid)) {
+        arup(ARUPType::LOCKED, true);
     }
+
+    arup(ARUPType::CM, true);
 }
 
 void AOClient::cmdInvite(int argc, QStringList argv)
@@ -114,11 +109,10 @@ void AOClient::cmdInvite(int argc, QStringList argv)
         sendServerMessage("No client with that ID found.");
         return;
     }
-    else if (area->invited.contains(invited_id)) {
+    else if (!area->invite(invited_id)) {
         sendServerMessage("That ID is already on the invite list.");
         return;
     }
-    area->invited.append(invited_id);
     sendServerMessage("You invited ID " + argv[0]);
 }
 
@@ -135,30 +129,29 @@ void AOClient::cmdUnInvite(int argc, QStringList argv)
         sendServerMessage("No client with that ID found.");
         return;
     }
-    else if (area->owners.contains(uninvited_id)) {
+    else if (area->owners().contains(uninvited_id)) {
         sendServerMessage("You cannot uninvite a CM!");
         return;
     }
-    else if (!area->invited.contains(uninvited_id)) {
+    else if (!area->uninvite(uninvited_id)) {
         sendServerMessage("That ID is not on the invite list.");
         return;
     }
-    area->invited.removeAll(uninvited_id);
     sendServerMessage("You uninvited ID " + argv[0]);
 }
 
 void AOClient::cmdLock(int argc, QStringList argv)
 {
     AreaData* area = server->areas[current_area];
-    if (area->locked == AreaData::LockStatus::LOCKED) {
+    if (area->lockStatus() == AreaData::LockStatus::LOCKED) {
         sendServerMessage("This area is already locked.");
         return;
     }
     sendServerMessageArea("This area is now locked.");
-    area->locked = AreaData::LockStatus::LOCKED;
+    area->lock();
     for (AOClient* client : server->clients) {
         if (client->current_area == current_area && client->joined) {
-            area->invited.append(client->id);
+            area->invite(client->id);
         }
     }
     arup(ARUPType::LOCKED, true);
@@ -167,15 +160,15 @@ void AOClient::cmdLock(int argc, QStringList argv)
 void AOClient::cmdSpectatable(int argc, QStringList argv)
 {
     AreaData* area = server->areas[current_area];
-    if (area->locked == AreaData::LockStatus::SPECTATABLE) {
+    if (area->lockStatus() == AreaData::LockStatus::SPECTATABLE) {
         sendServerMessage("This area is already in spectate mode.");
         return;
     }
     sendServerMessageArea("This area is now spectatable.");
-    area->locked = AreaData::LockStatus::SPECTATABLE;
+    area->spectatable();
     for (AOClient* client : server->clients) {
         if (client->current_area == current_area && client->joined) {
-            area->invited.append(client->id);
+            area->invite(client->id);
         }
     }
     arup(ARUPType::LOCKED, true);
@@ -184,12 +177,12 @@ void AOClient::cmdSpectatable(int argc, QStringList argv)
 void AOClient::cmdUnLock(int argc, QStringList argv)
 {
     AreaData* area = server->areas[current_area];
-    if (area->locked == AreaData::LockStatus::FREE) {
+    if (area->lockStatus() == AreaData::LockStatus::FREE) {
         sendServerMessage("This area is not locked.");
         return;
     }
     sendServerMessageArea("This area is now unlocked.");
-    area->locked = AreaData::LockStatus::FREE;
+    area->unlock();
     arup(ARUPType::LOCKED, true);
 }
 
@@ -241,9 +234,9 @@ void AOClient::cmdAreaKick(int argc, QStringList argv)
 void AOClient::cmdSetBackground(int argc, QStringList argv)
 {
     AreaData* area = server->areas[current_area];
-    if (authenticated || !area->bg_locked) {
+    if (authenticated || !area->bgLocked()) {
         if (server->backgrounds.contains(argv[0])) {
-            area->background = argv[0];
+            area->background() = argv[0];
             server->broadcast(AOPacket("BN", {argv[0]}), current_area);
             sendServerMessageArea(current_char + " changed the background to " + argv[0]);
         }
@@ -259,14 +252,22 @@ void AOClient::cmdSetBackground(int argc, QStringList argv)
 void AOClient::cmdBgLock(int argc, QStringList argv)
 {
     AreaData* area = server->areas[current_area];
-    area->bg_locked = true;
+
+    if (area->bgLocked() == false) {
+        area->toggleBgLock();
+    };
+
     server->broadcast(AOPacket("CT", {server->server_name, current_char + " locked the background.", "1"}), current_area);
 }
 
 void AOClient::cmdBgUnlock(int argc, QStringList argv)
 {
     AreaData* area = server->areas[current_area];
-    area->bg_locked = false;
+
+    if (area->bgLocked() == true) {
+        area->toggleBgLock();
+    };
+
     server->broadcast(AOPacket("CT", {server->server_name, current_char + " unlocked the background.", "1"}), current_area);
 }
 
@@ -274,34 +275,23 @@ void AOClient::cmdStatus(int argc, QStringList argv)
 {
     AreaData* area = server->areas[current_area];
     QString arg = argv[0].toLower();
-    if (arg == "idle")
-        area->status = AreaData::IDLE;
-    else if (arg == "rp")
-        area->status = AreaData::RP;
-    else if (arg == "casing")
-        area->status = AreaData::CASING;
-    else if (arg == "looking-for-players" || arg == "lfp")
-        area->status = AreaData::LOOKING_FOR_PLAYERS;
-    else if (arg == "recess")
-        area->status = AreaData::RECESS;
-    else if (arg == "gaming")
-        area->status = AreaData::GAMING;
-    else {
-        sendServerMessage("That does not look like a valid status. Valid statuses are idle, rp, casing, lfp, recess, gaming");
-        return;
+
+    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);
+    } else {
+        sendServerMessage("That does not look like a valid status. Valid statuses are " + AreaData::map_statuses.keys().join(", "));
     }
-    arup(ARUPType::STATUS, true);
-    server->broadcast(AOPacket("CT", {server->server_name, current_char + " changed status to " + arg.toUpper(), "1"}), current_area);
 }
 
 void AOClient::cmdJudgeLog(int argc, QStringList argv)
 {
     AreaData* area = server->areas[current_area];
-    if (area->judgelog.isEmpty()) {
+    if (area->judgelog().isEmpty()) {
         sendServerMessage("There have been no judge actions in this area.");
         return;
     }
-    QString message = area->judgelog.join("\n");
+    QString message = area->judgelog().join("\n");
     //Judgelog contains an IPID, so we shouldn't send that unless the caller has appropriate permissions
     if (checkAuth(ACLFlags.value("KICK")) == 1 || checkAuth(ACLFlags.value("BAN")) == 1) {
             sendServerMessage(message);
diff --git a/src/commands/authentication.cpp b/core/src/commands/authentication.cpp
similarity index 100%
rename from src/commands/authentication.cpp
rename to core/src/commands/authentication.cpp
diff --git a/src/commands/casing.cpp b/core/src/commands/casing.cpp
similarity index 81%
rename from src/commands/casing.cpp
rename to core/src/commands/casing.cpp
index a83cb94..9a8aa3b 100644
--- a/src/commands/casing.cpp
+++ b/core/src/commands/casing.cpp
@@ -25,10 +25,10 @@ void AOClient::cmdDoc(int argc, QStringList argv)
     QString sender_name = ooc_name;
     AreaData* area = server->areas[current_area];
     if (argc == 0) {
-        sendServerMessage("Document: " + area->document);
+        sendServerMessage("Document: " + area->document());
     }
     else {
-        area->document = argv.join(" ");
+        area->changeDoc(argv.join(" "));
         sendServerMessageArea(sender_name + " changed the document.");
     }
 }
@@ -37,7 +37,7 @@ void AOClient::cmdClearDoc(int argc, QStringList argv)
 {
     QString sender_name = ooc_name;
     AreaData* area = server->areas[current_area];
-    area->document = "No document.";
+    area->changeDoc("No document.");
     sendServerMessageArea(sender_name + " cleared the document.");
 }
 
@@ -46,13 +46,13 @@ void AOClient::cmdEvidenceMod(int argc, QStringList argv)
     AreaData* area = server->areas[current_area];
     argv[0] = argv[0].toLower();
     if (argv[0] == "cm")
-        area->evi_mod = AreaData::EvidenceMod::CM;
+        area->setEviMod(AreaData::EvidenceMod::CM);
     else if (argv[0] == "mod")
-        area->evi_mod = AreaData::EvidenceMod::MOD;
+        area->setEviMod(AreaData::EvidenceMod::MOD);
     else if (argv[0] == "hiddencm")
-        area->evi_mod = AreaData::EvidenceMod::HIDDEN_CM;
+        area->setEviMod(AreaData::EvidenceMod::HIDDEN_CM);
     else if (argv[0] == "ffa")
-        area->evi_mod = AreaData::EvidenceMod::FFA;
+        area->setEviMod(AreaData::EvidenceMod::FFA);
     else {
         sendServerMessage("Invalid evidence mod.");
         return;
@@ -66,7 +66,7 @@ void AOClient::cmdEvidenceMod(int argc, QStringList argv)
 void AOClient::cmdEvidence_Swap(int argc, QStringList argv)
 {
     AreaData* area = server->areas[current_area];
-    int ev_size = area->evidence.size() -1;
+    int ev_size = area->evidence().size() -1;
 
     if (ev_size < 0) {
         sendServerMessage("No evidence in area.");
@@ -85,12 +85,7 @@ void AOClient::cmdEvidence_Swap(int argc, QStringList argv)
         return;
     }
     if ((ev_id2 <= ev_size) && (ev_id1 <= ev_size)) {
-#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0)
-        //swapItemsAt does not exist in Qt older than 5.13
-        area->evidence.swap(ev_id1, ev_id2);
-#else
-        area->evidence.swapItemsAt(ev_id1, ev_id2);
-#endif
+        area->swapEvidence(ev_id1, ev_id2);
         sendEvidenceList(area);
         sendServerMessage("The evidence " + QString::number(ev_id1) + " and " + QString::number(ev_id2) + " have been swapped.");
     }
@@ -102,12 +97,12 @@ void AOClient::cmdEvidence_Swap(int argc, QStringList argv)
 void AOClient::cmdTestify(int argc, QStringList argv)
 {
     AreaData* area = server->areas[current_area];
-    if (area->test_rec == AreaData::TestimonyRecording::RECORDING) {
+    if (area->testimonyRecording() == AreaData::TestimonyRecording::RECORDING) {
         sendServerMessage("Testimony recording is already in progress. Please stop it before starting a new one.");
     }
     else {
         clearTestimony();
-        area->test_rec = AreaData::TestimonyRecording::RECORDING;
+        area->setTestimonyRecording(AreaData::TestimonyRecording::RECORDING);
         sendServerMessage("Started testimony recording.");
     }
 }
@@ -115,15 +110,14 @@ void AOClient::cmdTestify(int argc, QStringList argv)
 void AOClient::cmdExamine(int argc, QStringList argv)
 {
     AreaData* area = server->areas[current_area];
-    if (area->testimony.size() -1 > 0)
+    if (area->testimony().size() -1 > 0)
     {
-        area->test_rec = AreaData::TestimonyRecording::PLAYBACK;
+        area->restartTestimony();
         server->broadcast(AOPacket("RT",{"testimony2"}), current_area);
-        server->broadcast(AOPacket("MS", {area->testimony[0]}), current_area);
-        area->statement = 0;
+        server->broadcast(AOPacket("MS", {area->testimony()[0]}), current_area);
         return;
     }
-    if (area->test_rec == AreaData::TestimonyRecording::PLAYBACK)
+    if (area->testimonyRecording() == AreaData::TestimonyRecording::PLAYBACK)
         sendServerMessage("Unable to examine while another examination is running");
     else
         sendServerMessage("Unable to start replay without prior examination.");
@@ -132,15 +126,15 @@ void AOClient::cmdExamine(int argc, QStringList argv)
 void AOClient::cmdTestimony(int argc, QStringList argv)
 {
    AreaData* area = server->areas[current_area];
-   if (area->testimony.size() -1 < 1) {
+   if (area->testimony().size() -1 < 1) {
        sendServerMessage("Unable to display empty testimony.");
        return;
    }
 
    QString ooc_message;
-   for (int i = 1; i <= area->testimony.size() -1; i++)
+   for (int i = 1; i <= area->testimony().size() -1; i++)
    {
-       QStringList packet = area->testimony.at(i);
+       QStringList packet = area->testimony().at(i);
        QString ic_message = packet[4];
        ooc_message.append( "[" + QString::number(i) + "]" + ic_message + "\n");
    }
@@ -150,35 +144,34 @@ void AOClient::cmdTestimony(int argc, QStringList argv)
 void AOClient::cmdDeleteStatement(int argc, QStringList argv)
 {
     AreaData* area = server->areas[current_area];
-    int c_statement = area->statement;
-    if (area->testimony.size() - 1 == 0) {
+    int c_statement = area->statement();
+    if (area->testimony().size() - 1 == 0) {
         sendServerMessage("Unable to delete statement. No statements saved in this area.");
     }
-    if (c_statement > 0 && area->testimony.size() > 2) {
-        area->testimony.remove(c_statement);
-        area->statement = c_statement - 1;
+    if (c_statement > 0 && area->testimony().size() > 2) {
+        area->removeStatement(c_statement);
         sendServerMessage("The statement with id " + QString::number(c_statement) + " has been deleted from the testimony.");
     }
 }
 
 void AOClient::cmdUpdateStatement(int argc, QStringList argv)
 {
-    server->areas[current_area]->test_rec = AreaData::TestimonyRecording::UPDATE;
+    server->areas[current_area]->setTestimonyRecording(AreaData::TestimonyRecording::UPDATE);
     sendServerMessage("The next IC-Message will replace the last displayed replay message.");
 }
 
 void AOClient::cmdPauseTestimony(int argc, QStringList argv)
 {
     AreaData* area = server->areas[current_area];
-    area->test_rec = AreaData::TestimonyRecording::STOPPED;
+    area->setTestimonyRecording(AreaData::TestimonyRecording::STOPPED);
     server->broadcast(AOPacket("RT",{"testimony1#1"}), current_area);
     sendServerMessage("Testimony has been stopped.");
 }
 
 void AOClient::cmdAddStatement(int argc, QStringList argv)
 {
-    if (server->areas[current_area]->statement < server->maximum_statements) {
-        server->areas[current_area]->test_rec = AreaData::TestimonyRecording::ADD;
+    if (server->areas[current_area]->statement() < server->maximum_statements) {
+        server->areas[current_area]->setTestimonyRecording(AreaData::TestimonyRecording::ADD);
         sendServerMessage("The next IC-Message will be inserted into the testimony.");
     }
     else
@@ -196,7 +189,7 @@ void AOClient::cmdSaveTestimony(int argc, QStringList argv)
 
     if (permission_found) {
         AreaData* area = server->areas[current_area];
-        if (area->testimony.size() -1 <= 0) {
+        if (area->testimony().size() -1 <= 0) {
             sendServerMessage("Can't save an empty testimony.");
             return;
         }
@@ -216,9 +209,9 @@ void AOClient::cmdSaveTestimony(int argc, QStringList argv)
         QTextStream out(&file);
         out.setCodec("UTF-8");
         if(file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
-            for (int i = 0; i <= area->testimony.size() -1; i++)
+            for (int i = 0; i <= area->testimony().size() -1; i++)
             {
-                out << area->testimony.at(i).join("#") << "\n";
+                out << area->testimony().at(i).join("#") << "\n";
             }
             sendServerMessage("Testimony saved. To load it use /loadtestimony " + testimony_name);
             testimony_saving = false;
@@ -258,7 +251,7 @@ void AOClient::cmdLoadTestimony(int argc, QStringList argv)
         if (testimony_lines <= server->maximum_statements) {
             QString line = in.readLine();
             QStringList packet = line.split("#");
-            area->testimony.append(packet);
+            area->addStatement(area->testimony().size(), packet);
             testimony_lines = testimony_lines + 1;
         }
         else {
diff --git a/src/commands/command_helper.cpp b/core/src/commands/command_helper.cpp
similarity index 95%
rename from src/commands/command_helper.cpp
rename to core/src/commands/command_helper.cpp
index 6fb8665..88c82e4 100644
--- a/src/commands/command_helper.cpp
+++ b/core/src/commands/command_helper.cpp
@@ -32,7 +32,7 @@ QStringList AOClient::buildAreaList(int area_idx)
     QString area_name = server->area_names[area_idx];
     AreaData* area = server->areas[area_idx];
     entries.append("=== " + area_name + " ===");
-    switch (area->locked) {
+    switch (area->lockStatus()) {
         case AreaData::LockStatus::LOCKED:
             entries.append("[LOCKED]");
             break;
@@ -43,13 +43,13 @@ QStringList AOClient::buildAreaList(int area_idx)
         default:
             break;
     }
-    entries.append("[" + QString::number(area->player_count) + " users][" + QVariant::fromValue(area->status).toString().replace("_", "-") + "]");
+    entries.append("[" + QString::number(area->playerCount()) + " users][" + QVariant::fromValue(area->status()).toString().replace("_", "-") + "]");
     for (AOClient* client : server->clients) {
         if (client->current_area == area_idx && client->joined) {
             QString char_entry = "[" + QString::number(client->id) + "] " + client->current_char;
             if (client->current_char == "")
                 char_entry += "Spectator";
-            if (area->owners.contains(client->id))
+            if (area->owners().contains(client->id))
                 char_entry.insert(0, "[CM] ");
             if (authenticated)
                 char_entry += " (" + client->getIpid() + "): " + client->ooc_name;
@@ -103,7 +103,7 @@ QString AOClient::getAreaTimer(int area_idx, int timer_idx)
     if (timer_idx == 0)
         timer = server->timer;
     else if (timer_idx > 0 && timer_idx <= 4)
-        timer = area->timers[timer_idx - 1];
+        timer = area->timers().at(timer_idx - 1);
     else
         return "Invalid timer ID.";
 
diff --git a/src/commands/messaging.cpp b/core/src/commands/messaging.cpp
similarity index 98%
rename from src/commands/messaging.cpp
rename to core/src/commands/messaging.cpp
index 2df2c15..c5cf7e0 100644
--- a/src/commands/messaging.cpp
+++ b/core/src/commands/messaging.cpp
@@ -106,7 +106,7 @@ void AOClient::cmdRandomChar(int argc, QStringList argv)
     bool taken = true;
     while (taken) {
         selected_char_id = genRand(0, server->characters.size() - 1);
-        if (!area->characters_taken.contains(selected_char_id)) {
+        if (!area->charactersTaken().contains(selected_char_id)) {
             taken = false;
         }
     }
@@ -423,7 +423,7 @@ void AOClient::cmdA(int argc, QStringList argv)
     }
 
     AreaData* area = server->areas[area_id];
-    if (!area->owners.contains(id)) {
+    if (!area->owners().contains(id)) {
         sendServerMessage("You are not CM in that area.");
         return;
     }
@@ -441,7 +441,7 @@ void AOClient::cmdS(int argc, QStringList argv)
     QString ooc_message = argv.join(" ");
 
     for (int i = 0; i <= all_areas; i++) {
-        if (server->areas[i]->owners.contains(id))
+        if (server->areas[i]->owners().contains(id))
             server->broadcast(AOPacket("CT", {"[CM]" + sender_name, ooc_message}), i);
     }
 }
diff --git a/src/commands/moderation.cpp b/core/src/commands/moderation.cpp
similarity index 97%
rename from src/commands/moderation.cpp
rename to core/src/commands/moderation.cpp
index 5ce9665..31777c5 100644
--- a/src/commands/moderation.cpp
+++ b/core/src/commands/moderation.cpp
@@ -328,8 +328,8 @@ void AOClient::cmdAllowBlankposting(int argc, QStringList argv)
 {
     QString sender_name = ooc_name;
     AreaData* area = server->areas[current_area];
-    area->blankposting_allowed = !area->blankposting_allowed;
-    if (area->blankposting_allowed == false) {
+    area->toggleBlankposting();
+    if (area->blankpostingAllowed() == false) {
         sendServerMessageArea(sender_name + " has set blankposting in the area to forbidden.");
     }
     else {
@@ -388,16 +388,16 @@ void AOClient::cmdReload(int argc, QStringList argv)
 void AOClient::cmdForceImmediate(int argc, QStringList argv)
 {
     AreaData* area = server->areas[current_area];
-    area->force_immediate = !area->force_immediate;
-    QString state = area->force_immediate ? "on." : "off.";
+    area->toggleImmediate();
+    QString state = area->forceImmediate() ? "on." : "off.";
     sendServerMessage("Forced immediate text processing in this area is now " + state);
 }
 
 void AOClient::cmdAllowIniswap(int argc, QStringList argv)
 {
     AreaData* area = server->areas[current_area];
-    area->iniswap_allowed = !area->iniswap_allowed;
-    QString state = area->iniswap_allowed ? "allowed." : "disallowed.";
+    area->toggleIniswap();
+    QString state = area->iniswapAllowed() ? "allowed." : "disallowed.";
     sendServerMessage("Iniswapping in this area is now " + state);
 }
 
diff --git a/src/commands/music.cpp b/core/src/commands/music.cpp
similarity index 91%
rename from src/commands/music.cpp
rename to core/src/commands/music.cpp
index 5500b78..e11648d 100644
--- a/src/commands/music.cpp
+++ b/core/src/commands/music.cpp
@@ -28,8 +28,8 @@ void AOClient::cmdPlay(int argc, QStringList argv)
     }
     AreaData* area = server->areas[current_area];
     QString song = argv.join(" ");
-    area->current_music = song;
-    area->music_played_by = showname;
+    area->currentMusic() = song;
+    area->musicPlayerBy() = showname;
     AOPacket music_change("MC", {song, QString::number(server->getCharID(current_char)), showname, "1", "0"});
     server->broadcast(music_change, current_area);
 }
@@ -37,8 +37,8 @@ void AOClient::cmdPlay(int argc, QStringList argv)
 void AOClient::cmdCurrentMusic(int argc, QStringList argv)
 {
     AreaData* area = server->areas[current_area];
-    if (area->current_music != "" && area->current_music != "~stop.mp3") // dummy track for stopping music
-        sendServerMessage("The current song is " + area->current_music + " played by " + area->music_played_by);
+    if (area->currentMusic() != "" && area->currentMusic() != "~stop.mp3") // dummy track for stopping music
+        sendServerMessage("The current song is " + area->currentMusic() + " played by " + area->musicPlayerBy());
     else
         sendServerMessage("There is no music playing.");
 }
@@ -86,7 +86,7 @@ void AOClient::cmdUnBlockDj(int argc, QStringList argv)
 void AOClient::cmdToggleMusic(int argc, QStringList argv)
 {
     AreaData* area = server->areas[current_area];
-    area->toggle_music = !area->toggle_music;
-    QString state = area->toggle_music ? "allowed." : "disallowed.";
+    area->toggleMusic();
+    QString state = area->isMusicAllowed() ? "allowed." : "disallowed.";
     sendServerMessage("Music in this area is now " + state);
 }
diff --git a/src/commands/roleplay.cpp b/core/src/commands/roleplay.cpp
similarity index 89%
rename from src/commands/roleplay.cpp
rename to core/src/commands/roleplay.cpp
index ef27f72..d291d98 100644
--- a/src/commands/roleplay.cpp
+++ b/core/src/commands/roleplay.cpp
@@ -48,7 +48,7 @@ void AOClient::cmdTimer(int argc, QStringList argv)
         QStringList timers;
         timers.append("Currently active timers:");
         for (int i = 0; i <= 4; i++) {
-            timers.append(getAreaTimer(area->index, i));
+            timers.append(getAreaTimer(area->index(), i));
         }
         sendServerMessage(timers.join("\n"));
         return;
@@ -65,7 +65,7 @@ void AOClient::cmdTimer(int argc, QStringList argv)
     // Called with one argument
     // Shows the status of one timer
     if (argc == 1) {
-        sendServerMessage(getAreaTimer(area->index, timer_id));
+        sendServerMessage(getAreaTimer(area->index(), timer_id));
         return;
     }
 
@@ -83,7 +83,7 @@ void AOClient::cmdTimer(int argc, QStringList argv)
         requested_timer = server->timer;
     }
     else
-        requested_timer = area->timers[timer_id - 1];
+        requested_timer = area->timers().at(timer_id - 1);
 
     AOPacket show_timer("TI", {QString::number(timer_id), "2"});
     AOPacket hide_timer("TI", {QString::number(timer_id), "3"});
@@ -130,38 +130,33 @@ void AOClient::cmdTimer(int argc, QStringList argv)
 void AOClient::cmdNoteCard(int argc, QStringList argv)
 {
     AreaData* area = server->areas[current_area];
-    if (area->notecards.keys().contains(current_char))
-        area->notecards.remove(current_char);
     QString notecard = argv.join(" ");
-    area->notecards[current_char] = notecard;
+    area->addNotecard(current_char, notecard);
     sendServerMessageArea(current_char + " wrote a note card.");
 }
 
 void AOClient::cmdNoteCardClear(int argc, QStringList argv)
 {
     AreaData* area = server->areas[current_area];
-    if (area->notecards.keys().contains(current_char)) {
-        area->notecards.remove(current_char);
+    if (!area->addNotecard(current_char, QString())) {
         sendServerMessageArea(current_char + " erased their note card.");
     }
-    else
-        sendServerMessage("You do not have a note card.");
 }
 
 void AOClient::cmdNoteCardReveal(int argc, QStringList argv)
 {
     AreaData* area = server->areas[current_area];
-    if (area->notecards.isEmpty()) {
+    const QStringList l_notecards = area->getNotecards();
+
+    if (l_notecards.isEmpty()) {
         sendServerMessage("There are no cards to reveal in this area.");
         return;
     }
-    QStringList message;
-    message << "Note cards have been revealed.";
-    QMap<QString, QString>::iterator i;
-    for (i = area->notecards.begin(); i != area->notecards.end(); ++i)
-        message << i.key() + ": " + i.value();
-    sendServerMessageArea(message.join("\n"));
-    area->notecards.clear();
+
+    QString message("Note cards have been revealed.\n");
+    message.append(l_notecards.join("\n") + "\n");
+
+    sendServerMessageArea(message);
 }
 
 void AOClient::cmd8Ball(int argc, QStringList argv)
diff --git a/src/config_manager.cpp b/core/src/config_manager.cpp
similarity index 100%
rename from src/config_manager.cpp
rename to core/src/config_manager.cpp
diff --git a/src/db_manager.cpp b/core/src/db_manager.cpp
similarity index 100%
rename from src/db_manager.cpp
rename to core/src/db_manager.cpp
diff --git a/src/discord.cpp b/core/src/discord.cpp
similarity index 96%
rename from src/discord.cpp
rename to core/src/discord.cpp
index 22864a2..de97e20 100644
--- a/src/discord.cpp
+++ b/core/src/discord.cpp
@@ -37,7 +37,7 @@ void Discord::postModcallWebhook(QString name, QString reason, int current_area)
     QJsonArray jsonArray;
     QJsonObject jsonObject {
         {"color", "13312842"},
-        {"title", name + " filed a modcall in " + server->areas[current_area]->name},
+        {"title", name + " filed a modcall in " + server->areas[current_area]->name()},
         {"description", reason}
     };
     jsonArray.append(jsonObject);
@@ -55,7 +55,7 @@ void Discord::postModcallWebhook(QString name, QString reason, int current_area)
         QHttpPart file;
         file.setRawHeader(QByteArray("Content-Disposition"), QByteArray("form-data; name=\"file\"; filename=\"log.txt\""));
         file.setRawHeader(QByteArray("Content-Type"), QByteArray("plain/text"));
-        QQueue<QString> buffer = server->areas[current_area]->logger->getBuffer(); // I feel no shame for doing this
+        QQueue<QString> buffer = server->areas[current_area]->buffer(); // I feel no shame for doing this
         QString log;
         while (!buffer.isEmpty()) {
             log.append(buffer.dequeue() + "\n");
diff --git a/core/src/logger.cpp b/core/src/logger.cpp
new file mode 100644
index 0000000..260acb7
--- /dev/null
+++ b/core/src/logger.cpp
@@ -0,0 +1,124 @@
+//////////////////////////////////////////////////////////////////////////////////////
+//    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/>.        //
+//////////////////////////////////////////////////////////////////////////////////////
+
+#include <QDir>
+
+#include "include/logger.h"
+
+void Logger::logIC(const QString& f_charName_r, const QString& f_ipid_r, const QString& f_message_r)
+{
+    addEntry(f_charName_r, f_ipid_r, "IC", f_message_r);
+}
+
+void Logger::logOOC(const QString& f_charName_r, const QString& f_ipid_r, const QString& f_message_r)
+{
+    addEntry(f_charName_r, f_ipid_r, "OOC", f_message_r);
+}
+
+void Logger::logModcall(const QString& f_charName_r, const QString& f_ipid_r, const QString& f_modcallReason_r)
+{
+    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)
+{
+    // 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") {
+        addEntry(f_charName_r, f_ipid_r, "LOGIN", "Attempted login");
+    }
+    else if (l_cmd == "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 {
+        logOOC(f_charName_r, f_ipid_r, f_oocMessage_r);
+    }
+}
+
+void Logger::logLogin(const QString& f_charName_r, const QString& f_ipid_r, bool success, const QString& f_modname_r)
+{
+    QString l_message = success ? "Logged in as " + f_modname_r : "Failed to log in as " + f_modname_r;
+    addEntry(f_charName_r, f_ipid_r, "LOGIN", l_message);
+}
+
+void Logger::addEntry(
+        const QString& f_charName_r,
+        const QString& f_ipid_r,
+        const QString& f_type_r,
+        const QString& f_message_r)
+{
+    QString l_time = QDateTime::currentDateTime().toString("ddd MMMM d yyyy | hh:mm:ss");
+
+    QString l_logEntry = QStringLiteral("[%1][%2][%6] %3(%4): %5\n")
+            .arg(l_time, m_areaName, f_charName_r, f_ipid_r, f_message_r, f_type_r);
+
+    if (m_buffer.length() < m_maxLength) {
+        m_buffer.enqueue(l_logEntry);
+
+        if (m_logType == "full") {
+           flush();
+        }
+    }
+    else {
+        m_buffer.dequeue();
+        m_buffer.enqueue(l_logEntry);
+    }
+}
+
+void Logger::flush()
+{
+    QDir l_dir("logs/");
+    if (!l_dir.exists()) {
+        l_dir.mkpath(".");
+    }
+
+    QFile l_logfile;
+
+    if (m_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") {
+        l_logfile.setFileName(QString("logs/%1.log").arg(QDate::currentDate().toString("yyyy-MM-dd")));
+    }
+    else {
+        qCritical("Invalid logger set!");
+    }
+
+    if (l_logfile.open(QIODevice::WriteOnly | QIODevice::Append)) {
+        QTextStream file_stream(&l_logfile);
+
+        while (!m_buffer.isEmpty())
+            file_stream << m_buffer.dequeue();
+    }
+
+    l_logfile.close();
+}
+
+QQueue<QString> Logger::buffer() const
+{
+    return m_buffer;
+}
diff --git a/src/packets.cpp b/core/src/packets.cpp
similarity index 88%
rename from src/packets.cpp
rename to core/src/packets.cpp
index 75ae6a5..3551d73 100644
--- a/src/packets.cpp
+++ b/core/src/packets.cpp
@@ -101,20 +101,20 @@ void AOClient::pktLoadingDone(AreaData* area, int argc, QStringList argv, AOPack
     }
 
     server->player_count++;
-    area->player_count++;
+    area->clientJoinedArea();
     joined = true;
     server->updateCharsTaken(area);
 
     arup(ARUPType::PLAYER_COUNT, true); // Tell everyone there is a new player
     sendEvidenceList(area);
 
-    sendPacket("HP", {"1", QString::number(area->def_hp)});
-    sendPacket("HP", {"2", QString::number(area->pro_hp)});
+    sendPacket("HP", {"1", QString::number(area->defHP())});
+    sendPacket("HP", {"2", QString::number(area->proHP())});
     sendPacket("FA", server->area_names);
     sendPacket("OPPASS", {"DEADBEEF"});
     sendPacket("DONE");
-    sendPacket("BN", {area->background});
-
+    sendPacket("BN", {area->background()});
+  
     sendServerMessage("=== MOTD ===\r\n" + server->MOTD + "\r\n=============");
 
     fullArup(); // Give client all the area data
@@ -125,8 +125,8 @@ void AOClient::pktLoadingDone(AreaData* area, int argc, QStringList argv, AOPack
     else {
         sendPacket("TI", {"0", "3"});
     }
-    for (QTimer* timer : area->timers) {
-        int timer_id = area->timers.indexOf(timer) + 1;
+    for (QTimer* timer : area->timers()) {
+        int timer_id = area->timers().indexOf(timer) + 1;
         if (timer->isActive()) {
             sendPacket("TI", {QString::number(timer_id), "2"});
             sendPacket("TI", {QString::number(timer_id), "0", QString::number(QTime(0,0).msecsTo(QTime(0,0).addMSecs(timer->remainingTime())))});
@@ -173,10 +173,9 @@ void AOClient::pktIcChat(AreaData* area, int argc, QStringList argv, AOPacket pa
     if (pos != "")
         validated_packet.contents[5] = pos;
 
-    area->logger->logIC(this, &validated_packet);
+    area->log(current_char, ipid, validated_packet);
     server->broadcast(validated_packet, current_area);
-    area->last_ic_message.clear();
-    area->last_ic_message.append(validated_packet.contents);
+    area->updateLastICMessage(validated_packet.contents);
 
     server->can_send_ic_messages = false;
     server->next_message_timer.start(server->message_floodguard);
@@ -213,13 +212,13 @@ void AOClient::pktOocChat(AreaData* area, int argc, QStringList argv, AOPacket p
         command = command.right(command.length() - 1);
         cmd_argv.removeFirst();
         int cmd_argc = cmd_argv.length();
-        area->logger->logCmd(this, &final_packet, command, cmd_argv);
+
         handleCommand(command, cmd_argc, cmd_argv);
     }
     else {
         server->broadcast(final_packet, current_area);
-        area->logger->logOOC(this, &final_packet);
     }
+    area->log(current_char, ipid, final_packet);
 }
 
 void AOClient::pktPing(AreaData* area, int argc, QStringList argv, AOPacket packet)
@@ -247,7 +246,7 @@ void AOClient::pktChangeMusic(AreaData* area, int argc, QStringList argv, AOPack
                 sendServerMessage("You are blocked from changing the music.");
                 return;
             }
-            if (!area->toggle_music && !checkAuth(ACLFlags.value("CM"))) {
+            if (!area->isMusicAllowed() && !checkAuth(ACLFlags.value("CM"))) {
                 sendServerMessage("Music is disabled in this area.");
                 return;
             }
@@ -262,8 +261,8 @@ void AOClient::pktChangeMusic(AreaData* area, int argc, QStringList argv, AOPack
             else
                 final_song = argument;
             AOPacket music_change("MC", {final_song, argv[1], showname, "1", "0", effects});
-            area->current_music = final_song;
-            area->music_played_by = showname;
+            area->currentMusic() = final_song;
+            area->musicPlayerBy() = showname;
             server->broadcast(music_change, current_area);
             return;
         }
@@ -297,14 +296,18 @@ void AOClient::pktHpBar(AreaData* area, int argc, QStringList argv, AOPacket pac
         sendServerMessage("You are blocked from using the judge controls.");
         return;
     }
+    int l_newValue = argv.at(1).toInt();
+
     if (argv[0] == "1") {
-        area->def_hp = std::min(std::max(0, argv[1].toInt()), 10);
+        area->changeHP(AreaData::Side::DEFENCE, l_newValue);
     }
     else if (argv[0] == "2") {
-        area->pro_hp = std::min(std::max(0, argv[1].toInt()), 10);
+        area->changeHP(AreaData::Side::PROSECUTOR, l_newValue);
     }
-    server->broadcast(AOPacket("HP", {"1", QString::number(area->def_hp)}), area->index);
-    server->broadcast(AOPacket("HP", {"2", QString::number(area->pro_hp)}), area->index);
+
+    server->broadcast(AOPacket("HP", {"1", QString::number(area->defHP())}), area->index());
+    server->broadcast(AOPacket("HP", {"2", QString::number(area->proHP())}), area->index());
+
     updateJudgeLog(area, this, "updated the penalties");
 }
 
@@ -343,7 +346,7 @@ void AOClient::pktModCall(AreaData* area, int argc, QStringList argv, AOPacket p
         if (client->authenticated)
             client->sendPacket(packet);
     }
-    area->logger->logModcall(this, &packet);
+    area->log(current_char, ipid, packet);
 
     if (server->webhook_enabled) {
         QString name = ooc_name;
@@ -352,7 +355,8 @@ void AOClient::pktModCall(AreaData* area, int argc, QStringList argv, AOPacket p
 
         server->webhookRequest(name, packet.contents[0], current_area);
     }
-    area->logger->flush();
+    
+    area->flushLogs();
 }
 
 void AOClient::pktAddEvidence(AreaData* area, int argc, QStringList argv, AOPacket packet)
@@ -360,7 +364,7 @@ void AOClient::pktAddEvidence(AreaData* area, int argc, QStringList argv, AOPack
     if (!checkEvidenceAccess(area))
         return;
     AreaData::Evidence evi = {argv[0], argv[1], argv[2]};
-    area->evidence.append(evi);
+    area->appendEvidence(evi);
     sendEvidenceList(area);
 }
 
@@ -370,8 +374,8 @@ void AOClient::pktRemoveEvidence(AreaData* area, int argc, QStringList argv, AOP
         return;
     bool is_int = false;
     int idx = argv[0].toInt(&is_int);
-    if (is_int && idx <= area->evidence.size() && idx >= 0) {
-        area->evidence.removeAt(idx);
+    if (is_int && idx <= area->evidence().size() && idx >= 0) {
+        area->deleteEvidence(idx);
     }
     sendEvidenceList(area);
 }
@@ -383,8 +387,8 @@ void AOClient::pktEditEvidence(AreaData* area, int argc, QStringList argv, AOPac
     bool is_int = false;
     int idx = argv[0].toInt(&is_int);
     AreaData::Evidence evi = {argv[1], argv[2], argv[3]};
-    if (is_int && idx <= area->evidence.size() && idx >= 0) {
-        area->evidence.replace(idx, evi);
+    if (is_int && idx <= area->evidence().size() && idx >= 0) {
+        area->replaceEvidence(idx, evi);
     }
     sendEvidenceList(area);
 }
@@ -455,8 +459,8 @@ void AOClient::updateEvidenceList(AreaData* area)
     QStringList evidence_list;
     QString evidence_format("%1&%2&%3");
 
-    for (AreaData::Evidence evidence : area->evidence) {
-        if (!checkAuth(ACLFlags.value("CM")) && area->evi_mod == AreaData::EvidenceMod::HIDDEN_CM) {
+    for (AreaData::Evidence evidence : area->evidence()) {
+        if (!checkAuth(ACLFlags.value("CM")) && area->eviMod() == AreaData::EvidenceMod::HIDDEN_CM) {
             QRegularExpression regex("<owner=(.*?)>");
             QRegularExpressionMatch match = regex.match(evidence.description);
             if (match.hasMatch()) {
@@ -491,7 +495,7 @@ AOPacket AOClient::validateIcPacket(AOPacket packet)
         // Spectators cannot use IC
         return invalid;
     AreaData* area = server->areas[current_area];
-    if (area->locked == AreaData::LockStatus::SPECTATABLE && !area->invited.contains(id) && !checkAuth(ACLFlags.value("BYPASS_LOCKS")))
+    if (area->lockStatus() == AreaData::LockStatus::SPECTATABLE && !area->invited().contains(id) && !checkAuth(ACLFlags.value("BYPASS_LOCKS")))
         // Non-invited players cannot speak in spectatable areas
         return invalid;
 
@@ -516,7 +520,7 @@ AOPacket AOClient::validateIcPacket(AOPacket packet)
     if (current_char != incoming_args[2].toString()) {
         // Selected char is different from supplied folder name
         // This means the user is INI-swapped
-        if (!area->iniswap_allowed) {
+        if (!area->iniswapAllowed()) {
             if (!server->characters.contains(incoming_args[2].toString()))
                 return invalid;
         }
@@ -536,12 +540,12 @@ AOPacket AOClient::validateIcPacket(AOPacket packet)
         return invalid;
 
     QString incoming_msg = dezalgo(incoming_args[4].toString().trimmed());
-    if (!area->last_ic_message.isEmpty()
-            && incoming_msg == area->last_ic_message[4]
+    if (!area->lastICMessage().isEmpty()
+            && incoming_msg == area->lastICMessage()[4]
             && incoming_msg != "")
         return invalid;
 
-    if (incoming_msg == "" && area->blankposting_allowed == false) {
+    if (incoming_msg == "" && area->blankpostingAllowed() == false) {
         sendServerMessage("Blankposting has been forbidden in this area.");
         return invalid;
     }
@@ -614,7 +618,7 @@ AOPacket AOClient::validateIcPacket(AOPacket packet)
 
     // evidence
     int evi_idx = incoming_args[11].toInt();
-    if (evi_idx > area->evidence.length())
+    if (evi_idx > area->evidence().length())
         return invalid;
     args.append(QString::number(evi_idx));
 
@@ -641,7 +645,7 @@ AOPacket AOClient::validateIcPacket(AOPacket packet)
     if (incoming_args.length() > 15) {
         // showname
         QString incoming_showname = dezalgo(incoming_args[15].toString().trimmed());
-        if (!(incoming_showname == current_char || incoming_showname.isEmpty()) && !area->showname_allowed) {
+        if (!(incoming_showname == current_char || incoming_showname.isEmpty()) && !area->shownameAllowed()) {
             sendServerMessage("Shownames are not allowed in this area!");
             return invalid;
         }
@@ -707,7 +711,7 @@ AOPacket AOClient::validateIcPacket(AOPacket packet)
 
         // immediate text processing
         int immediate = incoming_args[18].toInt();
-        if (area->force_immediate) {
+        if (area->forceImmediate()) {
             if (args[7] == "1" || args[7] == "2") {
                 args[7] = "0";
                 immediate = 1;
@@ -749,10 +753,10 @@ AOPacket AOClient::validateIcPacket(AOPacket packet)
         int additive = incoming_args[24].toInt();
         if (additive != 0 && additive != 1)
             return invalid;
-        else if (area->last_ic_message.isEmpty()){
+        else if (area->lastICMessage().isEmpty()){
             additive = 0;
         }
-        else if (!(char_id == area->last_ic_message[8].toInt())) {
+        else if (!(char_id == area->lastICMessage()[8].toInt())) {
             additive = 0;
         }
         else if (additive == 1) {
@@ -765,38 +769,61 @@ AOPacket AOClient::validateIcPacket(AOPacket packet)
     }
 
     //Testimony playback
-    if (area->test_rec == AreaData::TestimonyRecording::RECORDING || area->test_rec == AreaData::TestimonyRecording::ADD) {
+    if (area->testimonyRecording() == AreaData::TestimonyRecording::RECORDING || area->testimonyRecording() == AreaData::TestimonyRecording::ADD) {
         if (args[5] != "wit")
             return AOPacket("MS", args);
 
-        if (area->statement == -1) {
+        if (area->statement() == -1) {
             args[4] = "~~\\n-- " + args[4] + " --";
             args[14] = "3";
             server->broadcast(AOPacket("RT",{"testimony1"}), current_area);
         }
         addStatement(args);
     }
-    else if (area->test_rec == AreaData::TestimonyRecording::UPDATE) {
+    else if (area->testimonyRecording() == AreaData::TestimonyRecording::UPDATE) {
         args = updateStatement(args);
     }
-    else if (area->test_rec == AreaData::TestimonyRecording::PLAYBACK) {
+    else if (area->testimonyRecording() == AreaData::TestimonyRecording::PLAYBACK) {
+        AreaData::TestimonyProgress l_progress;
+
         if (args[4] == ">") {
             pos = "wit";
-            area->statement = area->statement + 1;
-            args = playTestimony();
+            std::make_pair(args, l_progress) = area->jumpToStatement(area->statement() + 1);
+
+            if (l_progress == AreaData::TestimonyProgress::LOOPED) {
+                sendServerMessageArea("Last statement reached. Looping to first statement.");
+            }
         }
         if (args[4] == "<") {
             pos = "wit";
-            area->statement = area->statement - 1;
-            args = playTestimony();
+            std::make_pair(args, l_progress) = area->jumpToStatement(area->statement() - 1);
+
+            if (l_progress == AreaData::TestimonyProgress::STAYED_AT_FIRST) {
+                sendServerMessage("First statement reached.");
+            }
         }
+
         QString decoded_message = decodeMessage(args[4]); //Get rid of that pesky encoding first.
         QRegularExpression jump("(?<arrow>>)(?<int>[0,1,2,3,4,5,6,7,8,9]+)");
         QRegularExpressionMatch match = jump.match(decoded_message);
         if (match.hasMatch()) {
             pos = "wit";
-            area->statement = match.captured("int").toInt();
-            args= playTestimony();
+            std::make_pair(args, l_progress) = area->jumpToStatement(match.captured("int").toInt());
+
+            switch (l_progress){
+            case AreaData::TestimonyProgress::LOOPED:
+            {
+                sendServerMessageArea("Last statement reached. Looping to first statement.");
+            }
+            case AreaData::TestimonyProgress::STAYED_AT_FIRST:
+            {
+                sendServerMessage("First statement reached.");
+            }
+            case AreaData::TestimonyProgress::OK:
+            default:
+                // No need to handle.
+                break;
+            }
         }
     }
 
@@ -812,7 +839,7 @@ QString AOClient::dezalgo(QString p_text)
 
 bool AOClient::checkEvidenceAccess(AreaData *area)
 {
-    switch(area->evi_mod) {
+    switch(area->eviMod()) {
     case AreaData::EvidenceMod::FFA:
         return true;
     case AreaData::EvidenceMod::CM:
@@ -833,12 +860,7 @@ void AOClient::updateJudgeLog(AreaData* area, AOClient* client, QString action)
     QString ipid = client->getIpid();
     QString message = action;
     QString logmessage = QString("[%1]: [%2] %3 (%4) %5").arg(timestamp, uid, char_name, ipid, message);
-    int size = area->judgelog.size();
-    if (size == 10) {
-        area->judgelog.removeFirst();
-        area->judgelog.append(logmessage);
-    }
-    else area->judgelog.append(logmessage);
+    area->appendJudgelog(logmessage);
 }
 
 QString AOClient::decodeMessage(QString incoming_message)
@@ -862,7 +884,7 @@ void AOClient::loginAttempt(QString message)
             sendPacket("AUTH", {"0"}); // Client: "Login unsuccessful."
             sendServerMessage("Incorrect password.");
         }
-        server->areas.value(current_area)->logger->logLogin(this, authenticated, "moderator");
+        server->areas.value(current_area)->logLogin(current_char, ipid, authenticated, "moderator");
     }
     else if (server->auth_type == "advanced") {
         QStringList login = message.split(" ");
@@ -886,7 +908,7 @@ void AOClient::loginAttempt(QString message)
             sendPacket("AUTH", {"0"}); // Client: "Login unsuccessful."
             sendServerMessage("Incorrect password.");
         }
-        server->areas.value(current_area)->logger->logLogin(this, authenticated, username);
+        server->areas.value(current_area)->logLogin(current_char, ipid, authenticated, username);
     }
     else {
             qWarning() << "config.ini has an unrecognized auth_type!";
diff --git a/src/server.cpp b/core/src/server.cpp
similarity index 98%
rename from src/server.cpp
rename to core/src/server.cpp
index 6dfe42c..8af3463 100644
--- a/src/server.cpp
+++ b/core/src/server.cpp
@@ -168,7 +168,7 @@ void Server::updateCharsTaken(AreaData* area)
 {
     QStringList chars_taken;
     for (QString cur_char : characters) {
-        chars_taken.append(area->characters_taken.contains(getCharID(cur_char))
+        chars_taken.append(area->charactersTaken().contains(getCharID(cur_char))
                                ? QStringLiteral("-1")
                                : QStringLiteral("0"));
     }
@@ -176,7 +176,7 @@ void Server::updateCharsTaken(AreaData* area)
     AOPacket response_cc("CharsCheck", chars_taken);
 
     for (AOClient* client : clients) {
-        if (client->current_area == area->index){
+        if (client->current_area == area->index()){
             if (!client->is_charcursed)
                 client->sendPacket(response_cc);
             else {
@@ -276,7 +276,7 @@ void Server::loadServerConfig()
     auth_type = config.value("auth","simple").toString();
     modpass = config.value("modpass","").toString();
     bool maximum_statements_conversion_success;
-    maximum_statements = config.value("maximum_statements", "10").toInt(&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;
diff --git a/src/testimony_recorder.cpp b/core/src/testimony_recorder.cpp
similarity index 62%
rename from src/testimony_recorder.cpp
rename to core/src/testimony_recorder.cpp
index 312ebf9..88bc074 100644
--- a/src/testimony_recorder.cpp
+++ b/core/src/testimony_recorder.cpp
@@ -22,30 +22,29 @@
 void AOClient::addStatement(QStringList packet)
 {
     AreaData* area = server->areas[current_area];
-    int c_statement = area->statement;
+    int c_statement = area->statement();
     if (c_statement >= -1) {
-        if (area->test_rec == AreaData::TestimonyRecording::RECORDING) {
+        if (area->testimonyRecording() == AreaData::TestimonyRecording::RECORDING) {
             if (c_statement <= server->maximum_statements) {
                 if (c_statement == -1)
                     packet[14] = "3";
                 else
                     packet[14] = "1";
-                area->statement = c_statement + 1;
-                area->testimony.append(packet);
+                area->recordStatement(packet);
                 return;
             }
             else {
                 sendServerMessage("Unable to add more statements. The maximum amount of statements has been reached.");
             }
         }
-        else if (area->test_rec == AreaData::TestimonyRecording::ADD) {
+        else if (area->testimonyRecording() == AreaData::TestimonyRecording::ADD) {
                packet[14] = "1";
-               area->testimony.insert(c_statement,packet);
-               area->test_rec = AreaData::TestimonyRecording::PLAYBACK;
+               area->addStatement(c_statement, packet);
+               area->setTestimonyRecording(AreaData::TestimonyRecording::PLAYBACK);
             }
             else {
                 sendServerMessage("Unable to add more statements. The maximum amount of statements has been reached.");
-                area->test_rec = AreaData::TestimonyRecording::PLAYBACK;
+                area->setTestimonyRecording(AreaData::TestimonyRecording::PLAYBACK);
             }
     }
 }
@@ -53,15 +52,15 @@ void AOClient::addStatement(QStringList packet)
 QStringList AOClient::updateStatement(QStringList packet)
 {
     AreaData* area = server->areas[current_area];
-    int c_statement = area->statement;
-    area->test_rec = AreaData::TestimonyRecording::PLAYBACK;
-    if (c_statement <= 0 || area->testimony[c_statement].empty())
+    int c_statement = area->statement();
+    area->setTestimonyRecording(AreaData::TestimonyRecording::PLAYBACK);
+    if (c_statement <= 0 || area->testimony()[c_statement].empty())
         sendServerMessage("Unable to update an empty statement. Please use /addtestimony.");
     else {
         packet[14] = "1";
-        area->testimony.replace(c_statement, packet);
+        area->replaceStatement(c_statement, packet);
         sendServerMessage("Updated current statement.");
-        return area->testimony[c_statement];
+        return area->testimony()[c_statement];
     }
     return packet;
 }
@@ -69,28 +68,5 @@ QStringList AOClient::updateStatement(QStringList packet)
 void AOClient::clearTestimony()
 {
     AreaData* area = server->areas[current_area];
-    area->test_rec = AreaData::TestimonyRecording::STOPPED;
-    area->statement = -1;
-    area->testimony.clear(); //!< Empty out the QVector
-    area->testimony.squeeze(); //!< Release memory. Good idea? God knows, I do not.
+    area->clearTestimony();
 }
-
-QStringList AOClient::playTestimony()
-{
-    AreaData* area = server->areas[current_area];
-    int c_statement = area->statement;
-    if (c_statement > area->testimony.size() - 1) {
-        sendServerMessageArea("Last statement reached. Looping to first statement.");
-        area->statement = 1;
-        return area->testimony[area->statement];
-    }
-    if (c_statement <= 0) {
-        sendServerMessage("First statement reached.");
-        area->statement = 1;
-        return area->testimony[area->statement = 1];
-    }
-    else {
-        return area->testimony[c_statement];
-    }
-}
-
diff --git a/src/ws_client.cpp b/core/src/ws_client.cpp
similarity index 100%
rename from src/ws_client.cpp
rename to core/src/ws_client.cpp
diff --git a/src/ws_proxy.cpp b/core/src/ws_proxy.cpp
similarity index 100%
rename from src/ws_proxy.cpp
rename to core/src/ws_proxy.cpp
diff --git a/doxygen/akashi.qch b/doxygen/akashi.qch
index 4582a17..e440da0 100644
Binary files a/doxygen/akashi.qch and b/doxygen/akashi.qch differ
diff --git a/include/area_data.h b/include/area_data.h
deleted file mode 100644
index 8b17e2a..0000000
--- a/include/area_data.h
+++ /dev/null
@@ -1,359 +0,0 @@
-//////////////////////////////////////////////////////////////////////////////////////
-//    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 AREA_DATA_H
-#define AREA_DATA_H
-
-#include "include/logger.h"
-
-#include <QMap>
-#include <QString>
-#include <QSettings>
-#include <QDebug>
-#include <QTimer>
-#include <QElapsedTimer>
-
-class Logger;
-
-/**
- * @brief Represents an area on the server, a distinct "room" for people to chat in.
- */
-class AreaData : public QObject {
-  Q_OBJECT
-  public:
-    /**
-     * @brief Constructor for the AreaData class.
-     *
-     * @param p_name The name of the area. This must be in the format of `"X:YYYYYY"`, where `X` is an integer,
-     * and `YYYYYY` is the actual name of the area.
-     * @param p_index The index of the area in the area list.
-     */
-    AreaData(QString p_name, int p_index);
-
-    /**
-     * @brief The data for evidence in the area.
-     */
-    struct Evidence {
-        QString name; //!< The name of the evidence, shown when hovered over clientside.
-        QString description; //!< The longer description of the evidence, when the user opens the evidence window.
-        QString image; //!< A path originating from `base/evidence/` that points to an image file.
-    };
-
-    /**
-     * @brief The list of timers available in the area.
-     */
-    QList<QTimer*> timers;
-
-    /**
-     * @brief The user-facing and internal name of the area.
-     */
-    QString name;
-
-    /**
-     * @brief The index of the area in the server's area list.
-     */
-    int index;
-
-    /**
-     * @brief A list of the character IDs of all characters taken.
-     */
-    QList<int> characters_taken;
-
-    /**
-     * @brief A list of Evidence currently available in the area's court record.
-     *
-     * @details This contains *all* evidence, not just the ones a given side can see.
-     *
-     * @see HIDDEN_CM
-     */
-    QList<Evidence> evidence;
-
-    /**
-     * @brief The amount of clients inside the area.
-     */
-    int player_count;
-
-    /**
-     * @brief The status of an area.
-     *
-     * @details This is purely aesthetic, and serves no functional purpose from a gameplay perspective.
-     * It's only benefit is giving the users a rough idea as to what is going on in an area.
-     */
-    enum Status {
-      IDLE, //!< The area is currently not busy with anything, or the area is empty.
-      RP, //!< There is some (non-Ace Attorney-related) roleplay going on in the area.
-      CASING, //!< An Ace Attorney or Danganronpa-styled case is currently being held in the area.
-      LOOKING_FOR_PLAYERS, //!< Something is being planned in the area, but it needs more players.
-      RECESS, //!< The area is currently taking a break from casing, but will continue later.
-      GAMING //!< The users inside the area are playing some game outside of AO, and are using the area to communicate.
-    };
-
-    /// Exposes the metadata of the Status enum.
-    Q_ENUM(Status);
-
-    /**
-     * @brief The status of the area.
-     *
-     * @see Status
-     */
-    Status status;
-
-    /**
-     * @brief The IDs of all the owners (or Case Makers / CMs) of the area.
-     */
-    QList<int> owners;
-
-    /**
-     * @brief The list of clients invited to the area.
-     *
-     * @see LOCKED and SPECTATABLE for the benefits of being invited.
-     */
-    QList<int> invited;
-
-    /**
-     * @brief Determines who may traverse and communicate in the area.
-     */
-    enum LockStatus {
-      FREE,
-      LOCKED,
-      SPECTATABLE
-    };
-
-    /**
-     * @var LockStatus FREE
-     * Anyone may enter the area, and there are no restrictions on communicating in-character.
-     */
-
-    /**
-     * @var LockStatus LOCKED
-     * Only invited clients may enter the area, but those who are invited are free to communicate in-character.
-     *
-     * When an area transitions from FREE to LOCKED, anyone present in the area
-     * at the time of the transition is considered invited.
-     */
-
-    /**
-     * @var LockStatus SPECTATABLE
-     * Anyone may enter the area, but only invited clients may communicate in-character.
-     *
-     * When an area transitions from FREE to SPECTATABLE, anyone present in the area
-     * at the time of the transition is considered invited.
-     */
-
-    /// Exposes the metadata of the LockStatus enum.
-    Q_ENUM(LockStatus);
-
-    /**
-     * @brief The status of the area's accessibility to clients.
-     *
-     * @see LockStatus
-     */
-    LockStatus locked;
-
-    /**
-     * @brief The background of the area.
-     *
-     * @details Represents a directory's name in `base/background/` clientside.
-     */
-    QString background;
-
-    /**
-     * @brief If true, nobody may become the CM of this area.
-     */
-    bool is_protected;
-
-    /**
-     * @brief If true, clients are allowed to put on "shownames", custom names
-     * in place of their character's normally displayed name.
-     */
-    bool showname_allowed;
-
-    /**
-     * @brief If true, clients are allowed to use the cursed art of iniswapping in the area.
-     */
-    bool iniswap_allowed;
-
-    /**
-     * @brief If true, clients are allowed to send empty IC messages
-     */
-    bool blankposting_allowed;
-
-    /**
-     * @brief If true, the background of the area cannot be changed except by a moderator.
-     */
-    bool bg_locked;
-
-    /**
-     * @brief The hyperlink to the document of the area.
-     *
-     * @details Documents are generally used for cases or roleplays, where they contain the related game's
-     * rules. #document can also be something like "None" if there is no case or roleplay being run.
-     */
-    QString document;
-
-    /**
-     * @brief The Confidence Gauge's value for the Defence side.
-     *
-     * @details Unit is 10%, and the values range from 0 (= 0%) to 10 (= 100%).
-     */
-    int def_hp;
-
-    /**
-     * @brief The Confidence Gauge's value for the Prosecutor side.
-     *
-     * @copydetails #def_hp
-     */
-    int pro_hp;
-
-    /**
-     * @brief The title of the music currently being played in the area.
-     *
-     * @details Title is a path to the music file, with the starting point on
-     * `base/sounds/music/` clientside, with file extension.
-     */
-    QString current_music;
-
-    /**
-     * @brief The name of the client (or client's character) that started the currently playing music.
-     */
-    QString music_played_by;
-
-    /**
-     * @brief A pointer to a Logger, used to send requests to log data.
-     */
-    Logger* logger;
-
-    /**
-     * @brief The level of "authorisation" needed to be able to modify, add, and remove evidence in the area.
-     */
-    enum EvidenceMod{
-        FFA,
-        MOD,
-        CM,
-        HIDDEN_CM
-    };
-
-    /**
-     * @var EvidenceMod FFA
-     * "Free-for-all" -- anyone can add, remove or modify evidence.
-     */
-
-    /**
-     * @var EvidenceMod MOD
-     * Only mods can add, remove or modify evidence.
-     */
-
-    /**
-     * @var EvidenceMod CM
-     * Only Case Makers and Mods can add, remove or modify evidence.
-     */
-
-    /**
-     * @var EvidenceMod HIDDEN_CM
-     * Only Case Makers and Mods can add, remove or modify evidence.
-     *
-     * CMs can also hide evidence from various sides by putting `<owner=XXX>` into the evidence's description,
-     * where `XXX` is either a position, of a list of positions separated by `,`.
-     */
-
-    /**
-     * @brief The evidence mod of the area.
-     *
-     * @see EvidenceMod
-     */
-    EvidenceMod evi_mod;
-    QMap<QString, QString> notecards;
-
-    /**
-     * @brief The five "states" the testimony recording system can have in an area.
-     */
-    enum TestimonyRecording{
-        STOPPED,
-        RECORDING,
-        UPDATE,
-        ADD,
-        PLAYBACK,
-    };
-
-    /**
-     * @var TestimonyRecording STOPPED
-     * The testimony recorder is inactive and no ic-messages can be played back.
-     * If messages are inside the buffer when its stopped, the messages will remain until the recorder is set to RECORDING
-     */
-
-    /**
-     * @var TestimonyRecording RECORDING
-     * The testimony recorder is active and any ic-message send is recorded for playback.
-     * It does not differentiate between positions, so any message is recorded. Further improvement?
-     * When the recorder is started, it will clear the buffer and will make the first message the title.
-     * To prevent accidental recording by not disabling the recorder, a configurable buffer size can be set in the config.
-     */
-
-    /**
-     * @var TestimonyRecording UPDATE
-     * The testimony recorder is active and replaces the current message at the index with the next ic-message
-     * Once the IC-Message is send the recorder will default back into playback mode to prevent accidental overwriting of messages.
-     */
-
-    /**
-     * @var TestimonyRecording ADD
-     * The testimony recorder is active and inserts the next message after the currently displayed ic-message
-     * This will increase the size by 1.
-     */
-
-    /**
-     * @var TestimonyRecording PLAYBACK
-     * The testimony recorder is inactive and ic-messages in the buffer will be played back.
-     */
-
-    /// Exposes the metadata of the TestimonyRecording enum.
-    Q_ENUM(TestimonyRecording);
-    TestimonyRecording test_rec;
-
-
-    QVector<QStringList> testimony; //!< Vector of all statements saved. Index 0 is always the title of the testimony.
-    int statement; //!< Keeps track of the currently played statement.
-
-    /**
-    * @brief The judgelog of an area.
-    *
-    * @details This list contains up to 10 recorded packets of the most recent judge actions (WT/CE or penalty updates) in an area.
-    */
-    QStringList judgelog;
-
-    /**
-     * @brief The last IC packet sent in an area.
-     */
-    QStringList last_ic_message;
-
-    /**
-     * @brief The value of logger in config.ini.
-     */
-    QString log_type;
-
-    /**
-     * @brief Whether or not to force immediate text processing in this area.
-     */
-    bool force_immediate;
-
-    /**
-     * @brief Whether or not music is allowed in this area. If false, only CMs can change the music.
-     */
-    bool toggle_music;
-};
-
-#endif // AREA_DATA_H
diff --git a/src/area_data.cpp b/src/area_data.cpp
deleted file mode 100644
index dfedd32..0000000
--- a/src/area_data.cpp
+++ /dev/null
@@ -1,73 +0,0 @@
-//////////////////////////////////////////////////////////////////////////////////////
-//    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/>.        //
-//////////////////////////////////////////////////////////////////////////////////////
-#include "include/area_data.h"
-
-AreaData::AreaData(QString p_name, int p_index) :
-    index(p_index),
-    player_count(0),
-    status(IDLE),
-    locked(FREE),
-    document("No document."),
-    def_hp(10),
-    pro_hp(10),
-    judgelog(),
-    last_ic_message()
-{
-    QStringList name_split = p_name.split(":");
-    name_split.removeFirst();
-    name = name_split.join(":");
-    QSettings areas_ini("config/areas.ini", QSettings::IniFormat);
-    areas_ini.setIniCodec("UTF-8");
-    areas_ini.beginGroup(p_name);
-    background = areas_ini.value("background", "gs4").toString();
-    is_protected = areas_ini.value("protected_area", "false").toBool();
-    iniswap_allowed = areas_ini.value("iniswap_allowed", "true").toBool();
-    bg_locked = areas_ini.value("bg_locked", "false").toBool();
-    QString configured_evi_mod = areas_ini.value("evidence_mod", "FFA").toString().toLower();
-    blankposting_allowed = areas_ini.value("blankposting_allowed","true").toBool();
-    force_immediate = areas_ini.value("force_immediate", "false").toBool();
-    toggle_music = areas_ini.value("toggle_music", "true").toBool();
-    showname_allowed = areas_ini.value("shownames_allowed", "true").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();
-    log_type = config_ini.value("logger","modcall").toString();
-    config_ini.endGroup();
-    if (log_size == 0)
-        log_size = 500;
-    logger = new Logger(log_size, this);
-    QTimer* timer1 = new QTimer();
-    timers.append(timer1);
-    QTimer* timer2 = new QTimer();
-    timers.append(timer2);
-    QTimer* timer3 = new QTimer();
-    timers.append(timer3);
-    QTimer* timer4 = new QTimer();
-    timers.append(timer4);
-
-    if (configured_evi_mod == "cm")
-        evi_mod = EvidenceMod::CM;
-    else if (configured_evi_mod == "mod")
-        evi_mod = EvidenceMod::MOD;
-    else if (configured_evi_mod == "hiddencm")
-        evi_mod = EvidenceMod::HIDDEN_CM;
-    else
-        evi_mod = EvidenceMod::FFA;
-}
diff --git a/src/logger.cpp b/src/logger.cpp
deleted file mode 100644
index a4c33f9..0000000
--- a/src/logger.cpp
+++ /dev/null
@@ -1,120 +0,0 @@
-//////////////////////////////////////////////////////////////////////////////////////
-//    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/>.        //
-//////////////////////////////////////////////////////////////////////////////////////
-#include "include/logger.h"
-
-void Logger::logIC(AOClient *client, AOPacket *packet)
-{
-    QString message = packet->contents[4];
-    addEntry(buildEntry(client, "IC", message));
-}
-
-void Logger::logOOC(AOClient* client, AOPacket* packet)
-{
-    QString message = packet->contents[1];
-    addEntry(buildEntry(client, "OOC", message));
-}
-
-void Logger::logModcall(AOClient* client, AOPacket* packet)
-{
-    QString message = packet->contents[0];
-    addEntry(buildEntry(client, "MODCALL", message));
-}
-
-void Logger::logCmd(AOClient *client, AOPacket *packet, QString cmd, QStringList args)
-{
-    // Some commands contain sensitive data, like passwords
-    // These must be filtered out
-    if (cmd == "login") {
-        addEntry(buildEntry(client, "LOGIN", "Attempted login"));
-    }
-    else if (cmd == "rootpass") {
-        addEntry(buildEntry(client, "USERS", "Root password created"));
-    }
-    else if (cmd == "adduser" && !args.isEmpty()) {
-        addEntry(buildEntry(client, "USERS", "Added user " + args[0]));
-    }
-    else
-        logOOC(client, packet);
-}
-
-void Logger::logLogin(AOClient *client, bool success, QString modname)
-{
-    QString message = success ? "Logged in as " + modname : "Failed to log in as " + modname;
-    addEntry(buildEntry(client, "LOGIN", message));
-}
-
-QString Logger::buildEntry(AOClient *client, QString type, QString message)
-{
-    QString time = QDateTime::currentDateTime().toString("ddd MMMM d yyyy | hh:mm:ss");
-    QString area_name = area->name;
-    QString char_name = client->current_char;
-    QString ipid = client->getIpid();
-
-    QString log_entry = QStringLiteral("[%1][%2][%6] %3(%4): %5\n")
-            .arg(time)
-            .arg(area_name)
-            .arg(char_name)
-            .arg(ipid)
-            .arg(message)
-            .arg(type);
-    return log_entry;
-}
-
-void Logger::addEntry(QString entry)
-{
-    if (buffer.length() < max_length) {
-        buffer.enqueue(entry);
-        if (area->log_type == "full") {
-           flush();
-        }
-    }
-    else {
-        buffer.dequeue();
-        buffer.enqueue(entry);
-    }
-}
-
-void Logger::flush()
-{
-    QDir dir("logs/");
-    if (!dir.exists()) {
-        dir.mkpath(".");
-    }
-
-    QFile logfile;
-    if (area->log_type == "modcall") {
-        logfile.setFileName(QString("logs/report_%1_%2.log").arg((area->name), (QDateTime::currentDateTime().toString("yyyy-MM-dd_hhmmss"))));
-    }
-    else if (area->log_type == "full") {
-        logfile.setFileName(QString("logs/%1.log").arg(QDate::currentDate().toString("yyyy-MM-dd")));
-    }
-    else {
-        qCritical("Invalid logger set!");
-    }
-    if (logfile.open(QIODevice::WriteOnly | QIODevice::Append)) {
-    QTextStream file_stream(&logfile);
-    while (!buffer.isEmpty())
-        file_stream << buffer.dequeue();
-        }
-    logfile.close();
-}
-
-QQueue<QString> Logger::getBuffer()
-{
-    return buffer;
-}
diff --git a/tests/tests.pro b/tests/tests.pro
new file mode 100644
index 0000000..8bc046a
--- /dev/null
+++ b/tests/tests.pro
@@ -0,0 +1,4 @@
+TEMPLATE = subdirs
+
+SUBDIRS += \
+    unittest_area
diff --git a/tests/tests_common.pri b/tests/tests_common.pri
new file mode 100644
index 0000000..f9dc917
--- /dev/null
+++ b/tests/tests_common.pri
@@ -0,0 +1,12 @@
+QT += network websockets core sql testlib
+
+CONFIG += qt console warn_on depend_includepath testcase no_testcase_installs
+CONFIG -= app_bundle
+
+DESTDIR = $$PWD/../bin_tests
+
+win32: LIBS += -L$$PWD/../bin/ -lcore
+else:unix: LIBS += -L$$PWD/../bin/ -lcore
+
+INCLUDEPATH += $$PWD/../core
+DEPENDPATH += $$PWD/../core
diff --git a/tests/unittest_area/tst_unittest_area.cpp b/tests/unittest_area/tst_unittest_area.cpp
new file mode 100644
index 0000000..b9b8afd
--- /dev/null
+++ b/tests/unittest_area/tst_unittest_area.cpp
@@ -0,0 +1,241 @@
+#include <QtTest>
+
+#include <include/area_data.h>
+
+Q_DECLARE_METATYPE(AreaData::Side);
+
+namespace tests {
+namespace unittests {
+
+/**
+ * @brief Unit Tester class for the area-related functions.
+ */
+class Area : public QObject
+{
+    Q_OBJECT
+
+public:
+    /**
+     * @brief An AreaData pointer to test with.
+     */
+    AreaData* m_area;
+
+private slots:
+    /**
+     * @brief Initialises every tests with creating a new area with the title "Test Area", and the index of 0.
+     */
+    void init();
+
+    /**
+     * @brief Cleans up the area pointer.
+     */
+    void cleanup();
+
+    /**
+     * @test Tests various scenarios of a client joining and leaving, and what it changes on the area.
+     */
+    void clientJoinLeave();
+
+    /**
+     * @brief The data function for areaStatuses().
+     */
+    void areaStatuses_data();
+
+    /**
+     * @test Tests various attempts at changing area statuses.
+     */
+    void areaStatuses();
+
+    /**
+     * @brief The data function for changeHP().
+     */
+    void changeHP_data();
+
+    /**
+     * @test Tests changing Confidence bar values for the sides.
+     */
+    void changeHP();
+
+    /**
+     * @test Tests changing character in the area.
+     */
+    void changeCharacter();
+
+    void testimony();
+};
+
+void Area::init()
+{
+    m_area = new AreaData("Test Area", 0);
+}
+
+void Area::cleanup()
+{
+    delete m_area;
+}
+
+void Area::clientJoinLeave()
+{
+    {
+        // There must be exactly one client in the area, and it must have a charid of 5.
+        m_area->clientJoinedArea(5);
+
+        QCOMPARE(m_area->charactersTaken().size(), 1);
+        QCOMPARE(m_area->charactersTaken().at(0), 5);
+    }
+    {
+        // No clients must be left in the area.
+        m_area->clientLeftArea(5);
+
+        QCOMPARE(m_area->charactersTaken().size(), 0);
+    }
+}
+
+void Area::areaStatuses_data()
+{
+    QTest::addColumn<QString>("statusCall");
+    QTest::addColumn<AreaData::Status>("expectedStatus");
+    QTest::addColumn<bool>("isSuccessful");
+
+    QTest::newRow("Idle") << "idle" << AreaData::Status::IDLE << true;
+    QTest::newRow("RP") << "rp" << AreaData::Status::RP << true;
+    QTest::newRow("Casing") << "casing" << AreaData::Status::CASING << true;
+    QTest::newRow("Looking for players (long)") << "looking-for-players" << AreaData::Status::LOOKING_FOR_PLAYERS << true;
+    QTest::newRow("Looking for players (short)") << "lfp" << AreaData::Status::LOOKING_FOR_PLAYERS << true;
+    QTest::newRow("Gaming") << "gaming" << AreaData::Status::GAMING << true;
+    QTest::newRow("Recess") << "recess" << AreaData::Status::RECESS << true;
+    QTest::newRow("Nonsense") << "blah" << AreaData::Status::IDLE << false;
+}
+
+void Area::areaStatuses()
+{
+    QFETCH(QString, statusCall);
+    QFETCH(AreaData::Status, expectedStatus);
+    QFETCH(bool, isSuccessful);
+
+    bool l_success = m_area->changeStatus(statusCall);
+
+    QCOMPARE(m_area->status(), expectedStatus);
+    QCOMPARE(l_success, isSuccessful);
+}
+
+void Area::changeHP_data()
+{
+    QTest::addColumn<AreaData::Side>("side");
+    QTest::addColumn<int>("setHP");
+    QTest::addColumn<int>("expectedHP");
+
+    QTest::newRow("Set = Expected (DEF)") << AreaData::Side::DEFENCE << 3 << 3;
+    QTest::newRow("Set = Expected (PRO)") << AreaData::Side::PROSECUTOR << 5 << 5;
+    QTest::newRow("Below Zero (DEF)") << AreaData::Side::DEFENCE << -5 << 0;
+    QTest::newRow("Below Zero (PRO)") << AreaData::Side::PROSECUTOR << -7 << 0;
+    QTest::newRow("Above Ten (DEF)") << AreaData::Side::DEFENCE << 12 << 10;
+    QTest::newRow("Above Ten (PRO)") << AreaData::Side::PROSECUTOR << 14 << 10;
+}
+
+void Area::changeHP()
+{
+    QFETCH(AreaData::Side, side);
+    QFETCH(int, setHP);
+    QFETCH(int, expectedHP);
+
+    m_area->changeHP(side, setHP);
+
+    if (AreaData::Side::DEFENCE == side) {
+        QCOMPARE(expectedHP, m_area->defHP());
+    } else {
+        QCOMPARE(expectedHP, m_area->proHP());
+    }
+}
+
+void Area::changeCharacter()
+{
+    {
+        // A client with a charid of 6 joins. There's only them in there.
+        m_area->clientJoinedArea(6);
+
+        QCOMPARE(m_area->charactersTaken().size(), 1);
+        QCOMPARE(m_area->charactersTaken().at(0), 6);
+    }
+    {
+        // Charid 7 is marked as taken. No other client in the area still.
+        // Charids 6 and 7 are taken.
+        m_area->changeCharacter(-1, 7);
+
+        QCOMPARE(m_area->playerCount(), 1);
+        QCOMPARE(m_area->charactersTaken().size(), 2);
+        QCOMPARE(m_area->charactersTaken().at(0), 6);
+        QCOMPARE(m_area->charactersTaken().at(1), 7);
+    }
+    {
+        // Client switches to charid 8.
+        // Charids 8 and 7 are taken.
+        m_area->changeCharacter(6, 8);
+
+        QCOMPARE(m_area->playerCount(), 1);
+        QCOMPARE(m_area->charactersTaken().size(), 2);
+        QCOMPARE(m_area->charactersTaken().at(0), 7);
+        QCOMPARE(m_area->charactersTaken().at(1), 8);
+    }
+    {
+        // Charid 7 is unlocked for use.
+        // Charid 8 is taken.
+        m_area->changeCharacter(7, -1);
+
+        QCOMPARE(m_area->playerCount(), 1);
+        QCOMPARE(m_area->charactersTaken().size(), 1);
+        QCOMPARE(m_area->charactersTaken().at(0), 8);
+    }
+}
+
+void Area::testimony()
+{
+    QVector<QStringList> l_testimony = {
+        {"A"},
+        {"B"},
+        {"C"},
+        {"D"},
+    };
+
+    {
+        // Add all statements, and check that they're added.
+        for (const auto& l_statement : l_testimony)
+        {
+            m_area->recordStatement(l_statement);
+
+            QCOMPARE(l_statement, m_area->testimony().at(m_area->statement() - 1));
+        }
+    }
+    {
+        // Restart testimony, advance three times.
+        m_area->restartTestimony();
+
+        for (int i = 0; i < l_testimony.size() - 1; i++) {
+           const auto& l_results = m_area->jumpToStatement(m_area->statement() + 1);
+
+           QCOMPARE(l_results.first, l_testimony.at(i + 1));
+           QCOMPARE(l_results.second, AreaData::TestimonyProgress::OK);
+        }
+    }
+    {
+        // Next advancement loops the testimony.
+        const auto& l_results = m_area->jumpToStatement(m_area->statement() + 1);
+
+        QCOMPARE(l_results.first, l_testimony.at(0));
+        QCOMPARE(l_results.second, AreaData::TestimonyProgress::LOOPED);
+    }
+    {
+        // Going back makes the testimony stay at the first statement.
+        const auto& l_results = m_area->jumpToStatement(m_area->statement() - 1);
+
+        QCOMPARE(l_results.first, l_testimony.at(0));
+        QCOMPARE(l_results.second, AreaData::TestimonyProgress::STAYED_AT_FIRST);
+    }
+}
+
+}
+}
+
+QTEST_APPLESS_MAIN(tests::unittests::Area)
+
+#include "tst_unittest_area.moc"
diff --git a/tests/unittest_area/unittest_area.pro b/tests/unittest_area/unittest_area.pro
new file mode 100644
index 0000000..cd33e8d
--- /dev/null
+++ b/tests/unittest_area/unittest_area.pro
@@ -0,0 +1,5 @@
+QT -= gui
+
+include(../tests_common.pri)
+
+SOURCES +=  tst_unittest_area.cpp