text: // Copyright (C) 2007 Id Software, Inc.
  //
  
  #include "precompiled.h"
  #pragma hdrstop
  
  #if defined( _DEBUG ) && !defined( ID_REDIRECT_NEWDELETE )
  #define new DEBUG_NEW
  #undef THIS_FILE
  static char THIS_FILE[] = __FILE__;
  #endif
  
  #include "../framework/Licensee.h"
  
  #include "roles/Inventory.h"
  #include "roles/FireTeams.h"
  #include "roles/ObjectiveManager.h"
  #include "roles/Tasks.h"
  #include "structures/TeamManager.h"
  #include "rules/GameRules.h"
  #include "Player.h"
  #include "../framework/BuildVersion.h"
  #include "../framework/KeyInput.h"
  #include "guis/UserInterfaceLocal.h"
  #include "guis/UserInterfaceManagerLocal.h"
  #include "guis/UIList.h"
  #include "guis/UIRadialMenu.h"
  #include "CommandMapInfo.h"
  #include "demos/DemoManager.h"
  #include "rules/UserGroup.h"
  #include "rules/AdminSystem.h"
  #include "rules/VoteManager.h"
  #include "Atmosphere.h"
  #include "misc/Door.h"
  #include "vehicles/Transport.h"
  #include "vehicles/VehicleWeapon.h"
  #include "ScriptEntity.h"
  #include "Waypoints/LocationMarker.h"
  #include "proficiency/StatsTracker.h"
  #include "../framework/async/Demo.h"
  #include "../sys/sys_local.h"
  
  #ifndef _XENON
  #include "../sdnet/SDNet.h"
  #endif
  
  #define ON_UIVALID( handle, thename ) sdUserInterfaceLocal* thename = GetUserInterface( handle ); if ( thename )
  #define ON_UIINVALID( handle, thename ) sdUserInterfaceLocal* thename = GetUserInterface( handle ); if ( !thename )
  
  idList< qhandle_t > idGameLocal::taskListHandles;
  
  /*
  ================
  idGameLocal::HandleGuiEvent
  ================
  */
  bool idGameLocal::HandleGuiEvent( const sdSysEvent* event ) {
  	if ( networkSystem->IsDedicated() ) {
  		return false;
  	}
  
  	ON_UIVALID( uiMainMenuHandle, ui ) {
  		if ( ui->IsActive() ) {
  			return ui->PostEvent( event );
  		}
  	}
  
  	sdHudModule* module = localPlayerProperties.GetActiveHudModule();
  	if ( module && module->HandleGuiEvent( event ) ) {
  		return true;
  	}
  
  	idPlayer* localPlayer = gameLocal.GetLocalPlayer();
  	if ( localPlayer ) {
  		if( isServer && event->GetKey() == K_F1 && event->IsKeyDown() && isPaused && localPlayer->IsSinglePlayerToolTipPlaying() ) { 
  			cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "unPauseGame\n" ); 
  			return true; 
  		}
  
  		if ( localPlayer->HandleGuiEvent( event ) ) {
  			return true;
  		}
  	}
  
  	if ( rules ) {
  		if ( rules->HandleGuiEvent( event ) ) {
  			return true;
  		}
  	}
  
  	return false;
  }
  
  /*
  ================
  idGameLocal::TranslateGuiBind
  ================
  */
  bool idGameLocal::TranslateGuiBind( const idKey& key, sdKeyCommand** cmd ) {
  	ON_UIVALID( uiMainMenuHandle, ui ) {
  		if ( ui->IsActive() ) {
  			if ( ui->Translate( key, cmd ) ) {
  				return true;
  			}
  		}
  	}
  
  	sdHudModule* module = gameLocal.localPlayerProperties.GetActiveHudModule();
  	if ( module != NULL ) {
  		sdUserInterfaceLocal* ui = module->GetGui();
  		if ( ui != NULL ) {
  			if ( ui->Translate( key, cmd ) ) {
  				return true;
  			}
  		}
  	}
  
  	idPlayer* localPlayer = gameLocal.GetLocalPlayer();
  	if ( localPlayer ) {
  		if ( localPlayer->TranslateGuiBind( key, cmd ) ) {
  			return true;
  		}
  	}
  
  	if ( rules != NULL ) {
  		if ( rules->TranslateGuiBind( key, cmd ) ) {
  			return true;
  		}
  	}
  
  	return false;
  }
  
  /*
  ============
  idGameLocal::ShowMainMenu
  ============
  */
  void idGameLocal::ShowMainMenu() {
  	ON_UIVALID( uiMainMenuHandle, ui ) {
  		ui->Activate();
  	}
  }
  
  /*
  ============
  idGameLocal::HideMainMenu
  ============
  */
  void idGameLocal::HideMainMenu() {
  	// ao: never called
  
  	ON_UIVALID( uiMainMenuHandle, ui ) {
  		ui->Deactivate( true );
  	}
  }
  
  /*
  ============
  idGameLocal::IsMainMenuActive
  ============
  */
  bool idGameLocal::IsMainMenuActive() {
  	ON_UIVALID( uiMainMenuHandle, ui ) {
  		return ui->IsActive();
  	}
  	return false;
  }
  
  /*
  ============
  idGameLocal::DrawMainMenu
  ============
  */
  void idGameLocal::DrawMainMenu() {
  	ON_UIINVALID( uiMainMenuHandle, ui ) {
  		return;
  	}
  
  	if ( gamestate == GAMESTATE_NOMAP ) {
  		sdGlobalStatsTracker::GetInstance().UpdateStatsRequests();
  	}
  
  	ui->Draw();
  }
  
  /*
  ============
  idGameLocal::DrawSystemUI
  ============
  */
  void idGameLocal::DrawSystemUI() {
  	ON_UIINVALID( uiSystemUIHandle, ui ) {
  		return;
  	}
  
  	ui->Draw();
  }
  
  /*
  ============
  idGameLocal::DrawLoadScreen
  ============
  */
  void idGameLocal::DrawLoadScreen() {
  	ON_UIINVALID( uiLevelLoadHandle, ui ) {
  		return;
  	}
  
  	ui->Activate();
  	ui->Update();
  	ui->Draw();
  }
  
  /*
  ============
  idGameLocal::DrawPureWaitScreen
  ============
  */
  void idGameLocal::DrawPureWaitScreen() {
  	ON_UIINVALID( pureWaitHandle, ui ) {
  		return;
  	}
  
  	ui->Activate();
  	ui->Update();
  	ui->Draw();
  }
  
  /*
  ============
  idGameLocal::CreateKeyBindingList
  ============
  */
  void idGameLocal::CreateKeyBindingList( sdUIList* list ) {
  	assert( list );
  
  	using namespace sdProperties;
  	idStr category;
  	if( sdProperty* property = list->GetScope().GetProperties().GetProperty( "bindCategory", PT_STRING )) {
  		category = *property->value.stringValue;
  	}
  
  	sdUIList::ClearItems( list );
  	const sdDeclKeyBinding* bind = gameLocal.declKeyBindingType.LocalFind( category, false );
  	if( bind == NULL ) {
  		return;
  	}
  
  	idWStr text;
  	for( int i = 0; i < bind->GetKeys().GetNumKeyVals(); i++ ) {
  		const idKeyValue* kv = bind->GetKeys().GetKeyVal( i );
  		int item = sdUIList::InsertItem( list, va( L"<loc = '%hs'>", kv->GetKey().c_str() ), -1, 0 );
  
  		int numKeys = 0;
  		keyInputManager->KeysFromBinding( gameLocal.GetDefaultBindContext(), kv->GetValue(), numKeys, NULL );
  
  		idKey** keys = NULL;
  		
  		if( numKeys > 0 ) {
  			keys = static_cast< idKey** >( _alloca( numKeys * sizeof( idKey* ) ) );
  			keyInputManager->KeysFromBinding( gameLocal.GetDefaultBindContext(), kv->GetValue(), numKeys, keys );
  		}
  
  		if( numKeys > 0 ) {
  			assert( keys != NULL );
  			keys[ 0 ]->GetLocalizedText( text );
  			sdUIList::CleanUserInput( text );
  			sdUIList::SetItemText( list, text.c_str(), item, 1 );
  		} else {
  			sdUIList::SetItemText( list, L"<loc = 'guis/mainmenu/bindings/unbound'>", item, 1 );
  		}
  		if( numKeys > 1 ) {
  			assert( keys != NULL );
  			keys[ 1 ]->GetLocalizedText( text );
  			sdUIList::CleanUserInput( text );
  			sdUIList::SetItemText( list, text.c_str(), item, 2 );
  		} else {
  			sdUIList::SetItemText( list, L"<loc = 'guis/mainmenu/bindings/unbound'>", item, 2 );
  		}
  		sdUIList::SetItemText( list, va( L"%hs", kv->GetValue().c_str() ), item, 3 );
  	}
  }
  
  /*
  ============
  idGameLocal::CreateCrosshairList
  ============
  */
  void idGameLocal::CreateCrosshairList( sdUIList* list ) {
  	assert( list != NULL );
  
  	sdUIList::ClearItems( list );
  	
  	int num = gameLocal.declStringMapType.Num();
  	for( int i = 0; i < num; i++ ) {
  		const sdDeclStringMap* decl = gameLocal.declStringMapType.LocalFindByIndex( i, false );
  		if( idStr::Icmpn( decl->GetName(), "crosshairs", 10 ) != 0 ) {
  			continue;
  		}
  		// ensure we're parsed
  		gameLocal.declStringMapType.LocalFindByIndex( i, true );
  
  		const idDict& dict = decl->GetDict();
  		for( int j = 0; j < dict.GetNumKeyVals(); j++ ) {
  			const idKeyValue* kv = dict.GetKeyVal( j );
  			sdUIList::InsertItem( list, va( L"<material = 'literal: %hs'>\t%hs\t%hs", kv->GetValue().c_str(), decl->GetName(), kv->GetKey().c_str() ), -1, 0 );
  		}
  	}
  
  }
  
  /*
  ============
  idGameLocal::CreateDemoList
  ============
  */
  void idGameLocal::CreateDemoList( sdUIList* list ) {
  
  	sdUIList::ClearItems( list );
      idFileList* files = fileSystem->ListFiles( "demos", ".ndm" );
  	if( files == NULL ) {
  		return;
  	}
  
  	idStr file;
  	idWStr date;
  	idStr fullPath;
  	sysTime_t time;
  
  	if( files->GetNumFiles() > 0 ) {
  		list->BeginBatch();
  		list->SetItemGranularity( files->GetNumFiles() );
  		for( int i = 0; i < files->GetNumFiles(); i++ ) {
  			file = files->GetFile( i );
  			if( networkSystem->CanPlayDemo( file.c_str() ) ) {
  				unsigned int timeStamp;
  
  				fullPath = files->GetBasePath();
  				fullPath += "/" + file;
  				fileSystem->ReadFile( fullPath, NULL, &timeStamp );
  
  				sys->SecondsToTime( timeStamp, time );
  				sdNetProperties::FormatTimeStamp( time, date );
  
  				file.StripFileExtension();
  				int index = sdUIList::InsertItem( list, va( L"%hs\t%ls\t%hs", file.c_str(), date.c_str(), fullPath.c_str() ), -1, 0 );
  				list->SetItemDataInt( timeStamp, index, 1, true );
  			}
  		}
  		list->EndBatch();
  	}
  
  	fileSystem->FreeFileList( files );
  }
  
  /*
  ============
  idGameLocal::CreateVehiclePlayerList
  ============
  */
  void idGameLocal::CreateVehiclePlayerList( sdUIList* list ) {
  	idPlayer* localPlayer = gameLocal.GetLocalViewPlayer();
  	if ( localPlayer == NULL ) {
  		if( g_debugPlayerList.GetInteger() ) {
  			sdUIList::ClearItems( list );
  			idWStr formatString;
  			for( int i = 0; i < g_debugPlayerList.GetInteger(); i++ ) {
  				formatString = va( L"<material = \"soldier\" >\tPlayer%i", i );
  				sdUIList::InsertItem( list, formatString.c_str(), -1, 0 );
  			}
  		}
  		return;
  	}
  
  	sdUIList::ClearItems( list );
  
  	idEntity* proxy = localPlayer->GetProxyEntity();
  	if( proxy == NULL ) {
  		return;
  	}
  
  	if ( sdUsableInterface* iface = proxy->GetUsableInterface() ) {
  		const wchar_t* formatString;
  
  		int selIndex = -1;
  		idWStr cleanPlayerName;
  
  		for( int i = 0; i < iface->GetNumPositions(); i++ ) {
  			const idPlayer* player = iface->GetPlayerAtPosition( i );
  			if( player == NULL ) {
  				if( g_debugPlayerList.GetInteger() ) {
  					formatString = va( L"<material = \"soldier\" >\tPlayer%i", i );
  					sdUIList::InsertItem( list, formatString, -1, 0 );
  				}
  				continue;
  			}
  			if( const sdDeclPlayerClass* pc = player->GetInventory().GetClass() ) {
  				cleanPlayerName = va( L"%hs", player->userInfo.name.c_str() );
  				sdUIList::CleanUserInput( cleanPlayerName );
  				formatString = va( L"<material = \"%hs\" >\t%ls", ( pc == NULL ) ? "" : pc->GetName(), cleanPlayerName.c_str() );
  				int index = sdUIList::InsertItem( list, formatString, -1, 0 );
  				if( player == localPlayer ) {
  					selIndex = index;
  				}
  			}
  		}
  		list->SelectItem( selIndex );
  	} 
  }
  
  /*
  ============
  idGameLocal::CreateScoreboardList
  ============
  */
  void idGameLocal::CreateScoreboardList( sdUIList* list ) {
  	if( gameLocal.rules ) {
  		idStr teamName;
  		if( sdUserInterfaceLocal* ui = list->GetUI() ) {
  			ui->PopScriptVar( teamName );
  		}
  		gameLocal.rules->UpdateScoreboard( list, teamName );
  	} else {
  		if( sdUserInterfaceLocal* ui = list->GetUI() ) {
  			idStr name;
  			ui->PopScriptVar( name );
  			ui->PushScriptVar( 0.0f );
  			ui->PushScriptVar( 0.0f );
  			ui->PushScriptVar( 0.0f );
  		}
  	}
  }
  
  /*
  ============
  idGameLocal::CreatePlayerAdminList
  ============
  */
  void idGameLocal::CreatePlayerAdminList( sdUIList* list ) {
  	sdAdminSystem::GetInstance().CreatePlayerAdminList( list );
  }
  
  /*
  ============
  idGameLocal::CreateUserGroupList
  ============
  */
  void idGameLocal::CreateUserGroupList( sdUIList* list ) {
  	sdUserGroupManager::GetInstance().CreateUserGroupList( list );
  }
  
  /*
  ============
  idGameLocal::CreateUserGroupList
  ============
  */
  void idGameLocal::CreateServerConfigList( sdUIList* list ) {
  	sdUserGroupManager::GetInstance().CreateServerConfigList( list );
  }
  
  /*
  ============
  idGameLocal::CreateFireTeamListEntry
  ============
  */
  void idGameLocal::CreateFireTeamListEntry( sdUIList* list, int index, idPlayer* player ) {
  	static const idVec4 noDraw( 0.0f, 0.0f, 0.0f, 0.0f );
  
  	sdUIList::InsertItem( list, L"", index, 0 );
  
  	const sdDeclPlayerClass* cls = player->GetInventory().GetClass();
  	if ( cls != NULL ) {
  		// class
  		sdUIList::SetItemIcon( list, cls->GetName(), index, 0 );
  	}
  
  	// rank/voip
  	bool sendingVoice = ( sys->Milliseconds() - networkSystem->GetLastVoiceReceivedTime( player->entityNumber ) ) < SEC2MS( 0.5f );
  	if ( sendingVoice ) {
  		sdUIList::SetItemIcon( list, "voip", index, 1 );
  		sdUIList::SetItemForeColor( list, colorWhite, index, 1 );
  	}  else if ( const sdDeclRank* rank = player->GetProficiencyTable().GetRank() ) {
  		sdUIList::SetItemIcon( list, rank->GetMaterial(), index, 1 );
  		sdUIList::SetItemForeColor( list, colorWhite, index, 1 );
  	} else {
  		sdUIList::SetItemForeColor( list, noDraw, index, 1 );
  	}
  
  	// name
  
  	idWStr cleanPlayerName = va( L"%hs", player->userInfo.cleanName.c_str() );
  	sdUIList::CleanUserInput( cleanPlayerName );
  
  	sdUIList::SetItemText( list, cleanPlayerName.c_str(), index, 2 );
  
  	if( player->IsInLimbo() ) {
  		sdUIList::SetItemForeColor( list, colorBlack, index, 2 );
  	} else {
  		float healthFrac = player->GetHealth() / ( float )player->GetMaxHealth();
  		healthFrac = idMath::ClampFloat( 0.0f, 1.0f, healthFrac );
  		idVec4 healthColor( 1.0f - healthFrac, healthFrac, 0.0f, 1.0f );
  		sdUIList::SetItemForeColor( list, healthColor, index, 2 );
  	}	
  }
  
  /*
  ============
  idGameLocal::CreateFireTeamList
  ============
  */
  void idGameLocal::CreateFireTeamList( sdUIList* list ) {
  	sdUIList::ClearItems( list );
  
  	int listType;
  	if( sdUserInterfaceLocal* ui = list->GetUI() ) {
  		ui->PopScriptVar( listType );
  	}
  
  	if ( gameLocal.rules == NULL ) {
  		return;
  	}
  
  	if ( gameLocal.GetLocalPlayer() == NULL ) {
  		return;
  	}
  
  	if ( listType == FIRETEAMLIST_MYFIRETEAM ) {
  		CreateFireTeamList_MyFireTeam( list );
  	} else if ( listType == FIRETEAMLIST_MAIN ) {
  		CreateFireTeamList_Main( list );
  	} else if ( listType == FIRETEAMLIST_JOIN ) {
  		CreateFireTeamList_Join( list );
  	} else if ( listType == FIRETEAMLIST_INVITE ) {
  		CreateFireTeamList_Invite( list );
  	} else if ( listType == FIRETEAMLIST_KICK ) {
  		CreateFireTeamList_Kick( list );
  	} else if ( listType == FIRETEAMLIST_PROMOTE ) {
  		CreateFireTeamList_Promote( list );
  	} else if ( listType == FIRETEAMLIST_MANAGE ) {
  		CreateFireTeamList_Manage( list );
  	} else {
  		assert( 0 );
  	}
  }
  
  /*
  ============
  idGameLocal::CreateFireTeamList_MyFireTeam
  ============
  */
  void idGameLocal::CreateFireTeamList_MyFireTeam( sdUIList* list ) {
  	idPlayer* localPlayer = gameLocal.GetLocalPlayer();
  	sdFireTeam* fireTeam = gameLocal.rules->GetPlayerFireTeam( localPlayer->entityNumber );
  	if ( fireTeam != NULL ) {
  		int index = 0;
  		for ( int i = 0; i < fireTeam->GetNumMembers(); i++, index++ ) {
  			idPlayer* player = fireTeam->GetMember( i );
  			CreateFireTeamListEntry( list, index, player );
  		}
  		if ( g_debugPlayerList.GetInteger() ) {
  			int i = 0;
  
  			int num = 8;
  			while ( list->GetNumItems() < num ) {
  				idWStr formatString = va( L"<material = \"soldier\" >\t<material = \"guis/assets/icons/rank01\" >\tPlayer%i\t<fore = 1,1,1,1>100.00XP\t100%%", i );
  				sdUIList::InsertItem( list, formatString.c_str(), -1, 0 );
  				i++;
  			}
  		}
  	} else {
  		sdTeamInfo* localTeam = localPlayer->GetGameTeam();
  		if ( localTeam == NULL ) {
  			return;
  		}
  
  		idStaticList< idPlayer*, MAX_CLIENTS > playerList;
  
  		// Mission teams
  		for ( int i = 0; i < MAX_CLIENTS; i++ ) {
  			idPlayer* player = gameLocal.GetClient( i );
  			if ( player == NULL ) {
  				continue;
  			}
  
  			if ( player->GetGameTeam() != localTeam ) {
  				continue;
  			}
  
  			if ( gameLocal.rules->GetPlayerFireTeam( i ) != NULL ) {
  				continue;
  			}
  
  			if( player->GetActiveTaskHandle() != localPlayer->GetActiveTaskHandle() ) {
  				continue;
  			}
  
  			playerList.Append( player );
  		}
  
  		for ( int i = 0; i < playerList.Num() && i < MAX_MISSION_TEAM_PLAYERS; i++ ) {
  			idPlayer* player = playerList[ i ];
  			CreateFireTeamListEntry( list, i, player );
  		}
  	}
  }
  
  /*
  ============
  idGameLocal::CreateFireTeamList_Main
  ============
  */
  void idGameLocal::CreateFireTeamList_Main( sdUIList* list ) {
  	idPlayer* localPlayer = gameLocal.GetLocalPlayer();
  
  	bool canJoin = false;
  	bool canCreate = false;
  	for ( int i = 0; i < sdTeamInfo::MAX_FIRETEAMS; i++ ) {
  		sdFireTeam& fireTeam = localPlayer->GetTeam()->GetFireTeam( i );
  		if ( fireTeam.GetNumMembers() > 0 ) {
  			if ( !fireTeam.IsPrivate() ) {
  				if ( fireTeam.MaxMembers() - fireTeam.GetNumMembers() > 0 ) {
  					canJoin = true;
  				}
  			}
  		} else {
  			canCreate = true;
  		}
  	}
  
  	if ( canJoin ) {
  		sdUIList::InsertItem( list, va( L" 1. %ls\tcanjoin", common->LocalizeText( "guis/hud/fireteam/menu/join" ).c_str() ), 0, 0 );
  	} else {
  		sdUIList::InsertItem( list, va( L" 1. ^1%ls\tcannot", common->LocalizeText( "guis/hud/fireteam/menu/join" ).c_str() ), 0, 0 );
  	}
  
  	if ( canCreate ) {
  		sdUIList::InsertItem( list, va( L" 2. %ls\tcancreate", common->LocalizeText( "guis/hud/fireteam/menu/create" ).c_str() ), 1, 0 );
  	} else {
  		sdUIList::InsertItem( list, va( L" 2. ^1%ls\tcannot", common->LocalizeText( "guis/hud/fireteam/menu/create" ).c_str() ), 1, 0 );
  	}
  }
  
  /*
  ============
  idGameLocal::CreateFireTeamList_Join
  ============
  */
  void idGameLocal::CreateFireTeamList_Join( sdUIList* list ) {
  	idPlayer* localPlayer = gameLocal.GetLocalPlayer();
  
  	int num = 0;
  	for ( int i = 0; i < sdTeamInfo::MAX_FIRETEAMS; i++ ) {
  		sdFireTeam& fireTeam = localPlayer->GetTeam()->GetFireTeam( i );
  		if ( !fireTeam.IsPrivate() ) {
  			if ( fireTeam.GetNumMembers() > 0 && fireTeam.MaxMembers() - fireTeam.GetNumMembers() > 0 ) {
  				sdUIList::InsertItem( list, va( L" %i. %hs\t%i", num+1, fireTeam.GetName(), i ), i, 0 );
  				num++;
  			}
  		}
  	}
  }
  
  /*
  ============
  idGameLocal::CreateFireTeamList_Invite
  ============
  */
  void idGameLocal::CreateFireTeamList_Invite( sdUIList* list ) {
  	idPlayer* localPlayer = gameLocal.GetLocalPlayer();
  	sdFireTeam* fireTeam = gameLocal.rules->GetPlayerFireTeam( localPlayer->entityNumber );
  
  	if ( fireTeam == NULL ) {
  		return;
  	}
  
  	int page;
  	if( sdUserInterfaceLocal* ui = list->GetUI() ) {
  		ui->PopScriptVar( page );
  	}
  
  	assert( page >= 0 && page <= 1 );
  
  	int numAdded = 0;
  	int numFound = 0;
  	if ( fireTeam->MaxMembers() - fireTeam->GetNumMembers() >= 1 ) {
  		for ( int i = 0; i < MAX_CLIENTS; i++ ) {
  			idPlayer *p = gameLocal.GetClient( i );
  			if ( p != NULL && p->GetTeam() == localPlayer->GetTeam() ) {
  				if ( gameLocal.rules->GetPlayerFireTeam( i ) == NULL ) {
  					numFound++;
  					if ( numFound > FIRETEAMLIST_PAGE_NUM_PLAYERS * page && numAdded < FIRETEAMLIST_PAGE_NUM_PLAYERS ) {
  						sdUIList::InsertItem( list, va( L" %i. %hs\t%hs", numAdded+1, p->userInfo.cleanName.c_str(), p->userInfo.cleanName.c_str() ), numAdded, 0 );
  						numAdded++;
  					}
  				}
  			}
  		}
  	}
  
  	if ( numFound != numAdded ) {
  		if ( page == 1 ) {
  			sdUIList::InsertItem( list, va( L" %i. %ls\tprevpage", numAdded+1, common->LocalizeText( "guis/hud/fireteam/menu/prevpage" ).c_str() ), numAdded, 0 );
  		} else {
  			sdUIList::InsertItem( list, va( L" %i. %ls\tnextpage", numAdded+1, common->LocalizeText( "guis/hud/fireteam/menu/nextpage" ).c_str() ), numAdded, 0 );
  		}
  		numAdded++;
  	}
  }
  
  /*
  ============
  idGameLocal::CreateFireTeamList_Kick
  ============
  */
  void idGameLocal::CreateFireTeamList_Kick( sdUIList* list ) {
  	idPlayer* localPlayer = gameLocal.GetLocalPlayer();
  	sdFireTeam* fireTeam = gameLocal.rules->GetPlayerFireTeam( localPlayer->entityNumber );
  
  	if ( fireTeam == NULL ) {
  		return;
  	}
  
  	int numAdded = 0;
  	for ( int i = 0; i < MAX_CLIENTS; i++ ) {
  		idPlayer* p = gameLocal.GetClient( i );
  		if ( p != NULL && p != localPlayer && fireTeam->IsMember( p ) ) {
  			sdUIList::InsertItem( list, va( L" %i. %hs\t%hs", numAdded+1, p->userInfo.cleanName.c_str(), p->userInfo.cleanName.c_str() ), numAdded, 0 );
  			numAdded++;
  		}
  	}
  }
  
  /*
  ============
  idGameLocal::CreateFireTeamList_Promote
  ============
  */
  void idGameLocal::CreateFireTeamList_Promote( sdUIList* list ) {
  	idPlayer* localPlayer = gameLocal.GetLocalPlayer();
  	sdFireTeam* fireTeam = gameLocal.rules->GetPlayerFireTeam( localPlayer->entityNumber );
  
  	if ( fireTeam == NULL ) {
  		return;
  	}
  
  	int numAdded = 0;
  	for ( int i = 0; i < MAX_CLIENTS; i++ ) {
  		idPlayer* p = gameLocal.GetClient( i );
  		if ( p != NULL && p != localPlayer && fireTeam->IsMember( p ) ) {
  			if ( !p->userInfo.isBot ) {
  				sdUIList::InsertItem( list, va( L" %i. %hs\t%hs", numAdded+1, p->userInfo.cleanName.c_str(), p->userInfo.cleanName.c_str() ), numAdded, 0 );
  				numAdded++;
  			}
  		}
  	}
  }
  
  /*
  ============
  idGameLocal::CreateFireTeamList_Manage
  ============
  */
  void idGameLocal::CreateFireTeamList_Manage( sdUIList* list ) {
  	idPlayer* localPlayer = gameLocal.GetLocalPlayer();
  	sdFireTeam* fireTeam = gameLocal.rules->GetPlayerFireTeam( localPlayer->entityNumber );
  
  	if ( fireTeam == NULL ) {
  		return;
  	}
  
  	int index = 0;
  	
  	if ( fireTeam->IsCommander( localPlayer ) ) {
  		bool canInvite = false;
  		if ( fireTeam->MaxMembers() - fireTeam->GetNumMembers() > 0 ) {
  			for ( int i = 0; i < MAX_CLIENTS; i++ ) {
  				idPlayer *p = gameLocal.GetClient( i );
  				if ( p != NULL && p->GetTeam() == localPlayer->GetTeam() ) {
  					if ( gameLocal.rules->GetPlayerFireTeam( i ) == NULL ) {
  						canInvite = true;
  						break;
  					}
  				}
  			}
  		}
  
  		if ( canInvite ) {
  			sdUIList::InsertItem( list, va( L" %i. %ls\tcaninvite", index+1, common->LocalizeText( "guis/hud/fireteam/menu/invite" ).c_str() ), index, 0 );
  		} else {
  			sdUIList::InsertItem( list, va( L" %i. ^1%ls\tcannot", index+1, common->LocalizeText( "guis/hud/fireteam/menu/invite" ).c_str() ), index, 0 );
  		}
  		index++;
  
  		if ( fireTeam->GetNumMembers() > 1 ) {
  			sdUIList::InsertItem( list, va( L" %i. %ls\tcankick", index+1, common->LocalizeText( "guis/hud/fireteam/menu/kick" ).c_str() ), index, 0 );
  		} else {
  			sdUIList::InsertItem( list, va( L" %i. ^1%ls\tcannot", index+1, common->LocalizeText( "guis/hud/fireteam/menu/kick" ).c_str() ), index, 0 );
  		}
  		index++;
  
  		sdUIList::InsertItem( list, va( L" %i. %ls\tdisband", index+1, common->LocalizeText( "guis/hud/fireteam/menu/disband" ).c_str() ), index, 0 );
  		index++;
  
  		sdUIList::InsertItem( list, va( L" %i. %ls\tleave", index+1, common->LocalizeText( "guis/hud/fireteam/menu/leave" ).c_str() ), index, 0 );
  		index++;
  
  
  		int numPromote = 0;
  		for ( int i = 0; i < MAX_CLIENTS; i++ ) {
  			idPlayer* p = gameLocal.GetClient( i );
  			if ( p != NULL && p != localPlayer && fireTeam->IsMember( p ) ) {
  				if ( !p->userInfo.isBot ) {
  					numPromote++;
  				}
  			}
  		}
  		if ( numPromote > 0 ) {
  			sdUIList::InsertItem( list, va( L" %i. %ls\tcanpromote", index+1, common->LocalizeText( "guis/hud/fireteam/menu/promote" ).c_str() ), index, 0 );
  		} else {
  			sdUIList::InsertItem( list, va( L" %i. ^1%ls\tcannot", index+1, common->LocalizeText( "guis/hud/fireteam/menu/promote" ).c_str() ), index, 0 );
  		}
  		index++;
  
  		if ( fireTeam->IsPrivate() ) {
  			sdUIList::InsertItem( list, va( L" %i. %ls\tpublic", index+1, common->LocalizeText( "guis/hud/fireteam/menu/makepublic" ).c_str() ), index, 0 );
  		} else {
  			sdUIList::InsertItem( list, va( L" %i. %ls\tprivate", index+1, common->LocalizeText( "guis/hud/fireteam/menu/makeprivate" ).c_str() ), index, 0 );
  		}
  		index++;
  
  		sdUIList::InsertItem( list, va( L" %i. %ls\trename", index+1, common->LocalizeText( "guis/hud/fireteam/menu/rename" ).c_str() ), index, 0 );
  		index++;
  	} else {
  		bool canPropose = false;
  		if ( fireTeam->MaxMembers() - fireTeam->GetNumMembers() >= 1 ) {
  			for ( int i = 0; i < MAX_CLIENTS; i++ ) {
  				idPlayer *p = gameLocal.GetClient( i );
  				if ( p != NULL && p->GetTeam() == localPlayer->GetTeam() ) {
  					if ( gameLocal.rules->GetPlayerFireTeam( i ) == NULL ) {
  						canPropose = true;
  					}
  				}
  			}
  		}
  
  		if ( canPropose ) {
  			sdUIList::InsertItem( list, va( L" %i. %ls\tcanpropose", index+1, common->LocalizeText( "guis/hud/fireteam/menu/propose" ).c_str() ), index, 0 );
  		} else {
  			sdUIList::InsertItem( list, va( L" %i. ^1%ls\tcannot", index+1, common->LocalizeText( "guis/hud/fireteam/menu/propose" ).c_str() ), index, 0 );
  		}
  		index++;
  
  		sdUIList::InsertItem( list, va( L" %i. %ls\tleave", index+1, common->LocalizeText( "guis/hud/fireteam/menu/leave" ).c_str() ), index, 0 );
  		index++;
  	}
  }
  
  /*
  ============
  idGameLocal::GeneratePlayerListForTask
  ============
  */
  void idGameLocal::GeneratePlayerListForTask( idStr& playerList, const sdPlayerTask* task ) {
  	sdStringBuilder_Heap builder;
  	playerList.Clear();
  
  	sdTaskManagerLocal& manager = sdTaskManager::GetInstance();
  
  	for( int playerIndex = 0; playerIndex < gameLocal.numClients; playerIndex++ ) {
  		idPlayer* player = gameLocal.GetClient( playerIndex );
  		if ( player == NULL ) {
  			continue;
  		}
  		if ( player->GetActiveTaskHandle() != task->GetHandle() ) {
  			continue;
  		}
  
  		builder += " " + player->userInfo.name;
  	}
  	builder.ToString( playerList );
  }
  
  
  /*
  ============
  idGameLocal::InsertTask
  ============
  */
  int idGameLocal::InsertTask( sdUIList* list, const sdPlayerTask* task, bool highlightActive ) {	
  	const char* iconMat = "guis/nodraw";
  	if ( task->GetInfo()->GetNumWayPoints() > 0 ) {
  		const idMaterial* mat = task->GetInfo()->GetWaypointIcon( 0 );
  		if( mat != NULL ) {
  			iconMat = mat->GetName();
  		}
  	}
  
  	idWStr xpString = task->GetInfo()->GetXPString();
  	sdUIList::CleanUserInput( xpString );
  
  	int index = sdUIList::InsertItem( list, va( L"<material = \"::%hs\">\t%ls\t<align = right>%ls", 
  										iconMat, task->GetTitle(), xpString.c_str() ), -1, 0 );
  	
  	return index;
  }
  
  /*
  ============
  idGameLocal::CreateActiveTaskList
  ============
  */
  void idGameLocal::CreateActiveTaskList( sdUIList* list ) {
  	sdTaskManagerLocal& manager = sdTaskManager::GetInstance();
  
  	sdUIList::ClearItems( list );
  	bool showAll;
  	list->GetUI()->PopScriptVar( showAll );
  
  	idPlayer* localPlayer = gameLocal.GetLocalPlayer();
  	idPlayer* localPlayerView = gameLocal.GetLocalViewPlayer();
  	if ( localPlayerView == NULL ) {
  		return;
  	}
  
  	if ( g_debugPlayerList.GetInteger() ) {
  		sdUIList::InsertItem( list, L"Task 1", 0, 1 );
  		idWStrList parms( 1 );
  		keyInputManager->KeysFromBinding( gameLocal.GetDefaultBindContext(), "_taskmenu", true, parms.Alloc() );
  		sdUIList::InsertItem( list, va( L"<fore = 0 1 0 1>\t%ls", common->LocalizeText( "guis/hud/tasks_are_available", parms ).c_str() ), -1, 1 );
  		return;
  	}
  
  	sdTeamInfo* team = localPlayerView->GetGameTeam();
  	if ( team == NULL ) {
  		return;
  	}
  
  	bool canSelect = true;
  	sdPlayerTask* activeTask = NULL;
  
  	sdFireTeam* fireTeam = gameLocal.rules->GetPlayerFireTeam( localPlayerView->entityNumber );
  	if ( fireTeam != NULL ) {
  		activeTask = fireTeam->GetCommander()->GetActiveTask();
  		canSelect = fireTeam->GetCommander() == localPlayer;
  	} else {
  		activeTask = localPlayerView->GetActiveTask();
  	}	
  
  	const sdPlayerTask::nodeType_t& objectiveTasks = sdTaskManager::GetInstance().GetObjectiveTasks( team );
  	sdPlayerTask* objectiveTask = objectiveTasks.Next();
  	if ( ( showAll || activeTask == NULL ) && objectiveTask != NULL ) {
  		int index = InsertTask( list, objectiveTask, false );
  		if( activeTask == NULL ) {
  			list->SelectItem( index );
  		}
  	}
  
  	gameLocal.localPlayerProperties.SetHasTask( !showAll && canSelect && ( activeTask != NULL || ( activeTask == NULL && objectiveTask != NULL ) ) );
  
  	if( showAll && canSelect && localPlayerView == localPlayer ) {
  		playerTaskList_t miniList;
  		manager.BuildTaskList( localPlayer, miniList );
  		int num = Min( sdPlayerTask::MAX_CHOOSABLE_TASKS, miniList.Num() );
  
  		for( int i = 0; i < num; i++ ) {			
  			int index = InsertTask( list, miniList[ i ], false );
  			if( activeTask == miniList[ i ] ){
  				list->SelectItem( index );
  			}
  		}
  		/*
  		int index = sdUIList::InsertItem( list, common->LocalizeText( "guis/game/scoreboard/nomission" ).c_str(), -1, 1 );
  		if( activeTask == NULL ) {
  			list->SelectItem( index );
  		}
  		*/
  		idWStrList args( 1 );
  		keyInputManager->KeysFromBinding( gameLocal.GetDefaultBindContext(), "_taskmenu", true, args.Alloc() );
  		
  		const idWStr localized = common->LocalizeText( "guis/hud/tasks_are_available", args );
  		sdUIList::InsertItem( list, localized.c_str(), -1, 1 );
  
  		return;
  	}
  
  	if ( activeTask != NULL ) {
  		int index = InsertTask( list, activeTask, false );
  		list->SelectItem( index );
  	}
  
  	if ( localPlayerView != localPlayer ) {
  		return;
  	}
  
  	playerTaskList_t miniList;
  	manager.BuildTaskList( localPlayer, miniList );
  
  	int numAvailableTasks = miniList.Num();
  
  	if( objectiveTask != NULL ) {
  		numAvailableTasks++;
  	}
  
  	if ( numAvailableTasks > 1 && canSelect ) {
  		idWStrList args( 1 );
  		keyInputManager->KeysFromBinding( gameLocal.GetDefaultBindContext(), "_taskmenu", true, args.Alloc() );
  
  		const idWStr localized = common->LocalizeText( "guis/hud/tasks_are_available", args );
  		sdUIList::InsertItem( list, localized.c_str(), -1, 1 );
  	}
  
  	if ( list->GetNumItems() == 0 ) {
  		const idWStr localized = common->LocalizeText( "guis/hud/no_tasks" );
  		sdUIList::InsertItem( list, localized.c_str(), -1, 1 );
  	}
  }
  
  /*
  ============
  idGameLocal::GetTaskListHandle
  ============
  */
  qhandle_t idGameLocal::GetTaskListHandle( int index ) {
  	if ( index < 0 || index >= taskListHandles.Num() ) {
  		return -1;
  	}
  
  	return taskListHandles[ index ];
  }
  
  /*
  ============
  idGameLocal::TestGUI_f
  ============
  */
  void idGameLocal::TestGUI_f( const idCmdArgs& args ) {
  	if( args.Argc() < 2 || idStr::Length( args.Argv( 1 )) == 0 ) {
  		gameLocal.uiMainMenuHandle	= gameLocal.LoadUserInterface( "mainmenu",	false, true );
  	} else {
  		gameLocal.uiMainMenuHandle	= gameLocal.LoadUserInterface( args.Argv( 1 ),	false, true );			
  		// jrad - something of a hack:
  		// ensure that we're pushing the proper times through to the gui
  		sdUserInterfaceLocal* ui =gameLocal.GetUserInterface( gameLocal.uiMainMenuHandle );
  		if( ui != NULL ) {
  			ui->SetGUIFlag( sdUserInterfaceLocal::GUI_FRONTEND );
  		}
  	}
  	gameLocal.ShowMainMenu();
  }
  
  /*
  ================
  idGameLocal::LoadUserInterface
  ================
  */
  guiHandle_t idGameLocal::LoadUserInterface( const char* name, bool requireUnique, bool permanent, const char* theme, sdHudModule* module ) {
  	return uiManager->AllocUI( name, requireUnique, permanent, theme, module );
  }
  
  /*
  ================
  idGameLocal::FreeUserInterface
  ================
  */
  void idGameLocal::FreeUserInterface( guiHandle_t handle ) {
  	uiManager->FreeUserInterface( handle );
  }
  
  /*
  ================
  idGameLocal::GetUserInterface
  ================
  */
  sdUserInterfaceLocal* idGameLocal::GetUserInterface( const guiHandle_t handle ) {
  	return static_cast< sdUserInterfaceLocal* >( uiManager->GetUserInterface( handle ) );
  }
  
  
  /*
  ============
  idGameLocal::GetUserInterfaceWindow
  ============
  */
  sdUIWindow* idGameLocal::GetUserInterfaceWindow( const guiHandle_t handle, const char* windowName ) {
  	if( handle.IsValid() ) {
  		sdUserInterfaceLocal* ui = GetUserInterface( handle );
  		if( ui ) {
  			return ui->GetWindow( windowName )->Cast< sdUIWindow >();
  		}
  	}
  	return NULL;
  }
  
  /*
  ============
  idGameLocal::GetUserInterfaceProperty
  ============
  */
  sdProperties::sdProperty*	idGameLocal::GetUserInterfaceProperty( guiHandle_t handle, const char* propertyName, sdProperties::ePropertyType expectedType ) {
  	if( sdUserInterfaceLocal* ui = GetUserInterface( handle ) ) {
  		return ui->GetState().GetProperty( propertyName, expectedType );
  	}
  	return NULL;
  }
  
  /*
  ============
  idGameLocal::GetUserInterfaceProperty_r
  ============
  */
  sdProperties::sdProperty*	idGameLocal::GetUserInterfaceProperty_r( guiHandle_t handle, const char* propertyName, sdProperties::ePropertyType expectedType ) {
  	sdProperties::sdProperty* property = GetUserInterfaceProperty( handle, propertyName, expectedType );
  	if( property ) {
  		return property;
  	}
  
  	if( sdUserInterfaceLocal* ui = GetUserInterface( handle ) ) {
  		idLexer parser( propertyName, idStr::Length( propertyName ), "GetUserInterfaceProperty_r", sdDeclGUI::LEXER_FLAGS | LEXFL_NOERRORS );
  				
  		sdUserInterfaceScope* scope = GetUserInterfaceScope( ui->GetState(), &parser );
  		if( scope ) {
  			idToken token;
  			if( parser.ReadToken( &token )) {
  				return scope->GetProperty( token, expectedType );
  			}			
  		}
  	}
  	return NULL;
  }
  
  /*
  ================
  idGameLocal::GetUserInterfaceScope
  ================
  */
  sdUserInterfaceScope* idGameLocal::GetGlobalUserInterfaceScope( sdUserInterfaceScope& scope, const char* name ) {
  	for( int i = 0; i < uiScopes.Num(); i++ ) {
  		guiScope_t& scope = uiScopes[ i ];
  		if( idStr::Icmp( name, scope.name ) == 0 ) {
  			return scope.scope;
  		}
  	}
  
  	return NULL;
  }
  
  /*
  ================
  idGameLocal::GetUserInterfaceScope
  ================
  */
  sdUserInterfaceScope* idGameLocal::GetUserInterfaceScope( sdUserInterfaceScope& scope, idLexer* src ) {
  	idToken dot;
  	idToken token;
  
  	if ( !src->ReadToken( &token ) ) {
  		return &scope;
  	}
  
  	if ( token.type != TT_NAME ) {
  		src->UnreadToken( &token );
  		return &scope;
  	}
  
  	sdUserInterfaceScope* altScope = &scope;
  
  	sdUserInterfaceScope* globalScope = GetGlobalUserInterfaceScope( scope, token );
  	if ( globalScope ) {
  		if ( !src->ReadToken( &dot ) || dot != "." ) {
  			src->UnreadToken( &dot );
  			src->UnreadToken( &token );
  			return &scope;
  		}
  		src->ReadToken( &token );
  		altScope = globalScope;
  	}
  
  	while( true ) {
  		sdUserInterfaceScope* temp;
  		if ( !token.Cmp( "self" ) ) {
  			temp = altScope;
  		} else {
  			temp = altScope->GetSubScope( token );
  		}
  
  		if ( !temp || !src->ReadToken( &dot ) ) {
  			src->UnreadToken( &token );
  			return altScope;
  		}
  
  		if ( dot != "." ) {
  			src->UnreadToken( &dot );
  			src->UnreadToken( &token );
  			return altScope;
  		}
  
  		src->ReadToken( &token );
  		altScope = temp;
  
  		if ( token.type != TT_NAME ) {
  			return altScope;
  		}
  	}
  }
  
  /*
  ================
  sdUIScopeParser::sdUIScopeParser
  ================
  */
  sdUIScopeParser::sdUIScopeParser( const char* text ) {
  	idStr::Copynz( entryText, text, sizeof( entryText ) );
  
  	int len = idStr::Length( entryText );
  	( *entries.Alloc() ) = entryText;
  	int index = 0;
  	while ( ( index = idStr::FindChar( entryText, '.', index, len - 1 ) ) != idStr::INVALID_POSITION ) {
  		entryText[ index ] = '\0';
  		index++;
  		const char** entry = entries.Alloc();
  		if ( entry == NULL ) {
  			gameLocal.Error( "sdUIScopeParser: Max Entries Hit" );
  		}
  		*entry = &entryText[ index ];
  	}
  
  	entryIndex = 0;
  }
  
  /*
  ================
  idGameLocal::GetUserInterfaceScope
  ================
  */
  sdUserInterfaceScope* idGameLocal::GetUserInterfaceScope( sdUserInterfaceScope& scope, sdUIScopeParser& src ) {
  	sdUserInterfaceScope* altScope = &scope;
  
  	const char* token = src.GetNextEntry();
  	if ( src.IsLastEntry() ) {
  		src.Revert();
  		return altScope;
  	}
  
  	sdUserInterfaceScope* globalScope = GetGlobalUserInterfaceScope( scope, token );
  	if ( globalScope != NULL ) {
  		altScope = globalScope;
  		token = src.GetNextEntry();
  	}
  
  	while ( true ) {
  		if ( src.IsLastEntry() ) {
  			src.Revert();
  			return altScope;
  		}
  
  		sdUserInterfaceScope* temp = altScope->GetSubScope( token );
  		if ( temp == NULL ) {
  			gameLocal.Warning( "idGameLocal::GetUserInterfaceScope Failed To Find Scope: '%s'", token );
  			return altScope;
  		}
  
  		altScope = temp;
  		token = src.GetNextEntry();
  	}
  }
  
  /*
  ============
  idGameLocal::GuiFrameEvents
  ============
  */
  void idGameLocal::GuiFrameEvents( bool outOfSequence ) {
  	uiManager->Update( outOfSequence );
  }
  
  /*
  ============
  idGameLocal::CreateInventoryList
  ============
  */
  void idGameLocal::CreateInventoryList( sdUIList* list ) {
  	sdUserInterfaceLocal* ui = list->GetUI();
  		
  	assert( ui );
  
  	idStr classname;
  	int bank;
  	int package;
  
  	ui->PopScriptVar( classname );
  	ui->PopScriptVar( bank );
  	ui->PopScriptVar( package );
  	
  
  	idPlayer* player = gameLocal.GetLocalPlayer();
  	idList< itemBankPair_t > items;
  	ui->EnumerateItemsForBank( items, classname, bank, package );
  
  	for( int i = 0; i < items.Num(); i++ ) {
  		const wchar_t* formatString = va( L"<material = '::%hs'>\t<align = top><loc = '%hs'>", items[ i ].first->GetData().GetString( "mtr_weaponmenu", "default" ), items[ i ].first->GetItemName()->GetName() );
  		int index = sdUIList::InsertItem( list, formatString, -1, 0 );
  		list->SetItemDataInt( items[ i ].second, index, 0 );
  	}
  }
  
  typedef sdPair< int, const vidmode_t* > modePair_t;
  struct sortVidMode_t {
  	int operator()( const modePair_t& a, const modePair_t& b ) {
  		if( a.second->width == b.second->width ) {
  			return a.second->height - b.second->height;
  		}
  		return a.second->width - b.second->width;
  		return 0;
  	}
  };
  
  /*
  ============
  idGameLocal::CreateVideoModeList
  ============
  */
  void idGameLocal::CreateVideoModeList( sdUIList* list ) {
  	sdUIList::ClearItems( list );
  
  	bool fullScreen;
  	list->GetUI()->PopScriptVar( fullScreen );
  
  	int aspect;
  	list->GetUI()->PopScriptVar( aspect );
  
  	int currentMode = cvarSystem->GetCVarInteger( "r_mode" );
  	int startMode = 3;
  	int selIndex = -1;
  
  	static const char* ratios[ 4 ] = {
  		"(4:3)",
  		"(16:9)",
  		"(16:10)",
  		"(5:4)"
  	};
  	
  	idList< modePair_t > modes;
  
  	for( int i = startMode; i < common->GetNumVideoModes(); i++ ) {
  		const vidmode_t& mode = common->GetVideoMode( i );
  		// don't show modes that the monitor says it doesn't support
  		if ( fullScreen && !mode.available ) {
  			continue;
  		}
  		if ( aspect != -1 && aspect != mode.aspectRatio ) {
  			continue;
  		}
  		modes.Append( modePair_t( i, &mode ) );
  	}
  
  	sdQuickSort( modes.Begin(), modes.End(), sortVidMode_t() );
  
  
  	int nextBestModeWidth = -1;
  	int nextBestMode = -1;
  
  	const int currentWidth = currentMode == -1 ? cvarSystem->GetCVarInteger( "r_customWidth" ) : common->GetVideoMode( currentMode ).width;
  
  	for( int i = 0; i < modes.Num(); i++ ) {
  		const vidmode_t& mode = *modes[ i ].second;
  		int index = sdUIList::InsertItem( list, va( L"%ix%i %hs\t%i", mode.width, mode.height, ratios[ mode.aspectRatio ], modes[ i ].first ), -1, 0 );
  		if( modes[ i ].first == currentMode ) {
  			selIndex = index;
  		}
  		if( modes[ i ].second->width < currentWidth && modes[ i ].second->width > nextBestModeWidth ) {
  			nextBestMode = index;
  			nextBestModeWidth = modes[ i ].second->width;
  		}
  	}
  
  	if( selIndex != -1 ) {
  		list->SelectItem( selIndex );
  	} else if( nextBestMode != -1 ) {
  		list->SelectItem( nextBestMode );
  	}
  }
  
  /*
  ============
  idGameLocal::CreateCampaignList
  ============
  */
  void idGameLocal::CreateCampaignList( sdUIList* list ) {
  	sdUIList::ClearItems( list );
  	for( int i = 0; i < gameLocal.campaignMetaDataList->GetNumMetaData(); i++ ) {
  		const metaDataContext_t& metaDataContext = gameLocal.campaignMetaDataList->GetMetaDataContext( i );
  		if ( !gameLocal.IsMetaDataValidForPlay( metaDataContext, true ) ) {
  			continue;
  		}
  		const idDict& dict = *metaDataContext.meta;
  
  		const char* materialName = dict.GetString( "server_shot_thumb" );
  		const char* prettyName = dict.GetString( "pretty_name" );
  		const char* defName = dict.GetString( "metadata_name" );
  		int index = sdUIList::InsertItem( list, va( L"%hs\t%hs\t%hs", prettyName, defName, materialName ), -1, 0 );
  	}
  }
  
  /*
  ============
  idGameLocal::CreateTacticalList
  ============
  */
  void idGameLocal::CreateTacticalList( sdUIList* list ) {
  	sdUIList::ClearItems( list );
  	for( int i = 0; i < gameLocal.tacticalMetaDataList->GetNumMetaData(); i++ ) {
  		const metaDataContext_t& metaDataContext = gameLocal.tacticalMetaDataList->GetMetaDataContext( i );
  		if ( !gameLocal.IsMetaDataValidForPlay( metaDataContext, true ) ) {
  			continue;
  		}
  		const idDict& dict = *metaDataContext.meta;
  
  		const char* materialName = dict.GetString( "server_shot_thumb" );
  		const char* prettyName = dict.GetString( "pretty_name" );
  		const char* defName = dict.GetString( "metadata_name" );
  		int index = sdUIList::InsertItem( list, va( L"%hs\t%hs\t%hs", prettyName, defName, materialName ), -1, 0 );
  	}
  }
  
  /*
  ============
  idGameLocal::CreateMapList
  ============
  */
  void idGameLocal::CreateMapList( sdUIList* list ) {
  	sdUIList::ClearItems( list );
  
  	sdAddonMetaDataList* metaData = fileSystem->ListAddonMetaData( "mapMetaData" );
  	for( int i = 0; i < metaData->GetNumMetaData(); i++ ) {
  		const metaDataContext_t& metaDataContext = metaData->GetMetaDataContext( i );
  		if ( !gameLocal.IsMetaDataValidForPlay( metaDataContext, true ) ) {
  			continue;
  		}
  		const idDict& data = *metaDataContext.meta;
  
  		const char* materialName = data.GetString( "server_shot_thumb", "levelshots/thumbs/generic.tga" );
  		const char* defName = data.GetString( "metadata_name" );
  		const char* prettyName = data.GetString( "pretty_name" );
  		int index = sdUIList::InsertItem( list, va( L"%hs\t%hs\t%hs", prettyName, defName, materialName ), -1, 0 );
  	}
  
  	fileSystem->FreeAddonMetaDataList( metaData );
  }
  
  /*
  ============
  idGameLocal::PacifierUpdate
  ============
  */
  void idGameLocal::PacifierUpdate() {
  	gameLocal.localPlayerProperties.PacifierUpdate();
  
  	deviceContext->BeginEmitFullScreen();
  
  	if ( sdUserInterfaceLocal* ui = GetUserInterface( uiLevelLoadHandle ) ) {
  		ui->SetCurrentTime( sys->Milliseconds() );
  		ui->Update();
  		ui->Draw();
  	}
  
  	deviceContext->End();
  }
  
  /*
  ============
  idGameLocal::ShowLevelLoadScreen
  ============
  */
  void idGameLocal::ShowLevelLoadScreen( const char* mapName ) {
  	if ( networkSystem->IsDedicated() ) {
  		return;
  	}
  /*
  	if ( !reloadingSameMap ) {
  		bytesNeededForMapLoad = GetBytesNeededForMapLoad( mapName );
  	} else {
  		bytesNeededForMapLoad = 30 * 1024 * 1024;
  	}
  */
  	idStr stripped = mapName;
  	stripped.StripFileExtension();
  
  	mapMetaData = mapMetaDataList->FindMetaData( stripped, &defaultMetaData );
  	mapInfo = declMapInfoType.LocalFind( mapMetaData->GetString( "mapinfo", "default" ) );
  
  	using namespace sdProperties;	
  	if ( sdUserInterfaceScope* scope = globalProperties.GetSubScope( "mapinfo" ) ) {
  		if ( sdProperty* property = scope->GetProperty( "numObjectives", PT_FLOAT ) ) {
  			*property->value.floatValue = mapInfo->GetData().GetFloat( "numObjectives", "0" );
  		}
  	}
  
  
  	sdUserInterfaceLocal* ui;
  
  	ui = GetUserInterface( pureWaitHandle );
  	if ( ui != NULL ) {
  		ui->Deactivate( false );
  	}
  
  	uiLevelLoadHandle = LoadUserInterface( "loadscreen", false, true );
  
  	UpdateCampaignStats( true );
  
  	ui = GetUserInterface( uiLevelLoadHandle );
  	if ( ui != NULL ) {
  		ui->SetCurrentTime( sys->Milliseconds() );
  		ui->Activate();
  		PacifierUpdate();
  	}
  }
  
  
  /*
  ============
  idGameLocal::UpdateLevelLoadScreen
  ============
  */
  void idGameLocal::UpdateLevelLoadScreen( const wchar_t* status ) {
  	if ( networkSystem->IsDedicated() ) {
  		return;
  	}
  
  	using namespace sdProperties;
  	sdUserInterfaceScope* scope = globalProperties.GetSubScope( "mapinfo" );	
  	if( scope ) {
  		if( sdProperty* property = scope->GetProperty( "mapStatus", PT_WSTRING )) {
  			*property->value.wstringValue = status;			
  		}
  	}
  	common->PacifierUpdate();
  }
  
  
  /*
  ============
  idGameLocal::HideLevelLoadScreen
  ============
  */
  void idGameLocal::HideLevelLoadScreen() {
  	if ( networkSystem->IsDedicated() ) {
  		return;
  	}
  
  	if ( sdUserInterfaceLocal* ui = GetUserInterface( uiLevelLoadHandle ) ) {		
  		ui->Deactivate();
  	}
  }
  
  
  /*
  ============
  idGameLocal::GetUIManager
  ============
  */
  sdUserInterfaceManager* idGameLocal::GetUIManager() {
  	return uiManager;
  }
  
  /*
  ============
  idGameLocal::SetGUIFloat
  ============
  */
  void idGameLocal::SetGUIFloat( int handle, const char* name, float value ) {
  	if ( networkSystem->IsDedicated() ) {	
  		gameLocal.Warning( "Tried to run a GUI command on the server" );
  	}
  
  	sdUserInterfaceScope* scope;
  	if ( handle == GUI_GLOBALS_HANDLE ) {
  		scope = &gameLocal.globalProperties;
  	} else {
  		sdUserInterfaceLocal* ui = gameLocal.GetUserInterface( handle );
  		if ( !ui ) {
  			gameLocal.Warning( "idGameLocal::SetGUIFloat: could not find GUI" );
  			return;
  		}
  		scope = &ui->GetState();
  	}
  
  	sdUIScopeParser parser( name );
  	scope = gameLocal.GetUserInterfaceScope( *scope, parser );
  
  	sdProperties::sdProperty* prop = NULL;
  
  	const char* token = parser.GetNextEntry();
  	if ( token == NULL ) {
  		assert( false );
  	} else {
  		prop = scope->GetProperty( token, sdProperties::PT_FLOAT );
  	}
  
  	if ( prop != NULL ) {
  		*( prop->value.floatValue ) = value;
  	} else {
  		gameLocal.Warning( "idGameLocal::SetGUIFloat: could not find float property '%s'", name );
  	}
  }
  
  /*
  ============
  idGameLocal::GetGUIFloat
  ============
  */
  float idGameLocal::GetGUIFloat( int handle, const char* name ) {
  	if( networkSystem->IsDedicated() ) {	
  		gameLocal.Warning( "Tried to run a GUI command on the server" );
  	}
  
  	sdUserInterfaceScope* scope;
  
  	if ( handle == GUI_GLOBALS_HANDLE ) {
  		scope = &gameLocal.globalProperties;
  	} else {
  		sdUserInterfaceLocal* ui = gameLocal.GetUserInterface( handle );
  		if( !ui ) {
  			gameLocal.Warning( "idGameLocal::GetGUIFloat: could not find GUI" );
  			return 0.0f;
  		}
  		scope = &ui->GetState();
  	}
  
  	sdUIScopeParser parser( name );
  	scope = gameLocal.GetUserInterfaceScope( *scope, parser );
  
  	sdProperties::sdProperty* prop = NULL;
  
  	const char* token = parser.GetNextEntry();
  	if ( token == NULL ) {
  		assert( false );
  	} else {
  		prop = scope->GetProperty( token, sdProperties::PT_FLOAT );
  	}
  
  	if ( prop == NULL ) {
  		 gameLocal.Warning( "idGameLocal::GetGUIFloat: could not find float property '%s'", name );
  		 return 0.f;
  	 }
  
  	return prop->value.floatValue->GetValue();
  }
  
  /*
  ============
  idGameLocal::SetGUIInt
  ============
  */
  void idGameLocal::SetGUIInt( int handle, const char* name, int value ) {
  	if ( networkSystem->IsDedicated() ) {	
  		gameLocal.Warning( "Tried to run a GUI command on the server" );
  	}
  
  	sdUserInterfaceScope* scope;
  	if ( handle == GUI_GLOBALS_HANDLE ) {
  		scope = &gameLocal.globalProperties;
  	} else {
  		sdUserInterfaceLocal* ui = gameLocal.GetUserInterface( handle );
  		if ( !ui ) {
  			gameLocal.Warning( "idGameLocal::SetGUIInt: could not find GUI" );
  			return;
  		}
  		scope = &ui->GetState();
  	}
  
  	sdUIScopeParser parser( name );
  	scope = gameLocal.GetUserInterfaceScope( *scope, parser );
  
  	sdProperties::sdProperty* prop = NULL;
  
  	const char* token = parser.GetNextEntry();
  	if ( token == NULL ) {
  		assert( false );
  	} else {
  		prop = scope->GetProperty( token, sdProperties::PT_INT );
  	}
  
  	if ( prop != NULL ) {
  		*( prop->value.intValue ) = value;
  	} else {
  		gameLocal.Warning( "idGameLocal::SetGUIInt: could not find int property '%s'", name );
  	}
  }
  
  /*
  ============
  idGameLocal::SetGUIVec2
  ============
  */
  void idGameLocal::SetGUIVec2( int handle, const char* name, const idVec2& value ) {
  	if ( networkSystem->IsDedicated() ) {	
  		gameLocal.Warning( "Tried to run a GUI command on the server" );
  	}
  
  	sdUserInterfaceScope* scope;
  	if ( handle == GUI_GLOBALS_HANDLE ) {
  		scope = &gameLocal.globalProperties;
  	} else {
  		sdUserInterfaceLocal* ui = gameLocal.GetUserInterface( handle );
  		if( !ui ) {
  			gameLocal.Warning( "idGameLocal::SetGUIVec2: could not find GUI" );
  			return;
  		}
  		scope = &ui->GetState();
  	}
  
  	sdUIScopeParser parser( name );
  	scope = gameLocal.GetUserInterfaceScope( *scope, parser );
  
  	sdProperties::sdProperty* prop = NULL;
  
  	const char* token = parser.GetNextEntry();
  	if ( token == NULL ) {
  		assert( false );
  	} else {
  		prop = scope->GetProperty( token, sdProperties::PT_VEC2 );
  	}
  
  	if ( prop != NULL ) {
  		*( prop->value.vec2Value ) = value;
  	} else {
  		gameLocal.Warning( "idGameLocal::SetGUIVec2: could not find vec2 property '%s'", name );
  	}
  }
  
  /*
  ============
  idGameLocal::SetGUIVec3
  ============
  */
  void idGameLocal::SetGUIVec3( int handle, const char* name, const idVec3& value ) {
  	if ( networkSystem->IsDedicated() ) {	
  		gameLocal.Warning( "Tried to run a GUI command on the server" );
  	}
  
  	sdUserInterfaceScope* scope;
  	if ( handle == GUI_GLOBALS_HANDLE ) {
  		scope = &gameLocal.globalProperties;
  	} else {
  		sdUserInterfaceLocal* ui = gameLocal.GetUserInterface( handle );
  		if( !ui ) {
  			gameLocal.Warning( "idGameLocal::SetGUIVec3: could not find GUI" );
  			return;
  		}
  		scope = &ui->GetState();
  	}
  
  	sdUIScopeParser parser( name );
  	scope = gameLocal.GetUserInterfaceScope( *scope, parser );
  
  	sdProperties::sdProperty* prop = NULL;
  
  	const char* token = parser.GetNextEntry();
  	if ( token == NULL ) {
  		assert( false );
  	} else {
  		prop = scope->GetProperty( token, sdProperties::PT_VEC3 );
  	}
  
  	if ( prop != NULL ) {
  		*( prop->value.vec3Value ) = value;
  	} else {
  		gameLocal.Warning( "idGameLocal::SetGUIVec3: could not find vec3 property '%s'", name );
  	}
  }
  
  /*
  ============
  idGameLocal::SetGUIVec4
  ============
  */
  void idGameLocal::SetGUIVec4( int handle, const char* name, const idVec4& value ) {
  	if ( networkSystem->IsDedicated() ) {	
  		gameLocal.Warning( "Tried to run a GUI command on the server" );
  	}
  
  	sdUserInterfaceScope* scope;
  	if ( handle == GUI_GLOBALS_HANDLE ) {
  		scope = &gameLocal.globalProperties;
  	} else {
  		sdUserInterfaceLocal* ui = gameLocal.GetUserInterface( handle );
  		if( !ui ) {
  			gameLocal.Warning( "idGameLocal::SetGUIVec4: could not find GUI" );
  			return;
  		}
  		scope = &ui->GetState();
  	}
  
  	sdUIScopeParser parser( name );
  	scope = gameLocal.GetUserInterfaceScope( *scope, parser );
  
  	sdProperties::sdProperty* prop = NULL;
  
  	const char* token = parser.GetNextEntry();
  	if ( token == NULL ) {
  		assert( false );
  	} else {
  		prop = scope->GetProperty( token, sdProperties::PT_VEC4 );
  	}
  
  	if ( prop != NULL ) {
  		*( prop->value.vec4Value ) = value;
  	} else {
  		gameLocal.Warning( "idGameLocal::SetGUIVec4: could not find vec4 property '%s'", name );
  	}
  }
  
  /*
  ============
  idGameLocal::SetGUIString
  ============
  */
  void idGameLocal::SetGUIString( int handle, const char* name, const char* value ) {
  	if ( networkSystem->IsDedicated() ) {	
  		gameLocal.Warning( "Tried to run a GUI command on the server" );
  	}
  
  	sdUserInterfaceScope* scope;
  	if ( handle == GUI_GLOBALS_HANDLE ) {
  		scope = &gameLocal.globalProperties;
  	} else {
  		sdUserInterfaceLocal* ui = gameLocal.GetUserInterface( handle );
  		if( !ui ) {
  			gameLocal.Warning( "idGameLocal::SetGUIString: could not find GUI" );
  			return;
  		}
  		scope = &ui->GetState();
  	}
  
  	sdUIScopeParser parser( name );
  	scope = gameLocal.GetUserInterfaceScope( *scope, parser );
  
  	sdProperties::sdProperty* prop = NULL;
  
  	const char* token = parser.GetNextEntry();
  	if ( token == NULL ) {
  		assert( false );
  	} else {
  		prop = scope->GetProperty( token, sdProperties::PT_STRING );
  	}
  
  	if ( prop != NULL ) {
  		*( prop->value.stringValue ) = value;
  	} else {
  		gameLocal.Warning( "idGameLocal::SetGUIString: could not find string property '%s'", name );
  	}
  }
  
  /*
  ============
  idGameLocal::SetGUIWString
  ============
  */
  void idGameLocal::SetGUIWString( int handle, const char* name, const wchar_t* value ) {
  	if( networkSystem->IsDedicated() ) {	
  		gameLocal.Warning( "Tried to run a GUI command on the server" );
  	}
  
  	sdUserInterfaceScope* scope;
  
  	if ( handle == GUI_GLOBALS_HANDLE ) {
  		scope = &gameLocal.globalProperties;
  	} else {
  		sdUserInterfaceLocal* ui = gameLocal.GetUserInterface( handle );
  		if( !ui ) {
  			gameLocal.Warning( "idGameLocal::SetGUIWString: could not find GUI" );
  			return;
  		}
  		scope = &ui->GetState();
  	}
  
  	sdUIScopeParser parser( name );
  	scope = gameLocal.GetUserInterfaceScope( *scope, parser );
  
  	sdProperties::sdProperty* prop = NULL;
  
  	const char* token = parser.GetNextEntry();
  	if ( token == NULL ) {
  		assert( false );
  	} else {
  		prop = scope->GetProperty( token, sdProperties::PT_WSTRING );
  	}
  
  	if ( prop != NULL ) {
  		*( prop->value.wstringValue ) = value;
  	} else {
  		gameLocal.Warning( "idGameLocal::SetGUIWString: could not find string property '%s'", name );
  	}
  }
  
  /*
  ============
  idGameLocal::SetGUITheme
  ============
  */
  void idGameLocal::SetGUITheme( guiHandle_t handle, const char* theme ) {
  	if( !handle ) {
  		gameLocal.Error( "SetGUITheme: Invalid GUI handle." );
  		return;
  	}
  
  	sdUserInterfaceLocal* ui = static_cast< sdUserInterfaceLocal* >( uiManager->GetUserInterface( handle ) );
  	if( !ui ) {
  		gameLocal.Warning( "idGameLocal::SetGUITheme : Could not find GUI." );
  		return;
  	}
  	ui->SetTheme( theme );
  }
  
  /*
  ============
  idGameLocal::GUIPostNamedEvent
  ============
  */
  void idGameLocal::GUIPostNamedEvent( int handle, const char* window, const char* name ) {
  	if( networkSystem->IsDedicated() ) {	
  		gameLocal.Warning( "Tried to run a GUI command on the server" );
  	}
  
  	sdUserInterfaceLocal* ui = gameLocal.GetUserInterface( handle );
  	if( !ui ) {
  		gameLocal.Warning( "idGameLocal::GUIPostNamedEvent: could not find GUI" );
  		return;
  	}
  	sdUIObject* object = NULL;
  	if( idStr::Length( window )) {
  		object = ui->GetWindow( window );
  	}
  
  	if( !object && idStr::Length( window )) {
  		gameLocal.Warning( "idGameLocal::GUIPostNamedEvent: could not find window '%s'", window );
  		return;
  	} else if( object ) {
  		object->PostNamedEvent( name, false );
  	} else {	
  		ui->PostNamedEvent( name );
  	}	
  }
  
  
  
  /*
  ============
  idGameLocal::UpdateCampaignStats
  ============
  */
  void idGameLocal::UpdateCampaignStats( bool allowMedia ) {
  	if( networkSystem->IsDedicated() ) {
  		return;
  	}
  
  	sdGameRules* rulesInstance = gameLocal.GetRulesInstance( serverInfo.GetString( "si_rules" ) );
  
  	if( rulesInstance != NULL ) {
  		rulesInstance->UpdateClientFromServerInfo( serverInfo, allowMedia );
  	}
  }
  
  
  /*
  ============
  idGameLocal::CreateWeaponSwitchList
  ============
  */
  void idGameLocal::CreateWeaponSwitchList( sdUIList* list ) {
  	sdUIList::ClearItems( list );
  	idPlayer* localPlayer = gameLocal.GetLocalPlayer();
  	if( localPlayer == NULL ) {
  		return;
  	}
  
  	using namespace sdProperties;
  
  	const sdInventory& inv = localPlayer->GetInventory();
  	const int currentWeapon = inv.GetCurrentWeapon();
  	const int switchingWeapon = inv.GetSwitchingWeapon();
  
  	if( currentWeapon == -1 || switchingWeapon == -1 ) {
  		return;
  	}
  
  	const sdDeclInvItem* item = inv.GetItem( currentWeapon );
  	const sdDeclInvItem* switchItem = inv.GetItem( switchingWeapon );
  
  	typedef sdPair< const sdDeclInvItem*, int > invElement_t;
  	idStaticList< invElement_t, 10 > items;
  
  	for( int i = 0; i < inv.GetNumItems(); i++ ) {		
  		if ( !inv.CanAutoEquip( i, false ) ) {
  			continue;
  		}
  
  		const sdDeclInvItem* item = inv.GetItem( i );
  		if ( inv.GetSlotForWeapon( i ) != -1 ) {
  			int j = 0;
  			const sdDeclInvSlot* slot = gameLocal.declInvSlotType.LocalFindByIndex( item->GetSlot() );
  
  			while( j < items.Num() ) {
  				if( item->GetSlot() < items[ j ].first->GetSlot() ) {
  					break;
  				}
  
  				const sdDeclInvSlot* otherSlot = gameLocal.declInvSlotType.LocalFindByIndex( items[ j ].first->GetSlot() );
  				if( slot->GetBank() < otherSlot->GetBank() ) {
  					break;
  				}
  
  				j++;
  			}
  			items.Insert( invElement_t( item, i ), j );
  		}
  	}
  
  	int offset = 0;
  	for( int i = 0; i < items.Num(); i++ ) {
  		int bankNumber = i;
  		const sdDeclInvItem* invItem = inv.GetItem( items[ i ].second );
  		const bool available = inv.CheckWeaponHasAmmo( invItem ) || invItem->GetData().GetBool( "select_when_empty", "0" );
  		const char* material = invItem->GetData().GetString( "mtr_weaponmenu", "_default" );
  		const int title = invItem->GetItemName()->Index();
   		float weaponAmmo = -1.0f;
  	
  		const idList< itemClip_t >& itemClips = invItem->GetClips();
  
  		if ( itemClips.Num() && itemClips[ 0 ].ammoPerShot > 0 ) {
  			float ammo;
  			float totalAmmo;
  			if( invItem->GetData().GetBool( "show_all_ammo", "0" )) {
  				ammo = inv.GetAmmo( itemClips[ 0 ].ammoType ) / itemClips[ 0 ].ammoPerShot;
  				totalAmmo = inv.GetMaxAmmo( itemClips[ 0 ].ammoType ) / itemClips[ 0 ].ammoPerShot;				
  			} else {
  				ammo = inv.GetClip( items[ i ].second, 0 );
  				totalAmmo = inv.GetClipSize( items[ i ].second, 0 );				
  			}
  			weaponAmmo = idMath::ClampFloat( 0.0f, 1.0f, ammo / totalAmmo );
  		}
  
  		int index = sdUIList::InsertItem( list, va( L"%i\t<material = '::%hs'>\t%f\t%i\t%i", invItem->GetSlot(), material, weaponAmmo, title, available ), -1, 0 );
  
  		if( invItem == switchItem ) {
  			list->SelectItem( index );
  			localPlayer->SetWeaponSelectionItemIndex( bankNumber );
  		}
  	}
  }
  
  /*
  ============
  idGameLocal::CreateColorList
  ============
  */
  void idGameLocal::CreateColorList( sdUIList* list ) {
  	idVec4 customColor;
  	list->GetUI()->PopScriptVar( customColor );
  
  	idStr currentColorStr;
  	list->GetUI()->PopScriptVar( currentColorStr );
  
  	idVec4 currentColor = colorWhite;
  	if( !currentColorStr.IsEmpty() ) {
  		currentColor = sdTypeFromString< idVec4 >( currentColorStr.c_str() );
  	}
  
  	// if refreshing, only add new custom colors
  	bool refreshOnly;
  	list->GetUI()->PopScriptVar( refreshOnly );
  
  	if( refreshOnly == false ) {
  		sdUIList::ClearItems( list );
  	}
  
  	idWStr str;
  	idWStr colorSymbol;
  	idStr color;
  
  	bool customMatchesDefault = false;
  	int toSelect = -1;
  	static const float COLOR_EPSILON = 0.001f;
  
  	// check against previous custom colors
  	if( refreshOnly == false || list->GetNumItems() == 0 ) {
  		// skip default color
  		for( int i = 1; i < COLOR_BITS + 1; i++ ) {
  			const idVec4& c = idStr::ColorForIndex( i );
  			if( c.ToVec3().Compare( customColor.ToVec3(), COLOR_EPSILON ) ) {
  				customMatchesDefault = true;
  			}
  			colorSymbol = va( L"%hs", idStr::StrForColorIndex( i ) );
  			sdUIList::CleanUserInput( colorSymbol );
  			color = c.ToString( 2 );
  			str = va( L"<fore = %hs><material = '::guis/assets/white'>\t%ls\t%hs", color.c_str(), colorSymbol.c_str(), color.c_str() );
  			int index = sdUIList::InsertItem( list, str.c_str(), -1, 0 );
  
  			if( currentColor.ToVec3().Compare( c.ToVec3(), COLOR_EPSILON ) ) {
  				toSelect = index;
  			}
  		}
  	} else {
  		for( int i = 0; i < list->GetNumItems(); i++ ) {
  			idVec4 c = sdTypeFromString< idVec4 >( va( "%ls", list->GetItemText( i, 2, true ) ) );
  			if( c.ToVec3().Compare( customColor.ToVec3(), COLOR_EPSILON ) ) {
  				customMatchesDefault = true;
  			}
  			if( currentColor.ToVec3().Compare( c.ToVec3(), COLOR_EPSILON ) ) {
  				toSelect = i;
  			}
  		}
  	}
  
  
  
  	if( !customMatchesDefault ) {
  		str = va( L"<fore = %hs><material = '::guis/assets/white'>\t%\t%hs", customColor.ToString( 2 ), customColor.ToString( 2 ) );
  		int index = sdUIList::InsertItem( list, str.c_str(), -1, 0 );
  		if( currentColor.ToVec3().Compare( customColor.ToVec3(), COLOR_EPSILON ) ) {
  			toSelect = index;
  		}
  	}
  	list->SelectItem( toSelect );
  }
  
  /*
  ============
  idGameLocal::CreateSpawnLocationList
  ============
  */
  void idGameLocal::CreateSpawnLocationList( sdUIList* list ) {
  	sdUIList::ClearItems( list );
  	idStr team;
  	list->GetUI()->PopScriptVar( team );
  
  	const sdTeamInfo* info = sdTeamManager::GetInstance().GetTeamSafe( team.c_str() );
  	if( info == NULL ) {
  		return;
  	}
  
  	int currentSpawn = -1;
  	int defaultSpawn = -1;
  
  	int numLocs = info->GetNumSpawnLocations();
  	int* spawnCounts = static_cast< int* >( _alloca( numLocs * sizeof( int ) ) );
  	memset( &spawnCounts[ 0 ], 0, numLocs * sizeof( int ) );
  	
  	const idPlayer* localPlayer = gameLocal.GetLocalPlayer();
  	for( int i = 0; i < MAX_CLIENTS; i++ ) {
  		idPlayer* player = gameLocal.GetClient( i );
  		if( player == NULL || player->GetGameTeam() != info ) {
  			continue;
  		}
  		const idEntity* playerSpawnLoc = player->GetSpawnPoint();
  		for( int si = 0; si < numLocs; si++ ) {
  			const idEntity* spawn = info->GetSpawnLocation( si );
  			if( playerSpawnLoc == NULL && spawn == info->GetDefaultSpawn() ) {
  				defaultSpawn = si;
  				if( playerSpawnLoc == NULL ) {
  					spawnCounts[ defaultSpawn ]++;
  					break;
  				}
  			}
  			if( spawn == playerSpawnLoc ) {
  				spawnCounts[ si ]++;
  
  				if( player == localPlayer ) {
  					currentSpawn = si;
  				}
  				break;
  			}			
  		}
  	}
  
  	idWStr loc;	
  	for( int i = 0; i < numLocs; i++ ) {
  		const idEntity* spawn = info->GetSpawnLocation( i );
  
  		sdLocationMarker::GetLocationText( spawn->GetPhysics()->GetOrigin(), loc );
  		int index = sdUIList::InsertItem( list, va( L"%ls\t%i\t%hs", loc.c_str(), spawnCounts[ i ], spawn->GetName() ), -1, 0 );
  		if( i == currentSpawn ) {
  			list->SelectItem( index );
  		}
  	}
  	int index = sdUIList::InsertItem( list, L"<loc = 'guis/mainmenu/defaultspawn'>\t\t", -1, 0 );
  	if( currentSpawn == -1 ) {
  		list->SelectItem( index );
  	}
  }
  
  
  /*
  ============
  idGameLocal::CreateMSAAList
  ============
  */
  void idGameLocal::CreateMSAAList( sdUIList* list ) {
  	int num = renderSystem->GetNumMSAAModes();
  	for (int i=0; i<num; i++) {
  		int val;
  		const char *msaaName = renderSystem->GetMSAAMode( i, val );
  		int index = sdUIList::InsertItem( list, va( L"%hs", msaaName ), -1, 0 );
  		list->SetItemDataInt( val, index, 0, true );
  	}
  }
  
  
  /*
  ============
  idGameLocal::CreateSoundPlaybackList
  ============
  */
  void idGameLocal::CreateSoundPlaybackList( sdUIList* list ) {
  	sdUIList::ClearItems( list );
  	const idWStrList* devices = soundSystem->ListSoundPlaybackDevices();
  	
  	for( int i = 0; devices != NULL && i < devices->Num(); i++ ) {
  		const idWStr& str = (*devices)[ i ];
  		int hash = soundSystem->GetAudioDeviceHash( str.c_str() );
  		sdUIList::InsertItem( list, va( L"%ls\t%d", str.c_str(), hash ), -1, 0 );
  	}
  
  	soundSystem->FreeDeviceList( devices );
  }
  
  
  /*
  ============
  idGameLocal::CreateSoundCaptureList
  ============
  */
  void idGameLocal::CreateSoundCaptureList( sdUIList* list ) {
  	sdUIList::ClearItems( list );
  
  	const idWStrList* devices = soundSystem->ListSoundCaptureDevices();
  
  	for( int i = 0; devices != NULL && i < devices->Num(); i++ ) {
  		const idWStr& str = (*devices)[ i ];
  		int hash = soundSystem->GetAudioDeviceHash( str.c_str() );
  		sdUIList::InsertItem( list, va( L"%ls\t%d", str.c_str(), hash ), -1, 0 );
  	}
  
  	soundSystem->FreeDeviceList( devices );
  }
  
  /*
  ============
  idGameLocal::CreateModList
  ============
  */
  void idGameLocal::CreateModList( sdUIList* list ) {
  	sdUIList::ClearItems( list );
  
  	idModList* mods = fileSystem->ListMods();
  
  	if( mods != NULL ) {
  		for( int i = 0; i < mods->GetNumMods(); i++ ) {
  			sdUIList::InsertItem( list, va( L"%hs\t%hs", mods->GetDescription( i ), mods->GetMod( i ) ), -1, 0 );
  		}
  		fileSystem->FreeModList( mods );
  	}
  }
  
  static void AddLifeStatsItems( sdUIList* list, const char* material, const idList< const sdStatsTracker::lifeStatsData_t* >& data ) {
  	for( int i = 0; i < data.Num(); i++ ) {
  		const sdStatsTracker::lifeStatsData_t* item = data[ i ];
  		const lifeStat_t& stat = gameLocal.lifeStats[ item->index ];
  
  		
  		switch( item->newValue.GetType() ) {
  			case sdNetStatKeyValue::SVT_INT:
  			case sdNetStatKeyValue::SVT_INT_MAX:
  				if( stat.isTimeBased ) {
  					idStr::hmsFormat_t format;
  					format.showZeroMinutes = true;
  
  					sdUIList::InsertItem( list, va( L"<material = '%hs'>\t<loc = '%hs'>\t%hs (%hs)", material, stat.text->GetName(), idStr::MS2HMS( item->newValue.GetInt() * 1000, format ), idStr::MS2HMS( item->oldValue.GetInt() * 1000, format ) ), -1, 0 );
  				} else {
  					sdUIList::InsertItem( list, va( L"<material = '%hs'>\t<loc = '%hs'>\t%i (%i)", material, stat.text->GetName(), item->newValue.GetInt(), item->oldValue.GetInt() ), -1, 0 );
  				}
  				break;
  			case sdNetStatKeyValue::SVT_FLOAT:
  			case sdNetStatKeyValue::SVT_FLOAT_MAX:
  				sdUIList::InsertItem( list, va( L"<material = '%hs'>\t<loc = '%hs'>\t%i (%i)", material, stat.text->GetName(), idMath::Ftoi( idMath::Ceil( item->newValue.GetFloat() ) ), idMath::Ftoi( idMath::Ceil( item->oldValue.GetFloat() ) ) ), -1, 0 );
  				break;
  		}
  	}
  }
  
  /*
  ============
  idGameLocal::CreateLifeStatsList
  ============
  */
  void idGameLocal::CreateLifeStatsList( sdUIList* list ) {
  	sdUIList::ClearItems( list );
  
  	idList< const sdStatsTracker::lifeStatsData_t* > improved;
  	idList< const sdStatsTracker::lifeStatsData_t* > unchanged;
  	idList< const sdStatsTracker::lifeStatsData_t* > worse;
  
  	sdGlobalStatsTracker::GetInstance().GetTopLifeStats( improved, unchanged, worse );
  
  
  	AddLifeStatsItems( list, "improved", improved );
  	AddLifeStatsItems( list, "unchanged", unchanged );
  	AddLifeStatsItems( list, "nodraw", worse );
  }
  
  
  class sdSortCategories {
  public:
  	sdSortCategories( const sdProficiencyTable& table ) : table( table ) {}
  	int operator()( const sdDeclPlayerClass::proficiencyCategory_t* lhs, const sdDeclPlayerClass::proficiencyCategory_t* rhs ) {
  		float lhsPercent = table.GetPercent( lhs->index );
  		float rhsPercent = table.GetPercent( rhs->index );
  
  		return ( idMath::Ftoi( rhsPercent * 100 ) ) - ( idMath::Ftoi( lhsPercent * 100 ) );
  	}
  
  	const sdProficiencyTable& table;
  };
  
  /*
  ============
  idGameLocal::CreatePredictedUpgradesList
  ============
  */
  void idGameLocal::CreatePredictedUpgradesList( sdUIList* list ) {
  	sdUIList::ClearItems( list );
  
  	idPlayer* localPlayer = gameLocal.GetLocalPlayer();
  	if( localPlayer == NULL ) {	
  		return;
  	}
  
  	const sdTeamInfo* ti = localPlayer->GetGameTeam();
  	idList< const sdDeclPlayerClass::proficiencyCategory_t* > bestCategories;
  
  	const sdProficiencyTable& table = localPlayer->GetProficiencyTable();
  
  	for( int i = 0; i < gameLocal.declPlayerClassType.Num(); i++ ) {
  		const sdDeclPlayerClass* pc = gameLocal.declPlayerClassType.LocalFindByIndex( i );
  		if( pc->GetTeam() != ti && ti != NULL ) {
  			continue;
  		}
  
  		for ( int j = 0; j < pc->GetNumProficiencies(); j++ ) {
  			const sdDeclPlayerClass::proficiencyCategory_t& category = pc->GetProficiency( j );
  
  			float percent = table.GetPercent( category.index );
  			if( percent >= 1.0f ) {
  				continue;
  			}
  
  			int k;
  			for( k = 0; k < bestCategories.Num(); k++ ) {
  				if( bestCategories[ k ]->index == category.index ) {
  					break;
  				}
  			}
  			if( k >= bestCategories.Num() ) {
  				bestCategories.Append( &category );
  			}
  		}
  	}
  
  	sdQuickSort( bestCategories.Begin(), bestCategories.End(), sdSortCategories( table ) );
  
  	int numAdded = 0;
  	for( int i = 0; i < bestCategories.Num() && numAdded < 2; i++ ) {
  		int currentLevel = table.GetLevel( bestCategories[ i ]->index );
  		if( currentLevel < bestCategories[ i ]->upgrades.Num() ) {
  			const sdDeclProficiencyType* type = gameLocal.declProficiencyTypeType.LocalFindByIndex( bestCategories[ i ]->index );
  
  			const sdDeclPlayerClass::proficiencyUpgrade_t& upgrade = bestCategories[ i ]->upgrades[ currentLevel ];
  			int index = sdUIList::InsertItem( list, va( L"<material = '%hs'>\t<loc = '%hs'>\t<flags customdraw>%hs", 
  				upgrade.materialInfo.c_str(), 
  				upgrade.title->GetName(),
  				type->GetName()	),
  				-1, 0 );
  
  			float percent = table.GetPercent( bestCategories[ i ]->index );
  
  			// push some extra data along for the GUI
  			list->SetItemDataInt( percent * 100 , index, 0, true );
  			list->SetItemDataInt( currentLevel, index, 1, true );
  			numAdded++;
  		}
  	}
  }
  
  
  /*
  ============
  idGameLocal::CreateUpgradesReviewList
  ============
  */
  void idGameLocal::CreateUpgradesReviewList( sdUIList* list ) {
  	sdUIList::ClearItems( list );
  
  	idPlayer* localPlayer = gameLocal.GetLocalPlayer();
  	if( localPlayer == NULL ) {	
  		return;
  	}
  
  	const sdTeamInfo* ti = localPlayer->GetGameTeam();
  	const sdProficiencyTable& table = localPlayer->GetProficiencyTable();
  
  	idList< const sdDeclPlayerClass::proficiencyCategory_t* > bestCategories;
  
  	for( int i = 0; i < gameLocal.declPlayerClassType.Num(); i++ ) {
  		const sdDeclPlayerClass* pc = gameLocal.declPlayerClassType.LocalFindByIndex( i );
  		if( pc->GetTeam() != ti && ti != NULL ) {
  			continue;
  		}
  
  		for ( int j = 0; j < pc->GetNumProficiencies(); j++ ) {
  			const sdDeclPlayerClass::proficiencyCategory_t& category = pc->GetProficiency( j );
  
  			int k;
  			for( k = 0; k < bestCategories.Num(); k++ ) {
  				if( bestCategories[ k ]->index == category.index ) {
  					break;
  				}
  			}
  			if( k < bestCategories.Num() ) {
  				continue;				
  			}
  
  			bestCategories.Append( &category );
  
  			int currentLevel = table.GetLevel(category.index );
  
  			for ( int level = 0; level < currentLevel && level < category.upgrades.Num(); level++ ) {
  				const sdDeclPlayerClass::proficiencyUpgrade_t& upgrade = category.upgrades[ level ];
  
  				int index = sdUIList::InsertItem( list, va( L"<material = '%hs'>\t<loc = '%hs'>", 
  					upgrade.materialInfo.c_str(), 
  					upgrade.title->GetName()),
  					-1, 0 );
  			}
  		}
  	}
  }
  
  /*
  ===============
  idGameLocal::UpdateHudStats
  ===============
  */
  void idGameLocal::UpdateHudStats( idPlayer* player ) {
  	localPlayerProperties.Update( player );
  	limboProperties.Update();
  	sdAdminSystem::GetInstance().UpdateProperties();
  }
  
  #undef ON_UIVALID
  #undef ON_UIINVALID
  
  