/**
  \file main.c
  \author FC
  \date 2026
  \brief SCAOS game and networking logic

  Main source file of SCAOS. All incoming connections, packets ooc commands
  and internal server logic is handled here.

  Some macros are injected into the code when makelists.c is run through .autogen_macros.h
  During preprocessing, commands, help pages, charlist, etc are generated.

  The general flow of the program and the client lifecycle is pretty simple. 
  
  1. main() initializes the server and listens for new connections in its while loop.
    In main(), when a client establishes a tcp connection he's added to connections[] as Conn,
    now all of his packets will be processed in processPacket()

  2. Client sends a HI#% thus a Player in players[] is created for him. 
    Now processPacket() loops through handshakePacketDispatchTable[] when the client sends a valid AO packet 
    (refer to the networking protocol).

  3. When the player finally sends RD#%, handle_RD() puts the player on a joined state through joinArea(),
    which means processPacket() check the commonPacketDispatchTable[] for any new packets.

  4. If the player sends a CT packet and its second argument starts with a /, the *CommandDispatchTable[]s 
    will be checked.

  5. When the player leaves the server, main() handles it disconnection, resetting the struct at the position of
    his Conn in conns[] and his Player at players[].

    Released under CC0 1.0 (https://creativecommons.org/publicdomain/zero/1.0/)
    plus a waiver of all other intellectual property. The goal of this work is
    be and remain completely in the public domain forever, available for any use
    whatsoever.
*/

#if defined(_WIN32) || defined(WIN32) || defined(__WIN32__) ||   defined(__NT__) || defined(__APPLE__)
  #warning NIGGER OS DETECTED.
#endif

#define _POSIX_C_SOURCE 200809L

/* ==================================== INITIAL MACRO DECLARATIONS ==================================== */

/* SOFTWARE INFO */
#define SERVER_SOFTWARE "SCAOS"
#define SERVER_VERSION "1.0.0"

#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdint.h>

/* JEWDOWS PORTABILITY SLOP */
#ifdef _WIN32
    #ifdef _WIN32_WINNT
     #undef _WIN32_WINNT
    #endif
    #define _WIN32_WINNT _WIN32_WINNT_WINXP

    #include <winsock2.h>
    #include <ws2tcpip.h>
    #include <windows.h>
    #pragma comment(lib, "ws2_32.lib")
    typedef SOCKET socket_t;
    #define sleep(x) Sleep((x) * 1000)
    #define SOCKET_ERROR_CHECK(x) ((x) == INVALID_SOCKET)
    #define SOCKET_IO_ERROR_CHECK(x) ((x) == SOCKET_ERROR)
    #define SEND_FLAGS 0
    #define INVALID_FD INVALID_SOCKET
    const char *inet_ntop_xp(int af, const void *src, char *dst, socklen_t size) {
        if(af == AF_INET) {
            struct sockaddr_in in = {0};
            in.sin_family = AF_INET;
            memcpy(&in.sin_addr, src, sizeof(struct in_addr));
            DWORD addrlen = (DWORD)size;
            if (WSAAddressToStringA((LPSOCKADDR)&in, sizeof(in), NULL, dst, &addrlen) != 0) {
                return NULL;
            }
            return dst;
        }
        return NULL;
    }
    #define inet_ntop inet_ntop_xp
    #define closeSocket closesocket
    #define FOR_EACH_FD(fd, set, maxfd) \
    for (u_int i = 0; i < (set)->fd_count; i++) \
        for ((fd) = (set)->fd_array[i]; (fd) != INVALID_FD; (fd) = INVALID_FD)
#else
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <netinet/in.h>
    #include <netdb.h>
    #include <sys/socket.h>
    // #include <signal.h>
    typedef int socket_t;
    #define SOCKET_ERROR_CHECK(x) ((x) < 0)
    #define SOCKET_IO_ERROR_CHECK(x) ((x) <= 0)
    #define SEND_FLAGS MSG_NOSIGNAL
    #define INVALID_FD -1
    #define closeSocket close
    #define FOR_EACH_FD(fd, set, maxfd) \
        for (fd = 0; fd <= maxfd; fd++) \
            if (!FD_ISSET(fd, set)) continue; \
            else
#endif

#include <sys/types.h>
#include <sys/time.h>
#include <time.h>

/* MACROS NEEDED FOR BUFFERS */
#define STR(x) #x
#define XSTR(x) STR(x)
#define DIGITS(x) (sizeof(XSTR(x)) - 1)
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
#define STRLEN_CT(s) (sizeof(s) - 1)

#include "../config/config.h"
#ifndef USE_RUNTIME_LISTS
    #include "../config/.autogen_macros.h"
#else
    #undef DIGITS
    #define DIGITS(x) 5
    #define CHAR_LIST_COUNT 4096
#endif


#if DEBUG == 1
    #define DEBUG_PRINT(line) do { line; } while(0)
#else
    #define DEBUG_PRINT(line) do { } while(0)
#endif

/*
    This is where >SOME< macros to milimetrically calculate the size of buffers are declared. 
    If it segfaults try adding a +1488
*/
#define COMMON_PACKET_BYTES_EXTENSION 6 ///<AA# #%\0
#define SUM_COMMON_EXT(a) ((size_t)((a) + (COMMON_PACKET_BYTES_EXTENSION))) ///<Apply COMMON_PACKET_BYTES_EXTENSION to (a)
#define SUM_OOC_HOST_EXT(a) ((size_t)((a) + SUM_COMMON_EXT(STRLEN_CT(OOC_HOSTNAME) + 1))) ///<Apply OOC_HOSTNAME string length to a.
#define EVIDENCE_LIST_PACKET_SIZE SUM_COMMON_EXT((EVIDENCE_NAME_MAX_CHARS + EVIDENCE_DESC_MAX_CHARS + EVIDENCE_IMAGE_MAX_CHARS + 3) * EVIDENCE_MAX_COUNT) - 1

#define MS_PACKET_LENGTH                      \
     SUM_COMMON_EXT(                          \
     1 +                            /*desk*/        \
     EMOTE_NAME_MAX_LENGTH +        /*preanim name*/ \
     CHAR_NAME_MAX_LENGTH  +        /*charname*/ \
     EMOTE_NAME_MAX_LENGTH +        /*emote name*/ \
     IC_MESSAGE_MAX_LENGTH +        /*message*/ \
     POS_NAME_MAX_LENGTH +          /*side*/     \
     SFX_NAME_MAX_LENGTH +          /*sfx*/      \
     1 +                            /*emote mod*/ \
     DIGITS(CHAR_LIST_COUNT) +      /*char id*/    \
     SFX_DELAY_MS_MAX_LENGTH +      /*sfx ms (set in config.h)*/ \
     1 + 1 + SHOUT_NAME_MAX_LENGTH + /*shout number + & + name*/ \
     DIGITS(EVIDENCE_MAX_COUNT) +   /*evidence*/ \
     1 +                            /*flip*/ \
     1 +                            /*realization*/\
     1 +                            /*color*/\
     SHOWNAME_MAX_LENGTH +          /*showname*/\
     DIGITS(CHAR_LIST_COUNT) +      /*other (pair)*/\
     (4 + 5 + 4) +                  /*offset (-100) <and> (-100)*/\
     1 +                            /*noninterrupting*/\
     1 +                            /*sfx_loop*/\
     1 +                            /*screenshake*/\
                                    /*SKIPPING frames_**/ \
     1 +                            /*additive*/\
     EFFECT_NAME_MAX_LENGTH +       /*effect*/\
     BLIPS_NAME_MAX_LENGTH +        /*blips*/\
     27)                            /*# for the amount of args expected*/


#define SERVER_MS_PACKET_LENGTH MS_PACKET_LENGTH + SHOWNAME_MAX_LENGTH + EMOTE_NAME_MAX_LENGTH + 3 + 1 + 3 + 1 + (4)
#define TI_BUFFER_SIZE SUM_COMMON_EXT(DIGITS(AREA_TIMERS_MAX) + 3 + 8)
#define PR_BUFFER_SIZE SUM_COMMON_EXT(DIGITS(MAX_PLAYERS) + 1 + 2)
#define FEATURES_PACKET_STRING "\
FL#\
yellowtext#\
flipping#\
customobjections#\
fastloading#\
noencryption#\
deskmod#\
evidence#\
cccc_ic_support#\
arup#\
looping_sfx#\
additive#\
effects#\
y_offset#\
expanded_desk_mods#\
auth_packet#\
%"

#define ASS_PACKET_STRING "ASS#" ASSET_LINK "#%"


#define AREA_LOCK_STATUS_LIST \
    X(FREE) \
    X(SPECTATABLE) \
    X(LOCKED)

enum AREA_LOCK_STATUS {
#define X(name) name,
    AREA_LOCK_STATUS_LIST
#undef X
};

#define X(name) #name,
static const char *AREA_LOCK_STATUS_NAMES[] = {AREA_LOCK_STATUS_LIST}; /**< Array with area lockstatus name strings*/
#undef X

/*These are the available testimony status*/
#define AREA_TESTIMONY_STATUS_PAUSED 0      /**< do nothing */
#define AREA_TESTIMONY_STATUS_RECORDING 1   /**< add ic messages by testifying players to area->testimonyStatements */
#define AREA_TESTIMONY_STATUS_TESTIFYING 2  /**< enable controls */
#define AREA_TESTIMONY_STATUS_ADDING 3      /**< add a message after the one selected by area->currentStatement */
#define AREA_TESTIMONY_STATUS_AMENDING 4    /**< edit the message selected by area->currentStatement */

/*AREA CREATION*/

/**
  Evidence structure.
  The size of the buffers are decided by their values in config.h.

  \param name
    Name of the evidence.
  \param description
    Body of the evidence.
  \param image
    Name of the image found in base/evidence/example.png.
*/

typedef struct{
    char description[EVIDENCE_DESC_MAX_CHARS];
    char name[EVIDENCE_NAME_MAX_CHARS];
    char image[EVIDENCE_IMAGE_MAX_CHARS];
} Evidence;

/**
  Timer structure.

  Used for area timers and the only global one.

  \param timer
    Duration of the timer in seconds.
  \param started
    Boolean. Will be sent to the player if 1.
  \param show
    Boolean. If the timer is not started and show is 0, it will not be sent to new players.
*/

typedef struct{
    uint16_t timer;
    uint8_t started;
    uint8_t show;
} Timer;

/**
  Area structure.

  Members marked as "Server setting" are meant to be able to be changed by server logic.
  Members marked as "Initialized" are initialized on main() with a default value.

  Most of these can be customized in config/arealist.h

  \param id
    Initialized. Index in areaList.
  \param name
    Name to display on the area/music list.
  \param players
    Server setting. Amount of players in the current area. Starts as 0.
  \param background
    Server setting. Area background folder name.
  \param areaMessage
    Server setting. Message displayed by the host when joining the area.
  \param currentTrack
    Server setting. Name of the song that is currently playing in the area.
  \param evidence
    Server setting. Array with the current evidence. Length declared in config.h. See Evidence.
  \param timers
    Server setting. Array with the status of the current area timers. Length declared in config.h. See Timer.
  \param lockStatus
    Server setting. See AREA_LOCK_STATUS_LIST. Starts as FREE (0).
  \param status
    Server setting. See AREA_LOCK_STATUS_LIST in config.h. Starts as IDLE (0).
  \param testimonyStatements
    Server setting. Array of SERVER_MS_PACKET_LENGTH containing all raw packets recorded during a testimony.
  \param currentStatement
    Server setting. Points to the currently selected packet/statement.
  \param testimonyStatus
    Server setting. See AREA_TESTIMONY_STATUS_*. Starts as AREA_TESTIMONY_STATUS_PAUSED (0).
  \param hp
    Initialized. Server setting. Array containing the bar of every side. Starts as [10, 10].
  \param autoPair
    Initialized. Server setting. Array with the id of every player in autopair. Starts as [MAX_PLAYERS, maxPlayers]].
  \param didntSpeak
    Server setting. Id of the player to be replaced by another in autopair.
*/

typedef struct{
    char background[BACKGROUND_NAME_MAX_LENGTH];
    char areaMessage[AREA_MESSAGE_MAX_LENGTH];
    const char *name;
    char currentTrack[SONG_NAME_MAX_LENGTH];
    Evidence evidence[EVIDENCE_MAX_COUNT];
    Timer timers[AREA_TIMERS_MAX];
    char testimonyStatements[AREA_MAX_TESTIMONY_STATEMENTS][SERVER_MS_PACKET_LENGTH];
    char (*currentStatement)[SERVER_MS_PACKET_LENGTH];
    enum AREA_LOCK_STATUS lockStatus;
    uint8_t autoPair[2];
    uint8_t hp[2];
    uint8_t didntSpeak;
    uint8_t status;
    uint8_t testimonyStatus;
    uint8_t id;
    uint8_t players;
} Area;

/*MUSICLIST CREATION*/
/**
  Song structure.
  Loaded from config/musiclist.txt into config/.autogen_macros.h by makelists.c

  \param name
    Name to show on the music list.
  \param realName
    Optional parameter. Link containing the "real name" of the song. Usually a link.
*/
typedef struct{
    const char *name;
    const char *realName;
} Song;

/**
  Player structure

  Members marked as "Server setting" are meant to be able to be changed by server logic.
  Members marked as "Initialized" are initialized on processPacket() for loop
  with a default value.

  Most of these can be customized in config/arealist.h

  \param id
    Initialized. Index in players[].
  \param connectionId
    Initialized. Socket id AKA file descriptor (fd). 
    Set to INVALID_FD on main(), then set to the fd of the connection that sent a HI#% in processPacket().
    Set to INVALID_FD again on closeFd() (usually through) disconnectClient()
    TODO: Make this a pointer to Conn instead

  \param charId
    Initialized. Set in SC. Server setting. Starts at CHAR_LIST_COUNT (none).
  \param areaId
    Server setting. Set in MC. Player's current area id. Starts at 0.
  \param charName
    Server setting. Set in MS (can be different
    that the one corresponding to charList[player->charId] because iniswap yes. Character folder name.
    Length (CHAR_NAME_MAX_LENGTH) is set automatically to the longest name
    found in config/charlist.txt if the one on config/config.h is
    smaller than that one.
  \param showname
    Server setting. Set in MS. Player display name.
  \param oocName
    Server setting. Set in CT. Name displayed on OOC. Can't be empty/repeated/have a <dollar>.
  \param pos
    Server setting. Set in MS or /pos <pos>, /forcepos, etc. Player's position.

  \param otherId
    Initialized. Server setting. Set in /pair <pid> or in MS (when other id is 
    received through MS the server looks for a player in the area using the character of the received id).
    Id of the client the player is pairing with. Starts at MAX_PLAYERS (none).
  \param emote
    Server setting. Set in MS. Last emote/sprite sent. Used to send the right emote when pairing.
  \param offset_x
    Server setting. Set in MS. Last offset x sent.
  \param offset_y
    Server setting. Set in MS. Last offset y sent.

  \param areaInviteFlags
    Server setting. Set in /areainvite. int where every bit represents an area. It becomes 1 if the player gets invited.
    Being invited to an area means that the lock status is ignored when joining.
    Other servers use this for more TODO.

  \param lastIcMessage
    Server setting. Set in MS. Last IC message sent by the player in MS. Used to prevent duplicate messages.

  \param lastMessageTime
    Server setting. Timestamp of the last packet. Used to count violations.
  \param violations
    Server setting. Incremented when a packet is sent before FLOODGUARD_MAX_SECONDS (config.h) seconds passed since lastMessageTime.
    If violations == FLOODGUARD_VIOLATIONS_MAX (config.h), the player will not be able to send more packets until
    FLOODGUARD_MAX_SECONDS pass since lastMessageTime

  \param flags
  Server setting. See PLAYER_*_FLAG.
*/

typedef struct{
    char lastIcMessage[IC_MESSAGE_MAX_LENGTH];
    char emote[EMOTE_NAME_MAX_LENGTH];
    char charName[CHAR_NAME_MAX_LENGTH];
    char showname[SHOWNAME_MAX_LENGTH];
    char oocName[OOC_NAME_MAX_LENGTH];
    char pos[POS_NAME_MAX_LENGTH];
    // char clientName[12];
    // char clientVersion[10];
    time_t lastMessageTime;
    uint16_t charId;
    socket_t connectionId;
    uint16_t flags;
    uint16_t areaInviteFlags;
    uint8_t id;
    uint8_t areaId;
    //https://vgmtreasurechest.com/soundtracks/half-life-2-full-soundtrack-2014/lbzsopke/05.%20CP%20Violation.mp3
    uint8_t violations;
    uint8_t otherId;
    int8_t offset_x;
    int8_t offset_y;
} Player;

#define PLAYER_JOINED_FLAG          (1 << 0)   /**< Set on the first joinArea()*/
#define PLAYER_LEAVING_FLAG         (1 << 1)   /**< Set when the player gets disconnected on disconnectClient()*/
#define PLAYER_MOD_FLAG             (1 << 2)   /**< Set on succesful auth*/
#define PLAYER_CM_FLAG              (1 << 3)   /**< Set on /cm*/
#define PLAYER_TESTIFYING_FLAG      (1 << 4)   /**< Set on /testify*/
#define PLAYER_OTR_FLAG             (1 << 5)   /**< Set on /otr. */
#define PLAYER_MUTE_FLAG            (1 << 6)   /**< Set on /mute*/
#define PLAYER_GIMP_FLAG            (1 << 7)   /**< Set on /gimp*/
#define PLAYER_DISEMVOWEL_FLAG      (1 << 8)   /**< Set on /disemvowel*/
#define PLAYER_FIRSTPERSON_FLAG     (1 << 9)   /**< Set on /fp*/
#define PLAYER_NUKE_FLAG            (1 << 10)  /**< Set on /nuke*/

#define PLAYER_AUTOPAIR_FLAG        (1 << 11)  /**< Set on /autopair. Initialized on 1 if area status is not casing
                                                   and set to 0 when /status casing*/

#define PLAYER_HIDE_DESK_FLAG       (1 << 12)  /**< Set on /desk. Initialized on 1 if first IC message is in /pos wit
                                                   (see PLAYER_SENT_MESSAGE_FLAG) and set to 0 when /status casing.*/

#define PLAYER_SENT_MESSAGE_FLAG    (1 << 13)  /**< Set on MS when the player sends the first IC message*/
#define PLAYER_FLIP_FLAG            (1 << 14)  /**< Set on MS when the client ticks the flip box*/
#define PLAYER_LOGGING_IN_FLAG      (1 << 15)  /**< Set on /login when no password is sent to allow 
                                                   the user to enter the password safely. Unset after attempting to log-in.*/

/**
  Connections structure

  This is how we treat connections in a way that is separate from players.
  All of this have an arbitrary initial value when created.

  \param fd
    File descriptor.
    Assigned to a Player as connectionId during the handshake. See Player.
    Initialized to INVALID_FD on startup, set to the fd of the sender on
    FOR_EACH_FD() when the Conn is properly created.
    Set to 0 on closeFd().
    TODO: use a pointer to Conn in Player's connectionId instead of fd.

  \param lastCh
    Last time since a Player who completed the handshake 
    (sent HI#% and RD#% successfully) of the same fd sent a CH#%.

    If lastCh - the current time is higher than CH_MAX_TIMEOUT_SEC (config.h),
    the client gets disconnected. (see in main() now - c->lastCh > CH_MAX_TIMEOUT_SEC)

    This allows to remove dead connections while at the same time prevent the server
    to be full due to clients that didn't finish the handshake or didn't even start it.

    Initialized to the current time on FOR_EACH_FD()

  \param ip
    Ip address.
    Set to the client's ip on FOR_EACH_FD() using inet_ntop().

    In case this is 127.0.0.1 (local) and the client joined through the websocket port,
    the IP will be set to the value of the X-Real-Ip http header in handle_websocket_handshake().
    
    \param flags
    See CONN_*_FLAG
*/
typedef struct {
    socket_t fd;

    #if WS_LISTEN_PORT > 0
     uint8_t flags;
    #endif

    char ip[16];
    time_t lastCh;
} Conn;

#define CONN_IS_WS_FLAG             (1 << 0) /**< Set in FOR_EACH_FD if the fd joined through WS_LISTEN_PORT */

#define CONN_WS_HANDSHAKE_DONE_FLAG (1 << 1) /**< Set in FOR_EACH_FD's else after a handle_websocket_handshake*/

#if WS_LISTEN_PORT > 0
    #include "websockets.h"
#endif

/* ==================================== GLOBAL STATE DECLARATIONS ==================================== */

Timer globalTimer;                       /**< The global one. See Timer*/ 
uint8_t playerCount = 0;                 /**< The amount of players currently in the server. 
                                         Incremented on handle_RD(), decremented on disconnectClient().*/

time_t last = 0;                         /**< Last server tick timestamp. See main() */
time_t lastAdvertised = 0;               /**< Last server advertise timestamp. See advertise()*/
uint16_t seed = 12;                      /**< Seed for pseudorandom number generation. See xorShiftSeed() */
uint8_t serverFlags = 0;                 /**< See SERVER_*_FLAG */

#define SERVER_NUKING_FLAG        (1 << 0) /**< Set when a players is being nuked, unset when not anymore. 
                                         Used along with SERVER_TIMER_ENABLED_FLAG
                                         in order to alter the tickrate when enabled.
                                         See main() */
#define SERVER_TIMER_ENABLED_FLAG (1 << 1) /**< Set when the global timer is enabled. See handle_timer*/
#define SERVER_SHUTTING_DOWN_FLAG (1 << 2) /**< Set on handle_shutdown. 
                                         Checked by the main() loop on every tick to have a shutdown process*/


#include "lists.h" /**<A LOT OF SHIT IS SEDCLARED HERE CHECK IT OUT FUCK THIS IS MENTAL ILNESS ADDING SUPPORT FOR A DYNAMIC VERSION WAS PROBABLY THE BIGGEST MISTAKE IN THIS NIGGER MONKEY JEWISH SHIT PROJECT*/
#define GET_AREA_SIZE ((29 + DIGITS(MAX_PLAYERS) + SHOWNAME_MAX_LENGTH + CHAR_NAME_MAX_LENGTH + OOC_NAME_MAX_LENGTH + POS_NAME_MAX_LENGTH) * MAX_PLAYERS + (5*2 + 2 + AREA_NAME_MAX_LENGTH))

Conn* getConnByFd(socket_t fd) {
    for (uint16_t i = 0; i < maxConnections; i++) if (conns[i].fd == fd) return &conns[i];
    return NULL;
}
                                         

/* ==================================== GENERAL MACRO DECLARATIONS ==================================== */

/**A buffer for the size of CT#OOC_HOSTNAME#msg#1#% + variableSize is created.*/
#define CREATE_CT_BUFFER(msg, variableSize) \
    char ctBuffer[SUM_OOC_HOST_EXT(STRLEN_CT(msg) + variableSize + 4)] = "";

/**CREATE_CT_BUFFER, append message and track its used buffer size*/
#define CREATE_AND_APPEND_NAME_HOST_AND_SIGNATURE_TO_OOC_BUFFER_AND_MSGARG(msg, addedSize, formatString, arg) \
    CREATE_CT_BUFFER(msg, addedSize); \
    size_t used = appendPacketNameAndHostToOocMessageBuffer(ctBuffer); \
    used += sprintf(ctBuffer + used, formatString, arg); \
    used += sprintf(ctBuffer + used, "#1"); \

/**CREATE_CT_BUFFER, but only applying packet name (CT) and hostname*/
#define CREATE_AND_APPEND_NAME_AND_HOST_TO_OOC_BUFFER(msg, addedSize); \
    CREATE_CT_BUFFER(msg, addedSize); \
    size_t used = appendPacketNameAndHostToOocMessageBuffer(ctBuffer); \

/**CREATE_CT_BUFFER, pass it to hostSendOocMessageToPlayer with msg and send it to playerid*/
#define HOST_SEND_OOC_MESSAGE_TO_PLAYER(msg, playerId) \
    do{ \
    CREATE_CT_BUFFER(msg, 0); \
    hostSendOocMessageToPlayer(ctBuffer, (char *)msg, playerId); \
    } while(0)

/**
    Create the buffer for a host ooc message with the string size of the message 
    (formatString) + argLength -2 (for the %s for example) 
*/
#define HOST_SEND_VARIABLE_OOC_MESSAGE_TO_PLAYER(formatString, argLength, playerId, arg) \
    do{ \
    CREATE_AND_APPEND_NAME_HOST_AND_SIGNATURE_TO_OOC_BUFFER_AND_MSGARG(formatString, STRLEN_CT(formatString) + argLength -2, formatString, arg); \
    closeAndSendPacketToPlayer(ctBuffer, used, playerId); \
    } while(0)

/**Create and send a constant message to playerId*/
#define HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN(msg, playerId) \
    do{ \
    HOST_SEND_OOC_MESSAGE_TO_PLAYER(msg, playerId); \
    return; \
    } while(0)

/**HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN with custom rv*/
#define HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN_CUSTOM(msg, playerId, rv) \
    do{ \
    HOST_SEND_OOC_MESSAGE_TO_PLAYER(msg, playerId); \
    return rv; \
    } while(0)

/**HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN but area*/
#define HOST_SEND_OOC_MESSAGE_TO_AREA(msg, areaId) \
    do{ \
    CREATE_CT_BUFFER(msg, 0); \
    HostSendOocMesageToArea(ctBuffer, (char *)msg, areaId); \
    } while(0)

/**HOST_SEND_VARIABLE_OOC_MESSAGE_TO_PLAYER but area*/
#define HOST_SEND_VARIABLE_OOC_MESSAGE_TO_AREA(formatString, argLength, areaId, arg) \
    do{ \
    CREATE_AND_APPEND_NAME_HOST_AND_SIGNATURE_TO_OOC_BUFFER_AND_MSGARG(formatString, STRLEN_CT(formatString) + argLength -2, formatString, arg); \
    closeHostAndSendPacketToArea(ctBuffer, used, areaId); \
    } while(0)

/**Create string to say player field > max*/
#define OOC_CANNOT_EXCEED(field, max) \
    field " cannot exceed " XSTR(max) " characters"

/**Validate length of string, notify player and return if invalid.*/
#define VALIDATE_STRING_LENGTH_NOTIFY_AND_RETURN(arg, maxLength, fieldName, playerId) \
    if(strlen(arg) > maxLength){ \
        HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN(OOC_CANNOT_EXCEED(fieldName, maxLength), playerId); \
    } \

#define VALIDATE_STRING_LENGTH_NOTIFY_AND_RETURN_FALSE(arg, maxLength, fieldName, playerId) \
    if(strlen(arg) > maxLength){ \
        HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN_CUSTOM(OOC_CANNOT_EXCEED(fieldName, maxLength), playerId, 0); \
    } \

/**
    Validate id of player and return if validatePlayerIdStringAndParse(rapist) is maxPlayers.
    Otherwise affectedId is defined in the function's body.
*/
#define VALIDATE_AFFECTED_ID_AND_RETURN_IF_INVALID(args, rapist) \
    uint8_t affectedId = validatePlayerIdStringAndParse(args, rapist, 0); \
    if(affectedId == maxPlayers) return;

/**VALIDATE_AFFECTED_ID_AND_RETURN_IF_INVALID but in an area.*/
#define VALIDATE_AFFECTED_ID_IN_AREA_AND_RETURN_IF_INVALID(args, rapist) \
    uint8_t affectedId = validatePlayerIdStringAndParse(args, rapist, 1); \
    if(affectedId == maxPlayers) return;

/**Toggle FLAG and send a message notifying the flag has been toggled/untoggled*/
#define VALIDATE_AFFECTED_ID_AND_RETURN_IF_INVALID_TOGGLE_FLAG_NOTIFY_AND_RETURN(args, toggler, FLAG, actionName) \
    do{ \
    VALIDATE_AFFECTED_ID_AND_RETURN_IF_INVALID(args, toggler); \
    players[affectedId].flags ^= FLAG; \
    if(players[affectedId].flags & FLAG) HOST_SEND_OOC_MESSAGE_TO_PLAYER("Client " actionName, toggler); \
    else HOST_SEND_OOC_MESSAGE_TO_PLAYER("Client un" actionName, toggler); \
    return; \
    } while(0)

/* ==================================== GENERAL FUNCTION DECLARATIONS ==================================== */

/**Shift the seed's bits to introduce pseudo-randomness to commands that require it (/coinflip, /roll) */
void xorShiftSeed(){
    seed ^= seed << 7;
    seed ^= seed >> 9;
    seed ^= seed << 8;
}

/**
    Make an 16 bit unsigned int out of a string. Loops through it and 
    returns 0 if/when no numbers are found. 
*/
uint16_t parseInt(const char *s)
{
    uint16_t value = 0;
    while (*s >= '0' && *s <= '9') value = value * 10 + (*s++ - '0');
    return value;
}

/**Same as parseInt() but 8 bit signed.*/
int8_t parsePotentialNegative(const char *s){
    int8_t value = 0;
    uint8_t isNegative = 0;
    if(*s == '-'){
        isNegative = 1;
        s++;
    }
    while (*s >= '0' && *s <= '9') value = value * 10 + (*s++ - '0');
    if (isNegative) return value * -1;
    else return value;
}

/**Use memcpy to copy *src to *dst and make sure there is a \0 at the end (dst_size)*/
void safe_strcpy(char *dst, const char *src, size_t dst_size){
    size_t len = strlen(src);

    if (len >= dst_size)
        len = dst_size - 1;

    memcpy(dst, src, len);
    dst[len] = '\0';
}

/**
    Used to remove special symbols ($%#&) from a string.
    Used for ooc and ic messages.
    THIS MODIFIES THE ORIGINAL STRING BEWARE
*/
void sanitizeStringBuffer(char *str) {
    int i = 0, j = 0;

    while (str[i] != '\0') {
        if (!strncmp(&str[i], "<dollar>", 8)) {
            str[j++] = '$';
            i += 8;
        } else if (!strncmp(&str[i], "<percent>", 9)) {
            str[j++] = '%';
            i += 9;
        } else if (!strncmp(&str[i], "<num>", 5)) {
            str[j++] = '#';
            i += 5;
        } else if (!strncmp(&str[i], "<and>", 5)) {
            str[j++] = '&';
            i += 5;
        } else {
            str[j++] = str[i++];
        }
    }

    str[j] = '\0';
}

/**Add a timestamp to logs/<date> at the end of the file.*/
FILE* addLogTimeStamp() {
    time_t t = time(NULL);
    struct tm *tm_info = localtime(&t);

    char date[13];
    strftime(date, sizeof(date), "%Y-%m-%d", tm_info);

    char pathBuffer[19];
    sprintf(pathBuffer, "logs/%s", date);

    FILE *fptr = fopen(pathBuffer, "a");
    if (fptr == NULL) {
        return NULL;
    }

    char timeStamp[6];
    strftime(timeStamp, sizeof(timeStamp), "%H:%M", tm_info);

    fprintf(fptr, "[%s]", timeStamp);

    return fptr;
}

/*LOGGING LOGIC*/

/**
    Base logger. 
    Receives a printf format as the first argument
    and the arguments to replace as the variadic arguments.
*/
void logBase(const char *format, ...) {
    if(!LOGGING) return;
    FILE *f = addLogTimeStamp();
    if (!f) return;

    va_list args;
    va_start(args, format);
    vfprintf(f, format, args);
    va_end(args);

    fprintf(f, "\n");
    fclose(f);
}

/**[ACTION]*/
void logSingleAction(const char *action) {logBase("[%s]", action);}

/**Log user action ([JUDGE ACTION][1])*/
void logAction(const char *action, uint8_t affected) {logBase("[%s][%u]", action, affected);}

/**Log action string [DISCONNECT][127.0.0.1]*/
void logActionString(const char *action, const char *affected) {logBase("[%s][%s]", action, affected);}

/**Log action towards [KICK][0 -> 1]*/
void logActionTowards(const char *action, uint8_t actioner, uint8_t affected) {logBase("[%s][%u -> %u]", action, actioner, affected);}

/**Log action towards string [SELECT CHAR][0 -> Phoenix]*/
void logActionTowardsString(const char *action, uint8_t actioner, const char *affected) {logBase("[%s][%u -> %s]", action, actioner, affected);}

/*DISCONNECTION LOGIC*/

void closeAndClearFd(socket_t fd, fd_set *master){
    closeSocket(fd);
    FD_CLR(fd, master);
    DEBUG_PRINT(printf("Cerrando conexión con fd %d.\n", fd));
}

void closeFd(socket_t fd, fd_set *master){
    closeAndClearFd(fd, master);
    Conn *c = getConnByFd(fd);
    logActionString("DISCONNECT", c->ip);
    c->fd = INVALID_FD;
    c->flags = 0;
}

/*PACKET SEND LOGIC*/

/**Sends *packet to connectionId (fd/socket) */
void sendToClient(socket_t connectionId, const char *packet){
    DEBUG_PRINT(printf("SENDING %s to %d\n", packet, connectionId));

    if(WS_LISTEN_PORT > 0 && getConnByFd(connectionId)->flags & CONN_IS_WS_FLAG){
        websocket_send_text(connectionId, packet);
        return;
    }
    int n = send(connectionId, packet, strlen(packet), SEND_FLAGS);
    if (SOCKET_IO_ERROR_CHECK(n)) perror("send");
}

/**Sends *packet to a player of playerId (players[i]) as long as he is not leaving. */
void sendPacket(const char *packet, uint8_t playerId){
    if (playerId >= maxPlayers) return;
    socket_t fd = players[playerId].connectionId;
    if (SOCKET_ERROR_CHECK(fd) || (players[playerId].flags & PLAYER_LEAVING_FLAG)) return;
    sendToClient(fd, packet);
}

/**Add a % to packet */
void closePacket(char *packet, size_t used){
    sprintf(packet + used, "#%%");
}

/**Sends *packet to area of areaId (areaList[i]) as long as they joined the server and are not leaving. */
void sendPacketToArea(char *packet, uint8_t areaId){
    for (uint8_t i = 0; i < maxPlayers; i++) if((players[i].flags & PLAYER_JOINED_FLAG) && players[i].areaId == areaId && !(players[i].flags & PLAYER_LEAVING_FLAG)) sendPacket(packet, players[i].id);
}

/**Send packet to a certain area if playerId is MAX_PLAYERS. Otherwise sends it to the player. */
void sendPacketToAreaIfInvalidId(char *packet, uint8_t playerId, uint8_t areaId){
    if(playerId == maxPlayers) sendPacketToArea(packet, areaId);
    else sendPacket(packet, playerId);
}

/**Sends *packet to every client player as long as they joined the server. */
void sendPacketToEveryone(char *packet){
    for (uint8_t i = 0; i < maxPlayers; i++) if((players[i].flags & PLAYER_JOINED_FLAG)) sendPacket(packet, players[i].id);
}

/**Do sendPacketToEveryone() if playerId == maxPlayers */
void sendPacketToEveryoneIfInvalidId(char *packet, uint8_t playerId){
    if(playerId == maxPlayers) sendPacketToEveryone(packet);
    else sendPacket(packet, playerId);
}

/*OOC PACKET LOGIC*/

/**Add CT#OOC_HOSTNAME# to ctBuffer */
size_t appendPacketNameAndHostToOocMessageBuffer(char *ctBuffer){
    return sprintf(ctBuffer, "CT#%s#", OOC_HOSTNAME);
}

/**Create a full host ooc message like CT#OOC_HOSTNAME#message#1#% */
void hostCreateOocMessage(char *ctBuffer, char *message){
    size_t used = appendPacketNameAndHostToOocMessageBuffer(ctBuffer);
    sprintf(ctBuffer + used, "%s#1#%%", message);
}

/**Put message in ctBuffer and send to playerId */
void hostSendOocMessageToPlayer(char *ctBuffer, char *message, uint8_t playerId){
    hostCreateOocMessage(ctBuffer, message);
    sendPacket(ctBuffer, playerId);
}

/**hostSendOocMessageToPlayer() but area */
void HostSendOocMesageToArea(char *ctBuffer, char *message, uint8_t areaId){
    hostCreateOocMessage(ctBuffer, message);
    sendPacketToArea(ctBuffer, areaId);
}

void closeHost(char *packet, size_t used){
    used += sprintf(packet + used, "#1");
    closePacket(packet, used);
}

/**Add a #1#% to packet and send to playerId */
void closeHostAndSendPacketToPlayer(char *packet, size_t used, uint8_t playerId){
    closeHost(packet, used);
    sendPacket(packet, playerId);
}

/**Close packet and send to playerId */
void closeAndSendPacketToPlayer(char *packet, size_t used, uint8_t playerId){
    closePacket(packet, used);
    sendPacket(packet, playerId);
}

/**Close packet and send to areaId */
void closeHostAndSendPacketToArea(char *packet, size_t used, uint8_t areaId){
    closeHost(packet, used);
    sendPacketToArea(packet, areaId);
}

/**Create the server's area song change packet, where songName is being played by charId */
void createMcBuffer(char *buffer, const char *songName, const char *showname, uint16_t charId){
    sprintf(buffer, "MC#%s#%d#%s#%d#%%", songName, charId, showname, 1);
}

/**Check if player of id is in the server and in sameArea as sender (optionally by bool), return maxPlayers if not */
uint8_t validatePlayerIdStringAndParse(char *args, uint8_t sender, uint8_t sameArea){
    if (args[0] < '0' || args[0] > '9'){
        HOST_SEND_OOC_MESSAGE_TO_PLAYER("Invalid ID", sender);
        return maxPlayers;
    }

    uint8_t playerId = parseInt(args);
    if(playerId >= maxPlayers){
        HOST_SEND_OOC_MESSAGE_TO_PLAYER("ID out of bounds", sender);
        return maxPlayers;
    }
    if(players[playerId].connectionId == INVALID_FD){
        HOST_SEND_OOC_MESSAGE_TO_PLAYER("Player not connected", sender);
        return maxPlayers;
    }
    if(sameArea && players[playerId].areaId != players[sender].areaId){
        HOST_SEND_OOC_MESSAGE_TO_PLAYER("Player not in area", sender);
        return maxPlayers;
    }
    return playerId;
}


/*PACKET CREATION*/

/**
    Grab connectionId and loop through players[] 
    until it finds a player with that connectionId.
    Otherwise return maxPlayers
*/
uint8_t getPlayerIdByConnectionId(socket_t connectionId){
    for (uint8_t i = 0; i < maxPlayers; i++) {
        if(players[i].connectionId == connectionId) return i;
    }
    return maxPlayers;
}

/**
    Create SM# in MUSIC_LIST_PACKET, looping through first areaList[] and then musicList[] 
    adding its names in order
*/
void createAndStoreMusicPacket(){
    size_t used = 0;
                
    used += sprintf(MUSIC_LIST_PACKET + used, "SM");

    for (uint8_t i = 0; i < areaCount; i++) used += sprintf(MUSIC_LIST_PACKET + used, "#%s", areaList[i].name);
    for (uint16_t i = 0; i < musicCount; i++) used += sprintf(MUSIC_LIST_PACKET + used, "#%s", musicList[i].name);
    used += sprintf(MUSIC_LIST_PACKET + used, "%s", "#%");
}

/**Retvrn evidence count */
uint8_t countEvidence(uint8_t areaIndex){
    uint8_t evidenceCount = 0;
    for (uint8_t i = 0; i < EVIDENCE_MAX_COUNT; i++) {
        if (areaList[areaIndex].evidence[i].name[0] != '\0') evidenceCount++;
        else break;
    }
    return evidenceCount;
}

/**
    Send the LE# packet of areaIndex to playerId. 
    Send it to areaIndex if playerId == maxPlayers 
*/
void sendEvidenceListPacket(uint8_t areaIndex, uint8_t playerId) {
    char buf[EVIDENCE_LIST_PACKET_SIZE];
    size_t used = 0;

    used += sprintf(buf + used, "LE");

    for (uint8_t i = 0; i < EVIDENCE_MAX_COUNT; i++) {
        Evidence *e = &areaList[areaIndex].evidence[i];
        if (e->name[0] == '\0') break;
        used += sprintf(buf + used, "#%s&%s&%s", e->name, e->description, e->image);
    }

    sprintf(buf + used, "#%%");
    sendPacketToAreaIfInvalidId(buf, playerId, areaIndex);
}

/** Send areaList[areaId].background to playerId or area if playerId == maxPlayers */
void sendBackground(uint8_t areaId, uint8_t playerId){
    char bnBuffer[SUM_COMMON_EXT(BACKGROUND_NAME_MAX_LENGTH+1-1)] = "";
    if(areaList[areaId].background[0] != '\0') sprintf(bnBuffer, "BN#%s##%%", areaList[areaId].background);
    else sprintf(bnBuffer, "BN#default##%%");
    sendPacketToAreaIfInvalidId(bnBuffer, playerId, areaId);
}

/**Change areaList[areaId].background to background and do sendBackground() to everyone in the area*/
void changeBackground(uint8_t areaId, char *background){
    safe_strcpy(areaList[areaId].background, background, BACKGROUND_NAME_MAX_LENGTH);
    sendBackground(areaId, maxPlayers);
}

/**Change areaList[areaId].background to background and do sendBackground() to everyone in the area*/
void sendLifeBar(uint8_t areaId, uint8_t playerId, uint8_t bar){
    char hpBuffer[SUM_COMMON_EXT(4)] = "";
    //snprintf hack para que el compilador cierre el culo 
    snprintf(hpBuffer, 14, "HP#%u#%u#%%", bar, areaList[areaId].hp[bar-1]);
    sendPacketToAreaIfInvalidId(hpBuffer, playerId, areaId);
}

/** Set life bar to value and send it to areaId */
void updateLifeBar(uint8_t areaId, uint8_t bar, uint8_t value){
    areaList[areaId].hp[bar-1] = value;
    sendLifeBar(areaId, maxPlayers, bar);
}

/** Send both life bars of areaId to playerId */
void sendLifeBars(uint8_t areaId, uint8_t playerId){
    sendLifeBar(areaId, playerId, 1); sendLifeBar(areaId, playerId, 2);
}

/** Send PU#0 (ooc name update) to playerId or everyone*/
void sendPlayerOocNameUpdate(Player *player, uint8_t playerId){
    char puBuffer[SUM_COMMON_EXT(DIGITS(MAX_PLAYERS) + OOC_NAME_MAX_LENGTH) + 3] = "";
    sprintf(puBuffer, "PU#%u#%u#%s#%%", player->id, 0, player->oocName);
    sendPacketToEveryoneIfInvalidId(puBuffer, playerId);
}

/** Send PU#1 (character name update) to playerId or everyone*/
void sendCharacterNameUpdate(Player *player, uint8_t playerId){
    char puBuffer[SUM_COMMON_EXT(DIGITS(MAX_PLAYERS) + CHAR_NAME_MAX_LENGTH + 3)] = "";
    if(player->charId != charCount) sprintf(puBuffer, "PU#%d#%d#%s#%%", player->id, 1, charList[player->charId]);
    else sprintf(puBuffer, "PU#%u#%u#%s#%%", player->id, 1, "Spectator");
    sendPacketToEveryoneIfInvalidId(puBuffer, playerId);
}

/** Send PU#2 (character name update) to playerId or everyone*/
void sendCharacterShowNameUpdate(Player *player, uint8_t playerId){
    char puBuffer[SUM_COMMON_EXT(DIGITS(MAX_PLAYERS) + SHOWNAME_MAX_LENGTH + 3)] = "";
    if(player->charId < charCount) sprintf(puBuffer, "PU#%d#%d#%s#%%", player->id, 2, player->showname);
    else sprintf(puBuffer, "PU#%d#%d##%%", player->id, 2);
    sendPacketToEveryoneIfInvalidId(puBuffer, playerId);
}

/*I FUCKING HATE NIGGERS DAWG*/
/** Send PU#3 (player area id) to playerId or everyone*/
void sendPlayerAreaIdUpdate(Player *player, uint8_t playerId){
    char puBuffer[SUM_COMMON_EXT(DIGITS(MAX_PLAYERS) + DIGITS(AREA_LIST_COUNT) + 3)] = "";
    sprintf(puBuffer, "PU#%u#%u#%u#%%", player->id, 3, player->areaId);
    sendPacketToEveryoneIfInvalidId(puBuffer, playerId);
}

/** Send all PUs to playerId or everyone*/
void sendPlayerUpdates(Player *player, uint8_t playerId){
    sendPlayerOocNameUpdate(player, playerId);
    sendCharacterNameUpdate(player, playerId);
    sendCharacterShowNameUpdate(player, playerId);
}

/** Add playerId to list (PR#playerId)*/
void addPlayerToList(Player *player, uint8_t playerId){
    char prBuffer[PR_BUFFER_SIZE] = "";
    sprintf(prBuffer, "PR#%u#0#%%", player->id);
    sendPacketToEveryoneIfInvalidId(prBuffer, playerId);
}

/** Add playerId to list and update it*/
void addPlayerAndUpdate(Player *player, uint8_t playerId){
    addPlayerToList(player, playerId);
    sendPlayerUpdates(player, playerId);
    sendPlayerAreaIdUpdate(player, playerId);
}

/** Send all areas player count*/
void arupSendPlayers(){
    char arupBuffer[SUM_COMMON_EXT(DIGITS(MAX_PLAYERS) * AREA_LIST_COUNT) + MAX_PLAYERS + 3] = "";
    size_t used = sprintf(arupBuffer, "ARUP#0");
    for (uint8_t i = 0; i < areaCount; i++) used += sprintf(arupBuffer + used, "#%u", areaList[i].players);
    sprintf(arupBuffer + used, "#%%");
    sendPacketToEveryone(arupBuffer);
}

/** Send area statuses to playerId*/
void arupSendStatus(uint8_t playerId){
    char arupBuffer[SUM_COMMON_EXT((19 + 2) * AREA_LIST_COUNT + 3)] = "";
    size_t used = sprintf(arupBuffer, "ARUP#1");
    for (uint8_t i = 0; i < areaCount; i++) used += sprintf(arupBuffer + used, "#%s", AREA_STATUS_NAMES[areaList[i].status]);
    sprintf(arupBuffer + used, "#%%");
    sendPacketToEveryoneIfInvalidId(arupBuffer, playerId);
}

/** Send CM list in areas to playerId*/
void arupSendCm(uint8_t playerId){
    char arupBuffer[SUM_COMMON_EXT(MAX_PLAYERS * CHAR_NAME_MAX_LENGTH + 8) + 3] = "";
    size_t used = sprintf(arupBuffer, "ARUP#2");
    for (uint8_t i = 0; i < areaCount; i++){
        used += sprintf(arupBuffer + used, "#");
        for (size_t j = 0; j < maxPlayers; j++){
            Player *player = &players[j];
            if((player->flags & PLAYER_CM_FLAG) && player->areaId == i) used += sprintf(arupBuffer + used, "[%d] %s ", player->id, charList[player->charId]);
        }
    } 
    sprintf(arupBuffer + used, "#%%");
    sendPacketToEveryoneIfInvalidId(arupBuffer, playerId);
}

/** Send areas lock status to playerId*/
void arupSendLockStatus(uint8_t playerId){
    char arupBuffer[SUM_COMMON_EXT((11 + 2) * AREA_LIST_COUNT + 3)] = "";
    size_t used = sprintf(arupBuffer, "ARUP#3");
    for (uint8_t i = 0; i < areaCount; i++) used += sprintf(arupBuffer + used, "#%s", AREA_LOCK_STATUS_NAMES[areaList[i].lockStatus]);
    sprintf(arupBuffer + used, "#%%");
    sendPacketToEveryoneIfInvalidId(arupBuffer, playerId);
}

/** Check if charId is selected in areaId.*/
uint8_t isCharSelected(uint16_t charId, uint8_t areaId){
    for(uint8_t i = 0; i < maxPlayers; i++){
        Player *area_player = &players[i];
        if(!(area_player->flags & PLAYER_JOINED_FLAG) || area_player->areaId != areaId) continue;
        if(area_player->charId == charId) return 1;
    }
    return 0;
}

/** Pair with player if playerId is not pairedId.*/
void pairWithPlayer(uint8_t playerId, uint8_t pairedId){
    if(players[playerId].otherId == pairedId) return;
    if(playerId == pairedId) HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("Forever Alone", playerId);
    players[playerId].otherId = pairedId;
    HOST_SEND_VARIABLE_OOC_MESSAGE_TO_PLAYER(OOC_PAIRING, DIGITS(MAX_PLAYERS), playerId, pairedId);
}

/** Set playerId's otherid to MAX_PLAYERS.*/
void stopPairing(uint8_t playerId){
    HOST_SEND_VARIABLE_OOC_MESSAGE_TO_PLAYER(OOC_STOP_PAIRING, DIGITS(MAX_PLAYERS), playerId, players[playerId].otherId);
    players[playerId].otherId = maxPlayers;
}

/** If Player joined the server then uncm, remove from area playercount, and unlock the area and
    clean invites if Player was the last CM*/
void leaveAreaIfJoined(Player *player){
    //if the player already joined the server then uncm and remove from area playercount.
    if(!(player->flags & PLAYER_JOINED_FLAG)) return;
    player->flags &= ~PLAYER_TESTIFYING_FLAG;
    Area *area = &areaList[player->areaId];

    area->players--;

    uint8_t wasLastCm = 0;
    //uncm
    if((player->flags & PLAYER_CM_FLAG)){
        player->flags ^= PLAYER_CM_FLAG;
        arupSendCm(maxPlayers);
        uint8_t found = 0;
        for(uint8_t i = 0; i < maxPlayers; i++) {
            if((players[i].flags & PLAYER_JOINED_FLAG) && 
            players[i].areaId == area->id && 
            (players[i].flags & PLAYER_CM_FLAG)){
                found = 1;
                break;
            }
        }

        if(!found) wasLastCm = 1;
    }
    
    if(area->lockStatus != FREE && (area->players == 0 || wasLastCm)){
        area->lockStatus = FREE;
        arupSendLockStatus(maxPlayers);
    }

    for(uint8_t i = 0; i < maxPlayers; i++){
        if(!(players[i].flags & PLAYER_JOINED_FLAG)) continue; 
        if(players[i].otherId == player->id) stopPairing(i);
        if(wasLastCm || area->players == 0){
            players[i].areaInviteFlags &= ~(1 << player->areaId);
        }
    } 
}

/** Send CharsCheck# (see the protocol)*/
void sendCharsCheck(uint8_t areaId, uint8_t playerId){
    uint16_t charIdsInArea[MAX_PLAYERS];
    size_t idsIndex = 0;

    for(uint8_t i = 0; i < maxPlayers; i++){
        Player *area_player = &players[i];
        if(!(area_player->flags & PLAYER_JOINED_FLAG) || area_player->areaId != areaId || area_player->charId == maxPlayers) continue;

        charIdsInArea[idsIndex++] = area_player->charId;
    }

    if(idsIndex == 0) return;

    char charsCheckBuffer[SUM_COMMON_EXT(8 + 3 * CHAR_LIST_COUNT)] = "";
    size_t used = sprintf(charsCheckBuffer, "CharsCheck#");

    for(uint16_t i = 0; i < charCount; i++){
        uint8_t found = 0;

        for(size_t j = 0; j < idsIndex; j++) if(charIdsInArea[j] == i) found = 1;

        if(found == 1) used += sprintf(charsCheckBuffer + used, "-1#");
        else used += sprintf(charsCheckBuffer + used, "0#");
    }
    sprintf(charsCheckBuffer + used, "%%");
    
    sendPacketToAreaIfInvalidId(charsCheckBuffer, playerId, areaId);
}

void showTimerAndMinutesIfPaused(Timer *timer, char *tiBuffer, size_t used, uint8_t playerId, uint8_t areaId){
    sprintf(tiBuffer + used, "2#%%");
    sendPacketToAreaIfInvalidId(tiBuffer, playerId, areaId);
    if(!timer->started && timer->timer > 0) sprintf(tiBuffer + used, "1#%u000#%%", timer->timer);
    sendPacketToAreaIfInvalidId(tiBuffer, playerId, areaId);
}


uint8_t sendTimerIfShowingOrStarted(Timer *timer, char *tiBuffer, size_t used, uint8_t playerId){
    if(!timer->started && !timer->show) return 0;

    if(timer->started){
        sprintf(tiBuffer + used, "0#%u000#%%", timer->timer);
        sendPacket(tiBuffer, playerId);
    }

    if(timer->show) showTimerAndMinutesIfPaused(timer, tiBuffer, used, playerId, areaCount);
    return 1;
}


uint8_t grabAreaTimerUpdateIdInBufferAndSend(char *tiBuffer, size_t idPos, uint8_t playerId, uint8_t areaId, uint8_t i){
    Timer *timer = &areaList[areaId].timers[i];
    size_t used = idPos + sprintf(tiBuffer + idPos, "%u#", i+1);
    if(!sendTimerIfShowingOrStarted(timer, tiBuffer, used, playerId)) return used;
    return 0;
}


/*
    AreaId playercounts goes += 1. sendCharsCheck() is called on his previous area and then 
    leaveArea() on Player and the previous area (only if Player's joined flag is set) and joined flag gets set.
    Finally, areaId info is sent to Player.
*/
void joinArea(uint8_t areaId, Player *player){

    Area *area = &areaList[areaId];

    //forzar selección de char si ya está pickeado
    if(player->charId != charCount && isCharSelected(player->charId, areaId) == 1){
        player->charId = charCount;
        sendPacket("PV##CID#-1#%", player->id);
    }

    area->players++;
    leaveAreaIfJoined(player); //cambiar cosas del área de la que se sale si el jugador ya joineo.

    //se podria integrar con leaveareaifjoined? esto también es enviado
    //en la lógica de desconexión así que convendría
    uint8_t previousArea = player->areaId;
    player->areaId = areaId;
    if(player->flags & PLAYER_JOINED_FLAG){
        sendCharsCheck(previousArea, maxPlayers);
        sendPlayerAreaIdUpdate(player, maxPlayers);
        HOST_SEND_VARIABLE_OOC_MESSAGE_TO_PLAYER(OOC_JOINED_AREA, AREA_NAME_MAX_LENGTH, player->id, areaList[areaId].name);
        logActionTowardsString("JOIN AREA", player->id, areaList[areaId].name);

        char tiBuffer[TI_BUFFER_SIZE] = "";
        size_t idPos = sprintf(tiBuffer, "TI#");
        for(uint8_t i = 0; i < AREA_TIMERS_MAX; i++){
            size_t used = grabAreaTimerUpdateIdInBufferAndSend(tiBuffer, idPos, player->id, player->areaId, i);
            if(used > 0 && (areaList[previousArea].timers[i].started || areaList[previousArea].timers[i].show)){
                sprintf(tiBuffer + used, "1#0#%%");
                sendPacket(tiBuffer, player->id);
                sprintf(tiBuffer + used, "3#%%");
                sendPacket(tiBuffer, player->id);
            }
        }
    }
    player->flags |= PLAYER_JOINED_FLAG; // ???
    if(player->charId < charCount) sendCharsCheck(areaId, maxPlayers);
    arupSendPlayers();
    //send the evidence and hp to the player that just joined
    sendEvidenceListPacket(areaId, player->id);
    sendLifeBars(areaId, player->id);
    //dem tunez
    char mcBuffer[SUM_COMMON_EXT(SONG_NAME_MAX_LENGTH) + 5] = "";
    if(area->currentTrack[0] != '\0' && strcmp(area->currentTrack, "stop.mp3") != 0){
        sprintf(mcBuffer, "MC#%s#-1##1#%%", area->currentTrack);
    }else sprintf(mcBuffer, "MC##-1#%%");
    if(area->areaMessage[0]) HOST_SEND_OOC_MESSAGE_TO_PLAYER(area->areaMessage, player->id);
    sendPacket(mcBuffer, player->id);
    //bg
    sendBackground(player->areaId, player->id);
}

/**joinArea() if Player joined the server and the area isn't locked and Player is unable to bypass the lock.*/
void validateAndJoinArea(uint8_t areaId, Player *player){
    if(areaId > AREA_LIST_COUNT-1 || (areaId == player->areaId && (player->flags & PLAYER_JOINED_FLAG))) return;

    if(areaList[areaId].status == LOCKED && areaId != 0 && !(player->flags & PLAYER_MOD_FLAG) &&
    !(player->areaInviteFlags & (1 << areaId))){
        HOST_SEND_VARIABLE_OOC_MESSAGE_TO_PLAYER(OOC_CANT_JOIN_LOCKED_AREA_MESSAGE, AREA_NAME_MAX_LENGTH, player->id, areaList[areaId].name);
        return;
    }
    joinArea(areaId, player);
}

/**
    Close the connection with player's fd, set the leaving flag, perform area leave logic, decrease playerCount, reset
    player in players array, send arup and remove it from everyone's player list.
*/

void disconnectClient(uint8_t playerId, fd_set *master){
    Player *player = &players[playerId];
    uint8_t areaId = player->areaId;
    uint16_t charId = player->charId;
    closeFd(player->connectionId, master);
    player->flags |= PLAYER_LEAVING_FLAG;
    leaveAreaIfJoined(player);
    if(player->flags & PLAYER_JOINED_FLAG) playerCount--;
    players[playerId] = (Player){0};
    players[playerId].connectionId = INVALID_FD;
    arupSendPlayers();
    if(charId != maxPlayers) sendCharsCheck(areaId, maxPlayers);
    char prBuffer[PR_BUFFER_SIZE] = "";
    sprintf(prBuffer, "PR#%d#1#%%", playerId);
    sendPacketToEveryoneIfInvalidId(prBuffer, maxPlayers);
}

/** Perform client disconnection logic on playerId if its not MAX_PLAYERS, otherwise just close conn's fd*/
void disconnectClientOrFd(Conn *conn, fd_set *master){
    uint8_t playerId = getPlayerIdByConnectionId(conn->fd);
    if(playerId == maxPlayers){
        closeFd(conn->fd, master);
        return;
    }
    disconnectClient(playerId, master);
}

/**Kick kickedId and notify kickerId*/
void kickPlayer(uint8_t kickedId, uint8_t kickerId, fd_set *master){
    HOST_SEND_VARIABLE_OOC_MESSAGE_TO_PLAYER(OOC_KICKED_PLAYER, DIGITS(MAX_PLAYERS), kickerId, kickedId);
    logActionTowards("KICK", kickerId, kickedId);
    sendPacket("KK##%", kickedId);
    disconnectClient(kickedId, master);
}

/**Kick all clients of kickedId and notify kickerId. Also kick oneself if kickSelf is 1*/
void kickAllPlayerClients(uint8_t playerId, uint8_t kickerId, fd_set *master, uint8_t kickSelf){
    Conn *kicked_c = getConnByFd(players[playerId].connectionId);
    // uint8_t kickedClients = 0;
    for(uint8_t i = 0; i < maxPlayers; i++){
        if(i == playerId) continue;
        if(!strcmp(getConnByFd(players[i].connectionId)->ip, kicked_c->ip)){
            kickPlayer(players[i].id, kickerId, master);
            // kickedClients++;
        } 
    }
    if(kickSelf) kickPlayer(playerId, kickerId, master);
    // HOST_SEND_VARIABLE_OOC_MESSAGE_TO_PLAYER("Kicked %u clients", DIGITS(MAX_PLAYERS), kickedClients, kickerId);
}

/**Validate args and kick if its a valid player id*/
void validateIdAndKick(char *args, uint8_t kickerId, fd_set *master){
    VALIDATE_AFFECTED_ID_AND_RETURN_IF_INVALID(args, kickerId)
    kickAllPlayerClients(affectedId, kickerId, master, 1);
}

/**Add ip to config/banlist.txt, notify bannerId and disconnect all clients and connections that match ip*/
void banIpAndDisconnect(char *ip, uint8_t bannerId, fd_set *master){
    FILE *fptr;
    fptr = fopen("config/banlist.txt", "a");
    if(fptr == NULL) HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("Error opening config/banlist.txt", bannerId);
    fprintf(fptr, "%s\n", ip);
    fclose(fptr);
    HOST_SEND_VARIABLE_OOC_MESSAGE_TO_PLAYER(OOC_IP_ADDED, 15, bannerId, ip);
    logActionTowardsString("BAN", bannerId, ip);
    for(uint8_t i = 0; i < maxConnections; i++) if(!strcmp(conns[i].ip, ip)){
        disconnectClientOrFd(&conns[i], master);
    } 
}

/**Validate args for a valid playerId, that player's conn and do banIpAndDisconnect() on conn's ip*/
void validateIdAndBanAndDisconnectPlayer(char *args, uint8_t bannerId, fd_set *master){
    VALIDATE_AFFECTED_ID_AND_RETURN_IF_INVALID(args, bannerId)
    Conn *banned_conn = getConnByFd(players[affectedId].connectionId);
    banIpAndDisconnect(banned_conn->ip, bannerId, master);
}

/**Create buffer with songName, showName and charId, save songName on currentTrack of areaList and send the buffer to areaId*/
void createMcBufferAndChangeAreaTrack(const char *songName, const char *showName, uint16_t charId, uint8_t areaId){
    char mcBuffer[SUM_COMMON_EXT(SONG_NAME_MAX_LENGTH + DIGITS(CHAR_LIST_COUNT) + SHOWNAME_MAX_LENGTH + 6)] = "";
    createMcBuffer(mcBuffer, songName, showName, charId);
    safe_strcpy(areaList[areaId].currentTrack, songName, SONG_NAME_MAX_LENGTH);
    sendPacketToArea(mcBuffer, areaId);
}

/**Do createMcBufferAndChangeAreaTrack() with Player data*/
void playerPlaySong(const char *songName, Player *player){
    createMcBufferAndChangeAreaTrack(songName, player->showname, player->charId, player->areaId);
}

/** Do playerPlaySong() with songName and player if it starts with http or https,
    otherwise, check if a Song on musicList.name is songName. 
    If that Song is found and it doesn't have a realName, send it, otherwise send its realName (streaming link usually).*/
void playAreaSong(const char *songName, Player *player){    
    if(strstr(songName, "http://") != NULL || strstr(songName, "https://") != NULL) playerPlaySong(songName, player);
    else{
        for (uint16_t i = 0; i < musicCount; i++){
            if(strcmp(musicList[i].name, songName) != 0) continue;
            if(*musicList[i].realName != '\0') playerPlaySong(musicList[i].realName, player);
            else playerPlaySong(musicList[i].name, player);
            logActionTowardsString("PLAY_SONG", player->id, musicList[i].name);
        }
    }
}

/**Set evidence data to name, description and image.*/
void setEvidence(Evidence *evidence, const char *name, const char *description, const char *image){
    safe_strcpy(evidence->name, name, EVIDENCE_NAME_MAX_CHARS);
    safe_strcpy(evidence->description, description, EVIDENCE_DESC_MAX_CHARS);
    safe_strcpy(evidence->image, image, EVIDENCE_IMAGE_MAX_CHARS);
}

/**Do setevidence() on evidence if name, description and image string length aren't above its MAX_CHARS and return 1,
otherwise return 0.*/
uint8_t validateAndSetEvidence(Evidence *evidence, const char *name, const char *description, const char *image, uint8_t playerId){
    VALIDATE_STRING_LENGTH_NOTIFY_AND_RETURN_FALSE(name, EVIDENCE_NAME_MAX_CHARS, "Evidence name", playerId)
    VALIDATE_STRING_LENGTH_NOTIFY_AND_RETURN_FALSE(description, EVIDENCE_DESC_MAX_CHARS, "Evidence description", playerId)
    VALIDATE_STRING_LENGTH_NOTIFY_AND_RETURN_FALSE(image, EVIDENCE_IMAGE_MAX_CHARS, "Evidence image", playerId)
    setEvidence(evidence, name, description, image);
    return 1;
}

/** Return 1 if all evidence fields are \0*/
uint8_t isEvidenceEmpty(Evidence *evidence){
    return evidence->name[0] == '\0' && evidence->description[0] == '\0' && evidence->image[0] == '\0';
}

/** Change player->charId to charId if it isn't taken and charId < maxPlayers. 
    Send charscheck and name update after that.
    Returns 0 if the char was selected, 1 if not.*/
uint8_t playerChangeCharacter(uint16_t charId, Player *player){    
    if(isCharSelected(charId, player->areaId)) return 0;

    player->charId = charId;
    char pvBuffer[SUM_COMMON_EXT(DIGITS(CHAR_LIST_COUNT) + 6)] = "";
    if(charId < charCount){
        sprintf(pvBuffer, "PV##CID#%u#%%", player->charId);
        logActionTowardsString("SELECT CHAR", player->id, charList[player->charId]);
    } 
    else{
        sprintf(pvBuffer, "PV##CID#-1#%%");
        logActionTowardsString("SELECT CHAR", player->id, "Spectator");
    } 
    sendPacket(pvBuffer, player->id);
    sendCharsCheck(player->areaId, maxPlayers);
    sendCharacterNameUpdate(player, maxPlayers);
    if(player->flags & PLAYER_CM_FLAG) arupSendCm(maxPlayers);
    return 1;
}

/** Put data of players in areaId in ctBuffer. Show their ip if showIp is 1*/
void getArea(uint8_t areaId, char *ctBuffer, size_t *used, uint8_t showIp){
    uint8_t madeHeader = 0;
    for(size_t j = 0; j < maxPlayers; j++){
        Player *area_player = &players[j];
        if(area_player->connectionId == INVALID_FD) continue;
        if(area_player->areaId != areaId) continue;
        if(madeHeader == 0) { madeHeader = 1; *used += sprintf(ctBuffer + *used, "==== %s ====\n", areaList[areaId].name); }
        *used += sprintf(ctBuffer + *used, "[%u] ", area_player->id);
        if(area_player->charId < charCount){
            *used += sprintf(ctBuffer + *used, "%s ", charList[area_player->charId]);
            if(area_player->showname[0] != '\0') *used += sprintf(ctBuffer + *used, " (%s)", area_player->showname);
            else if(!(area_player->flags & PLAYER_JOINED_FLAG)) *used += sprintf(ctBuffer + *used, "Joining...");
        }
        else *used += sprintf(ctBuffer + *used, "Spectator");
        if(*area_player->oocName) *used += sprintf(ctBuffer + *used, " %s", area_player->oocName);
        if(*area_player->pos) *used += sprintf(ctBuffer + *used, " <%s>", area_player->pos);

        if(showIp){
            *used += sprintf(ctBuffer + *used, " %s", getConnByFd(area_player->connectionId)->ip);
        }
        *used += sprintf(ctBuffer + *used, "\n");
    }
}

/**Set player's cm flag if value is 1, unset if its 0. Also notify accordingly if the CM was removed by someone else
    or if one just stopped being a CM.*/
void makeCm(uint8_t value, Player *player, uint8_t cmer){

    // setear el bit
    if(value) player->flags |= PLAYER_CM_FLAG;
    else player->flags &= ~PLAYER_CM_FLAG;

    arupSendCm(maxPlayers);

    uint8_t isCm = player->flags & PLAYER_CM_FLAG;

    if(cmer == maxPlayers){
        if(isCm) HOST_SEND_VARIABLE_OOC_MESSAGE_TO_AREA(OOC_IS_CM_MESSAGE, OOC_NAME_MAX_LENGTH, player->areaId, player->oocName);
        else HOST_SEND_VARIABLE_OOC_MESSAGE_TO_AREA(OOC_NO_LONGER_CM, OOC_NAME_MAX_LENGTH, player->areaId, player->oocName);
        return;
    }

    if(isCm){
        CREATE_AND_APPEND_NAME_AND_HOST_TO_OOC_BUFFER(OOC_GAVE_CM, STRLEN_CT(OOC_GAVE_CM) + OOC_NAME_MAX_LENGTH + DIGITS(MAX_PLAYERS) - 4);
        used += sprintf(ctBuffer + used, OOC_GAVE_CM, players[cmer].oocName, player->id);
        closeHostAndSendPacketToArea(ctBuffer, used, player->areaId);
    }else{
        CREATE_AND_APPEND_NAME_AND_HOST_TO_OOC_BUFFER(OOC_REMOVED_CM, STRLEN_CT(OOC_REMOVED_CM) + OOC_NAME_MAX_LENGTH + DIGITS(MAX_PLAYERS) - 4);
        used += sprintf(ctBuffer + used, OOC_REMOVED_CM, players[cmer].oocName, player->id);
        closeHostAndSendPacketToArea(ctBuffer, used, player->areaId);
    }

}

uint8_t areaHasCm(uint8_t areaId){
    for(uint8_t i = 0; i < maxPlayers; i++) if(players[i].areaId == areaId && (players[i].flags & PLAYER_CM_FLAG)) return 1;
    return 0;
}

uint8_t playerIsCmOrMod(Player * player){
    return (player->flags & PLAYER_CM_FLAG) || (player->flags & PLAYER_MOD_FLAG);
}

uint8_t areaHasCmAndIsNotPlayer(Player *player){
    return areaHasCm(player->areaId) && !playerIsCmOrMod(player);
}

/**Set area lockstatus to value.*/
void lockArea(Area *area, uint8_t value){
    area->lockStatus = value;
    arupSendLockStatus(maxPlayers);
    if(area->lockStatus == LOCKED) HOST_SEND_OOC_MESSAGE_TO_AREA("The area has been locked", area->id);
    else if(area->lockStatus == SPECTATABLE) HOST_SEND_OOC_MESSAGE_TO_AREA("The area has been set to spectatable mode. Only CMs can use IC chat now", area->id);
    else HOST_SEND_OOC_MESSAGE_TO_AREA("The area has been unlocked", area->id);
}

/**Set player's area status to value.*/
void playerSetAreaStatus(Player *player, uint8_t value){
    areaList[player->areaId].status = value;
    arupSendStatus(maxPlayers);

    if(value == 1){
        for(uint8_t i = 0; i < maxPlayers; i++){
            if(players[i].connectionId == INVALID_FD || players[i].areaId != player->areaId) continue;

            players[i].flags &= ~PLAYER_AUTOPAIR_FLAG;
            players[i].flags &= ~PLAYER_HIDE_DESK_FLAG;
        }
    }else if(value == 0){
        for(uint8_t i = 0; i < maxPlayers; i++){
            if(players[i].connectionId == INVALID_FD || players[i].areaId != player->areaId) continue;
            players[i].flags |= PLAYER_AUTOPAIR_FLAG;
        }
    }

    CREATE_AND_APPEND_NAME_AND_HOST_TO_OOC_BUFFER(OOC_CHANGED_STATUS, STRLEN_CT(OOC_CHANGED_STATUS) + 18 + OOC_NAME_MAX_LENGTH - 4);
    used += sprintf(ctBuffer + used, OOC_CHANGED_STATUS, player->oocName, AREA_STATUS_NAMES[value]);
    closeHostAndSendPacketToArea(ctBuffer, used, player->areaId);
}

/**Set player mod flag if password == MODPASS.*/
void login(char *password, Player *player){
    if(strcmp(password, MODPASS)){
        // HOST_SEND_OOC_MESSAGE_TO_PLAYER("Invalid password", player->id);
        sendPacket("AUTH#0#%", player->id);
        logAction("LOGIN FAILED", player->id);
        return;
    } 
    player->flags |= PLAYER_MOD_FLAG;
    HOST_SEND_OOC_MESSAGE_TO_PLAYER("Successfully logged in", player->id);
    sendPacket("AUTH#1#%", player->id);
    logAction("LOGIN SUCCESFUL", player->id);
}

/**Hack to check if oocCmd is an alias of pm lol. TODO: FIX THIS SHIT*/
uint8_t isMessageCmd(char *oocCmd){
    return strcmp(oocCmd, "message") == 0 || strcmp(oocCmd, "msg") == 0 || strcmp(oocCmd, "pm") == 0;
}

/**Decrease timer if timer->timer > 0 and set SERVER_TIMER_ENABLED_FLAG if that's the case 
to tell the server to use a different tickrate (yes its for all timers not just the global one).*/
void decreaseTimerIfStartedAndMarkGlobalStarted(Timer *timer){
    if(!timer->started) return;
    if(timer->timer == 0){
        timer->started = 0;
        return;
    }

    timer->timer--;

    serverFlags |= SERVER_TIMER_ENABLED_FLAG;
}

/** Add only a # to msBuffer if arg isn't 1 */
size_t nigArg(char *arg, char *msBuffer, size_t used){
    if(*arg != '1') *arg = '\0';
    return sprintf(msBuffer + used, "%s#", arg);
}

/** Set offset_*, if > 0, append the #s to msBuffer and increment used on each sprintf*/
void setMsOffset(int8_t offset_x, int8_t offset_y, char *msBuffer, size_t *used){
    if(offset_x != 0) *used += sprintf(msBuffer + *used, "%d", offset_x);
    if(offset_y != 0) *used += sprintf(msBuffer + *used, "&%d", offset_y);
    *used += sprintf(msBuffer + *used, "#");
}

/** 
    Append otherPlayer->charId to msBuffer, and increment used on each sprintf, 
    if there's a ^ in args (pair layer), add it, otherwise add ^0 so that the emote of the sender is shown
    first by default.
    After that, append charName and emote to msBuffer with the #s.
*/
void setMsOtherPair(Player *otherPlayer, char *args, char *msBuffer, size_t *used){
    *used += sprintf(msBuffer + *used, "%u", otherPlayer->charId);
    char *p; 
    if((p = strstr(args, "^"))) *used += sprintf(msBuffer + *used, "%s", p);
    else *used += sprintf(msBuffer + *used, "^0");
    *used += sprintf(msBuffer + *used, "#%s#%s#", otherPlayer->charName, otherPlayer->emote);
}

/** Return 1 if otherPlayer joined the server, isn't using firstperson, is in the same area and pos and its id is not player's*/
uint8_t isPairable(Player *player, Player *otherPlayer){
    if(!(otherPlayer->flags & PLAYER_JOINED_FLAG)) return 0;
    return !(otherPlayer->flags & PLAYER_FIRSTPERSON_FLAG) &&
    otherPlayer->areaId == player->areaId &&
    otherPlayer->id != player->id &&
    !strcmp(player->pos, otherPlayer->pos);
}

/** Return 1 if isPairable() is 1 and otherPlayer's otherId is player's and player's otherId is otherPlayer's*/
uint8_t canMutuallyPair(Player *player, Player *otherPlayer){
    return (isPairable(player, otherPlayer) &&
    otherPlayer->otherId == player->id && 
    otherPlayer->id == player->otherId);
}

/*MK ULTRA HANDLERS*/

/**
  Command Context Structure

  The context for every packet in handshakePacketDispatchTable[] and commonPacketDispatchTable[].
  Almost every argument is related to the client who sent the packet and the packet itself.

  \param client_fd
    Fd of the client who sent the packet
  \param argCount
    Count of every argument (#) in the packet.
  \param packetSize
    Total packet string length.
  \param args
    Array of pointers to every argument in the packet, separated by #.
  \param player
    Pointer to the client that sent the packet
  \param p_area
    Area of the player who sent the packet
  \param conn
    Connection of the player who sent the packet
  \param master
    The fd set, used to disconnect players.
*/

typedef struct {
    socket_t client_fd;
    char **args;
    uint64_t packetSize;
    fd_set *master;
    Area *p_area;
    Conn *conn;
    Player *player;
    uint8_t argCount;
} CommandContext;

/**
  Ooc Command Context Structure

  The context for every handler in the *CommandDispatchTable

  \param oocArgsc
    Count of every ooc argument.
  \param oocArgs
    Array of pointers to every argument in the command. Separated by a " ".
*/

typedef struct {
    uint8_t oocArgsc;
    char **oocArgs;
} OocCommandContext;

typedef void (*HandlerFunc)(CommandContext *ctx);

typedef void (*OocHandlerFunc)(CommandContext *ctx, OocCommandContext *oocCtx);

/**
  Unarged Packet Handler Structure

  Short version of CommonPacketHandler. Used in handshakePacketDispatchTable
*/
typedef struct {
    const char *packetName;
    HandlerFunc handler;
} UnargedPacketHandler;

/**
  Common Packet Handler Structure

  This is what goes in commonPacketDispatchTable[]. 
  Adding a new packet is just creating this struct and its handler.

  \param packetName
    Name of the packet to match when comparing the first argument received in the packet at processPacket()
  \param handler
    HandlerFunc that receives the CommandContext to process the packet.
  \param requiredArgs
    Amount of required args to validate before shooting the handler.
*/
typedef struct {
    const char *packetName;
    HandlerFunc handler;
    uint8_t requiredArgs;
} CommonPacketHandler;

void handle_ID(CommandContext *ctx) {
    // safe_strcpy(ctx->player->clientName, ctx->args[0], sizeof(ctx->player->clientName));
    // safe_strcpy(ctx->player->clientVersion, ctx->args[1], sizeof(ctx->player->clientVersion));
    sendPacket(FEATURES_PACKET_STRING, ctx->player->id);
    sendPacket(ASS_PACKET_STRING, ctx->player->id);
}

void handle_askchaa(CommandContext *ctx) {
    char siBuffer[SUM_COMMON_EXT(DIGITS(CHAR_LIST_COUNT) + DIGITS(EVIDENCE_MAX_COUNT) + DIGITS(AREA_MUSIC_LIST_LENGTH))] = "";
    sprintf(siBuffer, "SI#%u#%u#%llu#%%", charCount, countEvidence(0), AREA_MUSIC_LIST_LENGTH);
    sendPacket(siBuffer, ctx->player->id);
}

void handle_RC(CommandContext *ctx) {
    sendPacket(CHAR_LIST_PACKET, ctx->player->id);
}

void handle_RM(CommandContext *ctx) {
    //use the new packets instead of the old ones o algo not sure if this is necessary but heres the skkkode
    //4 future world reference
    // if(!strcmp(ctx->player->clientName, "AO2") && parseInt(ctx->player->clientVersion + 2)){
    //     sendPacket("SM#%", ctx->player->id);
    //     char faBuffer[SUM_COMMON_EXT(AREA_LIST_TOTAL_STRING_COUNT)] = "";
    //     size_t used = sprintf(faBuffer, "FA#");
    //     for(uint8_t i = 0; i < areaCount; i++) used += sprintf(faBuffer + used, "%s#", areaListName[i]);
    //     sprintf(faBuffer + used, "%%");
    //     sendPacket(faBuffer, ctx->player->id);

    //     char fmBuffer[SUM_COMMON_EXT(MUSIC_LIST_TOTAL_STRING_COUNT + MUSIC_LIST_LENGTH)] = "";
    //     used = sprintf(fmBuffer, "FM#");
    //     for(uint16_t i = 0; i < musicCount; i++) used += sprintf(fmBuffer + used, "%s#", musicList[i].name);
    //     sprintf(fmBuffer + used, "%%");
    //     sendPacket(fmBuffer, ctx->player->id);
    // }
    // else{
        sendPacket(MUSIC_LIST_PACKET, ctx->player->id);
    // }
}

void handle_RD(CommandContext *ctx) {
    sendCharsCheck(0, ctx->player->id);
    ctx->player->charId = charCount;
    sendPacket("DONE#%", ctx->player->id);
    HOST_SEND_OOC_MESSAGE_TO_PLAYER(MOTD, ctx->player->id);
    sendPacket("JD#1#%", ctx->player->id);
    // player->joined = 1;
    playerCount++;

    char tiBuffer[TI_BUFFER_SIZE] = "";
    size_t idPos = sprintf(tiBuffer, "TI#");
    size_t used = idPos + sprintf(tiBuffer + idPos, "0#");

    sendTimerIfShowingOrStarted(&globalTimer, tiBuffer, used, ctx->player->id);
    if(DEFAULT_AUTOPAIR && areaList[0].status != 1) ctx->player->flags |= PLAYER_AUTOPAIR_FLAG;
    for(uint8_t i = 0; i < AREA_TIMERS_MAX; i++) grabAreaTimerUpdateIdInBufferAndSend(tiBuffer, idPos, ctx->player->id, 0, i);
    
    for(uint8_t i = 0; i < maxPlayers; i++){
        Player *otherPlayer = &players[i];
        if(otherPlayer->connectionId == INVALID_FD || otherPlayer->id == ctx->player->id) continue;

        //send players to new player
        addPlayerAndUpdate(otherPlayer, ctx->player->id);
        //add new player to everyone else's list
        addPlayerAndUpdate(ctx->player, otherPlayer->id);
    }

    //add self to player list
    addPlayerAndUpdate(ctx->player, ctx->player->id);

    joinArea(0, ctx->player);

    arupSendStatus(ctx->player->id);
    arupSendCm(ctx->player->id);
    arupSendLockStatus(ctx->player->id);
    logAction("READY", ctx->player->id);
}

UnargedPacketHandler handshakePacketDispatchTable[] = {
    {"ID", handle_ID},
    {"askchaa", handle_askchaa},
    {"RC", handle_RC},
    {"RM", handle_RM},
    {"RD", handle_RD}
}; /**< This is where the handshake packets are declared. Refer to the official network protocol,
    UnargedPacketHandler and CommandContext*/

void handle_CH(CommandContext *ctx) {
    ctx->conn->lastCh = time(NULL);
    sendPacket("CHECK#%", ctx->player->connectionId);
}

void handle_CC(CommandContext *ctx) {
    uint16_t charId = charCount;
    if(*ctx->args[1] != '\0' && *ctx->args[1] != '-') charId = parseInt(ctx->args[1]);
    if(charId > charCount) return;
    playerChangeCharacter(charId, ctx->player); 
}

void handle_MC(CommandContext *ctx) {
    if(strstr(ctx->args[0], ".") != NULL) { playAreaSong(ctx->args[0], ctx->player); return; }

    for (uint8_t i = 0; i < areaCount; i++){
        if(strcmp(areaList[i].name, ctx->args[0]) != 0) continue;
        validateAndJoinArea(i, ctx->player);
        return;
    }

    playerPlaySong("", ctx->player);
}

void handle_HP(CommandContext *ctx) {
    if(areaHasCmAndIsNotPlayer(ctx->player)) return;
    uint8_t bar = parseInt(ctx->args[0]);
    if(bar > 2) return;
    uint8_t value = parseInt(ctx->args[1]);
    if(value > 10) return;
    updateLifeBar(ctx->player->areaId, bar, value);
    logAction("LIFE_UPDATE", ctx->player->id);
}

//OE GUEBONAZOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO AYHYYYYYYYY CAUSITAA
void handle_PE(CommandContext *ctx) {
    Evidence *evidenceList = ctx->p_area->evidence;
    uint8_t found = EVIDENCE_MAX_COUNT;
    for(uint8_t i = 0; i < EVIDENCE_MAX_COUNT; i++){
        Evidence *evidence = &evidenceList[i];
        if(isEvidenceEmpty(evidence) == 1){
            if(!validateAndSetEvidence(evidence, ctx->args[0], ctx->args[1], ctx->args[2], ctx->player->id)) return;
            if(isEvidenceEmpty(evidence)) setEvidence(evidence, "<name>", "<description>", "<image>");
            found = i;
            break;
        }
    }
    if(found == EVIDENCE_MAX_COUNT) HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("Evidence count reached limit of " XSTR(EVIDENCE_MAX_COUNT), ctx->player->id);
    sendEvidenceListPacket(ctx->player->areaId, maxPlayers);
    logAction("ADD_EVIDENCE", found);
}

void handle_DE(CommandContext *ctx) {
    uint8_t evidenceIndex = parseInt(ctx->args[0]);
    if(evidenceIndex >= EVIDENCE_MAX_COUNT) return;
    Evidence *evidenceList = ctx->p_area->evidence;
    for(uint8_t i = evidenceIndex; i < EVIDENCE_MAX_COUNT; i++){
        Evidence *evidence = &evidenceList[i];
        if(i == EVIDENCE_MAX_COUNT-1) { setEvidence(evidence, "\0", "\0", "\0"); break; }
        evidenceList[i] = evidenceList[i+1];
        if(isEvidenceEmpty(evidence)) break;
    }
    sendEvidenceListPacket(ctx->player->areaId, maxPlayers);
    logActionTowards("DELETE_EVIDENCE", ctx->player->id, evidenceIndex);
}

void handle_EE(CommandContext *ctx) {
    uint8_t evidenceIndex = parseInt(ctx->args[0]);
    if(evidenceIndex >= EVIDENCE_MAX_COUNT) return;
    if(!validateAndSetEvidence(&ctx->p_area->evidence[evidenceIndex], ctx->args[1], ctx->args[2], ctx->args[3], ctx->player->id)) return;
    sendEvidenceListPacket(ctx->player->areaId, maxPlayers);
    logActionTowards("EDIT_EVIDENCE", ctx->player->id, evidenceIndex);
}

void handle_RT(CommandContext *ctx) {
    char animationBuffer[SUM_COMMON_EXT(20)] = "";
    if(ctx->argCount >= 2) sprintf(animationBuffer, "RT#%s#%s#%%", ctx->args[0], ctx->args[1]); 
    else sprintf(animationBuffer, "RT#%s#%%", ctx->args[0]);
    sendPacket(animationBuffer, ctx->player->id);
    logAction("JUDGE_ACTION", ctx->player->id);
}

void handle_MA(CommandContext *ctx) {
    if(!(ctx->player->flags & PLAYER_MOD_FLAG)) return;
    if(*ctx->args[1] == '0' || *ctx->args[1] == '\0') { validateIdAndKick(ctx->args[0], ctx->player->id, ctx->master); return; }
    validateIdAndBanAndDisconnectPlayer(ctx->args[0], ctx->player->id, ctx->master);
}

void handle_MS(CommandContext *ctx) {
    /*=========== Abandon all hope, ye who enter here ===========*/
    if(ctx->packetSize > SERVER_MS_PACKET_LENGTH) HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("MS Packet size limit exceeded.", ctx->player->id);

    if(*ctx->player->lastIcMessage != '\0' && !strcmp(ctx->player->lastIcMessage, ctx->args[4]) && !(ctx->p_area->testimonyStatus == AREA_TESTIMONY_STATUS_TESTIFYING)) return;
    if(ctx->player->charId >= charCount) HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("Spectators VVIL NOT use IC chat", ctx->player->id);
    if(ctx->p_area->lockStatus == SPECTATABLE && !playerIsCmOrMod(ctx->player)) HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("The area is in spectatable mode. Only CMs can use IC chat for now", ctx->player->id);
    if(ctx->player->flags & PLAYER_MUTE_FLAG) HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("You're muted", ctx->player->id);

    char msBuffer[SERVER_MS_PACKET_LENGTH] = "";
    size_t used = sprintf(msBuffer, "MS#");
    if(DEFAULT_HIDE_WIT_DESK_ON_JOIN && !(ctx->player->flags & PLAYER_SENT_MESSAGE_FLAG) && 
    !strcmp(ctx->args[5], "wit") &&
    ctx->p_area->status == 0) ctx->player->flags |= PLAYER_HIDE_DESK_FLAG;

    //deskmod
    char *p_desk = &msBuffer[used];
    if(*ctx->args[0] == '0' || *ctx->args[0] > '5' || (ctx->player->flags & PLAYER_HIDE_DESK_FLAG)) *ctx->args[0] = '\0';
    else if(strcmp(ctx->args[0], "chat") == 0) *ctx->args[0] = '1';

    used += sprintf(msBuffer + used, "%s#", ctx->args[0]);

    //preanim
    VALIDATE_STRING_LENGTH_NOTIFY_AND_RETURN(ctx->args[1], EMOTE_NAME_MAX_LENGTH, "Emote", ctx->player->id);
    if(*ctx->args[1] == '-') *ctx->args[1] = '\0';
    used += sprintf(msBuffer + used, "%s#", ctx->args[1]);

    //char name
    VALIDATE_STRING_LENGTH_NOTIFY_AND_RETURN(ctx->args[2], CHAR_NAME_MAX_LENGTH, "Character", ctx->player->id);
    used += sprintf(msBuffer + used, "%s#", ctx->args[2]);
    strcpy(ctx->player->charName, ctx->args[2]);

    //emote
    if(!(ctx->player->flags & PLAYER_FIRSTPERSON_FLAG)){
        VALIDATE_STRING_LENGTH_NOTIFY_AND_RETURN(ctx->args[3], EMOTE_NAME_MAX_LENGTH, "Emote", ctx->player->id);
    }
    else *ctx->args[3] = '\0';
    used += sprintf(msBuffer + used, "%s#", ctx->args[3]);
    strcpy(ctx->player->emote, ctx->args[3]);

    //message
    /*i dont fucking remember*/
    if(strlen(ctx->args[4]) > IC_MESSAGE_MAX_LENGTH) ctx->args[4][IC_MESSAGE_MAX_LENGTH] = '\0';
    seed ^= *(ctx->args[4]);
    if(ctx->p_area->testimonyStatus == AREA_TESTIMONY_STATUS_RECORDING && ctx->p_area->currentStatement == &ctx->p_area->testimonyStatements[0]) used += sprintf(msBuffer + used, "~~--- %.*s ---#", IC_MESSAGE_MAX_LENGTH, ctx->args[4]);
    else if(ctx->p_area->testimonyStatus == AREA_TESTIMONY_STATUS_TESTIFYING && strlen(ctx->args[4]) == 1 && *ctx->args[4] >= 60 && *ctx->args[4] <= 62){
        if(*ctx->args[4] == '>'){
            if(ctx->p_area->currentStatement == &ctx->p_area->testimonyStatements[AREA_MAX_TESTIMONY_STATEMENTS] || ctx->p_area->currentStatement[1][0] == '\0'){
                HOST_SEND_OOC_MESSAGE_TO_AREA("Last statement has been reached. Looping back to the first statement", ctx->player->areaId);
                ctx->p_area->currentStatement = &ctx->p_area->testimonyStatements[1];
            }else{
                ctx->p_area->currentStatement++;
                HOST_SEND_VARIABLE_OOC_MESSAGE_TO_AREA("%s moved to the next statement", OOC_NAME_MAX_LENGTH, ctx->player->areaId, charList[ctx->player->charId]);
            } 
        }
        else if(*ctx->args[4] == '<'){
            if(ctx->p_area->currentStatement == &ctx->p_area->testimonyStatements[0] || ctx->p_area->currentStatement == &ctx->p_area->testimonyStatements[1]){
                HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("Testimony is already at the first statement", ctx->player->id);
            }else{
                ctx->p_area->currentStatement--;
                HOST_SEND_VARIABLE_OOC_MESSAGE_TO_AREA("%s moved to the previous statement", OOC_NAME_MAX_LENGTH, ctx->player->areaId, charList[ctx->player->charId]);
            } 
        }else HOST_SEND_VARIABLE_OOC_MESSAGE_TO_AREA("%s repeated the current statement", OOC_NAME_MAX_LENGTH, ctx->player->areaId, charList[ctx->player->charId]);
        sendPacketToArea(*ctx->p_area->currentStatement, ctx->player->areaId);
        logBase("[MOVE_TESTIMONY][%u][%c]", ctx->player->id, *ctx->args[4]);
        return;
    }
    else{
        if(ctx->player->flags & PLAYER_GIMP_FLAG){
            xorShiftSeed();
            ctx->args[4] = gimps[(seed % ARRAY_SIZE(gimps))];
        }
        else if(ctx->player->flags & PLAYER_DISEMVOWEL_FLAG){
            char *p = ctx->args[4];
            char *p_dst = ctx->args[4];
            while(*p){
                char c = *p >= 'A' && *p <= 'Z' ? *p + ('a' - 'A') : *p;
                if (c != 'a' && c != 'e' && c != 'i' && c != 'o' && c != 'u') *p_dst++ = *p;
                p++;
            }
            *p_dst = '\0';
        }
        used += sprintf(msBuffer + used, "%s#", ctx->args[4]);
    } 
    //side or position
    VALIDATE_STRING_LENGTH_NOTIFY_AND_RETURN(ctx->args[5], POS_NAME_MAX_LENGTH, "Side", ctx->player->id);
    used += sprintf(msBuffer + used, "%s#", ctx->args[5]);
    strcpy(ctx->player->pos, ctx->args[5]);

    //sfx name
    VALIDATE_STRING_LENGTH_NOTIFY_AND_RETURN(ctx->args[6], SFX_NAME_MAX_LENGTH, "SFX", ctx->player->id);
    if(*ctx->args[6] == '1' || *ctx->args[6] == '0') *ctx->args[6] = '\0';
    used += sprintf(msBuffer + used, "%s#", ctx->args[6]);

    //emote modifier
    if(*ctx->args[7] > '6' || *ctx->args[7] == '4' || *ctx->args[7] == '3' || *ctx->args[7] == '0') *ctx->args[7] = '\0';
    used += sprintf(msBuffer + used, "%s#", ctx->args[7]);

    /*char id*/
    uint16_t parsedCharId = parseInt(ctx->args[8]);
    if(parsedCharId != ctx->player->charId) HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("Invalid character ID", ctx->player->id);
    used += sprintf(msBuffer + used, "%d#", parsedCharId);

    //sfx delay
    VALIDATE_STRING_LENGTH_NOTIFY_AND_RETURN(ctx->args[9], SFX_NAME_MAX_LENGTH, "SFX Delay", ctx->player->id);
    if(*ctx->args[9] == '0') *ctx->args[9] = '\0';
    used += sprintf(msBuffer + used, "%s#", ctx->args[9]);

    //objection mod
    char *customShout = ctx->args[10] + 1;
    if(*ctx->args[10] > '4' || *ctx->args[10] == '0') *ctx->args[10] = '\0';
    used += sprintf(msBuffer + used, "%s", ctx->args[10]);
    if(*ctx->args[10] == '4' && *customShout != '#' && *customShout != '\0'){
        customShout++;
        if(*customShout == '&') customShout++;
        else if(strstr(customShout, "<and>") != NULL) customShout += 5;
        if(strlen(customShout) < SHOUT_NAME_MAX_LENGTH && *customShout != '0') used += sprintf(msBuffer + used, "&%s", customShout);
    }
    used += sprintf(msBuffer + used, "#");

    //evidence 
    if(*ctx->args[11] == '0' || parseInt(ctx->args[11]) > countEvidence(ctx->player->areaId)) *ctx->args[11] = '\0';
    used += sprintf(msBuffer + used, "%s#", ctx->args[11]);
    //flip
    if(*ctx->args[12] == '1'){
        ctx->player->flags |= PLAYER_FLIP_FLAG;
    }else{
        *ctx->args[12] = '\0';
        ctx->player->flags &= ~PLAYER_FLIP_FLAG;
    }

    used += sprintf(msBuffer + used, "%s#", ctx->args[12]);

    //realization
    used += nigArg(ctx->args[13], msBuffer, used);

    //text color
    //just like in reddit jewtube greentext stories from 4chan!!!!!!!!!!!!!!
    if(*ctx->args[4] == '>') *ctx->args[14] = '1';
    // else if(*ctx->args[4] == '<') *ctx->args[14] = '6';
    else if((*ctx->args[14] > '9' || *ctx->args[14] == '0') && !(ctx->player->flags & PLAYER_TESTIFYING_FLAG)) *ctx->args[14] = '\0';
    used += sprintf(msBuffer + used, "%s#", ctx->args[14]);
    char *color_p = &msBuffer[used-2];

    //showname. if longer than max showname length trunks cortalo silenciosamente puta
    if(strncmp(ctx->player->showname, ctx->args[15], SHOWNAME_MAX_LENGTH) != 0){
        safe_strcpy(ctx->player->showname, ctx->args[15], SHOWNAME_MAX_LENGTH);
        sendCharacterShowNameUpdate(ctx->player, maxPlayers);
    }
    used += sprintf(msBuffer + used, "%s#", ctx->player->showname);


    //NIGGERNIGGERNIGGERNIGGERNIGGERNIGGERNIGGERNIGGERNIGGERNIGGERNIGGERNIGGERNIGGERNIGGERNIGGER
    //PAIR. OTHER CHAR ID, OTHER NAME AND OTHER EMOTE
    Player *otherPlayer = ctx->player->otherId >= maxPlayers ? NULL : &players[ctx->player->otherId];

    if(otherPlayer && ((!(otherPlayer->flags & PLAYER_JOINED_FLAG) || otherPlayer->areaId != ctx->player->areaId))){
        stopPairing(ctx->player->id);
        otherPlayer = NULL;
    }

    if((*ctx->args[16] >= '0' && *ctx->args[16] <= '9') && !(ctx->player->flags & PLAYER_FIRSTPERSON_FLAG)){
        uint16_t parsedOtherCharId = parseInt(ctx->args[16]);
        for(uint8_t i = 0; i < maxPlayers; i++){
            Player *otherPlayer_i = &players[i];
            if(parsedOtherCharId == otherPlayer_i->charId){ 
                otherPlayer = otherPlayer_i;
                pairWithPlayer(ctx->player->id, otherPlayer->id);
                break; 
            }
        }
    }

    int8_t autopaired = 0;
    uint8_t paired = 0;

    if(otherPlayer && canMutuallyPair(ctx->player, otherPlayer) ){
        //SI SE ENCONTRÓ AL OTRO Y PUEDE PAIREAR, ENTONCES BIENVENIDO AL MUNDO DEL SIDA!!!
        setMsOtherPair(otherPlayer, ctx->args[16], msBuffer, &used);
        paired = 1;
    }else if(DEFAULT_AUTOPAIR && !strcmp(ctx->player->pos, "wit") && (ctx->player->flags & PLAYER_AUTOPAIR_FLAG)){
        //to fill a slot in case no one ap'd after the server started running
        if(ctx->p_area->autoPair[1] == maxPlayers){
            ctx->p_area->autoPair[1] = ctx->player->id;
        }
        else{
            //replace the one who didnt speak last if self not found
            if(ctx->p_area->autoPair[0] == ctx->player->id){ 
                autopaired = -1; 
                otherPlayer = &players[ctx->p_area->autoPair[1]];
                ctx->p_area->didntSpeak = 1;
            }
            else if(ctx->p_area->autoPair[1] == ctx->player->id){ 
                autopaired = 1; 
                otherPlayer = &players[ctx->p_area->autoPair[0]]; 
                ctx->p_area->didntSpeak = 0;
            }
            else{
                uint8_t base = ctx->p_area->didntSpeak;
                uint8_t other = !base;

                if (!isPairable(ctx->player, &players[ctx->p_area->autoPair[other]])) {
                    base = other;
                    other = !other;
                }

                ctx->p_area->autoPair[base] = ctx->player->id;
                otherPlayer = &players[ctx->p_area->autoPair[other]];
                ctx->p_area->didntSpeak = other;
                autopaired = (other == 0) ? 1 : -1;
            }

            //set ms if other is autopairable
            if(isPairable(ctx->player, otherPlayer) && (otherPlayer->flags & PLAYER_AUTOPAIR_FLAG)){
                //set ms and show on top
                setMsOtherPair(otherPlayer, "^0", msBuffer, &used);
                //hide desk if one of the two hid it
                if(*p_desk != '#' && 
                ((ctx->player->flags & PLAYER_HIDE_DESK_FLAG) || 
                otherPlayer->flags & PLAYER_HIDE_DESK_FLAG)) *p_desk = '0';
            } 
            else autopaired = 0;
        }
    }
    
    if(!paired && autopaired == 0){
        used += sprintf(msBuffer + used, "###");
        otherPlayer = NULL;
    } 

    //self offset
    int8_t offset_x = parsePotentialNegative(ctx->args[17]);

    int8_t offset_y = 0;
    char *offset_p;
    if((offset_p = strstr(ctx->args[17], "&"))) offset_y = parsePotentialNegative(offset_p + 1);
    else if((offset_p = strstr(ctx->args[17], "<and>"))) offset_y = parsePotentialNegative(offset_p + 5);

    ctx->player->offset_x = offset_x;
    ctx->player->offset_y = offset_y;
    if(autopaired != 0 && offset_x == 0) offset_x = autopaired * 25;
    setMsOffset(offset_x, offset_y, msBuffer, &used);
    

    //back to other shit because a retarded gorilla nigger designed thxis protocol fuck that retard
    //other_offset and other_flip
    if(!otherPlayer) used += sprintf(msBuffer + used, "##");
    else{
        uint8_t msOtherOffsetX = autopaired != 0 && otherPlayer->offset_x == 0 ? 25 * autopaired * -1 : otherPlayer->offset_x;
        setMsOffset(msOtherOffsetX, otherPlayer->offset_y, msBuffer, &used);
        if(otherPlayer->flags & PLAYER_FLIP_FLAG) used += sprintf(msBuffer + used, "1");
        used += sprintf(msBuffer + used, "#");
    }

    //noninterrupting preanim
    used += nigArg(ctx->args[18], msBuffer, used);

    //sfx looping
    used += nigArg(ctx->args[19], msBuffer, used);

    //sfx looping
    used += nigArg(ctx->args[20], msBuffer, used);

    //tf are these
    //frames_shake frames_realization frames_sfx, skipping 21 22 23
    //TODO???
    used += sprintf(msBuffer + used, "###");

    //additive
    used += nigArg(ctx->args[24], msBuffer, used);

    //effect
    if(strlen(ctx->args[25]) < EFFECT_NAME_MAX_LENGTH && *ctx->args[25] != '|') used += sprintf(msBuffer + used, "%s#", ctx->args[25]);


    //blips
    if(strlen(ctx->args[26]) < BLIPS_NAME_MAX_LENGTH) used += sprintf(msBuffer + used, "%s#", ctx->args[26]);


    sprintf(msBuffer + used, "%%");

    //DO SOMETHING IF TESTIMONES ARE BEING RECORDED/MODIFIED
    /*don't fucking ask me*/
    if(ctx->p_area->testimonyStatus != AREA_TESTIMONY_STATUS_PAUSED && ctx->p_area->testimonyStatus != AREA_TESTIMONY_STATUS_TESTIFYING){
        if(!(ctx->player->flags & PLAYER_TESTIFYING_FLAG)) HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("Order in the court! A witness is testifying!", ctx->player->id);
        if(ctx->p_area->testimonyStatus == AREA_TESTIMONY_STATUS_RECORDING){
            if(ctx->p_area->currentStatement == &ctx->p_area->testimonyStatements[AREA_MAX_TESTIMONY_STATEMENTS]) HOST_SEND_VARIABLE_OOC_MESSAGE_TO_PLAYER(OOC_TESTIMONY_MAX_REACHED, DIGITS(AREA_MAX_TESTIMONY_STATEMENTS), ctx->player->id, AREA_MAX_TESTIMONY_STATEMENTS);
            else{
                if(ctx->p_area->currentStatement == &ctx->p_area->testimonyStatements[0]){
                    *color_p = '3';
                    sendPacketToArea("RT#testimony1#%", ctx->p_area->id);
                    //the +1 is a hack for the compiler to stfu
                    safe_strcpy(*ctx->p_area->currentStatement, msBuffer, SERVER_MS_PACKET_LENGTH);
                }else{
                    *color_p = '1';
                    safe_strcpy(*ctx->p_area->currentStatement, msBuffer, SERVER_MS_PACKET_LENGTH);
                    *color_p = '0';
                }
                ctx->p_area->currentStatement++;
            }
        }else{
            *color_p = '1';
            if(ctx->p_area->testimonyStatus == AREA_TESTIMONY_STATUS_ADDING){
                if(ctx->p_area->currentStatement == &ctx->p_area->testimonyStatements[AREA_MAX_TESTIMONY_STATEMENTS-1]) HOST_SEND_VARIABLE_OOC_MESSAGE_TO_PLAYER(OOC_TESTIMONY_MAX_REACHED, DIGITS(AREA_MAX_TESTIMONY_STATEMENTS), ctx->player->id, AREA_MAX_TESTIMONY_STATEMENTS);
                else{
                    for (char (*p_test)[SERVER_MS_PACKET_LENGTH] =
                    &ctx->p_area->testimonyStatements[AREA_MAX_TESTIMONY_STATEMENTS-1]; 
                    p_test > (char (*)[SERVER_MS_PACKET_LENGTH])ctx->p_area->currentStatement + 1;
                    p_test--){
                        safe_strcpy(*p_test, *(p_test-1), SERVER_MS_PACKET_LENGTH);
                    }
                    ctx->p_area->currentStatement++;
                    safe_strcpy(*ctx->p_area->currentStatement, msBuffer, SERVER_MS_PACKET_LENGTH);
                }
            }else if(ctx->p_area->testimonyStatus == AREA_TESTIMONY_STATUS_AMENDING) safe_strcpy(*ctx->p_area->currentStatement, msBuffer, SERVER_MS_PACKET_LENGTH);

            ctx->p_area->testimonyStatus = AREA_TESTIMONY_STATUS_TESTIFYING;
            ctx->player->flags ^= PLAYER_TESTIFYING_FLAG;
        }
    }
    //GO SEND THAT SHIT
    sendPacketToArea(msBuffer, ctx->p_area->id);
    safe_strcpy(ctx->player->lastIcMessage, ctx->args[4], IC_MESSAGE_MAX_LENGTH);
    ctx->player->flags |= PLAYER_SENT_MESSAGE_FLAG;
    
    //DO WHATEVER WITH THE MESSAGE BEFORE LOGGING BECAUSE THE SANITIZER MODIFIES THE ORIGINAL STRING
    sanitizeStringBuffer(ctx->args[4]);
    if(!(ctx->player->flags & PLAYER_OTR_FLAG)) logBase("[IC][%s][%u][%s (%s)] %s", 
    areaList[ctx->player->areaId].name, 
    ctx->player->id, ctx->player->charName, 
    ctx->player->showname, 
    ctx->args[4]);
    else logAction("OTR_IC", ctx->player->id);
}

/**
  OOC Command Handler structure

  This is what goes in *CommandDispatchTable. These tables are populated using the macro lists
  *_COMMAND_LIST
  Adding a new command is just a macro list creating this struct with, the name (and aliases), its handler, 
  specifying the number of required arguments and the CT packet
  that gets sent to the player when /help <command> is received (the macros later add its <required> <arguments>).
  
  *_COMMAND_LIST are put in different lists to keep permissions and help categories separate. See loopOocTables()

  \param command
    Command name and aliases, separated by |. The one before the first | is the name of the command.
  \param help
    The des
  \param handler
    The OocHandlerFunc that gets shot if the command sent by the player matches the command or one of its alias.
  \param requiredArgs
    Amount of required args to validate before shooting the handler.
*/
typedef struct {
    const char *command;
    const char *help;
    OocHandlerFunc handler;
    uint8_t requiredArgs;
} OocCommandHandler;


/**The X() *_COMMAND_LIST receive in order: 
    command name, handler name, required args, help description, required arguments.
    If you want to know what each handler command does, check their descriptions below lol.*/
#define COMMAND_LIST \
    X("help", handle_help, 0, "Get information about a command or a list if no arguments are provided", "<command|mod>") \
    X("g", handle_global_message, 1, "Send OOC message to all connected clients", "<message>") \
    X("pm|msg|message", handle_private_message, 2, "Send private message to the selected player", "<pid> <message>") \
    X("getarea|ga", handle_getarea, 0, "Send list with the information of every player in the area", "") \
    X("getareas|gas", handle_getareas, 0, "/getarea but including all areas", "") \
    X("pair", handle_pair, 1, "Pair with the selected client", "<pid|clear> <rape?> <the_raped?>") \
    X("pos", handle_pos, 1, "Set player position", "<pos>") \
    X("firstperson|fp", handle_firstperson, 0, "Toggle firstperson", "") \
    X("autopair|ap", handle_autopair, 0, "Toggle automatic pairing on wit", "") \
    X("desk", handle_desk, 0, "Toggle desk visibility", "") \
    X("coinflip", handle_coinflip, 0, "Flip a coin", "") \
    X("roll", handle_roll, 1, "Roll a die", "<max>") \
    X("randomchar", handle_randomchar, 0, "Pick a random character from the list", "") \
    X("cm", handle_cm, 0, "Toggle own CM status (no arguments) or give to another player", "<pid?/all>") \
    X("otr", handle_otr, 0, "Toggle OTR (Off the Record). If activated, your IC messages won't be logged by the server", "") \
    X("kickother", handle_kickother, 0, "Kick all of your other clients from the server", "") \
    X("login", handle_login, 0, "Authenticate as mod", "<password?>") \

void handle_global_message(CommandContext *ctx, OocCommandContext *oocCtx){
    char ctBuffer[SUM_COMMON_EXT(OOC_NAME_MAX_LENGTH + OOC_MESSAGE_MAX_LENGTH + 13)] = "";
    sprintf(ctBuffer, "CT#$[G] %s#%s#%%", ctx->args[0], oocCtx->oocArgs[0]);
    sendPacketToEveryone(ctBuffer);
}

void handle_private_message(CommandContext *ctx, OocCommandContext *oocCtx){
    VALIDATE_AFFECTED_ID_AND_RETURN_IF_INVALID(oocCtx->oocArgs[0], ctx->player->id)
    if(affectedId == ctx->player->id) HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("You VVILL NOT pm yourself", ctx->player->id);

    char ctBuffer[SUM_COMMON_EXT(STRLEN_CT(OOC_PRIVATE_MESSAGE_PACKET) - 6 + DIGITS(MAX_PLAYERS) + OOC_NAME_MAX_LENGTH + OOC_MESSAGE_MAX_LENGTH)] = "";
    sprintf(ctBuffer, OOC_PRIVATE_MESSAGE_PACKET, ctx->player->id, ctx->player->oocName, oocCtx->oocArgs[1]);
    sendPacket(ctBuffer, ctx->player->id);
    sendPacket(ctBuffer, affectedId);
}

void handle_getarea(CommandContext *ctx, OocCommandContext *oocCtx){
    char ctBuffer[SUM_OOC_HOST_EXT(GET_AREA_SIZE)] = "";
    size_t used = appendPacketNameAndHostToOocMessageBuffer(ctBuffer);
    getArea(ctx->player->areaId, ctBuffer, &used, (ctx->player->flags & PLAYER_MOD_FLAG));
    ctBuffer[used-1] = '\0';
    used--;
    closeHostAndSendPacketToPlayer(ctBuffer, used, ctx->player->id);
}


void handle_getareas(CommandContext *ctx, OocCommandContext *oocCtx){
    char ctBuffer[SUM_OOC_HOST_EXT(GET_AREA_SIZE * AREA_LIST_COUNT)] = "";
    size_t used = appendPacketNameAndHostToOocMessageBuffer(ctBuffer);
    for(uint8_t i = 0; i < areaCount; i++) getArea(i, ctBuffer, &used, (ctx->player->flags & PLAYER_MOD_FLAG));
    ctBuffer[used-1] = '\0';
    used--;
    closeHostAndSendPacketToPlayer(ctBuffer, used, ctx->player->id);
}

void setPos(uint8_t playerId, const char *pos){
    if(!strcmp(players[playerId].pos, pos)) return;
    safe_strcpy(players[playerId].pos, pos, sizeof(players[playerId].pos));
    char spBuffer[SUM_COMMON_EXT(POS_NAME_MAX_LENGTH)] = "";
    sprintf(spBuffer, "SP#%s#%%", pos);
    sendPacket(spBuffer, playerId);
    HOST_SEND_VARIABLE_OOC_MESSAGE_TO_PLAYER("Position set to %s", POS_NAME_MAX_LENGTH, playerId, pos);
}

void handle_pos(CommandContext *ctx, OocCommandContext *oocCtx){
    VALIDATE_STRING_LENGTH_NOTIFY_AND_RETURN(oocCtx->oocArgs[0], POS_NAME_MAX_LENGTH, "Side", ctx->player->id);
    setPos(ctx->player->id, oocCtx->oocArgs[0]);
}

//TODO: abstraer estos en un macro toggler

void handle_firstperson(CommandContext *ctx, OocCommandContext *oocCtx){
    ctx->player->flags ^= PLAYER_FIRSTPERSON_FLAG; \
    if(ctx->player->flags & PLAYER_FIRSTPERSON_FLAG) HOST_SEND_OOC_MESSAGE_TO_PLAYER("Now chatting in first person", ctx->player->id); \
    else HOST_SEND_OOC_MESSAGE_TO_PLAYER("First person chat is now off", ctx->player->id); \
}

void handle_autopair(CommandContext *ctx, OocCommandContext *oocCtx){
    ctx->player->flags ^= PLAYER_AUTOPAIR_FLAG; \
    if(ctx->player->flags & PLAYER_AUTOPAIR_FLAG) HOST_SEND_OOC_MESSAGE_TO_PLAYER("Autopair enabled. Pairing automatically on wit", ctx->player->id); \
    else HOST_SEND_OOC_MESSAGE_TO_PLAYER("Autopair disabled", ctx->player->id); \
}

void handle_desk(CommandContext *ctx, OocCommandContext *oocCtx){
    ctx->player->flags ^= PLAYER_HIDE_DESK_FLAG; \
    if(ctx->player->flags & PLAYER_HIDE_DESK_FLAG) HOST_SEND_OOC_MESSAGE_TO_PLAYER("Hiding desk", ctx->player->id); \
    else HOST_SEND_OOC_MESSAGE_TO_PLAYER("Showing desk", ctx->player->id); \
}

void handle_pair(CommandContext *ctx, OocCommandContext *oocCtx){
    if(!strcmp(oocCtx->oocArgs[0], "clear")){
        if(ctx->player->otherId == maxPlayers) HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("You are not pairing", ctx->player->id);
        stopPairing(ctx->player->id);
        return;
    }
    if(!strcmp(oocCtx->oocArgs[0], "rape")){
        if(!playerIsCmOrMod(ctx->player)) HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN(OOC_NEED_CM, ctx->player->id);
        if(oocCtx->oocArgsc < 2) HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("Insufficient arguments", ctx->player->id);
        VALIDATE_AFFECTED_ID_IN_AREA_AND_RETURN_IF_INVALID(oocCtx->oocArgs[1], ctx->player->id);
        pairWithPlayer(ctx->player->id, affectedId);
        if(ctx->player->id == maxPlayers) return;
        pairWithPlayer(affectedId, ctx->player->id);
        return;
    }

    VALIDATE_AFFECTED_ID_IN_AREA_AND_RETURN_IF_INVALID(oocCtx->oocArgs[0], ctx->player->id);
    pairWithPlayer(ctx->player->id, affectedId);
}

void handle_coinflip(CommandContext *ctx, OocCommandContext *oocCtx){
    const char *cfRes[] = {"heads", "tails"};
    CREATE_AND_APPEND_NAME_AND_HOST_TO_OOC_BUFFER(OOC_COINFLIP, STRLEN_CT(OOC_COINFLIP) + OOC_NAME_MAX_LENGTH + 5 - 4);
    xorShiftSeed();
    used += sprintf(ctBuffer + used, OOC_COINFLIP, ctx->player->oocName, cfRes[seed & 1]);
    closeHostAndSendPacketToArea(ctBuffer, used, ctx->player->areaId);
}

void handle_roll(CommandContext *ctx, OocCommandContext *oocCtx){
    uint16_t max = parseInt(oocCtx->oocArgs[0]);
    if(!max) HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("You should know already", ctx->player->id);
    xorShiftSeed();

    CREATE_AND_APPEND_NAME_AND_HOST_TO_OOC_BUFFER(OOC_ROLL, STRLEN_CT(OOC_ROLL) + OOC_NAME_MAX_LENGTH + 10 - 6);
    used += sprintf(ctBuffer + used, OOC_ROLL, ctx->player->oocName, (seed * (max+1)) / 65535, max);
    closeHostAndSendPacketToArea(ctBuffer, used, ctx->player->areaId);
}

void handle_randomchar(CommandContext *ctx, OocCommandContext *oocCtx){
    uint16_t randomChar = seed % CHAR_LIST_COUNT;

    for (uint8_t i = 0; i < 11; i++){
        xorShiftSeed();
        if(playerChangeCharacter(randomChar, ctx->player)) return;
        randomChar = seed % CHAR_LIST_COUNT;
    }

    //fuck it
    for(uint16_t i = randomChar+1; i <= CHAR_LIST_COUNT; i++){
        if(i == CHAR_LIST_COUNT) i = 0;
        if(i == randomChar) return;
        if(playerChangeCharacter(i, ctx->player)) return;
    }
}

void handle_cm(CommandContext *ctx, OocCommandContext *oocCtx){
    if(areaHasCmAndIsNotPlayer(ctx->player)) HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("This area has a CM already", ctx->player->id);
    if(oocCtx->oocArgsc == 0){ makeCm(!(ctx->player->flags & PLAYER_CM_FLAG), ctx->player, maxPlayers); return; }

    if(!strcmp(oocCtx->oocArgs[0], "all")){
        for(uint8_t i = 0; i < maxPlayers; i++) if((players[i].flags & PLAYER_JOINED_FLAG) && players[i].areaId == ctx->player->areaId && !(players[i].flags & PLAYER_CM_FLAG)) makeCm(1, &players[i], ctx->player->id);
        HOST_SEND_OOC_MESSAGE_TO_AREA("Gave CM to everyone present in the area", ctx->player->areaId);
        return;
    }

    VALIDATE_AFFECTED_ID_IN_AREA_AND_RETURN_IF_INVALID(oocCtx->oocArgs[0], ctx->player->id)
    Player *other_player = &players[affectedId];
    makeCm(!(other_player->flags & PLAYER_CM_FLAG), other_player, ctx->player->id);
}

void handle_otr(CommandContext *ctx, OocCommandContext *oocCtx){
    ctx->player->flags ^= PLAYER_OTR_FLAG;
    if(ctx->player->flags & PLAYER_OTR_FLAG) HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("Stopped logging your IC messages to the server", ctx->player->id);
    else HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("Now logging your IC messages to the server", ctx->player->id);
}

void handle_kickother(CommandContext *ctx, OocCommandContext *oocCtx){
    kickAllPlayerClients(ctx->player->id, ctx->player->id, ctx->master, 0);
}

void handle_login(CommandContext *ctx, OocCommandContext *oocCtx){
    if(ctx->player->flags & PLAYER_MOD_FLAG) HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("Already logged in.", ctx->player->id);
    if(oocCtx->oocArgsc == 0){
        ctx->player->flags |= PLAYER_LOGGING_IN_FLAG;
        HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("Enter your password...", ctx->player->id);
    }
    login(oocCtx->oocArgs[0], ctx->player);
}


#define MOD_OR_CM_COMMAND_LIST \
    X("background|bg", handle_bg, 0, "Change the background. Sends the name of the current background if no arguments are provided", "<background?>") \
    X("play", handle_play, 1, "Play a song", "<song>") \
    X("timer", handle_timer, 2, "Modify the selected timer. 0 is the global timer and only mods can modify it.", "<id> <minutes|hh:mm:ss|pause|show|hide>") \
    X("areamessage|am|doc", handle_areamessage, 0, "Changes the OOC doc/message of the area, sends it if no arguments are provided", "<message?>") \
    X("lock|lockarea", handle_lockarea, 0, "Toggle area lock", "") \
    X("areainvite", handle_areainvite, 1, "Invite player to a locked area", "<pid>") \
    X("areakick|kickarea", handle_areakick, 1, "Needs CM. Kicks a player from the current area towards another (defaults to lobby)", "<pid> <area?>") \
    X("forcepos|fpos", handle_forcepos, 2, "Needs CM. Forces a position on the selected player(s)", "<id|all> <pos>") \
    X("status", handle_status, 1, "Change area status.", "<IDLE|LOOKING-FOR-PLAYERS|CASING|GAMING>") \
    X("spectatable", handle_spectatable, 0, "Toggle spectatable mode in area. When activated, only CMs may speak in IC", "") \
    X("testify", handle_testify, 0, "Server starts recording a testimony in the current area and only the players who use this command at any time during the testimony may speak IC", "") \
    X("pause|stop", handle_testimony_pause, 0, "Pauses testimony recording or testimony during cross examination (so that ic controls stop working)", "") \
    X("examine", handle_examine, 0, "Play the testimony recorded in the area. Use <, > or = in IC to advance through the testimony.", "") \
    X("add", handle_testimony_add, 0, "Add a new statement right after the current one", "") \
    X("amend", handle_testimony_add, 0, "Replace the current statement with the next IC message", "") \
    X("recover", handle_testimony_recover, 0, "Recover the previous testimony. Useful if you accidentally did /testify again after a testimony", "") \


void handle_bg(CommandContext *ctx, OocCommandContext *oocCtx){
    if(oocCtx->oocArgsc == 0){ HOST_SEND_VARIABLE_OOC_MESSAGE_TO_PLAYER(OOC_CURRENT_BG, BACKGROUND_NAME_MAX_LENGTH, ctx->player->id, ctx->p_area->background); return;}
        
    if(areaHasCmAndIsNotPlayer(ctx->player))HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN(OOC_NEED_CM, ctx->player->id);

    VALIDATE_STRING_LENGTH_NOTIFY_AND_RETURN(oocCtx->oocArgs[0], BACKGROUND_NAME_MAX_LENGTH, "Background name", ctx->player->id);

    changeBackground(ctx->player->areaId, oocCtx->oocArgs[0]);

    //HACK. me voy a olvidar de como funciona esta verga en 3 dias
    //el -4 es por el %s %s
    CREATE_AND_APPEND_NAME_AND_HOST_TO_OOC_BUFFER(OOC_CHANGED_BACKGROUND, 
    STRLEN_CT(OOC_CHANGED_BACKGROUND) + BACKGROUND_NAME_MAX_LENGTH + OOC_NAME_MAX_LENGTH - 4);
    //esto se puede abstraer en una funcion mas tarde o algo
    //no mentira no creo
    //no se nigger
    //https://inv.nadeko.net/watch?v=tBf7woTzVZg
    used += sprintf(ctBuffer + used, OOC_CHANGED_BACKGROUND, ctx->player->oocName, oocCtx->oocArgs[0]);
    closeHostAndSendPacketToArea(ctBuffer, used, ctx->player->areaId);
}

void handle_play(CommandContext *ctx, OocCommandContext *oocCtx){
    VALIDATE_STRING_LENGTH_NOTIFY_AND_RETURN(oocCtx->oocArgs[0], SONG_NAME_MAX_LENGTH, "Song name", ctx->player->id);
    playerPlaySong(oocCtx->oocArgs[0], ctx->player);
}

void handle_timer(CommandContext *ctx, OocCommandContext *oocCtx){
    uint8_t timer = parseInt(oocCtx->oocArgs[0]);
    Timer *modifiedTimer = NULL;
    if(timer == 0){
        if(!(ctx->player->flags & PLAYER_MOD_FLAG)) HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("You can't change the global timer without authorization", ctx->player->id);
        modifiedTimer = &globalTimer;
    }else{
        if(timer > AREA_TIMERS_MAX){
            HOST_SEND_VARIABLE_OOC_MESSAGE_TO_PLAYER("Timer ID can't be greater than %u", DIGITS(AREA_TIMERS_MAX), ctx->player->id, AREA_TIMERS_MAX);
            return;
        }
        modifiedTimer = &ctx->p_area->timers[timer-1];
    }

    char tiBuffer[TI_BUFFER_SIZE] = "";
    size_t used = sprintf(tiBuffer, "TI#%u#", timer);
    
    if(!strcmp(oocCtx->oocArgs[1], "show") || !strcmp(oocCtx->oocArgs[1], "pause")){
        uint8_t command = 1;
        if(!strcmp(oocCtx->oocArgs[1], "show")){
            modifiedTimer->show = !modifiedTimer->show;
            command = 3-modifiedTimer->show;
        }else modifiedTimer->started = !modifiedTimer->started;

        if(command == 1){
            if(!modifiedTimer->started) sprintf(tiBuffer + used, "%u#%u000#%%", command, modifiedTimer->timer);
            else{
                sprintf(tiBuffer + used, "0#%u000#%%", modifiedTimer->timer);
                sendPacketToArea(tiBuffer, ctx->player->areaId);
                serverFlags |= SERVER_TIMER_ENABLED_FLAG;
                return;
            }
            sendPacketToArea(tiBuffer, ctx->player->areaId);
        }
        else if(command == 2) showTimerAndMinutesIfPaused(modifiedTimer, tiBuffer, used, maxPlayers, ctx->player->areaId);
        else{
            sprintf(tiBuffer + used, "%u#%%", command);
            sendPacketToArea(tiBuffer, ctx->player->areaId);
        }
    }
    else if(*oocCtx->oocArgs[1] >= '0' && *oocCtx->oocArgs[1] <= '9'){
        uint16_t seconds = 0;
        if(strstr(oocCtx->oocArgs[1], ":")){
            char *ss = strrchr(oocCtx->oocArgs[1], ':');
            if (ss == NULL) return;
            seconds += parseInt(ss + 1);
            *ss = '\0';
            char *mm = strrchr(oocCtx->oocArgs[1], ':');
            if (mm){
                seconds += parseInt(mm + 1) * 60;
                *mm = '\0';
                seconds += parseInt(oocCtx->oocArgs[1]) * 3600;
            }else seconds += parseInt(oocCtx->oocArgs[1]) * 60;
        }else seconds += parseInt(oocCtx->oocArgs[1]) * 60;
        if(!seconds) return;
        modifiedTimer->timer = seconds;
        modifiedTimer->started = 1;
        modifiedTimer->show = 1;
        sprintf(tiBuffer + used, "2#%%");
        sendPacketToArea(tiBuffer, ctx->player->areaId);
        sprintf(tiBuffer + used, "0#%u000#%%", modifiedTimer->timer);
        sendPacketToArea(tiBuffer, ctx->player->areaId);
        serverFlags |= SERVER_TIMER_ENABLED_FLAG;
    }
    else HOST_SEND_OOC_MESSAGE_TO_PLAYER("Invalid argument", ctx->player->id);
}

void handle_areamessage(CommandContext *ctx, OocCommandContext *oocCtx){
    if(oocCtx->oocArgsc == 0){
        if(!ctx->p_area->areaMessage[0]) HOST_SEND_OOC_MESSAGE_TO_PLAYER("No area message set", ctx->player->id);
        else HOST_SEND_OOC_MESSAGE_TO_PLAYER(ctx->p_area->areaMessage, ctx->player->id);
        return;
    }

    VALIDATE_STRING_LENGTH_NOTIFY_AND_RETURN(oocCtx->oocArgs[0], AREA_MESSAGE_MAX_LENGTH, "Area message", ctx->player->id);

    safe_strcpy(ctx->p_area->areaMessage, oocCtx->oocArgs[0], sizeof(ctx->p_area->areaMessage));

    CREATE_AND_APPEND_NAME_AND_HOST_TO_OOC_BUFFER(OOC_AREA_MESSAGE_UPDATED, STRLEN_CT(OOC_AREA_MESSAGE_UPDATED) + AREA_MESSAGE_MAX_LENGTH + OOC_NAME_MAX_LENGTH - 4);
    used += sprintf(ctBuffer + used, OOC_AREA_MESSAGE_UPDATED, ctx->player->oocName, ctx->p_area->areaMessage);
    closeHostAndSendPacketToArea(ctBuffer, used, ctx->player->areaId);
}

void handle_lockarea(CommandContext *ctx, OocCommandContext *oocCtx){
    if(ctx->p_area->lockStatus == FREE) lockArea(ctx->p_area, LOCKED);
    else lockArea(ctx->p_area, FREE);
}

void handle_areainvite(CommandContext *ctx, OocCommandContext *oocCtx){
    VALIDATE_AFFECTED_ID_AND_RETURN_IF_INVALID(oocCtx->oocArgs[0], ctx->player->id);
    // if(ctx->p_area->lockStatus != LOCKED) HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("This area is not locked", ctx->player->id);

    // if(players[affectedId].areaId == ctx->player->areaId) HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("That player is already in the area", ctx->player->id);

    players[affectedId].areaInviteFlags ^= (1 << ctx->player->areaId);

    if((players[affectedId].areaInviteFlags & (1 << ctx->player->areaId))){
        HOST_SEND_VARIABLE_OOC_MESSAGE_TO_AREA("Player %u has been invited to the area", DIGITS(MAX_PLAYERS), ctx->player->areaId, affectedId);
        HOST_SEND_VARIABLE_OOC_MESSAGE_TO_PLAYER("You have been invited to area %s", AREA_NAME_MAX_LENGTH, affectedId, areaList[ctx->player->areaId].name);
    }else{
        HOST_SEND_VARIABLE_OOC_MESSAGE_TO_AREA("Player %u's invitation has been revoked", DIGITS(MAX_PLAYERS), ctx->player->areaId, affectedId);
        HOST_SEND_VARIABLE_OOC_MESSAGE_TO_PLAYER("Your invite to %s has been revoked", AREA_NAME_MAX_LENGTH, affectedId, areaList[ctx->player->areaId].name);
    }
}

void handle_areakick(CommandContext *ctx, OocCommandContext *oocCtx){
    VALIDATE_AFFECTED_ID_IN_AREA_AND_RETURN_IF_INVALID(oocCtx->oocArgs[0], ctx->player->id);

    uint8_t areaId = 0;

    if(oocCtx->oocArgsc >= 2) areaId = parseInt(oocCtx->oocArgs[1]);

    if(areaId >= AREA_LIST_COUNT){
        HOST_SEND_OOC_MESSAGE_TO_PLAYER("Invalid area ID. Defaulting to lobby", ctx->player->id);
        areaId = 0;
    }

    if(areaList[areaId].lockStatus == LOCKED && areaId != 0) HOST_SEND_OOC_MESSAGE_TO_PLAYER("That area is locked! Defaulting to lobby.", ctx->player->id);

    CREATE_AND_APPEND_NAME_AND_HOST_TO_OOC_BUFFER(OOC_AREA_KICKED, 
    STRLEN_CT(OOC_AREA_KICKED) + OOC_NAME_MAX_LENGTH + DIGITS(MAX_PLAYERS) + AREA_NAME_MAX_LENGTH*2 - 8);
    used += sprintf(ctBuffer + used, OOC_AREA_KICKED, ctx->player->oocName, affectedId, areaList[ctx->player->areaId].name, areaList[areaId].name);
    closeHostAndSendPacketToArea(ctBuffer, used, ctx->player->areaId);
    closeHostAndSendPacketToArea(ctBuffer, used, areaId);
    joinArea(areaId, &players[affectedId]);
}

void handle_forcepos(CommandContext *ctx, OocCommandContext *oocCtx){
    if(!playerIsCmOrMod(ctx->player)) HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN(OOC_NEED_CM, ctx->player->id);

    VALIDATE_STRING_LENGTH_NOTIFY_AND_RETURN(oocCtx->oocArgs[0], POS_NAME_MAX_LENGTH, "Side", ctx->player->id);

    if(!strcmp(oocCtx->oocArgs[0], "all")){
        for(uint8_t i = 0; i < maxPlayers; i++){
            if(!players[i].connectionId || players[i].areaId != ctx->player->areaId) continue;
            setPos(i, oocCtx->oocArgs[1]);
        }
        return;
    }

    VALIDATE_AFFECTED_ID_IN_AREA_AND_RETURN_IF_INVALID(oocCtx->oocArgs[0], ctx->player->id);
    setPos(affectedId, oocCtx->oocArgs[1]);
}


void handle_status(CommandContext *ctx, OocCommandContext *oocCtx){
    char *p = oocCtx->oocArgs[0];
    while (*p) {
        if (*p >= 'a' && *p <= 'z') *p = *p - ('a' - 'A');
        p++;
    }

    for (uint8_t i = 0; i < sizeof(AREA_STATUS_NAMES) / sizeof(AREA_STATUS_NAMES[0]); i++) if (strcmp(oocCtx->oocArgs[0], AREA_STATUS_NAMES[i]) == 0){
        if(i == ctx->p_area->status) HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("NIGGERNIGGERNIGGERNIGGERNIGGERNIGGERNIGGERNIGGERNIGGERNIGGERNIGGERNIGGER", ctx->player->id);

        playerSetAreaStatus(ctx->player, i);
        return;
    }

    HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("Invalid argument. Valid area states are IDLE, CASING, GAMING, LOOKING-FOR-PLAYERS", ctx->player->id);
}

void handle_spectatable(CommandContext *ctx, OocCommandContext *oocCtx){
    if(ctx->p_area->lockStatus == SPECTATABLE) lockArea(ctx->p_area, FREE);
    else lockArea(ctx->p_area, SPECTATABLE);
}

void handle_testify(CommandContext *ctx, OocCommandContext *oocCtx){
    if((ctx->player->flags & PLAYER_TESTIFYING_FLAG)){
        ctx->player->flags ^= PLAYER_TESTIFYING_FLAG;
        HOST_SEND_OOC_MESSAGE_TO_PLAYER("Stopped recording your messages", ctx->player->id);
        return;
    }

    if(ctx->p_area->testimonyStatus != AREA_TESTIMONY_STATUS_RECORDING){
        for(uint16_t i = 0; i < AREA_MAX_TESTIMONY_STATEMENTS; i++) ctx->p_area->testimonyStatements[i][0] = '\0';
        ctx->p_area->currentStatement = ctx->p_area->testimonyStatements;
        ctx->p_area->testimonyStatus = AREA_TESTIMONY_STATUS_RECORDING;
        HOST_SEND_OOC_MESSAGE_TO_AREA("A witness is testifying, everybody shut up! Use /pause when you're done", ctx->player->areaId);
        HOST_SEND_OOC_MESSAGE_TO_PLAYER("Use /recover if you didn't intend to overwrite the previous testimony", ctx->player->id);
    }else HOST_SEND_OOC_MESSAGE_TO_PLAYER("A testimony was being recorded already. You're now testifying", ctx->player->id);
    ctx->player->flags ^= PLAYER_TESTIFYING_FLAG;
}

void handle_testimony_pause(CommandContext *ctx, OocCommandContext *oocCtx){
    if(ctx->p_area->testimonyStatus == AREA_TESTIMONY_STATUS_TESTIFYING) HOST_SEND_OOC_MESSAGE_TO_AREA("Testimony paused.", ctx->player->areaId);
    else if(ctx->p_area->testimonyStatus == AREA_TESTIMONY_STATUS_RECORDING){
        sendPacketToArea("RT#testimony1#1#%", ctx->player->areaId);
        HOST_SEND_OOC_MESSAGE_TO_PLAYER("Testimony recording finished. Use /examine to start the cross-examination", ctx->player->id);
    } 
    else HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("Nothing to pause", ctx->player->id);

    ctx->p_area->testimonyStatus = AREA_TESTIMONY_STATUS_PAUSED;

    for(uint8_t i = 0; i < maxPlayers; i++){
        Player *area_player = &players[i];
        if(!(area_player->flags & PLAYER_JOINED_FLAG) || area_player->areaId != ctx->player->areaId) continue;
        ctx->player->flags ^= PLAYER_TESTIFYING_FLAG;
    }

}

void handle_examine(CommandContext *ctx, OocCommandContext *oocCtx){
    if(ctx->p_area->testimonyStatus == AREA_TESTIMONY_STATUS_RECORDING) HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("A testimony is being recorded. Use /pause to stop the recording", ctx->player->id);
    ctx->p_area->testimonyStatus = AREA_TESTIMONY_STATUS_TESTIFYING;
    ctx->p_area->currentStatement = ctx->p_area->testimonyStatements;
    if(**ctx->p_area->testimonyStatements == '\0') HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("No testimony loaded. Use /testify or /record", ctx->player->id);
    sendPacketToArea("RT#testimony2#%", ctx->player->areaId);
    sendPacketToArea(*ctx->p_area->currentStatement, ctx->player->areaId);
}

void handle_testimony_add(CommandContext *ctx, OocCommandContext *oocCtx){
    if(ctx->p_area->testimonyStatements[AREA_MAX_TESTIMONY_STATEMENTS-1][0] != '\0'){
        HOST_SEND_VARIABLE_OOC_MESSAGE_TO_PLAYER(OOC_TESTIMONY_MAX_REACHED, DIGITS(AREA_MAX_TESTIMONY_STATEMENTS), ctx->player->id, AREA_MAX_TESTIMONY_STATEMENTS);
        return;
    } 
    if(ctx->p_area->testimonyStatus == AREA_TESTIMONY_STATUS_RECORDING) HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("Can't add statement while recording", ctx->player->id);
    ctx->player->flags ^= PLAYER_TESTIFYING_FLAG;
    ctx->p_area->testimonyStatus = AREA_TESTIMONY_STATUS_ADDING;
    HOST_SEND_OOC_MESSAGE_TO_AREA("Adding new statement after next IC message", ctx->player->areaId);
}

void handle_testimony_amend(CommandContext *ctx, OocCommandContext *oocCtx){
    if(ctx->p_area->testimonyStatus != AREA_TESTIMONY_STATUS_TESTIFYING) HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("There is no cross examination in progress.", ctx->player->id);
    ctx->player->flags ^= PLAYER_TESTIFYING_FLAG;
    ctx->p_area->testimonyStatus = AREA_TESTIMONY_STATUS_AMENDING;
    HOST_SEND_OOC_MESSAGE_TO_AREA("Amending current statement with next IC message", ctx->player->areaId);
}

void handle_testimony_recover(CommandContext *ctx, OocCommandContext *oocCtx){
    if(ctx->p_area->testimonyStatements[0][1] != 'S') HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("No testimony to recover. Use /testify or /record", ctx->player->id);
    for(uint8_t i = 1; i < AREA_MAX_TESTIMONY_STATEMENTS; i++){
        if(ctx->p_area->testimonyStatements[i][1] == 'S') ctx->p_area->testimonyStatements[i][0] = 'M';
        else break;
    }
    HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("The previous testimony has been recovered. Use /examine to begin", ctx->player->id);
}

#define MOD_COMMAND_LIST \
    X("mute", handle_mute, 1, "Toggle mute. If activated, the player will not be able to send IC or OOC messages", "<pid>") \
    X("kick", handle_kick, 1, "Kick all player's connected clients", "<pid>") \
    X("kickonly", handle_kickonly, 1, "Kick ONLY the selected client", "<pid>") \
    X("ban", handle_ban, 1, "Ban the selected client or ip", "<pid/ip>") \
    X("announce", handle_announce, 1, "Announces a message to all connected clients with a pop-up window", "<message>") \
    X("summon", handle_summon, 1, "Summons a player to your current area", "<pid>") \
    X("gimp", handle_gimp, 1, "Toggle gimp. If activated, the player will send a random message selected from an array", "<pid>") \
    X("disemvowel", handle_disemvowel, 1, "Toggle disemvowel. If activated, the player's vowels will be removed from their messages", "<pid>") \
    X("nuke", handle_nuke, 1, "Try it on yourself first :p", "<pid>") \
    X("shutdown", handle_shutdown, 0, "OY VEY SHUT IT DOWN", "") \


void handle_mute(CommandContext *ctx, OocCommandContext *oocCtx){
    VALIDATE_AFFECTED_ID_AND_RETURN_IF_INVALID_TOGGLE_FLAG_NOTIFY_AND_RETURN(oocCtx->oocArgs[0], ctx->player->id, PLAYER_MUTE_FLAG, "muted");
}

void handle_kick(CommandContext *ctx, OocCommandContext *oocCtx){
    validateIdAndKick(oocCtx->oocArgs[0], ctx->player->id, ctx->master);
}

void handle_kickonly(CommandContext *ctx, OocCommandContext *oocCtx){
    VALIDATE_AFFECTED_ID_AND_RETURN_IF_INVALID(oocCtx->oocArgs[0], ctx->player->id)
    kickPlayer(affectedId, ctx->player->id, ctx->master);
}

void handle_ban(CommandContext *ctx, OocCommandContext *oocCtx){
    if(strchr(oocCtx->oocArgs[0], '.') == NULL){
        validateIdAndBanAndDisconnectPlayer(oocCtx->oocArgs[0], ctx->player->id, ctx->master);
        return;
    }
    if(strlen(oocCtx->oocArgs[0]) > 16) HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("Invalid IP", ctx->player->id);
    banIpAndDisconnect(oocCtx->oocArgs[0], ctx->player->id, ctx->master);
}

void handle_announce(CommandContext *ctx, OocCommandContext *oocCtx){
    VALIDATE_STRING_LENGTH_NOTIFY_AND_RETURN(oocCtx->oocArgs[0], 255, "Announce", ctx->player->id);
    char bbBuffer[SUM_COMMON_EXT(255)] = "";
    sprintf(bbBuffer, "BB#%s#%%", oocCtx->oocArgs[0]);
    sendPacketToEveryone(bbBuffer);
}

void handle_summon(CommandContext *ctx, OocCommandContext *oocCtx){
    VALIDATE_AFFECTED_ID_AND_RETURN_IF_INVALID(oocCtx->oocArgs[0], ctx->player->id)
    joinArea(ctx->player->areaId, &players[affectedId]);
    HOST_SEND_VARIABLE_OOC_MESSAGE_TO_PLAYER("Client %u has been summoned to the area", DIGITS(MAX_PLAYERS), ctx->player->id, affectedId);
}

void handle_nuke(CommandContext *ctx, OocCommandContext *oocCtx){
    VALIDATE_AFFECTED_ID_AND_RETURN_IF_INVALID(oocCtx->oocArgs[0], ctx->player->id)
    players[affectedId].flags ^= PLAYER_NUKE_FLAG;
    if(ctx->player->flags & PLAYER_NUKE_FLAG){
        sendPacket("AUTH#1#%", ctx->player->id);
        serverFlags |= SERVER_NUKING_FLAG;
        HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("Bombing client", ctx->player->id);
    } 
    else{
        HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("Stopped bombing client", ctx->player->id);
        if(!(players[affectedId].flags & PLAYER_MOD_FLAG)) sendPacket("AUTH#-1#%", ctx->player->id);
    } 
}

void handle_shutdown(CommandContext *ctx, OocCommandContext *oocCtx){
    serverFlags |= SERVER_SHUTTING_DOWN_FLAG;
}

void handle_gimp(CommandContext *ctx, OocCommandContext *oocCtx){
    VALIDATE_AFFECTED_ID_AND_RETURN_IF_INVALID_TOGGLE_FLAG_NOTIFY_AND_RETURN(oocCtx->oocArgs[0], ctx->player->id, PLAYER_GIMP_FLAG, "gimped");
}

void handle_disemvowel(CommandContext *ctx, OocCommandContext *oocCtx){
    VALIDATE_AFFECTED_ID_AND_RETURN_IF_INVALID_TOGGLE_FLAG_NOTIFY_AND_RETURN(oocCtx->oocArgs[0], ctx->player->id, PLAYER_DISEMVOWEL_FLAG, "disemvoweled");
}

/** /help message packets generation*/
#define X(name, _handler, _req, desc, _args) \
    "\n/" name
    //"\n/" name " - " desc

    static const char *helpCommandPacket = { "CT#" OOC_HOSTNAME 
    "#===GENERAL COMMANDS===" COMMAND_LIST 
    "\n\n===AREA MANAGEMENT===" MOD_OR_CM_COMMAND_LIST 
    "\n\nUse /help <command> to get information about a particular command" "#1#%" } ;

    static const char *modHelpCommandPacket = { "CT#" OOC_HOSTNAME "#==MOD COMMANDS==" MOD_COMMAND_LIST "#1#%" } ;

#undef X

typedef void (*OocTableLoopCallback)(OocCommandHandler *table, CommandContext *ctx, OocCommandContext *oocCtx, const char *oocCmd, uint8_t valid, uint8_t i);
void loopOocTables(CommandContext *ctx, OocCommandContext *oocCtx, const char *oocCmd, uint8_t valid, OocTableLoopCallback callback);

void validateArgsAndSendCommandHelp(OocCommandHandler *table, CommandContext *ctx, OocCommandContext *_oocCtx, const char *_oocCmd, uint8_t valid, uint8_t i){
    if(valid) sendPacket(table[i].help, ctx->player->id);
    else HOST_SEND_OOC_MESSAGE_TO_PLAYER("UNKNOWN COMMAND. CEASE ALL PURSUIT OF TRUTH", ctx->player->id);
}

void handle_help(CommandContext *ctx, OocCommandContext *oocCtx){
    if(oocCtx->oocArgsc == 0) { sendPacket(helpCommandPacket, ctx->player->id); return; }
    if(!strcmp(oocCtx->oocArgs[0], "mod")){
        if(!(ctx->player->flags & PLAYER_MOD_FLAG)) HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("Insufficient permissions", ctx->player->id);
        sendPacket(modHelpCommandPacket, ctx->player->id);
        return;
    } 
    loopOocTables(ctx, oocCtx, oocCtx->oocArgs[0],
        ((1 << 7) | (1 << 6) | ((!!(ctx->player->flags & PLAYER_MOD_FLAG)) << 5)),
        validateArgsAndSendCommandHelp
    );
}

/**This is where the dispatch tables are populated and the packets for the help commands
    automatically generated and stored.*/
#define X(name, handler, req, desc, args) \
    { name, "CT#" OOC_HOSTNAME "#/" name " - " desc "\nUsage: /" name " " args "#1#%", handler, req },

    OocCommandHandler commonCommandDispatchTable[] = {
        COMMAND_LIST
    };

    OocCommandHandler modOrCmCommandDispatchTable[] = {
        MOD_OR_CM_COMMAND_LIST
    };

    OocCommandHandler modCommandDispatchTable[] = {
        MOD_COMMAND_LIST
    };

#undef X

/** Loop over an ooc dispatch table and run callback() on it if the table's command or 
    its alias matches the command sent by the player.*/
uint8_t loopOocTable(OocCommandHandler *table, size_t size, CommandContext *ctx, OocCommandContext *oocCtx, const char *oocCmd, uint8_t valid, OocTableLoopCallback callback){
    for(size_t i = 0; i < size; i++){
        const char *start = table[i].command;
        const char *end;

        while (*start) {
            end = strchr(start, '|');
            size_t len = end ? (size_t)(end - start) : strlen(start);
            if (strlen(oocCmd) == len && !strncmp(start, oocCmd, len)) {
                callback(table, ctx, oocCtx, oocCmd, valid, i);
                return 1;
            }
            if (end) start = end + 1;
            else break;
        }
    }
    return 0;
}

/** Loop through all tables, return if oocCmd matches in loopOocTable(), otherwise send the help message. */
void loopOocTables(CommandContext *ctx, OocCommandContext *oocCtx, const char *oocCmd, uint8_t valid, OocTableLoopCallback callback){
    if(loopOocTable(commonCommandDispatchTable, ARRAY_SIZE(commonCommandDispatchTable), ctx, oocCtx, oocCmd, (valid >> 7) & 1, callback) ||
    loopOocTable(modOrCmCommandDispatchTable, ARRAY_SIZE(modOrCmCommandDispatchTable), ctx, oocCtx, oocCmd, (valid >> 6) & 1, callback) ||
    loopOocTable(modCommandDispatchTable, ARRAY_SIZE(modCommandDispatchTable), ctx, oocCtx, oocCmd, (valid >> 5) & 1, callback)) return;

    HOST_SEND_OOC_MESSAGE_TO_PLAYER("Unknown command. Use /help to see a list of all available commands", ctx->player->id);
}

/** Validate arguments for oocArgsc and valid permissions and inform the player if invalid. Otherwise, shoot the handler. */
void validateArgsAndShootCommand(OocCommandHandler *table, CommandContext *ctx, OocCommandContext *oocCtx, const char *_oocCmd, uint8_t valid, uint8_t i){
    if(oocCtx->oocArgsc < table[i].requiredArgs){
        HOST_SEND_OOC_MESSAGE_TO_PLAYER("Insufficient arguments", ctx->player->id);
        return;
    } 
    if(!valid){
        HOST_SEND_OOC_MESSAGE_TO_PLAYER("Insufficient permissions", ctx->player->id);
        return;
    } 
    table[i].handler(ctx, oocCtx);
}

/** Do validations and send a message if ooc name is valid and it doesn't start with /.
    If it's a command, then we make sure it's not a command that sends a "message" so that we don't split it into args.
    Otherwise we do the split normally by separating the args by " ", create the OocCommandCtx and then we call loopOocTables()
    with the player's mod and cm statuses to take into account the player's permissions if a command matches.
*/
void handle_CT(CommandContext *ctx){
    //OOC MESSAGE
    //reemplazar dolares por australes
    char *read = ctx->args[0];
    char *write = ctx->args[0];

    while(*read){
        if(!strncmp(read, "<dollar>", 8)) read += 8;
        if(!strncmp(read, "$", 1)) read += 1;
        else *write++ = *read++;
    }
    *write = '\0';

    /*reject empty names
    if(ctx->args[0][0] == '\0') HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("Where is your name?!", playerId);*/
    if(*ctx->args[0] == '\0') return;
    VALIDATE_STRING_LENGTH_NOTIFY_AND_RETURN(ctx->args[0], OOC_MESSAGE_MAX_LENGTH, "OOC name", ctx->player->id);

    //UPDATE OOC NAME
    if(strcmp(ctx->player->oocName, ctx->args[0])){
        for(uint8_t i = 0; i < maxPlayers; i++){
            Player *c_player = &players[i];
            if(!(c_player->flags & PLAYER_JOINED_FLAG) || c_player->id == ctx->player->id) continue;
            if(strcmp(ctx->args[0], c_player->oocName) == 0) HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("Someone is using that name already!", ctx->player->id);
        }
        safe_strcpy(ctx->player->oocName, ctx->args[0], OOC_NAME_MAX_LENGTH);
        sendPlayerOocNameUpdate(ctx->player, maxPlayers);
    }

    //reject empty messages
    if(*ctx->args[1] == '\0') return;

    VALIDATE_STRING_LENGTH_NOTIFY_AND_RETURN(ctx->args[1], OOC_MESSAGE_MAX_LENGTH, "OOC message", ctx->player->id);

    if(ctx->player->flags & PLAYER_LOGGING_IN_FLAG){
        ctx->player->flags ^= PLAYER_LOGGING_IN_FLAG;
        login(ctx->args[1], ctx->player);
        return;
    }

    if(*ctx->args[1] != '/'){
        if(ctx->player->flags & PLAYER_MUTE_FLAG) HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("You're muted", ctx->player->id);
        char ctBuffer[SUM_COMMON_EXT(OOC_NAME_MAX_LENGTH + OOC_MESSAGE_MAX_LENGTH + 1)] = "";
        sprintf(ctBuffer, "CT#%s#%s#%%", ctx->args[0], ctx->args[1]);
        sendPacketToArea(ctBuffer, ctx->player->areaId);
        seed ^= *(ctx->args[1]);
        //DO WHATEVER WITH THE MESSAGE BEFORE LOGGING BECAUSE THE SANITIZER MODIFIES THE ORIGINAL STRING
        sanitizeStringBuffer(ctx->args[1]);
        logBase("[OOC][%s][%d][%s] %s", 
        areaList[ctx->player->areaId].name, 
        ctx->player->id, 
        ctx->player->oocName, 
        ctx->args[1]);
        return;
    }
    char *restrict oocCmdLine = ctx->args[1] + 1;
    if(*oocCmdLine == '\0') HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("Command is empty", ctx->player->id);
    char *oocCmd = strtok(oocCmdLine, " ");
    if(isMessageCmd(oocCmd)) logAction("PM", ctx->player->id);
    else if(!(strcmp(oocCmd, "login"))) logAction("LOGIN", ctx->player->id);
    else{
        char *rest = oocCmd + strlen(oocCmd) + 1;
        if (*rest == ' ') rest++;
        logBase("[OOC_COMMAND][%s][%d] %s: %s %s", areaList[ctx->player->areaId].name, ctx->player->id, ctx->player->oocName, ctx->args[1], rest);
    }
    char *oocArgs[3] = { NULL };
    uint8_t oocArgsc = 0;
    char *oocTok = oocCmd;

    if(!oocTok) HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("command is empty i think", ctx->player->id);
    if(!strcmp(oocCmd, "g") || !strcmp(oocCmd, "play") || !strcmp(oocCmd, "announce")){
        oocTok = strtok(NULL, "\0");
        oocArgs[0] = oocTok;
        if(oocTok == NULL) HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("Insufficient arguments", ctx->player->id);
        oocArgsc++;
    }else if(isMessageCmd(oocCmd)){
        oocTok = strtok(NULL, " ");
        oocArgs[oocArgsc++] = oocTok;
        oocTok = strtok(NULL, "\0");
        if(oocTok == NULL && strcmp(oocCmd, "login")) HOST_SEND_OOC_MESSAGE_TO_PLAYER_AND_RETURN("Can't send empty private message", ctx->player->id);
        oocArgs[oocArgsc++] = oocTok;
    } 
    else{
        while((oocTok = strtok(NULL, " ")) != NULL && oocArgsc < 3) oocArgs[oocArgsc++] = oocTok;
    }

    OocCommandContext oocCtx = {oocArgsc, oocArgs};

    /*l33t or severe mental retardation?
    * i'm just passing the flags
    * >how? what flags?
    * trvst the plan PATRIQT
    */
    loopOocTables(ctx, &oocCtx, oocCmd,
        ((1 << 7)
        | ((!areaHasCmAndIsNotPlayer(ctx->player)) << 6)
        | ((!!(ctx->player->flags & PLAYER_MOD_FLAG)) << 5)),
        validateArgsAndShootCommand
    );
}

CommonPacketHandler commonPacketDispatchTable[] = {
    {"CH", handle_CH, 0},
    {"MS", handle_MS, 15},
    {"MC", handle_MC, 1},
    {"CC", handle_CC, 1},
    {"PE", handle_PE, 3},
    {"DE", handle_DE, 1},
    {"EE", handle_EE, 4},
    {"RT", handle_RT, 1},
    {"HP", handle_HP, 2},
    {"MA", handle_MA, 1},
    {"CT", handle_CT, 2}
}; /**< This is where most packets are declared. Refer to the official network protocol,
CommonPacketHandler (to understand how to implement a new package) and CommandContext*/

/**
    Process the packet (line), split it into args, count, make the context, etc. 
    If the player doesn't have an id assigned yet and sent a HI, 
    add him to the player array, which makes him able to sneed handshake packets.
    If the player is done with the handshake, he's on a joined state, which makes him
    able to send common packets, so in that case we
    check for any violations and if the player is good, we loop through the commonPacketDispatchTable[]
    to see if the packet's name matches.

    If the player sends and OOC command (a CT packet with its second argument starting with a '/'), 
    the process for looping through the tables is almost the same as here.
    See handle_CT() for more information.

    To see how a packet is structured please refer to the network protocol.
*/
void processPacket(socket_t client_fd, fd_set *master, char *line, Conn *conn) {
    char *packet = line;
    socket_t connectionId = client_fd;

    char *p = packet;
    char *start = p;
    uint8_t argCount = 0;
    char *args[27] = { NULL };
    uint8_t firstToken = 1;
    uint64_t packetSize = 0;

    while (1) {
        packetSize++;
        if(*p == '%' || *p == '\0') break;
        if (*p == '#') {
            *p = '\0';
            if(!firstToken) args[argCount++] = start;
            else firstToken = 0;
            start = p + 1;
            if(argCount == 27) break;
        }
        p++;
    }

    for (uint8_t i = argCount; i < 27; i++) args[i] = "";

    DEBUG_PRINT(printf("ConnID: %d | Packet: %s | args=%d\n", connectionId, packet, argCount));
    uint8_t playerId = getPlayerIdByConnectionId(connectionId);
    if(playerId == maxPlayers) DEBUG_PRINT(printf("Jugador no encontrado, intentando unir...\n"));
    else DEBUG_PRINT(printf("Encontrado jugador %d\n", playerId));
    if(playerId == maxPlayers){
        if(strcmp(packet, "HI") != 0) return;
        for (uint8_t i = 0; i < maxPlayers; i++) {
            if(players[i].connectionId == INVALID_FD){
                players[i].connectionId = connectionId;
                players[i].id = i;
                players[i].otherId = maxPlayers;
                players[i].charId = charCount;
                playerId = i;
                logBase("[ID ASSIGNED][%s -> %d]", conn->ip, i);
                break;
            }
        }
        
        char idBuffer[SUM_COMMON_EXT(2 + DIGITS(MAX_PLAYERS) + sizeof(SERVER_SOFTWARE) + sizeof(SERVER_VERSION))] = "";
        sprintf(idBuffer, "ID#%u#%s#%s#%%", playerId, SERVER_SOFTWARE, SERVER_VERSION);
        sendPacket(idBuffer, playerId);
        //no -2 because of the two extra ##s and a +1 for the zero
        char pnBuffer[SUM_COMMON_EXT(sizeof(SERVER_DESCRIPTION) + DIGITS(MAX_PLAYERS)) * 2 + 2] = "";
        sprintf(pnBuffer, "PN#%d#%d#%s#%%", playerCount, maxPlayers, SERVER_DESCRIPTION);
        sendPacket(pnBuffer, playerId);
        return;
    }

    Player *player = &players[playerId];
    CommandContext ctx = {
        .player = player,
        .master = master,
        .args = args,
        .argCount = argCount,
        .p_area = &areaList[players[playerId].areaId],
        .conn = conn,
        .packetSize = packetSize
    };
    
    //check only handshake packets if the player hasnt joined yet
    if(!(player->flags & PLAYER_JOINED_FLAG)){
        for(uint8_t i = 0; i < ARRAY_SIZE(handshakePacketDispatchTable); i++){
            if(strcmp(handshakePacketDispatchTable[i].packetName, packet)) continue;
            handshakePacketDispatchTable[i].handler(&ctx);
            break;
        }
        return;
    }

    time_t now = time(NULL);
    if(now - player->lastMessageTime < FLOODGUARD_MAX_SECONDS){
        if(player->violations == FLOODGUARD_VIOLATIONS_MAX){
            // HOST_SEND_OOC_MESSAGE_TO_PLAYER("Slow down Nigga", playerId);
            return;
        } 
        player->violations++;
    }else{
        player->violations = 0;
        player->lastMessageTime = now;
    }

    //REVISAR PAQUETES COMUNES
    for(uint8_t i = 0; i < ARRAY_SIZE(commonPacketDispatchTable); i++){
        if(strcmp(commonPacketDispatchTable[i].packetName, packet)) continue;
        if(argCount < commonPacketDispatchTable[i].requiredArgs) return;
        commonPacketDispatchTable[i].handler(&ctx);
        break;
    }
}

/** 
    We check if the player is banned, or if the server is full/incoming connection has max connections already.
    If everything's good, we send a decryptor so the client sends a HI#% (THIS SHIT IS NOT DOCUMENTED IN THE PROTOCOL)
*/
void validateClientAndStartHandshake(socket_t client_fd, fd_set *master, Conn *conn){

    uint8_t sameConnections = 0;

    for(uint16_t i = 0; i < maxConnections; i++){
        if(conns[i].ip[0] == '\0') continue;
        if(!strcmp(conns[i].ip, conn->ip)){
            sameConnections++;
            if(sameConnections > MAX_ALLOWED_MULTICLIENT){
                logActionString("MULTICLIENT REJECT", conn->ip);
                closeFd(client_fd, master);
                return;
            }
        } 
    }

    if (playerCount >= maxPlayers) {
        logActionString("SERVER FULL REJECT", conn->ip);
        closeFd(client_fd, master);
        return;
    }

    logActionString("CONNECTED", conn->ip);
    sendToClient(client_fd, "decryptor##%");
}

/** Socket configuration, used to have multiple listen ports.*/
int8_t configureSocket(socket_t *fd, uint16_t port) {
    int opt = 1;
    *fd = socket(AF_INET, SOCK_STREAM, 0);
    if (SOCKET_ERROR_CHECK(*fd)) {
        perror("socket");
        return -1;
    }

    setsockopt(*fd, SOL_SOCKET, SO_REUSEADDR, (const void *)&opt, sizeof(opt));

    struct sockaddr_in addr = {0};
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(port);

    if (bind(*fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        perror("bind");
        return -1;
    }

    if (listen(*fd, 8) < 0) {
        perror("listen");
        return -1;
    }

    return 0;
}

/** We initialize the conns array and players array with INVALID_FD where it corresponds,
    and areas id, hp, autopair values with defaults an so on.
    And then the main loop comes. We configure the networking logic and handle connections and server ticks
    (which may change during execution).
    We process and store connections after that, making handshakes instantly in the tcp port, but having a slightly
    more complex process with connections coming from the websocket port (GIGA BLOAT), 
    in which we wait for the client to send a websocket handshake over the tcp one.
    Then of course, we receive the packets in a different way (see the recvs), 
    but we process them in the same way once the sockets are consoomed at processPacket().

    If the server shuts down, the logic for it comes after the main while loop. For now its just a log message.
*/
int main() {
    // signal(SIGPIPE, SIG_IGN);
    logSingleAction("SERVER START");
    lists_init();
    createAndStoreMusicPacket();


    for (uint16_t i = 0; i < maxConnections; i++) conns[i].fd = INVALID_FD;
    for (uint8_t i = 0; i < maxPlayers; i++) players[i].connectionId = INVALID_FD;

    for (uint8_t i = 0; i < areaCount; i++) {
        if(!areaList[i].hp[0]) areaList[i].hp[0] = 10;
        if(!areaList[i].hp[1]) areaList[i].hp[1] = 10;
        areaList[i].id = i;
        // areaList[i].currentStatement = areaList[i].testimonyStatements;
        areaList[i].autoPair[1] = maxPlayers;
        // areaList[i].autoPair[0] = maxPlayers;
    }

    #ifdef _WIN32
        WSADATA wsa;
        if (WSAStartup(MAKEWORD(2,2), &wsa) != 0) {
            printf("WSAStartup failed\n");
            return 1;
        }
    #endif

    socket_t tcp_fd = INVALID_FD, ws_fd = INVALID_FD;

    if(configureSocket(&tcp_fd, TCP_LISTEN_PORT) < 0) return 1;

    fd_set master, readfds;
    FD_ZERO(&master);
    FD_SET(tcp_fd, &master);

    if(WS_LISTEN_PORT > 0){
        if(configureSocket((&ws_fd), WS_LISTEN_PORT) < 0) return 1;
        FD_SET(ws_fd, &master);
    }

    socket_t maxfd = (tcp_fd > ws_fd) ? tcp_fd : ws_fd;

    printf("Listening on ports %u (TCP) and %u (WS)\n", TCP_LISTEN_PORT, WS_LISTEN_PORT);

    while (!(serverFlags & SERVER_SHUTTING_DOWN_FLAG)) {
        time_t now = time(NULL);

        if(ADVERTISE && now - lastAdvertised >= ADVERTISE_SECONDS_INTERVAL) advertise();

        uint8_t foundNuke = 0;
        for(uint8_t i = 0; i < maxConnections; i++){
            Conn *c = &conns[i];
            if(c->fd == INVALID_FD) continue;

            if(now - c->lastCh > CH_MAX_TIMEOUT_SEC){
                logActionString("CHECK TIME OUT", c->ip);
                disconnectClientOrFd(c, &master);
                continue;
            }

            uint8_t playerId = getPlayerIdByConnectionId(c->fd);

            if(playerId == maxPlayers) continue;

            if(players[playerId].flags & PLAYER_NUKE_FLAG){
                foundNuke = 1;
                sendPacket("ZZ#NIGGER#%", playerId);
            }
        }
        
        foundNuke ? (serverFlags |= PLAYER_NUKE_FLAG) : (serverFlags &= ~PLAYER_NUKE_FLAG);

        readfds = master;

        int activity;
        if (ADVERTISE || (serverFlags & SERVER_NUKING_FLAG) || (serverFlags & SERVER_TIMER_ENABLED_FLAG)) {
            struct timeval tv;
            if(serverFlags & SERVER_NUKING_FLAG) tv.tv_sec = 0;
            else if(serverFlags & SERVER_TIMER_ENABLED_FLAG) tv.tv_sec = 1;
            else if(ADVERTISE) tv.tv_sec = now - lastAdvertised;
            tv.tv_usec = 0;
            activity = select(maxfd + 1, &readfds, NULL, NULL, &tv);
        }
        else{
            activity = select(maxfd + 1, &readfds, NULL, NULL, NULL);
        } 


        if (activity < 0) {
            perror("select");
            continue;
        }

        if(now > last && !foundNuke && (serverFlags & SERVER_TIMER_ENABLED_FLAG)){
            serverFlags ^= SERVER_TIMER_ENABLED_FLAG;
            decreaseTimerIfStartedAndMarkGlobalStarted(&globalTimer);
            for(uint8_t i = 0; i < areaCount; i++){
                Area *area = &areaList[i];
                for(uint8_t j = 0; j < AREA_TIMERS_MAX; j++) decreaseTimerIfStartedAndMarkGlobalStarted(&area->timers[j]);
            }
        }
        socket_t fd;
        FOR_EACH_FD(fd, &readfds, maxfd){

            if (fd == tcp_fd || fd == ws_fd) {
                struct sockaddr_in client_addr;
                socklen_t addrlen = sizeof(client_addr);

                socket_t client_fd = accept(fd, (struct sockaddr*)&client_addr, &addrlen);

                if (SOCKET_ERROR_CHECK(client_fd)) {
                    perror("accept");
                    continue;
                }

                char ip[16];
                inet_ntop(AF_INET, &client_addr.sin_addr, ip, sizeof(ip));

                DEBUG_PRINT(printf("Cliente intentando conectar desde %s\n", ip));

                FILE *fptr;

                fptr = fopen("config/banlist.txt", "r");
                if(fptr != NULL){
                    char line[17];
                    uint8_t banned = 0;
                    while (fgets(line, sizeof(line), fptr)){
                        if(strstr(line, ip)){
                            closeAndClearFd(client_fd, &master);
                            banned = 1;
                            break;
                        }
                    }

                    if(banned){
                        logActionString("REJECT JOIN BAN", ip);
                        continue;
                    }
                    fclose(fptr);
                }


                FD_SET(client_fd, &master);
                if (client_fd > maxfd) maxfd = client_fd;

                uint8_t found = 0;

                for (uint16_t i = 0; i < maxConnections; i++) {
                    if (conns[i].fd == INVALID_FD) {
                        conns[i].fd = client_fd;
                        conns[i].lastCh = time(NULL);
                        safe_strcpy(conns[i].ip, ip, sizeof(ip));
                        if(fd == ws_fd) conns[i].flags |= CONN_IS_WS_FLAG;
                        else validateClientAndStartHandshake(client_fd, &master, &conns[i]);
                        found = 1;
                        break;
                    }
                }

                if(!found){
                    logActionString("NO CONNECTION AVAILABLE", ip);
                    closeAndClearFd(client_fd, &master);
                } 
                
            } else {
                char buf[MAX_PACKET_TOLERANCE];
                int n;

                Conn *c = getConnByFd(fd);
                
                if(WS_LISTEN_PORT > 0 && c->flags & CONN_IS_WS_FLAG){
                    if(!(c->flags & CONN_WS_HANDSHAKE_DONE_FLAG)){
                        n = recv(fd, buf, sizeof(buf)-1, 0);
                        if(!handle_websocket_handshake(fd, buf, c)){
                            closeFd(fd, &master);
                            continue;
                        } 
                        c->flags |= CONN_WS_HANDSHAKE_DONE_FLAG;
                        validateClientAndStartHandshake(fd, &master, c);
                        continue;
                    }
                    n = websocket_recv_frame(fd, buf, sizeof(buf));
                }else n = recv(fd, buf, sizeof(buf)-1, 0);

                //ws also gets dc'd here if it sends something longer than buffers size
                if (n <= 0) {
                    DEBUG_PRINT(printf("Cliente en fd %d desconectado o no envió datos. n = %d\n", fd, n));
                    disconnectClientOrFd(c, &master);
                    continue;
                }
                
                buf[n] = '\0';
                DEBUG_PRINT(printf("RECIBIDO EN BUFFER: %s\n", buf));

                //spare TCP CHADS from dcing if they sent something bigger than buffer's size (FOR NOW)
                if(*buf == '\0' || buf[n-1] != '%') continue;

                char *p = buf;
                char *start = p;
                DEBUG_PRINT(printf("RECIBIDO: %s\n", start));

                while (1) {
                    if(*p == '\0'){
                        if(*start != '\0'){ DEBUG_PRINT(printf("PROCESANDO: %s\n", start)); processPacket(fd, &master, start, c); }
                        break;
                    }
                    if (*p == '%') {
                        *p = '\0';
                        DEBUG_PRINT(printf("PROCESANDO: %s\n", start));
                        processPacket(fd, &master, start, c);
                        start = p + 1;
                    }
                    p++;
                }
            }
        }
        last = now;
    }
    #ifdef _WIN32
        WSACleanup();
    #endif
    logSingleAction("SERVER SHUT DOWN");
    return 0;
}