<commit_before>unit KM_GUIMenuLobby;
{$I KaM_Remake.inc}
interface
uses
  {$IFDEF MSWindows} Windows, {$ENDIF}
  {$IFDEF Unix} LCLType, {$ENDIF}
  Controls, Math, SysUtils,
  KM_Defaults,
  KM_Controls, KM_Maps, KM_Saves, KM_Pics, KM_InterfaceDefaults, KM_Minimap, KM_Networking;


type
  TLobbyTab = (ltDesc, ltOptions);

  TKMMenuLobby = class
  private
    fOnPageChange: TGUIEventText; //will be in ancestor class

    fMapsMP: TKMapsCollection;
    fSavesMP: TKMSavesCollection;
    fMinimap: TKMMinimap;
    fNetworking: TKMNetworking;

    fLobbyTab: TLobbyTab;
    fChatMode: TChatMode;
    fChatWhisperRecipient: Integer; //Server index of the player who will receive the whisper

    procedure CreateControls(aParent: TKMPanel);
    procedure CreateChatMenu(aParent: TKMPanel);
    procedure CreatePlayerMenu(aParent: TKMPanel);
    procedure CreateSettingsPopUp(aParent: TKMPanel);

    procedure Reset(aKind: TNetPlayerKind; aPreserveMessage: Boolean = False; aPreserveMaps: Boolean = False);
    procedure GameOptionsTabSwitch(Sender: TObject);
    procedure GameOptionsChange(Sender: TObject);
    procedure FileDownloadClick(Sender: TObject);

    procedure ChatMenuSelect(aItem: Integer);
    procedure ChatMenuClick(Sender: TObject);
    procedure ChatMenuShow(Sender: TObject);

    procedure PlayerMenuClick(Sender: TObject);
    procedure PlayerMenuShow(Sender: TObject);

    procedure PlayersSetupChange(Sender: TObject);
    procedure MapColumnClick(aValue: Integer);
    procedure MapTypeChange(Sender: TObject);
    procedure MapList_SortUpdate(Sender: TObject);
    procedure MapList_ScanUpdate(Sender: TObject);
    procedure RefreshMapList(aJumpToSelected: Boolean);
    procedure RefreshSaveList(aJumpToSelected: Boolean);
    procedure MapChange(Sender: TObject);
    procedure PostKeyDown(Sender: TObject; Key: Word);

    procedure MinimapLocClick(aValue: Integer);

    procedure Lobby_OnDisconnect(const aData: UnicodeString);
    procedure Lobby_OnGameOptions(Sender: TObject);
    procedure Lobby_OnMapName(const aData: UnicodeString);
    procedure Lobby_OnMapMissing(const aData: UnicodeString);
    procedure Lobby_OnMessage(const aText: UnicodeString);
    procedure Lobby_OnPingInfo(Sender: TObject);
    procedure Lobby_OnPlayersSetup(Sender: TObject);
    procedure Lobby_OnReassignedToHost(Sender: TObject);
    procedure Lobby_OnFileTransferProgress(aTotal, aProgress: Cardinal);

    function DetectMapType: Integer;
    procedure BackClick(Sender: TObject);
    procedure SettingsClick(Sender: TObject);
    procedure StartClick(Sender: TObject);
  protected
    Panel_Lobby: TKMPanel;
      Panel_LobbySettings: TKMPanel;
        Edit_LobbyDescription: TKMEdit;
        Edit_LobbyPassword: <PASSWORD>;
        Button_LobbySettingsResetBans: TKMButton;
        Button_LobbySettingsSave: TKMButton;
        Button_LobbySettingsCancel: TKMButton;

      Menu_Chat: TKMPopUpMenu;
      Menu_Host: TKMPopUpMenu;

      Panel_LobbyServerName: TKMPanel;
        Label_LobbyServerName: TKMLabel;

      Panel_LobbyPlayers: TKMPanel;
        CheckBox_LobbyHostControl: TKMCheckBox;
        CheckBox_LobbyRandomizeTeamLocations: TKMCheckBox;
        Image_LobbyFlag: array [0..MAX_LOBBY_SLOTS-1] of TKMImage;
        DropBox_LobbyPlayerSlot: array [0..MAX_LOBBY_SLOTS-1] of TKMDropList;
        Label_LobbyPlayer: array [0..MAX_LOBBY_SLOTS-1] of TKMLabel;
        DropBox_LobbyLoc: array [0..MAX_LOBBY_SLOTS-1] of TKMDropList;
        DropBox_LobbyTeam: array [0..MAX_LOBBY_SLOTS-1] of TKMDropList;
        DropBox_LobbyColors: array [0..MAX_LOBBY_SLOTS-1] of TKMDropColumns;
        Image_LobbyReady: array [0..MAX_LOBBY_SLOTS-1] of TKMImage;
        Label_LobbyPing: array [0..MAX_LOBBY_SLOTS-1] of TKMLabel;

      Panel_LobbySetup: TKMPanel;
        Radio_LobbyMapType: TKMRadioGroup;
        DropCol_LobbyMaps: TKMDropColumns;
        Label_LobbyMapName: TKMLabel;
        Panel_LobbySetupTransfer: TKMPanel;
          Button_LobbySetupDownload: TKMButton;
          PercentBar_LobbySetupProgress: TKMPercentBar;
          CheckBox_LobbySetupOverwrite: TKMCheckbox;
        Panel_LobbySetupMinimap: TKMPanel;
          MinimapView_Lobby: TKMMinimapView;
        Button_LobbyTabDesc, Button_LobbyTabOptions: TKMButton;
        Panel_LobbySetupDesc: TKMPanel;
          Memo_LobbyMapDesc: TKMMemo;
        Panel_LobbySetupOptions: TKMPanel;
          TrackBar_LobbyPeacetime: TKMTrackBar;
          TrackBar_LobbySpeedPT, TrackBar_LobbySpeedAfterPT: TKMTrackBar;

      Memo_LobbyPosts: TKMMemo;
      Button_LobbyPost: TKMButtonFlat;
      Edit_LobbyPost: TKMEdit;

      Button_LobbyBack: TKMButton;
      Button_LobbyChangeSettings: TKMButton;
      Button_LobbyStart: TKMButton;
  public
    constructor Create(aParent: TKMPanel; aOnPageChange: TGUIEventText);
    destructor Destroy; override;

    function GetChatText: string;
    function GetChatMessages: string;
    procedure Show(aKind: TNetPlayerKind; aNetworking: TKMNetworking; aMainHeight: Word);
    procedure Lobby_Resize(aMainHeight: Word);
    procedure UpdateState;
  end;


implementation
uses KM_ResTexts, KM_ResLocales, KM_Utils, KM_Sound, KM_ResSound, KM_RenderUI, KM_Resource, KM_ResFonts, KM_NetPlayersList, KM_Main, KM_GameApp;


{ TKMGUIMenuLobby }
constructor TKMMenuLobby.Create(aParent: TKMPanel; aOnPageChange: TGUIEventText);
begin
  inherited Create;

  fOnPageChange := aOnPageChange;

  fMinimap := TKMMinimap.Create(True, True);

  fMapsMP := TKMapsCollection.Create(True);
  fSavesMP := TKMSavesCollection.Create;

  CreateControls(aParent);
  CreateChatMenu(aParent);
  CreatePlayerMenu(aParent);
  CreateSettingsPopUp(aParent);
end;


destructor TKMMenuLobby.Destroy;
begin
  fMapsMP.Free;
  fSavesMP.Free;
  fMinimap.Free;

  inherited;
end;


procedure TKMMenuLobby.CreateControls(aParent: TKMPanel);
const
  CW = 690; C1 = 35; C2 = 195; C3 = 355; C4 = 445; C5 = 570; C6 = 650;
var
  I, K, OffY: Integer;
begin
  Panel_Lobby := TKMPanel.Create(aParent,0,0,aParent.Width, aParent.Height);
  Panel_Lobby.AnchorsStretch;

    //Server Name
    Panel_LobbyServerName := TKMPanel.Create(Panel_Lobby, 30, 30, CW, 30);
      TKMBevel.Create(Panel_LobbyServerName,   0,  0, CW, 30);
      Label_LobbyServerName := TKMLabel.Create(Panel_LobbyServerName, 10, 10, CW-20, 20, '', fnt_Metal, taLeft);

    //Players
    Panel_LobbyPlayers := TKMPanel.Create(Panel_Lobby, 30, 65, CW, 316);
      TKMBevel.Create(Panel_LobbyPlayers,  0,  0, CW, 316);

      CheckBox_LobbyHostControl := TKMCheckBox.Create(Panel_LobbyPlayers, 10, 10, 450, 20, gResTexts[TX_LOBBY_HOST_DOES_SETUP], fnt_Metal);
      CheckBox_LobbyHostControl.OnClick := PlayersSetupChange;

      CheckBox_LobbyRandomizeTeamLocations := TKMCheckBox.Create(Panel_LobbyPlayers, 10, 28, 450, 20, gResTexts[TX_LOBBY_RANDOMIZE_LOCATIONS], fnt_Metal);
      CheckBox_LobbyRandomizeTeamLocations.OnClick := PlayersSetupChange;

      //Column titles
      TKMLabel.Create(Panel_LobbyPlayers, C1, 50, 150,  20, gResTexts[TX_LOBBY_HEADER_PLAYERS], fnt_Outline, taLeft);
      TKMLabel.Create(Panel_LobbyPlayers, C2, 50, 150,  20, gResTexts[TX_LOBBY_HEADER_STARTLOCATION], fnt_Outline, taLeft);
      TKMLabel.Create(Panel_LobbyPlayers, C3, 50,  80,  20, gResTexts[TX_LOBBY_HEADER_TEAM], fnt_Outline, taLeft);
      TKMLabel.Create(Panel_LobbyPlayers, C4, 50,  80,  20, gResTexts[TX_LOBBY_HEADER_FLAGCOLOR], fnt_Outline, taLeft);
      TKMLabel.Create(Panel_LobbyPlayers, C5, 50, gResTexts[TX_LOBBY_HEADER_READY], fnt_Outline, taCenter);
      TKMLabel.Create(Panel_LobbyPlayers, C6, 50, gResTexts[TX_LOBBY_HEADER_PING], fnt_Outline, taCenter);

      for I := 0 to MAX_LOBBY_SLOTS - 1 do
      begin
        OffY := 70 + I * 24;
        Image_LobbyFlag[I] := TKMImage.Create(Panel_LobbyPlayers, 10, OffY, 20, 20, 0, rxGuiMain);
        Image_LobbyFlag[I].ImageCenter;
        Image_LobbyFlag[I].Tag := I+1; //Required for PlayerMenuShow
        Image_LobbyFlag[I].OnClick := PlayerMenuShow;

        Label_LobbyPlayer[I] := TKMLabel.Create(Panel_LobbyPlayers, C1, OffY+2, 150, 20, '', fnt_Grey, taLeft);
        Label_LobbyPlayer[I].Hide;

        DropBox_LobbyPlayerSlot[I] := TKMDropList.Create(Panel_LobbyPlayers, C1, OffY, 150, 20, fnt_Grey, '', bsMenu);
        DropBox_LobbyPlayerSlot[I].Add(gResTexts[TX_LOBBY_SLOT_OPEN]); //Player can join into this slot
        DropBox_LobbyPlayerSlot[I].Add(gResTexts[TX_LOBBY_SLOT_CLOSED]); //Closed, nobody can join it
        DropBox_LobbyPlayerSlot[I].Add(gResTexts[TX_LOBBY_SLOT_AI_PLAYER]); //This slot is an AI player
        DropBox_LobbyPlayerSlot[I].ItemIndex := 0; //Open
        DropBox_LobbyPlayerSlot[I].OnChange := PlayersSetupChange;

        DropBox_LobbyLoc[I] := TKMDropList.Create(Panel_LobbyPlayers, C2, OffY, 150, 20, fnt_Grey, '', bsMenu);
        DropBox_LobbyLoc[I].Add(gResTexts[TX_LOBBY_RANDOM], LOC_RANDOM);
        DropBox_LobbyLoc[I].OnChange := PlayersSetupChange;

        DropBox_LobbyTeam[I] := TKMDropList.Create(Panel_LobbyPlayers, C3, OffY, 80, 20, fnt_Grey, '', bsMenu);
        DropBox_LobbyTeam[I].Add('-');
        for K := 1 to 4 do DropBox_LobbyTeam[I].Add(IntToStr(K));
        DropBox_LobbyTeam[I].OnChange := PlayersSetupChange;

        DropBox_LobbyColors[I] := TKMDropColumns.Create(Panel_LobbyPlayers, C4, OffY, 80, 20, fnt_Grey, '', bsMenu);
        DropBox_LobbyColors[I].SetColumns(fnt_Outline, [''], [0]);
        DropBox_LobbyColors[I].List.ShowHeader := False;
        DropBox_LobbyColors[I].FadeImageWhenDisabled := False;
        DropBox_LobbyColors[I].Add(MakeListRow([''], [$FFFFFFFF], [MakePic(rxGuiMain, 31)], 0));
        for K := Low(MP_TEAM_COLORS) to High(MP_TEAM_COLORS) do
          DropBox_LobbyColors[I].Add(MakeListRow([''], [MP_TEAM_COLORS[K]], [MakePic(rxGuiMain, 30)]));
        DropBox_LobbyColors[I].OnChange := PlayersSetupChange;

        Image_LobbyReady[I] := TKMImage.Create(Panel_LobbyPlayers, C5-8, OffY, 16, 16, 32, rxGuiMain);
        Label_LobbyPing[I] := TKMLabel.Create(Panel_LobbyPlayers, C6, OffY, '', fnt_Metal, taCenter);
      end;

    //Chat area
    Memo_LobbyPosts := TKMMemo.Create(Panel_Lobby, 30, 386, CW, 294, fnt_Arial, bsMenu);
    Memo_LobbyPosts.Anchors := [anLeft, anTop, anBottom];
    Memo_LobbyPosts.AutoWrap := True;
    Memo_LobbyPosts.IndentAfterNL := True; //Don't let players fake system messages
    Memo_LobbyPosts.ScrollDown := True;
    Button_LobbyPost := TKMButtonFlat.Create(Panel_Lobby, 30, 683, 30, 20, 0);
    Button_LobbyPost.CapOffsetY := -11;
    Button_LobbyPost.Font := fnt_Grey;
    Button_LobbyPost.OnClick := ChatMenuShow;
    Button_LobbyPost.Anchors := [anLeft, anBottom];

    Edit_LobbyPost := TKMEdit.Create(Panel_Lobby, 60, 683, CW, 20, fnt_Arial);
    Edit_LobbyPost.OnKeyDown := PostKeyDown;
    Edit_LobbyPost.Anchors := [anLeft, anBottom];
    Edit_LobbyPost.ShowColors := True;

    //Setup
    Panel_LobbySetup := TKMPanel.Create(Panel_Lobby, 725, 30, 270, 712);
    Panel_LobbySetup.Anchors := [anLeft, anTop, anBottom];
      with TKMBevel.Create(Panel_LobbySetup,  0,  0, 270, 712) do AnchorsStretch;
      Radio_LobbyMapType := TKMRadioGroup.Create(Panel_LobbySetup, 10, 10, 250, 80, fnt_Metal);
      Radio_LobbyMapType.Add(gResTexts[TX_LOBBY_MAP_BUILD]);
      Radio_LobbyMapType.Add(gResTexts[TX_LOBBY_MAP_FIGHT]);
      Radio_LobbyMapType.Add(gResTexts[TX_LOBBY_MAP_COOP]);
      Radio_LobbyMapType.Add(gResTexts[TX_LOBBY_MAP_SPECIAL]);
      Radio_LobbyMapType.Add(gResTexts[TX_LOBBY_MAP_SAVED]);
      Radio_LobbyMapType.ItemIndex := 0;
      Radio_LobbyMapType.OnChange := MapTypeChange;

      DropCol_LobbyMaps := TKMDropColumns.Create(Panel_LobbySetup, 10, 95, 250, 20, fnt_Metal, gResTexts[TX_LOBBY_MAP_SELECT], bsMenu);
      DropCol_LobbyMaps.DropCount := 19;
      DropCol_LobbyMaps.DropWidth := 430; //Wider to fit mapnames well
      DropCol_LobbyMaps.SetColumns(fnt_Outline, [gResTexts[TX_MENU_MAP_TITLE], '#', gResTexts[TX_MENU_MAP_SIZE]], [0, 290, 320]);
      DropCol_LobbyMaps.List.OnColumnClick := MapColumnClick;
      DropCol_LobbyMaps.List.SearchColumn := 0;
      DropCol_LobbyMaps.OnChange := MapChange;
      Label_LobbyMapName := TKMLabel.Create(Panel_LobbySetup, 10, 95, 250, 20, '', fnt_Metal, taLeft);

      Panel_LobbySetupMinimap := TKMPanel.Create(Panel_LobbySetup, 0, 120, 270, 200);
        TKMBevel.Create(Panel_LobbySetupMinimap, 35, 0, 199, 199);
        MinimapView_Lobby := TKMMinimapView.Create(Panel_LobbySetupMinimap, 39, 4, 191, 191);
        MinimapView_Lobby.ShowLocs := True; //In the minimap we want player locations to be shown
        MinimapView_Lobby.OnLocClick := MinimapLocClick;

      Panel_LobbySetupTransfer := TKMPanel.Create(Panel_LobbySetup, 0, 120, 270, 200);
        CheckBox_LobbySetupOverwrite := TKMCheckbox.Create(Panel_LobbySetupTransfer, 10, 0, 250, 16, gResTexts[TX_LOBBY_DOWNLOAD_OVERWRITE], fnt_Game);
        CheckBox_LobbySetupOverwrite.OnClick := FileDownloadClick;
        Button_LobbySetupDownload := TKMButton.Create(Panel_LobbySetupTransfer, 10, 0, 250, 30, gResTexts[TX_LOBBY_DOWNLOAD], bsMenu);
        Button_LobbySetupDownload.OnClick := FileDownloadClick;
        PercentBar_LobbySetupProgress := TKMPercentBar.Create(Panel_LobbySetupTransfer, 10, 0, 250, 24, fnt_Game);
      Panel_LobbySetupTransfer.Hide;

      Button_LobbyTabDesc := TKMButton.Create(Panel_LobbySetup, 10, 324, 125, 20, gResTexts[TX_LOBBY_MAP_DESCRIPTION], bsMenu);
      Button_LobbyTabDesc.OnClick := GameOptionsTabSwitch;
      Button_LobbyTabDesc.Hide;
      Button_LobbyTabOptions := TKMButton.Create(Panel_LobbySetup, 10+125, 324, 125, 20, gResTexts[TX_LOBBY_OPTIONS], bsMenu);
      Button_LobbyTabOptions.OnClick := GameOptionsTabSwitch;
      Button_LobbyTabOptions.Hide;

      Panel_LobbySetupDesc := TKMPanel.Create(Panel_LobbySetup, 0, 324, 270, 218);
      Panel_LobbySetupDesc.Anchors := [anLeft, anTop, anBottom];
        Memo_LobbyMapDesc := TKMMemo.Create(Panel_LobbySetupDesc, 10, 0, 250, 218, fnt_Game, bsMenu);
        Memo_LobbyMapDesc.Anchors := [anLeft,anTop,anBottom];
        Memo_LobbyMapDesc.AutoWrap := True;
        Memo_LobbyMapDesc.ItemHeight := 16;

      Panel_LobbySetupOptions := TKMPanel.Create(Panel_LobbySetup, 0, 542, 270, 170);
      Panel_LobbySetupOptions.Anchors := [anLeft,anBottom];
        with TKMLabel.Create(Panel_LobbySetupOptions, 10, 4, 250, 20, gResTexts[TX_LOBBY_GAME_OPTIONS], fnt_Outline, taLeft) do Anchors := [anLeft,anBottom];
        TrackBar_LobbyPeacetime := TKMTrackBar.Create(Panel_LobbySetupOptions, 10, 26, 250, 0, 120);
        TrackBar_LobbyPeacetime.Anchors := [anLeft,anBottom];
        TrackBar_LobbyPeacetime.Caption := gResTexts[TX_LOBBY_PEACETIME];
        TrackBar_LobbyPeacetime.Step := 5; //Round to 5min steps
        TrackBar_LobbyPeacetime.OnChange := GameOptionsChange;

        TrackBar_LobbySpeedPT := TKMTrackBar.Create(Panel_LobbySetupOptions, 10, 72, 250, 1, 5);
        TrackBar_LobbySpeedPT.Anchors := [anLeft,anBottom];
        TrackBar_LobbySpeedPT.Caption := gResTexts[TX_LOBBY_GAMESPEED_PEACETIME];
        TrackBar_LobbySpeedPT.ThumbWidth := 45; //Enough to fit 'x2.5'
        TrackBar_LobbySpeedPT.OnChange := GameOptionsChange;

        TrackBar_LobbySpeedAfterPT := TKMTrackBar.Create(Panel_LobbySetupOptions, 10, 116, 250, 1, 5);
        TrackBar_LobbySpeedAfterPT.Anchors := [anLeft,anBottom];
        TrackBar_LobbySpeedAfterPT.Caption := gResTexts[TX_LOBBY_GAMESPEED];
        TrackBar_LobbySpeedAfterPT.ThumbWidth := 45; //Enough to fit 'x2.5'
        TrackBar_LobbySpeedAfterPT.OnChange := GameOptionsChange;

    Button_LobbyBack := TKMButton.Create(Panel_Lobby, 30, 712, 220, 30, gResTexts[TX_LOBBY_QUIT], bsMenu);
    Button_LobbyBack.Anchors := [anLeft, anBottom];
    Button_LobbyBack.OnClick := BackClick;

    Button_LobbyChangeSettings := TKMButton.Create(Panel_Lobby, 265, 712, 220, 30, gResTexts[TX_LOBBY_ROOMSETTINGS], bsMenu);
    Button_LobbyChangeSettings.Anchors := [anLeft, anBottom];
    Button_LobbyChangeSettings.OnClick := SettingsClick;

    Button_LobbyStart := TKMButton.Create(Panel_Lobby, 500, 712, 220, 30, NO_TEXT, bsMenu);
    Button_LobbyStart.Anchors := [anLeft, anBottom];
    Button_LobbyStart.OnClick := StartClick;
end;


procedure TKMMenuLobby.CreateChatMenu(aParent: TKMPanel);
begin
  Menu_Chat := TKMPopUpMenu.Create(aParent, 140);
  Menu_Chat.Anchors := [anLeft, anBottom];
  //Menu gets populated right before show
  Menu_Chat.AddItem(NO_TEXT);
  Menu_Chat.OnClick := ChatMenuClick;
end;


procedure TKMMenuLobby.CreatePlayerMenu(aParent: TKMPanel);
begin
  Menu_Host := TKMPopUpMenu.Create(aParent, 220);
  Menu_Host.AddItem(gResTexts[TX_LOBBY_PLAYER_KICK]);
  Menu_Host.AddItem(gResTexts[TX_LOBBY_PLAYER_BAN]);
  //Menu_Host.AddItem(fTextMain[TX_LOBBY_PLAYER_SETHOST]);
  Menu_Host.OnClick := PlayerMenuClick;
end;


procedure TKMMenuLobby.CreateSettingsPopUp(aParent: TKMPanel);
begin
  Panel_LobbySettings := TKMPanel.Create(aParent, 362, 250, 320, 300);
  Panel_LobbySettings.Anchors := [];
    TKMBevel.Create(Panel_LobbySettings, -1000,  -1000, 4000, 4000);
    TKMImage.Create(Panel_LobbySettings, -20, -75, 340, 310, 15, rxGuiMain);
    TKMBevel.Create(Panel_LobbySettings,   0,  0, 320, 300);
    TKMLabel.Create(Panel_LobbySettings,  20, 10, 280, 20, gResTexts[TX_LOBBY_ROOMSETTINGS], fnt_Outline, taCenter);

    TKMLabel.Create(Panel_LobbySettings, 20, 50, 156, 20, gResTexts[TX_LOBBY_ROOM_DESCRIPTION], fnt_Outline, taLeft);
    Edit_LobbyDescription := TKMEdit.Create(Panel_LobbySettings, 20, 70, 152, 20, fnt_Grey);
    Edit_LobbyDescription.AllowedChars := acText;

    TKMLabel.Create(Panel_LobbySettings, 20, 100, 156, 20, gResTexts[TX_LOBBY_ROOM_PASSWORD], fnt_Outline, taLeft);
    Edit_LobbyPassword := TKMEdit.Create(Panel_LobbySettings, 20, 120, 152, 20, fnt_Grey);
    Edit_LobbyPassword.AllowedChars := acText;

    Button_LobbySettingsResetBans := TKMButton.Create(Panel_LobbySettings, 20, 160, 200, 20, gResTexts[TX_LOBBY_RESET_BANS], bsMenu);
    Button_LobbySettingsResetBans.OnClick := SettingsClick;

    Button_LobbySettingsSave := TKMButton.Create(Panel_LobbySettings, 20, 210, 280, 30, gResTexts[TX_LOBBY_ROOM_OK], bsMenu);
    Button_LobbySettingsSave.OnClick := SettingsClick;
    Button_LobbySettingsCancel := TKMButton.Create(Panel_LobbySettings, 20, 250, 280, 30, gResTexts[TX_LOBBY_ROOM_CANCEL], bsMenu);
    Button_LobbySettingsCancel.OnClick := SettingsClick;
end;


procedure TKMMenuLobby.ChatMenuSelect(aItem: Integer);

  procedure UpdateButtonCaption(aCaption: UnicodeString; aColor: Cardinal = 0);
  var CapWidth: Integer;
  const MIN_SIZE = 80; //Minimum size for the button
  begin
    //Update button width according to selected item
    CapWidth := gResource.Fonts.GetTextSize(aCaption, Button_LobbyPost.Font).X;
    CapWidth := Max(MIN_SIZE, CapWidth+10); //Apply minimum size
    if aColor <> 0 then
      aCaption := WrapColor(aCaption, aColor);
    Button_LobbyPost.Caption := aCaption;
    Button_LobbyPost.Width := CapWidth;

    Edit_LobbyPost.AbsLeft := Button_LobbyPost.AbsLeft + Button_LobbyPost.Width + 4;
    Edit_LobbyPost.Width := Memo_LobbyPosts.Width - Button_LobbyPost.Width - 4;
  end;

var I: Integer;
begin
  //All
  if aItem = -1 then
  begin
    fChatMode := cmAll;
    UpdateButtonCaption(gResTexts[TX_CHAT_ALL]);
    Edit_LobbyPost.DrawOutline := False; //No outline for All
  end
  else
    //Team
    if aItem = -2 then
    begin
      fChatMode := cmTeam;
      UpdateButtonCaption(gResTexts[TX_CHAT_TEAM], $FF66FF66);
      Edit_LobbyPost.DrawOutline := True;
      Edit_LobbyPost.OutlineColor := $FF66FF66;
    end
    else
    //Whisper
    begin
      I := fNetworking.NetPlayers.ServerToLocal(aItem);
      if I <> -1 then
      begin
        fChatMode := cmWhisper;
        Edit_LobbyPost.DrawOutline := True;
        Edit_LobbyPost.OutlineColor := $FF00B9FF;
        with fNetworking.NetPlayers[I] do
        begin
          fChatWhisperRecipient := IndexOnServer;
          UpdateButtonCaption(UnicodeString(Nikname), IfThen(FlagColorID <> 0, FlagColorToTextColor(FlagColor), 0));
        end;
      end;
    end;
end;


procedure TKMMenuLobby.ChatMenuClick(Sender: TObject);
begin
  if Menu_Chat.ItemIndex <> -1 then
    ChatMenuSelect(Menu_Chat.ItemTags[Menu_Chat.ItemIndex]);
end;


procedure TKMMenuLobby.ChatMenuShow(Sender: TObject);
var
  C: TKMControl;
  I: Integer;
  n: TKMNetPlayerInfo;
begin
  //Populate menu with right options
  Menu_Chat.Clear;

  Menu_Chat.AddItem(gResTexts[TX_CHAT_ALL], -1);
  //Only show "Team" if the player is on a team
  if fNetworking.NetPlayers[fNetworking.MyIndex].Team <> 0 then
    Menu_Chat.AddItem('[$66FF66]' + gResTexts[TX_CHAT_TEAM], -2);

  for I := 1 to fNetworking.NetPlayers.Count do
  if I <> fNetworking.MyIndex then //Can't whisper to yourself
  begin
    n := fNetworking.NetPlayers[I];

    if n.IsHuman and n.Connected and not n.Dropped then
      Menu_Chat.AddItem(UnicodeString(n.NiknameColored), n.IndexOnServer);
  end;

  C := TKMControl(Sender);
  //Position the menu next to the icon, but do not overlap players name
  Menu_Chat.ShowAt(C.AbsLeft, C.AbsTop - Menu_Chat.Height);
end;


//Try to detect which kind it is
function TKMMenuLobby.DetectMapType: Integer;
begin
  //Default
  Result := 0;

  case fNetworking.SelectGameKind of
    ngk_Map:  if fNetworking.MapInfo.IsCoop then
                Result := 2
              else
              if fNetworking.MapInfo.IsSpecial then
                Result := 3
              else
              if fNetworking.MapInfo.MissionMode = mm_Tactic then
                Result := 1;
    ngk_Save: Result := 4;
  end;
end;


//Access text that user was typing to copy it over to gameplay chat
function TKMMenuLobby.GetChatText: string;
begin
  Result := Edit_LobbyPost.Text;
end;


//Access chat messages history to copy it over to gameplay chat
function TKMMenuLobby.GetChatMessages: string;
begin
  Result := Memo_LobbyPosts.Text;
end;


procedure TKMMenuLobby.Show(aKind: TNetPlayerKind; aNetworking: TKMNetworking; aMainHeight: Word);
begin
  fNetworking := aNetworking;

  Reset(aKind);

  //Events binding is the same for Host and Joiner because of stand-alone Server
  //E.g. If Server fails, Host can be disconnected from it as well as a Joiner
  fNetworking.OnTextMessage  := Lobby_OnMessage;
  fNetworking.OnPlayersSetup := Lobby_OnPlayersSetup;
  fNetworking.OnGameOptions  := Lobby_OnGameOptions;
  fNetworking.OnMapName      := Lobby_OnMapName;
  fNetworking.OnMapMissing   := Lobby_OnMapMissing;
  fNetworking.OnPingInfo     := Lobby_OnPingInfo;
  //fNetworking.OnStartMap - already assigned in fGameApp when Net is created
  //fNetworking.OnStartSave - already assigned in fGameApp when Net is created
  fNetworking.OnDisconnect   := Lobby_OnDisconnect;
  fNetworking.OnReassignedHost := Lobby_OnReassignedToHost;
  fNetworking.OnFileTransferProgress := Lobby_OnFileTransferProgress;

  ChatMenuSelect(-1); //All

  Panel_Lobby.Show;
  Lobby_Resize(aMainHeight);
end;


procedure TKMMenuLobby.Lobby_Resize(aMainHeight: Word);
begin
  if not Panel_Lobby.Visible then Exit;
  //If the vertical screen height goes below a certain amount we need to switch to "compact" mode
  if aMainHeight >= 660 then
  begin
    //We have enough space, so stack Options below Desc
    Panel_LobbySetupDesc.Top := 324;
    Panel_LobbySetupDesc.Height := aMainHeight-550;
    Panel_LobbySetupOptions.Top := 324 + Panel_LobbySetupDesc.Height;
    Button_LobbyTabDesc.Hide;
    Button_LobbyTabOptions.Hide;
    Panel_LobbySetupDesc.Show;
    Panel_LobbySetupOptions.Show;
  end
  else
  begin
    //Not enough space, so enabled tabbed view
    Panel_LobbySetupDesc.Top := 350;
    Panel_LobbySetupDesc.Height := aMainHeight-420;
    Panel_LobbySetupOptions.Top := 350;
    Button_LobbyTabDesc.Show;
    Button_LobbyTabOptions.Show;
    GameOptionsTabSwitch(nil);
  end;
end;
<commit_msg>Tweaked lobby layout slightly, now certain tall arial characters in chat EditBox don't overflow at the bottom (+2px to EditBox height)<commit_after>unit KM_GUIMenuLobby;
{$I KaM_Remake.inc}
interface
uses
  {$IFDEF MSWindows} Windows, {$ENDIF}
  {$IFDEF Unix} LCLType, {$ENDIF}
  Controls, Math, SysUtils,
  KM_Defaults,
  KM_Controls, KM_Maps, KM_Saves, KM_Pics, KM_InterfaceDefaults, KM_Minimap, KM_Networking;


type
  TLobbyTab = (ltDesc, ltOptions);

  TKMMenuLobby = class
  private
    fOnPageChange: TGUIEventText; //will be in ancestor class

    fMapsMP: TKMapsCollection;
    fSavesMP: TKMSavesCollection;
    fMinimap: TKMMinimap;
    fNetworking: TKMNetworking;

    fLobbyTab: TLobbyTab;
    fChatMode: TChatMode;
    fChatWhisperRecipient: Integer; //Server index of the player who will receive the whisper

    procedure CreateControls(aParent: TKMPanel);
    procedure CreateChatMenu(aParent: TKMPanel);
    procedure CreatePlayerMenu(aParent: TKMPanel);
    procedure CreateSettingsPopUp(aParent: TKMPanel);

    procedure Reset(aKind: TNetPlayerKind; aPreserveMessage: Boolean = False; aPreserveMaps: Boolean = False);
    procedure GameOptionsTabSwitch(Sender: TObject);
    procedure GameOptionsChange(Sender: TObject);
    procedure FileDownloadClick(Sender: TObject);

    procedure ChatMenuSelect(aItem: Integer);
    procedure ChatMenuClick(Sender: TObject);
    procedure ChatMenuShow(Sender: TObject);

    procedure PlayerMenuClick(Sender: TObject);
    procedure PlayerMenuShow(Sender: TObject);

    procedure PlayersSetupChange(Sender: TObject);
    procedure MapColumnClick(aValue: Integer);
    procedure MapTypeChange(Sender: TObject);
    procedure MapList_SortUpdate(Sender: TObject);
    procedure MapList_ScanUpdate(Sender: TObject);
    procedure RefreshMapList(aJumpToSelected: Boolean);
    procedure RefreshSaveList(aJumpToSelected: Boolean);
    procedure MapChange(Sender: TObject);
    procedure PostKeyDown(Sender: TObject; Key: Word);

    procedure MinimapLocClick(aValue: Integer);

    procedure Lobby_OnDisconnect(const aData: UnicodeString);
    procedure Lobby_OnGameOptions(Sender: TObject);
    procedure Lobby_OnMapName(const aData: UnicodeString);
    procedure Lobby_OnMapMissing(const aData: UnicodeString);
    procedure Lobby_OnMessage(const aText: UnicodeString);
    procedure Lobby_OnPingInfo(Sender: TObject);
    procedure Lobby_OnPlayersSetup(Sender: TObject);
    procedure Lobby_OnReassignedToHost(Sender: TObject);
    procedure Lobby_OnFileTransferProgress(aTotal, aProgress: Cardinal);

    function DetectMapType: Integer;
    procedure BackClick(Sender: TObject);
    procedure SettingsClick(Sender: TObject);
    procedure StartClick(Sender: TObject);
  protected
    Panel_Lobby: TKMPanel;
      Panel_LobbySettings: TKMPanel;
        Edit_LobbyDescription: TKMEdit;
        Edit_LobbyPassword: <PASSWORD>;
        Button_LobbySettingsResetBans: TKMButton;
        Button_LobbySettingsSave: TKMButton;
        Button_LobbySettingsCancel: TKMButton;

      Menu_Chat: TKMPopUpMenu;
      Menu_Host: TKMPopUpMenu;

      Panel_LobbyServerName: TKMPanel;
        Label_LobbyServerName: TKMLabel;

      Panel_LobbyPlayers: TKMPanel;
        CheckBox_LobbyHostControl: TKMCheckBox;
        CheckBox_LobbyRandomizeTeamLocations: TKMCheckBox;
        Image_LobbyFlag: array [0..MAX_LOBBY_SLOTS-1] of TKMImage;
        DropBox_LobbyPlayerSlot: array [0..MAX_LOBBY_SLOTS-1] of TKMDropList;
        Label_LobbyPlayer: array [0..MAX_LOBBY_SLOTS-1] of TKMLabel;
        DropBox_LobbyLoc: array [0..MAX_LOBBY_SLOTS-1] of TKMDropList;
        DropBox_LobbyTeam: array [0..MAX_LOBBY_SLOTS-1] of TKMDropList;
        DropBox_LobbyColors: array [0..MAX_LOBBY_SLOTS-1] of TKMDropColumns;
        Image_LobbyReady: array [0..MAX_LOBBY_SLOTS-1] of TKMImage;
        Label_LobbyPing: array [0..MAX_LOBBY_SLOTS-1] of TKMLabel;

      Panel_LobbySetup: TKMPanel;
        Radio_LobbyMapType: TKMRadioGroup;
        DropCol_LobbyMaps: TKMDropColumns;
        Label_LobbyMapName: TKMLabel;
        Panel_LobbySetupTransfer: TKMPanel;
          Button_LobbySetupDownload: TKMButton;
          PercentBar_LobbySetupProgress: TKMPercentBar;
          CheckBox_LobbySetupOverwrite: TKMCheckbox;
        Panel_LobbySetupMinimap: TKMPanel;
          MinimapView_Lobby: TKMMinimapView;
        Button_LobbyTabDesc, Button_LobbyTabOptions: TKMButton;
        Panel_LobbySetupDesc: TKMPanel;
          Memo_LobbyMapDesc: TKMMemo;
        Panel_LobbySetupOptions: TKMPanel;
          TrackBar_LobbyPeacetime: TKMTrackBar;
          TrackBar_LobbySpeedPT, TrackBar_LobbySpeedAfterPT: TKMTrackBar;

      Memo_LobbyPosts: TKMMemo;
      Button_LobbyPost: TKMButtonFlat;
      Edit_LobbyPost: TKMEdit;

      Button_LobbyBack: TKMButton;
      Button_LobbyChangeSettings: TKMButton;
      Button_LobbyStart: TKMButton;
  public
    constructor Create(aParent: TKMPanel; aOnPageChange: TGUIEventText);
    destructor Destroy; override;

    function GetChatText: string;
    function GetChatMessages: string;
    procedure Show(aKind: TNetPlayerKind; aNetworking: TKMNetworking; aMainHeight: Word);
    procedure Lobby_Resize(aMainHeight: Word);
    procedure UpdateState;
  end;


implementation
uses KM_ResTexts, KM_ResLocales, KM_Utils, KM_Sound, KM_ResSound, KM_RenderUI, KM_Resource, KM_ResFonts, KM_NetPlayersList, KM_Main, KM_GameApp;


{ TKMGUIMenuLobby }
constructor TKMMenuLobby.Create(aParent: TKMPanel; aOnPageChange: TGUIEventText);
begin
  inherited Create;

  fOnPageChange := aOnPageChange;

  fMinimap := TKMMinimap.Create(True, True);

  fMapsMP := TKMapsCollection.Create(True);
  fSavesMP := TKMSavesCollection.Create;

  CreateControls(aParent);
  CreateChatMenu(aParent);
  CreatePlayerMenu(aParent);
  CreateSettingsPopUp(aParent);
end;


destructor TKMMenuLobby.Destroy;
begin
  fMapsMP.Free;
  fSavesMP.Free;
  fMinimap.Free;

  inherited;
end;


procedure TKMMenuLobby.CreateControls(aParent: TKMPanel);
const
  CW = 690; C1 = 35; C2 = 195; C3 = 355; C4 = 445; C5 = 570; C6 = 650;
var
  I, K, OffY: Integer;
begin
  Panel_Lobby := TKMPanel.Create(aParent,0,0,aParent.Width, aParent.Height);
  Panel_Lobby.AnchorsStretch;

    //Server Name
    Panel_LobbyServerName := TKMPanel.Create(Panel_Lobby, 30, 30, CW, 30);
      TKMBevel.Create(Panel_LobbyServerName,   0,  0, CW, 30);
      Label_LobbyServerName := TKMLabel.Create(Panel_LobbyServerName, 10, 10, CW-20, 20, '', fnt_Metal, taLeft);

    //Players
    Panel_LobbyPlayers := TKMPanel.Create(Panel_Lobby, 30, 65, CW, 316);
      TKMBevel.Create(Panel_LobbyPlayers,  0,  0, CW, 316);

      CheckBox_LobbyHostControl := TKMCheckBox.Create(Panel_LobbyPlayers, 10, 10, 450, 20, gResTexts[TX_LOBBY_HOST_DOES_SETUP], fnt_Metal);
      CheckBox_LobbyHostControl.OnClick := PlayersSetupChange;

      CheckBox_LobbyRandomizeTeamLocations := TKMCheckBox.Create(Panel_LobbyPlayers, 10, 28, 450, 20, gResTexts[TX_LOBBY_RANDOMIZE_LOCATIONS], fnt_Metal);
      CheckBox_LobbyRandomizeTeamLocations.OnClick := PlayersSetupChange;

      //Column titles
      TKMLabel.Create(Panel_LobbyPlayers, C1, 50, 150,  20, gResTexts[TX_LOBBY_HEADER_PLAYERS], fnt_Outline, taLeft);
      TKMLabel.Create(Panel_LobbyPlayers, C2, 50, 150,  20, gResTexts[TX_LOBBY_HEADER_STARTLOCATION], fnt_Outline, taLeft);
      TKMLabel.Create(Panel_LobbyPlayers, C3, 50,  80,  20, gResTexts[TX_LOBBY_HEADER_TEAM], fnt_Outline, taLeft);
      TKMLabel.Create(Panel_LobbyPlayers, C4, 50,  80,  20, gResTexts[TX_LOBBY_HEADER_FLAGCOLOR], fnt_Outline, taLeft);
      TKMLabel.Create(Panel_LobbyPlayers, C5, 50, gResTexts[TX_LOBBY_HEADER_READY], fnt_Outline, taCenter);
      TKMLabel.Create(Panel_LobbyPlayers, C6, 50, gResTexts[TX_LOBBY_HEADER_PING], fnt_Outline, taCenter);

      for I := 0 to MAX_LOBBY_SLOTS - 1 do
      begin
        OffY := 70 + I * 24;
        Image_LobbyFlag[I] := TKMImage.Create(Panel_LobbyPlayers, 10, OffY, 20, 20, 0, rxGuiMain);
        Image_LobbyFlag[I].ImageCenter;
        Image_LobbyFlag[I].Tag := I+1; //Required for PlayerMenuShow
        Image_LobbyFlag[I].OnClick := PlayerMenuShow;

        Label_LobbyPlayer[I] := TKMLabel.Create(Panel_LobbyPlayers, C1, OffY+2, 150, 20, '', fnt_Grey, taLeft);
        Label_LobbyPlayer[I].Hide;

        DropBox_LobbyPlayerSlot[I] := TKMDropList.Create(Panel_LobbyPlayers, C1, OffY, 150, 20, fnt_Grey, '', bsMenu);
        DropBox_LobbyPlayerSlot[I].Add(gResTexts[TX_LOBBY_SLOT_OPEN]); //Player can join into this slot
        DropBox_LobbyPlayerSlot[I].Add(gResTexts[TX_LOBBY_SLOT_CLOSED]); //Closed, nobody can join it
        DropBox_LobbyPlayerSlot[I].Add(gResTexts[TX_LOBBY_SLOT_AI_PLAYER]); //This slot is an AI player
        DropBox_LobbyPlayerSlot[I].ItemIndex := 0; //Open
        DropBox_LobbyPlayerSlot[I].OnChange := PlayersSetupChange;

        DropBox_LobbyLoc[I] := TKMDropList.Create(Panel_LobbyPlayers, C2, OffY, 150, 20, fnt_Grey, '', bsMenu);
        DropBox_LobbyLoc[I].Add(gResTexts[TX_LOBBY_RANDOM], LOC_RANDOM);
        DropBox_LobbyLoc[I].OnChange := PlayersSetupChange;

        DropBox_LobbyTeam[I] := TKMDropList.Create(Panel_LobbyPlayers, C3, OffY, 80, 20, fnt_Grey, '', bsMenu);
        DropBox_LobbyTeam[I].Add('-');
        for K := 1 to 4 do DropBox_LobbyTeam[I].Add(IntToStr(K));
        DropBox_LobbyTeam[I].OnChange := PlayersSetupChange;

        DropBox_LobbyColors[I] := TKMDropColumns.Create(Panel_LobbyPlayers, C4, OffY, 80, 20, fnt_Grey, '', bsMenu);
        DropBox_LobbyColors[I].SetColumns(fnt_Outline, [''], [0]);
        DropBox_LobbyColors[I].List.ShowHeader := False;
        DropBox_LobbyColors[I].FadeImageWhenDisabled := False;
        DropBox_LobbyColors[I].Add(MakeListRow([''], [$FFFFFFFF], [MakePic(rxGuiMain, 31)], 0));
        for K := Low(MP_TEAM_COLORS) to High(MP_TEAM_COLORS) do
          DropBox_LobbyColors[I].Add(MakeListRow([''], [MP_TEAM_COLORS[K]], [MakePic(rxGuiMain, 30)]));
        DropBox_LobbyColors[I].OnChange := PlayersSetupChange;

        Image_LobbyReady[I] := TKMImage.Create(Panel_LobbyPlayers, C5-8, OffY, 16, 16, 32, rxGuiMain);
        Label_LobbyPing[I] := TKMLabel.Create(Panel_LobbyPlayers, C6, OffY, '', fnt_Metal, taCenter);
      end;

    //Chat area
    Memo_LobbyPosts := TKMMemo.Create(Panel_Lobby, 30, 386, CW, 302, fnt_Arial, bsMenu);
    Memo_LobbyPosts.Anchors := [anLeft, anTop, anBottom];
    Memo_LobbyPosts.AutoWrap := True;
    Memo_LobbyPosts.IndentAfterNL := True; //Don't let players fake system messages
    Memo_LobbyPosts.ScrollDown := True;
    Button_LobbyPost := TKMButtonFlat.Create(Panel_Lobby, 30, 690, 30, 22, 0);
    Button_LobbyPost.CapOffsetY := -11;
    Button_LobbyPost.Font := fnt_Grey;
    Button_LobbyPost.OnClick := ChatMenuShow;
    Button_LobbyPost.Anchors := [anLeft, anBottom];

    Edit_LobbyPost := TKMEdit.Create(Panel_Lobby, 60, 690, CW, 22, fnt_Arial);
    Edit_LobbyPost.OnKeyDown := PostKeyDown;
    Edit_LobbyPost.Anchors := [anLeft, anBottom];
    Edit_LobbyPost.ShowColors := True;

    //Setup
    Panel_LobbySetup := TKMPanel.Create(Panel_Lobby, 725, 30, 270, 720);
    Panel_LobbySetup.Anchors := [anLeft, anTop, anBottom];
      with TKMBevel.Create(Panel_LobbySetup,  0,  0, 270, 720) do AnchorsStretch;
      Radio_LobbyMapType := TKMRadioGroup.Create(Panel_LobbySetup, 10, 10, 250, 80, fnt_Metal);
      Radio_LobbyMapType.Add(gResTexts[TX_LOBBY_MAP_BUILD]);
      Radio_LobbyMapType.Add(gResTexts[TX_LOBBY_MAP_FIGHT]);
      Radio_LobbyMapType.Add(gResTexts[TX_LOBBY_MAP_COOP]);
      Radio_LobbyMapType.Add(gResTexts[TX_LOBBY_MAP_SPECIAL]);
      Radio_LobbyMapType.Add(gResTexts[TX_LOBBY_MAP_SAVED]);
      Radio_LobbyMapType.ItemIndex := 0;
      Radio_LobbyMapType.OnChange := MapTypeChange;

      DropCol_LobbyMaps := TKMDropColumns.Create(Panel_LobbySetup, 10, 95, 250, 20, fnt_Metal, gResTexts[TX_LOBBY_MAP_SELECT], bsMenu);
      DropCol_LobbyMaps.DropCount := 19;
      DropCol_LobbyMaps.DropWidth := 430; //Wider to fit mapnames well
      DropCol_LobbyMaps.SetColumns(fnt_Outline, [gResTexts[TX_MENU_MAP_TITLE], '#', gResTexts[TX_MENU_MAP_SIZE]], [0, 290, 320]);
      DropCol_LobbyMaps.List.OnColumnClick := MapColumnClick;
      DropCol_LobbyMaps.List.SearchColumn := 0;
      DropCol_LobbyMaps.OnChange := MapChange;
      Label_LobbyMapName := TKMLabel.Create(Panel_LobbySetup, 10, 95, 250, 20, '', fnt_Metal, taLeft);

      Panel_LobbySetupMinimap := TKMPanel.Create(Panel_LobbySetup, 0, 120, 270, 200);
        TKMBevel.Create(Panel_LobbySetupMinimap, 35, 0, 199, 199);
        MinimapView_Lobby := TKMMinimapView.Create(Panel_LobbySetupMinimap, 39, 4, 191, 191);
        MinimapView_Lobby.ShowLocs := True; //In the minimap we want player locations to be shown
        MinimapView_Lobby.OnLocClick := MinimapLocClick;

      Panel_LobbySetupTransfer := TKMPanel.Create(Panel_LobbySetup, 0, 120, 270, 200);
        CheckBox_LobbySetupOverwrite := TKMCheckbox.Create(Panel_LobbySetupTransfer, 10, 0, 250, 16, gResTexts[TX_LOBBY_DOWNLOAD_OVERWRITE], fnt_Game);
        CheckBox_LobbySetupOverwrite.OnClick := FileDownloadClick;
        Button_LobbySetupDownload := TKMButton.Create(Panel_LobbySetupTransfer, 10, 0, 250, 30, gResTexts[TX_LOBBY_DOWNLOAD], bsMenu);
        Button_LobbySetupDownload.OnClick := FileDownloadClick;
        PercentBar_LobbySetupProgress := TKMPercentBar.Create(Panel_LobbySetupTransfer, 10, 0, 250, 24, fnt_Game);
      Panel_LobbySetupTransfer.Hide;

      Button_LobbyTabDesc := TKMButton.Create(Panel_LobbySetup, 10, 324, 125, 20, gResTexts[TX_LOBBY_MAP_DESCRIPTION], bsMenu);
      Button_LobbyTabDesc.OnClick := GameOptionsTabSwitch;
      Button_LobbyTabDesc.Hide;
      Button_LobbyTabOptions := TKMButton.Create(Panel_LobbySetup, 10+125, 324, 125, 20, gResTexts[TX_LOBBY_OPTIONS], bsMenu);
      Button_LobbyTabOptions.OnClick := GameOptionsTabSwitch;
      Button_LobbyTabOptions.Hide;

      Panel_LobbySetupDesc := TKMPanel.Create(Panel_LobbySetup, 0, 324, 270, 218);
      Panel_LobbySetupDesc.Anchors := [anLeft, anTop, anBottom];
        Memo_LobbyMapDesc := TKMMemo.Create(Panel_LobbySetupDesc, 10, 0, 250, 218, fnt_Game, bsMenu);
        Memo_LobbyMapDesc.Anchors := [anLeft,anTop,anBottom];
        Memo_LobbyMapDesc.AutoWrap := True;
        Memo_LobbyMapDesc.ItemHeight := 16;

      Panel_LobbySetupOptions := TKMPanel.Create(Panel_LobbySetup, 0, 548, 270, 170);
      Panel_LobbySetupOptions.Anchors := [anLeft,anBottom];
        with TKMLabel.Create(Panel_LobbySetupOptions, 10, 4, 250, 20, gResTexts[TX_LOBBY_GAME_OPTIONS], fnt_Outline, taLeft) do Anchors := [anLeft,anBottom];
        TrackBar_LobbyPeacetime := TKMTrackBar.Create(Panel_LobbySetupOptions, 10, 26, 250, 0, 120);
        TrackBar_LobbyPeacetime.Anchors := [anLeft,anBottom];
        TrackBar_LobbyPeacetime.Caption := gResTexts[TX_LOBBY_PEACETIME];
        TrackBar_LobbyPeacetime.Step := 5; //Round to 5min steps
        TrackBar_LobbyPeacetime.OnChange := GameOptionsChange;

        TrackBar_LobbySpeedPT := TKMTrackBar.Create(Panel_LobbySetupOptions, 10, 72, 250, 1, 5);
        TrackBar_LobbySpeedPT.Anchors := [anLeft,anBottom];
        TrackBar_LobbySpeedPT.Caption := gResTexts[TX_LOBBY_GAMESPEED_PEACETIME];
        TrackBar_LobbySpeedPT.ThumbWidth := 45; //Enough to fit 'x2.5'
        TrackBar_LobbySpeedPT.OnChange := GameOptionsChange;

        TrackBar_LobbySpeedAfterPT := TKMTrackBar.Create(Panel_LobbySetupOptions, 10, 116, 250, 1, 5);
        TrackBar_LobbySpeedAfterPT.Anchors := [anLeft,anBottom];
        TrackBar_LobbySpeedAfterPT.Caption := gResTexts[TX_LOBBY_GAMESPEED];
        TrackBar_LobbySpeedAfterPT.ThumbWidth := 45; //Enough to fit 'x2.5'
        TrackBar_LobbySpeedAfterPT.OnChange := GameOptionsChange;

    Button_LobbyBack := TKMButton.Create(Panel_Lobby, 30, 720, 220, 30, gResTexts[TX_LOBBY_QUIT], bsMenu);
    Button_LobbyBack.Anchors := [anLeft, anBottom];
    Button_LobbyBack.OnClick := BackClick;

    Button_LobbyChangeSettings := TKMButton.Create(Panel_Lobby, 265, 720, 220, 30, gResTexts[TX_LOBBY_ROOMSETTINGS], bsMenu);
    Button_LobbyChangeSettings.Anchors := [anLeft, anBottom];
    Button_LobbyChangeSettings.OnClick := SettingsClick;

    Button_LobbyStart := TKMButton.Create(Panel_Lobby, 500, 720, 220, 30, NO_TEXT, bsMenu);
    Button_LobbyStart.Anchors := [anLeft, anBottom];
    Button_LobbyStart.OnClick := StartClick;
end;


procedure TKMMenuLobby.CreateChatMenu(aParent: TKMPanel);
begin
  Menu_Chat := TKMPopUpMenu.Create(aParent, 140);
  Menu_Chat.Anchors := [anLeft, anBottom];
  //Menu gets populated right before show
  Menu_Chat.AddItem(NO_TEXT);
  Menu_Chat.OnClick := ChatMenuClick;
end;


procedure TKMMenuLobby.CreatePlayerMenu(aParent: TKMPanel);
begin
  Menu_Host := TKMPopUpMenu.Create(aParent, 220);
  Menu_Host.AddItem(gResTexts[TX_LOBBY_PLAYER_KICK]);
  Menu_Host.AddItem(gResTexts[TX_LOBBY_PLAYER_BAN]);
  //Menu_Host.AddItem(fTextMain[TX_LOBBY_PLAYER_SETHOST]);
  Menu_Host.OnClick := PlayerMenuClick;
end;


procedure TKMMenuLobby.CreateSettingsPopUp(aParent: TKMPanel);
begin
  Panel_LobbySettings := TKMPanel.Create(aParent, 362, 250, 320, 300);
  Panel_LobbySettings.Anchors := [];
    TKMBevel.Create(Panel_LobbySettings, -1000,  -1000, 4000, 4000);
    TKMImage.Create(Panel_LobbySettings, -20, -75, 340, 310, 15, rxGuiMain);
    TKMBevel.Create(Panel_LobbySettings,   0,  0, 320, 300);
    TKMLabel.Create(Panel_LobbySettings,  20, 10, 280, 20, gResTexts[TX_LOBBY_ROOMSETTINGS], fnt_Outline, taCenter);

    TKMLabel.Create(Panel_LobbySettings, 20, 50, 156, 20, gResTexts[TX_LOBBY_ROOM_DESCRIPTION], fnt_Outline, taLeft);
    Edit_LobbyDescription := TKMEdit.Create(Panel_LobbySettings, 20, 70, 152, 20, fnt_Grey);
    Edit_LobbyDescription.AllowedChars := acText;

    TKMLabel.Create(Panel_LobbySettings, 20, 100, 156, 20, gResTexts[TX_LOBBY_ROOM_PASSWORD], fnt_Outline, taLeft);
    Edit_LobbyPassword := TKMEdit.Create(Panel_LobbySettings, 20, 120, 152, 20, fnt_Grey);
    Edit_LobbyPassword.AllowedChars := acText;

    Button_LobbySettingsResetBans := TKMButton.Create(Panel_LobbySettings, 20, 160, 200, 20, gResTexts[TX_LOBBY_RESET_BANS], bsMenu);
    Button_LobbySettingsResetBans.OnClick := SettingsClick;

    Button_LobbySettingsSave := TKMButton.Create(Panel_LobbySettings, 20, 210, 280, 30, gResTexts[TX_LOBBY_ROOM_OK], bsMenu);
    Button_LobbySettingsSave.OnClick := SettingsClick;
    Button_LobbySettingsCancel := TKMButton.Create(Panel_LobbySettings, 20, 250, 280, 30, gResTexts[TX_LOBBY_ROOM_CANCEL], bsMenu);
    Button_LobbySettingsCancel.OnClick := SettingsClick;
end;


procedure TKMMenuLobby.ChatMenuSelect(aItem: Integer);

  procedure UpdateButtonCaption(aCaption: UnicodeString; aColor: Cardinal = 0);
  var CapWidth: Integer;
  const MIN_SIZE = 80; //Minimum size for the button
  begin
    //Update button width according to selected item
    CapWidth := gResource.Fonts.GetTextSize(aCaption, Button_LobbyPost.Font).X;
    CapWidth := Max(MIN_SIZE, CapWidth+10); //Apply minimum size
    if aColor <> 0 then
      aCaption := WrapColor(aCaption, aColor);
    Button_LobbyPost.Caption := aCaption;
    Button_LobbyPost.Width := CapWidth;

    Edit_LobbyPost.AbsLeft := Button_LobbyPost.AbsLeft + Button_LobbyPost.Width + 4;
    Edit_LobbyPost.Width := Memo_LobbyPosts.Width - Button_LobbyPost.Width - 4;
  end;

var I: Integer;
begin
  //All
  if aItem = -1 then
  begin
    fChatMode := cmAll;
    UpdateButtonCaption(gResTexts[TX_CHAT_ALL]);
    Edit_LobbyPost.DrawOutline := False; //No outline for All
  end
  else
    //Team
    if aItem = -2 then
    begin
      fChatMode := cmTeam;
      UpdateButtonCaption(gResTexts[TX_CHAT_TEAM], $FF66FF66);
      Edit_LobbyPost.DrawOutline := True;
      Edit_LobbyPost.OutlineColor := $FF66FF66;
    end
    else
    //Whisper
    begin
      I := fNetworking.NetPlayers.ServerToLocal(aItem);
      if I <> -1 then
      begin
        fChatMode := cmWhisper;
        Edit_LobbyPost.DrawOutline := True;
        Edit_LobbyPost.OutlineColor := $FF00B9FF;
        with fNetworking.NetPlayers[I] do
        begin
          fChatWhisperRecipient := IndexOnServer;
          UpdateButtonCaption(UnicodeString(Nikname), IfThen(FlagColorID <> 0, FlagColorToTextColor(FlagColor), 0));
        end;
      end;
    end;
end;


procedure TKMMenuLobby.ChatMenuClick(Sender: TObject);
begin
  if Menu_Chat.ItemIndex <> -1 then
    ChatMenuSelect(Menu_Chat.ItemTags[Menu_Chat.ItemIndex]);
end;


procedure TKMMenuLobby.ChatMenuShow(Sender: TObject);
var
  C: TKMControl;
  I: Integer;
  n: TKMNetPlayerInfo;
begin
  //Populate menu with right options
  Menu_Chat.Clear;

  Menu_Chat.AddItem(gResTexts[TX_CHAT_ALL], -1);
  //Only show "Team" if the player is on a team
  if fNetworking.NetPlayers[fNetworking.MyIndex].Team <> 0 then
    Menu_Chat.AddItem('[$66FF66]' + gResTexts[TX_CHAT_TEAM], -2);

  for I := 1 to fNetworking.NetPlayers.Count do
  if I <> fNetworking.MyIndex then //Can't whisper to yourself
  begin
    n := fNetworking.NetPlayers[I];

    if n.IsHuman and n.Connected and not n.Dropped then
      Menu_Chat.AddItem(UnicodeString(n.NiknameColored), n.IndexOnServer);
  end;

  C := TKMControl(Sender);
  //Position the menu next to the icon, but do not overlap players name
  Menu_Chat.ShowAt(C.AbsLeft, C.AbsTop - Menu_Chat.Height);
end;


//Try to detect which kind it is
function TKMMenuLobby.DetectMapType: Integer;
begin
  //Default
  Result := 0;

  case fNetworking.SelectGameKind of
    ngk_Map:  if fNetworking.MapInfo.IsCoop then
                Result := 2
              else
              if fNetworking.MapInfo.IsSpecial then
                Result := 3
              else
              if fNetworking.MapInfo.MissionMode = mm_Tactic then
                Result := 1;
    ngk_Save: Result := 4;
  end;
end;


//Access text that user was typing to copy it over to gameplay chat
function TKMMenuLobby.GetChatText: string;
begin
  Result := Edit_LobbyPost.Text;
end;


//Access chat messages history to copy it over to gameplay chat
function TKMMenuLobby.GetChatMessages: string;
begin
  Result := Memo_LobbyPosts.Text;
end;


procedure TKMMenuLobby.Show(aKind: TNetPlayerKind; aNetworking: TKMNetworking; aMainHeight: Word);
begin
  fNetworking := aNetworking;

  Reset(aKind);

  //Events binding is the same for Host and Joiner because of stand-alone Server
  //E.g. If Server fails, Host can be disconnected from it as well as a Joiner
  fNetworking.OnTextMessage  := Lobby_OnMessage;
  fNetworking.OnPlayersSetup := Lobby_OnPlayersSetup;
  fNetworking.OnGameOptions  := Lobby_OnGameOptions;
  fNetworking.OnMapName      := Lobby_OnMapName;
  fNetworking.OnMapMissing   := Lobby_OnMapMissing;
  fNetworking.OnPingInfo     := Lobby_OnPingInfo;
  //fNetworking.OnStartMap - already assigned in fGameApp when Net is created
  //fNetworking.OnStartSave - already assigned in fGameApp when Net is created
  fNetworking.OnDisconnect   := Lobby_OnDisconnect;
  fNetworking.OnReassignedHost := Lobby_OnReassignedToHost;
  fNetworking.OnFileTransferProgress := Lobby_OnFileTransferProgress;

  ChatMenuSelect(-1); //All

  Panel_Lobby.Show;
  Lobby_Resize(aMainHeight);
end;


procedure TKMMenuLobby.Lobby_Resize(aMainHeight: Word);
begin
  if not Panel_Lobby.Visible then Exit;
  //If the vertical screen height goes below a certain amount we need to switch to "compact" mode
  if aMainHeight >= 660 then
  begin
    //We have enough space, so stack Options below Desc
    Panel_LobbySetupDesc.Top := 324;
    Panel_LobbySetupDesc.Height := aMainHeight-550;
    Panel_LobbySetupOptions.Top := 330 + Panel_LobbySetupDesc.Height;
    Button_LobbyTabDesc.Hide;
    Button_LobbyTabOptions.Hide;
    Panel_LobbySetupDesc.Show;
    Panel_LobbySetupOptions.Show;
  end
  else
  begin
    //Not enough space, so enabled tabbed view
    Panel_LobbySetupDesc.Top := 350;
    Panel_LobbySetupDesc.Height := aMainHeight-420;
    Panel_LobbySetupOptions.Top := 350;
    Button_LobbyTabDesc.Show;
    Button_LobbyTabOptions.Show;
    GameOptionsTabSwitch(nil);
  end;
end;

{'fraction_non_alphanumeric': 0.0975084203259346, 'fraction_numerical': 0.02776092550365055, 'mean_word_length': 4.41911347919737, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 14, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '787200', 'n_tokens_mistral': 18486, 'n_tokens_neox': 16736, 'n_words': 3186}