/*
* Descent 3 
* Copyright (C) 2024 Parallax Software
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.

--- HISTORICAL COMMENTS FOLLOW ---

 * $Logfile: /DescentIII/Main/dmfc/dmfcpackets.cpp $
 * $Revision: 1.1.1.1 $
 * $Date: 2003/08/26 03:57:21 $
 * $Author: kevinb $
 *
 * DMFC Special packet handling functions
 *
 * $Log: dmfcpackets.cpp,v $
 * Revision 1.1.1.1  2003/08/26 03:57:21  kevinb
 * initial 1.5 import
 *
 *
 * 29    8/21/99 12:32a Jeff
 * Changed the name of the GetRealGametime function to
 * GetRealGametimePacket since it was confusing some compilers with the
 * other GetRealGametime function.
 *
 * 28    7/09/99 2:54p Jeff
 * handle gametime better (pause it when needed) if the server is 'waiting
 * for players'
 *
 * 27    7/08/99 6:25p Jeff
 * remote admin in and working
 *
 * 26    7/08/99 2:39a Jeff
 * rough implementation of remote administration checked in.  Still needs
 * some polishing, but should work basically.
 *
 * 25    5/13/99 4:55p Ardussi
 * changes for compiling on the Mac
 *
 * 24    5/12/99 11:05p Jeff
 * dmfc and multiplayer games now have endian friendly packets (*whew*)
 *
 * 23    4/07/99 9:24p Jeff
 * fixed bug in sending team names over
 *
 * 22    3/17/99 12:24p Jeff
 * converted DMFC to be COM interface
 *
 * 21    2/11/99 12:51a Jeff
 * changed names of exported variables
 *
 * 20    12/08/98 4:47p Jeff
 * umm, various changes, fixed pilot pics so that they work for everyone
 * now
 *
 * 19    12/08/98 12:17p Jeff
 * various changes that include an improved Team Control dialog (doesn't
 * switch teams until exit..) and spew/respawn players that change teams
 *
 * 18    11/17/98 12:36p Jeff
 * fixed bug in sending new team name to clients
 *
 * 17    11/16/98 5:35p Jeff
 * removed log functions, added support for changing team names, fixed ctf
 * to work better with different team names
 *
 * 16    11/11/98 7:19p Jeff
 * changes made so that a dedicated server's team is always -1 (team game
 * or not)
 *
 * 15    10/29/98 7:01p Jeff
 * creation of team placement dialog.  Moved TranslateEvent into DMFC
 *
 * 14    10/23/98 11:22a Jeff
 * changes to handle mixcase, and display the client netgame info
 * correctly
 *
 * 13    10/20/98 12:16p Jeff
 * added death message filter, hud callsign filter
 *
 * 12    10/08/98 3:37p Jeff
 * general improvements (Netgame info things, save to registry).  Changes
 * so it would send packets on NETSEQ_OBJECTS
 *
 * 11    9/30/98 4:21p Jeff
 * team changing is handled correctly
 *
 * 10    9/25/98 8:02p Jason
 * added checks for jeff
 *
 * 9     9/21/98 7:11p Jeff
 * made InputCommand interface API and moved existing input commands to
 * the interface.  Changed mprintf/ASSERT so they are valid in DMFC
 *
 * $NoKeywords: $
 */

#include "gamedll_header.h"
#include "DMFC.h"
#include "dmfcinternal.h"


// DMFCBase::RegisterPacketReceiver
//
// Sets up a handler for a Special Packet ID.  When a special packet is recieved it's ID is compared
// to the ID's given to this function.  If any match than it calls the handler given to process
// the packet.
// id = ID of the packet
// func = Function handler to handle the packet.  Must be declared like void MyFunction(uint8_t *data);
void DMFCBase::RegisterPacketReceiver(uint8_t id, void (*func)(uint8_t *)) {
  // go to the last handler
  tSPHandler *current, *last;

  if (!SPRoot) {
    // handle special case for first node being added
    SPRoot = (tSPHandler *)malloc(sizeof(tSPHandler));
    if (!SPRoot)
      return;
    current = SPRoot;
  } else {
    last = current = SPRoot;
    while (current) {
      last = current;
      current = current->next;
    }

    // allocate memory for the node
    current = (tSPHandler *)malloc(sizeof(tSPHandler));
    if (!current)
      return;
    // add it to the list
    last->next = current;
  }

  // fill in the struct
  current->next = NULL;
  current->ID = id;
  current->type = SPH_FUNC;
  current->func = func;
}
void DMFCBase::RegisterPacketReceiver(uint8_t id, void (DMFCBase::*func)(uint8_t *)) {
  // go to the last handler
  tSPHandler *current, *last;

  if (!SPRoot) {
    // handle special case for first node being added
    SPRoot = (tSPHandler *)malloc(sizeof(tSPHandler));
    if (!SPRoot)
      return;
    current = SPRoot;
  } else {
    last = current = SPRoot;
    while (current) {
      last = current;
      current = current->next;
    }

    // allocate memory for the node
    current = (tSPHandler *)malloc(sizeof(tSPHandler));
    if (!current)
      return;
    // add it to the list
    last->next = current;
  }

  // fill in the struct
  current->next = NULL;
  current->ID = id;
  current->type = SPH_DMFCFUNC;
  current->DMFCfunc = func;
}

// DMFCBase::StartPacket
//
//	 Initializes a packet so it is ready to be sent out.
//	 data = pointer to buffer of data for packet.  This buffer should be MAX_GAME_DATA_SIZE in size, even
//			if you don't plan on using all that data, there is header information that is added
//			in this function.
//   id = ID of the Special packet.  When the packet is recieved by whomever, this is the ID that determines which
//   handler to
//        call.
//	 count = pointer to your packet index pointer
void DMFCBase::StartPacket(uint8_t *data, uint8_t id, int *count) {
  *count = 0;

  MultiAddByte(id, data, count); // set the first byte (invisible to coder) as the id
}

// DMFCBase::SendPacket
//
//   Ships a Special Packet away to the destination.  This call must be preceeded by a StartPacket call
//	 data = buffer of data to be sent out (same buffer that was passed to StartPacket
//   size = size (in bytes) of the packet
//   destination = either a player number, SP_ALL for all the players or SP_SERVER to send to the server (if you are a
//   client)
void DMFCBase::SendPacket(uint8_t *data, int size, int destination) {
  ASSERT(size < MAX_GAME_DATA_SIZE - 2); // allow for a header by the d3
  if (size >= MAX_GAME_DATA_SIZE - 2)
    return;

  // if we are the server and are trying to send to the server then exit out
  if ((GetLocalRole() == LR_SERVER) && (destination == SP_SERVER)) {
    return;
  }

  // we can safely send this packet out to it's appropriate destination

  // see if we are trying to send to the server, it's specially handled
  if (destination == SP_SERVER) {
    DLLMultiClientSendSpecialPacket(data, size);
    return;
  }

  // server trying to send to clients
  if (GetLocalRole() == LR_CLIENT) {
    // we can't send client to client
    return;
  }

  // if server is trying to send to all the clients then we need to send to each individual
  if (destination == SP_ALL) {
    int pnum;
    for (pnum = 0; pnum < DLLMAX_PLAYERS; pnum++) {

      if ((pnum != GetPlayerNum()) && (PacketCheckPlayerNum(pnum))) {
        // ship it!
        DLLMultiSendSpecialPacket(pnum, data, size);
      }
    }
    return;
  }

  // we are only sending to one client
  if (destination != GetPlayerNum())
    DLLMultiSendSpecialPacket(destination, data, size);
}

// DMFCBase::GetGameStateRequest
//
//		Receiver for the server from a client asking for the state of the game
void DMFCBase::GetGameStateRequest(uint8_t *data) {
  int pnum;
  int count = 0;
  pnum = MultiGetInt(data, &count);
  CallOnGameStateRequest(pnum);
}

// DMFCBase::SendTeamAssignment
//
//     A DMFC Special Packet function, this sends a team assignment packet to all the players. Server only.
//     playernum = player to change teams
//     team = new team
void DMFCBase::SendTeamAssignment(int playernum, int team, bool spew_onrespawn) {
  if (GetLocalRole() != LR_SERVER)
    return;

  uint8_t data[MAX_GAME_DATA_SIZE];
  int count = 0;

  StartPacket(data, SPID_TEAMASSIGNMENT, &count);

  MultiAddByte((spew_onrespawn) ? 1 : 0, data, &count);
  MultiAddInt(playernum, data, &count);
  MultiAddInt(team, data, &count);

  SendPacket(data, count, SP_ALL);
}

// DMFCBase::GetTeamAssignmentPacket
//
//     Reciever for the team assignment packet.
void DMFCBase::GetTeamAssignmentPacket(uint8_t *data) {
  int count = 0;
  bool spew = MultiGetByte(data, &count) ? true : false;

  int pnum = MultiGetInt(data, &count);
  int team = MultiGetInt(data, &count);

  if (CheckPlayerNum(pnum)) {
    Players[pnum].team = team;
    CallOnPlayerChangeTeam(pnum, team, true, spew);
  }
}

// DMFCBase::GetChangeTeamPacket
//
//     Reciever for the change team packet.(Server Only)
void DMFCBase::GetChangeTeamPacket(uint8_t *data) {
  if (GetLocalRole() != LR_SERVER)
    return;
  // We recieved a request for a player to change teams

  int count = 0;

  bool spew_on_respawn = MultiGetByte(data, &count) ? true : false;
  int pnum = MultiGetByte(data, &count);
  int newteam = MultiGetByte(data, &count);
  if (newteam == 255)
    newteam = -1;

  if (!AllowTeamChange()) {
    return;
  }

  if (!CallOnCanChangeTeam(pnum, newteam)) {
    return;
  }

  if (!CheckPlayerNum(pnum))
    return;

  if ((newteam < 0) || (newteam > m_iNumTeams))
    return;

  Players[pnum].team = newteam;
  CallOnPlayerChangeTeam(pnum, newteam, true, spew_on_respawn);
  SendTeamAssignment(pnum, newteam, spew_on_respawn);
}

// DMFCBase::SendChangeTeamRequest
//
//		Sends a request to the server to change teams (Client Only)
void DMFCBase::SendChangeTeamRequest(int newteam, bool spew_onrespawn) {
  uint8_t data[MAX_GAME_DATA_SIZE];
  int count = 0;

  StartPacket(data, SPID_CHANGETEAM, &count);

  MultiAddByte((spew_onrespawn) ? 1 : 0, data, &count);
  MultiAddByte(GetPlayerNum(), data, &count);
  MultiAddByte((newteam == -1) ? 255 : newteam, data, &count);

  SendPacket(data, count, SP_SERVER);
}

// DMFCBase::GetDMFCGameInfo
//
//		Receives information about the DMFC game
void DMFCBase::GetDMFCGameInfo(uint8_t *data) {
  int count = 0;

  uint8_t level = MultiGetByte(data, &count); // server's HUD Callsign max level
  m_bMakeClientsWait = MultiGetByte(data, &count) ? true : false;
  SwitchServerHudCallsignLevel(level);

  int num_teams = MultiGetByte(data, &count);

  char buffer[256];

  if (num_teams > 1) {
    MultiGetString(buffer, data, &count);
    SetTeamName(RED_TEAM, buffer, false);

    MultiGetString(buffer, data, &count);
    SetTeamName(BLUE_TEAM, buffer, false);

    MultiGetString(buffer, data, &count);
    SetTeamName(GREEN_TEAM, buffer, false);

    MultiGetString(buffer, data, &count);
    SetTeamName(YELLOW_TEAM, buffer, false);
  }

  WaitForServer(m_bMakeClientsWait);
}

// DMFCBase::SendDMFCGameInfo
//
//		Sends information about the DMFC game
void DMFCBase::SendDMFCGameInfo(int player) {
  int r_len, b_len, g_len, y_len;
  int count = 0;
  uint8_t data[MAX_GAME_DATA_SIZE];

  StartPacket(data, SPID_DMFCGAMEINFO, &count);

  if (GetNumTeams() > 1) {
    // team game
    r_len = strlen(GetTeamString(RED_TEAM)) + 1;
    b_len = strlen(GetTeamString(BLUE_TEAM) + 1);
    g_len = strlen(GetTeamString(GREEN_TEAM) + 1);
    y_len = strlen(GetTeamString(YELLOW_TEAM) + 1);

    MultiAddByte(m_iServerHUDCallsignLevel, data, &count);
    MultiAddByte(m_bMakeClientsWait, data, &count);
    MultiAddByte(GetNumTeams(), data, &count);

    MultiAddString(GetTeamString(RED_TEAM), data, &count);
    MultiAddString(GetTeamString(BLUE_TEAM), data, &count);
    MultiAddString(GetTeamString(GREEN_TEAM), data, &count);
    MultiAddString(GetTeamString(YELLOW_TEAM), data, &count);

  } else {
    // non-team game
    MultiAddByte(m_iServerHUDCallsignLevel, data, &count);
    MultiAddByte(m_bMakeClientsWait, data, &count);
    MultiAddByte(GetNumTeams(), data, &count);
  }

  SendPacket(data, count, player);

  SendRealGametime(player);
}

// DMFCBase::SendRealGametime
//
//	Updates a player about the 'real' game time
void DMFCBase::SendRealGametime(int pnum) {
  int count = 0;
  uint8_t data[MAX_GAME_DATA_SIZE];

  StartPacket(data, SPID_REALGAMETIME, &count);

  MultiAddFloat(RealGametime, data, &count);

  if (m_iProtectedFlags & DMFC_PRF_PAUSETIME) {
    // time is paused
    MultiAddByte(1, data, &count);
  } else {
    // time is not paused
    MultiAddByte(0, data, &count);
  }

  SendPacket(data, count, pnum);
}

// DMFCBase::GetRealGametime
//
//	handles a packet about updating the real game time
void DMFCBase::GetRealGametimePacket(uint8_t *data) {
  int count = 0;

  RealGametime = MultiGetFloat(data, &count);

  if (MultiGetByte(data, &count)) {
    // time is paused
    ENABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_PAUSETIME);
  } else {
    // time is not paused
    DISABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_PAUSETIME);
  }
}

// DMFCBase::SendRemoteKey
//
// Sends out the players remote admin key
void DMFCBase::SendRemoteKey(int pnum) {
  // Send this user's remote admin key
  player_record *pr = PRec_GetPRecordByPnum(pnum);
  int precnum = -1;
  if (pr) {
    precnum = translate_precptr_to_index(pr);
  }
  if (precnum != -1) {
    int count = 0;
    uint8_t data[MAX_GAME_DATA_SIZE];

    StartPacket(data, SPID_REMOTEKEY, &count);

    uint8_t *key = Remote_GetKey(precnum);
    memcpy(&data[count], key, 8);
    count += 8;

    SendPacket(data, count, pnum);
  } else {
    Int3();
  }
}

// DMFCBase::GetRemoteKey
//
// Handles a new remote key from the server
void DMFCBase::GetRemoteKey(uint8_t *data) {
  int count = 0;
  uint8_t key[8];
  memcpy(key, &data[count], 8);
  count += 8;
  Remote_SetMyKey(key);
}

// DMFCBase::RequestGameState
//
//		Sends a request to the server for the game state
void DMFCBase::RequestGameState(void) {
  if (GetLocalRole() == LR_SERVER)
    return;
  int count = 0;
  uint8_t data[MAX_GAME_DATA_SIZE];
  StartPacket(data, SPID_REQGAMESTATE, &count);

  MultiAddInt(GetPlayerNum(), data, &count);

  SendPacket(data, count, SP_SERVER);
}

void DMFCBase::SendVersionToClient(int pnum) {
  if (GetLocalRole() != LR_SERVER)
    return;
  int count = 0;
  uint8_t data[MAX_GAME_DATA_SIZE];

  StartPacket(data, SPID_VERSIONID, &count);

  MultiAddInt(DMFC_VERSION_MAJOR, data, &count);
  MultiAddInt(DMFC_VERSION_MINOR, data, &count);

  SendPacket(data, count, pnum);
}

void DMFCBase::GetDMFCVersionCheck(uint8_t *data) {
  int major, minor, count = 0;

  major = MultiGetInt(data, &count);
  minor = MultiGetInt(data, &count);

  if ((major != DMFC_VERSION_MAJOR) || (minor != DMFC_VERSION_MINOR)) {
    char buffer[200];
    snprintf(buffer, sizeof(buffer), "Excpecting Version %d.%d, found %d.%d", major, minor, DMFC_VERSION_MAJOR,
             DMFC_VERSION_MINOR);
    FatalError(buffer);
  }
}

// DMFCBase::SendRequestForPlayerRecords
//
//
//	Sends a request to the server to send the player records
void DMFCBase::SendRequestForPlayerRecords(void) {
  if (GetLocalRole() == LR_SERVER)
    return;
  int count = 0;
  uint8_t data[MAX_GAME_DATA_SIZE];
  StartPacket(data, SPID_PRECORDREQ, &count);

  MultiAddByte(GetPlayerNum(), data, &count);

  SendPacket(data, count, SP_SERVER);
}

// DMFCBase::ReceiveRequestForPlayerRecords
//
//
//	Recieves and processes a request for a player record
void DMFCBase::ReceiveRequestForPlayerRecords(uint8_t *data) {
  if (GetLocalRole() != LR_SERVER)
    return;
  int count = 0;
  int pnum = MultiGetByte(data, &count);
  PRec_SendPRecToPlayer(pnum);

  // send the player his remote admin key
  SendRemoteKey(pnum);
}

// DMFCBase::SendControlMessageToPlayer
//
//
//	Sends a [1 byte] control message to a player
void DMFCBase::SendControlMessageToPlayer(int pnum, uint8_t msg) {
  int count = 0;
  uint8_t data[MAX_GAME_DATA_SIZE];
  StartPacket(data, SPID_CONTROLMSG, &count);

  MultiAddByte(msg, data, &count);
  MultiAddByte(GetPlayerNum(), data, &count);
  MultiAddByte(pnum, data, &count);

  if (GetLocalRole() != LR_SERVER) {
    // send it to the server to forward to a client
    SendPacket(data, count, SP_SERVER);
  } else
    SendPacket(data, count, pnum);
}

// DMFCBase::ReceiveControlMessage
//
//
//	Handles a control message
void DMFCBase::ReceiveControlMessage(uint8_t *data) {
  int count = 0;
  uint8_t msg = MultiGetByte(data, &count);
  uint8_t src = MultiGetByte(data, &count);
  uint8_t dst = MultiGetByte(data, &count);

  if ((GetLocalRole() == LR_SERVER) && (dst != SP_SERVER || dst != GetPlayerNum())) {
    // forward this packet to the correct player
    uint8_t fdata[MAX_GAME_DATA_SIZE];
    count = 0;

    StartPacket(fdata, SPID_CONTROLMSG, &count);
    MultiAddByte(msg, fdata, &count);
    MultiAddByte(src, fdata, &count);
    MultiAddByte(dst, fdata, &count);
    SendPacket(fdata, count, dst);
  } else {
    // handle the packet
    CallOnControlMessage(msg, src);
  }
}

// DMFCBase::SendNetGameInfoSync
//
//	Sends out a NetGame info sync packet to all the players
void DMFCBase::SendNetGameInfoSync(int to_who) {
  if (GetLocalRole() != LR_SERVER)
    return;

  uint8_t data[MAX_GAME_DATA_SIZE];
  int count = 0;
  StartPacket(data, SPID_NETGAMESYNC, &count);

  MultiAddByte(Netgame->packets_per_second, data, &count);
  MultiAddByte((Netgame->flags & NF_KILLGOAL) ? 1 : 0, data, &count);
  MultiAddByte((Netgame->flags & NF_TIMER) ? 1 : 0, data, &count);

  MultiAddUshort(Netgame->timelimit, data, &count);
  MultiAddUshort(Netgame->killgoal, data, &count);
  MultiAddUshort(Netgame->respawn_time, data, &count);
  MultiAddUshort(Netgame->max_players, data, &count);

  SendPacket(data, count, to_who);
}

// DMFCBase::ReceiveNetGameInfoSync
//
//  Receives a NetGame info sync packet from the server
void DMFCBase::ReceiveNetGameInfoSync(uint8_t *data) {
  int count = 0;

  Netgame->packets_per_second = MultiGetByte(data, &count);
  if (MultiGetByte(data, &count))
    Netgame->flags |= NF_KILLGOAL;
  else
    Netgame->flags &= ~NF_KILLGOAL;
  if (MultiGetByte(data, &count))
    Netgame->flags |= NF_TIMER;
  else
    Netgame->flags &= ~NF_TIMER;

  Netgame->timelimit = MultiGetUshort(data, &count);
  Netgame->killgoal = MultiGetUshort(data, &count);
  Netgame->respawn_time = MultiGetUshort(data, &count);
  Netgame->max_players = MultiGetUshort(data, &count);
}

// DMFCBase::SendNewTeamName
//
//	Tells the clients about a team's new name
void DMFCBase::SendNewTeamName(int team) {
  if (GetLocalRole() != LR_SERVER)
    return;

  int count = 0;
  uint8_t data[MAX_GAME_DATA_SIZE];
  StartPacket(data, SPID_NEWTEAMNAME, &count);

  MultiAddByte(team, data, &count);
  MultiAddString(DMFC_team_names[team], data, &count);

  SendPacket(data, count, SP_ALL);
}

// DMFCBase::ReceiveNewTeamName
//
//	The server is telling us about a new team name
void DMFCBase::ReceiveNewTeamName(uint8_t *data) {
  int count = 0;
  int team = MultiGetByte(data, &count);

  ASSERT(team >= 0 && team < DLLMAX_TEAMS);
  if (team < 0 || team >= DLLMAX_TEAMS)
    return; // invalid team

  char old_teamname[MAX_TEAMNAME_LEN];
  strcpy(old_teamname, DMFC_team_names[team]);

  MultiGetString(DMFC_team_names[team], data, &count);

  CallOnTeamChangeName(team, old_teamname, DMFC_team_names[team]);
}
