/*
* 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/dmfcbase.cpp $
 * $Revision: 1.1.1.1 $
 * $Date: 2003/08/26 03:57:20 $
 * $Author: kevinb $
 *
 * Base functions for DMFC implementation
 *
 * $Log: dmfcbase.cpp,v $
 * Revision 1.1.1.1  2003/08/26 03:57:20  kevinb
 * initial 1.5 import
 *
 *
 * 170   9/28/01 2:09p Matt
 * Don't show ping/loss info on HUD if peer-to-peer game.
 *
 * 169   9/07/01 8:57a Matt
 * Fixed hud name bug from previous change.
 *
 * 168   9/05/01 6:04p Matt
 * Added code to save the user's preferred HUD name level setting even if
 * the server bashes it down.
 *
 * 167   10/26/99 10:31a Jeff
 * fixed COM interface bug
 *
 * 166   10/21/99 9:27p Jeff
 * B.A. Macintosh code merge
 *
 * 165   8/21/99 12:33a Jeff
 * Changed the name of the GetRealGametime function to
 * GetRealGametimePacket since it was confusing some compilers with the
 * other GetRealGametime function.
 *
 * 164   8/15/99 4:36p Jeff
 * finished exporting all inventory class functions.  export object_info
 * array. added check for -nooutragelogo to display Outrage logo display.
 *
 * 163   8/11/99 6:36p Jeff
 * don't set input command list root to NULL when creating input commands
 * (it would create a memleak and ignore any ICs added by a custom mod)
 *
 * 162   8/11/99 1:21p Jeff
 * exported needed functions for camera windows
 *
 * 161   7/30/99 11:59a Jeff
 * fixed bug with autobalance being turned off
 *
 * 160   7/14/99 11:47a Jeff
 * localized text for patch fixes
 *
 * 159   7/13/99 10:05a Jeff
 * text taunt token decoding
 *
 * 158   7/11/99 3:31p Jeff
 * exported game arguments, made command line option to specify
 * autoexec.dmfc
 *
 * 157   7/09/99 7:02p Jeff
 * put in countdown timer for when a level is about to end
 *
 * 156   7/09/99 6:17p Jeff
 * added $remoteadminlogout and $wait commands
 *
 * 155   7/09/99 2:53p Jeff
 * handle gametime better (pause it when needed) if the server is 'waiting
 * for players'
 *
 * 154   7/08/99 6:25p Jeff
 * remote admin in and working
 *
 * 153   7/08/99 2:39a Jeff
 * rough implementation of remote administration checked in.  Still needs
 * some polishing, but should work basically.
 *
 * 152   7/07/99 5:00p Jeff
 * removed vararg functions from interface functions, just made different
 * versions of them
 *
 * 151   6/11/99 5:36p Jeff
 * removed ai_info #ifdefs (better way of doing it)
 *
 * 150   6/10/99 12:34p Jeff
 * GetCounterMeasureOwner doesn't use ->ai_info for non-Outrage versions
 *
 * 149   6/10/99 11:10a Jeff
 * don't display the Outrage logo for non-Outrage games
 *
 * 148   5/22/99 1:12a Jeff
 * correctly handle Viewer_object
 *
 * 147   5/20/99 5:32p Jeff
 * called PlayerStopSounds if respawning a player
 *
 * 146   5/20/99 3:51p Jeff
 * for apply damage to player, server says should be 1 (so it works in
 * peer-peer)
 *
 * 145   5/13/99 4:55p Ardussi
 * changes for compiling on the Mac
 *
 * 144   5/12/99 11:04p Jeff
 * dmfc and multiplayer games now have endian friendly packets (*whew*)
 *
 * 143   5/10/99 2:43a Jeff
 * handle new scheme of player's joining in a team game, where the team is
 * set before player enters game in the main code, but the team is
 * determined via event call to dmfc
 *
 * 142   5/09/99 6:20a Jeff
 * improved Entropy (added sounds, max virii per room).  Fixed rendering
 * bugs for other multiplayer dlls.
 *
 * 141   5/08/99 11:06a Jeff
 *
 * 140   5/07/99 4:34p Jason
 * fixed audio taunt icon
 *
 * 139   5/07/99 12:52p Jeff
 * audio taunt icon is ppic if available.  coop has hard max team set of 4
 *
 * 138   5/04/99 8:46p Jeff
 * display icon when someone plays an audio taunt
 *
 * 137   5/03/99 8:39a Jeff
 * fixed apply damage to player
 *
 * 136   4/30/99 10:52p Jeff
 * added $warp command
 *
 * 135   4/30/99 7:36p Jeff
 * exported vis_effects to dmfc
 *
 * 134   4/27/99 1:56p Jeff
 * audio taunts stuff in pilot menu, added stringtables
 *
 * 133   4/26/99 3:41p Jeff
 * put in debug multiplayer command dump to file
 *
 * 132   4/25/99 7:18p Jeff
 * added code to handle suicides in pinfo
 *
 * 131   4/23/99 6:15p Jeff
 * fixed double calls to GameClose
 *
 * 130   4/23/99 4:49p Jason
 * played with loss colors a bit
 *
 * 129   4/23/99 4:24p Jason
 * fixed ping display bug
 *
 * 128   4/23/99 12:46p Jeff
 * lag loss indicator on by default
 *
 * 127   4/22/99 11:28a Jeff
 * changed percent sign as a result of Samir's change
 *
 * 126   4/21/99 10:35p Jeff
 * fixed lag/loss displays
 *
 * 125   4/20/99 8:57p Jeff
 * compile for Linux
 *
 * 124   4/14/99 3:03p Jeff
 *
 * 123   4/12/99 11:39a Jeff
 * removed movement smoothing, added permissable cs to netgame info
 *
 * 122   4/04/99 4:55p Jeff
 * added functionality to call osiris functions from multiplayer d3ms
 *
 * 121   4/03/99 4:06p Jeff
 * added loss/ping gauge
 *
 * 120   4/02/99 9:02p Jeff
 * fixed crashes if there was an error initializing module
 *
 * 119   3/30/99 9:01p Jeff
 * exported polymodels
 *
 * 118   3/30/99 7:42p Jeff
 * fixed a misspelling on a function name
 *
 * 117   3/22/99 6:20p Jeff
 * added 2 more audio taunts.  a mulitplayer event when someone plays an
 * audio taunt.  option to disable audio taunts.
 *
 * 116   3/22/99 1:55p Jeff
 * make sure initialization happens (possible crashing)
 *
 * 115   3/17/99 12:23p Jeff
 * converted DMFC to be COM interface
 *
 * 114   3/09/99 1:13p Jeff
 * fixed control packet for wait being overflowed out and accidently sent
 * by the client
 *
 * 113   3/05/99 1:29p Jeff
 * fixed 99% of the high res issues
 *
 * 112   3/01/99 8:48p Jeff
 * fixed banning bug (when not a master tracker game)
 *
 * 111   2/25/99 8:54p Jeff
 * Inventory supports level change persistant items.  Inventory supports
 * time-out objects.  Inventory Reset changed (takes a level of reset
 * now).  Quad lasers stay across level change (single player).  Guidebot
 * bug fixed (now back in ship on level start).  Quads time out when
 * spewed.  Invulnerability and cloak powerups no longer use game
 * event/callbacks, so they can be saved in game saves (moved to
 * MakePlayerInvulnerable and MakeObjectInvisible)
 *
 * 110   2/11/99 12:50a Jeff
 * changed names of exported variables
 *
 * 109   2/10/99 1:47p Matt
 * Changed object handle symbolic constants
 *
 * 108   2/09/99 3:32p Jeff
 * table file parser takes quotes strings for force keywords
 *
 * 107   2/07/99 2:06a Jeff
 * updated coop...fixed bug when getting countermeasure owner, if owner is
 * observer
 *
 * 106   2/07/99 1:19a Jeff
 * added new multiplayer game events EVT_GAMEOBJKILLED and
 * EVT_GAMEOBJDESTROYED
 *
 * 105   2/05/99 8:24p Jeff
 * added table file parser macros
 *
 * 104   2/03/99 4:09p Jeff
 * moved function pointers to seperate file. created autoexec.dmfc
 *
 * 103   2/02/99 8:43a Chris
 * I made buildings with AI work correctly (ie really big robots should be
 * buildings)
 * anim to and from states are now shorts instead of bytes
 *
 * 102   1/31/99 7:26p Matt
 * Renamed a bunch of functions to have HUD capitalized
 *
 * 101   1/21/99 11:16p Jeff
 * exported vecmat functions
 *
 * 100   1/20/99 8:06p Jeff
 * added members into DLLinfo struct for game change segment events, pass
 * them over on execute dll packets
 *
 * 99    1/19/99 5:34p Jeff
 * updated monsterball
 *
 * 98    1/18/99 7:27p Jeff
 * localized strings in dmfcbase.cpp
 *
 * 97    1/17/99 11:52p Jeff
 * added some new events, and changed a couple event handlers
 *
 * 96    1/15/99 8:29p Jeff
 * updates to powerball
 *
 * 95    1/15/99 7:52p Chris
 * Updated ObjSetPos() to include a f_update_attach_children flag
 *
 * 94    1/15/99 3:53a Jeff
 * exported a couple more functions.  Added event handlers for Weapon
 * collide event
 *
 * 93    1/12/99 2:55p Jeff
 * added/finished the waiting for player's to join dialog
 *
 * 92    1/12/99 11:29a Jason
 * fixed a broken externed function
 *
 * 91    1/07/99 5:01p Jeff
 * added Int3's and updated all net games to use stats manager...correctly
 * too
 *
 * 90    1/06/99 7:02p Jeff
 * added a multiplayer event for game controls
 *
 * 89    1/06/99 12:53a Jeff
 * put in support for $piggyback and $observer
 *
 * 88    1/04/99 8:11p Jason
 * fixed packet loss tracking problem
 *
 * 87    1/04/99 2:19p Jeff
 * exported table file management functions
 *
 * 86    1/04/99 12:21p Jeff
 * added support for hosts.allow/deny and updates stats manager a little
 *
 * 85    12/13/98 5:32p Jeff
 * fixed ugly crash due to freeing memory allocated in another heap
 *
 * 84    12/09/98 12:38p Jeff
 * removed possible security bug displaying a player's ip address (now
 * server only can see it)
 *
 * 83    12/08/98 4:47p Jeff
 * umm, various changes, fixed pilot pics so that they work for everyone
 * now
 *
 * 82    12/08/98 3:29p Jeff
 * updated the team control dialog so the server can determine if they
 * want to make the clients wait
 *
 * 81    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
 *
 * 80    12/04/98 7:04p Jeff
 * almost finished up dmfc stat manager
 *
 * 79    12/01/98 6:56p Jeff
 * put in quick and dirty implementation of pilot pics for testing
 *
 * 78    11/19/98 5:56p Jeff
 * added slider exported and improved Hoard
 *
 * 77    11/17/98 6:29p Jeff
 * mod can specify whether or not to display the team setup dialog on team
 * game start.  Added a menu item to display team setup dialog in mid-game
 *
 * 76    11/17/98 12:36p Jeff
 * fixed dedicated server detection and display a dprintf for setting new
 * team
 *
 * 75    11/16/98 5:35p Jeff
 * removed log functions, added support for changing team names, fixed ctf
 * to work better with different team names
 *
 * 74    11/13/98 6:36p Jeff
 * created dmfc_dll (a DLL version of DMFC) and converted current mods to
 * use it
 *
 * 73    11/12/98 12:16p Jeff
 * more changes to handle (ignore) dedicated server
 *
 * 72    11/11/98 7:19p Jeff
 * changes made so that a dedicated server's team is always -1 (team game
 * or not)
 *
 * 71    11/02/98 4:38p Jeff
 * added ability to sort and display by efficiency
 *
 * 70    11/01/98 1:59a Jeff
 * made a $help inputcommand for help in a dedicated server environment
 *
 * 69    10/30/98 12:47p Jeff
 * cut down a couple bytes on memory usage
 *
 * 68    10/29/98 7:01p Jeff
 * creation of team placement dialog.  Moved TranslateEvent into DMFC
 *
 * 67    10/24/98 2:35p Matt
 * Changed "callsign" to "name" or "player name" in the multiplayer menus
 * and commands.
 *
 * 66    10/24/98 2:18p Jeff
 *
 * 65    10/23/98 6:51p Jeff
 * fixed hud num of teams sort in ctf, and memory overwrite in registry
 *
 * 64    10/23/98 11:22a Jeff
 * changes to handle mixcase, and display the client netgame info
 * correctly
 *
 * 63    10/21/98 5:02p Jeff
 * removed player from HUD if in observer nide
 *
 * 62    10/20/98 4:35p Jeff
 * added a flag for menu to add a <None> to MIT_PLIST...
 *
 * 61    10/20/98 12:16p Jeff
 * added death message filter, hud callsign filter
 *
 * 60    10/19/98 7:19p Matt
 * Added system to support different types of damage to the player and
 * have these different types make different sounds.
 *
 * 59    10/18/98 7:59p Jeff
 * functions added to dmfc for client->server objnum matching.  Banning
 * now uses tracker id when available.
 *
 * 58    10/17/98 7:30p Jeff
 * network_address compares don't compare port on somethings
 *
 * 57    10/15/98 6:18p Jeff
 * created the is player banned event, removed prejoin event
 *
 * 56    10/15/98 1:34p Jeff
 * added scrollable onscreen menu.  Remove ban in dmfc.  prejoin event
 *
 * 55    10/13/98 6:01p Jeff
 * added attaching
 *
 * 54    10/13/98 2:15a Jeff
 * created new event for when a player leaves a multiplayer game.  Fixed
 * some 'english' bugs in the network games.
 *
 * 53    10/08/98 3:37p Jeff
 * general improvements (Netgame info things, save to registry).  Changes
 * so it would send packets on NETSEQ_OBJECTS
 *
 * 51    10/05/98 2:49p Jeff
 *
 * 50    10/02/98 6:10p Jeff
 *
 * 49    10/01/98 11:30a Jeff
 * made the observer mode events into just a client event
 *
 * 48    9/30/98 4:21p Jeff
 * team changing is handled correctly
 *
 * 47    9/30/98 3:50p Jeff
 * general improvements (many)
 *
 * 46    9/29/98 3:04p Jeff
 * added time in game and start_time support
 *
 * 45    9/28/98 5:05p Jeff
 * made the statisitical death messages an option in the menu
 *
 * 44    9/25/98 7:25p Jeff
 *
 * 43    9/25/98 4:50p Jeff
 *
 * 42    9/24/98 6:54p Jeff
 * added DisconnectMe() and made use of it when you get kicked/banned
 *
 * 41    9/24/98 6:29p Jeff
 *
 * 40    9/24/98 5:52p Jeff
 * starting adding statistical death messages, checked in for testing
 *
 * 39    9/23/98 5:27p Jeff
 * fixed death message bug (using objnum instead of pnum)
 *
 * 38    9/23/98 4:17p Jeff
 * basic changes/improvements, started changing death messages
 *
 * 37    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 "dmfcinputcommands.h"

#include <stdlib.h>
#include <stdarg.h>
#include <algorithm>

char **DMFCStringTable;
int DMFCStringTableSize = 0;
const char *_DMFCErrorString = "DMFC Missing String";
uint8_t seeds1[31] = {49, 73, 0,  44, 87, 253, 35, 74, 62, 250, 4,  247, 251, 72,  244, 30,
                    59, 61, 60, 52, 50, 237, 23, 48, 56, 55,  65, 232, 231, 230, 0};
uint8_t seeds2[6] = {70, 95, 103, 102, 112, 0};

const char *DMFCGetString(int d) {
  if ((d < 0) || (d >= DMFCStringTableSize))
    return _DMFCErrorString;
  else
    return DMFCStringTable[d];
}

DMFCBase::DMFCBase(void) {
  m_cPtrs = 0;

  m_iServerHUDCallsignLevel = HUD_CALLSIGN_LEVEL_FULL;
  m_iMyCurrentHUDCallsignLevel = m_iMyPreferredHUDCallsignLevel = HUD_CALLSIGN_LEVEL_FULL;
  m_iProtectedFlags = 0;
  m_bHasInitialized = false;
  LossGuageEnabled = true;
  DMFCInit = false;
  RealGametime = 0;
  DedicatedLevelWait = 0;
  m_bDisplayTimelimitCountdown = true;
  m_iTimeLimitCountdown = 10;

  ENABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_PLRFIRSTTIME | DMFC_PRF_CANTEAMCHANGE);
  ENABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_AUTOSAVELEVELEND);
  ENABLE_FLAGS(m_iProtectedFlags,
               DMFC_PRF_AUTOTIMELIMIT | DMFC_PRF_AUTODEATHMSG | DMFC_PRF_AUTOTEAMSELECT | DMFC_PRF_DISPSTATHUDMSGS);
  DISABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_INOPTIONS | DMFC_PRF_DISPPLAYERINFO | DMFC_PRF_DISPNETGAMEINFO);
  DISABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_AUTOSAVEDISC | DMFC_PRF_DISPMENUBACKGR | DMFC_PRF_PAUSETIME);

  m_iNumBanPlayers = 0;
  m_bMakeClientsWait = false;
  m_iPlayerDisplayed = 0;

  DatabaseRegisteredName[0] = '\0';

  SPRoot = NULL; // no special packet handlers yet

  int i;

  for (i = 0; i < MAX_DEATH_MSGS; i++) {
    DeathMsgs[i].inuse = false;
    DeathMsgs[i].message = NULL;
    SuicideMsgs[i].inuse = false;
    SuicideMsgs[i].message = NULL;
  }

  for (i = 0; i < MAX_WEAPONS; i++) {
    WeaponMessages[i].inuse = false;
    WeaponMessages[i].message = NULL;
    WeaponHash[i] = -1;
  }

  m_iDeathMsgCount = 0;
  m_iSuicideMsgCount = 0;
  m_iUIWindowID = -1;
  m_UIUserData = NULL;

  m_DenyList = NULL;
  m_AllowList = NULL;
  m_InputCommandRootNode = NULL;
  Hard_max_players = DLLMAX_PLAYERS;

  DecryptData(seeds2, 5);

  TeamMenuItem = NULL;
  AutoBalanceItem = NULL;
  AllowTeamChangeItem = NULL;
  ObserverItem = NULL;
  m_BanList = NULL;
  StatisticMessagesItem = NULL;
  SaveStatsLevelEndItem = NULL;
  SaveStatsDisconnectItem = NULL;
  NetGameInfoItem = NULL;
  MenuBackgroundItem = NULL;
  ServerHUDCallsignsItem = NULL;
  HUDCallsignsItem = NULL;
  DeathMessageFilterItem = NULL;
  ShipLogosItem = NULL;
  HUDIndicatorItem = NULL;

  DisplayTauntIndicator = false;
  TauntIndicatorPlayerNum = -1;
  TauntIndicatorStartTime = 0;
  TauntIndicatorBMP = -1;

  for (i = 0; i < DLLMAX_TEAMS; i++)
    DMFC_team_names[i][0] = '\0';

  for (i = 0; i < DLLMAX_PLAYERS; i++) {
    PilotPicBmpHandles[i] = BAD_BITMAP_HANDLE;
  }

  InitializeVirtualHandles();
}

DMFCBase::~DMFCBase(void) {
  int i;

  ResetPInfo();

  RemoveAllBans();

  for (i = 0; i < DLLMAX_PLAYERS; i++) {
    if (PilotPicBmpHandles[i] > BAD_BITMAP_HANDLE) {
      DLLbm_FreeBitmap(PilotPicBmpHandles[i]);
      PilotPicBmpHandles[i] = BAD_BITMAP_HANDLE;
    }
  }

  if (TauntIndicatorBMP > BAD_BITMAP_HANDLE) {
    DLLbm_FreeBitmap(TauntIndicatorBMP);
    TauntIndicatorBMP = BAD_BITMAP_HANDLE;
  }

  for (i = 0; i < MAX_DEATH_MSGS; i++) {
    if (DeathMsgs[i].inuse) {
      if (DeathMsgs[i].message)
        free(DeathMsgs[i].message);
      DeathMsgs[i].inuse = false;
    }
    if (SuicideMsgs[i].inuse) {
      if (SuicideMsgs[i].message)
        free(SuicideMsgs[i].message);
      SuicideMsgs[i].inuse = false;
    }
  }

  for (i = 0; i < MAX_WEAPONS; i++) {
    if (WeaponMessages[i].inuse) {
      if (WeaponMessages[i].message) {
        free(WeaponMessages[i].message);
        WeaponMessages[i].message = NULL;
      }
    }
  }

  // remove all the special packet handlers
  tSPHandler *current, *next;
  current = next = SPRoot;
  while (current) {
    next = current->next;
    free(current);
    current = next;
  }
  SPRoot = NULL;
}

// ##################Setup and processing functions#######################

// DMFCBase::InitializeForLevel
//
//
void DMFCBase::InitializeForLevel(void) {
  if (m_bHasInitialized)
    return;

  // Reset the timers
  InitTimers();

  m_bHasInitialized = true;
}

// DMFCBase::LoadSettings
//
//	Loads the settings of DMFC from the registry and initializes those variables
void DMFCBase::LoadSettings(void) {
  // set default values
  ENABLE_FLAGS(m_iProtectedFlags,
               DMFC_PRF_AUTOTIMELIMIT | DMFC_PRF_AUTODEATHMSG | DMFC_PRF_AUTOTEAMSELECT | DMFC_PRF_DISPSTATHUDMSGS);
  ENABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_AUTOSAVELEVELEND | DMFC_PRF_CANTEAMCHANGE);
  DISABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_AUTOSAVEDISC | DMFC_PRF_DISPMENUBACKGR);
  m_iServerHUDCallsignLevel = HUD_CALLSIGN_LEVEL_FULL;
  m_iMyCurrentHUDCallsignLevel = m_iMyPreferredHUDCallsignLevel = HUD_CALLSIGN_LEVEL_FULL;
  m_iDeathMessageFilter = DM_FILTER_FULL;
  LossGuageEnabled = true;

  bool bTemp;

  if (DatabaseRead3("DMFCAutoTimeLimit", &bTemp)) {
    if (bTemp)
      ENABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_AUTOTIMELIMIT);
    else
      DISABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_AUTOTIMELIMIT);
  }

  if (DatabaseRead3("DMFCAutoDeathMessage", &bTemp)) {
    if (bTemp)
      ENABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_AUTODEATHMSG);
    else
      DISABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_AUTODEATHMSG);
  }

  if (DatabaseRead3("DMFCAutoTeamSelect", &bTemp)) {
    if (bTemp)
      ENABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_AUTOTEAMSELECT);
    else
      DISABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_AUTOTEAMSELECT);
  }

  if (DatabaseRead3("DMFCStatHUDMessages", &bTemp)) {
    if (bTemp)
      ENABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_DISPSTATHUDMSGS);
    else
      DISABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_DISPSTATHUDMSGS);
  }

  if (DatabaseRead3("DMFCAutoSaveStatLevelEnd", &bTemp)) {
    if (bTemp)
      ENABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_AUTOSAVELEVELEND);
    else
      DISABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_AUTOSAVELEVELEND);
  }

  if (DatabaseRead3("DMFCAutoSaveStatDisconnect", &bTemp)) {
    if (bTemp)
      ENABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_AUTOSAVEDISC);
    else
      DISABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_AUTOSAVEDISC);
  }

  if (DatabaseRead3("DMFCMenuBackground", &bTemp)) {
    if (bTemp)
      ENABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_DISPMENUBACKGR);
    else
      DISABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_DISPMENUBACKGR);
  }

  if (DatabaseRead3("DMFCCanChangeTeams", &bTemp)) {
    if (bTemp)
      ENABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_CANTEAMCHANGE);
    else
      DISABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_CANTEAMCHANGE);
  }

  DatabaseRead3("DMFCLagLossIndicator", &LossGuageEnabled);

  DatabaseRead2("DMFCShowHudCallsigns", &m_iMyPreferredHUDCallsignLevel, sizeof(m_iMyPreferredHUDCallsignLevel));
  DatabaseRead2("DMFCServerShowHudCallsigns", &m_iServerHUDCallsignLevel, sizeof(m_iServerHUDCallsignLevel));
  DatabaseRead2("DMFCDeathMessageFilter", &m_iDeathMessageFilter, sizeof(m_iDeathMessageFilter));

  m_iMyCurrentHUDCallsignLevel = m_iMyPreferredHUDCallsignLevel;
}

// DMFCBase::SaveSettings
//
//	Saves out the settings of DMFC to the registry
void DMFCBase::SaveSettings(void) {
  bool bTemp;

  bTemp = (bool)((m_iProtectedFlags & DMFC_PRF_AUTOTIMELIMIT) != 0);
  DatabaseWrite2("DMFCAutoTimeLimit", bTemp);

  bTemp = (bool)((m_iProtectedFlags & DMFC_PRF_AUTODEATHMSG) != 0);
  DatabaseWrite2("DMFCAutoDeathMessage", bTemp);

  bTemp = (bool)((m_iProtectedFlags & DMFC_PRF_AUTOTEAMSELECT) != 0);
  DatabaseWrite2("DMFCAutoTeamSelect", bTemp);

  bTemp = (bool)((m_iProtectedFlags & DMFC_PRF_DISPSTATHUDMSGS) != 0);
  DatabaseWrite2("DMFCStatHUDMessages", bTemp);

  bTemp = (bool)((m_iProtectedFlags & DMFC_PRF_AUTOSAVELEVELEND) != 0);
  DatabaseWrite2("DMFCAutoSaveStatLevelEnd", bTemp);

  bTemp = (bool)((m_iProtectedFlags & DMFC_PRF_AUTOSAVEDISC) != 0);
  DatabaseWrite2("DMFCAutoSaveStatDisconnect", bTemp);

  bTemp = (bool)((m_iProtectedFlags & DMFC_PRF_DISPMENUBACKGR) != 0);
  DatabaseWrite2("DMFCMenuBackground", bTemp);

  bTemp = (bool)((m_iProtectedFlags & DMFC_PRF_CANTEAMCHANGE) != 0);
  DatabaseWrite2("DMFCCanChangeTeams", bTemp);

  DatabaseWrite2("DMFCLagLossIndicator", LossGuageEnabled);

  DatabaseWrite2("DMFCShowHudCallsigns", m_iMyPreferredHUDCallsignLevel);
  DatabaseWrite2("DMFCServerShowHudCallsigns", m_iServerHUDCallsignLevel);
  DatabaseWrite2("DMFCDeathMessageFilter", m_iDeathMessageFilter);
}

// DMFCBase::LoadFunctions
//
//	Initialized all the functions for a multiplayer game...must be first thing called
void DMFCBase::LoadFunctions(int *api_func) {
  DLLGetGameAPI = (GetGameAPI_fp)api_func;
  DLLGetGameAPI(&API);

  Objects = (object *)API.objs;
  Rooms = (room *)API.rooms;
  Terrain_seg = (terrain_segment *)API.terrain;
  Players = (player *)API.players;
  Netgame = (netgame_info *)API.netgame;
  NetPlayers = (netplayer *)API.netplayers;
  Ships = (ship *)API.ships;
  Weapons = (weapon *)API.weapons;
  Current_mission = (tMission *)API.Current_mission;
  GameTextures = (texture *)API.GameTextures;
  GameVClips = (vclip *)API.GameVClips;

  // Do functions, variables
  // Note, these must match the ordering on the D3 side (Game2DLL.cpp)
  DLLAddHUDMessage = (AddHUDMessage_fp)API.fp[0];
  DLLMultiSendClientExecuteDLL = (MultiSendClientExecuteDLL_fp)API.fp[1];
  DLLFindObjectIDName = (FindObjectIDName_fp)API.fp[2];
  DLLGetGoalRoomForTeam = (GetGoalRoomForTeam_fp)API.fp[3];
  DLLSetMaxTeams = (SetMaxTeams_fp)API.fp[4];
  DLLComputeRoomCenter = (ComputeRoomCenter_fp)API.fp[5];
  DLLObjCreate = (ObjCreate_fp)API.fp[6];
  DLLMultiSendObject = (MultiSendObject_fp)API.fp[7];
  DLLMultiPaintGoalRooms = (MultiPaintGoalRooms_fp)API.fp[8];
  DLLMultiSendSpecialPacket = (MultiSendSpecialPacket_fp)API.fp[9];
  DLLIncTeamScore = (IncTeamScore_fp)API.fp[10];
  DLLDebug_ConsolePrintf = (Debug_ConsolePrintf_fp)API.fp[11];
  DLLObjSetPosNoMark = (ObjSetPosNoMark_fp)API.fp[12];
  DLLInvCheckItem = (InvCheckItem_fp)API.fp[13];
  DLLInvAddTypeID = (InvAddTypeID_fp)API.fp[14];
  DLLInvRemove = (InvRemove_fp)API.fp[15];
  DLLPlayerSetLighting = (PlayerSetLighting_fp)API.fp[16];
  DLLFindShipName = (FindShipName_fp)API.fp[17];
  DLLPlayerSetRotatingBall = (PlayerSetRotatingBall_fp)API.fp[18];
  DLLPlayerChangeShip = (PlayerChangeShip_fp)API.fp[19];
  DLLInvGetTypeIDCount = (InvGetTypeIDCount_fp)API.fp[20];
  DLLPlay3dSound = (D3W_Play3dSound_fp)API.fp[21];
  DLLFindSoundName = (FindSoundName_fp)API.fp[22];
  DLLSpewCreate = (SpewCreate_fp)API.fp[23];
  DLLSpewClearEvent = (SpewClearEvent_fp)API.fp[24];
  DLLbm_AllocLoadFileBitmap = (bm_AllocLoadFileBitmap_fp)API.fp[25];
  DLLbm_FreeBitmap = (bm_FreeBitmap_fp)API.fp[26];
  DLLrend_DrawScaledBitmap = (rend_DrawScaledBitmap_fp)API.fp[27];
  DLLgrtext_Printf = (grtext_Printf_fp)API.fp[28];
  DLLgrtext_Flush = (grtext_Flush_fp)API.fp[29];
  DLLgrtext_SetColor = (grtext_SetColor_fp)API.fp[30];
  DLLgrtext_SetFancyColor = (grtext_SetFancyColor_fp)API.fp[31];
  DLLgrtext_SetAlpha = (grtext_SetAlpha_fp)API.fp[32];
  DLLgrtext_GetAlpha = (grtext_GetAlpha_fp)API.fp[33];
  DLLgrtext_SetFont = (grtext_SetFont_fp)API.fp[34];
  DLLgrtext_GetFont = (grtext_GetFont_fp)API.fp[35];
  DLLgrtext_GetTextLineWidth = (grtext_GetTextLineWidth_fp)API.fp[36];
  DLLgrfont_GetHeight = (grfont_GetHeight_fp)API.fp[37];
  DLLgrtext_CenteredPrintf = (grtext_CenteredPrintf_fp)API.fp[38];
  DLLAddColoredHUDMessage = (AddColoredHUDMessage_fp)API.fp[39];
  DLLbm_w = (bm_w_fp)API.fp[40];
  DLLbm_h = (bm_h_fp)API.fp[41];
  DLLrend_DrawSimpleBitmap = (rend_DrawSimpleBitmap_fp)API.fp[42];
  DLLMultiClientSendSpecialPacket = (MultiClientSendSpecialPacket_fp)API.fp[43];
  DLLAddBlinkingHUDMessage = (AddBlinkingHUDMessage_fp)API.fp[44];
  DLLInvReset = (InvReset_fp)API.fp[45];
  DLLAddHUDItem = (AddHUDItem_fp)API.fp[46];
  DLLRenderHUDQuad = (RenderHUDQuad_fp)API.fp[47];
  DLLRenderHUDText = (RenderHUDText_fp)API.fp[48];
  DLLMultiEndLevel = (MultiEndLevel_fp)API.fp[49];
  DLLbm_data = (bm_data_fp)API.fp[50];
  DLLbm_AllocBitmap = (bm_AllocBitmap_fp)API.fp[51];
  DLLrend_FillRect = (rend_FillRect_fp)API.fp[52];
  DLLbm_CreateChunkedBitmap = (bm_CreateChunkedBitmap_fp)API.fp[53];
  DLLbm_DestroyChunkedBitmap = (bm_DestroyChunkedBitmap_fp)API.fp[54];
  DLLrend_DrawChunkedBitmap = (rend_DrawChunkedBitmap_fp)API.fp[55];
  DLLrend_DrawScaledChunkedBitmap = (rend_DrawScaledChunkedBitmap_fp)API.fp[56];
  DLLOpenCFILE = (OpenCFILE_fp)API.fp[57];
  DLLcfclose = (cfclose_fp)API.fp[58];
  DLLcfeof = (cfeof_fp)API.fp[59];
  DLLcfexist = (cfexist_fp)API.fp[60];
  DLLcf_ReadBytes = (cf_ReadBytes_fp)API.fp[61];
  DLLcf_ReadInt = (cf_ReadInt_fp)API.fp[62];
  DLLcf_ReadShort = (cf_ReadShort_fp)API.fp[63];
  DLLcf_ReadByte = (cf_ReadByte_fp)API.fp[64];
  DLLcf_ReadFloat = (cf_ReadFloat_fp)API.fp[65];
  DLLcf_ReadDouble = (cf_ReadDouble_fp)API.fp[66];
  DLLcf_ReadString = (cf_ReadString_fp)API.fp[67];
  DLLcf_WriteBytes = (cf_WriteBytes_fp)API.fp[68];
  DLLcf_WriteString = (cf_WriteString_fp)API.fp[69];
  DLLcf_WriteInt = (cf_WriteInt_fp)API.fp[70];
  DLLcf_WriteShort = (cf_WriteShort_fp)API.fp[71];
  DLLcf_WriteByte = (cf_WriteByte_fp)API.fp[72];
  DLLcf_WriteFloat = (cf_WriteFloat_fp)API.fp[73];
  DLLcf_WriteDouble = (cf_WriteDouble_fp)API.fp[74];
  DLLcf_CopyFile = (cf_CopyFile_fp)API.fp[75];
  DLLcf_Diff = (cf_Diff_fp)API.fp[76];
  DLLMultiDisconnectPlayer = (MultiDisconnectPlayer_fp)API.fp[77];
  DLLnw_GetNumbersFromHostAddress = (nw_GetNumbersFromHostAddress_fp)API.fp[78];
  DLLnw_GetThisIP = (nw_GetThisIP_fp)API.fp[79];
  DLLCreateStringTable = (CreateStringTable_fp)API.fp[80];
  DLLDestroyStringTable = (DestroyStringTable_fp)API.fp[81];
  DLLRenderHUDTextFlags = (RenderHUDTextFlags_fp)API.fp[82];
  DLLPlayerSpewInventory = (PlayerSpewInventory_fp)API.fp[83];
  DLLPlayerSetHUDNameFOV = (PlayerSetHUDNameFOV_fp)API.fp[84];
  DLLGetUltimateParentForObject = (GetUltimateParentForObject_fp)API.fp[85];
  DLLSetObjectDeadFlagRaw = (SetObjectDeadFlagRaw_fp)API.fp[86];
  FatalError = (DLLFatalError_fp)API.fp[87];
  DLLassert = (assertdll_fp)API.fp[88];
  DLLMultiMatchWeapon = (MultiMatchWeapon_fp)API.fp[89];
  DLLMultiGetMatchChecksum = (MultiGetMatchChecksum_fp)API.fp[90];
  DLLFindWeaponName = (FindWeaponName_fp)API.fp[91];
  DLLcf_OpenLibrary = (cf_OpenLibrary_fp)API.fp[92];
  DLLcf_CloseLibrary = (cf_CloseLibrary_fp)API.fp[93];
  DLLMultiSendRequestToObserve = (MultiSendRequestToObserve_fp)API.fp[94];
  DLLFindTextureName = (FindTextureName_fp)API.fp[95];
  DLLApplyDamageToPlayer = (ApplyDamageToPlayer_fp)API.fp[96];
  DLLMultiMatchGeneric = (MultiMatchGeneric_fp)API.fp[97];
  DLLSetUITextItemText = (SetUITextItemText_fp)API.fp[98];
  DLLNewUIWindowCreate = (NewUIWindowCreate_fp)API.fp[99];
  DLLNewUIWindowDestroy = (NewUIWindowDestroy_fp)API.fp[100];
  DLLNewUIWindowOpen = (NewUIWindowOpen_fp)API.fp[101];
  DLLNewUIWindowClose = (NewUIWindowClose_fp)API.fp[102];
  DLLTextCreate = (TextCreate_fp)API.fp[103];
  DLLEditCreate = (EditCreate_fp)API.fp[104];
  DLLButtonCreate = (ButtonCreate_fp)API.fp[105];
  DLLListCreate = (ListCreate_fp)API.fp[106];
  DLLListRemoveAll = (ListRemoveAll_fp)API.fp[107];
  DLLListAddItem = (ListAddItem_fp)API.fp[108];
  DLLListRemoveItem = (ListRemoveItem_fp)API.fp[109];
  DLLListSelectItem = (ListSelectItem_fp)API.fp[110];
  DLLListGetItem = (ListGetItem_fp)API.fp[111];
  DLLListGetSelectedIndex = (ListGetSelectedIndex_fp)API.fp[112];
  DLLEditSetText = (EditSetText_fp)API.fp[113];
  DLLEditGetText = (EditGetText_fp)API.fp[114];
  DLLDoUI = (DoUI_fp)API.fp[115];
  DLLDoMessageBox = (DoMessageBox_fp)API.fp[116];
  DLLDescentDefer = (DescentDefer_fp)API.fp[117];
  DLLNewUIGameWindowCreate = (NewUIGameWindowCreate_fp)API.fp[118];
  DLLNewUIGameWindowDestroy = (NewUIGameWindowDestroy_fp)API.fp[119];
  DLLNewUIGameWindowOpen = (NewUIGameWindowOpen_fp)API.fp[120];
  DLLNewUIGameWindowClose = (NewUIGameWindowClose_fp)API.fp[121];
  DLLHotSpotCreate = (HotSpotCreate_fp)API.fp[122];
  DLLPollUI = (PollUI_fp)API.fp[123];
  DLLRemoveUITextItem = (RemoveUITextItem_fp)API.fp[124];
  DLLCreateNewUITextItem = (CreateNewUITextItem_fp)API.fp[125];
  DLLUIConsoleGadgetCreate = (UIConsoleGadgetCreate_fp)API.fp[126];
  DLLUIConsoleGadgetputs = (UIConsoleGadgetputs_fp)API.fp[127];
  DLLNewUIWindowSetFocusOnEditGadget = (NewUIWindowSetFocusOnEditGadget_fp)API.fp[128];
  DLLOldEditCreate = (OldEditCreate_fp)API.fp[129];
  DLLOldListCreate = (OldListCreate_fp)API.fp[130];
  DLLOldListRemoveAll = (OldListRemoveAll_fp)API.fp[131];
  DLLOldListAddItem = (OldListAddItem_fp)API.fp[132];
  DLLOldListRemoveItem = (OldListRemoveItem_fp)API.fp[133];
  DLLOldListSelectItem = (OldListSelectItem_fp)API.fp[134];
  DLLOldListGetItem = (OldListGetItem_fp)API.fp[135];
  DLLOldListGetSelectedIndex = (OldListGetSelectedIndex_fp)API.fp[136];
  DLLOldEditSetText = (OldEditSetText_fp)API.fp[137];
  DLLOldEditGetText = (OldEditGetText_fp)API.fp[138];
  DLLToggleUICallback = (ToggleUICallback_fp)API.fp[139];
  DLLSetOldEditBufferLen = (SetOldEditBufferLen_fp)API.fp[140];
  DLLNewUIWindowLoadBackgroundImage = (NewUIWindowLoadBackgroundImage_fp)API.fp[141];
  DLLDeleteUIItem = (DeleteUIItem_fp)API.fp[142];
  DLLHotSpotSetStates = (HotSpotSetStates_fp)API.fp[143];
  DLLSetUICallback = (SetUICallback_fp)API.fp[144];
  DLLGetUICallback = (RetrieveUICallback_fp)API.fp[145];
  DLLSuspendControls = (SuspendControls_fp)API.fp[146];
  DLLResumeControls = (ResumeControls_fp)API.fp[147];
  DLLShowCursor = (ui_ShowCursor_fp)API.fp[148];
  DLLHideCursor = (ui_HideCursor_fp)API.fp[149];
  DLLGameFrame = (GameFrame_fp)API.fp[150];
  DPrintf = (DPrintf_fp)API.fp[151];
  DLLddio_MakePath = (ddio_MakePath_fp)API.fp[152];
  DLLddio_SplitPath = (ddio_SplitPath_fp)API.fp[153];
  DLLPlay2dSound = (Play2dSound_fp)API.fp[154];
  DLLTouchSound = (TouchSound_fp)API.fp[155];
  DatabaseRead1 = (dDatabaseRead_fp1)API.fp[156];
  DatabaseRead2 = (dDatabaseRead_fp2)API.fp[157];
  DatabaseRead3 = (dDatabaseRead_fp3)API.fp[158];
  DatabaseWrite1 = (dDatabaseWrite_fp1)API.fp[159];
  DatabaseWrite2 = (dDatabaseWrite_fp2)API.fp[160];
  DLLAttachObject = (AttachObject_fp)API.fp[161];
  DLLObjGet = (ObjGet_fp)API.fp[162];
  DLLListSetSelectedIndex = (ListSetSelectedIndex_fp)API.fp[163];
  DLLRemoveUIBmpItem = (RemoveUIBmpItem_fp)API.fp[164];
  DLLCreateNewUIBmpItem = (CreateNewUIBmpItem_fp)API.fp[165];
  DLLGetUIItemWidth = (GetUIItemWidth_fp)API.fp[166];
  DLLGetUIItemHeight = (GetUIItemHeight_fp)API.fp[167];
  DLLSliderCreate = (SliderCreate_fp)API.fp[168];
  DLLSliderSetRange = (SliderSetRange_fp)API.fp[169];
  DLLSliderGetRange = (SliderGetRange_fp)API.fp[170];
  DLLSliderSetPos = (SliderSetPos_fp)API.fp[171];
  DLLSliderGetPos = (SliderGetPos_fp)API.fp[172];
  DLLSliderSetSelectChangeCallback = (SliderSetSelectChangeCallback_fp)API.fp[173];
  DLLSliderSetSelectChangeCallbackWData = (SliderSetSelectChangeCallbackWData_fp)API.fp[174];
  DLLTextSetTitle = (TextSetTitle_fp)API.fp[175];
  DLLPPic_GetPilot = (PPic_GetPilot_fp)API.fp[176];
  DLLPPic_GetBitmapHandle = (PPic_GetBitmapHandle_fp)API.fp[177];
  DLLrend_DrawLine = (rend_DrawLine_fp)API.fp[178];
  DLLrend_SetFlatColor = (rend_SetFlatColor_fp)API.fp[179];
  DLLMultiSetLogoState = (MultiSetLogoState_fp)API.fp[180];
  DLLPlayerGetRandomStartPosition = (PlayerGetRandomStartPosition_fp)API.fp[181];
  DLLInitPlayerNewShip = (InitPlayerNewShip_fp)API.fp[182];
  DLLCheckBoxCreate = (CheckBoxCreate_fp)API.fp[183];
  DLLCheckBoxSetCheck = (CheckBoxSetCheck_fp)API.fp[184];
  DLLCheckBoxIsChecked = (CheckBoxIsChecked_fp)API.fp[185];
  DLLnw_GetHostAddressFromNumbers = (nw_GetHostAddressFromNumbers_fp)API.fp[186];
  TableFilesClear = (TableFilesClear_fp)API.fp[187];
  TableFileAdd = (TableFileAdd_fp)API.fp[188];
  DLLDebugBreak_callback_stop = (DebugBreak_callback_stop_fp)API.fp[189];
  DLLDebugBreak_callback_resume = (DebugBreak_callback_resume_fp)API.fp[190];
  DLLInt3MessageBox = (Int3MessageBox_fp)API.fp[191];
  DLLGetUIItemPosition = (GetUIItemPosition_fp)API.fp[192];
  DLLAttachObjectRadius = (AttachObjectRadius_fp)API.fp[193];
  DLLUnattachChildren = (UnattachChildren_fp)API.fp[194];
  DLLUnattachChild = (UnattachChild_fp)API.fp[195];
  DLLUnattachFromParent = (UnattachFromParent_fp)API.fp[196];
  DLLvm_GetMagnitude = (vm_GetMagnitude_fp)API.fp[197];
  DLLvm_MatrixMulVector = (vm_MatrixMulVector_fp)API.fp[198];
  DLLphys_apply_force = (phys_apply_force_fp)API.fp[199];
  DLLphys_apply_rot = (phys_apply_rot_fp)API.fp[200];
  DLLvm_TransposeMatrix = (vm_TransposeMatrix_fp)API.fp[201];
  DLLvm_CrossProduct = (vm_CrossProduct_fp)API.fp[202];
  DLLvm_NormalizeVector = (vm_NormalizeVector_fp)API.fp[203];
  DLLConvertEulerToAxisAmount = (ConvertEulerToAxisAmount_fp)API.fp[204];
  DLLConvertAxisAmountToEuler = (ConvertAxisAmountToEuler_fp)API.fp[205];
  DLLvm_GetMagnitudeFast = (vm_GetMagnitudeFast_fp)API.fp[206];
  DLLvm_MakeIdentity = (vm_MakeIdentity_fp)API.fp[207];
  DLLvm_MakeVectorZero = (vm_MakeVectorZero_fp)API.fp[208];
  DLLvm_MakeAngleZero = (vm_MakeAngleZero_fp)API.fp[209];
  DLLvm_VectorMulTMatrix = (vm_VectorMulTMatrix_fp)API.fp[210];
  DLLvm_MatrixMul = (vm_MatrixMul_fp)API.fp[211];
  DLLvm_MatrixMulTMatrix = (vm_MatrixMulTMatrix_fp)API.fp[212];
  DLLvm_DotProduct = (vm_DotProduct_fp)API.fp[213];
  DLLvm_SubVectors = (vm_SubVectors_fp)API.fp[214];
  DLLvm_AddVectors = (vm_AddVectors_fp)API.fp[215];
  DLLvm_AverageVector = (vm_AverageVector_fp)API.fp[216];
  DLLvm_ScaleVector = (vm_ScaleVector_fp)API.fp[217];
  DLLvm_ScaleAddVector = (vm_ScaleAddVector_fp)API.fp[218];
  DLLvm_DivVector = (vm_DivVector_fp)API.fp[219];
  DLLvm_NormalizeVectorFast = (vm_NormalizeVectorFast_fp)API.fp[220];
  DLLvm_ClearMatrix = (vm_ClearMatrix_fp)API.fp[221];
  DLLvm_AnglesToMatrix = (vm_AnglesToMatrix_fp)API.fp[222];
  DLLvm_Orthogonalize = (vm_Orthogonalize_fp)API.fp[223];
  DLLvm_VectorToMatrix = (vm_VectorToMatrix_fp)API.fp[224];
  DLLvm_VectorAngleToMatrix = (vm_VectorAngleToMatrix_fp)API.fp[225];
  DLLvm_SinCos = (vm_SinCos_fp)API.fp[226];
  DLLvm_GetSlope = (vm_GetSlope_fp)API.fp[227];
  DLLvm_GetPerp = (vm_GetPerp_fp)API.fp[228];
  DLLvm_GetNormal = (vm_GetNormal_fp)API.fp[229];
  DLLvm_VectorDistance = (vm_VectorDistance_fp)API.fp[230];
  DLLvm_VectorDistanceQuick = (vm_VectorDistanceQuick_fp)API.fp[231];
  DLLvm_GetNormalizedDir = (vm_GetNormalizedDir_fp)API.fp[232];
  DLLvm_GetNormalizedDirFast = (vm_GetNormalizedDirFast_fp)API.fp[233];
  DLLvm_ExtractAnglesFromMatrix = (vm_ExtractAnglesFromMatrix_fp)API.fp[234];
  DLLvm_DeltaAngVec = (vm_DeltaAngVec_fp)API.fp[235];
  DLLvm_DeltaAngVecNorm = (vm_DeltaAngVecNorm_fp)API.fp[236];
  DLLvm_DistToPlane = (vm_DistToPlane_fp)API.fp[237];
  DLLvm_CalcDetValue = (vm_CalcDetValue_fp)API.fp[238];
  DLLvm_MakeInverseMatrix = (vm_MakeInverseMatrix_fp)API.fp[239];
  DLLvm_SinCosToMatrix = (vm_SinCosToMatrix_fp)API.fp[240];
  DLLvm_GetCentroid = (vm_GetCentroid_fp)API.fp[241];
  DLLvm_MakeRandomVector = (vm_MakeRandomVector_fp)API.fp[242];
  DLLvm_ComputeBoundingSphere = (vm_ComputeBoundingSphere_fp)API.fp[243];
  DLLvm_GetCentroidFast = (vm_GetCentroidFast_fp)API.fp[244];
  DLLRenderHUDGetTextLineWidth = (RenderHUDGetTextLineWidth_fp)API.fp[245];
  DLLRenderHUDGetTextHeight = (RenderHUDGetTextHeight_fp)API.fp[246];
  DLLStartFrame = (StartFrame_fp)API.fp[247];
  DLLEndFrame = (EndFrame_fp)API.fp[248];
  DLLResetFacings = (ResetFacings_fp)API.fp[249];
  DLLGameRenderWorld = (GameRenderWorld_fp)API.fp[250];
  DLLGetFrameParameters = (GetFrameParameters_fp)API.fp[251];
  DLLrend_SetZBufferState = (rend_SetZBufferState_fp)API.fp[252];
  DLLrend_SetLighting = (rend_SetLighting_fp)API.fp[253];
  DLLrend_SetColorModel = (rend_SetColorModel_fp)API.fp[254];
  DLLrend_SetTextureType = (rend_SetTextureType_fp)API.fp[255];
  DLLrend_DrawPolygon = (rend_DrawPolygon_fp)API.fp[256];
  DLLrend_SetMipState = (rend_SetMipState_fp)API.fp[257];
  DLLrend_SetFogState = (rend_SetFogState_fp)API.fp[258];
  DLLrend_SetFiltering = (rend_SetFiltering_fp)API.fp[259];
  DLLrend_SetOverlayMap = (rend_SetOverlayMap_fp)API.fp[260];
  DLLrend_SetOverlayType = (rend_SetOverlayType_fp)API.fp[261];
  DLLrend_ClearScreen = (rend_ClearScreen_fp)API.fp[262];
  DLLrend_SetPixel = (rend_SetPixel_fp)API.fp[263];
  DLLrend_GetPixel = (rend_GetPixel_fp)API.fp[264];
  DLLrend_FillCircle = (rend_FillCircle_fp)API.fp[265];
  DLLrend_DrawCircle = (rend_DrawCircle_fp)API.fp[266];
  DLLrend_SetAlphaType = (rend_SetAlphaType_fp)API.fp[267];
  DLLrend_SetAlphaValue = (rend_SetAlphaValue_fp)API.fp[268];
  DLLrend_SetWrapType = (rend_SetWrapType_fp)API.fp[269];
  DLLrend_SetZBias = (rend_SetZBias_fp)API.fp[270];
  DLLrend_SetZBufferWriteMask = (rend_SetZBufferWriteMask_fp)API.fp[271];
  DLLrend_GetLFBLock = (rend_GetLFBLock_fp)API.fp[272];
  DLLrend_ReleaseLFBLock = (rend_ReleaseLFBLock_fp)API.fp[273];
  DLLrend_DrawLFBBitmap = (rend_DrawLFBBitmap_fp)API.fp[274];
  DLLrend_DrawSpecialLine = (rend_DrawSpecialLine_fp)API.fp[275];
  DLLfvi_FindIntersection = (fvi_FindIntersection_fp)API.fp[276];
  DLLfvi_QuickDistFaceList = (fvi_QuickDistFaceList_fp)API.fp[277];
  DLLfvi_QuickDistCellList = (fvi_QuickDistCellList_fp)API.fp[278];
  DLLfvi_QuickDistObjectList = (fvi_QuickDistObjectList_fp)API.fp[279];
  DLLfvi_QuickRoomCheck = (fvi_QuickRoomCheck_fp)API.fp[280];
  DLLObjSetPos = (ObjSetPos_fp)API.fp[281];
  DLLSetObjectDeadFlag = (SetObjectDeadFlag_fp)API.fp[282];
  DLLtaunt_AreEnabled = (taunt_AreEnabled_fp)API.fp[283];
  DLLtaunt_Enable = (taunt_Enable_fp)API.fp[284];
  GetPlayerRankIndex = (GetPlayerRankIndex_fp)API.fp[285];
  DLLVisEffectAllocate = (VisEffectAllocate_fp)API.fp[286];
  DLLVisEffectFree = (VisEffectFree_fp)API.fp[287];
  DLLVisEffectInitType = (VisEffectInitType_fp)API.fp[288];
  DLLVisEffectCreate = (VisEffectCreate_fp)API.fp[289];
  DLLVisEffectLink = (VisEffectLink_fp)API.fp[290];
  DLLVisEffectUnlink = (VisEffectUnlink_fp)API.fp[291];
  DLLVisEffectRelink = (VisEffectRelink_fp)API.fp[292];
  DLLVisEffectDelete = (VisEffectDelete_fp)API.fp[293];
  DLLCreateRandomSparks = (CreateRandomSparks_fp)API.fp[294];
  DLLCreateRandomLineSparks = (CreateRandomLineSparks_fp)API.fp[295];
  DLLVisEffectCreateControlled = (VisEffectCreateControlled_fp)API.fp[296];
  DLLCreateRandomParticles = (CreateRandomParticles_fp)API.fp[297];
  DLLAttachRandomNapalmEffectsToObject = (AttachRandomNapalmEffectsToObject_fp)API.fp[298];
  DLLInitObjectScripts = (InitObjectScripts_fp)API.fp[299];
  DLLg3_StartFrame = (g3_StartFrame_fp)API.fp[300];
  DLLg3_EndFrame = (g3_EndFrame_fp)API.fp[301];
  DLLg3_GetViewPosition = (g3_GetViewPosition_fp)API.fp[302];
  DLLg3_GetViewMatrix = (g3_GetViewMatrix_fp)API.fp[303];
  DLLg3_GetUnscaledMatrix = (g3_GetUnscaledMatrix_fp)API.fp[304];
  DLLg3_StartInstanceMatrix = (g3_StartInstanceMatrix_fp)API.fp[305];
  DLLg3_StartInstanceAngles = (g3_StartInstanceAngles_fp)API.fp[306];
  DLLg3_DoneInstance = (g3_DoneInstance_fp)API.fp[307];
  DLLg3_CheckNormalFacing = (g3_CheckNormalFacing_fp)API.fp[308];
  DLLg3_RotatePoint = (g3_RotatePoint_fp)API.fp[309];
  DLLg3_ProjectPoint = (g3_ProjectPoint_fp)API.fp[310];
  DLLg3_CalcPointDepth = (g3_CalcPointDepth_fp)API.fp[311];
  DLLg3_Point2Vec = (g3_Point2Vec_fp)API.fp[312];
  DLLg3_CodePoint = (g3_CodePoint_fp)API.fp[313];
  DLLg3_RotateDeltaX = (g3_RotateDeltaX_fp)API.fp[314];
  DLLg3_RotateDeltaY = (g3_RotateDeltaY_fp)API.fp[315];
  DLLg3_RotateDeltaZ = (g3_RotateDeltaZ_fp)API.fp[316];
  DLLg3_RotateDeltaVec = (g3_RotateDeltaVec_fp)API.fp[317];
  DLLg3_AddDeltaVec = (g3_AddDeltaVec_fp)API.fp[318];
  DLLg3_DrawPoly = (g3_DrawPoly_fp)API.fp[319];
  DLLg3_DrawSphere = (g3_DrawSphere_fp)API.fp[320];
  DLLg3_CheckAndDrawPoly = (g3_CheckAndDrawPoly_fp)API.fp[321];
  DLLg3_DrawLine = (g3_DrawLine_fp)API.fp[322];
  DLLg3_DrawBitmap = (g3_DrawBitmap_fp)API.fp[323];
  DLLg3_DrawRotatedBitmap = (g3_DrawRotatedBitmap_fp)API.fp[324];
  DLLg3_DrawBox = (g3_DrawBox_fp)API.fp[325];
  DLLg3_SetCustomClipPlane = (g3_SetCustomClipPlane_fp)API.fp[326];
  DLLg3_SetFarClipZ = (g3_SetFarClipZ_fp)API.fp[327];
  DLLg3_ClipPolygon = (g3_ClipPolygon_fp)API.fp[328];
  DLLg3_FreeTempPoints = (g3_FreeTempPoints_fp)API.fp[329];
  DLLg3_GetMatrixScale = (g3_GetMatrixScale_fp)API.fp[330];
  DLLg3_SetTriangulationTest = (g3_SetTriangulationTest_fp)API.fp[331];
  DLLg3_DrawSpecialLine = (g3_DrawSpecialLine_fp)API.fp[332];
  DLLg3_DrawPlanarRotatedBitmap = (g3_DrawPlanarRotatedBitmap_fp)API.fp[333];
  DLLPlayerStopSounds = (PlayerStopSounds_fp)API.fp[334];
  DLLFindArg = (FindArg_fp)API.fp[335];
  DLLFireWeaponFromObject = (FireWeaponFromObject_fp)API.fp[336];
  DLLCreateAndFireWeapon = (CreateAndFireWeapon_fp)API.fp[337];
  DLLSelectNextCameraView = (SelectNextCameraView_fp)API.fp[338];
  Inven_Add = (dInven_Add_fp)API.fp[339];
  Inven_AddObject = (dInven_AddObject_fp)API.fp[340];
  Inven_AddCounterMeasure = (dInven_AddCounterMeasure_fp)API.fp[341];
  Inven_Remove = (dInven_Remove_fp)API.fp[342];
  Inven_Use = (dInven_Use_fp)API.fp[343];
  Inven_UseObjHandle = (dInven_UseObjHandle_fp)API.fp[344];
  Inven_Size = (dInven_Size_fp)API.fp[345];
  Inven_CheckItem = (dInven_CheckItem_fp)API.fp[346];
  Inven_Reset = (dInven_Reset_fp)API.fp[347];
  Inven_ResetPos = (dInven_ResetPos_fp)API.fp[348];
  Inven_NextPos = (dInven_NextPos_fp)API.fp[349];
  Inven_PrevPos = (dInven_PrevPos_fp)API.fp[350];
  Inven_GetPosTypeID = (dInven_GetPosTypeID_fp)API.fp[351];
  Inven_GetAuxPosTypeID = (dInven_GetAuxPosTypeID_fp)API.fp[352];
  Inven_GetPosDescription = (dInven_GetPosDescription_fp)API.fp[353];
  Inven_GetPosIconName = (dInven_GetPosIconName_fp)API.fp[354];
  Inven_GetPosName = (dInven_GetPosName_fp)API.fp[355];
  Inven_GetPosInfo = (dInven_GetPosInfo_fp)API.fp[356];
  Inven_GetPosCount = (dInven_GetPosCount_fp)API.fp[357];
  Inven_AtBeginning = (dInven_AtBeginning_fp)API.fp[358];
  Inven_AtEnd = (dInven_AtEnd_fp)API.fp[359];
  Inven_GotoPos = (dInven_GotoPos_fp)API.fp[360];
  Inven_GotoPosTypeID = (dInven_GotoPosTypeID_fp)API.fp[361];
  Inven_UsePos = (dInven_UsePos_fp)API.fp[362];
  Inven_GetPos = (dInven_GetPos_fp)API.fp[363];
  Inven_ValidatePos = (dInven_ValidatePos_fp)API.fp[364];
  Inven_IsSelectable = (dInven_IsSelectable_fp)API.fp[365];
  Inven_IsUsable = (dInven_IsUsable_fp)API.fp[366];
  Inven_GetTypeIDCount = (dInven_GetTypeIDCount_fp)API.fp[367];
  Inven_FindPos = (dInven_FindPos_fp)API.fp[368];
  Inven_GetInventoryItemList = (dInven_GetInventoryItemList_fp)API.fp[369];

  // Do variables
  Player_num = (int *)API.vp[0];
  Highest_room_index = (int *)API.vp[1];
  Game_window_w = (int *)API.vp[2];
  Game_window_h = (int *)API.vp[3];
  Game_fonts = (int *)API.vp[4];
  Frametime = (float *)API.vp[5];
  Gametime = (float *)API.vp[6];
  ShieldDelta = (float *)API.vp[7];
  Game_interface_mode = (int *)API.vp[8];
  LocalD3Dir = (char *)API.vp[9];
  m_bTrackerGame = (int *)API.vp[10];
  Local_object_list = (uint16_t *)API.vp[11];
  Server_object_list = (uint16_t *)API.vp[12];
  Dedicated_server = (bool *)API.vp[13];
  Player_colors = (ddgr_color *)API.vp[14];
  Hud_aspect_x = (float *)API.vp[15];
  Hud_aspect_y = (float *)API.vp[16];
  Viewer_object = (object **)API.vp[17];
  Render_zoom = (float *)API.vp[18];
  Game_window_x = (int *)API.vp[19];
  Game_window_y = (int *)API.vp[20];
  Poly_models = (poly_model *)API.vp[21];
  VisEffects = (vis_effect *)API.vp[22];
  Highest_vis_effect_index = (int *)API.vp[23];
  Multi_next_level = (int *)API.vp[24];
  Level_info = (level_info *)API.vp[25];
  GameArgs = (void *)API.vp[26];
  Camera_view_mode = (int *)API.vp[27];
  Object_info = (object_info *)API.vp[28];
}

// DMFCBase::GameInit
//
//   Sets up all the DLL functions and pointers and preps the class for use.  This ABSOLUTLY must be
//   called, so if you override DMFCApp::GameInit, make sure that you put a call to this somewhere in
//   the override.
void DMFCBase::GameInit(int teams) {
  ASSERT(!DMFCInit);
  DMFCInit = true;

  LoadSettings();

  DLLCreateStringTable("dmfc.str", &DMFCStringTable, &DMFCStringTableSize);
  mprintf(0, "DMFC Note: %d strings loaded from string table\n", DMFCStringTableSize);

  // initialize player records
  PRec_Init();

  // initialize remote administration
  Remote_Initialize();

  // see if we should display Outrage logo at all
  if (DLLFindArg("-nooutragelogo"))
    m_bDisplayOutrageLogo = false;
  else
    m_bDisplayOutrageLogo = true;

  SetNumberOfTeams(teams);

  SetTeamName(RED_TEAM, DTXT_RED, false);
  SetTeamName(BLUE_TEAM, DTXT_BLUE, false);
  SetTeamName(GREEN_TEAM, DTXT_GREEN, false);
  SetTeamName(YELLOW_TEAM, DTXT_YELLOW, false);

  // Load bitmaps
  hBitmapObserver = DLLbm_AllocLoadFileBitmap("observer icon.ogf", 0, BITMAP_FORMAT_1555);
  if (hBitmapObserver <= BAD_BITMAP_HANDLE)
    hBitmapObserver = BAD_BITMAP_HANDLE;

  RegisterPacketReceiver(SPID_TEAMASSIGNMENT, &DMFCBase::GetTeamAssignmentPacket);
  RegisterPacketReceiver(SPID_CHANGETEAM, &DMFCBase::GetChangeTeamPacket);
  RegisterPacketReceiver(SPID_REQGAMESTATE, &DMFCBase::GetGameStateRequest);
  RegisterPacketReceiver(SPID_DMFCGAMEINFO, &DMFCBase::GetDMFCGameInfo);
  RegisterPacketReceiver(SPID_VERSIONID, &DMFCBase::GetDMFCVersionCheck);
  RegisterPacketReceiver(SPID_PRECORD, PRec_ReceivePRecFromServer);
  RegisterPacketReceiver(SPID_PRECORDREQ, &DMFCBase::ReceiveRequestForPlayerRecords);
  RegisterPacketReceiver(SPID_CONTROLMSG, &DMFCBase::ReceiveControlMessage);
  RegisterPacketReceiver(SPID_NETGAMESYNC, &DMFCBase::ReceiveNetGameInfoSync);
  RegisterPacketReceiver(SPID_NEWTEAMNAME, &DMFCBase::ReceiveNewTeamName);
  RegisterPacketReceiver(SPID_REMOTEKEY, &DMFCBase::GetRemoteKey);
  RegisterPacketReceiver(SPID_REALGAMETIME, &DMFCBase::GetRealGametimePacket);
  AddWeaponHash("Laser", "Laser Level 1 - Red", "Laser Level 2 - Blue", "Laser Level 3 - Purple",
                "Laser Level 4 - Green", NULL);
  AddWeaponHash("Vauss", "Vauss Spark", NULL);
  AddWeaponHash("Raygun", NULL);
  AddWeaponHash("Plasma", "plasma sparks", NULL);
  AddWeaponHash("Fusion", NULL);
  AddWeaponHash("Super Laser", "Laser Level 5 -Yellow", "Laser Level 6 -White", NULL);
  AddWeaponHash("Mass Driver", NULL);
  AddWeaponHash("Napalm", NULL);
  AddWeaponHash("EMDyellowleft", "EMDyellowright", "EMDgreenleft", "EMDgreenright", NULL);
  AddWeaponHash("Omega", NULL);
  AddWeaponHash("Concussion", NULL);
  AddWeaponHash("Homing", NULL);
  AddWeaponHash("Smart", "SmartPlasmaHomers", NULL);
  AddWeaponHash("Mega", "MegaExplosion", NULL);
  AddWeaponHash("Frag", "frag particles", NULL);
  AddWeaponHash("Guided", NULL);
  AddWeaponHash("NapalmRocket", "NapalmBlob", "NapalmParticles", NULL);
  AddWeaponHash("Cyclone Pack", "Swarm", NULL);
  AddWeaponHash("Yellow Flare", NULL);

  SetWeaponDeathMessage("Laser", DTXT_DEATHLASER, false);
  SetWeaponDeathMessage("Vauss", DTXT_DEATHVAUSS, false);
  SetWeaponDeathMessage("RayGun", DTXT_DEATHMICROWAVE, true);
  SetWeaponDeathMessage("Plasma", DTXT_DEATHPLASMA, false);
  SetWeaponDeathMessage("Fusion", DTXT_DEATHFUSION, false);
  SetWeaponDeathMessage("Super Laser", DTXT_DEATHSUPERLASER, false);
  SetWeaponDeathMessage("Mass Driver", DTXT_DEATHMASS, false);
  SetWeaponDeathMessage("Napalm", DTXT_DEATHNAPALM, false);
  SetWeaponDeathMessage("EMDyellowleft", DTXT_DEATHEMD, false);
  SetWeaponDeathMessage("Omega", DTXT_DEATHOMEGA, false);
  SetWeaponDeathMessage("Concussion", DTXT_DEATHCONC, true);
  SetWeaponDeathMessage("Homing", DTXT_DEATHHOMING, false);
  SetWeaponDeathMessage("Smart", DTXT_DEATHSMART, true);
  SetWeaponDeathMessage("Mega", DTXT_DEATHMEGA, false);
  SetWeaponDeathMessage("Frag", DTXT_DEATHFRAG, true);
  SetWeaponDeathMessage("Guided", DTXT_DEATHGUIDED, false);
  SetWeaponDeathMessage("NapalmRocket", DTXT_DEATHNAPALMROCKET, true);
  SetWeaponDeathMessage("Cyclone Pack", DTXT_DEATHCYCLONE, false);
  SetWeaponDeathMessage("Yellow Flare", DTXT_DEATHFLARE, false);

  /*
  $$TABLE_WEAPON "Laser"
  $$TABLE_WEAPON "Laser Level 1 - Red"
  $$TABLE_WEAPON "Laser Level 2 - Blue"
  $$TABLE_WEAPON "Laser Level 3 - Purple"
  $$TABLE_WEAPON "Laser Level 4 - Green"
  $$TABLE_WEAPON "Vauss"
  $$TABLE_WEAPON "Vauss Spark"
  $$TABLE_WEAPON "Raygun"
  $$TABLE_WEAPON "Plasma"
  $$TABLE_WEAPON "plasma sparks"
  $$TABLE_WEAPON "Fusion"
  $$TABLE_WEAPON "Super Laser"
  $$TABLE_WEAPON "Laser Level 5 -Yellow"
  $$TABLE_WEAPON "Laser Level 6 -White"
  $$TABLE_WEAPON "Mass Driver"
  $$TABLE_WEAPON "Napalm"
  $$TABLE_WEAPON "EMDyellowleft"
  $$TABLE_WEAPON "EMDyellowright"
  $$TABLE_WEAPON "EMDgreenleft"
  $$TABLE_WEAPON "EMDgreenright"
  $$TABLE_WEAPON "Omega"
  $$TABLE_WEAPON "Concussion"
  $$TABLE_WEAPON "Homing"
  $$TABLE_WEAPON "Smart"
  $$TABLE_WEAPON "SmartPlasmaHomers"
  $$TABLE_WEAPON "Mega"
  $$TABLE_WEAPON "MegaExplosion"
  $$TABLE_WEAPON "Frag"
  $$TABLE_WEAPON "frag particles"
  $$TABLE_WEAPON "Guided"
  $$TABLE_WEAPON "NapalmRocket"
  $$TABLE_WEAPON "NapalmBlob"
  $$TABLE_WEAPON "NapalmParticles"
  $$TABLE_WEAPON "Cyclone Pack"
  $$TABLE_WEAPON "Swarm"
  $$TABLE_WEAPON "Yellow Flare"
  */

  // Set up default messages so we have something
  AddDeathMessage(DTXT_KILLED1, true);
  AddSuicideMessage(DTXT_SUICIDE1);

  // Setup InputCommand handlers
  InputCommandInit();

  ParseStartupScript();

  TauntIndicatorBMP = DLLbm_AllocLoadFileBitmap("Taunt.ogf", 0, BITMAP_FORMAT_1555);

  MenuBackgroundBMP = DLLbm_AllocBitmap(32, 32, 0);
  if (MenuBackgroundBMP > BAD_BITMAP_HANDLE) {
    uint16_t *data = DLLbm_data(MenuBackgroundBMP, 0);
    int j;
    for (j = 0; j < 32 * 32; j++)
      data[j] = OPAQUE_FLAG | GR_RGB(0, 0, 0);
  }

  MenuItem *lev1, *lev2, *lev3;

  if (GetLocalRole() == LR_SERVER) {
    // Start Server Commands Menu
    lev1 = new MenuItem(DTXT_MNUSRVRCOMMAND, MIT_NORMAL, 0, NULL); // Server
    lev2 = new MenuItem(DTXT_MNUKICK, MIT_PLIST, 0, KickPlayer);   // |- Kick
    lev1->AddSubMenu(lev2);
    lev2 = new MenuItem(DTXT_MNUBAN, MIT_PLIST, 0, BanPlayer); // |- Ban
    lev1->AddSubMenu(lev2);

    tCustomMenu cm;
    cm.GetItem = GetBannedPlayerString;
    cm.GetListCount = GetBanPlayerList;
    lev2 = new MenuItem(DTXT_OSM_REMOVEBAN, MIT_CUSTOM, 0, RemoveBanByIndex, &cm);
    lev1->AddSubMenu(lev2);

    lev2 = new MenuItem(DTXT_OSM_REHASHLIST, MIT_NORMAL, 0, RehashAllowDenyLists);
    lev1->AddSubMenu(lev2);

    lev2 = new MenuItem(DTXT_MNUENDLEVEL, MIT_NORMAL, 0, EndMultiLevel); // |- End Level
    lev1->AddSubMenu(lev2);
    ServerHUDCallsignsItem = lev2 =
        new MenuItem(DTXT_OSM_MAXHUDNAME, MIT_STATE, 0, SwitchServerHudPlayerName, 3, m_iServerHUDCallsignLevel,
                     DTXT_PLAIN_NONE, DTXT_OSM_TEAMONLY, DTXT_OSM_FULL);
    lev1->AddSubMenu(lev2);
    if (teams > 1) {
      // Team Game Stuff here
      lev2 = new MenuItem(DTXT_MNUTEAMCONTROL, MIT_NORMAL, 0, NULL);     // |- Team Control
      lev3 = new MenuItem(DTXT_MNUBALANCE, MIT_NORMAL, 0, BalanceTeams); //		|- Balance Teams
      lev2->AddSubMenu(lev3);                                            //    |- AutoTeam Balance Off/On
      AutoBalanceItem = lev3 =
          new MenuItem(DTXT_MNUAUTOBALANCE, MIT_STATE, 0, SwitchAutoTeamSelect, 2,
                       (m_iProtectedFlags & DMFC_PRF_AUTOTEAMSELECT) ? 1 : 0, DTXT_MNUOFF, DTXT_MNUON);
      lev2->AddSubMenu(lev3);
      AllowTeamChangeItem = lev3 = new MenuItem(DTXT_MNUALLOWTEAMCNG, MIT_STATE, 0, SwitchTeamChange, 2,
                                                AllowTeamChange() ? 1 : 0, DTXT_MNUOFF, DTXT_MNUON);
      lev2->AddSubMenu(lev3);
      lev3 = new MenuItem(DTXT_OSM_TEAMCONFIG, MIT_NORMAL, 0, OnScreenDisplayTeamConfig);
      lev2->AddSubMenu(lev3);
      lev1->AddSubMenu(lev2);
    }

    Menu.AddSubMenu(lev1);

    // End Server Commands Menu
  }

  if (GetLocalRole() == LR_CLIENT) {
    // Start Client Commands Menu
    bool peertopeer = (bool)((Netgame->flags & NF_PEER_PEER) != 0);
    if (!peertopeer) {
      lev1 = HUDIndicatorItem = new MenuItem(DTXT_LOSSPINGIND, MIT_STATE, 0, SwitchLossPingIndicator, 2,
                                             (LossGuageEnabled) ? 1 : 0, DTXT_MNUOFF, DTXT_MNUON);
      Menu.AddSubMenu(lev1);
    }
    // End Client Commands Menu
  }

  // Do General stuff here
  lev1 = new MenuItem(DTXT_MNUOBSERVER, MIT_NORMAL, 0, NULL);
  ObserverItem = lev2 = new MenuItem(DTXT_MNUOBSERVER, MIT_STATE, 0, SwitchObserverMode, 2, 0, DTXT_MNUOFF, DTXT_MNUON);
  lev1->AddSubMenu(lev2);

  lev2 = new MenuItem(DTXT_OSM_PIGGYBACK, MIT_PLIST, 0, SwitchPiggyBack);
  lev1->AddSubMenu(lev2);
  Menu.AddSubMenu(lev1);

  /*
  lev1 = new MenuItem("OnScreen Menu Options",MIT_NORMAL,0,NULL);
  lev2 = MenuBackgroundItem = new MenuItem("Background",MIT_STATE,0,SwitchMenuBackground,2,0,DTXT_MNUOFF,DTXT_MNUON);
  lev1->AddSubMenu(lev2);
  Menu.AddSubMenu(lev1);
  */
  lev1 = MenuBackgroundItem =
      new MenuItem(DTXT_OSM_BACKGROUND, MIT_STATE, 0, SwitchMenuBackground, 2, 0, DTXT_MNUOFF, DTXT_MNUON);
  Menu.AddSubMenu(lev1);

  HUDCallsignsItem = lev1 =
      new MenuItem(DTXT_OSM_HUDNAMELEVEL, MIT_STATE, 0, SwitchHudPlayerNum, 3, m_iMyPreferredHUDCallsignLevel,
                   DTXT_PLAIN_NONE, DTXT_OSM_TEAMONLY, DTXT_OSM_FULL);
  Menu.AddSubMenu(lev1);

  ShipLogosItem = lev1 = new MenuItem(DTXT_OSM_SHIPLOGOS, MIT_STATE, 0, SwitchShipLogoEnable, 2,
                                      (AreLogosEnabled()) ? 1 : 0, DTXT_MNUOFF, DTXT_MNUON);
  Menu.AddSubMenu(lev1);

  AudioTauntsItem = lev1 = new MenuItem(DTXT_OSM_AUDIOTAUNTS, MIT_STATE, 0, SwitchAudioTauntsEnable, 2,
                                        (AreTauntsEnabled()) ? 1 : 0, DTXT_MNUOFF, DTXT_MNUON);
  Menu.AddSubMenu(lev1);

  // File Statitics menu
  lev1 = new MenuItem(DTXT_OSM_STATSTOFILE, MIT_NORMAL, 0, NULL);

  lev2 = new MenuItem(DTXT_SAVESTATS, MIT_NORMAL, 0, SaveStatsToFile);
  lev1->AddSubMenu(lev2);

  SaveStatsLevelEndItem = lev2 =
      new MenuItem(DTXT_OSM_STATLEVELEND, MIT_STATE, 0, SwitchSaveStatsLevelEnd, 2, 1, DTXT_MNUOFF, DTXT_MNUON);
  lev1->AddSubMenu(lev2);

  SaveStatsDisconnectItem = lev2 =
      new MenuItem(DTXT_OSM_STATDISCONNECT, MIT_STATE, 0, SwitchSaveStatsDisconnect, 2, 1, DTXT_MNUOFF, DTXT_MNUON);
  lev1->AddSubMenu(lev2);

  Menu.AddSubMenu(lev1);

  lev1 = new MenuItem(DTXT_MNUGETPLYRINFO, MIT_PLIST, MIF_INCLUDENONE, SwitchPlayerInfo);
  Menu.AddSubMenu(lev1);

  NetGameInfoItem = lev1 =
      new MenuItem(DTXT_OSM_NETGAMEINFO, MIT_STATE, 0, SwitchNetGameInfo, 2,
                   (m_iProtectedFlags & DMFC_PRF_DISPNETGAMEINFO) ? 1 : 0, DTXT_MNUOFF, DTXT_MNUON);
  Menu.AddSubMenu(lev1);

  lev1 = new MenuItem(DTXT_OSM_HUDFILTER, MIT_NORMAL, 0, NULL);
  lev2 = DeathMessageFilterItem = new MenuItem(DTXT_OSM_KILLMESSAGES, MIT_STATE, 0, SwitchDeathMessageFilter, 3,
                                               m_iDeathMessageFilter, DTXT_PLAIN_NONE, DTXT_OSM_SIMPLE, DTXT_OSM_FULL);
  lev1->AddSubMenu(lev2);

  lev2 = StatisticMessagesItem =
      new MenuItem(DTXT_OSM_STATMESSAGES, MIT_STATE, 0, SwitchStatHUDMessages, 2, 1, DTXT_MNUOFF, DTXT_MNUON);
  lev1->AddSubMenu(lev2);
  Menu.AddSubMenu(lev1);

  if (teams > 1) {
    // Team Game Stuff here
    char names[DLLMAX_TEAMS][MAX_TEAMNAME_LEN];
    strcpy(names[RED_TEAM], GetTeamString(RED_TEAM));
    strcpy(names[BLUE_TEAM], GetTeamString(BLUE_TEAM));
    switch (teams) {
    case 2:
      TeamMenuItem = lev1 = new MenuItem(DTXT_MNUCHANGETEAMS, MIT_STATE, 0, ChangeTeams, 2,
                                         GetPlayerTeam(GetPlayerNum()), names[RED_TEAM], names[BLUE_TEAM]);
      break;
    case 3:
      strcpy(names[GREEN_TEAM], GetTeamString(GREEN_TEAM));
      TeamMenuItem = lev1 =
          new MenuItem(DTXT_MNUCHANGETEAMS, MIT_STATE, 0, ChangeTeams, 3, GetPlayerTeam(GetPlayerNum()),
                       names[RED_TEAM], names[BLUE_TEAM], names[GREEN_TEAM]);
      break;
    case 4:
      strcpy(names[GREEN_TEAM], GetTeamString(GREEN_TEAM));
      strcpy(names[YELLOW_TEAM], GetTeamString(YELLOW_TEAM));
      TeamMenuItem = lev1 =
          new MenuItem(DTXT_MNUCHANGETEAMS, MIT_STATE, 0, ChangeTeams, 4, GetPlayerTeam(GetPlayerNum()),
                       names[RED_TEAM], names[BLUE_TEAM], names[GREEN_TEAM], names[YELLOW_TEAM]);
      break;
    }
    Menu.AddSubMenu(lev1);
  }

  lev1 = new MenuItem(DTXT_OSM_EXIT, MIT_NORMAL, 0, MenuExitMenu);
  Menu.AddSubMenu(lev1);

  // save the hud level var
  int curr_call_level = m_iMyPreferredHUDCallsignLevel;
  m_iMyCurrentHUDCallsignLevel = -1; // set this to -1 so it definitly gets changes/set
  SwitchShowHudCallsignLevel(curr_call_level, false);

  Menu.SetInputFocus();

  EnableShipLogos(true);

  ReadInHostsAllowDeny();

  InitializeForLevel();
}

// DMFCBase::GameClose
//
//   Closes up any needed DLL and DMFC tasks.  This ABSOLUTLY must be called, so if you override
//   DMFCApp::GameClose, make sure that you put a call to this somewhere in the override
void DMFCBase::GameClose(void) {
  ASSERT(DMFCInit);
  DMFCInit = false;

  SaveSettings();

  // Free bitmaps
  if (hBitmapObserver > BAD_BITMAP_HANDLE) {
    DLLbm_FreeBitmap(hBitmapObserver);
    hBitmapObserver = BAD_BITMAP_HANDLE;
  }

  FreeHostsLists();

  // Close up InputCommands
  InputCommandFree();

  // close up and free player records
  PRec_Close();

  if (MenuBackgroundBMP > BAD_BITMAP_HANDLE)
    DLLbm_FreeBitmap(MenuBackgroundBMP);

  DLLDestroyStringTable(DMFCStringTable, DMFCStringTableSize);
}

void DMFCBase::DrawMenu(void) {
  DLLgrtext_SetFont(Game_fonts[HUD_FONT_INDEX]);

  // Menu.Draw(10,100,DLLgrfont_GetHeight(Game_fonts[HUD_FONT_INDEX])+1,this,(m_iProtectedFlags&DMFC_PRF_DISPMENUBACKGR)?MenuBackgroundBMP:-1);
  // Menu.Draw(10,100,DLLRenderHUDGetTextHeight("X")+1,this,(m_iProtectedFlags&DMFC_PRF_DISPMENUBACKGR)?MenuBackgroundBMP:-1);
  int font_height = DLLgrfont_GetHeight(Game_fonts[HUD_FONT_INDEX]);

  Menu.Draw(10, (font_height * 8) + 10, font_height + 1,
            (m_iProtectedFlags & DMFC_PRF_DISPMENUBACKGR) ? MenuBackgroundBMP : -1);
}

char DMFCPlayerInfo[6][100];
void DMFCBase::SetPlayerInfo(int pnum) {
  if (!CheckPlayerNum(pnum)) {
    return;
  }

  m_iPlayerDisplayed = pnum;

  int x, y;
  char buffer[100];
  y = 0;
  x = 15;
  int index = 0;
  bool display_addr = false;

  // Print out callsign
  snprintf(buffer, sizeof(buffer), "%s (%s)", Players[pnum].callsign, (0 == pnum) ? DTXT_SERVER : DTXT_CLIENT);
  strcpy(DMFCPlayerInfo[index], buffer);
  index++;

  if (m_iNumTeams > 1 && Players[pnum].team != -1) {
    snprintf(buffer, sizeof(buffer), DTXT_TEAMFORMAT, GetTeamString(GetPlayerTeam(pnum)));
    strcpy(DMFCPlayerInfo[index], buffer);
    index++;
  }

  // Print out playernum
  snprintf(buffer, sizeof(buffer), DTXT_PLRNUMFORMAT, pnum);
  strcpy(DMFCPlayerInfo[index], buffer);
  index++;

#ifdef _DEBUG
  display_addr = true;
#endif
  if (GetLocalRole() == LR_SERVER)
    display_addr = true;

  // Print out net address
  if (display_addr) {
    network_protocol proto = NP_NONE;

    if (pnum != GetPlayerNum())
      proto = NetPlayers[pnum].addr.connection_type;
    else
      proto = NP_TCP;

    if (proto != NP_NONE) {
      char string[32];
      if (pnum != 0) {
        DLLnw_GetNumbersFromHostAddress(&NetPlayers[pnum].addr, string);
      } else {
        int addr = DLLnw_GetThisIP();
        network_address local_addr;
        memset(&local_addr, 0, sizeof(network_address));
        memcpy(local_addr.address, &addr, sizeof(int));
        local_addr.connection_type = NP_TCP;

        DLLnw_GetNumbersFromHostAddress(&local_addr, string);
      }
      strcpy(DMFCPlayerInfo[index], string);
      index++;
    }
  }

  // Print out Ship info
  snprintf(buffer, sizeof(buffer), DTXT_SHIPFORM, Ships[Players[pnum].ship_index].name);
  strcpy(DMFCPlayerInfo[index], buffer);
  index++;

  for (; index < 5; index++) {
    DMFCPlayerInfo[index][0] = '\0';
  }
}

void DMFCBase::DisplayPlayerInfo(int background_bmp, bool dedicated_server) {
  DLLgrtext_SetFont(Game_fonts[HUD_FONT_INDEX]);

  int height = DLLgrfont_GetHeight(Game_fonts[HUD_FONT_INDEX]) + 1;
  // int height = DLLRenderHUDGetTextHeight("X")+1;
  ddgr_color color = GR_RGB(180, 255, 180);
  int x, y;
  y = 0;
  x = 15;
  int index = 0;

  if (!CheckPlayerNum(m_iPlayerDisplayed)) {
    m_iPlayerDisplayed = -1;
    DISABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_DISPPLAYERINFO);
    return;
  }

  if ((m_iProtectedFlags & DMFC_PRF_DISPMENUBACKGR) && background_bmp > BAD_BITMAP_HANDLE) {
    // draw the background bitmap
    DLLrend_SetAlphaValue(255 * 0.85f);
    DLLrend_SetZBufferState(0);
    DLLrend_SetTextureType(TT_LINEAR);
    DLLrend_SetLighting(LS_NONE);
    DLLrend_SetAlphaType(AT_CONSTANT_TEXTURE);
    DLLrend_DrawScaledBitmap(0, 0, *Game_window_w, 6 * height, background_bmp, 0, 0, 1, 1, 1.0, -1, NULL);
    DLLrend_SetZBufferState(1);
  }

  // Print out callsign
  // DLLRenderHUDText(color,255,0,x,y,DMFCPlayerInfo[index]); y+=height;
  DLLgrtext_SetColor(color);
  DLLgrtext_SetAlpha(255);
  DLLgrtext_Printf(x, y, DMFCPlayerInfo[index]);
  y += height;

  if (dedicated_server)
    DPrintf("%s\n", DMFCPlayerInfo[index]);
  index++;
  color = GR_RGB(40, 255, 40);

  for (; index < 5; index++) {
    // DLLRenderHUDText(color,255,0,x,y,DMFCPlayerInfo[index]);
    DLLgrtext_SetColor(color);
    DLLgrtext_SetAlpha(255);
    DLLgrtext_Printf(x, y, DMFCPlayerInfo[index]);

    if (DMFCPlayerInfo[index][0] != '\0')
      y += height;
    if (dedicated_server)
      DPrintf("%s\n", DMFCPlayerInfo[index]);
  }

  // print out the volatile information
  player_record *pr = PRec_GetPRecordByPnum(m_iPlayerDisplayed);
  char temp[150];

  if (!pr || pr->state != STATE_INGAME)
    return;

  int precindex = translate_precptr_to_index(pr);
  float ti = GetTimeInGame(precindex);

  snprintf(temp, sizeof(temp), DTXT_PI_TIMEINGAME, GetTimeString(ti));
  // DLLRenderHUDText(color,255,0,x,y,temp); y+=height;
  DLLgrtext_SetColor(color);
  DLLgrtext_SetAlpha(255);
  DLLgrtext_Printf(x, y, temp);
  y += height;

  if (dedicated_server)
    DPrintf("%s\n", temp);
}

// DMFCBase::SwitchPlayerInfoDisplay
//
//	Switches on/off displaying a Playerinfo
void DMFCBase::SwitchPlayerInfoDisplay(int pnum) {
  m_iProtectedFlags ^= DMFC_PRF_DISPPLAYERINFO;
  if (m_iProtectedFlags & DMFC_PRF_DISPPLAYERINFO) {

    if (IsDisplayingNetGameInfo()) // if we are displaying netgame info, turn it off
      SwitchNetGameInfoDisplay(0);

    if (!CheckPlayerNum(pnum)) {
      DISABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_DISPPLAYERINFO);
      return;
    }
    SetPlayerInfo(pnum);
  }
}

// DMFCBase::SwitchNetGameInfoDisplay
//
//	Switches on/off displaying netgame info
void DMFCBase::SwitchNetGameInfoDisplay(int on) {
  if (on)
    ENABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_DISPNETGAMEINFO);
  else
    DISABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_DISPNETGAMEINFO);

  if (on) {
    if (DisplayingPlayerInfo() != -1) {
      // turn on displaying of player info before displaying netgame info
      SwitchPlayerInfoDisplay(-1);
    }
  }
}

//	DMFCBase::IsDisplayingNetGameInfo
//
//	returns true if we are currently displaying netgame info
bool DMFCBase::IsDisplayingNetGameInfo(void) { return (m_iProtectedFlags & DMFC_PRF_DISPNETGAMEINFO) ? true : false; }

// DMFCBase::DisplayingPlayerInfo
//
//	Returns the pnum of the player info being displayed, -1 if none
int DMFCBase::DisplayingPlayerInfo(void) {
  if (m_iProtectedFlags & DMFC_PRF_DISPPLAYERINFO)
    return m_iPlayerDisplayed;
  else
    return -1;
}

// DMFCBase::SetTeamName
//
//	Sets the team name for a given team
//	team:	integer value of the team to change
//	name:	new name for the team
//	announce:	if this is true, and we are the server, it will tell all the clients about the change
bool DMFCBase::SetTeamName(int team, const char *name, bool announce) {
  if (team < 0 || team > DLLMAX_TEAMS)
    return false;
  if (!name)
    return false;

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

  strncpy(DMFC_team_names[team], name, MAX_TEAMNAME_LEN - 1);
  DMFC_team_names[team][MAX_TEAMNAME_LEN - 1] = '\0';

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

  if (announce && GetLocalRole() == LR_SERVER) {
    // tell the clients about the new team name
    SendNewTeamName(team);
    DPrintf(DTXT_SETTEAMNAME, old_teamname, name);
  }

  return true;
}

// DMFCBase::GetTeamFromString
//
//		Returns the int value of a team based on a string, -1 if not a team
int DMFCBase::GetTeamFromString(char *str) {
  if (!str)
    return -1;
  for (int i = 0; i < DLLMAX_TEAMS; i++) {
    if (!stricmp(str, DMFC_team_names[i]))
      return i;
  }
  return -1;
}

// DMFCBase::GetPlayerNum
//
//   Returns your playernum, useful when finding out if an event is referring to you (by looking at
//   the playernum passed to the event handler, compared to your playernum
int DMFCBase::GetPlayerNum(void) { return *Player_num; }

// DMFCBase::GetLocalRole
//
//   Returns your role in the multiplayer game.  The result returned will be either LR_SERVER or LR_CLIENT
int DMFCBase::GetLocalRole(void) { return Netgame->local_role; }

// DMFCBase::CheckPlayerNum
//
//    Returns true if the player number passed in is a valid player number (the player is connected)
bool DMFCBase::CheckPlayerNum(int player_num) {
  if ((player_num < 0) || (player_num >= DLLMAX_PLAYERS))
    return false;
  if ((NetPlayers[player_num].flags & NPF_CONNECTED) && (NetPlayers[player_num].sequence >= NETSEQ_PLAYING))
    return true;
  else
    return false;
}

//	DMFCBase::PacketCheckPlayerNum
//
//		Returns true if it's ok to send a packet to this player
bool DMFCBase::PacketCheckPlayerNum(int player_num) {
  if ((player_num < 0) || (player_num >= DLLMAX_PLAYERS))
    return false;
  if ((NetPlayers[player_num].flags & NPF_CONNECTED) && (NetPlayers[player_num].sequence >= NETSEQ_OBJECTS))
    return true;
  else
    return false;
}

// DMFCBase::CallClientEvent
//
//    Server Only.  This will send an event to a client for it to execute.
//    event = An EVT_CLIENT_*
//    me_objnum,it_objnum = Object numbers of the objects to be me and it for the event
//    destination = Player number of the client to send to, 0or -1 if to send to all
void DMFCBase::CallClientEvent(int event, int me_objnum, int it_objnum, int destination, bool parms) {
  if (GetLocalRole() != LR_SERVER)
    return;
  if ((destination < -1) || (destination > DLLMAX_PLAYERS))
    return;
  DLLMultiSendClientExecuteDLL(event, me_objnum, it_objnum, destination, parms ? Data : NULL);
}

// DMFCBase::GetTimeLeft
//
//    Server Only.  This will fill in the float pointer passed to it with how much time is
//    left in the multiplayer game.  The value placed in the float is only valid if GetTimeLeft
//    returns true.  If it returns false it was because either it is not a game with a time limit
//    or it is being called on a client.
bool DMFCBase::GetTimeLeft(float *time) {
  if (GetLocalRole() != LR_SERVER)
    return false;
  if (Netgame->flags & NF_TIMER) {
    *time = (Netgame->timelimit * 60.0f) - RealGametime;
    return true;
  }
  return false;
}

// DMFCBase::EndLevel
//
//    Server Only.  This will end the current multiplayer level, and will go on to the next level.
void DMFCBase::EndLevel(void) {
  if (GetLocalRole() != LR_SERVER)
    return;
  DLLMultiEndLevel();
}

// DMFCBase::GetScoreLimit
//
//   Server Only.  This will fill in the int pointer passed to it with what the scoring limit
//   is, set in in the multiplayer options.  The value point into the int is only valid if
//   GetScoreLimit returns true.  If it returns false it is because you are either not the
//   server or this option wasn't set in the multiplayer options
bool DMFCBase::GetScoreLimit(int *limit) {
  if (GetLocalRole() != LR_SERVER)
    return false;
  if (Netgame->flags & NF_KILLGOAL) {
    *limit = Netgame->killgoal;
    return true;
  }
  return false;
}

// DMFCBase::AutoTimeLimit
//
//    Server Only.  This turns off or on the automatic level ending by DMFC of a timed multiplayer game.
//    If you turn it off, it is your responsibility to end a time multiplayer game when it's time
//    is up.  If it is turned on, DMFC will automatically handle ending the game.  By default it is on.
void DMFCBase::AutoTimeLimit(bool turnon) {
  if (GetLocalRole() != LR_SERVER)
    return;
  if ((Netgame->flags & NF_TIMER) == 0)
    return;
  if (turnon)
    ENABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_AUTOTIMELIMIT);
  else
    DISABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_AUTOTIMELIMIT);
}

// DMFCBase::AutoDeathMessage
//
//    This turns on or off DMFC's automatic handling of death messages.  If it is turned on (Default)
//    then when a player dies it will display a random death message from the list added by you using
//    AddDeathMessage.  If it is turned off, then it is your responsibility to handle the messages, you
//    can use DoRandomDeathMessage to display one when appropriate.
void DMFCBase::AutoDeathMessage(bool turnon) {
  if (turnon)
    ENABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_AUTODEATHMSG);
  else
    DISABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_AUTODEATHMSG);
}

// DMFCBase::AddDeathMessage
//
//    This will add a death message to DMFC.
//    format = string in a "printf" type format (using %s for player callsigns) of the message
//    victim_first = Set this to true if the victim is listed first in the format
void DMFCBase::AddDeathMessage(const char *string, bool victim_first) {
  if ((m_iDeathMsgCount >= 0) && (m_iDeathMsgCount < MAX_DEATH_MSGS)) {
    DeathMsgs[m_iDeathMsgCount].message = (char *)malloc(strlen(string) + 1);
    if (DeathMsgs[m_iDeathMsgCount].message) {
      DeathMsgs[m_iDeathMsgCount].inuse = true;
      DeathMsgs[m_iDeathMsgCount].victim_first = victim_first;
      strcpy(DeathMsgs[m_iDeathMsgCount].message, string);
      m_iDeathMsgCount++;
    }
  }
}

// DMFCBase::AddSuicideMessage
//
//    This will add a death message to DMFC.
//    format = string in a "printf" type format (using %s for player callsigns) of the message
void DMFCBase::AddSuicideMessage(const char *string) {
  if ((m_iSuicideMsgCount >= 0) && (m_iSuicideMsgCount < MAX_DEATH_MSGS)) {
    SuicideMsgs[m_iSuicideMsgCount].message = (char *)malloc(strlen(string) + 1);
    if (SuicideMsgs[m_iSuicideMsgCount].message) {
      SuicideMsgs[m_iSuicideMsgCount].inuse = true;
      strcpy(SuicideMsgs[m_iSuicideMsgCount].message, string);
      m_iSuicideMsgCount++;
    }
  }
}

// DMFCBase::GetItObjNum
//
// Returns the it object number of the current event for use.
int DMFCBase::GetItObjNum(void) {
  if (Data->it_handle == OBJECT_HANDLE_NONE)
    return -1;

  return Data->it_handle & HANDLE_OBJNUM_MASK;
}

// DMFCBase::GetMeObjNum
//
// Returns the me object number of the current event for use.
int DMFCBase::GetMeObjNum(void) {
  if (Data->me_handle == OBJECT_HANDLE_NONE)
    return -1;
  return Data->me_handle & HANDLE_OBJNUM_MASK;
}

// DMFCBase::OnGameStateRequest
//
//		Server only.  Override this to listen for Game state requests from the clients
//    When this function is called a client (who's player number is passed in) is requesting
//    game state information.  Do what you need to do to send game state information to that player
void DMFCBase::OnGameStateRequest(int pnum) {
  if (pnum == -1)
    return;
  mprintf(0, "%s is requesting Game State information\n", Players[pnum].callsign);
}

// DMFCBase::GetTeamForNewPlayer
//
//    A Helper function (Server only), which will give you the optimum team assignment (whoever has the
//    lowest amount of players on their team, for a new player.
//    player_num = player number of the new player
//    num_teams = The number of teams in your game
int DMFCBase::GetTeamForNewPlayer(int player_num, int num_teams) {
  if ((player_num < 0) || (player_num >= DLLMAX_PLAYERS))
    return 0;

  // see if it's a dedicated server game, and if so, are we the server
  if (IAmDedicatedServer() && (player_num == GetPlayerNum())) {
    return -1; // place on the special "no team"
  }

  int i;
  int *Teams;
  Teams = (int *)malloc(sizeof(int) * num_teams);
  if (!Teams)
    return 0;

  for (i = 0; i < num_teams; i++)
    Teams[i] = 0;

  for (i = 0; i < DLLMAX_PLAYERS; i++) {
    if ((i != player_num) && (CheckPlayerNum(i)) && (Players[i].team != -1)) {
      if (Players[i].team < num_teams)
        Teams[Players[i].team]++;
    }
  }

  int low_team, low_count;

  low_count = Teams[0];
  low_team = 0;

  for (i = 0; i < num_teams; i++) {
    if (low_count > Teams[i]) {
      low_count = Teams[i];
      low_team = i;
    }
  }
  free(Teams);
  return low_team;
}

// DMFCBase::SetNumberOfTeams
//
//    Sets the number of teams to be used in the game.  By default there is 1 team (everyone against everyone).
//    You can set up to a maximum of 4 teams.  Call this function as soon as possible.
//    teams = number of teams
void DMFCBase::SetNumberOfTeams(int teams) {
  if ((teams < 1) || (teams > 4))
    return;

  DLLSetMaxTeams(teams);
  DLLMultiPaintGoalRooms(NULL);

  m_iNumTeams = teams;

  char names[DLLMAX_TEAMS][MAX_TEAMNAME_LEN];
  strcpy(names[RED_TEAM], GetTeamString(RED_TEAM));
  strcpy(names[BLUE_TEAM], GetTeamString(BLUE_TEAM));
  if (TeamMenuItem) {
    switch (m_iNumTeams) {
    case 2:
      TeamMenuItem->SetStateItemList(m_iNumTeams, names[RED_TEAM], names[BLUE_TEAM]);
      break;
    case 3:
      strcpy(names[GREEN_TEAM], GetTeamString(GREEN_TEAM));
      TeamMenuItem->SetStateItemList(m_iNumTeams, names[RED_TEAM], names[BLUE_TEAM], names[GREEN_TEAM]);
      break;
    case 4:
      strcpy(names[GREEN_TEAM], GetTeamString(GREEN_TEAM));
      strcpy(names[YELLOW_TEAM], GetTeamString(YELLOW_TEAM));
      TeamMenuItem->SetStateItemList(m_iNumTeams, names[RED_TEAM], names[BLUE_TEAM], names[GREEN_TEAM],
                                     names[YELLOW_TEAM]);
      break;
    }
  }
}

// DMFCBase::AutoTeamSelect
//
//    Turns on or off DMFC's auto team assignment.  If it is on, then when a new player joins, they will
//    be placed on the team with fewest players.  If it is off, then you must handle that.  Defualt on.
void DMFCBase::AutoTeamSelect(bool turnon) {
  if (turnon)
    ENABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_AUTOTEAMSELECT);
  else
    DISABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_AUTOTEAMSELECT);
}

// DMFCBase::RequestChangeTeams
//
//   If you are the server it will make the player change teams.  If you
//   are a client then it sends a request to the server to change teams
void DMFCBase::RequestTeamChange(int team, int pnum, bool spew_on_respawn) {
  if (!CheckPlayerNum(pnum))
    return;
  if (Players[pnum].team == -1) // Dedicated server can't change teams
    return;
  if ((team < 0) || (team >= m_iNumTeams))
    return;
  if (Players[pnum].team == team)
    return;

  if (GetLocalRole() == LR_SERVER) {
    if (!AllowTeamChange()) {
      return;
    }
    if (!CallOnCanChangeTeam(pnum, team)) {
      return;
    }

    Players[pnum].team = team;
    CallOnPlayerChangeTeam(pnum, team, true, spew_on_respawn);
    SendTeamAssignment(pnum, team, spew_on_respawn);
  } else {
    if (pnum != GetPlayerNum())
      return;
    SendChangeTeamRequest(team, spew_on_respawn);
  }
}

// DMFCBase::AddHudItemCallback
//
//    Adds an item to the hud.  Everytime the hud needs to be updated, it will call the
//    handler passed in.  The handler must be declared as:
//	  void func(struct tHUDItem *item);
//    type = HI_BITMAP  for bitmap, HI_TEXT for a text item
//    func = function callback
void DMFCBase::AddHUDItemCallback(int type, void (*func)(struct tHUDItem *)) {
  // add all the needed Hud Items
  tHUDItem item;
  switch (type) {
  case HI_TEXT:
    item.type = HUD_ITEM_CUSTOMTEXT;
    break;
  case HI_BITMAP:
    item.type = HUD_ITEM_CUSTOMIMAGE;
    break;
  default:
    item.type = HUD_ITEM_CUSTOMTEXT;
    break;
  }
  item.stat = STAT_CUSTOM;
  item.flags = HUD_FLAG_PERSISTANT;
  item.data.text = NULL;
  item.render_fn = func;
  item.y = 0;
  item.x = 0;
  DLLAddHUDItem(&item);
}

// DMFCBase::GetMyTeam
//
//    Returns the int value of the team you are on...only useful in a team game.
int DMFCBase::GetMyTeam(void) { return Players[GetPlayerNum()].team; }

// DMFCBase::GetTeamString
//
//     Returns a pointer to a string name of a team
//   team = integer value of the team
const char *DMFCBase::GetTeamString(int team) {
  static char name[20];

  if (team >= 0 && team < DLLMAX_TEAMS)
    strcpy(name, DMFC_team_names[team]);
  else
    strcpy(name, DTXT_LONER);

  return name;
}

// DMFCBase::GetTeamColor
//
//    Returns the color components of a team
//   team = integer value of the team
ddgr_color DMFCBase::GetTeamColor(int team) {
  switch (team) {
  case RED_TEAM:
    return GR_RGB(255, 40, 40);
    break;
  case BLUE_TEAM:
    return GR_RGB(40, 40, 255);
    break;
  case GREEN_TEAM:
    return GR_RGB(40, 255, 40);
    break;
  case YELLOW_TEAM:
    return GR_RGB(255, 255, 40);
    break;
  default:
    return GR_RGB(255, 255, 255);
    break;
  }
}

// DMFCBase::GetNumTeams
//
//   Returns the number of teams in the game
int DMFCBase::GetNumTeams(void) { return m_iNumTeams; }

// DMFCBase::AllowTeamChange
//
//		Returns true if team changing is allowed
bool DMFCBase::AllowTeamChange(void) { return (m_iProtectedFlags & DMFC_PRF_CANTEAMCHANGE) ? true : false; }

// DMFCBase::SwitchAllowTeamChange
//
//		Turns on/off allowing of team changing
void DMFCBase::SwitchAllowTeamChange(bool turnon) {
  if (turnon)
    ENABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_CANTEAMCHANGE);
  else
    DISABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_CANTEAMCHANGE);
}

// DMFCBase::IsMenuUp
//
//	  Returns true if the on screen menu is being shown
bool DMFCBase::IsMenuUp(void) { return (m_iProtectedFlags & DMFC_PRF_INOPTIONS) ? true : false; }

// DMFCBase::ClipString
//
//	   Given a width, the string will be clipped to that width.  If you pass true for arrow, then an arrow will be
//    appended if the string has been clipped.  Based off current font
void DMFCBase::ClipString(int width, char *string, bool arrow) {
  if (!string)
    return;

  // float ratio = DEFAULT_HUD_WIDTH / ((float)*Game_window_w);

  int string_length = strlen(string);
  char arrow_string[2];
  int arrow_length = 0;
  snprintf(arrow_string, sizeof(arrow_string), "%c", CHAR_RIGHT_ARROW);
  if (arrow) {
    // arrow_length = ratio * ((float)DLLRenderHUDGetTextLineWidth(arrow_string));
    arrow_length = DLLgrtext_GetTextLineWidth(arrow_string);
  }

  if (width < arrow_length) {
    // Nothing can fit
    strcpy(string, "");
    return;
  }

  // adjust width based on the arrow
  width -= arrow_length;

  int size = 0;
  char save = string[0];
  while (size < string_length) {
    string[size] = '\0';
    // if((((float)DLLRenderHUDGetTextLineWidth(string))*ratio)>=width){
    if (DLLgrtext_GetTextLineWidth(string) >= width) {
      // We have to clip
      size--;
      if (arrow) {
        string[size] = CHAR_RIGHT_ARROW;
        string[size + 1] = '\0';
      }
      return;
    }
    // replace the char and move to the next
    string[size] = save;
    size++;
    save = string[size];
  }
  // The string didn't need to be clipped
}

// DMFCBase::ConvertHUDAlpha
//
//	  Returns a converted alpha based on what you give, it will be a more transparent if the onscreen menu is up
float DMFCBase::ConvertHUDAlpha(float normal) {
  if (!IsMenuUp())
    return normal;
  return normal * 0.3f;
}
uint8_t DMFCBase::ConvertHUDAlpha(uint8_t normal) {
  if (!IsMenuUp())
    return normal;

  float conv = ((float)normal);
  conv = conv * 0.3f;
  return (uint8_t)conv;
}

bool DMFCBase::DMFC_compare_slots(int a, int b) {
  int ascore, bscore;
  player_record *apr, *bpr;
  apr = GetPlayerRecord(a);
  bpr = GetPlayerRecord(b);
  if (!apr)
    return true;
  if (!bpr)
    return false;
  if (apr->state == STATE_EMPTY)
    return true;
  if (bpr->state == STATE_EMPTY)
    return false;
  if ((apr->state == STATE_INGAME) && (bpr->state == STATE_INGAME)) {
    // both players were in the game
    ascore = apr->dstats.kills[DSTAT_LEVEL] - apr->dstats.suicides[DSTAT_LEVEL];
    bscore = bpr->dstats.kills[DSTAT_LEVEL] - bpr->dstats.suicides[DSTAT_LEVEL];
    return (ascore < bscore);
  }
  if ((apr->state == STATE_INGAME) && (bpr->state == STATE_DISCONNECTED)) {
    // apr gets priority since he was in the game on exit
    return false;
  }
  if ((apr->state == STATE_DISCONNECTED) && (bpr->state == STATE_INGAME)) {
    // bpr gets priority since he was in the game on exit
    return true;
  }
  // if we got here then both players were disconnected
  ascore = apr->dstats.kills[DSTAT_LEVEL] - apr->dstats.suicides[DSTAT_LEVEL];
  bscore = bpr->dstats.kills[DSTAT_LEVEL] - bpr->dstats.suicides[DSTAT_LEVEL];
  return (ascore < bscore);
}

// kills/(kills+deaths+suicides)
bool DMFCBase::DMFC_compare_slots_efficiency(int a, int b) {
  int ascore, bscore;
  player_record *apr, *bpr;
  float atemp, btemp;

  apr = GetPlayerRecord(a);
  bpr = GetPlayerRecord(b);
  if (!apr)
    return true;
  if (!bpr)
    return false;
  if (apr->state == STATE_EMPTY)
    return true;
  if (bpr->state == STATE_EMPTY)
    return false;
  if ((apr->state == STATE_INGAME) && (bpr->state == STATE_INGAME)) {
    // both players were in the game
    atemp =
        (float)(apr->dstats.kills[DSTAT_LEVEL] + apr->dstats.suicides[DSTAT_LEVEL] + apr->dstats.deaths[DSTAT_LEVEL]);
    ascore = (float)(apr->dstats.kills[DSTAT_LEVEL]) / ((atemp) ? atemp : 0.0000001f);

    btemp =
        (float)(bpr->dstats.kills[DSTAT_LEVEL] + bpr->dstats.suicides[DSTAT_LEVEL] + bpr->dstats.deaths[DSTAT_LEVEL]);
    bscore = (float)(bpr->dstats.kills[DSTAT_LEVEL]) / ((btemp) ? btemp : 0.0000001f);
    return (ascore < bscore);
  }
  if ((apr->state == STATE_INGAME) && (bpr->state == STATE_DISCONNECTED)) {
    // apr gets priority since he was in the game on exit
    return false;
  }
  if ((apr->state == STATE_DISCONNECTED) && (bpr->state == STATE_INGAME)) {
    // bpr gets priority since he was in the game on exit
    return true;
  }
  // if we got here then both players were disconnected
  atemp = (float)(apr->dstats.kills[DSTAT_LEVEL] + apr->dstats.suicides[DSTAT_LEVEL] + apr->dstats.deaths[DSTAT_LEVEL]);
  ascore = (float)(apr->dstats.kills[DSTAT_LEVEL]) / ((atemp) ? atemp : 0.0000001f);

  btemp = (float)(bpr->dstats.kills[DSTAT_LEVEL] + bpr->dstats.suicides[DSTAT_LEVEL] + bpr->dstats.deaths[DSTAT_LEVEL]);
  bscore = (float)(bpr->dstats.kills[DSTAT_LEVEL]) / ((btemp) ? btemp : 0.0000001f);
  return (ascore < bscore);
}

// DMFCBase::GetSortedPlayerSlots
//
//  Fills in the passed array (of size maxsize) with the playernums sorted by (kills-suicides)
void DMFCBase::GetSortedPlayerSlots(int *sortedindex, int maxsize) {
  int t;
  int i, j;
  int tempsort[MAX_PLAYER_RECORDS];

  // copy scores into scoreinfo array
  for (i = 0; i < MAX_PLAYER_RECORDS; i++) {
    tempsort[i] = i;
  }

  for (i = 1; i <= MAX_PLAYER_RECORDS - 1; i++) {
    t = tempsort[i];

    // Shift elements down until
    // insertion point found.
    for (j = i - 1; j >= 0 && DMFC_compare_slots(tempsort[j], t); j--) {
      tempsort[j + 1] = tempsort[j];
    }

    // insert
    tempsort[j + 1] = t;
  }

  if (maxsize < MAX_PLAYER_RECORDS)
    memcpy(sortedindex, tempsort, maxsize * sizeof(int));
  else
    memcpy(sortedindex, tempsort, MAX_PLAYER_RECORDS * sizeof(int));
}

//	DMFCBase::GetSortedPlayerSlotsByEfficiency
//
//	Fills in the passed array (of size maxsize) with the playernums sorted by kills/(kills+deaths+suicides)
void DMFCBase::GetSortedPlayerSlotsByEfficiency(int *sortedindex, int maxsize) {
  int t;
  int i, j;
  int tempsort[MAX_PLAYER_RECORDS];

  // copy scores into scoreinfo array
  for (i = 0; i < MAX_PLAYER_RECORDS; i++) {
    tempsort[i] = i;
  }

  for (i = 1; i <= MAX_PLAYER_RECORDS - 1; i++) {
    t = tempsort[i];

    // Shift elements down until
    // insertion point found.
    for (j = i - 1; j >= 0 && DMFC_compare_slots_efficiency(tempsort[j], t); j--) {
      tempsort[j + 1] = tempsort[j];
    }

    // insert
    tempsort[j + 1] = t;
  }

  if (maxsize < MAX_PLAYER_RECORDS)
    memcpy(sortedindex, tempsort, maxsize * sizeof(int));
  else
    memcpy(sortedindex, tempsort, MAX_PLAYER_RECORDS * sizeof(int));
}

// DMFCBase::DisplayOutrageLogo(void)
//
//   Displays the Outrage Logo for 5 seconds (call repeatedly, after 5 seconds it will just short circuit)
//	Doesn't do anything for non-Outrage games
void DMFCBase::DisplayOutrageLogo(void) {
  if (!m_bDisplayOutrageLogo)
    return;

#ifdef OUTRAGE_VERSION
  static bool done = false;
  static float time = 0;
  static bool loadflag = true;
  static chunked_bitmap bmp;

  if (done)
    return;

  if (loadflag) {
    // We need to load the handle
    loadflag = false;
    int temp = DLLbm_AllocLoadFileBitmap("Outlogo.ogf", 0, BITMAP_FORMAT_1555);
    DLLbm_CreateChunkedBitmap(temp, &bmp);
    DLLbm_FreeBitmap(temp);
    time = 0;
  }

  // draw the Outrage Logo if its time
  if (time < 5.0) {
    int x, y;
    x = (*Game_window_w) - bmp.pw;
    y = (*Game_window_h) - bmp.ph;
    DLLrend_DrawScaledChunkedBitmap(&bmp, x, y, bmp.pw, bmp.ph, 128);
    time += (*Frametime);
  } else {
    // we are done
    done = true;
    DLLbm_DestroyChunkedBitmap(&bmp);
  }
#endif
}

// DMFCBase::SwitchShowHudCallsignLevel
//
//
//   Sets the level for displaying of Player's Callsigns on the HUD
void DMFCBase::SwitchShowHudCallsignLevel(uint8_t level, bool announce) {
  m_iMyPreferredHUDCallsignLevel = level;

  if (level > m_iServerHUDCallsignLevel)
    level = m_iServerHUDCallsignLevel;

  if (m_iMyCurrentHUDCallsignLevel == level)
    return;

  m_iMyCurrentHUDCallsignLevel = level;

  switch (level) {
  case HUD_CALLSIGN_LEVEL_FULL:
    DLLPlayerSetHUDNameFOV(10);
    if (announce)
      DLLAddHUDMessage(DTXT_HUDLEVEL_FULL);
    break;
  case HUD_CALLSIGN_LEVEL_TEAM:
    DLLPlayerSetHUDNameFOV(10);
    if (announce)
      DLLAddHUDMessage(DTXT_HUDLEVEL_TEAM);
    break;
  case HUD_CALLSIGN_LEVEL_NONE:
    DLLPlayerSetHUDNameFOV(-1);
    if (announce)
      DLLAddHUDMessage(DTXT_HUDLEVEL_NONE);
    break;
  default:
    mprintf(0, "DMFC: Invalid HUD Name Level\n");
    return;
    break;
  };
}

// DMFCBase::SwitchServerHudCallsignLevel
//
//
//	Sets the max level of HUD callsign displayage...determined by the server
void DMFCBase::SwitchServerHudCallsignLevel(uint8_t level) {
  switch (level) {
  case HUD_CALLSIGN_LEVEL_FULL:
    m_iServerHUDCallsignLevel = level;
    DLLAddHUDMessage(DTXT_SHUDLEVEL_FULL);
    break;
  case HUD_CALLSIGN_LEVEL_TEAM:
    m_iServerHUDCallsignLevel = level;
    DLLAddHUDMessage(DTXT_SHUDLEVEL_TEAM);
    break;
  case HUD_CALLSIGN_LEVEL_NONE:
    m_iServerHUDCallsignLevel = level;
    DLLAddHUDMessage(DTXT_SHUDLEVEL_NONE);
    break;
  default:
    mprintf(0, "DMFC: Invalid Server HUD Name Level\n");
    return;
    break;
  }

  // make sure we're not set higher than the server allows
  SwitchShowHudCallsignLevel(m_iMyPreferredHUDCallsignLevel);

  if (GetLocalRole() == LR_SERVER)
    SendDMFCGameInfo(SP_ALL);
}

// DMFCBase::ShouldIDisplayHUDName
//
//	Given a player num, it will determine if the callsign should be drawn on the HUD
bool DMFCBase::ShouldIDisplayHUDName(int pnum) {
  switch (m_iMyCurrentHUDCallsignLevel) {
  case HUD_CALLSIGN_LEVEL_FULL:
    return true;
    break;
  case HUD_CALLSIGN_LEVEL_TEAM: {
    // see if this is a team game
    if (GetNumTeams() >= 2) {
      // ok it's a team game, see if this player is on my team, if so, than disply
      if (Players[pnum].team == GetMyTeam()) {
        return true;
      } else {
        return false;
      }
    }
    return false;
  } break;
  case HUD_CALLSIGN_LEVEL_NONE:
    return false;
    break;
  }
  return false;
}

// DMFCBase::GetCounterMeasureOwner
//
//
//	Given a counter measure it will determine the pnum of it's owner...if it can't find it, it returns -1
int DMFCBase::GetCounterMeasureOwner(object *robot) {
  if (!robot)
    return -1;

  if (robot->type != OBJ_ROBOT && (!(robot->type == OBJ_BUILDING && robot->ai_info)))
    return -1;

  object *killer;
  DLLGetUltimateParentForObject(&killer, robot);

  if (killer == robot)
    return -1;

  if ((killer->type != OBJ_PLAYER) && (killer->type != OBJ_OBSERVER))
    return -1;

  return killer->id;
}

// DMFCBase::EncryptData
//
//	Encrypts (weak) a buffer of data
void DMFCBase::EncryptData(uint8_t *data, int size) {
  if (!data)
    return;
  if (size <= 0)
    return;

  int offset = size;

  for (int i = 0; i < size; i++) {
    data[i] = data[i] + i - (offset + i);
    offset++;
  }
}

// DMFCBase::DecryptData
//
// Decrypts a buffer of data
void DMFCBase::DecryptData(uint8_t *data, int size) {
  if (!data)
    return;
  if (size <= 0)
    return;

  int offset = size;

  for (int i = 0; i < size; i++) {
    data[i] = data[i] - i + (offset + i);
    offset++;
  }
}

void DMFCBase::VersionCheck(int pnum) { SendVersionToClient(pnum); }

// DMFCBase::FindPInfo
//
//	Given a player number it will search the list and find that player, returning the pointer, or NULL if it
//	can't be found or created
PInfo *DMFCBase::FindPInfo(int pnum) {
  ASSERT((pnum >= 0) && (pnum < DLLMAX_PLAYERS));
  player_record *pr = GetPlayerRecordByPnum(pnum);
  if (!pr)
    return NULL;
  return pr->pinfo;
}

// DMFCBase::UpdatePInfo
//
//
//	Updates a victim's pinfo stat (pass in player nums)
void DMFCBase::UpdatePInfo(int victim, int killer, int amount) {
  ASSERT((victim >= 0) || (victim < DLLMAX_PLAYERS) || (killer >= 0) || (killer < DLLMAX_PLAYERS));
  if ((victim < 0) || (victim >= DLLMAX_PLAYERS) || (killer < 0) || (killer >= DLLMAX_PLAYERS)) {

    return;
  }

  PInfo *id;
  int victim_slot = PRec_GetPlayerSlot(victim);
  tPKillerInfo ki;

  memset(&ki, 0, sizeof(tPKillerInfo));
  id = FindPInfo(killer);

  if ((id == NULL) || (victim_slot == -1)) {
    mprintf(0, "Unable to find PInfos\n");
    return;
  }

  // update killer
  ki.kills = amount;
  id->Update(victim_slot, &ki);

  PInfo *kpi, *vpi;
  kpi = id;
  vpi = FindPInfo(victim);
  int killer_slot = PRec_GetPlayerSlot(killer);

  if ((vpi == NULL) || (killer_slot == -1)) {
    mprintf(0, "Unable to find PInfos\n");
    return;
  }

  if (victim != killer) {
    // regular kill
    kpi->HandleKill(victim_slot);
    vpi->HandleDeath(killer_slot);
  } else {
    // suicide
    kpi->HandleSuicide();
  }
}

// DMFCBase::ResetPInfo
//
//
//	Resets all the PInfo stats (and frees memory)
void DMFCBase::ResetPInfo(void) {
  for (int i = 0; i < MAX_PLAYER_RECORDS; i++) {
    player_record *pr = GetPlayerRecord(i);
    if (pr && pr->pinfo) {
      pr->pinfo->ResetAll();
    }
  }
}

// DMFCBase::WriteDMFCStatsToFile
//
//
//	This function will write out DMFC's stats to the file given (it must be opened for writing)
void DMFCBase::WriteDMFCStatsToFile(CFILE *file) {
#define BUFSIZE 150
  ASSERT(file != NULL);
  char buffer[BUFSIZE];
  player_record *pr, *dpr;
  tPInfoStat stat;
  int count, length;

  count = 1;

  for (int p = 0; p < MAX_PLAYER_RECORDS; p++) {
    pr = GetPlayerRecord(p);
    if (pr && pr->state != STATE_EMPTY) {
      // Write out header
      snprintf(buffer, sizeof(buffer), "%d) %s%s", count, (pr->state == STATE_INGAME) ? "" : "*", pr->callsign);
      DLLcf_WriteString(file, buffer);
      length = strlen(buffer);
      memset(buffer, '=', length);
      buffer[length] = '\0';
      DLLcf_WriteString(file, buffer);

      if (FindPInfoStatFirst(p, &stat)) {
        char tempbuffer[25];
        snprintf(buffer, sizeof(buffer), "%s", DTXT_DMFC_STAT_HEADER);
        DLLcf_WriteString(file, buffer);
        memset(buffer, ' ', BUFSIZE);
        dpr = GetPlayerRecord(stat.slot);

        snprintf(tempbuffer, sizeof(tempbuffer), "%s", dpr->callsign);
        memcpy(buffer, tempbuffer, strlen(tempbuffer));

        snprintf(tempbuffer, sizeof(tempbuffer), "%d", stat.kills);
        memcpy(&buffer[30], tempbuffer, strlen(tempbuffer));

        snprintf(tempbuffer, sizeof(tempbuffer), "%d", stat.deaths);
        memcpy(&buffer[40], tempbuffer, strlen(tempbuffer));

        int pos;
        pos = 40 + strlen(tempbuffer) + 1;
        if (pos < BUFSIZE)
          buffer[pos] = '\0';

        buffer[BUFSIZE - 1] = '\0';
        DLLcf_WriteString(file, buffer);

        while (FindPInfoStatNext(&stat)) {
          memset(buffer, ' ', BUFSIZE);
          dpr = GetPlayerRecord(stat.slot);

          snprintf(tempbuffer, sizeof(tempbuffer), "%s", dpr->callsign);
          memcpy(buffer, tempbuffer, strlen(tempbuffer));

          snprintf(tempbuffer, sizeof(tempbuffer), "%d", stat.kills);
          memcpy(&buffer[30], tempbuffer, strlen(tempbuffer));

          snprintf(tempbuffer, sizeof(tempbuffer), "%d", stat.deaths);
          memcpy(&buffer[40], tempbuffer, strlen(tempbuffer));

          int pos;
          pos = 40 + strlen(tempbuffer) + 1;
          if (pos < BUFSIZE)
            buffer[pos] = '\0';

          buffer[BUFSIZE - 1] = '\0';
          DLLcf_WriteString(file, buffer);
        }
      }
      FindPInfoStatClose();
      DLLcf_WriteString(file, ""); // skip a line
      count++;
    }
  }
}

tPInfoStat _CurrentPInfoStat;
int _CurrentPInfoSlot = -1;
bool _DoneWithKills = false;
bool _SlotsUsed[MAX_PLAYER_RECORDS];

// DMFCBase::FindPInfoStatFirst
//
//	Fills in a tPInfoStat that contains information about a given player record slot. Returns true on success, false
//	if there is no information available for the given slot. Call this first, then follow with repeated calls to
//	FindPInfoStatNext until you get a false value returned...call FindPInfoStatClose when done.
bool DMFCBase::FindPInfoStatFirst(int slot, tPInfoStat *stat) {
  for (int i = 0; i < MAX_PLAYER_RECORDS; i++)
    _SlotsUsed[i] = false;

  if (slot < 0 || slot >= MAX_PLAYER_RECORDS)
    return false;

  if (!stat)
    return false;

  FindPInfoStatClose();

  player_record *pr, *dpr;
  tPKillerInfo *killer, *victim;
  int deaths;

  _SlotsUsed[slot] = true;
  _CurrentPInfoSlot = slot;
  memset(&_CurrentPInfoStat, 0, sizeof(tPInfoStat));

  // return the given slot
  pr = GetPlayerRecord(slot);
  if (!pr || pr->state == STATE_EMPTY || !pr->pinfo)
    return false;

  killer = pr->pinfo->GetFirstKiller();
  victim = NULL;

  if (!killer) {
    _DoneWithKills = true;
    // go through the slots looking for deaths
    for (int p = 0; p < MAX_PLAYER_RECORDS; p++) {
      if (!_SlotsUsed[p]) {
        _SlotsUsed[p] = true;
        dpr = GetPlayerRecord(p);
        deaths = 0;
        if (dpr && dpr->state != STATE_EMPTY && dpr->pinfo) {
          victim = ((PInfo *)dpr->pinfo)->GetKillerInfo(slot);
          if (victim)
            deaths = victim->kills;
        }
        if (deaths > 0) {
          _CurrentPInfoStat.slot = p;
          _CurrentPInfoStat.kills = 0;
          _CurrentPInfoStat.deaths = deaths;
          memcpy(stat, &_CurrentPInfoStat, sizeof(tPInfoStat));
          return true;
        }
      }
    }
    return false;
  }

  _CurrentPInfoStat.slot = killer->slot;
  _CurrentPInfoStat.kills = killer->kills;

  deaths = 0;
  dpr = GetPlayerRecord(killer->slot);
  if (dpr && dpr->state != STATE_EMPTY && dpr->pinfo) {
    victim = ((PInfo *)dpr->pinfo)->GetKillerInfo(slot);
  }

  if (victim)
    deaths = victim->kills;

  _CurrentPInfoStat.deaths = deaths;
  _SlotsUsed[killer->slot] = true;

  memcpy(stat, &_CurrentPInfoStat, sizeof(tPInfoStat));
  return true;
}

// DMFCBase::FindPInfoStatNext
//
//	Call this repeatedly until you get a value of false, finish by calling FindPInfoStatClose
bool DMFCBase::FindPInfoStatNext(tPInfoStat *stat) {
  if (_CurrentPInfoSlot == -1)
    return false;
  if (!stat)
    return false;

  int slot;
  player_record *pr, *dpr;
  slot = _CurrentPInfoSlot;
  tPKillerInfo *killer, *victim;
  int deaths = 0;
  memset(&_CurrentPInfoStat, 0, sizeof(tPInfoStat));

  // return the given slot
  pr = GetPlayerRecord(slot);
  if (!pr || pr->state == STATE_EMPTY || !pr->pinfo)
    return false;

  killer = pr->pinfo->GetNextKiller();
  victim = NULL;

  if (!killer) {
    _DoneWithKills = true;
    // go through the slots looking for deaths
    for (int p = 0; p < MAX_PLAYER_RECORDS; p++) {
      if (!_SlotsUsed[p]) {
        _SlotsUsed[p] = true;
        dpr = GetPlayerRecord(p);
        deaths = 0;
        if (dpr && dpr->state != STATE_EMPTY && dpr->pinfo) {
          victim = ((PInfo *)dpr->pinfo)->GetKillerInfo(slot);
          if (victim)
            deaths = victim->kills;
        }
        if (deaths > 0) {
          _CurrentPInfoStat.slot = p;
          _CurrentPInfoStat.kills = 0;
          _CurrentPInfoStat.deaths = deaths;
          memcpy(stat, &_CurrentPInfoStat, sizeof(tPInfoStat));
          return true;
        }
      }
    }
    return false;
  }

  _CurrentPInfoStat.slot = killer->slot;
  _CurrentPInfoStat.kills = killer->kills;

  deaths = 0;
  dpr = GetPlayerRecord(killer->slot);
  if (dpr && dpr->state != STATE_EMPTY && dpr->pinfo) {
    victim = dpr->pinfo->GetKillerInfo(slot);
  }

  if (victim)
    deaths = victim->kills;

  _CurrentPInfoStat.deaths = deaths;
  _SlotsUsed[killer->slot] = true;

  memcpy(stat, &_CurrentPInfoStat, sizeof(tPInfoStat));
  return true;
}

// DMFCBase::FindPInfoStatClose
//
//	Closes up a FindPInfo series of calls
void DMFCBase::FindPInfoStatClose(void) {
  _CurrentPInfoSlot = -1;
  _DoneWithKills = false;
}

// DMFCBase::SetWeaponDeathMessage
//
//	Sets a death message for a weapon kill
bool DMFCBase::SetWeaponDeathMessage(const char *weapon_name, const char *message, bool victim_first) {
  ASSERT(weapon_name != NULL);
  ASSERT(message != NULL);

  int weapon_index = DLLFindWeaponName(IGNORE_TABLE(weapon_name));
  if (weapon_index == -1) {
    mprintf(0, "Unable to set WeaponMessage for %s...can't find it\n", weapon_name);
    return false;
  }

  int real_weapon = WeaponHash[weapon_index];

  if (real_weapon == -1) {
    mprintf(0, "You forgot to call AddWeaponHash before adding this Message\n");
    return false;
  }

  if ((WeaponMessages[real_weapon].inuse) && (WeaponMessages[real_weapon].message)) {
    free(WeaponMessages[real_weapon].message);
    WeaponMessages[real_weapon].message = NULL;
  }

  WeaponMessages[real_weapon].message = (char *)malloc(strlen(message) + 1);
  if (WeaponMessages[real_weapon].message) {
    strcpy(WeaponMessages[real_weapon].message, message);
    WeaponMessages[real_weapon].inuse = true;
    WeaponMessages[real_weapon].victim_first = victim_first;
  }

  return WeaponMessages[real_weapon].inuse;
}

// DMFCBase::GetWeaponDeathMessage
//
//  Returns the format string for a weapon death message, NULL if it doesn't exist
char *DMFCBase::GetWeaponDeathMessage(int index, bool *victim_first) {
  if ((index < 0) || (index >= MAX_WEAPONS))
    return NULL;

  int real_weapon;

  real_weapon = WeaponHash[index];

  if (real_weapon == -1)
    return NULL;

  if (!WeaponMessages[real_weapon].inuse)
    return NULL;

  *victim_first = WeaponMessages[real_weapon].victim_first;
  return WeaponMessages[real_weapon].message;
}

// DMFCBase::AddWeaponHash
//
//	Since one weapon may actually consist of many weapons, in order to save space you can create
//  one weapon where all those other weapon id's will be mapped to it...use WeaponHash[id] to
//  get the actual weapon.  End list of children with a NULL
void DMFCBase::AddWeaponHash(const char *parent, ...) {
  ASSERT(parent != NULL);

  int parent_id = DLLFindWeaponName(IGNORE_TABLE(parent));
  if (parent_id == -1) {
    mprintf(0, "Unable to find parent weapon ID in AddWeaponHash (%s)\n", parent);
    return;
  }

  va_list marker;
  va_start(marker, parent);

  WeaponHash[parent_id] = parent_id;

  bool done = false;

  while (!done) {
    int id;
    char *name;
    name = va_arg(marker, char *);
    if (name)
      id = DLLFindWeaponName(IGNORE_TABLE(name));
    else
      id = -1;

    if (id == -1) {
      done = true;
    } else {
      if ((id < 0) || (id >= MAX_WEAPONS))
        done = true;
      else {
        WeaponHash[id] = parent_id;
      }
    }
  }

  va_end(marker);
}

// DMFCBase::AddWeaponHashArray
//
//	Since one weapon may actually consist of many weapons, in order to save space you can create
//  one weapon where all those other weapon id's will be mapped to it...use WeaponHash[id] to
//  get the actual weapon.
void DMFCBase::AddWeaponHashArray(const char *parent, int count, char **array) {
  ASSERT(parent != NULL);

  int parent_id = DLLFindWeaponName(IGNORE_TABLE(parent));
  if (parent_id == -1) {
    mprintf(0, "Unable to find parent weapon ID in AddWeaponHash (%s)\n", parent);
    return;
  }

  WeaponHash[parent_id] = parent_id;

  int index = 0;
  while (index < count) {
    int id;
    char *name;
    name = array[index++];
    if (name)
      id = DLLFindWeaponName(IGNORE_TABLE(name));
    else
      break;

    if (id >= 0 && id < MAX_WEAPONS) {
      WeaponHash[id] = parent_id;
    }
  }
}

// DMFCBase::SetupPlayerRecord
//
//	Sets up memory for multiplayer mod user defined data in the Player Records. Pass in the size (in bytes)
//	of 1 struct (each player will get one).  Use GetPlayerRecordData to retrieve a pointer to the data.
//
//	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)
int DMFCBase::SetupPlayerRecord(int sizeof_individual_data, int (*pack_callback)(void *user_info, uint8_t *data),
                                int (*unpack_callback)(void *user_info, uint8_t *data)) {
  return PRec_SetupUserPRec(sizeof_individual_data, pack_callback, unpack_callback);
}

// DMFCBase::GetPlayerRecordData
//
//	Returns a pointer to the memory set aside for a particular player (NULL on error)
void *DMFCBase::GetPlayerRecordData(int pnum) {
  int slot;
  slot = PRec_GetPlayerSlot(pnum);
  if (slot == -1)
    return NULL;
  return PRec_GetUserStats(slot);
}

// DMFCBase::GetPlayerRecord
//
//	Returns a pointer to the player record at the given slot (passed in slot must be in the range
// 0<=slot<MAX_PLAYER_RECORDS)
player_record *DMFCBase::GetPlayerRecord(int slot) { return PRec_GetPRecord(slot); }

// DMFCBase::GetPlayerRecordByPnum
//
//	Same as above, but you can specify by a player's pnum, return NULL on error
player_record *DMFCBase::GetPlayerRecordByPnum(int pnum) {
  int slot;
  slot = PRec_GetPlayerSlot(pnum);
  if (slot == -1)
    return NULL;
  return PRec_GetPRecord(slot);
}

// DMFCBase::BanPlayerFromGame
//
//   Puts a temp ban on a player which will only last the duration of the game
void DMFCBase::BanPlayerFromGame(int pnum) {
  if (pnum >= 0 && pnum < DLLMAX_PLAYERS) {
    mprintf(0, "Banning %s\n", Players[pnum].callsign);
    DPrintf(DTXT_DEDS_BAN, Players[pnum].callsign);
    tBanItem *c;
    c = m_BanList;
    if (!m_BanList) {
      c = m_BanList = (tBanItem *)malloc(sizeof(tBanItem));
      if (!c)
        return;
    } else {
      while (c->next) {
        c = c->next;
      }
      c->next = (tBanItem *)malloc(sizeof(tBanItem));
      c = c->next;
      if (!c)
        return;
    }
    c->next = NULL;
    memcpy(&c->addr, &NetPlayers[pnum].addr, sizeof(network_address));
    strncpy(c->callsign, Players[pnum].callsign, MAX_CALLSIGN_SIZE - 1);
    c->callsign[MAX_CALLSIGN_SIZE - 1] = '\0';
    c->tracker_id[0] = '\0';

    if (IsMasterTrackerGame()) {
      strcpy(c->tracker_id, Players[pnum].tracker_id);
    }

    m_iNumBanPlayers++;
  } else {
    mprintf(0, "Unable to ban player...pnum not valid\n");
    DPrintf(DTXT_DEDS_BAN_ERROR);
  }
}

// DMFCBase::RemoveAllBans
//
//	Removes all the temp bans
void DMFCBase::RemoveAllBans(void) {
  tBanItem *c, *n;
  c = n = m_BanList;
  while (c) {
    n = c->next;
    free(c);
    c = n;
  }
  m_BanList = NULL;
  m_iNumBanPlayers = 0;
}

// DMFCBase::GetNumBannedPlayers
//
//	Returns the number of players banned from the server
int DMFCBase::GetNumBannedPlayers(void) { return m_iNumBanPlayers; }

// DMCBase::GetBannedPlayerCallsign
//
//	Returns the callsign of the banned player at position index, NULL on error
char *DMFCBase::GetBannedPlayerCallsign(int index) {
  if (index < 0 || index >= m_iNumBanPlayers)
    return NULL;

  tBanItem *c;
  c = m_BanList;
  while (c) {
    if (index == 0) {
      return c->callsign;
    }
    index--;
    c = c->next;
  }
  return NULL;
}

// DMFCBase::RemoveBan
//
//	Removes a temp ban on given the ban #...returns true on success
bool DMFCBase::RemoveBan(int index) {
  if (index < 0 || index >= m_iNumBanPlayers)
    return false;

  tBanItem *c, *p;
  c = m_BanList;
  p = NULL;
  while (c) {
    if (!index) {
      // we found em, remove em
      if (p) {
        p->next = c->next;
        free(c);
        m_iNumBanPlayers--;
      } else {
        m_BanList = c->next;
        free(c);
        m_iNumBanPlayers--;
      }
      return true;
    }
    p = c;
    c = c->next;
    index--;
  }
  return false;
}

// DMFCBase::IsPlayerBanned
//
//	returns true is the given pnum is banned from the game
bool DMFCBase::IsPlayerBanned(int pnum) {
  mprintf(0, "Checking a ban on a player...");

  if (pnum < 0 || pnum >= DLLMAX_PLAYERS) {
    mprintf(0, "Playernum not valid\n");
    return false;
  }

  if (IsAddressBanned(&NetPlayers[pnum].addr, Players[pnum].tracker_id)) {
    return true;
  }

  return false;
}

// DMFCBase::IsAddressBanned
//
//	returns true if the given address is banned from the game
bool DMFCBase::IsAddressBanned(network_address *addr, const char *tracker_id) {
  tBanItem *c;
  c = m_BanList;

  mprintf(0, "Checking a ban on an address...");

  while (c) {
    if (addr && CompareNetworkAddress(&c->addr, addr, false)) {
      mprintf(0, "Player addr IS BANNED\n");
      return true;
    }
    if (*m_bTrackerGame) {
      if (tracker_id && !strcmp(tracker_id, c->tracker_id)) {
        mprintf(0, "Player tid IS BANNED\n");
        return true;
      }
    }

    c = c->next;
  }

  if (!addr) {
    mprintf(0, "Player not banned\n");
    return false;
  }

  // ok, the player doesn't have a temp ban on him, now, if it's tcp/ip, check
  // the hosts.allow/.deny

  if (addr->connection_type != NP_TCP) {
    mprintf(0, "Player not banned\n");
    return false;
  }

  uint32_t address;
  tHostsNode *curr;

  memcpy(&address, &addr->address, 4);

  // check the allow list, if the address is specified on this list,
  // than it isn't banned
  curr = m_AllowList;
  while (curr) {
    if ((address & curr->mask) == (curr->ip & curr->mask)) {
      // its a match
      mprintf(0, "Player not banned\n");
      return false;
    }
    curr = curr->next;
  }

  // check the deny list, if the address is specified on this list,
  // than it is banned
  curr = m_DenyList;
  while (curr) {
    if ((address & curr->mask) == (curr->ip & curr->mask)) {
      // its a match
      mprintf(0, "Player IS banned\n");
      return true;
    }
    curr = curr->next;
  }

  mprintf(0, "Player not banned\n");
  return false;
}

// DMFCBase::DoDamageToPlayer
//
//	Server only...applies damage to player
void DMFCBase::DoDamageToPlayer(int pnum, int type, float amount, bool playsound) {
  if (GetLocalRole() != LR_SERVER)
    return;
  if (!CheckPlayerNum(pnum))
    return;
  if (pnum == GetPlayerNum()) {
    // apply it to myself
    if (amount <= 0) {
      // just add it...no damage sound
      Objects[Players[pnum].objnum].shields -= amount;
    } else {
      DLLApplyDamageToPlayer(&Objects[Players[pnum].objnum], NULL, type, amount, 1, 255, playsound);
    }
  } else {
    // add it to additional damage
    DLLApplyDamageToPlayer(&Objects[Players[pnum].objnum], NULL, type, amount, 1, 255, playsound);
  }
}

// DMFCBase::StartUIWindow
//
//	Prepares the game so that you can display UI stuff
void DMFCBase::StartUIWindow(int id, void *user_data) {
  if (m_iUIWindowID != -1) {
    mprintf(0, "DMFCBase::StartUIWindow(): A Window ID (0x%X) is already defined\n", m_iUIWindowID);
    return;
  }

  *Game_interface_mode = GAME_DLL_INTERFACE;
  m_iUIWindowID = id;
  m_UIUserData = user_data;
}

//	DMFCBase::EnableStatisticalMessages
//
//	Turns on or off the statistical HUD messages that appear due to a player death
void DMFCBase::EnableStatisticalMessages(bool on) {
  if (on)
    ENABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_DISPSTATHUDMSGS);
  else
    DISABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_DISPSTATHUDMSGS);
}

//	DMFCBase::GetTimeInGame
//
//	Returns the totaltime the player has been in the game at the moment you call
//	this function.  Pass in the player record slot.
float DMFCBase::GetTimeInGame(int slot) {
  if (slot < 0 || slot >= MAX_PLAYER_RECORDS)
    return 0;

  player_record *pr = PRec_GetPRecord(slot);

  if (!pr || pr->state == STATE_EMPTY)
    return 0;

  float ti = pr->total_time_in_game;

  if (pr->state == STATE_INGAME)
    ti += RealGametime - pr->start_time;

  return ti;
}

//	DMFCBase::GetTimeString
//
//	Returns a string that contains a formated time (i.e. 32 seconds, 1:15 minutes, 3:21:23 hours)
char *DMFCBase::GetTimeString(float sec) {
  static char buffer[20];
  int minutes, seconds, hours;
  minutes = seconds = hours = 0;

  seconds = (int)sec;

  // we have seconds, so convert to minutes:seconds
  minutes = seconds / 60;
  seconds = seconds % 60;

  // now we have minutes and seconds, so convert minutes to hours:minutes
  hours = minutes / 60;
  minutes = minutes % 60;

  // now form our string hh:mm:ss
  if (hours) {
    if (hours == 1 && minutes == 0 && seconds == 0)
      snprintf(buffer, sizeof(buffer), "%s", DTXT_TIME_HOUR);
    else
      snprintf(buffer, sizeof(buffer), DTXT_TIME_HOURS, hours, minutes, seconds);
  } else if (minutes) {
    if (minutes == 1 && seconds == 0)
      snprintf(buffer, sizeof(buffer), "%s", DTXT_TIME_MINUTE);
    else
      snprintf(buffer, sizeof(buffer), DTXT_TIME_MINUTES, minutes, seconds);
  } else {
    if (seconds == 1)
      snprintf(buffer, sizeof(buffer), "%s", DTXT_TIME_SECOND);
    else
      snprintf(buffer, sizeof(buffer), DTXT_TIME_SECONDS, seconds);
  }
  return buffer;
}

// DMFCBase::PauseRealGameTime
//
//	Pauses/unpauses the realgame time
void DMFCBase::PauseRealGameTime(bool enable) {
  if (GetLocalRole() != LR_SERVER)
    return;

  if (enable) {
    ENABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_PAUSETIME);
    SendControlMessageToPlayer(SP_ALL, CM_PAUSETIME);
  } else {
    DISABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_PAUSETIME);
    SendControlMessageToPlayer(SP_ALL, CM_UNPAUSETIME);
  }
}

/*
 ********************************************************************************
 *																				*
 *						Input Command Functions
 **
 *																				*
 ********************************************************************************
 *
 */

// DMFCBase::InputCommandHandle
//
//	Goes through the list of input commands and calls the associated handle (if it exists).  Returns
//	true if it called a handler, else false.
bool DMFCBase::InputCommandHandle(char *command_string) {
  ASSERT(command_string != NULL);

  // make sure it's a valid command (starts with a $)
  if (command_string[0] != '$')
    return false;

  /*
  if(!stricmp(command_string,"$help dump"))
  {
          mprintf(0,"Dumping help commands to file\n");
          tInputCommandNode *node = m_InputCommandRootNode;

          CFILE *file;
          DLLOpenCFILE(&file,"C:\\DMFCHelp.txt","wt");

          if(!file)
                  return false;

          char buffer[1024];

          while(node){

                  sprintf(buffer,"$%s",node->data);
                  DLLcf_WriteString(file,buffer);
                  DLLcf_WriteString(file,"-------------------------------------------");
                  sprintf(buffer,"%s\n",node->desc);
                  DLLcf_WriteString(file,buffer);
                  node = node->next;
          }

          DLLcfclose(file);
  }
  */

  // now extract the command
  char command[64], index = 0;
  char *p = &command_string[1];

  // lowercase the command_string
  while (*p && *p != ' ') {
    if ((*p >= 'A') && (*p <= 'Z')) {
      *p = tolower(*p);
    }
    p++;
  }

  p = &command_string[1];

  // break off the first string
  while ((*p) && (*p != ' ')) {
    ASSERT(index < 64);
    command[index] = *p;
    p++;
    index++;
  }
  command[index] = '\0'; // tack on an ending NULL

  // now go through our list and see if we can find the command added by the user
  tInputCommandNode *node = m_InputCommandRootNode;
  while (node) {
    if (!stricmp(node->data, command)) {
      // we found a match!
      if (node->handler) {
        (*node->handler)(command_string);
        return true;
      }
      return false; // for some reason the handler is NULL (it should never happen)
    }
    node = node->next;
  }

  // if we got here than no nodes match
  return false;
}

// DMFCBase::InputCommandFree
//
//	Frees up all memory allocated for the input commands ($ messages)
void DMFCBase::InputCommandFree(void) {
  tInputCommandNode *next, *node = m_InputCommandRootNode;

  // Destroy the linked list of nodes
  while (node) {
    next = node->next;
    if (node->data)
      free(node->data);
    if (node->desc)
      free(node->desc);
    free(node);
    node = next;
  }

  // set the root to NULL to finish up
  m_InputCommandRootNode = NULL;
}

// DMFCBase::InputCommandInit
//
//	Initializes the variables and data for the input commands.  Default DMFC commands are to
//	be placed in here.
void DMFCBase::InputCommandInit(void) {
  if (AddInputCommand(DTXT_IC_ALLOWTEAMCHANGE, DTXT_IC_ALLOWTEAMCHANGED, DMFCInputCommand_AllowTeamChange, true) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_AUTOBALANCE, DTXT_IC_AUTOBALANCED, DMFCInputCommand_AutoBalance, true) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_AUTOSAVEDISC, DTXT_IC_AUTOSAVEDISCD, DMFCInputCommand_AutoSaveDisconnect, true) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_AUTOSAVELEVEL, DTXT_IC_AUTOSAVELEVELD, DMFCInputCommand_AutoSaveLevel, true) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_BALANCE, DTXT_IC_BALANCED, DMFCInputCommand_Balance, true) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_BAN, DTXT_IC_BAND, DMFCInputCommand_Ban, true) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_BANLIST, DTXT_IC_BANLISTD, DMFCInputCommand_BanList) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_CHANGETEAM, DTXT_IC_CHANGETEAMD, DMFCInputCommand_ChangeTeam, true) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_ENDLEVEL, DTXT_IC_ENDLEVELD, DMFCInputCommand_EndLevel, true) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_HELP, DTXT_IC_HELPD, DMFCInputCommand_Help) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_HUDNAME, DTXT_IC_HUDNAMED, DMFCInputCommand_HudCallsigns) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_KICK, DTXT_IC_KICKD, DMFCInputCommand_Kick, true) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_KILLMSGFILTER, DTXT_IC_KILLMSGFILTERD, DMFCInputCommand_KillMsgFilter) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_NETGAMEINFO, DTXT_IC_NETGAMEINFO, DMFCInputCommand_NetGameInfo) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_OBSERVER, DTXT_IC_OBSERVERD, DMFCInputCommand_Observer) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_PIGGYBACK, DTXT_IC_PIGGYBACKD, DMFCInputCommand_Piggyback) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_PLAYERINFO, DTXT_IC_PLAYERINFOD, DMFCInputCommand_PlayerInfo) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_PLAYERS, DTXT_IC_PLAYERSD, DMFCInputCommand_Players) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_REHASH, DTXT_IC_REHASHD, DMFCInputCommand_Rehash, true) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_REMOTE, DTXT_IC_REMOTED, DMFCInputCommand_Remote) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_REMOTEADMIN, DTXT_IC_REMOTEADMIND, DMFCInputCommand_RemoteAdmin, true) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_REMOTEADMINLOGOUT, DTXT_IC_REMOTEADMINLOGOUTD, DMFCInputCommand_RemoteAdminLogout) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_REMOTEADMINPASS, DTXT_IC_REMOTEADMINPASSD, DMFCInputCommand_RemoteAdminPass, true) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_REMOVEBAN, DTXT_IC_REMOVEBAND, DMFCInputCommand_RemoveBan, true) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_SAVESTATS, DTXT_IC_SAVESTATSD, DMFCInputCommand_SaveStats, true) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_SCORES, DTXT_IC_SCORESD, DMFCInputCommand_Scores) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_SERVERHUDNAMES, DTXT_IC_SERVERHUDNAMESD, DMFCInputCommand_ServerHudCallsigns, true) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_SETGOALLIMIT, DTXT_IC_SETGOALLIMITD, DMFCInputCommand_SetGoalLimit, true) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_SETMAXPLAYERS, DTXT_IC_SETMAXPLAYERSD, DMFCInputCommand_SetMaxPlayers, true) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_SETPPS, DTXT_IC_SETPPSD, DMFCInputCommand_SetPPS, true) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_SETRESPAWNTIME, DTXT_IC_SETRESPAWNTIMED, DMFCInputCommand_SetRespawnTime, true) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_SETTEAMNAME, DTXT_IC_SETTEAMNAMED, DMFCInputCommand_SetTeamName, true) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_SETTIMELIMIT, DTXT_IC_SETTIMELIMITD, DMFCInputCommand_SetTimeLimit, true) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_STATMSGS, DTXT_IC_STATMSGSD, DMFCInputCommand_StatMsgs) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_TEAM, DTXT_IC_TEAMD, DMFCInputCommand_Team) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_WAIT, DTXT_IC_WAITD, DMFCInputCommand_Wait, true) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");

  if (AddInputCommand(DTXT_IC_WARP, DTXT_IC_WARPD, DMFCInputCommand_Warp, true) < 1)
    mprintf(0, "DMFC Warning: Error Adding Input Command\n");
}

// Returns true if the input command can be called remotely
//
// Returns 1 if the input command can be called remotely
// Returns -1 if the input command is not found
// Returns 0 if the input command can be called remotely
int DMFCBase::CanInputCommandBeUsedRemotely(char *command) {
  tInputCommandNode *node = m_InputCommandRootNode;

  while (node) {
    if (!stricmp(node->data, command)) {
      // we found a match!
      return (node->allow_remote) ? 1 : 0;
    }
    node = node->next;
  }

  return -1;
}

//	Displays dedicated server help
void DMFCBase::DisplayInputCommandHelp(const char *s) {
  if (!IAmDedicatedServer())
    return;

  tInputCommandNode *node = m_InputCommandRootNode;

  if (s[0] == '\0') {
    char buffer[80];
    DPrintf(DTXT_IC_HEADEERLIST);
    DPrintf(DTXT_IC_HEADERINSTRUCT);
    // just display the list of commands
    while (node) {
      memset(buffer, ' ', 80);
      memcpy(&buffer[0], node->data, strlen(node->data));
      node = node->next;
      if (node) {
        memcpy(&buffer[30], node->data, strlen(node->data));
        int pos = 30 + strlen(node->data);
        if (pos <= 78) {
          buffer[pos] = '\n';
          buffer[pos + 1] = '\0';
        }
      }
      buffer[78] = '\n';
      buffer[79] = '\0';

      DPrintf(buffer);

      if (node)
        node = node->next;
    }

    return;
  }

  // display help description
  while (node) {
    if (!stricmp(node->data, s)) {
      // we found a match!
      DPrintf("%s:\n", node->data);
      if (node->desc)
        DPrintf("%s\n", node->desc);
      else
        DPrintf(DTXT_IC_NOADDITIONALINFO);
      return;
    }
    node = node->next;
  }

  DPrintf(DTXT_IC_COMMANDNOTFOUND);
}

// copies a string from src to dest, making dest all lower case
void strlowcpy(char *dest, const char *src) {
  ASSERT((dest != NULL) && (src != NULL));
  char *s, *d;
  s = (char *)src;
  d = dest;

  while (*s) {
    *d = tolower(*s);
    s++;
    d++;
  }
  // do the final NULL
  *d = '\0';
}

// DMFCBase::AddInputCommand
//
//	When the client (or dedicated server) types a message that begins with a $, DMFC will look through
//	all the values you passed to AddInputCommand() and see if any match.  If so it passes the entire
//	command string to the given function handler. Returns 1 on success, -1 if out of memory, 0 if it already
//	exists. These commands are not case sensitive.
//	Ex. AddInputCommand("team");	//this handles all the '$team' passed in
int8_t DMFCBase::AddInputCommand(const char *command, const char *description, void (*handler)(const char *), bool allow_remotely) {
  ASSERT(command != NULL);
  ASSERT(handler != NULL);

  // traverse to the end of the list checking for duplicates along the way
  tInputCommandNode *node = m_InputCommandRootNode;

  if (!node) {
    // there is none currently in the list, add it now
    node = (tInputCommandNode *)malloc(sizeof(tInputCommandNode));
    if (!node) {
      return -1;
    }
    // fill in data
    node->next = NULL;
    node->handler = handler;
    node->allow_remote = allow_remotely;
    node->data = (char *)malloc(strlen(command) + 1);
    if (!node->data) { // out of memory
      free(node);
      return -1;
    }
    strlowcpy(node->data, command);

    node->desc = (char *)malloc(strlen(description) + 1);
    if (node->desc)
      strcpy(node->desc, description);

    // all went ok
    m_InputCommandRootNode = node;
    return 1;
  }

  while (node->next) {
    // check this node to make sure it's not a duplicate
    if (!stricmp(node->data, command)) {
      // we found a duplicate
      return 0;
    }
    node = node->next;
  }

  // we need to check the current node since while(node->next) breaks out before checking the last node
  if (!stricmp(node->data, command)) {
    // we found a duplicate
    return 0;
  }

  // it looks like everything went ok, now just create a new node and tack it on to the end
  // of the list.  All that can go wrong now is an out of memory error.
  tInputCommandNode *temp;
  temp = (tInputCommandNode *)malloc(sizeof(tInputCommandNode));
  if (!temp) { // out of memory
    return -1;
  }
  temp->next = NULL;
  temp->handler = handler;
  temp->allow_remote = allow_remotely;
  temp->data = (char *)malloc(strlen(command) + 1);
  if (!temp->data) { // out of memory
    free(temp);
    return -1;
  }
  strlowcpy(temp->data, command);

  temp->desc = (char *)malloc(strlen(description) + 1);
  if (temp->desc)
    strcpy(temp->desc, description);

  // now tack it on the end of the list and thats all
  node->next = temp;
  return 1;
}

//	DMFCBase::DisconnectMe
//
//	Disconnects yourself from the game.  It should be called when you are kicked or banned to make the
//	whole process a bit nicer/cleaner.  Even if it doesn't get called when kicked, you will still
//	eventually disconnect.
void DMFCBase::DisconnectMe(void) { Netgame->flags |= NF_EXIT_NOW; }

//	Does a check on on the pinfo info making sure it is valid
void DMFCBase::CheckPInfo() {
  for (int slot = 0; slot < MAX_PLAYER_RECORDS; slot++) {
    player_record *pr = GetPlayerRecord(slot);
    if (pr && pr->state != STATE_EMPTY) {
      // now go through the victim list and check
      tPKillerInfo *node;

      ASSERT(pr->pinfo != NULL);
      node = pr->pinfo->GetFirstKiller();

      while (node) {
        ASSERT(node->slot >= 0 && node->slot < 64);
        node = node->next;
      }
    }
  }
}

//	DMFCBase::EnableOnScreenMenu
//
// Turns on/off the onscreen menu
void DMFCBase::EnableOnScreenMenu(bool enable) {
  if (enable)
    ENABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_INOPTIONS);
  else
    DISABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_INOPTIONS);
}

//	DMFCBase::EnableAutoSaveLevelEnd
//
//	Disables/Enables the autosaving of game stats to file on level end
void DMFCBase::EnableAutoSaveLevelEnd(bool enable) {
  if (enable)
    ENABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_AUTOSAVELEVELEND);
  else
    DISABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_AUTOSAVELEVELEND);
}

//	DMFCBase::EnableAutoSaveDisconnect
//
//	Disables/Enables the autosaving of game stats to file on disconnect from the game
void DMFCBase::EnableAutoSaveDisconnect(bool enable) {
  if (enable)
    ENABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_AUTOSAVEDISC);
  else
    DISABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_AUTOSAVEDISC);
}

//	DMFCBase::GenerateStatFilename
//
//	Given the following information it will return a full path to what
//	the recommended filename to save stats to should be.
//	root = Multiplayer DLL Name (filename will start with this)
//	end_of_level = pass true if this is the end of a level stats
void DMFCBase::GenerateStatFilename(char *filename, const char *root, bool end_of_level) {
  int level = Current_mission->cur_level;
  char *name = Netgame->name;
  struct tm *newtime;
  time_t long_time;

  time(&long_time);
  newtime = localtime(&long_time);

  char fname[256];
  char timestr[100];
  char day[8], month[8];

  switch (newtime->tm_wday) {
  case 0:
    strcpy(day, DTXT_SUNDAY);
    break;
  case 1:
    strcpy(day, DTXT_MONDAY);
    break;
  case 2:
    strcpy(day, DTXT_TUESDAY);
    break;
  case 3:
    strcpy(day, DTXT_WEDNESDAY);
    break;
  case 4:
    strcpy(day, DTXT_THURSDAY);
    break;
  case 5:
    strcpy(day, DTXT_FRIDAY);
    break;
  case 6:
    strcpy(day, DTXT_SATURDAY);
    break;
  }

  switch (newtime->tm_mon) {
  case 0:
    strcpy(month, DTXT_JANUARY);
    break;
  case 1:
    strcpy(month, DTXT_FEBRUARY);
    break;
  case 2:
    strcpy(month, DTXT_MARCH);
    break;
  case 3:
    strcpy(month, DTXT_APRIL);
    break;
  case 4:
    strcpy(month, DTXT_MAY);
    break;
  case 5:
    strcpy(month, DTXT_JUNE);
    break;
  case 6:
    strcpy(month, DTXT_JULY);
    break;
  case 7:
    strcpy(month, DTXT_AUGUST);
    break;
  case 8:
    strcpy(month, DTXT_SEPTEMBER);
    break;
  case 9:
    strcpy(month, DTXT_OCTOBER);
    break;
  case 10:
    strcpy(month, DTXT_NOVEMBER);
    break;
  case 11:
    strcpy(month, DTXT_DECEMBER);
    break;
  }

  snprintf(timestr, sizeof(timestr), "%s._%s._%d_%d_%02d%02d", day, month, newtime->tm_mday, newtime->tm_year + 1900,
           newtime->tm_hour, newtime->tm_min);
  snprintf(fname, sizeof(fname), "%s_%s_%d_%s%s.stats", root, name, level, (end_of_level) ? DTXT_ENDOFLEVELCONCAT : "",
           timestr);

  // remove all spaces (convert to _)
  char *p = fname;
  while ((*p)) {
    if (*p == ' ')
      *p = '_';
    p++;
  }

  // build the path info here
  DLLddio_MakePath(filename, LocalD3Dir, "netgames", fname, NULL);
}

//	DMFCBase::IsPlayerObserver
//
//	Returns true if the given pnum is currently an observer in the game, else returns false.
//	If an invalid pnum is given, then it returns false
bool DMFCBase::IsPlayerObserver(int pnum) {
  if (!CheckPlayerNum(pnum))
    return false;

  if (Objects[Players[pnum].objnum].type == OBJ_OBSERVER)
    return true;

  return false;
}

//	DMFCBase::EnableOnScreenMenuBackground
//
//	Enables/disables the onscreen menu background
void DMFCBase::EnableOnScreenMenuBackground(bool enable) {
  if (enable)
    ENABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_DISPMENUBACKGR);
  else
    DISABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_DISPMENUBACKGR);
}

//	DMFCBase::DisplayNetGameInfo
//
//	Displays information about the Netgame that is currently being played
//	on to the screen.
void DMFCBase::DisplayNetGameInfo(int background_bmp, bool dedicated_server) {
  // char server_config_name[PAGENAME_LEN];
  // char connection_name[PAGENAME_LEN];
  DLLgrtext_SetFont(Game_fonts[HUD_FONT_INDEX]);

  int height = DLLgrfont_GetHeight(Game_fonts[HUD_FONT_INDEX]) + 1;
  // int height = DLLRenderHUDGetTextHeight("X")+1;
  ddgr_color color = GR_RGB(180, 255, 180);
  int x, y;
  y = 0;
  x = 15;
  int w, maxx = 0;
  char fullbuffer[1024];

  int topy = 0;
  bool peertopeer = (bool)((Netgame->flags & NF_PEER_PEER) != 0);
  bool permissable = (bool)((Netgame->flags & NF_PERMISSABLE) != 0);

  if ((m_iProtectedFlags & DMFC_PRF_DISPMENUBACKGR) && background_bmp > BAD_BITMAP_HANDLE) {
    // draw the background bitmap
    DLLrend_SetAlphaValue(255 * 0.85f);
    DLLrend_SetZBufferState(0);
    DLLrend_SetTextureType(TT_LINEAR);
    DLLrend_SetLighting(LS_NONE);
    DLLrend_SetAlphaType(AT_CONSTANT_TEXTURE);
    DLLrend_DrawScaledBitmap(0, 0, *Game_window_w, 8 * height, background_bmp, 0, 0, 1, 1, 1.0, -1, NULL);
    DLLrend_SetZBufferState(1);
  }

  // Print out Title
  // DLLRenderHUDText(color,255,0,x,y,DTXT_NGI_HEADING); y+=height;
  DLLgrtext_SetColor(color);
  DLLgrtext_SetAlpha(255);
  snprintf(fullbuffer, sizeof(fullbuffer), "%s", DTXT_NGI_HEADING);
  DLLgrtext_Printf(x, y, fullbuffer);
  w = DLLgrtext_GetTextLineWidth(fullbuffer);
  maxx = (w > maxx) ? w : maxx;
  y += height;

  if (dedicated_server)
    DPrintf(DTXT_NGI_HEADING);
  color = GR_RGB(0, 255, 0);
  topy = y; // save this position

  // Print out Game name
  // DLLRenderHUDText(color,255,0,x,y,DTXT_NGI_GAMENAME,Netgame->name); y+=height;
  DLLgrtext_SetColor(color);
  DLLgrtext_SetAlpha(255);
  snprintf(fullbuffer, sizeof(fullbuffer), DTXT_NGI_GAMENAME, Netgame->name);
  DLLgrtext_Printf(x, y, fullbuffer);
  w = DLLgrtext_GetTextLineWidth(fullbuffer);
  maxx = (w > maxx) ? w : maxx;
  y += height;

  if (dedicated_server)
    DPrintf(DTXT_NGI_GAMENAME, Netgame->name);

  // Print out Mission name
  // DLLRenderHUDText(color,255,0,x,y,DTXT_NGI_MISSIONNAME,Netgame->mission); y+=height;
  DLLgrtext_SetColor(color);
  DLLgrtext_SetAlpha(255);
  snprintf(fullbuffer, sizeof(fullbuffer), DTXT_NGI_MISSIONNAME, Netgame->mission);
  DLLgrtext_Printf(x, y, fullbuffer);
  w = DLLgrtext_GetTextLineWidth(fullbuffer);
  maxx = (w > maxx) ? w : maxx;
  y += height;

  if (dedicated_server)
    DPrintf(DTXT_NGI_MISSIONNAME, Netgame->mission);

  // Print out Script name
  // DLLRenderHUDText(color,255,0,x,y,DTXT_NGI_SCRIPTNAME,Netgame->scriptname); y+=height;
  DLLgrtext_SetColor(color);
  DLLgrtext_SetAlpha(255);
  snprintf(fullbuffer, sizeof(fullbuffer), DTXT_NGI_SCRIPTNAME, Netgame->scriptname);
  DLLgrtext_Printf(x, y, fullbuffer);
  w = DLLgrtext_GetTextLineWidth(fullbuffer);
  maxx = (w > maxx) ? w : maxx;
  y += height;

  if (dedicated_server)
    DPrintf(DTXT_NGI_SCRIPTNAME, Netgame->scriptname);

  // Print out PPS
  if (GetLocalRole() == LR_SERVER) {
    // DLLRenderHUDText(color,255,0,x,y,DTXT_NGI_PPS,Netgame->packets_per_second); y+=height;
    DLLgrtext_SetColor(color);
    DLLgrtext_SetAlpha(255);
    snprintf(fullbuffer, sizeof(fullbuffer), DTXT_NGI_PPS, Netgame->packets_per_second);
    DLLgrtext_Printf(x, y, fullbuffer);
    w = DLLgrtext_GetTextLineWidth(fullbuffer);
    maxx = (w > maxx) ? w : maxx;
    y += height;

    if (dedicated_server)
      DPrintf(DTXT_NGI_PPS, Netgame->packets_per_second);
  } else {
    // DLLRenderHUDText(color,255,0,x,y,DTXT_NGI_PPSSERVER,Netgame->packets_per_second,NetPlayers[GetPlayerNum()].pps);
    // y+=height;
    DLLgrtext_SetColor(color);
    DLLgrtext_SetAlpha(255);
    snprintf(fullbuffer, sizeof(fullbuffer), DTXT_NGI_PPSSERVER, Netgame->packets_per_second,
             NetPlayers[GetPlayerNum()].pps);
    DLLgrtext_Printf(x, y, fullbuffer);
    w = DLLgrtext_GetTextLineWidth(fullbuffer);
    maxx = (w > maxx) ? w : maxx;
    y += height;

    if (dedicated_server)
      DPrintf(DTXT_NGI_PPSSERVER, Netgame->packets_per_second, NetPlayers[GetPlayerNum()].pps);
  }

  // Print out Max Players
  // DLLRenderHUDText(color,255,0,x,y,DTXT_NGI_MAXPLAYERS,Netgame->max_players); y+=height;
  DLLgrtext_SetColor(color);
  DLLgrtext_SetAlpha(255);
  snprintf(fullbuffer, sizeof(fullbuffer), DTXT_NGI_MAXPLAYERS, Netgame->max_players);
  DLLgrtext_Printf(x, y, fullbuffer);
  w = DLLgrtext_GetTextLineWidth(fullbuffer);
  maxx = (w > maxx) ? w : maxx;
  y += height;

  if (dedicated_server)
    DPrintf(DTXT_NGI_MAXPLAYERS, Netgame->max_players);

  // Accurate Collision Detection
  bool use_acc_weap = (bool)((Netgame->flags & NF_USE_ACC_WEAP) != 0);
  // DLLRenderHUDText(color,255,0,x,y,DTXT_NGI_ACCURATECOLL,(use_acc_weap)?DTXT_MNUON:DTXT_MNUOFF); y+=height;
  DLLgrtext_SetColor(color);
  DLLgrtext_SetAlpha(255);
  snprintf(fullbuffer, sizeof(fullbuffer), DTXT_NGI_ACCURATECOLL, (use_acc_weap) ? DTXT_MNUON : DTXT_MNUOFF);
  DLLgrtext_Printf(x, y, fullbuffer);
  w = DLLgrtext_GetTextLineWidth(fullbuffer);
  maxx = (w > maxx) ? w : maxx;
  y += height;

  if (dedicated_server)
    DPrintf(DTXT_NGI_ACCURATECOLL, (use_acc_weap) ? DTXT_MNUON : DTXT_MNUOFF);

  // move to second column
  y = topy;
  x += (maxx + 10);

  // Rotatitional Velocity
  bool rot_vel_sent = (bool)((Netgame->flags & NF_SENDROTVEL) != 0);
  // DLLRenderHUDText(color,255,0,x,y,DTXT_NGI_SENDROTATIONAL,(rot_vel_sent)?DTXT_MNUON:DTXT_MNUOFF); y+=height;
  DLLgrtext_SetColor(color);
  DLLgrtext_SetAlpha(255);
  snprintf(fullbuffer, sizeof(fullbuffer), DTXT_NGI_SENDROTATIONAL, (rot_vel_sent) ? DTXT_MNUON : DTXT_MNUOFF);
  DLLgrtext_Printf(x, y, fullbuffer);
  w = DLLgrtext_GetTextLineWidth(fullbuffer);
  y += height;

  if (dedicated_server)
    DPrintf(DTXT_NGI_SENDROTATIONAL, (rot_vel_sent) ? DTXT_MNUON : DTXT_MNUOFF);

  // Print out time limit
  if (Netgame->flags & NF_TIMER) {
    DLLgrtext_SetColor(color);
    DLLgrtext_SetAlpha(255);
    DLLgrtext_Printf(x, y, DTXT_NGI_TIMELIMIT, Netgame->timelimit,
                     (Netgame->timelimit == 1) ? DTXT_MINUTE : DTXT_MINUTES);
    y += height;

    if (dedicated_server)
      DPrintf(DTXT_NGI_TIMELIMIT, Netgame->timelimit, (Netgame->timelimit == 1) ? DTXT_MINUTE : DTXT_MINUTES);

    float t_left = 0;
    t_left = (Netgame->timelimit * 60.0f) - RealGametime;

    // Print out time left
    DLLgrtext_SetColor(color);
    DLLgrtext_SetAlpha(255);
    DLLgrtext_Printf(x, y, DTXT_NGI_TIMELEFT, GetTimeString(t_left));
    y += height;

    if (dedicated_server)
      DPrintf(DTXT_NGI_TIMELEFT, GetTimeString(t_left));
  } else {
    DLLgrtext_SetColor(color);
    DLLgrtext_SetAlpha(255);
    DLLgrtext_Printf(x, y, DTXT_NGI_NOTIMELIMIT);
    y += height;

    if (dedicated_server)
      DPrintf(DTXT_NGI_NOTIMELIMIT);
  }

  // Print out Goal
  if (Netgame->killgoal > 0) {
    // DLLRenderHUDText(color,255,0,x,y,DTXT_NGI_GOALLIMIT,Netgame->killgoal); y+=height;
    DLLgrtext_SetColor(color);
    DLLgrtext_SetAlpha(255);
    DLLgrtext_Printf(x, y, DTXT_NGI_GOALLIMIT, Netgame->killgoal);
    y += height;

    if (dedicated_server)
      DPrintf(DTXT_NGI_GOALLIMIT, Netgame->killgoal);
  } else {
    // DLLRenderHUDText(color,255,0,x,y,DTXT_NGI_NOGOAL); y+=height;
    DLLgrtext_SetColor(color);
    DLLgrtext_SetAlpha(255);
    DLLgrtext_Printf(x, y, DTXT_NGI_NOGOAL);
    y += height;

    if (dedicated_server)
      DPrintf(DTXT_NGI_NOGOAL);
  }

  // Print out Respawn time
  // DLLRenderHUDText(color,255,0,x,y,DTXT_NGI_RESPAWNTIME,GetTimeString(Netgame->respawn_time)); y+=height;
  DLLgrtext_SetColor(color);
  DLLgrtext_SetAlpha(255);
  DLLgrtext_Printf(x, y, DTXT_NGI_RESPAWNTIME, GetTimeString(Netgame->respawn_time));
  y += height;

  if (dedicated_server)
    DPrintf(DTXT_NGI_RESPAWNTIME, GetTimeString(Netgame->respawn_time));

  // Print out Client/Server Packet Loss
  if ((GetPlayerNum() == 0) || peertopeer) {
    // packet loss info not available
    // DLLRenderHUDText(color,255,0,x,y,DTXT_NGI_PACKETLOSSNA); y+= height;
    DLLgrtext_SetColor(color);
    DLLgrtext_SetAlpha(255);
    DLLgrtext_Printf(x, y, DTXT_NGI_PACKETLOSSNA);
    y += height;

    if (dedicated_server)
      DPrintf(DTXT_NGI_PACKETLOSSNA);
  } else {
    float packetloss = NetPlayers[GetPlayerNum()].percent_loss;

    // DLLRenderHUDText(color,255,0,x,y,DTXT_NGI_PACKETLOSS,packetloss); y+=height;
    DLLgrtext_SetColor(color);
    DLLgrtext_SetAlpha(255);
    DLLgrtext_Printf(x, y, DTXT_NGI_PACKETLOSS, packetloss);
    y += height;

    if (dedicated_server)
      DPrintf(DTXT_NGI_PACKETLOSS, packetloss);
  }

  // Print out Architecture
  // DLLRenderHUDText(color,255,0,x,y,DTXT_NGI_NETWORKMODEL,(peertopeer)?DTXT_PEERTOPEER:DTXT_CLIENTSERVER); y+=height;
  DLLgrtext_SetColor(color);
  DLLgrtext_SetAlpha(255);
  DLLgrtext_Printf(x, y, DTXT_NGI_NETWORKMODEL,
                   (peertopeer) ? DTXT_PEERTOPEER : ((permissable) ? DTXT_PERMISSABLE : DTXT_CLIENTSERVER));
  y += height;

  if (dedicated_server)
    DPrintf(DTXT_NGI_NETWORKMODEL,
            (peertopeer) ? DTXT_PEERTOPEER : ((permissable) ? DTXT_PERMISSABLE : DTXT_CLIENTSERVER));
}

/*
 ****************************************************************
 *                Registry Database Functions					*
 ****************************************************************
 */

// DatabaseRegister
//		Registers your multiplayer game with the database.  This must be
//	called before any other database function, or they will fail until this
//	is called.
void DMFCBase::DatabaseRegister(char *name) {
  if (name) {
    strncpy(DatabaseRegisteredName, name, MAX_DBNAME_SIZE - 1);
    DatabaseRegisteredName[MAX_DBNAME_SIZE - 1] = '\0';
  }
}

// DatabaseRead
//		Reads a string from the database
bool DMFCBase::DatabaseRead(const char *label, char *entry, int *entrylen) {
  if (DatabaseRegisteredName[0] == '\0')
    return false;
  if (!label || !entry || !entrylen)
    return false;

  int reg_len = strlen(DatabaseRegisteredName);
  int copy = MAX_DBLABEL_SIZE - 1 - reg_len;
  char buffer[MAX_DBLABEL_SIZE];

  strcpy(buffer, DatabaseRegisteredName);
  strncpy(&buffer[reg_len], label, copy);
  buffer[MAX_DBLABEL_SIZE - 1] = '\0';

  return DatabaseRead1(buffer, entry, entrylen);
}

// DatabaseRead
//		Reads wordsize bytes from the database
bool DMFCBase::DatabaseRead(const char *label, void *entry, int wordsize) {
  if (DatabaseRegisteredName[0] == '\0')
    return false;
  if (!label || !entry)
    return false;

  int reg_len = strlen(DatabaseRegisteredName);
  int copy = MAX_DBLABEL_SIZE - 1 - reg_len;
  char buffer[MAX_DBLABEL_SIZE];

  strcpy(buffer, DatabaseRegisteredName);
  strncpy(&buffer[reg_len], label, copy);
  buffer[MAX_DBLABEL_SIZE - 1] = '\0';

  return DatabaseRead2(buffer, entry, wordsize);
}

// DatabaseRead
//		Reads a bool from the database
bool DMFCBase::DatabaseRead(const char *label, bool *entry) {
  if (DatabaseRegisteredName[0] == '\0')
    return false;
  if (!label || !entry)
    return false;

  int reg_len = strlen(DatabaseRegisteredName);
  int copy = MAX_DBLABEL_SIZE - 1 - reg_len;
  char buffer[MAX_DBLABEL_SIZE];

  strcpy(buffer, DatabaseRegisteredName);
  strncpy(&buffer[reg_len], label, copy);
  buffer[MAX_DBLABEL_SIZE - 1] = '\0';

  return DatabaseRead3(buffer, entry);
}

// DatabaseWrite
//		Writes/Updates a string to the database
bool DMFCBase::DatabaseWrite(const char *label, const char *entry, int entrylen) {
  if (DatabaseRegisteredName[0] == '\0')
    return false;
  if (!label || !entry)
    return false;

  int reg_len = strlen(DatabaseRegisteredName);
  int copy = MAX_DBLABEL_SIZE - 1 - reg_len;
  char buffer[MAX_DBLABEL_SIZE];

  strcpy(buffer, DatabaseRegisteredName);
  strncpy(&buffer[reg_len], label, copy);
  buffer[MAX_DBLABEL_SIZE - 1] = '\0';

  return DatabaseWrite(buffer, entry, entrylen);
}

// DatabaseWrite
//		Writes/Updates a value to the database
bool DMFCBase::DatabaseWrite(const char *label, int entry) {
  if (DatabaseRegisteredName[0] == '\0')
    return false;
  if (!label)
    return false;

  int reg_len = strlen(DatabaseRegisteredName);
  int copy = MAX_DBLABEL_SIZE - 1 - reg_len;
  char buffer[MAX_DBLABEL_SIZE];

  strcpy(buffer, DatabaseRegisteredName);
  strncpy(&buffer[reg_len], label, copy);
  buffer[MAX_DBLABEL_SIZE - 1] = '\0';

  return DatabaseWrite(buffer, entry);
}

// CompareNetworkAddress
//
//	Compare's two network addresses, returns true if they are the same, false if not.
//	use_port: if this is true, than it will consider the port part of the network address
bool DMFCBase::CompareNetworkAddress(network_address *one, network_address *two, bool use_port) {
  if (use_port) {
    // compare the whole darned thing
    if (!memcmp(one, two, sizeof(network_address)))
      return true;
    else
      return false;
  } else {
    // compare all but the port
    if (!memcmp(one->address, two->address, 6) && !memcmp(one->net_id, two->net_id, 4) &&
        !memcmp(&one->connection_type, &two->connection_type, sizeof(network_protocol))) {
      // looks the same to me
      return true;
    } else {
      // something didn't match
      return false;
    }
  }

  return false;
}

// IsMasterTrackerGame
//
//	returns true if this game is being played on the master tracker
bool DMFCBase::IsMasterTrackerGame(void) { return (bool)((*m_bTrackerGame) != 0); }

//	ConvertLocalToServerObjnum
//
//	Given an objnum, it will convert the number from your local objnum to the server's objnum
//	It will return -1 on error
int DMFCBase::ConvertLocalToServerObjnum(int objnum) {
  if (GetLocalRole() == LR_SERVER)
    return (objnum < 0 || objnum >= MAX_OBJECTS) ? -1 : objnum;

  if (objnum < 0 || objnum >= MAX_OBJECTS)
    return -1;

  int s = Local_object_list[objnum];

  return (s == 65535) ? -1 : s;
}

//	ConvertServerToLocalObjnum
//
//	Given an objnum from the server, this function will convert the objnum to your local objnum
//  It will return -1 on error
int DMFCBase::ConvertServerToLocalObjnum(int objnum) {
  if (GetLocalRole() == LR_SERVER)
    return (objnum < 0 || objnum >= MAX_OBJECTS) ? -1 : objnum;

  if (objnum < 0 || objnum >= MAX_OBJECTS)
    return -1;

  int s = Server_object_list[objnum];

  return (s == 65535) ? -1 : s;
}

//	DMFCBase::AnnounceTeamChangeDeny
//
//	Tells a player that team change request was denied
void DMFCBase::AnnounceTeamChangeDeny(int pnum) {
  if (pnum == *Player_num) {
    DLLAddHUDMessage(DTXT_CHANGETEAMDENIED);
  } else {
    if (GetLocalRole() == LR_SERVER) {
      // tell the client the change has been denied
      SendControlMessageToPlayer(pnum, CM_TEAMCHANGEDENIED);
    }
  }
}

// DMFCBase::SetDeathMessageFilter
//
// Sets the death message filter
void DMFCBase::SetDeathMessageFilter(int level) {
  if (level < 0)
    level = 0;
  if (level > 2)
    level = 2;

  if (level == m_iDeathMessageFilter)
    return;

  m_iDeathMessageFilter = level;
  switch (level) {
  case DM_FILTER_FULL:
    DLLAddHUDMessage(DTXT_KILLMSGFULL);
    break;
  case DM_FILTER_SIMPLE:
    DLLAddHUDMessage(DTXT_KILLMSGSIMPLE);
    break;
  case DM_FILTER_NONE:
    DLLAddHUDMessage(DTXT_KILLMSGNONE);
    break;
  };
}

//	DMFCBase::MakeClientsWait
//
//	If passed true than all joining clients are forced to wait until they are given the signal to
//	join, either through SignalClientStart() or by passing false to MakeClientsWait
void DMFCBase::MakeClientsWait(bool wait) {
  m_bMakeClientsWait = wait;

  if (!wait) {
    // tell all the clients it's time to start
    for (int i = 0; i < DLLMAX_PLAYERS; i++) {
      if (CheckPlayerNum(i)) {
        SignalClientStart(i);
      }
    }
  } else {
    // tell all the clients to hold up
    WaitForServer(true);
  }
}

//	DMFCBase::SignalClientStart
//
//	Given a player num, it tells the client that they can start moving and playing
void DMFCBase::SignalClientStart(int pnum) {
  if (GetLocalRole() != LR_SERVER) {
    return;
  }

  SendControlMessageToPlayer(pnum, CM_SIGNALSTART);
  if (pnum == GetPlayerNum())
    WaitForServer(false);
}

//	DMFCBase::WaitForServer
//
//	Call this to start waiting, until the server tells us to stop (by passing false as a parm)
void DMFCBase::WaitForServer(bool wait) {
  if (wait) {
    Players[GetPlayerNum()].movement_scalar = 0;
    Players[GetPlayerNum()].weapon_recharge_scalar = 99999.9f;
    m_bMakeClientsWait = true;
  } else {
    Players[GetPlayerNum()].movement_scalar = 1.0f;
    Players[GetPlayerNum()].weapon_recharge_scalar = 1.0f;
    m_bMakeClientsWait = false;
  }

  if (GetLocalRole() != LR_SERVER)
    return;

  if (!wait)
    return;

  for (int i = 0; i < DLLMAX_PLAYERS; i++) {
    if (CheckPlayerNum(i)) {
      SendControlMessageToPlayer(i, CM_SIGNALWAIT);
    }
  }
}

//	DMFCBase::WaitingForServerLoop
//
//	Called per frame while waiting for the server to signal go
void DMFCBase::WaitingForServerLoop(void) {
  static float time_ac = 0;
  static bool show = true;

  time_ac += *Frametime;

  if (time_ac > 0.5) {
    time_ac = 0;
    show = !show;
  }

  if (show) {
    DLLgrtext_SetFont(Game_fonts[MENU_FONT_INDEX]);

    int height = DLLgrfont_GetHeight(Game_fonts[MENU_FONT_INDEX]);

    DLLRenderHUDTextFlags(HUDTEXT_CENTERED, GR_WHITE, 255, 0, 0, 240 - (height / 2), DTXT_WAITFORSERVER);
    DLLgrtext_Flush();
  }

  // Make sure we stay waiting...
  Players[GetPlayerNum()].movement_scalar = 0;
  Players[GetPlayerNum()].weapon_recharge_scalar = 99999.9f;
}

// DMFCBase::IAmDedicatedServer
//
//	Returns true if we are a dedicated server
bool DMFCBase::IAmDedicatedServer(void) { return *Dedicated_server; }

// DMFCBase::IsPlayerDedicatedServer
//
//	Returns true if the passed in pnum/player_record is a dedicated server
bool DMFCBase::IsPlayerDedicatedServer(int pnum) {
  // since we are looking for a server, the pnum must be 0
  if (pnum != 0)
    return false;

  if (Players[pnum].team == -1)
    return true;

  return false;
}
bool DMFCBase::IsPlayerDedicatedServer(player_record *pr) {
  if (pr->state == STATE_EMPTY)
    return false;

  if (pr->state == STATE_DISCONNECTED)
    return (pr->team == -1) ? true : false;

  return IsPlayerDedicatedServer(pr->pnum);
}

// DMFCBase::GetPlayerTeam
//
//	Returns the team of the player...call this instead of accessing .team directly
int DMFCBase::GetPlayerTeam(int pnum) { return (Players[pnum].team == -1) ? 0 : Players[pnum].team; }

// DMFCBase::ConvertHUDCoord
//
//	Given an x,y based on a virtual 640x480 screen, this will convert it to the x,y that should be
//	used based on the current screen size
void DMFCBase::ConvertHUDCoord(int x, int y, int *rx, int *ry) {
  *rx = ((float)(x) * (*Hud_aspect_x));
  *ry = ((float)(y) * (*Hud_aspect_y));
}

// DMFCBase::GetPlayerLogoBmp
//
//	Given a player_num, it will return a bitmap handle to that player's ship logo, or
//	-1 if they don't have a logo for their ship.
//	if is_vclip comes back as true, than it is not a bitmap handle, but a handle
//	to a vclip (animated bitmap).  It is an index into the DLLGameVClips[].
int DMFCBase::GetPlayerLogoBmp(int player_num, bool *is_vclip) {
  if (!CheckPlayerNum(player_num))
    return -1; // invalid pnum

  int texture_handle = Players[player_num].custom_texture_handle;

  if (GameTextures[texture_handle].bm_handle <= BAD_BITMAP_HANDLE)
    return -1;

  *is_vclip = (GameTextures[texture_handle].flags & TF_ANIMATED) ? true : false;

  return GameTextures[texture_handle].bm_handle;
}

// DMFCBase::EnableShipLogos
//
//	Disables or enables logo displaying for the client
void DMFCBase::EnableShipLogos(bool enable) {
  if (enable) {
    ENABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_SHIPLOGOSENABLED);
  } else {
    DISABLE_FLAGS(m_iProtectedFlags, DMFC_PRF_SHIPLOGOSENABLED);
  }

  DLLMultiSetLogoState(enable);
}

// DMFCBase::AreLogosEnabled
//
//	returns true if ship logos are enabled
bool DMFCBase::AreLogosEnabled(void) { return (bool)((m_iProtectedFlags & DMFC_PRF_SHIPLOGOSENABLED) != 0); }

// DMFCBase::EnableAudioTaunts
//
//	Disables or enables audio taunts
void DMFCBase::EnableAudioTaunts(bool enable) { DLLtaunt_Enable(enable); }

// DMFCBase::AreTauntsEnabled(void)
//
//	returns true if audio taunts are enabled
bool DMFCBase::AreTauntsEnabled(void) { return DLLtaunt_AreEnabled(); }

// DMFCBase::RespawnPlayer
//
//	Takes the given player and respawns his at a random start point (in a team game, team respawn
//	points are taken into consideration, so make sure the player is on the correct team before you
//	respawn him.  spew_everything, if false, will override spew_energy_and_shields
void DMFCBase::RespawnPlayer(int pnum, bool spew_energy_and_shields, bool spew_everything) {
  if (!CheckPlayerNum(pnum))
    return;

  object *obj = &Objects[Players[pnum].objnum];

  if (spew_everything)
    DLLPlayerSpewInventory(obj, spew_energy_and_shields, spew_everything);

  DLLPlayerStopSounds(pnum);

  DLLInitPlayerNewShip(pnum, (spew_everything) ? INVRESET_ALL : INVRESET_DEATHSPEW);

  if (pnum != GetPlayerNum())
    return;

  int slot = DLLPlayerGetRandomStartPosition(pnum);

  DLLObjSetPos(obj, &Players[slot].start_pos, Players[slot].start_roomnum, &Players[slot].start_orient, false);
}

// If you are going to create submenus you MUST use this function. along with:
// void SetState(int state);
// bool SetStateItemList(int count, ... ); for MIT_STATE items
MenuItem *DMFCBase::CreateMenuItem(const char *title, char type, uint8_t flags, void (*fp)(int), ...) {
  MenuItem *p;

  if (type != MIT_CUSTOM && type != MIT_STATE) {
    p = new MenuItem(title, type, flags, fp);
    return p;
  } else {
    if (type == MIT_CUSTOM) {
      tCustomMenu cm;

      va_list marker;
      va_start(marker, fp);
      memcpy(&cm, va_arg(marker, tCustomMenu *), sizeof(tCustomMenu));
      va_end(marker);

      p = new MenuItem(title, type, flags, fp, &cm);
      return p;
    } else {
      p = new MenuItem(title, type, flags, fp, 0, 0); // set initially to 0 state items
      return p;
    }
  }

  return NULL;
}

void ParseHostsFile(char *filename, tHostsNode **root) {
  CFILE *file;
  DLLOpenCFILE(&file, filename, "rt");

  tHostsNode *curr = *root;

  if (!file)
    return;

  char buffer[256];
  char save_buffer[256];

  char s_ip[16], s_mask[16];
  uint32_t ip_address;
  uint32_t mask;
  char *ptr;

  while (!DLLcfeof(file)) {
    DLLcf_ReadString(buffer, 256, file);

    // handle the buffer of data
    ptr = buffer;

    // parse white space
    while (*ptr && (*ptr == ' ' || *ptr == '\t'))
      ptr++;
    strcpy(save_buffer, ptr);

    if (*ptr) {
      // attempt to parse the dotted ip address (only 0-9,'.' and '*' allowed)
      char *start = ptr;

      // check to make sure its in a valid form

      int dot_count = 0;
      while (*ptr) {
        if (*ptr == '.')
          dot_count++;
        if ((*ptr < '0' || *ptr > '9') && *ptr != '*' && *ptr != '.')
          goto error_parse; // invalid character
        ptr++;
      }
      if (dot_count != 3)
        goto error_parse; // not enough dots
      ptr = start;

      // check the string length, no matter what it shouldn't be more than 15
      if (strlen(ptr) > 15)
        goto error_parse;

      // break up the string into seperate strings
      while (*ptr) {
        if (*ptr == '.')
          *ptr = '\0';
        ptr++;
      }
      ptr = start;

      // check to make sure the string length of each string isn't more than 3
      int length, count;

      for (count = 0; count < 4; count++) {
        length = strlen(ptr);
        if (length > 3 || length == 0)
          goto error_parse;

        if (length != 1) {
          // make sure it's a pure number
          char *save = ptr;
          while (*ptr) {
            if (!isdigit(*ptr))
              goto error_parse;
            ptr++;
          }
          ptr = save;
          int value = atoi(ptr);
          if (value < 0 || value > 255)
            goto error_parse;
        }

        ptr += length + 1;
      }
      ptr = start;

      // Its a valid IP address mask! yeah...*whew* that was fun

      int value;
      char temp_str[16];
      s_ip[0] = s_mask[0] = '\0';

      for (count = 0; count < 4; count++) {
        length = strlen(ptr);

        // Mask
        if (*ptr == '*') {
          value = 0;
        } else {
          value = 255;
        }
        snprintf(temp_str, sizeof(temp_str), "%d", value);
        strcat(s_mask, temp_str);
        if (count != 3)
          strcat(s_mask, ".");

        // IP Address
        if (*ptr == '*') {
          value = 0;
        } else {
          value = atoi(ptr);
        }
        snprintf(temp_str, sizeof(temp_str), "%d", value);
        strcat(s_ip, temp_str);
        if (count != 3)
          strcat(s_ip, ".");

        ptr += length + 1;
      }

      // we now have a valid mask (s_mask) and a valid ip (s_ip)
      mprintf(0, "IP: %s   Mask: %s\n", s_ip, s_mask);
      ip_address = DLLnw_GetHostAddressFromNumbers(s_ip);
      mask = DLLnw_GetHostAddressFromNumbers(s_mask);

      // add the node
      if (!curr) {
        curr = *root = (tHostsNode *)malloc(sizeof(tHostsNode));
      } else {
        curr->next = (tHostsNode *)malloc(sizeof(tHostsNode));
        curr = curr->next;
      }
      if (curr) {
        curr->next = NULL;
        curr->ip = ip_address;
        curr->mask = mask;
      }

      goto noerror_parse;
    }

  error_parse:
    mprintf(0, "Error parsing IP Address Mask: %s\n", save_buffer);

  noerror_parse:;
  }

  DLLcfclose(file);
}

// DMFCBase::ReadInHostsAllowDeny
//
//	Reads in the hosts.allow and hosts.deny files (if available)
void DMFCBase::ReadInHostsAllowDeny(void) {
  char allow_fn[_MAX_PATH], deny_fn[_MAX_PATH];
  bool allow_exist, deny_exist;

  // build the path info here
  DLLddio_MakePath(allow_fn, LocalD3Dir, "netgames", "hosts.allow", NULL);
  DLLddio_MakePath(deny_fn, LocalD3Dir, "netgames", "hosts.deny", NULL);

  allow_exist = (bool)(DLLcfexist(allow_fn) != 0);
  deny_exist = (bool)(DLLcfexist(deny_fn) != 0);

  m_DenyList = NULL;
  m_AllowList = NULL;

  // parse away
  if (deny_exist) {
    mprintf(0, "Parsing hosts.deny\n");
    ParseHostsFile(deny_fn, &m_DenyList);
  }
  if (allow_exist) {
    mprintf(0, "Parsing hosts.allow\n");
    ParseHostsFile(allow_fn, &m_AllowList);
  }
}

// DMFCBase::FreeHostsLists
//
//	Frees all the memory allocated for the host allow/deny lists
void DMFCBase::FreeHostsLists(void) {
  tHostsNode *curr = NULL, *next;

  curr = m_DenyList;
  while (curr) {
    next = curr->next;
    free(curr);
    curr = next;
  }

  curr = m_AllowList;
  while (curr) {
    next = curr->next;
    free(curr);
    curr = next;
  }
}

// DMFCBase::RehashAllowDeny
//
//	Flushes and reloads the hosts.allow/.deny lists
void DMFCBase::RehashAllowDeny(void) {
  FreeHostsLists();
  ReadInHostsAllowDeny();
}

// DMFCBase::IsPlayerAlive
//
//	Returns true is the given pnum is a player, flying around the level (not dying, dead or observing)
bool DMFCBase::IsPlayerAlive(int pnum) {
  if (!CheckPlayerNum(pnum))
    return false;

  if (Objects[Players[pnum].objnum].type != OBJ_PLAYER)
    return false;

  if (Players[pnum].flags & (PLAYER_FLAGS_DYING | PLAYER_FLAGS_DEAD))
    return false;
  return true;
}

//	DMFCBase::ParseStartupScript
//
//
//	Loads up the startup script and sets variables accordingly
void DMFCBase::ParseStartupScript(void) {
  CFILE *file;
  char path[_MAX_PATH];
  char buffer[256];
  int size;
  bool ok_to_read;

  int autoexec_arg = -1;

  if ((autoexec_arg = DLLFindArg("-autoexec")) != 0) {
    // a specific autoexec.dmfc file was specified, use that
    strcpy(path, GetGameArg(autoexec_arg + 1));
    mprintf(0, "Override AUTOEXEC.DMFC to %s\n", path);
  } else {
    // use the default autoexec.dmfc
    DLLddio_MakePath(path, LocalD3Dir, "netgames", "autoexec.dmfc", NULL);
  }

  DLLOpenCFILE(&file, path, "rt");
  if (!file)
    return;

  while (!DLLcfeof(file)) {
    ok_to_read = true;

    size = DLLcf_ReadString(&buffer[1], 254, file);
    buffer[255] = '\0';

    if (size >= 254) {
      // invalid string, too long..trash the rest of the line
      ok_to_read = false;
      while (size >= 254) {
        size = DLLcf_ReadString(buffer, 254, file);
      }
    }

    if (!ok_to_read) {
      mprintf(0, "AUTOEXEC.DMFC: Line too long\n");
    } else {
      if (buffer[1] == '$') {
        InputCommandHandle(&buffer[1]);
      } else {
        buffer[0] = '$'; // insert starting $ sign for input command
        InputCommandHandle(buffer);
      }
    }
  }

  DLLcfclose(file);
}

dllinfo *DMFCBase::GetDLLInfoCallData(void) { return Data; }

int DMFCBase::GetHighestRoomIndex(void) { return *Highest_room_index; }

int DMFCBase::GetGameWindowW(void) { return *Game_window_w; }

int DMFCBase::GetGameWindowH(void) { return *Game_window_h; }

int DMFCBase::GetGameWindowX(void) { return *Game_window_x; }

int DMFCBase::GetGameWindowY(void) { return *Game_window_y; }

int *DMFCBase::GetGameFontTranslateArray(void) { return Game_fonts; }

int DMFCBase::GetObserverModeBitmap(void) { return hBitmapObserver; }

float DMFCBase::GetFrametime(void) { return *Frametime; }

float DMFCBase::GetGametime(void) { return *Gametime; }

float DMFCBase::GetRealGametime(bool *ispaused) {
  if (ispaused) {
    *ispaused = (m_iProtectedFlags & DMFC_PRF_PAUSETIME) ? true : false;
  }

  return RealGametime;
}

float *DMFCBase::GetShieldDeltaArray(void) { return ShieldDelta; }

float DMFCBase::GetHudAspectX(void) { return *Hud_aspect_x; }

float DMFCBase::GetHudAspectY(void) { return *Hud_aspect_y; }

const char *DMFCBase::GetLocalD3Dir(void) { return LocalD3Dir; }

const tMission *DMFCBase::GetCurrentMission(void) { return Current_mission; }

room *DMFCBase::GetRooms(void) { return Rooms; }

object *DMFCBase::GetObjects(void) { return Objects; }

terrain_segment *DMFCBase::GetTerrainSegs(void) { return Terrain_seg; }

netgame_info *DMFCBase::GetNetgameInfo(void) { return Netgame; }

player *DMFCBase::GetPlayers(void) { return Players; }

netplayer *DMFCBase::GetNetPlayers(void) { return NetPlayers; }

ship *DMFCBase::GetShips(void) { return Ships; }

weapon *DMFCBase::GetWeapons(void) { return Weapons; }

texture *DMFCBase::GetGameTextures(void) { return GameTextures; }

vclip *DMFCBase::GetGameVClips(void) { return GameVClips; }

poly_model *DMFCBase::GetGamePolyModels(void) { return Poly_models; }

ddgr_color *DMFCBase::GetPlayerColors(void) { return Player_colors; }

game_controls DMFCBase::GetLastGameControls(void) { return Last_game_controls; }

int *DMFCBase::GetPilotPicBitmapHandles(void) { return PilotPicBmpHandles; }

void DMFCBase::GetViewerObjectPtr(object **v_obj) { *v_obj = *Viewer_object; }

void DMFCBase::SetViewerObjectPtr(object *v_obj) { *Viewer_object = v_obj; }

float DMFCBase::GetRenderZoom(void) { return *Render_zoom; }

IMenuItem *DMFCBase::GetOnScreenMenu(void) { return &Menu; }

tOSIRISModuleInit *DMFCBase::GetOsirisModuleData(void) { return API.osiris_functions; }

vis_effect *DMFCBase::GetVisEffectArray(int **Highviseptr) {
  if (Highviseptr)
    *Highviseptr = Highest_vis_effect_index;

  return VisEffects;
}

char *DMFCBase::GetGameArg(int arg) {
  static char EMPTY_ARG[1] = "";

  ASSERT(arg >= 0 && arg < MAX_ARGS);
  if (arg < 0 || arg >= MAX_ARGS)
    return (char *)EMPTY_ARG;

  char *arguments = (char *)GameArgs;

  int position = arg * MAX_CHARS_PER_ARG;

  return &arguments[position];
}

//	DMFCBase::EnableLossGuage
//
//	Turns on/off the Loss guage on the screen
void DMFCBase::EnableLossGuage(bool enable) {
  bool peertopeer = (bool)((Netgame->flags & NF_PEER_PEER) != 0);

  if ((GetLocalRole() == LR_SERVER) || peertopeer) {
    return;
  }

  LossGuageEnabled = enable;
}

ddgr_color GetTriLinColor(float val, float low, float med, float hi, ddgr_color lowc, ddgr_color medc, ddgr_color hic) {
  ddgr_color low_col, hi_col;
  float perc;

  if (val <= low) {
    // we're pure lowc
    perc = 1.0f;
    low_col = hi_col = lowc;
  } else if (val <= med) {
    // we're between lowc and medc
    low_col = lowc;
    hi_col = medc;

    perc = (val - low) / (med - low);
  } else if (val <= hi) {
    // we're between medc and hic
    low_col = medc;
    hi_col = hic;
    perc = (val - med) / (hi - med);
  } else {
    // we're pure hic
    perc = 1.0f;
    low_col = hi_col = hic;
  }

  if (perc < 0.0f)
    perc = 0.0f;
  if (perc > 1.0f)
    perc = 1.0f;

  ddgr_color color_to_use;
  int r, g, b;
  float amount_low, amount_hi;

  amount_low = 1.0 - perc;
  amount_hi = perc;

  r = GR_COLOR_RED(low_col) * amount_low + GR_COLOR_RED(hi_col) * amount_hi;
  g = GR_COLOR_GREEN(low_col) * amount_low + GR_COLOR_GREEN(hi_col) * amount_hi;
  b = GR_COLOR_BLUE(low_col) * amount_low + GR_COLOR_BLUE(hi_col) * amount_hi;

  color_to_use = GR_RGB(r, g, b);

  return color_to_use;
}

//	DMFCBase::LossGuageFrame
//
//	Processes the Loss guage for the frame
void DMFCBase::LossGuageFrame(void) {
  if (!LossGuageEnabled)
    return;

  if (GetLocalRole() == LR_SERVER)
    return;

  // Don't show if peer-to-peer
  if (Netgame->flags & NF_PEER_PEER)
    return;

#define LOW_COLOR GR_RGB(20, 255, 20)
#define MED_COLOR GR_RGB(255, 255, 40)
#define HI_COLOR GR_RGB(255, 20, 20)
#define LOW_LOSS 1.0f
#define MED_LOSS 5.0f
#define HI_LOSS 10.0f

  float packetloss = NetPlayers[GetPlayerNum()].percent_loss;

  ddgr_color loss_color = GetTriLinColor(packetloss, LOW_LOSS, MED_LOSS, HI_LOSS, LOW_COLOR, MED_COLOR, HI_COLOR);
  ddgr_color ping_color =
      GetTriLinColor(NetPlayers[GetPlayerNum()].ping_time * 1000.0f, 0, 200.0f, 400.0f, LOW_COLOR, MED_COLOR, HI_COLOR);

  DLLgrtext_SetFont(Game_fonts[HUD_FONT_INDEX]);

  int max_x = 0;
  int y = 2;

  DLLRenderHUDText(loss_color, 255, 2, 0, y, DTXT_NETWORK_LOSS);
  max_x = DLLRenderHUDGetTextLineWidth(DTXT_NETWORK_LOSS);

  DLLRenderHUDText(ping_color, 255, 2, 0, y + 10, DTXT_NETWORK_PING);
  max_x = std::max(max_x, DLLRenderHUDGetTextLineWidth(DTXT_NETWORK_PING));

  max_x += 10;
  DLLRenderHUDText(loss_color, 255, 0, 2 + max_x, y, "%.2f%%", packetloss);
  DLLRenderHUDText(ping_color, 255, 0, 2 + max_x, y + 10, "%dms",
                   (int)(NetPlayers[GetPlayerNum()].ping_time * 1000.0f));
}

level_info *DMFCBase::GetLevelInfo(void) { return Level_info; }

// 1 - (num_levels)
void DMFCBase::WarpToLevel(int lev) {
  if (GetLocalRole() != LR_SERVER)
    return;

  if (lev < 1 || lev > Current_mission->num_levels)
    return;

  *Multi_next_level = lev;
  EndLevel();
}

void DMFCBase::SetPlayerTauntIndicator(int pnum) {
  if (!CheckPlayerNum(pnum))
    return;

  DisplayTauntIndicator = true;
  TauntIndicatorStartTime = *Gametime;
  TauntIndicatorPlayerNum = pnum;
}

void DMFCBase::DisplayTauntIndicatorFrame(void) {
  if (!DisplayTauntIndicator)
    return;

  if (!CheckPlayerNum(TauntIndicatorPlayerNum)) {
    DisplayTauntIndicator = false;
    return;
  }

  float alpha, perc;
  int igt = floor((*Gametime));
  float igth = (*Gametime) - igt;
  if (igth > 0.5f) {
    igth -= 0.5f;
  }

  if (igth > 0.25f) {
    perc = 1.0f - ((igth - 0.25) / 0.25f);
  } else {
    perc = (igth / 0.25f);
  }

  float base_alpha = 0.40f;
  float range = 1.0f - base_alpha;

  alpha = base_alpha + (perc * range);
  if (alpha < base_alpha)
    alpha = base_alpha;
  if (alpha > 1.0f)
    alpha = 1.0f;

  int bmp = TauntIndicatorBMP;
  if (PilotPicBmpHandles[TauntIndicatorPlayerNum] > BAD_BITMAP_HANDLE) {
    bmp = PilotPicBmpHandles[TauntIndicatorPlayerNum];
  }

  if (bmp <= BAD_BITMAP_HANDLE) {
    DisplayTauntIndicator = false;
    return;
  }

  int bmh = 32;
  int bmw = 32;
  int x, y;

  x = 10;
  y = 200;

  DLLrend_SetAlphaType(AT_CONSTANT_TEXTURE);
  DLLrend_SetAlphaValue(alpha * 255);
  DLLrend_SetLighting(LS_NONE);
  DLLrend_SetColorModel(CM_MONO);
  DLLrend_SetOverlayType(OT_NONE);
  DLLrend_SetFiltering(0);
  DLLrend_SetWrapType(WT_CLAMP);
  DLLrend_SetZBufferState(0);
  DLLrend_SetTextureType(TT_LINEAR);

  DLLrend_DrawScaledBitmap(x, y, x + bmw, y + bmh, bmp, 0, 0, 1, 1, 1, -1, NULL);
  DLLrend_SetFiltering(1);
  DLLrend_SetZBufferState(1);

  int font_height;

  DLLgrtext_SetFont(Game_fonts[HUD_FONT_INDEX]);
  font_height = DLLgrfont_GetHeight(Game_fonts[HUD_FONT_INDEX]);

  x = 15 + bmw;
  y = (y + (bmh / 2)) - (font_height / 2);

  DLLgrtext_SetColor(GR_GREEN);
  DLLgrtext_SetAlpha(alpha * 255.0f);
  DLLgrtext_Printf(x, y, Players[TauntIndicatorPlayerNum].callsign);

  if ((*Gametime - TauntIndicatorStartTime) > 3.0f)
    DisplayTauntIndicator = false;
}

// DMFCBase::SetMaxPlayerHardLimit
//
//	Sets a hard limit to the max number of players allowed in the game
//	changing the number of players can never go above this...defaults
//	to DLLMAX_PLAYERS
void DMFCBase::SetMaxPlayerHardLimit(int max) {
  if (GetLocalRole() != LR_SERVER)
    return;
  if (max > DLLMAX_PLAYERS || max < 1)
    return;

  Hard_max_players = max;
  if (Netgame->max_players > Hard_max_players) {
    Netgame->max_players = max;
    DLLAddHUDMessage(DTXT_MAXPLAYERSSETMSG, max);
    SendNetGameInfoSync();
  }
}

// DMFCBase::MarkPlayersInGame
//
//	Goes through all the player records and marks those that are in the game
//	This is needed because at level end, all clients disconnect, so we lose
//	that information.
void DMFCBase::MarkPlayersInGame(void) {
  player_record *pr;
  for (int i = 0; i < MAX_PLAYER_RECORDS; i++) {
    pr = GetPlayerRecord(i);

    if (pr && pr->state == STATE_INGAME) {
      players_in_game[i] = pr->pnum;
    } else {
      players_in_game[i] = -1;
    }
  }
}

// DMFCBase::ResetPlayersInGame
//
//	Resets the Players-in-game list
void DMFCBase::ResetPlayersInGame(void) {
  for (int i = 0; i < MAX_PLAYER_RECORDS; i++) {
    players_in_game[i] = -1;
  }
}

//	DMFCBase::WasPlayerInGameAtLevelEnd
//
//	Given a player record it returns true/false whether
//	the player was in the game at the time the level ended.
int DMFCBase::WasPlayerInGameAtLevelEnd(int prec) {
  if (prec < 0 || prec >= MAX_PLAYER_RECORDS)
    return -1;

  return players_in_game[prec];
}

// DMFCBase::GetConnectingPlayerTeam
//
//	Returns the team of a player just connecting, this is to be set by the
//	game and is DMFC assumes that all players have the correct team on join
int DMFCBase::GetConnectingPlayerTeam(int slot) {
  if (GetLocalRole() != LR_SERVER)
    return -2;

  int prec_num;
  if (IsMasterTrackerGame())
    prec_num = PRec_FindPlayer(Players[slot].callsign, NULL, Players[slot].tracker_id);
  else
    prec_num = PRec_FindPlayer(Players[slot].callsign, &NetPlayers[slot].addr, NULL);

  int team = -2;

  if (prec_num != -1) {
    // we have a reconnecting player

    // we need to reconnect the player to the player records before we get the team
    if (!PRec_ReconnectPlayerToSlot(slot, prec_num, Players, NetPlayers)) {
      mprintf(0, "Unable to reassign reconnecting player (%s) to Player Record slot #%d\n",
              Players[slot].callsign,
              prec_num);
      Int3();
    } else {
      team = PRec_GetPlayerTeam(slot);

      // disconnect the player again since he isn't in the game just yet
      PRec_DisconnectPlayer(slot);
    }
  } else {
    // we have a new player

    if (slot == 0 && IAmDedicatedServer()) {
      team = -1;
    } else {
      // get team assignment
      if (m_iProtectedFlags & DMFC_PRF_AUTOTEAMSELECT) {
        team = GetTeamForNewPlayer(slot, m_iNumTeams);
      } else {
        team = RED_TEAM;
      }
    }
  }

  mprintf(0, "CONNECTING PLAYER (%s): Team assigned to %d\n", Players[slot].callsign, team);

  return team;
}

//	DMFCBase::SetDedicatedWait
//
//	Sets the level wait time for the dedicated server.  A dedicated server will make all
//	clients wait until this time (in seconds) is up each level.
void DMFCBase::SetDedicatedWait(float time_secs) {
  if (time_secs < 0)
    time_secs = 0;

  DedicatedLevelWait = time_secs;
}

// DMFCBase::EnableTimelimitCountdown
//
//	Enables/Disables the timelimit countdown (if there is a level time limit)
//	Optionally you can specify what time to start the count down. (default 10)
void DMFCBase::EnableTimelimitCountdown(bool enable, int seconds) {
  m_bDisplayTimelimitCountdown = enable;

  if (enable && seconds > 0) {
    m_iTimeLimitCountdown = seconds;
  }
}

// DMFCBase::DisplayTimelimitCountdown
//
//	Displays (if needed) the time limit countdown
void DMFCBase::DisplayTimelimitCountdown(void) {
  if (!m_bDisplayTimelimitCountdown)
    return;

  if (!(Netgame->flags & NF_TIMER))
    return;

  float time_left = (Netgame->timelimit * 60.0f) - RealGametime;

  if (time_left > m_iTimeLimitCountdown || time_left < 0)
    return;

  // display a countdown!
  DLLgrtext_SetFont(Game_fonts[HUD_FONT_INDEX]);
  DLLgrtext_SetColor(GR_WHITE);
  DLLgrtext_SetAlpha(255);

  char buffer[256];
  snprintf(buffer, sizeof(buffer), "Time Left: T - %d", (int)time_left);

  int font_height = DLLgrfont_GetHeight(Game_fonts[HUD_FONT_INDEX]);
  DLLgrtext_CenteredPrintf(0, font_height * 10, buffer);
}

// DMFCBase::TranslateTextMacro
//
//	Given a macro with tokens, this function will replace the tokens
//	and create a new string for display.
//	All tokens begin with $$ (i.e.  $$TEAMBASE)
void DMFCBase::TranslateTextMacro(const char *src, char *destination, int dest_size) {
  ASSERT(destination != NULL);
  ASSERT(src != NULL);
  ASSERT(dest_size >= 0);

  if (dest_size <= 0)
    return;

  char token[128];
  char token_string[512];

  char *curr_dest = destination;
  int src_idx = 0;
  bool done = false;

  // now we need to go through the src and copy to destination
  // while looking for tokens, and making sure we don't go over our size
  while (dest_size > 1 && !done) {
    switch (src[src_idx]) {
    case '$': {
      // possible token!, check the next character
      if (src[src_idx + 1] == '$') {
        // TOKEN!!!
        src_idx += 2; // move to the beginning of the token
        if (src[src_idx] == ' ' || src[src_idx] == '\0') {
          // ok, maybe it isn't a token
          *curr_dest = src[src_idx - 2];
          curr_dest++;
          dest_size--;
          *curr_dest = src[src_idx - 1];
          curr_dest++;
          dest_size--;
          *curr_dest = src[src_idx];
          if (*curr_dest == '\0') {
            done = true;
          } else {
            src_idx++;
          }
          curr_dest++;
          dest_size--;
        } else {
          // process it, it's gotta be a token

          // extract the token out
          char *ptr = token;
          while (src[src_idx] != ' ' && src[src_idx] != '\0') {
            *ptr = src[src_idx];
            ptr++;
            src_idx++;
          }
          *ptr = '\0';

          // now replace the token...
          *token_string = '\0';
          mprintf(0, "Looking for token for %s\n", token);
          CallOnGetTokenString(token, token_string, 512);

          // make sure we don't go too far
          int len = strlen(token_string);
          if (len > (dest_size - 1))
            len = dest_size - 1;

          ptr = token_string;

          while (len > 0) {
            *curr_dest = *ptr;
            curr_dest++;
            dest_size--;
            ptr++;
            len--;
          }
        }
      } else {
        // just copy the character over
        *curr_dest = src[src_idx];
        src_idx++;
        curr_dest++;
        dest_size--;
      }
    } break;
    case '\0':
      done = true;
      break;
    default: {
      // just copy the character over
      *curr_dest = src[src_idx];
      src_idx++;
      curr_dest++;
      dest_size--;
    } break;
    }
  }

  *curr_dest = '\0'; // NULL terminate
}

// DMFCBase::SelectNextCameraView
//
//	This function, given which window (corresponds to left, middle, right), switches the
//	current view of the small camera windows on the screen
void DMFCBase::SelectNextCameraView(int window) {
  if (window < 0 || window >= NUM_CAMERA_VIEWS)
    return;
  DLLSelectNextCameraView(window);
}

int DMFCBase::GetCameraViewType(int window) {
  if (window < 0 || window >= NUM_CAMERA_VIEWS)
    return -1;
  return Camera_view_mode[window];
}

// Given a generic object (OBJ_POWERUP,OBJ_ROBOT,OBJ_BUILDING or OBJ_CLUTTER) id
// in the range of 0 to MAX_OBJECT_IDS, this returns a pointer to it's information (see objinfo.h)
//	It returns NULL if an invalid id is given (or it's not used)
object_info *DMFCBase::GetObjectInfo(int objinfo_id) {
  if (objinfo_id < 0 || objinfo_id >= MAX_OBJECT_IDS)
    return NULL;

  if (Object_info[objinfo_id].type == OBJ_NONE)
    return NULL;

  return &Object_info[objinfo_id];
}
