/*
* 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/dmfcmenu.cpp $
 * $Revision: 1.1.1.1 $
 * $Date: 2003/08/26 03:57:21 $
 * $Author: kevinb $
 *
 * DMFC in-game Graphical menu functions
 *
 * $Log: dmfcmenu.cpp,v $
 * Revision 1.1.1.1  2003/08/26 03:57:21  kevinb
 * initial 1.5 import
 *
 *
 * 42    3/20/00 12:25p Matt
 * Merge of Duane's post-1.3 changes.
 * Initialize m_cPtrs in altenate menuItem constructor.
 *
 * 41    10/26/99 10:32a Jeff
 * fixed COM interface bug
 *
 * 40    7/07/99 5:00p Jeff
 * removed vararg functions from interface functions, just made different
 * versions of them
 *
 * 39    5/13/99 4:55p Ardussi
 * changes for compiling on the Mac
 *
 * 38    5/09/99 6:20a Jeff
 * improved Entropy (added sounds, max virii per room).  Fixed rendering
 * bugs for other multiplayer dlls.
 *
 * 37    4/03/99 4:06p Jeff
 * added loss/ping gauge
 *
 * 36    3/22/99 6:21p Jeff
 * added 2 more audio taunts.  a mulitplayer event when someone plays an
 * audio taunt.  option to disable audio taunts.
 *
 * 35    3/17/99 12:24p Jeff
 * converted DMFC to be COM interface
 *
 * 34    3/05/99 1:30p Jeff
 * fixed 99% of the high res issues
 *
 * 33    2/11/99 12:51a Jeff
 * changed names of exported variables
 *
 * 32    1/31/99 7:26p Matt
 * Renamed a bunch of functions to have HUD capitalized
 *
 * 31    1/19/99 3:55a Jeff
 * all strings localized out
 *
 * 30    1/04/99 12:21p Jeff
 * added support for hosts.allow/deny and updates stats manager a little
 *
 * 29    12/13/98 5:32p Jeff
 * fixed ugly crash due to freeing memory allocated in another heap
 *
 * 28    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
 *
 * 27    11/19/98 5:56p Jeff
 * added slider exported and improved Hoard
 *
 * 26    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
 *
 * 25    11/17/98 12:36p Jeff
 * remove onscreen menu when endlevel selected
 *
 * 24    11/13/98 6:36p Jeff
 * created dmfc_dll (a DLL version of DMFC) and converted current mods to
 * use it
 *
 * 23    11/11/98 7:19p Jeff
 * changes made so that a dedicated server's team is always -1 (team game
 * or not)
 *
 * 22    10/20/98 5:39p Jeff
 * fixed MIF_INCLUDEONE bug with team games
 *
 * 21    10/20/98 4:35p Jeff
 * added a flag for menu to add a <None> to MIT_PLIST...
 *
 * 20    10/20/98 12:16p Jeff
 * added death message filter, hud callsign filter
 *
 * 19    10/15/98 1:34p Jeff
 * added scrollable onscreen menu.  Remove ban in dmfc.  prejoin event
 *
 * 18    10/14/98 11:26p Jeff
 * added scrollable menus
 *
 * 17    10/05/98 2:50p Jeff
 *
 * 16    9/30/98 3:50p Jeff
 * general improvements (many)
 *
 * 15    9/29/98 3:04p Jeff
 * added time in game and start_time support
 *
 * 14    9/28/98 5:05p Jeff
 * made the statisitical death messages an option in the menu
 *
 * 13    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 "DMFC.h"
#include "gamedll_header.h"
#include "dmfcinternal.h"


#include <algorithm>

extern DMFCBase *basethis;

MenuItem::MenuItem() {
  m_cPtrs = 0;
  m_bMoreToScroll = false;
  m_bAtBottom = false;
  m_iTopIndex = 0;
  SubMenuCount = 0;
  CurrSubMenu = -1;
  m_iState = 0;
  SubMenus = NULL;
  func = NULL;
  HasInputFocus = false;
  m_cType = MIT_NORMAL;

  m_Alpha = 0;
  m_AlphaDir = true;

  memset(&m_cmInfo, 0, sizeof(tCustomMenu));

  strcpy(m_sTitle, " ");
}

MenuItem::MenuItem(const char *title, char type, uint8_t flags, void (*fp)(int), ...) {
  m_bMoreToScroll = false;
  m_bAtBottom = false;
  m_iTopIndex = 0;
  SubMenuCount = 0;
  CurrSubMenu = -1;
  m_iState = 0;
  SubMenus = NULL;
  func = fp;
  HasInputFocus = false;
  m_cType = type;
  m_Alpha = 0;
  m_AlphaDir = true;
  m_iFlags = flags;
  m_cPtrs = 0; // DAJ Do this or fail the second netgame start

  memset(&m_cmInfo, 0, sizeof(tCustomMenu));

  if (title) {
    strncpy(m_sTitle, title, MAX_STRING_LEN - 1);
    m_sTitle[MAX_STRING_LEN - 1] = '\0';
  } else {
    strcpy(m_sTitle, " ");
  }

  // if the type is MIT_PLIST than add a dummy submenu so it will register
  if (type == MIT_PLIST) {
    SubMenuCount = 1;
    SubMenus = (IMenuItem **)malloc(sizeof(IMenuItem *) * 1);
    SubMenus[0] = new MenuItem("Pfuncer", MIT_STATEITEM, 0, fp);
  }

  // if the type is MIT_CUSTOM than add a dummy submenu so it will register
  // the only variable parameter should be a pointer to a tCustomMenu struct that
  // contains the needed callbacks
  if (type == MIT_CUSTOM) {
    SubMenuCount = 1;
    SubMenus = (IMenuItem **)malloc(sizeof(IMenuItem *) * 1);
    SubMenus[0] = new MenuItem("Pfuncer", MIT_STATEITEM, 0, fp);

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

  // if the type is MIT_STATE than we need to pull off the extra parameters for the states
  if (type == MIT_STATE) {
    va_list marker;
    va_start(marker, fp);
    SubMenuCount = va_arg(marker, int);
    m_iState = va_arg(marker, int);
    if ((SubMenuCount > 0) && (SubMenuCount < MAX_STATE_SUBMENUS)) {
      SubMenus = (IMenuItem **)malloc(sizeof(IMenuItem *) * SubMenuCount);
      if (!SubMenus)
        return;
      char *string;
      for (int i = 0; i < SubMenuCount; i++) {
        string = va_arg(marker, char *);
        SubMenus[i] = new MenuItem(string, MIT_STATEITEM, 0, fp);
      }
    } else
      SubMenuCount = 0;
    va_end(marker);
  }
}

MenuItem::~MenuItem() {
  MenuItem *p;

  if (SubMenus) {
    if ((m_cType == MIT_PLIST) || (m_cType == MIT_CUSTOM)) {
      p = (MenuItem *)SubMenus[0];
      if (p) {
        SubMenus[0] = NULL;
        delete p;
      }
    } else {
      for (int i = 0; i < SubMenuCount; i++) {
        p = (MenuItem *)SubMenus[i];
        if (p) {
          SubMenus[i] = NULL;
          delete p;
        }
      }
    }

    free(SubMenus);
    SubMenus = NULL;
  }

  SubMenuCount = 0;
}

bool MenuItem::AddSubMenu(IMenuItem *p) {
  // No submenus for a Player list submenu or custom or if we are working with a stateitem submenu item
  if ((m_cType == MIT_PLIST) || (m_cType == MIT_STATEITEM) || (m_cType == MIT_STATE) || (m_cType == MIT_CUSTOM))
    return false;

  MenuItem **temp;
  temp = (MenuItem **)SubMenus;

  // create a new array of submenus and copy the old to it
  SubMenus = (IMenuItem **)malloc(sizeof(IMenuItem *) * (SubMenuCount + 1));
  for (int i = 0; i < SubMenuCount; i++) {
    SubMenus[i] = temp[i];
  }

  if (temp)
    free(temp);

  // Put the new submenu item at the end of this one
  SubMenus[SubMenuCount] = p;
  SubMenuCount++;
  return true;
}

bool MenuItem::DetachSubMenu(IMenuItem *p) {
  // No submenus for a Player list submenu or custom or if we are working with a stateitem submenu item
  if ((m_cType == MIT_PLIST) || (m_cType == MIT_STATEITEM) || (m_cType == MIT_STATE) || (m_cType == MIT_CUSTOM))
    return false;

  if (SubMenuCount < 1)
    return false;

  int i;
  bool found = false;

  // make sure there is this menu somewhere in our list
  for (i = 0; i < SubMenuCount; i++) {
    if (SubMenus[i] == p) {
      found = true;
      break;
    }
  }
  if (!found) {
    // doesn't exist
    return false;
  }

  MenuItem **temp;
  temp = (MenuItem **)SubMenus;

  if (SubMenuCount > 1) {
    // we need to resize our list
    // create a new array of submenus and copy the old to it
    int c;

    SubMenus = (IMenuItem **)malloc(sizeof(IMenuItem *) * (SubMenuCount - 1));
    for (i = 0, c = 0; i < SubMenuCount; i++) {
      // make sure it isn't the one we are trying to detach
      if (temp[i] != p) {
        ASSERT((c < (SubMenuCount - 1)));
        SubMenus[c] = temp[i];
        c++;
      }
    }

    if (temp)
      free(temp);

    SubMenuCount--;
  } else {
    // this is the only remaining submenu
    SubMenuCount = 0;

    if (temp)
      free(temp);
    SubMenus = NULL;
  }

  return true;
}

bool MenuItem::Forward(void) {
  if ((m_cType == MIT_PLIST) || (m_cType == MIT_STATE) || (m_cType == MIT_CUSTOM))
    return false; // short circuit on a plist and state since it has no submenus in reality

  char type = SubMenus[CurrSubMenu]->GetType();

  // We can only move forward if there is a submenu
  if ((type == MIT_CUSTOM && SubMenus[CurrSubMenu]->GetCustomSubMenuCount() > 0) ||
      (type != MIT_CUSTOM && (SubMenuCount > 0))) {
    // If this submenu has input focus, than we need to check the currently selected submenu
    // and set that as the new item with focus, and remove focus from this one
    if (HasInputFocus) {
      if ((CurrSubMenu != -1) && (SubMenus[CurrSubMenu]->HasSubMenus())) {
        SubMenus[CurrSubMenu]->SetInputFocus();
        HasInputFocus = false;
      }
      return true;
    }
    // This submenu doesn't have input focus, so move to it's currently selected submenu and
    // Tell it to go forward if it can
    if (CurrSubMenu != -1)
      return SubMenus[CurrSubMenu]->Forward();
  }
  return false;
}
bool MenuItem::Back(void) {
  // We need to look ahead and see if the next submenu has input focus
  // if it does have focus, than we need to make it lose focus, and make this one have focus
  if (SubMenuCount > 0) {
    if (CurrSubMenu != -1) {
      if (SubMenus[CurrSubMenu]->GetFocus()) {
        // The next submenu has focus so we can remove it and set this guy to have focus
        SubMenus[CurrSubMenu]->LoseInputFocus();
        HasInputFocus = true;
        return true;
      }
      // The next submenu doesn't have focus, but one of it's submenus might (they better),
      // so move on to the next submenu and call Back()
      return SubMenus[CurrSubMenu]->Back();
    }
  }
  return false;
}
bool MenuItem::Up(void) {
  // See if this MenuItem has focus, if it does, and we can move up, then move up
  if (HasInputFocus) {
    if (CurrSubMenu > 0)
      CurrSubMenu--;
    if (m_iTopIndex > 0 && m_iTopIndex >= CurrSubMenu) {
      // move the top and index's up
      m_iTopIndex--;
    }

    return true;
  }
  // Ok, one of the submenus better have focus, so move on to the next submenu and call Up()
  if (CurrSubMenu != -1)
    return SubMenus[CurrSubMenu]->Up();
  return false;
}
bool MenuItem::Down(void) {
  // See if this MenuItem has focus, if it does, and we can move down, then move down
  if (HasInputFocus) {
    if (CurrSubMenu < SubMenuCount - 1)
      CurrSubMenu++;
    if (m_bMoreToScroll && m_bAtBottom) {
      m_iTopIndex++;
    }
    return true;
  }
  // Ok, one of the submenus better have focus, so move on to the next submenu and call Down()
  if (CurrSubMenu != -1)
    return SubMenus[CurrSubMenu]->Down();
  return false;
}
void MenuItem::Execute(void) {
  switch (m_cType) {
  case MIT_NORMAL: {
    // We need to go through the Submenus and check to see if the submenu has focus
    // if it does, than we need to execute it's function
    if (SubMenuCount > 0) {
      if (CurrSubMenu != -1) {
        if (HasInputFocus) {
          // The submenu has focus, so call it's handler as long as it's not a plist type
          char type = SubMenus[CurrSubMenu]->GetType();
          if ((type != MIT_PLIST) && (type != MIT_STATE) && (type != MIT_CUSTOM))
            SubMenus[CurrSubMenu]->CallFunc(CurrSubMenu);
          return;
        }
        // The next submenu doesn't have focus, so move on and check it's submenu to see if we
        // can Execute() that
        SubMenus[CurrSubMenu]->Execute();
      }
    } else {
      // We don't have any SubMenus so try and call our handler
      CallFunc(0);
    }
  } break;
  case MIT_PLIST: {
    // We need to go through the Submenus and check to see if the submenu has focus
    // if it does, than we need to execute it's function
    if ((SubMenuCount > 0) && (CurrSubMenu != -1) && (HasInputFocus)) {
      if (m_iFlags & MIF_INCLUDENONE) {
        // there is a <None> so take that into consideration
        if (CurrSubMenu == 0) {
          //<None> is selected
          CallFunc(-1);
          return;
        }

        if (basethis->CheckPlayerNum(Pnums[CurrSubMenu - 1])) {
          // The submenu has focus, so call it's handler
          CallFunc(Pnums[CurrSubMenu - 1]);
          return;
        }
      } else {
        // a regular plist
        if (basethis->CheckPlayerNum(Pnums[CurrSubMenu])) {
          // The submenu has focus, so call it's handler
          CallFunc(Pnums[CurrSubMenu]);
          return;
        }
      }
    }
  } break;
  case MIT_CUSTOM: {
    // We need to go through the Submenus and check to see if the submenu has focus
    // if it does, than we need to execute it's function
    int count = (m_cmInfo.GetListCount) ? (*m_cmInfo.GetListCount)() : 0;
    if (count > 0) {
      if (CurrSubMenu != -1) {
        if (HasInputFocus) {
          // The submenu has focus, so call it's handler
          CallFunc(CurrSubMenu);
          return;
        }
      }
    }
  }
  case MIT_STATE: {
    // We need to go through the Submenus and check to see if the submenu has focus
    // if it does, than we need to execute it's function
    if (SubMenuCount > 0) {
      if (CurrSubMenu != -1) {
        if (HasInputFocus) {
          // The submenu has focus, so call it's handler
          CallFunc(CurrSubMenu);
          m_iState = CurrSubMenu;
          return;
        }
      }
    }
  } break;
  }
}
void MenuItem::Draw(int x, int y, int height, int bmp, float *not_used) {
  int old_font = DLLgrtext_GetFont();
  DLLgrtext_SetFont(basethis->Game_fonts[HUD_FONT_INDEX]);

  /*
  static float aspratio;
  if(!ratio){
          //we need to calculate a ratio
          aspratio = DEFAULT_HUD_WIDTH / ((float)*base->Game_window_w);
  }

  float useratio;
  if(!ratio) useratio = aspratio; else useratio = *ratio;
  */

  uint8_t alpha_to_use = 255;

#define FLASHRATE 700.0 // alphas per second

  int16_t a = m_Alpha;
  int units = (int)FLASHRATE * (*basethis->Frametime);

  // do alphaing effect
  while (units > 0) {
    if (m_AlphaDir) {
      // find the amount to adjust
      uint8_t amount = std::min<int>(255 - a, units);
      units -= amount;
      a += amount;
      if (a >= 255) {
        a = 255;
        m_AlphaDir = false;
      }
    } else {
      // find the amount to adjust
      uint8_t amount = std::min<int>(a, units);
      units -= amount;
      a -= amount;
      if (a <= 0) {
        a = 0;
        m_AlphaDir = true;
      }
    }
  }

  m_Alpha = alpha_to_use = (uint8_t)a;

  ddgr_color color;
  int temp, maxx;
  temp = maxx = 0;
  char buff[256];
  char checked[3];
  char buffer[100];

  if (SubMenuCount <= 0) {
    DLLgrtext_SetFont(old_font);
    return;
  }

  bool nodisp_custom = false;

  // First find the max width of the submenu items
  switch (m_cType) {
  case MIT_NORMAL:
  case MIT_STATE: {
    for (int i = 0; i < SubMenuCount; i++) {
      // temp = DLLRenderHUDGetTextLineWidth(SubMenus[i]->GetTitle());
      temp = DLLgrtext_GetTextLineWidth(SubMenus[i]->GetTitle());
      if (temp > maxx)
        maxx = temp;
    }
    if (m_cType == MIT_NORMAL) {
      maxx += (x + 10);
    } else {
      snprintf(checked, sizeof(checked), "%c ", CHAR_RIGHT_ARROW);
      // maxx += (x + 10 + (useratio * ((float)DLLRenderHUDGetTextLineWidth(checked))));
      // maxx += (x + 10 + DLLRenderHUDGetTextLineWidth(checked));
      maxx += (x + 10 + DLLgrtext_GetTextLineWidth(checked));
    }
  } break;
  case MIT_PLIST: {
    // First create/update the playerlist
    SubMenuCount = 0;
    int i;
    for (i = 0; i < DLLMAX_PLAYERS; i++) {
      if (basethis->CheckPlayerNum(i)) {
        // we have a valid pnum, fill it in
        Pnums[SubMenuCount] = i;
        SubMenuCount++;
      }
    }

    if (m_iFlags & MIF_INCLUDENONE)
      SubMenuCount++; // account for <None>

    // Do some error checking and make sure values for the currsub are ok
    if (CurrSubMenu >= SubMenuCount)
      CurrSubMenu = SubMenuCount - 1;

    if (CurrSubMenu < m_iTopIndex)
      m_iTopIndex = (CurrSubMenu >= 0) ? CurrSubMenu : 0;

    int end_i = SubMenuCount;

    // Now we have a list of pnums, so draw them
    if (m_iFlags & MIF_INCLUDENONE) {
      // temp = useratio * ((float)DLLRenderHUDGetTextLineWidth(DTXT_NONE));
      // temp = DLLRenderHUDGetTextLineWidth(DTXT_NONE);
      temp = DLLgrtext_GetTextLineWidth(DTXT_NONE);
      end_i = SubMenuCount - 1;
    }

    // go through all the names and get the longest name
    for (i = 0; i < end_i; i++) {
      // temp = useratio * ((float)DLLRenderHUDGetTextLineWidth(base->Players[Pnums[i]].callsign));
      // temp = DLLRenderHUDGetTextLineWidth(base->Players[Pnums[i]].callsign);
      temp = DLLgrtext_GetTextLineWidth(basethis->Players[Pnums[i]].callsign);
      if (temp > maxx)
        maxx = temp;
    }
    // maxx += (x + 10 + (useratio * ((float)DLLRenderHUDGetTextLineWidth("[33]"))));
    // maxx += (x + 10 + DLLRenderHUDGetTextLineWidth("[33]"));
    maxx += (x + 10 + DLLgrtext_GetTextLineWidth("[33]"));
  } break;
  case MIT_CUSTOM: {
    // First find out how many submenus there are going to be
    SubMenuCount = (m_cmInfo.GetListCount) ? (*m_cmInfo.GetListCount)() : 0;

    // Do some error checking and make sure values are ok
    if (CurrSubMenu >= SubMenuCount)
      CurrSubMenu = SubMenuCount - 1;
    if (CurrSubMenu < m_iTopIndex)
      m_iTopIndex = (CurrSubMenu >= 0) ? CurrSubMenu : 0;

    if (SubMenuCount <= 0) {
      nodisp_custom = true;
    }

    // now find out the biggest string
    if (m_cmInfo.GetItem) {
      for (int i = 0; i < SubMenuCount; i++) {
        char *ptr = (*m_cmInfo.GetItem)(i);
        if (ptr) {
          // temp = useratio * ((float)DLLRenderHUDGetTextLineWidth(ptr));
          // temp = DLLRenderHUDGetTextLineWidth(ptr);
          temp = DLLgrtext_GetTextLineWidth(ptr);
          if (temp > maxx)
            maxx = temp;
        }
      }
    }
    maxx += (x + 10);
  } break;
  }

  int end_index = std::min(SubMenuCount, m_iTopIndex + ((400 - y) / height));
  int num_teams = basethis->GetNumTeams();

  if (SubMenuCount && bmp > BAD_BITMAP_HANDLE) {
    DLLrend_SetAlphaValue(210);
    DLLrend_SetZBufferState(0);
    DLLrend_SetTextureType(TT_LINEAR);
    DLLrend_SetLighting(LS_NONE);
    DLLrend_SetAlphaType(AT_CONSTANT_TEXTURE);
    DLLrend_DrawScaledBitmap(x, y, maxx, y + (height * (end_index - m_iTopIndex + 1)), bmp, 0, 0, 1, 1, 1.0, 1, NULL);
    DLLrend_SetZBufferState(1);
  }

  x += 5;

  uint8_t al;

  m_bMoreToScroll = (end_index < SubMenuCount) ? true : false;
  // m_bAtBottom = (m_bMoreToScroll)?((CurrSubMenu<end_index-1)?false:true):((CurrSubMenu<end_index)?false:true);
  m_bAtBottom =
      (m_bMoreToScroll) ? ((CurrSubMenu < end_index - 2) ? false : true) : ((CurrSubMenu < end_index) ? false : true);

  // we have something to draw
  for (int i = m_iTopIndex; i < end_index; i++) {
    switch (m_cType) {
    case MIT_PLIST: {
      if (num_teams <= 1) {
        if ((i == CurrSubMenu) && (HasInputFocus)) {
          al = alpha_to_use;
          color = GR_RGB(180, 255, 180);
        } else {
          al = 255;
          color = GR_RGB(0, 180, 0);
        }
      } else {
        if ((i == CurrSubMenu) && (HasInputFocus)) {
          al = alpha_to_use;
          color = GR_RGB(180, 255, 180);
        } else {
          al = 255;
          if (m_iFlags & MIF_INCLUDENONE) {
            if (i == 0)
              color = GR_RGB(0, 180, 0);
            else
              color = basethis->GetTeamColor(basethis->Players[Pnums[i - 1]].team);
          } else {
            color = basethis->GetTeamColor(basethis->Players[Pnums[i]].team);
          }
        }
      }
    } break;
    case MIT_CUSTOM:
    case MIT_NORMAL:
    case MIT_STATE: {
      if ((i == CurrSubMenu) && (HasInputFocus)) {
        al = alpha_to_use;
        color = GR_RGB(180, 255, 180);
      } else {
        al = 255;
        color = GR_RGB(0, 180, 0);
      }
    } break;
    }

    bool draw_text = true;
    if (i == m_iTopIndex && m_iTopIndex > 0) {
      // we need to draw the UP arrow
      char b[2];
      snprintf(b, sizeof(b), "%c", CHAR_UP_ARROW);
      // DLLRenderHUDText(color,al,0,(maxx-x)/2+x,y,b);
      DLLgrtext_SetColor(color);
      DLLgrtext_SetAlpha(al);
      DLLgrtext_Printf((maxx - x) / 2 + x, y, b);
      draw_text = false;
    }

    if (m_bMoreToScroll && i == (end_index - 1)) {
      // we need to draw the DOWN arrow
      char b[2];
      snprintf(b, sizeof(b), "%c", CHAR_DOWN_ARROW);
      // DLLRenderHUDText(color,al,0,(maxx-x)/2+x,y,b);
      DLLgrtext_SetColor(color);
      DLLgrtext_SetAlpha(al);
      DLLgrtext_Printf((maxx - x) / 2 + x, y, b);
      draw_text = false;
    }

    if (draw_text) {
      switch (m_cType) {
      case MIT_NORMAL: {
        // DLLRenderHUDText(color,al,0,x,y,SubMenus[i]->GetTitle());
        DLLgrtext_SetColor(color);
        DLLgrtext_SetAlpha(al);
        DLLgrtext_Printf(x, y, SubMenus[i]->GetTitle());

        if (i == CurrSubMenu) { // go through the submenus
          // if(ratio){
          SubMenus[i]->Draw(maxx, y, height, bmp, not_used);
          //}else{
          //	SubMenus[i]->Draw(maxx,y,height,base,bmp,&aspratio);
          //}
        }
      } break;
      case MIT_PLIST: {
        if (m_iFlags & MIF_INCLUDENONE) {

          if (i == 0) {
            // include the <None> if index is 0
            strcpy(buffer, DTXT_NONE);
          } else {
            // do the regular
            snprintf(buffer, sizeof(buffer), "%s[%d]", basethis->Players[Pnums[i - 1]].callsign, Pnums[i - 1]);
          }
        } else {
          // do the regular
          snprintf(buffer, sizeof(buffer), "%s[%d]", basethis->Players[Pnums[i]].callsign, Pnums[i]);
        }

        // DLLRenderHUDText(color,al,0,x,y,buffer);
        DLLgrtext_SetColor(color);
        DLLgrtext_SetAlpha(al);
        DLLgrtext_Printf(x, y, buffer);

      } break;
      case MIT_CUSTOM: {
        if (m_cmInfo.GetItem) {
          char *ptr = (*m_cmInfo.GetItem)(i);
          if (ptr) {
            snprintf(buffer, sizeof(buffer), "%s", ptr);
            // DLLRenderHUDText(color,al,0,x,y,buffer);
            DLLgrtext_SetColor(color);
            DLLgrtext_SetAlpha(al);
            DLLgrtext_Printf(x, y, buffer);
          }
        }
      } break;
      case MIT_STATE: {
        if (i == m_iState)
          strcpy(buff, checked);
        else
          strcpy(buff, "  ");
        strcat(buff, SubMenus[i]->GetTitle());
        // DLLRenderHUDText(color,al,0,x,y,buff);
        DLLgrtext_SetColor(color);
        DLLgrtext_SetAlpha(al);
        DLLgrtext_Printf(x, y, buff);

      } break;
      }
    }

    y += height;
  }

  if (nodisp_custom) {
    // check to see if we have focus...if we do...we got to move back to the previous menu
    if (HasInputFocus) {
      basethis->Menu.Back();
    }
    SubMenuCount = 1;
  }

  DLLgrtext_SetFont(old_font);
}
const char *MenuItem::GetTitle(void) { return m_sTitle; }
void MenuItem::SetInputFocus(void) {
  // Sets this MenuItem with Input focus, reseting it's Current Sub Menu to 0 if it has a SubMenu
  HasInputFocus = true;
  if (SubMenuCount > 0)
    CurrSubMenu = (m_iTopIndex == 0) ? 0 : m_iTopIndex + 1;
  else
    CurrSubMenu = -1;
}
void MenuItem::LoseInputFocus(void) {
  // Make this guy lose focus, Set it's Current Submenu to -1
  HasInputFocus = false;
  CurrSubMenu = -1;
}
bool MenuItem::GetFocus(void) { return HasInputFocus; }
void MenuItem::CallFunc(int value) {
  if (func)
    (*func)(value);
}
bool MenuItem::HasSubMenus(void) { return (SubMenuCount > 0); }

int MenuItem::GetCustomSubMenuCount(void) {
  if (m_cType != MIT_CUSTOM)
    return -1;

  if (!m_cmInfo.GetListCount)
    return -1;

  return (*m_cmInfo.GetListCount)();
}

char MenuItem::GetType(void) { return m_cType; }
void MenuItem::SetState(int state) {
  if (m_cType != MIT_STATE)
    return;

  if ((state < 0) || (state >= SubMenuCount))
    return;

  m_iState = state;
}

bool MenuItem::SetStateItemList(int count, ...) {
  if (m_cType != MIT_STATE)
    return false;

  // remove original states
  for (int i = 0; i < SubMenuCount; i++) {
    if (SubMenus[i]) {
      delete SubMenus[i];
      SubMenus[i] = NULL;
    }
  }
  if (SubMenus) {
    free(SubMenus);
    SubMenus = NULL;
  }

  va_list marker;
  va_start(marker, count);
  SubMenuCount = count;
  m_iState = 0;

  if ((SubMenuCount > 0) && (SubMenuCount < MAX_STATE_SUBMENUS)) {
    SubMenus = (IMenuItem **)malloc(sizeof(IMenuItem *) * SubMenuCount);
    if (!SubMenus)
      return false;
    char *string;
    for (int i = 0; i < SubMenuCount; i++) {
      string = va_arg(marker, char *);
      SubMenus[i] = new MenuItem(string, MIT_STATEITEM, 0, func);
    }
  } else
    SubMenuCount = 0;
  va_end(marker);
  return true;
}

bool MenuItem::SetStateItemListArray(int count, char **array) {
  if (m_cType != MIT_STATE)
    return false;

  // remove original states
  for (int i = 0; i < SubMenuCount; i++) {
    if (SubMenus[i]) {
      delete SubMenus[i];
      SubMenus[i] = NULL;
    }
  }
  if (SubMenus) {
    free(SubMenus);
    SubMenus = NULL;
  }

  SubMenuCount = count;
  m_iState = 0;

  if ((SubMenuCount > 0) && (SubMenuCount < MAX_STATE_SUBMENUS)) {
    SubMenus = (IMenuItem **)malloc(sizeof(IMenuItem *) * SubMenuCount);
    if (!SubMenus)
      return false;
    char *string;
    for (int i = 0; i < SubMenuCount; i++) {
      string = array[i];
      SubMenus[i] = new MenuItem(string, MIT_STATEITEM, 0, func);
    }
  } else
    SubMenuCount = 0;
  return true;
}

///////////////////////////////////////////////////
// Menu Function handlers

int GetBanPlayerList(void) { return basethis->GetNumBannedPlayers(); }

char *GetBannedPlayerString(int index) {
  static char r[MAX_CALLSIGN_SIZE + 8];
  char *ptr = basethis->GetBannedPlayerCallsign(index);
  if (ptr) {
    snprintf(r, sizeof(r), "[%d]%s", index, ptr);
    return r;
  } else
    return NULL;
}

// Removes a ban for a player, give the ban index #
void RemoveBanByIndex(int index) { basethis->RemoveBan(index); }

// Server Only.  Kicks a player from the game
void KickPlayer(int pnum) {
  if (basethis->GetLocalRole() != LR_SERVER) {
    mprintf(0, "KickPlayer Error: You're Not The Server\n");
    return;
  }
  if (pnum == basethis->GetPlayerNum()) {
    mprintf(0, "KickPlayer Error: Server can't kick themself\n");
    DLLAddHUDMessage(DTXT_SERVERCANTKICK);
    return;
  }
  if (!basethis->CheckPlayerNum(pnum)) {
    mprintf(0, "KickPlayer Error: Pnum %d not in game\n", pnum);
    DLLAddHUDMessage(DTXT_INVALIDPNUM, pnum);
    return;
  }
  basethis->SendControlMessageToPlayer(pnum, CM_KICKED);
  DLLMultiDisconnectPlayer(pnum);
}
// Server Only. Temp bans a player from the game
void BanPlayer(int pnum) {
  if (basethis->GetLocalRole() != LR_SERVER) {
    mprintf(0, "BanPlayer Error: You're Not The Server\n");
    return;
  }
  if (pnum == basethis->GetPlayerNum()) {
    mprintf(0, "BanPlayer Error: Server can't ban themself\n");
    DLLAddHUDMessage(DTXT_SERVERCANTBAN);
    return;
  }
  if (!basethis->CheckPlayerNum(pnum)) {
    mprintf(0, "BanPlayer Error: Pnum %d not in game\n", pnum);
    DLLAddHUDMessage(DTXT_INVALIDPNUM, pnum);
    return;
  }
  basethis->SendControlMessageToPlayer(pnum, CM_BANNED);
  DLLMultiDisconnectPlayer(pnum);
  basethis->BanPlayerFromGame(pnum);
}
// Server Only. Ends the current level
void EndMultiLevel(int i) {
  if (basethis->GetLocalRole() != LR_SERVER) {
    mprintf(0, "You're Not The Server\n");
    return;
  }
  DLLMultiEndLevel();
  basethis->EnableOnScreenMenu(false);
}
// Server Only. Automatically balances the teams
void BalanceTeams(int i) {
  if (basethis->GetLocalRole() != LR_SERVER) {
    mprintf(0, "You're Not The Server\n");
    return;
  }

  mprintf(0, "Balancing Teams\n");
  DPrintf(DTXT_BALANCINGTEAMS);

  // First we need to get a count of how many are on each team
  int TeamCount[DLLMAX_TEAMS];
  for (i = 0; i < DLLMAX_TEAMS; i++)
    TeamCount[i] = 0;

  for (i = 0; i < DLLMAX_PLAYERS; i++) {
    if (basethis->CheckPlayerNum(i)) {
      if (basethis->Players[i].team != -1) { // don't count dedicated server
        TeamCount[basethis->Players[i].team]++;
      }
    }
  }

  // Now determine how many should be on each team
  int optcount = 0;
  int ingame = 0;
  int numteams = basethis->GetNumTeams();

  for (i = 0; i < MAX_TEAMS; i++)
    ingame += TeamCount[i];

  optcount = ingame / numteams;
  int leftover = ingame - (optcount * numteams);

  // fill in the number of players each team should have
  int ShouldHave[MAX_TEAMS];
  for (i = 0; i < MAX_TEAMS; i++) {
    if (i < numteams) {
      ShouldHave[i] = optcount;
    } else {
      ShouldHave[i] = 0;
    }
  }

  // Make adjustments for the leftover players
  for (i = 0; i < leftover; i++)
    ShouldHave[i]++;

  for (i = 0; i < MAX_TEAMS; i++) {
    mprintf(0, "%s team should have %d players and has %d now\n",
            basethis->GetTeamString(i),
            ShouldHave[i],
            TeamCount[i]);
  }

  // Now move the players around until the teams are of the right size
  int j;
  for (i = 0; i < MAX_TEAMS; i++) {
    while (ShouldHave[i] > TeamCount[i]) {
      // We need to adjust this team by adding more players
      int pn;
      float lowseniority;
      int lowpn;

      lowseniority = 0;
      lowpn = -1;

      for (j = 0; j < MAX_TEAMS; j++) {
        if (ShouldHave[j] < TeamCount[j]) {
          // We can remove a player from this team

          // Determine which player has least seniority
          for (pn = 0; pn < DLLMAX_PLAYERS; pn++) {
            if ((basethis->CheckPlayerNum(pn)) && (basethis->Players[pn].team == j) &&
                (basethis->Players[pn].team != -1)) {
              // get the pnum, and seniority info here
              if (lowseniority < basethis->Players[pn].time_in_game) {
                lowpn = pn;
                lowseniority = basethis->Players[pn].time_in_game;
              }
            } // end if
          }   // end for
          if (lowpn != -1) {
            TeamCount[j]--;
            break;
          }

        } // end if
      }   // end for
      if (lowpn != -1) {
        // Change this player to the new team
        TeamCount[i]++;
        basethis->RequestTeamChange(i, lowpn, true);
      }
    } // end while
  }   // end for
}
// Server Only.  Switches AutoTeamSelect On/Off
void SwitchAutoTeamSelect(int i) {
  if (basethis->GetLocalRole() != LR_SERVER) {
    mprintf(0, "You're Not The Server\n");
    return;
  }
  bool on = (i == 1) ? true : false;
  basethis->AutoTeamSelect(on);
}

// Server Only. Switches allowing Team Changing on/off
void SwitchTeamChange(int i) {
  if (basethis->GetLocalRole() != LR_SERVER) {
    mprintf(0, "You're not the server\n");
    return;
  }
  bool on = (i == 1) ? true : false;
  basethis->SwitchAllowTeamChange(on);
}

// Switch's on/off Displaying a player's info
void SwitchPlayerInfo(int pnum) {
  if (pnum == -1) {
    // the <None> was selected, so make sure that it's turned off
    int curr_disp = basethis->DisplayingPlayerInfo();

    if (curr_disp == -1) // we're not displaying anyone now anyway
      return;

    // turn off the pnum given to us
    basethis->SwitchPlayerInfoDisplay(curr_disp);

    return;
  }

  if (!basethis->CheckPlayerNum(pnum)) {
    mprintf(0, "%d is invalid for playerinfo\n", pnum);
    return;
  }

  int curr_disp = basethis->DisplayingPlayerInfo();

  if (curr_disp != -1 && curr_disp != pnum)
    basethis->SwitchPlayerInfoDisplay(pnum); // turn it off

  basethis->SwitchPlayerInfoDisplay(pnum);

  // Make the call to DisplayPlayerInfo now with passing true to dedicated_server so it will
  // print the info to the console once
  basethis->DisplayPlayerInfo(true);
}

// Switch's on/off Displaying a player's name on the HUD
void SwitchHudPlayerNum(int i) { basethis->SwitchShowHudCallsignLevel(i); }

// Switch the server's Max HUD Callsign level
void SwitchServerHudPlayerName(int i) {
  if (basethis->GetLocalRole() != LR_SERVER)
    return;
  basethis->SwitchServerHudCallsignLevel(i);
}

// Change teams (or more specifically request to change teams)
void ChangeTeams(int team) {
  if ((team < 0) || (team > 3)) {
    mprintf(0, "Invalid team requested (%d)\n", team);
    return;
  }
  int curr_team = basethis->Players[basethis->GetPlayerNum()].team;

  if (curr_team == -1) {
    mprintf(0, "Dedicated Server Can't Change Teams\n");
    DPrintf(DTXT_NODEDICATEDTEAM);
    return;
  }

  if (team == curr_team) {
    mprintf(0, "You're already on the %s team\n", basethis->GetTeamString(team));
    DPrintf(DTXT_SAMETEAMCHANGE, basethis->GetTeamString(team));
    return;
  }
  basethis->RequestTeamChange(team, basethis->GetPlayerNum(), true);
}

// Send a request to DMFC to save the game stats to file
void SaveStatsToFile(int i) { basethis->CallOnSaveStatsToFile(); }

// Turns on/off Observer Mode
void SwitchObserverMode(int i) {
  object *pobj;
  int pnum = basethis->GetPlayerNum();

  if ((pnum < 0) || (pnum >= DLLMAX_PLAYERS))
    return;

  pobj = &basethis->Objects[basethis->Players[pnum].objnum];

  mprintf(0, "SwitchObserverMode %s [%d]\n", (i == 1) ? "On" : "Off", pnum);

  if (pobj->type == OBJ_OBSERVER) {
    if ((i == 0) && (basethis->CallOnAllowObserverChange(false)))
      DLLMultiSendRequestToObserve(OBSERVER_MODE_ROAM, 0, 0);
  } else {
    if ((i == 1) && (basethis->CallOnAllowObserverChange(true)))
      DLLMultiSendRequestToObserve(OBSERVER_MODE_ROAM, 1, 0);
  }
}

// Turns on/off PiggyBack mode
void SwitchPiggyBack(int pnum) {
  if (!basethis->CheckPlayerNum(pnum)) {
    return;
  }

  object *pobj;
  pobj = &basethis->Objects[basethis->Players[basethis->GetPlayerNum()].objnum];

  if (pobj->type == OBJ_OBSERVER) {
    if (basethis->CallOnAllowObserverChange(false))
      DLLMultiSendRequestToObserve(OBSERVER_MODE_ROAM, 0, 0);
  }
  if (pnum == basethis->GetPlayerNum()) {
    mprintf(0, "Returning to self\n");
    return;
  }

  if (basethis->CallOnAllowObserverChange(true)) {
    mprintf(0, "Switching to piggyback for player %d\n", pnum);
    DLLMultiSendRequestToObserve(OBSERVER_MODE_PIGGYBACK, 1, basethis->Players[pnum].objnum);
  }
}

//	Turns on/off the statistical hud messages
void SwitchStatHUDMessages(int i) {
  bool on = (i) ? true : false;
  basethis->EnableStatisticalMessages(on);
}

//	Exits the on-screen menu
void MenuExitMenu(int i) { basethis->EnableOnScreenMenu(false); }

//	Switches on/off auto saving of the stats to file on level end
void SwitchSaveStatsLevelEnd(int i) { basethis->EnableAutoSaveLevelEnd((i) ? true : false); }

//	Switches on/off auto saving of the stats to file on disconnect from the game
void SwitchSaveStatsDisconnect(int i) { basethis->EnableAutoSaveDisconnect((i) ? true : false); }

//	Switches on/off displaying the background
void SwitchMenuBackground(int i) {
  bool on = (i) ? true : false;
  basethis->EnableOnScreenMenuBackground(on);
}

//	Switches on/off displaying the netgame info
void SwitchNetGameInfo(int i) { basethis->SwitchNetGameInfoDisplay(i); }

// Switches the death message filter
void SwitchDeathMessageFilter(int i) { basethis->SetDeathMessageFilter(i); }

// brings up the team config dialog for the server
void OnScreenDisplayTeamConfig(int i) {
  if (basethis->GetLocalRole() != LR_SERVER)
    return;
  if (basethis->GetNumTeams() < 2)
    return;

  static char enable;
  enable = 2;
  basethis->StartUIWindow(UIID_TEAMPLACEMENT, &enable);
  basethis->EnableOnScreenMenu(false);
}

// Switches turning on/off displaying ship logos
void SwitchShipLogoEnable(int i) {
  bool enable = (i) ? true : false;
  basethis->EnableShipLogos(enable);
  DLLAddHUDMessage(DTXT_SHIPLOGOSMSG, (enable) ? DTXT_ENABLED : DTXT_DISABLED);
}

void SwitchAudioTauntsEnable(int i) {
  bool enable = (i) ? true : false;
  basethis->EnableAudioTaunts(enable);
  DLLAddHUDMessage(DTXT_AUDIOTAUNTSMSG, (enable) ? DTXT_ENABLED : DTXT_DISABLED);
}

// Rehashs the hosts.allow/deny lists
void RehashAllowDenyLists(int i) {
  if (basethis->GetLocalRole() != LR_SERVER)
    return;

  DLLAddHUDMessage(DTXT_REHASHINGMSG);

  basethis->RehashAllowDeny();
}

void SwitchLossPingIndicator(int i) {
  bool enable = (i) ? true : false;
  basethis->EnableLossGuage(enable);
  DLLAddHUDMessage(DTXT_LOSSINDICATOR, (enable) ? DTXT_ENABLED : DTXT_DISABLED);
}
