Пример #1
0
bool NumPlayersSelection(
	GameMode mode, GraphicsDevice *graphics, EventHandlers *handlers)
{
	MenuSystem ms;
	MenuSystemInit(
		&ms, handlers, graphics,
		Vec2iZero(),
		graphics->cachedConfig.Res);
	ms.allowAborts = true;
	ms.root = ms.current = MenuCreateNormal(
		"",
		"Select number of players",
		MENU_TYPE_NORMAL,
		0);
	for (int i = 0; i < MAX_LOCAL_PLAYERS; i++)
	{
		char buf[2];
		if (IsMultiplayer(mode) && i == 0)
		{
			// At least two players for dogfights
			continue;
		}
		sprintf(buf, "%d", i + 1);
		MenuAddSubmenu(ms.current, MenuCreateReturn(buf, i + 1));
	}
	MenuAddExitType(&ms, MENU_TYPE_RETURN);

	MenuLoop(&ms);
	const bool ok = !ms.hasAbort;
	if (ok)
	{
		const int numPlayers = ms.current->u.returnCode;
		for (int i = 0; i < (int)gPlayerDatas.size; i++)
		{
			const PlayerData *p = CArrayGet(&gPlayerDatas, i);
			CASSERT(!p->IsLocal, "unexpected local player");
		}
		if (NetClientIsConnected(&gNetClient))
		{
			// Tell the server that we want to add new players
			NetMsgNewPlayers np;
			np.ClientId = gNetClient.ClientId;
			np.NumPlayers = numPlayers;
			NetClientSendMsg(&gNetClient, MSG_NEW_PLAYERS, &np);
		}
		else
		{
			// We are the server, just add the players
			for (int i = 0; i < numPlayers; i++)
			{
				PlayerData *p = PlayerDataAdd(&gPlayerDatas);
				PlayerDataSetLocalDefaults(p, i);
				p->inputDevice = INPUT_DEVICE_UNSET;
			}
		}
	}
	MenuSystemTerminate(&ms);
	return ok;
}
Пример #2
0
int NumPlayersSelection(
	int *numPlayers, campaign_mode_e mode,
	GraphicsDevice *graphics, InputDevices *input)
{
	MenuSystem ms;
	int i;
	int res = 0;
	MenuSystemInit(
		&ms, input, graphics,
		Vec2iZero(),
		Vec2iNew(
			graphics->cachedConfig.ResolutionWidth,
			graphics->cachedConfig.ResolutionHeight));
	ms.root = ms.current = MenuCreateNormal(
		"",
		"Select number of players",
		MENU_TYPE_NORMAL,
		0);
	for (i = 0; i < MAX_PLAYERS; i++)
	{
		if (mode == CAMPAIGN_MODE_DOGFIGHT && i == 0)
		{
			// At least two players for dogfights
			continue;
		}
		char buf[2];
		sprintf(buf, "%d", i + 1);
		MenuAddSubmenu(ms.current, MenuCreateReturn(buf, i + 1));
	}
	MenuAddExitType(&ms, MENU_TYPE_RETURN);

	for (;;)
	{
		int cmd;
		InputPoll(&gInputDevices, SDL_GetTicks());
		if (KeyIsPressed(&gInputDevices.keyboard, SDLK_ESCAPE))
		{
			res = 0;
			break;	// hack to allow exit
		}
		cmd = GetMenuCmd(gPlayerDatas);
		MenuProcessCmd(&ms, cmd);
		if (MenuIsExit(&ms))
		{
			*numPlayers = ms.current->u.returnCode;
			res = 1;
			break;
		}

		GraphicsBlitBkg(graphics);
		MenuDisplay(&ms);
		BlitFlip(graphics, &gConfig.Graphics);
		SDL_Delay(10);
	}

	MenuSystemTerminate(&ms);
	return res;
}
Пример #3
0
bool NumPlayersSelection(GraphicsDevice *graphics, EventHandlers *handlers)
{
	MenuSystem ms;
	MenuSystemInit(
		&ms, handlers, graphics,
		Vec2iZero(),
		graphics->cachedConfig.Res);
	ms.allowAborts = true;
	ms.root = ms.current = MenuCreateNormal(
		"",
		"Select number of players",
		MENU_TYPE_NORMAL,
		0);
	MenuAddSubmenu(ms.current, MenuCreateReturn("(No local players)", 0));
	for (int i = 0; i < MAX_LOCAL_PLAYERS; i++)
	{
		char buf[2];
		sprintf(buf, "%d", i + 1);
		MenuAddSubmenu(ms.current, MenuCreateReturn(buf, i + 1));
	}
	MenuAddExitType(&ms, MENU_TYPE_RETURN);
	// Select 1 player default
	ms.current->u.normal.index = 1;

	MenuLoop(&ms);
	const bool ok = !ms.hasAbort;
	if (ok)
	{
		const int numPlayers = ms.current->u.returnCode;
		CA_FOREACH(const PlayerData, p, gPlayerDatas)
			CASSERT(!p->IsLocal, "unexpected local player");
		CA_FOREACH_END()
		// Add the players
		for (int i = 0; i < numPlayers; i++)
		{
			GameEvent e = GameEventNew(GAME_EVENT_PLAYER_DATA);
			e.u.PlayerData = PlayerDataDefault(i);
			e.u.PlayerData.UID = gNetClient.FirstPlayerUID + i;
			GameEventsEnqueue(&gGameEvents, e);
		}
		// Process the events to force add the players
		HandleGameEvents(&gGameEvents, NULL, NULL, NULL);
		// This also causes the client to send player data to the server
	}
	MenuSystemTerminate(&ms);
	return ok;
}
Пример #4
0
bool ScreenWait(const char *message, void (*checkFunc)(menu_t *, void *))
{
	MenuSystem ms;
	MenuSystemInit(
		&ms, &gEventHandlers, &gGraphicsDevice,
		Vec2iZero(),
		gGraphicsDevice.cachedConfig.Res);
	ms.allowAborts = true;
	ms.root = ms.current = MenuCreateNormal("", message, MENU_TYPE_NORMAL, 0);
	MenuAddExitType(&ms, MENU_TYPE_RETURN);
	MenuSetPostUpdateFunc(ms.root, checkFunc, &ms);

	MenuLoop(&ms);
	const bool ok = !ms.hasAbort;
	MenuSystemTerminate(&ms);
	return ok;
}
Пример #5
0
static GameLoopData *PlayerListLoop(PlayerList *pl)
{
	MenuSystemInit(&pl->ms, &gEventHandlers, &gGraphicsDevice, pl->pos, pl->size);
	menu_t *menuScores = MenuCreateCustom(
		"View Scores", PlayerListCustomDraw, PlayerListInput, pl);
	if (pl->hasMenu)
	{
		pl->ms.root = MenuCreateNormal("", "", MENU_TYPE_NORMAL, 0);
		MenuAddSubmenu(pl->ms.root, menuScores);
		MenuAddSubmenu(pl->ms.root, MenuCreateReturn("Finish", 0));
	}
	else
	{
		pl->ms.root = menuScores;
	}
	pl->ms.allowAborts = true;
	MenuAddExitType(&pl->ms, MENU_TYPE_RETURN);
	return GameLoopDataNew(
		pl, PlayerListTerminate, PlayerListOnEnter, PlayerListOnExit,
		NULL, pl->updateFunc, PlayerListDraw);
}
Пример #6
0
void PlayerSelectMenusCreate(
	PlayerSelectMenu *menu,
	int numPlayers, int player, Character *c, struct PlayerData *pData,
	EventHandlers *handlers, GraphicsDevice *graphics,
	InputConfig *inputConfig)
{
	MenuSystem *ms = &menu->ms;
	PlayerSelectMenuData *data = &menu->data;
	struct PlayerData *p = pData;
	Vec2i pos = Vec2iZero();
	Vec2i size = Vec2iZero();
	int w = graphics->cachedConfig.Res.x;
	int h = graphics->cachedConfig.Res.y;

	data->nameMenuSelection = (int)strlen(letters);
	data->display.c = c;
	data->display.currentMenu = &ms->current;
	data->display.pData = pData;
	data->controls.inputConfig = inputConfig;
	data->controls.pData = pData;

	switch (numPlayers)
	{
	case 1:
		// Single menu, entire screen
		pos = Vec2iNew(w / 2, 0);
		size = Vec2iNew(w / 2, h);
		break;
	case 2:
		// Two menus, side by side
		pos = Vec2iNew(player * w / 2 + w / 4, 0);
		size = Vec2iNew(w / 4, h);
		break;
	case 3:
	case 4:
		// Four corners
		pos = Vec2iNew((player & 1) * w / 2 + w / 4, (player / 2) * h / 2);
		size = Vec2iNew(w / 4, h / 2);
		break;
	default:
		assert(0 && "not implemented");
		break;
	}
	MenuSystemInit(ms, handlers, graphics, pos, size);
	ms->align = MENU_ALIGN_LEFT;
	ms->root = ms->current = MenuCreateNormal(
		"",
		"",
		MENU_TYPE_NORMAL,
		0);
	MenuAddSubmenu(
		ms->root,
		MenuCreateCustom(
		"Name", DrawNameMenu, HandleInputNameMenu, data));

	data->faceData.c = c;
	data->faceData.pData = p;
	data->faceData.menuCount = FACE_COUNT;
	data->faceData.strFunc = IndexToFaceStr;
	data->faceData.property = &p->looks.face;
	MenuAddSubmenu(ms->root, CreateAppearanceMenu("Face", &data->faceData));

	data->skinData.c = c;
	data->skinData.pData = p;
	data->skinData.menuCount = SHADE_COUNT;
	data->skinData.strFunc = IndexToShadeStr;
	data->skinData.property = &p->looks.skin;
	MenuAddSubmenu(ms->root, CreateAppearanceMenu("Skin", &data->skinData));

	data->hairData.c = c;
	data->hairData.pData = p;
	data->hairData.menuCount = SHADE_COUNT;
	data->hairData.strFunc = IndexToShadeStr;
	data->hairData.property = &p->looks.hair;
	MenuAddSubmenu(ms->root, CreateAppearanceMenu("Hair", &data->hairData));

	data->armsData.c = c;
	data->armsData.pData = p;
	data->armsData.menuCount = SHADE_COUNT;
	data->armsData.strFunc = IndexToShadeStr;
	data->armsData.property = &p->looks.arm;
	MenuAddSubmenu(ms->root, CreateAppearanceMenu("Arms", &data->armsData));

	data->bodyData.c = c;
	data->bodyData.pData = p;
	data->bodyData.menuCount = SHADE_COUNT;
	data->bodyData.strFunc = IndexToShadeStr;
	data->bodyData.property = &p->looks.body;
	MenuAddSubmenu(ms->root, CreateAppearanceMenu("Body", &data->bodyData));

	data->legsData.c = c;
	data->legsData.pData = p;
	data->legsData.menuCount = SHADE_COUNT;
	data->legsData.strFunc = IndexToShadeStr;
	data->legsData.property = &p->looks.leg;
	MenuAddSubmenu(ms->root, CreateAppearanceMenu("Legs", &data->legsData));

	MenuAddSubmenu(ms->root, CreateUseTemplateMenu("Load", data));
	MenuAddSubmenu(ms->root, CreateSaveTemplateMenu("Save", data));

	MenuAddSubmenu(ms->root, MenuCreateSeparator(""));
	MenuAddSubmenu(
		ms->root, MenuCreateNormal("Done", "", MENU_TYPE_NORMAL, 0));
	MenuAddExitType(ms, MENU_TYPE_RETURN);
	MenuSystemAddCustomDisplay(ms, MenuDisplayPlayer, data);
	MenuSystemAddCustomDisplay(ms, MenuDisplayPlayerControls, &data->controls);

	// Detect when there have been new player templates created,
	// to re-enable the load menu
	CheckReenableLoadMenu(ms->root, NULL);
	MenuSetPostEnterFunc(ms->root, CheckReenableLoadMenu, NULL);

	SetPlayer(c, pData);
}
Пример #7
0
bool ScreenMissionSummary(
	CampaignOptions *c, struct MissionOptions *m, const bool completed)
{
	if (completed)
	{
		// Save password
		MissionSave ms;
		MissionSaveInit(&ms);
		ms.Campaign = c->Entry;
		// Don't make password for next level if there is none
		int passwordIndex = m->index + 1;
		if (passwordIndex == c->Entry.NumMissions)
		{
			passwordIndex--;
		}
		strcpy(ms.Password, MakePassword(passwordIndex, 0));
		ms.MissionsCompleted = m->index + 1;
		AutosaveAddMission(&gAutosave, &ms);
		AutosaveSave(&gAutosave, GetConfigFilePath(AUTOSAVE_FILE));
	}

	// Calculate bonus scores
	// Bonuses only apply if at least one player has lived
	if (AreAnySurvived())
	{
		int bonus = 0;
		// Objective bonuses
		CA_FOREACH(const Objective, o, m->missionData->Objectives)
			if (ObjectiveIsPerfect(o))
			{
				bonus += PERFECT_BONUS;
			}
		CA_FOREACH_END()
		bonus += GetAccessBonus(m);
		bonus += GetTimeBonus(m, NULL);

		CA_FOREACH(PlayerData, p, gPlayerDatas)
			ApplyBonuses(p, bonus);
		CA_FOREACH_END()
	}
	MenuSystem ms;
	const int h = FontH() * 10;
	MenuSystemInit(
		&ms, &gEventHandlers, &gGraphicsDevice,
		Vec2iNew(0, gGraphicsDevice.cachedConfig.Res.y - h),
		Vec2iNew(gGraphicsDevice.cachedConfig.Res.x, h));
	ms.current = ms.root = MenuCreateNormal("", "", MENU_TYPE_NORMAL, 0);
	// Use return code 0 for whether to continue the game
	if (completed)
	{
		MenuAddSubmenu(ms.root, MenuCreateReturn("Continue", 0));
	}
	else
	{
		MenuAddSubmenu(ms.root, MenuCreateReturn("Replay mission", 0));
		MenuAddSubmenu(ms.root, MenuCreateReturn("Back to menu", 1));
	}
	ms.allowAborts = true;
	MenuAddExitType(&ms, MENU_TYPE_RETURN);
	MenuSystemAddCustomDisplay(&ms, MissionSummaryDraw, m);
	MenuLoop(&ms);
	return ms.current->u.returnCode == 0;
}
Пример #8
0
bool GameOptions(const GameMode gm)
{
	// Create selection menus
	const int w = gGraphicsDevice.cachedConfig.Res.x;
	const int h = gGraphicsDevice.cachedConfig.Res.y;
	MenuSystem ms;
	MenuSystemInit(
		&ms, &gEventHandlers, &gGraphicsDevice,
		Vec2iZero(), Vec2iNew(w, h));
	ms.align = MENU_ALIGN_CENTER;
	ms.allowAborts = true;
	AllowedWeaponsData awData;
	AllowedWeaponsDataInit(&awData, &gMission.Weapons);
	switch (gm)
	{
	case GAME_MODE_DEATHMATCH:
		ms.root = ms.current = MenuCreateNormal(
			"",
			"",
			MENU_TYPE_OPTIONS,
			0);
		MenuAddConfigOptionsItem(
			ms.current, ConfigGet(&gConfig, "Game.PlayerHP"));
		MenuAddConfigOptionsItem(
			ms.current, ConfigGet(&gConfig, "Deathmatch.Lives"));
		MenuAddConfigOptionsItem(
			ms.current, ConfigGet(&gConfig, "Game.HealthPickups"));
		MenuAddConfigOptionsItem(
			ms.current, ConfigGet(&gConfig, "Game.Ammo"));
		MenuAddSubmenu(ms.current,
			MenuCreateAllowedWeapons("Weapons...", &awData));
		MenuAddSubmenu(ms.current, MenuCreateSeparator(""));
		MenuAddSubmenu(ms.current, MenuCreateReturn("Done", 0));
		break;
	case GAME_MODE_DOGFIGHT:
		ms.root = ms.current = MenuCreateNormal(
			"",
			"",
			MENU_TYPE_OPTIONS,
			0);
		MenuAddConfigOptionsItem(
			ms.current, ConfigGet(&gConfig, "Dogfight.PlayerHP"));
		MenuAddConfigOptionsItem(
			ms.current, ConfigGet(&gConfig, "Dogfight.FirstTo"));
		MenuAddSubmenu(ms.current,
			MenuCreateAllowedWeapons("Weapons...", &awData));
		MenuAddSubmenu(ms.current, MenuCreateSeparator(""));
		MenuAddSubmenu(ms.current, MenuCreateReturn("Done", 0));
		break;
	default:
		CASSERT(false, "unknown game mode");
		break;
	}
	MenuAddExitType(&ms, MENU_TYPE_RETURN);

	MenuLoop(&ms);

	const bool ok = !ms.hasAbort;
	if (ok)
	{
		ConfigApply(&gConfig);

		// Save options for later
		ConfigSave(&gConfig, GetConfigFilePath(CONFIG_FILE));

		// Set allowed weapons
		// First check if the player has unwittingly disabled all weapons
		// if so, enable all weapons
		bool allDisabled = true;
		for (int i = 0, j = 0; i < (int)awData.allowed.size; i++, j++)
		{
			const bool *allowed = CArrayGet(&awData.allowed, i);
			if (*allowed)
			{
				allDisabled = false;
				break;
			}
		}
		if (!allDisabled)
		{
			for (int i = 0, j = 0; i < (int)awData.allowed.size; i++, j++)
			{
				const bool *allowed = CArrayGet(&awData.allowed, i);
				if (!*allowed)
				{
					CArrayDelete(&gMission.Weapons, j);
					j--;
				}
			}
		}
	}
	AllowedWeaponsDataTerminate(&awData);
	MenuSystemTerminate(&ms);
	return ok;
}
Пример #9
0
void WeaponMenuCreate(
	WeaponMenu *menu,
	int numPlayers, int player, const int playerUID,
	EventHandlers *handlers, GraphicsDevice *graphics)
{
	MenuSystem *ms = &menu->ms;
	WeaponMenuData *data = &menu->data;
	Vec2i pos, size;
	int w = graphics->cachedConfig.Res.x;
	int h = graphics->cachedConfig.Res.y;

	data->display.PlayerUID = playerUID;
	data->display.currentMenu = &ms->current;
	data->display.Dir = DIRECTION_DOWN;
	data->PlayerUID = playerUID;

	switch (numPlayers)
	{
	case 1:
		// Single menu, entire screen
		pos = Vec2iNew(w / 2, 0);
		size = Vec2iNew(w / 2, h);
		break;
	case 2:
		// Two menus, side by side
		pos = Vec2iNew(player * w / 2 + w / 4, 0);
		size = Vec2iNew(w / 4, h);
		break;
	case 3:
	case 4:
		// Four corners
		pos = Vec2iNew((player & 1) * w / 2 + w / 4, (player / 2) * h / 2);
		size = Vec2iNew(w / 4, h / 2);
		break;
	default:
		CASSERT(false, "not implemented");
		pos = Vec2iNew(w / 2, 0);
		size = Vec2iNew(w / 2, h);
		break;
	}
	MenuSystemInit(ms, handlers, graphics, pos, size);
	ms->align = MENU_ALIGN_LEFT;
	ms->root = ms->current = MenuCreateNormal(
		"",
		"",
		MENU_TYPE_NORMAL,
		0);
	ms->root->u.normal.maxItems = 11;
	const CArray *weapons = &gMission.Weapons;
	for (int i = 0; i < (int)weapons->size; i++)
	{
		const GunDescription **g = CArrayGet(weapons, i);
		menu_t *gunMenu;
		if ((*g)->Description != NULL)
		{
			// Gun description menu
			gunMenu = MenuCreateNormal((*g)->name, "", MENU_TYPE_NORMAL, 0);
			char *buf;
			CMALLOC(buf, strlen((*g)->Description) * 2);
			FontSplitLines((*g)->Description, buf, size.x * 5 / 6);
			MenuAddSubmenu(gunMenu, MenuCreateBack(buf));
			CFREE(buf);
			gunMenu->u.normal.isSubmenusAlt = true;
			MenuSetCustomDisplay(gunMenu, DisplayDescriptionGunIcon, *g);
		}
		else
		{
			gunMenu = MenuCreate((*g)->name, MENU_TYPE_BASIC);
		}
		MenuAddSubmenu(ms->root, gunMenu);
	}
	MenuSetPostInputFunc(ms->root, WeaponSelect, &data->display);
	// Disable menu items where the player already has the weapon
	PlayerData *pData = PlayerDataGetByUID(playerUID);
	for (int i = 0; i < pData->weaponCount; i++)
	{
		for (int j = 0; j < (int)weapons->size; j++)
		{
			const GunDescription **g = CArrayGet(weapons, j);
			if (pData->weapons[i] == *g)
			{
				MenuDisableSubmenu(ms->root, j);
			}
		}
	}
	MenuAddSubmenu(ms->root, MenuCreateSeparator(""));
	MenuAddSubmenu(
		ms->root, MenuCreateNormal("(End)", "", MENU_TYPE_NORMAL, 0));
	// Select "(End)"
	ms->root->u.normal.index = (int)ms->root->u.normal.subMenus.size - 1;

	// Disable "Done" if no weapons selected
	if (pData->weaponCount == 0)
	{
		MenuDisableSubmenu(ms->root, (int)ms->root->u.normal.subMenus.size - 1);
	}

	MenuSetCustomDisplay(ms->root, DisplayGunIcon, NULL);
	MenuSystemAddCustomDisplay(ms, MenuDisplayPlayer, &data->display);
	MenuSystemAddCustomDisplay(ms, DisplayEquippedWeapons, data);
	MenuSystemAddCustomDisplay(
		ms, MenuDisplayPlayerControls, &data->PlayerUID);
}
Пример #10
0
void PlayerSelectMenusCreate(
	PlayerSelectMenu *menu,
	int numPlayers, int player, Character *c, struct PlayerData *pData,
	EventHandlers *handlers, GraphicsDevice *graphics,
	InputConfig *inputConfig, const NameGen *ng)
{
	MenuSystem *ms = &menu->ms;
	PlayerSelectMenuData *data = &menu->data;
	struct PlayerData *p = pData;
	Vec2i pos = Vec2iZero();
	Vec2i size = Vec2iZero();
	int w = graphics->cachedConfig.Res.x;
	int h = graphics->cachedConfig.Res.y;

	data->nameMenuSelection = (int)strlen(letters);
	data->display.c = c;
	data->display.currentMenu = &ms->current;
	data->display.pData = pData;
	data->controls.inputConfig = inputConfig;
	data->controls.pData = pData;
	data->nameGenerator = ng;

	switch (numPlayers)
	{
	case 1:
		// Single menu, entire screen
		pos = Vec2iNew(w / 2, 0);
		size = Vec2iNew(w / 2, h);
		break;
	case 2:
		// Two menus, side by side
		pos = Vec2iNew(player * w / 2 + w / 4, 0);
		size = Vec2iNew(w / 4, h);
		break;
	case 3:
	case 4:
		// Four corners
		pos = Vec2iNew((player & 1) * w / 2 + w / 4, (player / 2) * h / 2);
		size = Vec2iNew(w / 4, h / 2);
		break;
	default:
		assert(0 && "not implemented");
		break;
	}
	MenuSystemInit(ms, handlers, graphics, pos, size);
	ms->align = MENU_ALIGN_LEFT;
	ms->root = ms->current = MenuCreateNormal(
		"",
		"",
		MENU_TYPE_NORMAL,
		0);
	MenuAddSubmenu(
		ms->root,
		MenuCreateCustom(
		"Name", DrawNameMenu, HandleInputNameMenu, data));

	MenuAddSubmenu(ms->root, CreateCustomizeMenu("Customize...", data, c, p));
	MenuAddSubmenu(
		ms->root,
		MenuCreateVoidFunc("Shuffle", ShuffleAppearance, data));

	MenuAddSubmenu(ms->root, CreateUseTemplateMenu("Load", data));
	MenuAddSubmenu(ms->root, CreateSaveTemplateMenu("Save", data));

	MenuAddSubmenu(ms->root, MenuCreateSeparator(""));
	MenuAddSubmenu(
		ms->root, MenuCreateNormal("Done", "", MENU_TYPE_NORMAL, 0));
	MenuAddExitType(ms, MENU_TYPE_RETURN);
	MenuSystemAddCustomDisplay(ms, MenuDisplayPlayer, data);
	MenuSystemAddCustomDisplay(ms, MenuDisplayPlayerControls, &data->controls);

	// Detect when there have been new player templates created,
	// to re-enable the load menu
	CheckReenableLoadMenu(ms->root, NULL);
	MenuSetPostEnterFunc(ms->root, CheckReenableLoadMenu, NULL);

	SetPlayer(c, pData);
}
Пример #11
0
void PlayerSelectMenusCreate(
	PlayerSelectMenu *menu,
	int numPlayers, int player, const int playerUID,
	EventHandlers *handlers, GraphicsDevice *graphics,
	const NameGen *ng)
{
	MenuSystem *ms = &menu->ms;
	PlayerSelectMenuData *data = &menu->data;
	Vec2i pos, size;
	int w = graphics->cachedConfig.Res.x;
	int h = graphics->cachedConfig.Res.y;

	data->nameMenuSelection = (int)strlen(letters);
	data->display.PlayerUID = playerUID;
	data->display.currentMenu = &ms->current;
	data->PlayerUID = playerUID;
	data->nameGenerator = ng;

	switch (numPlayers)
	{
	case 1:
		// Single menu, entire screen
		pos = Vec2iNew(w / 2, 0);
		size = Vec2iNew(w / 2, h);
		break;
	case 2:
		// Two menus, side by side
		pos = Vec2iNew(player * w / 2 + w / 4, 0);
		size = Vec2iNew(w / 4, h);
		break;
	case 3:
	case 4:
		// Four corners
		pos = Vec2iNew((player & 1) * w / 2 + w / 4, (player / 2) * h / 2);
		size = Vec2iNew(w / 4, h / 2);
		break;
	default:
		CASSERT(false, "not implemented");
		pos = Vec2iNew(w / 2, 0);
		size = Vec2iNew(w / 2, h);
		break;
	}
	MenuSystemInit(ms, handlers, graphics, pos, size);
	ms->align = MENU_ALIGN_LEFT;
	ms->root = ms->current = MenuCreateNormal(
		"",
		"",
		MENU_TYPE_NORMAL,
		0);
	MenuAddSubmenu(
		ms->root,
		MenuCreateCustom(
		"Name", DrawNameMenu, HandleInputNameMenu, data));

	MenuAddSubmenu(
		ms->root, CreateCustomizeMenu("Customize...", data, playerUID));
	MenuAddSubmenu(
		ms->root,
		MenuCreateVoidFunc("Shuffle", ShuffleAppearance, data));

	MenuAddSubmenu(ms->root, CreateUseTemplateMenu("Load", data));
	MenuAddSubmenu(ms->root, CreateSaveTemplateMenu("Save", data));

	MenuAddSubmenu(ms->root, MenuCreateSeparator(""));
	MenuAddSubmenu(
		ms->root, MenuCreateNormal("Done", "", MENU_TYPE_NORMAL, 0));
	// Select "Done"
	ms->root->u.normal.index = (int)ms->root->u.normal.subMenus.size - 1;
	MenuAddExitType(ms, MENU_TYPE_RETURN);
	MenuSystemAddCustomDisplay(ms, MenuDisplayPlayer, data);
	MenuSystemAddCustomDisplay(
		ms, MenuDisplayPlayerControls, &data->PlayerUID);

	// Detect when there have been new player templates created,
	// to re-enable the load menu
	CheckReenableLoadMenu(ms->root, NULL);
	MenuSetPostEnterFunc(ms->root, CheckReenableLoadMenu, NULL, false);

	PlayerData *p = PlayerDataGetByUID(playerUID);
	CharacterSetColors(&p->Char);
}