/* * 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 . */ /* * $Logfile: /DescentIII/Main/Dmfc/dmfcprecord.cpp $ * $Revision: 1.1.1.1 $ * $Date: 2003/08/26 03:57:21 $ * $Author: kevinb $ * * DMFC Player record functions * * $Log: dmfcprecord.cpp,v $ * Revision 1.1.1.1 2003/08/26 03:57:21 kevinb * initial 1.5 import * * * 18 10/21/99 9:28p Jeff * B.A. Macintosh code merge * * 17 7/09/99 2:54p Jeff * handle gametime better (pause it when needed) if the server is 'waiting * for players' * * 16 5/21/99 10:44p Jeff * fix PXO crash due to bytes being out of line when joining a game * * 15 5/13/99 4:55p Ardussi * changes for compiling on the Mac * * 14 5/12/99 11:05p Jeff * dmfc and multiplayer games now have endian friendly packets (*whew*) * * 13 3/17/99 12:25p Jeff * converted DMFC to be COM interface * * 12 2/11/99 12:51a Jeff * changed names of exported variables * * 11 12/08/98 4:47p Jeff * umm, various changes, fixed pilot pics so that they work for everyone * now * * 10 11/11/98 7:19p Jeff * changes made so that a dedicated server's team is always -1 (team game * or not) * * 9 10/18/98 7:59p Jeff * functions added to dmfc for client->server objnum matching. Banning * now uses tracker id when available. * * 8 10/17/98 7:30p Jeff * network_address compares don't compare port on somethings * * 7 9/30/98 3:50p Jeff * general improvements (many) * * 6 9/29/98 3:04p Jeff * added time in game and start_time support * * 5 9/22/98 5:20p Jason * fixed some player bugs * * 4 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" #include #include player_record Player_records[MAX_PLAYER_RECORDS]; int Pnum_to_PRec[DLLMAX_PLAYERS]; // callback pointers for packet packing and unpacking int (*Player_record_pack_cb)(void *user_info, ubyte *data); int (*Player_record_unpack_cb)(void *user_info, ubyte *data); // Initializes the Player records void PRec_Init(void) { int i; for (i = 0; i < MAX_PLAYER_RECORDS; i++) { memset(&Player_records[i], 0, sizeof(player_record)); Player_records[i].state = STATE_EMPTY; Player_records[i].user_info = NULL; Player_records[i].pinfo = NULL; Player_records[i].tracker_id = NULL; Player_records[i].user_info_size = 0; Player_records[i].total_time_in_game = 0; } for (i = 0; i < DLLMAX_PLAYERS; i++) Pnum_to_PRec[i] = -1; Player_record_pack_cb = NULL; Player_record_unpack_cb = NULL; } // Frees up and closes the Player records void PRec_Close(void) { for (int i = 0; i < MAX_PLAYER_RECORDS; i++) { if (Player_records[i].user_info) free(Player_records[i].user_info); if (Player_records[i].tracker_id) free(Player_records[i].tracker_id); if (Player_records[i].pinfo) { delete Player_records[i].pinfo; Player_records[i].pinfo = NULL; } } Player_record_pack_cb = NULL; Player_record_unpack_cb = NULL; } // Sets up the Player records so they can be used for Multiplayer user defined stats // sizeof_user_stats : size of the struct for each player's stats // pack_callback : callback function called when data from the struct needs to be packed // into a packet. It is SUPER important that this function packs the data // in little endian format. This function gets a pointer to the struct that // needs to be packed (user_info), and a buffer in which to pack it to. This // function is to return the number of bytes it has packed. // unpack_callback : callback function called when data from the struct needs to be unpacket // from a packet. This data will be in little endian format. Returns the number of // bytes unpacked. // returns: 1 if size given was <=0 (if so all previous user stats will be removed) // 0 all went ok // -1 out of memory (all user stats memory will be freed) // -2 invalid callbacks int PRec_SetupUserPRec(int sizeof_user_stats, int (*pack_callback)(void *user_info, ubyte *data), int (*unpack_callback)(void *user_info, ubyte *data)) { ASSERT(pack_callback != NULL); ASSERT(unpack_callback != NULL); if (!pack_callback || !unpack_callback) return -2; int i; // calculate to make sure they don't specify too much int size = MAX_GAME_DATA_SIZE - (104 + 5 * MAX_PLAYER_RECORDS); if (sizeof_user_stats > size) { // the user mod specified too big of a size mprintf((0, "DMFC Warning: In PRec_SetupUserPRec(), the size given is too large for a packet\n")); Int3(); goto user_stats_err; } // check the first item to see whats set for it if (Player_records[0].user_info) { // we need to free up all the user_info's for (i = 0; i < MAX_PLAYER_RECORDS; i++) { if (Player_records[i].user_info) { free(Player_records[i].user_info); Player_records[i].user_info = NULL; } Player_records[i].user_info_size = 0; } } if (sizeof_user_stats <= 0) return 1; for (i = 0; i < MAX_PLAYER_RECORDS; i++) { Player_records[i].user_info = malloc(sizeof_user_stats); if (!Player_records[i].user_info) goto user_stats_err; Player_records[i].user_info_size = sizeof_user_stats; memset(Player_records[i].user_info, 0, sizeof_user_stats); } Player_record_pack_cb = pack_callback; Player_record_unpack_cb = unpack_callback; return 0; user_stats_err: for (i = 0; i < MAX_PLAYER_RECORDS; i++) { if (Player_records[i].user_info) { free(Player_records[i].user_info); Player_records[i].user_info = NULL; } Player_records[i].user_info_size = 0; } return -1; } // Given a pnum it will return the PRec slot of that player, or -1 if it's not a valid player int PRec_GetPlayerSlot(int pnum) { if ((pnum < 0) || (pnum >= DLLMAX_PLAYERS)) return -1; return Pnum_to_PRec[pnum]; } // Given a PRec slot, it returns a pointer to the user stats struct, NULL if error, or PRec_SetupUserPRec // hasn't been called yet void *PRec_GetUserStats(int slot) { if ((slot < 0) || (slot >= MAX_PLAYER_RECORDS)) return NULL; return Player_records[slot].user_info; } // Returns a pointer to a player_record for the requested slot, NULL if error player_record *PRec_GetPRecord(int slot) { if ((slot < 0) || (slot >= MAX_PLAYER_RECORDS)) return NULL; return &Player_records[slot]; } // Returns a pointer to a player_record of a player, NULL if error player_record *PRec_GetPRecordByPnum(int pnum) { int slot = PRec_GetPlayerSlot(pnum); return PRec_GetPRecord(slot); } // Sets a disconnect time for a player void PRec_SetDisconnectTime(int slot, float time) { if ((slot < 0) || (slot >= MAX_PLAYER_RECORDS)) return; Player_records[slot].disconnect_time = time; } // Returns the PRec slot of a player who matches the given info, -1 if none // addr = network address (can be NULL) // tracker_id = Mastertracker ID (can be NULL) int PRec_FindPlayer(const char *callsign, network_address *addr, const char *tracker_id) { if (!callsign) return -1; bool good_to_use; for (int i = 0; i < MAX_PLAYER_RECORDS; i++) { // check if the slot isn't empty if (Player_records[i].state == STATE_DISCONNECTED) { if (!strcmp(Player_records[i].callsign, callsign)) { // we have a possible match, the callsign matches good_to_use = true; if (addr) { // we need to check the network address if (!basethis->CompareNetworkAddress(&Player_records[i].net_addr, addr, false)) good_to_use = false; // the network address doesn't match } if (tracker_id && good_to_use) { // we need to check the master tracker_id if (Player_records[i].tracker_id) { if (strcmp(Player_records[i].tracker_id, tracker_id)) { good_to_use = false; // the tracker_id doesn't match } } else { good_to_use = false; } } if (good_to_use) { // we found the player based on the information passed in return i; } } } } return -1; // no matches } // Gets the first available free slot (STATE_EMPTY), if none are available it clears and returns // the oldest disconnected player (STATE_DISCONNECTED) int PRec_GetFreeSlot(void) { int i, old_player = -1; float old_time = 9999999.0f; for (i = 0; i < MAX_PLAYER_RECORDS; i++) { if (Player_records[i].state == STATE_EMPTY) return i; // we found a free slot if (Player_records[i].state == STATE_DISCONNECTED) { if (old_player == -1) { // we found our first, so make sure we mark it old_player = i; old_time = Player_records[i].disconnect_time; } // check to see if this player has been disconnected longer than any others if (Player_records[i].disconnect_time < old_time) { // he has old_time = Player_records[i].disconnect_time; old_player = i; } } } ASSERT(old_player != -1); if (old_player != -1) { // go through all the records and remove this guy as a victim for (int p = 0; p < MAX_PLAYER_RECORDS; p++) { if ((p != old_player) && (Player_records[p].pinfo)) { Player_records[p].pinfo->RemoveKiller(old_player); } } // clear out this player if (Player_records[old_player].tracker_id) free(Player_records[old_player].tracker_id); void *save_ptr = Player_records[old_player].user_info; int size = Player_records[old_player].user_info_size; if (Player_records[old_player].pinfo) { delete Player_records[old_player].pinfo; Player_records[old_player].pinfo = NULL; } memset(&Player_records[old_player], 0, sizeof(player_record)); Player_records[old_player].state = STATE_EMPTY; Player_records[old_player].user_info = save_ptr; Player_records[old_player].user_info_size = size; Player_records[old_player].total_time_in_game = 0; } return old_player; } // Assigns a player to a specified slot (fills in the info, and prepares the slot for action) // Do Not use this for a reconnecting player bool PRec_AssignPlayerToSlot(int pnum, int slot, player *players_array, netplayer *netplayers_array) { if ((slot < 0) || (slot >= MAX_PLAYER_RECORDS)) return false; if ((pnum < 0) || (pnum >= DLLMAX_PLAYERS)) return false; if (!players_array) return false; if (!netplayers_array) return false; if (Player_records[slot].state != STATE_EMPTY) return false; Pnum_to_PRec[pnum] = slot; Player_records[slot].state = STATE_INGAME; strcpy(Player_records[slot].callsign, players_array[pnum].callsign); memcpy(&Player_records[slot].net_addr, &netplayers_array[pnum].addr, sizeof(network_address)); Player_records[slot].pnum = pnum; Player_records[slot].start_time = basethis->GetRealGametime(); Player_records[slot].total_time_in_game = 0; Player_records[slot].team = players_array[pnum].team; if (basethis->IsMasterTrackerGame() && (basethis->Players[pnum].tracker_id)) { // we are in a master tracker game, so save the tracker ID mprintf((0, "PREC: Got a PXO Player ID of %s\n", basethis->Players[pnum].tracker_id)); Player_records[slot].tracker_id = strdup(basethis->Players[pnum].tracker_id); } else { // either it's not a tracker game or the tracker_id member of players is void Player_records[slot].tracker_id = NULL; } memset(&Player_records[slot].dstats, 0, sizeof(t_dstat)); if (Player_records[slot].pinfo) { delete Player_records[slot].pinfo; Player_records[slot].pinfo = NULL; } Player_records[slot].pinfo = new PInfo(slot); return true; } // Reconnects a player to a STATE_DISCONNECTED slot bool PRec_ReconnectPlayerToSlot(int pnum, int slot, player *players_array, netplayer *netplayers_array) { if ((slot < 0) || (slot >= MAX_PLAYER_RECORDS)) return false; if ((pnum < 0) || (pnum >= DLLMAX_PLAYERS)) return false; if (!players_array) return false; if (!netplayers_array) return false; if (Player_records[slot].state != STATE_DISCONNECTED) return false; Pnum_to_PRec[pnum] = slot; Player_records[slot].state = STATE_INGAME; strcpy(Player_records[slot].callsign, players_array[pnum].callsign); memcpy(&Player_records[slot].net_addr, &netplayers_array[pnum].addr, sizeof(network_address)); Player_records[slot].pnum = pnum; Player_records[slot].start_time = basethis->GetRealGametime(); if (!Player_records[slot].pinfo) { // try to create the pinfo now Player_records[slot].pinfo = new PInfo(slot); } return true; } // Disconnects a player from the Player records bool PRec_DisconnectPlayer(int pnum) { if ((pnum < 0) || (pnum >= DLLMAX_PLAYERS)) return false; int slot = Pnum_to_PRec[pnum]; if (slot == -1) return false; // player was never connected Pnum_to_PRec[pnum] = -1; Player_records[slot].state = STATE_DISCONNECTED; Player_records[slot].pnum = -1; float curr_time = basethis->GetRealGametime(); float time_in = curr_time - Player_records[slot].start_time; if (time_in > 0) { Player_records[slot].total_time_in_game += time_in; } PRec_SetDisconnectTime(slot, curr_time); return true; } // Clear all the level volatile (things that reset with the change of levels) things void PRec_LevelReset(void) { t_dstat *stat; for (int i = 0; i < MAX_PLAYER_RECORDS; i++) { stat = &Player_records[i].dstats; stat->kills[DSTAT_LEVEL] = 0; stat->deaths[DSTAT_LEVEL] = 0; stat->suicides[DSTAT_LEVEL] = 0; if (Player_records[i].pinfo) Player_records[i].pinfo->ResetAll(); } } // Gets the team of a player given the pnum int PRec_GetPlayerTeam(int pnum) { int slot = PRec_GetPlayerSlot(pnum); if (slot == -1) return 0; return Player_records[slot].team; } // Sets the team of a player given the pnum void PRec_SetPlayerTeam(int pnum, int team) { int slot = PRec_GetPlayerSlot(pnum); if (slot == -1) return; Player_records[slot].team = team; } void MultiPackNetworkAddress(network_address *addr, ubyte *data, int *count) { memcpy(&data[*count], addr->address, 6); (*count) += 6; MultiAddUshort(addr->port, data, count); memcpy(&data[*count], addr->net_id, 4); (*count) += 4; MultiAddByte(addr->connection_type, data, count); } void MultiUnpackNetworkAddress(network_address *addr, ubyte *data, int *count) { memcpy(addr->address, &data[*count], 6); (*count) += 6; addr->port = MultiGetUshort(data, count); memcpy(addr->net_id, &data[*count], 4); (*count) += 4; addr->connection_type = (network_protocol)MultiGetByte(data, count); } void MultiPackDStats(t_dstat *stat, ubyte *data, int *count) { MultiAddInt(stat->kills[0], data, count); MultiAddInt(stat->kills[1], data, count); MultiAddInt(stat->deaths[0], data, count); MultiAddInt(stat->deaths[1], data, count); MultiAddInt(stat->suicides[0], data, count); MultiAddInt(stat->suicides[1], data, count); } void MultiUnpackDStats(t_dstat *stat, ubyte *data, int *count) { stat->kills[0] = MultiGetInt(data, count); stat->kills[1] = MultiGetInt(data, count); stat->deaths[0] = MultiGetInt(data, count); stat->deaths[1] = MultiGetInt(data, count); stat->suicides[0] = MultiGetInt(data, count); stat->suicides[1] = MultiGetInt(data, count); } // Sends the Player records info to the given client void PRec_SendPRecToPlayer(int pnum) { if (basethis->GetLocalRole() != LR_SERVER) return; ubyte data[MAX_GAME_DATA_SIZE]; int callsignlen, totalsize, totalcount; totalsize = totalcount = 0; int pinfo_size, count; for (int i = 0; i < MAX_PLAYER_RECORDS; i++) { if (Player_records[i].state != STATE_EMPTY) { callsignlen = strlen(Player_records[i].callsign); if (Player_records[i].pinfo) pinfo_size = Player_records[i].pinfo->GetSizeOfData(); else pinfo_size = 0; ubyte tracker_len = 0; if (Player_records[i].tracker_id) { tracker_len = strlen(Player_records[i].tracker_id); } count = 0; basethis->StartPacket(data, SPID_PRECORD, &count); MultiAddByte(i, data, &count); // pack byte (record num) MultiAddByte(Player_records[i].state, data, &count); // pack byte (state) MultiAddByte(callsignlen, data, &count); // pack byte (strlen(callsign)) memcpy(&data[count], Player_records[i].callsign, callsignlen); count += callsignlen; // pack bytes (callsign) MultiPackNetworkAddress(&Player_records[i].net_addr, data, &count); // pack bytes (network_address) MultiAddByte(Player_records[i].pnum, data, &count); // pack byte (pnum) MultiAddByte((Player_records[i].team == -1) ? 255 : Player_records[i].team, data, &count); // pack byte (team) if (basethis->IsMasterTrackerGame()) { MultiAddByte(1, data, &count); MultiAddByte(tracker_len, data, &count); // pack byte (tracker len) if (tracker_len) { memcpy(&data[count], Player_records[i].tracker_id, tracker_len); count += tracker_len; // pack bytes (tracker_id) } } else { MultiAddByte(0, data, &count); } MultiAddFloat(Player_records[i].disconnect_time, data, &count); // pack float (disconnect_time/start_time) MultiAddFloat(Player_records[i].total_time_in_game, data, &count); // pack float (total time in game) MultiPackDStats(&Player_records[i].dstats, data, &count); // pack bytes (dstats) MultiAddByte((Player_records[i].user_info_size > 0) ? 1 : 0, data, &count); // pack byte (there is user info) if (Player_records[i].user_info_size > 0) // if(user_info_size>0) { if (Player_record_pack_cb) { count += (*Player_record_pack_cb)(Player_records[i].user_info, &data[count]); } else { Int3(); count += Player_records[i].user_info_size; // attempt to recover } } // PInfo stuff MultiAddInt(pinfo_size, data, &count); if (pinfo_size > 0) { Player_records[i].pinfo->PackData(&data[count]); count += pinfo_size; } totalsize += count; totalcount++; basethis->SendPacket(data, count, pnum); } } mprintf((0, "*Player Record: Send out %d packets of total %d bytes to %s\n", totalcount, totalsize, basethis->Players[pnum].callsign)); } // Receives the Player records info from the server void PRec_ReceivePRecFromServer(ubyte *data) { int count = 0; int callsignlen; int slot = MultiGetByte(data, &count); // unpack byte (record num) int pinfo_size; player_record *pr; ASSERT((slot >= 0) && (slot < MAX_PLAYER_RECORDS)); pr = &Player_records[slot]; pr->state = (slot_state)MultiGetByte(data, &count); // unpack byte (state) callsignlen = MultiGetByte(data, &count); // unpack byte (callsign length) memcpy(pr->callsign, &data[count], callsignlen); count += callsignlen; // unpack bytes (callsign) pr->callsign[callsignlen] = '\0'; MultiUnpackNetworkAddress(&pr->net_addr, data, &count); // unpack bytes (network address) pr->pnum = MultiGetByte(data, &count); // unpack byte (pnum) pr->team = MultiGetByte(data, &count); if (pr->team == 255) // unpack byte (team) pr->team = -1; if (MultiGetByte(data, &count)) { ASSERT(basethis->IsMasterTrackerGame() != 0); // there is tracker_id data coming in ubyte len = MultiGetByte(data, &count); pr->tracker_id = (char *)malloc(len + 1); if (pr->tracker_id) { memcpy(pr->tracker_id, &data[count], len); pr->tracker_id[len] = '\0'; } else { mprintf((0, "PREC: UH OH!!! OUT OF MEMORY\n")); } count += len; } else { ASSERT(basethis->IsMasterTrackerGame() == 0); } pr->disconnect_time = MultiGetFloat(data, &count); // unpack float (disconnect time/start time) pr->total_time_in_game = MultiGetFloat(data, &count); // unpack float (total time in game) MultiUnpackDStats(&pr->dstats, data, &count); // unpack bytes (dstats) if (MultiGetByte(data, &count)) { ASSERT(pr->user_info != NULL); if (Player_record_unpack_cb) { count += (*Player_record_unpack_cb)(pr->user_info, &data[count]); } else { Int3(); count += pr->user_info_size; // attempt to recover } } else { ASSERT(pr->user_info == NULL); } if (pr->pinfo) { delete pr->pinfo; } pr->pinfo = new PInfo(slot); // PInfo stuff pinfo_size = MultiGetInt(data, &count); if (pinfo_size > 0) { if (pr->pinfo) pr->pinfo->UnpackData(&data[count], pinfo_size); count += pinfo_size; } else { if (pr->pinfo) pr->pinfo->ResetAll(); } if (pr->pnum != 255) { Pnum_to_PRec[pr->pnum] = slot; } }