1 /*
2 	C-Dogs SDL
3 	A port of the legendary (and fun) action/arcade cdogs.
4 
5 	Copyright (c) 2013-2016, 2018-2020 Cong Xu
6 	All rights reserved.
7 
8 	Redistribution and use in source and binary forms, with or without
9 	modification, are permitted provided that the following conditions are met:
10 
11 	Redistributions of source code must retain the above copyright notice, this
12 	list of conditions and the following disclaimer.
13 	Redistributions in binary form must reproduce the above copyright notice,
14 	this list of conditions and the following disclaimer in the documentation
15 	and/or other materials provided with the distribution.
16 
17 	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 	AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 	IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 	ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21 	LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 	CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 	POSSIBILITY OF SUCH DAMAGE.
28 */
29 #include "player_select_menus.h"
30 
31 #include <assert.h>
32 #include <stddef.h>
33 
34 #include <cdogs/character_class.h>
35 #include <cdogs/draw/draw.h>
36 #include <cdogs/draw/drawtools.h>
37 #include <cdogs/font.h>
38 #include <cdogs/player_template.h>
39 
40 static char letters[] = "1234567890-QWERTYUIOP!ASDFGHJKL:'ZXCVBNM, .?";
41 static char smallLetters[] = "1234567890-qwertyuiop!asdfghjkl:'zxcvbnm, .?";
42 
DrawNameMenu(const menu_t * menu,GraphicsDevice * g,const struct vec2i pos,const struct vec2i size,const void * data)43 static void DrawNameMenu(
44 	const menu_t *menu, GraphicsDevice *g, const struct vec2i pos,
45 	const struct vec2i size, const void *data)
46 {
47 	const PlayerSelectMenuData *d = data;
48 
49 #define ENTRY_COLS 11
50 #define ENTRY_SPACING 7
51 
52 	int x = pos.x;
53 	const int dy = FontH();
54 	int y = CENTER_Y(
55 		pos, size, dy * (((int)strlen(letters) - 1) / ENTRY_COLS));
56 
57 	UNUSED(menu);
58 
59 	int i;
60 	for (i = 0; i < (int)strlen(letters); i++)
61 	{
62 		struct vec2i menuPos = svec2i(
63 			x + (i % ENTRY_COLS) * ENTRY_SPACING,
64 			y + (i / ENTRY_COLS) * dy);
65 		char buf[2];
66 		sprintf(buf, "%c", letters[i]);
67 		DisplayMenuItem(
68 			g,
69 			Rect2iNew(menuPos, svec2i(ENTRY_SPACING, dy)),
70 			buf, i == d->nameMenuSelection, false, colorWhite);
71 	}
72 
73 	const char *label = "(End)";
74 	DisplayMenuItem(
75 		g,
76 		Rect2iNew(svec2i(
77 			x + (i % ENTRY_COLS) * ENTRY_SPACING,
78 			y + (i / ENTRY_COLS) * FontH()),
79 			FontStrSize(label)),
80 		label, i == d->nameMenuSelection, false, colorWhite);
81 }
82 
HandleInputNameMenu(int cmd,void * data)83 static int HandleInputNameMenu(int cmd, void *data)
84 {
85 	PlayerSelectMenuData *d = data;
86 	PlayerData *p = PlayerDataGetByUID(d->display.PlayerUID);
87 
88 	if (cmd & CMD_BUTTON1)
89 	{
90 		if (d->nameMenuSelection == (int)strlen(letters))
91 		{
92 			MenuPlaySound(MENU_SOUND_ENTER);
93 			return 1;
94 		}
95 
96 		if (strlen(p->name) < sizeof p->name - 1)
97 		{
98 			size_t l = strlen(p->name);
99 			p->name[l + 1] = 0;
100 			if (l > 0 && p->name[l - 1] != ' ')
101 			{
102 				p->name[l] = smallLetters[d->nameMenuSelection];
103 			}
104 			else
105 			{
106 				p->name[l] = letters[d->nameMenuSelection];
107 			}
108 			MenuPlaySound(MENU_SOUND_ENTER);
109 		}
110 		else
111 		{
112 			MenuPlaySound(MENU_SOUND_ERROR);
113 		}
114 	}
115 	else if (cmd & CMD_BUTTON2)
116 	{
117 		if (p->name[0])
118 		{
119 			p->name[strlen(p->name) - 1] = 0;
120 			MenuPlaySound(MENU_SOUND_BACK);
121 		}
122 		else
123 		{
124 			MenuPlaySound(MENU_SOUND_ERROR);
125 		}
126 	}
127 	else if (cmd & CMD_LEFT)
128 	{
129 		if (d->nameMenuSelection > 0)
130 		{
131 			d->nameMenuSelection--;
132 			MenuPlaySound(MENU_SOUND_SWITCH);
133 		}
134 	}
135 	else if (cmd & CMD_RIGHT)
136 	{
137 		if (d->nameMenuSelection < (int)strlen(letters))
138 		{
139 			d->nameMenuSelection++;
140 			MenuPlaySound(MENU_SOUND_SWITCH);
141 		}
142 	}
143 	else if (cmd & CMD_UP)
144 	{
145 		if (d->nameMenuSelection >= ENTRY_COLS)
146 		{
147 			d->nameMenuSelection -= ENTRY_COLS;
148 			MenuPlaySound(MENU_SOUND_SWITCH);
149 		}
150 	}
151 	else if (cmd & CMD_DOWN)
152 	{
153 		if (d->nameMenuSelection <= (int)strlen(letters) - ENTRY_COLS)
154 		{
155 			d->nameMenuSelection += ENTRY_COLS;
156 			MenuPlaySound(MENU_SOUND_SWITCH);
157 		}
158 		else if (d->nameMenuSelection < (int)strlen(letters))
159 		{
160 			d->nameMenuSelection = (int)strlen(letters);
161 			MenuPlaySound(MENU_SOUND_SWITCH);
162 		}
163 	}
164 
165 	return 0;
166 }
167 
168 static void PostInputRotatePlayer(menu_t *menu, int cmd, void *data);
169 static void CheckReenableHairHatMenu(menu_t *menu, void *data);
170 
171 static void PostInputFaceMenu(menu_t *menu, int cmd, void *data);
CreateFaceMenu(MenuDisplayPlayerData * data)172 static menu_t *CreateFaceMenu(MenuDisplayPlayerData *data)
173 {
174 	menu_t *menu = MenuCreateNormal("Face", "", MENU_TYPE_NORMAL, 0);
175 	menu->u.normal.maxItems = 11;
176 	CA_FOREACH(const CharacterClass, c, gCharacterClasses.Classes)
177 	MenuAddSubmenu(menu, MenuCreateBack(c->Name));
178 	CA_FOREACH_END()
179 	CA_FOREACH(const CharacterClass, c, gCharacterClasses.CustomClasses)
180 	MenuAddSubmenu(menu, MenuCreateBack(c->Name));
181 	CA_FOREACH_END()
182 	MenuSetPostInputFunc(menu, PostInputFaceMenu, data);
183 	return menu;
184 }
PostInputFaceMenu(menu_t * menu,int cmd,void * data)185 static void PostInputFaceMenu(menu_t *menu, int cmd, void *data)
186 {
187 	const MenuDisplayPlayerData *d = data;
188 	// Change player face based on current menu selection
189 	PlayerData *p = PlayerDataGetByUID(d->PlayerUID);
190 	Character *c = &p->Char;
191 	if (menu->u.normal.index < (int)gCharacterClasses.Classes.size)
192 	{
193 		c->Class = CArrayGet(&gCharacterClasses.Classes, menu->u.normal.index);
194 	}
195 	else
196 	{
197 		c->Class = CArrayGet(
198 			&gCharacterClasses.CustomClasses,
199 			menu->u.normal.index - (int)gCharacterClasses.Classes.size);
200 	}
201 	PostInputRotatePlayer(menu, cmd, data);
202 }
203 
204 static void PostInputHairMenu(menu_t *menu, int cmd, void *data);
CreateHairMenu(MenuDisplayPlayerData * data)205 static menu_t *CreateHairMenu(MenuDisplayPlayerData *data)
206 {
207 	menu_t *menu = MenuCreateNormal("Hair/hat", "", MENU_TYPE_NORMAL, 0);
208 	menu->u.normal.maxItems = 11;
209 	MenuAddSubmenu(menu, MenuCreateBack("(None)"));
210 	CA_FOREACH(const char *, h, gPicManager.hairstyleNames)
211 	MenuAddSubmenu(menu, MenuCreateBack(*h));
212 	CA_FOREACH_END()
213 	MenuSetPostInputFunc(menu, PostInputHairMenu, data);
214 	return menu;
215 }
PostInputHairMenu(menu_t * menu,int cmd,void * data)216 static void PostInputHairMenu(menu_t *menu, int cmd, void *data)
217 {
218 	const MenuDisplayPlayerData *d = data;
219 	// Change player hairstyle based on current menu selection
220 	PlayerData *p = PlayerDataGetByUID(d->PlayerUID);
221 	Character *c = &p->Char;
222 	CFREE(c->Hair);
223 	c->Hair = NULL;
224 	if (menu->u.normal.index > 0)
225 	{
226 		const char **hair =
227 			CArrayGet(&gPicManager.hairstyleNames, menu->u.normal.index - 1);
228 		CSTRDUP(c->Hair, *hair);
229 	}
230 	PostInputRotatePlayer(menu, cmd, data);
231 }
232 
233 static void DrawColorMenu(
234 	const menu_t *menu, GraphicsDevice *g, const struct vec2i pos,
235 	const struct vec2i size, const void *data);
236 static int HandleInputColorMenu(int cmd, void *data);
CreateColorMenu(const char * name,ColorMenuData * data,const CharColorType type,const int playerUID)237 static menu_t *CreateColorMenu(
238 	const char *name, ColorMenuData *data, const CharColorType type,
239 	const int playerUID)
240 {
241 	data->Type = type;
242 	data->PlayerUID = playerUID;
243 	data->palette = PicManagerGetPic(&gPicManager, "palette");
244 	// Find the closest palette colour available, and select it
245 	// Use least squares method
246 	int errorLowest = -1;
247 	PlayerData *p = PlayerDataGetByUID(playerUID);
248 	const color_t currentColour = *CharColorGetByType(&p->Char.Colors, type);
249 	struct vec2i v;
250 	for (v.y = 0; v.y < data->palette->size.y; v.y++)
251 	{
252 		for (v.x = 0; v.x < data->palette->size.x; v.x++)
253 		{
254 			const color_t colour = PIXEL2COLOR(
255 				data->palette->Data[v.x + v.y * data->palette->size.x]);
256 			if (colour.a == 0)
257 			{
258 				continue;
259 			}
260 			const int er = (int)colour.r - currentColour.r;
261 			const int eg = (int)colour.g - currentColour.g;
262 			const int eb = (int)colour.b - currentColour.b;
263 			const int error = er * er + eg * eg + eb * eb;
264 			if (errorLowest == -1 || error < errorLowest)
265 			{
266 				errorLowest = error;
267 				data->selectedColor = v;
268 			}
269 		}
270 	}
271 	return MenuCreateCustom(name, DrawColorMenu, HandleInputColorMenu, data);
272 }
DrawColorMenu(const menu_t * menu,GraphicsDevice * g,const struct vec2i pos,const struct vec2i size,const void * data)273 static void DrawColorMenu(
274 	const menu_t *menu, GraphicsDevice *g, const struct vec2i pos,
275 	const struct vec2i size, const void *data)
276 {
277 	UNUSED(menu);
278 	const ColorMenuData *d = data;
279 	// Draw colour squares from the palette
280 	const struct vec2i swatchSize = svec2i(4, 4);
281 	const struct vec2i drawPos =
282 		svec2i(pos.x, CENTER_Y(pos, size, d->palette->size.y * swatchSize.y));
283 	struct vec2i v;
284 	for (v.y = 0; v.y < d->palette->size.y; v.y++)
285 	{
286 		for (v.x = 0; v.x < d->palette->size.x; v.x++)
287 		{
288 			const color_t colour =
289 				PIXEL2COLOR(d->palette->Data[v.x + v.y * d->palette->size.x]);
290 			if (colour.a == 0)
291 			{
292 				continue;
293 			}
294 			DrawRectangle(
295 				g, svec2i_add(drawPos, svec2i_multiply(v, swatchSize)),
296 				swatchSize, colour, true);
297 		}
298 	}
299 	// Draw a highlight around the selected colour
300 	DrawRectangle(
301 		g,
302 		svec2i_add(
303 			drawPos,
304 			svec2i_subtract(
305 				svec2i_multiply(d->selectedColor, swatchSize), svec2i(1, 1))),
306 		svec2i_add(swatchSize, svec2i(2, 2)), colorWhite, false);
307 }
HandleInputColorMenu(int cmd,void * data)308 static int HandleInputColorMenu(int cmd, void *data)
309 {
310 	ColorMenuData *d = data;
311 	struct vec2i selected = d->selectedColor;
312 	switch (cmd)
313 	{
314 	case CMD_LEFT:
315 		selected.x--;
316 		break;
317 	case CMD_RIGHT:
318 		selected.x++;
319 		break;
320 	case CMD_UP:
321 		selected.y--;
322 		break;
323 	case CMD_DOWN:
324 		selected.y++;
325 		break;
326 	case CMD_BUTTON1:
327 	case CMD_BUTTON2: // fallthrough
328 		MenuPlaySound(MENU_SOUND_ENTER);
329 		return cmd;
330 	default:
331 		break;
332 	}
333 	if (selected.x >= 0 && selected.x < d->palette->size.x &&
334 		selected.y >= 0 && selected.y < d->palette->size.y)
335 	{
336 		const color_t colour = PIXEL2COLOR(
337 			d->palette->Data[selected.x + selected.y * d->palette->size.x]);
338 		if (colour.a != 0)
339 		{
340 			d->selectedColor = selected;
341 			PlayerData *p = PlayerDataGetByUID(d->PlayerUID);
342 			*CharColorGetByType(&p->Char.Colors, d->Type) = colour;
343 			MenuPlaySound(MENU_SOUND_SWITCH);
344 		}
345 	}
346 	return 0;
347 }
348 
PostInputLoadTemplate(menu_t * menu,int cmd,void * data)349 static void PostInputLoadTemplate(menu_t *menu, int cmd, void *data)
350 {
351 	if (cmd & CMD_BUTTON1)
352 	{
353 		PlayerSelectMenuData *d = data;
354 		PlayerData *p = PlayerDataGetByUID(d->display.PlayerUID);
355 		const PlayerTemplate *t =
356 			PlayerTemplateGetById(&gPlayerTemplates, menu->u.normal.index);
357 		memset(p->name, 0, sizeof p->name);
358 		strcpy(p->name, t->name);
359 		p->Char.Class = StrCharacterClass(t->CharClassName);
360 		if (p->Char.Class == NULL)
361 		{
362 			p->Char.Class = StrCharacterClass("Jones");
363 		}
364 		CFREE(p->Char.Hair);
365 		p->Char.Hair = NULL;
366 		if (t->Hair)
367 		{
368 			CSTRDUP(p->Char.Hair, t->Hair);
369 		}
370 		p->Char.Colors = t->Colors;
371 	}
372 }
373 
374 // Load all the template names to the menu entries
PostEnterLoadTemplateNames(menu_t * menu,void * data)375 static void PostEnterLoadTemplateNames(menu_t *menu, void *data)
376 {
377 	bool *isSave = (bool *)data;
378 	MenuClearSubmenus(menu);
379 	for (int i = 0;; i++)
380 	{
381 		const PlayerTemplate *pt = PlayerTemplateGetById(&gPlayerTemplates, i);
382 		if (pt == NULL)
383 		{
384 			break;
385 		}
386 		MenuAddSubmenu(menu, MenuCreateBack(pt->name));
387 	}
388 	if (*isSave)
389 	{
390 		MenuAddSubmenu(menu, MenuCreateBack("(new)"));
391 	}
392 }
393 
CreateUseTemplateMenu(const char * name,PlayerSelectMenuData * data)394 static menu_t *CreateUseTemplateMenu(
395 	const char *name, PlayerSelectMenuData *data)
396 {
397 	menu_t *menu = MenuCreateNormal(name, "", MENU_TYPE_NORMAL, 0);
398 	menu->u.normal.maxItems = 11;
399 	MenuSetPostEnterFunc(menu, PostEnterLoadTemplateNames, &gFalse, false);
400 	MenuSetPostInputFunc(menu, PostInputLoadTemplate, data);
401 	return menu;
402 }
403 
PostInputSaveTemplate(menu_t * menu,int cmd,void * data)404 static void PostInputSaveTemplate(menu_t *menu, int cmd, void *data)
405 {
406 	if (!(cmd & CMD_BUTTON1))
407 	{
408 		return;
409 	}
410 	PlayerSelectMenuData *d = data;
411 	PlayerData *p = PlayerDataGetByUID(d->display.PlayerUID);
412 	PlayerTemplate *t =
413 		PlayerTemplateGetById(&gPlayerTemplates, menu->u.normal.index);
414 	if (t == NULL)
415 	{
416 		PlayerTemplate nt;
417 		memset(&nt, 0, sizeof nt);
418 		CArrayPushBack(&gPlayerTemplates.Classes, &nt);
419 		t = CArrayGet(
420 			&gPlayerTemplates.Classes, gPlayerTemplates.Classes.size - 1);
421 	}
422 	memset(t->name, 0, sizeof t->name);
423 	strcpy(t->name, p->name);
424 	CFREE(t->CharClassName);
425 	CSTRDUP(t->CharClassName, p->Char.Class->Name);
426 	if (p->Char.Hair)
427 	{
428 		CFREE(t->Hair);
429 		CSTRDUP(t->Hair, p->Char.Hair);
430 	}
431 	t->Colors = p->Char.Colors;
432 	PlayerTemplatesSave(&gPlayerTemplates);
433 }
434 
SaveTemplateDisplayTitle(const menu_t * menu,GraphicsDevice * g,const struct vec2i pos,const struct vec2i size,const void * data)435 static void SaveTemplateDisplayTitle(
436 	const menu_t *menu, GraphicsDevice *g, const struct vec2i pos,
437 	const struct vec2i size, const void *data)
438 {
439 	UNUSED(g);
440 	const PlayerSelectMenuData *d = data;
441 	char buf[256];
442 
443 	UNUSED(menu);
444 	UNUSED(size);
445 
446 	// Display "Save <template>..." title
447 	const PlayerData *p = PlayerDataGetByUID(d->display.PlayerUID);
448 	sprintf(buf, "Save %s...", p->name);
449 	FontStr(buf, svec2i_add(pos, svec2i(0, 0)));
450 }
451 
CreateSaveTemplateMenu(const char * name,PlayerSelectMenuData * data)452 static menu_t *CreateSaveTemplateMenu(
453 	const char *name, PlayerSelectMenuData *data)
454 {
455 	menu_t *menu = MenuCreateNormal(name, "", MENU_TYPE_NORMAL, 0);
456 	menu->u.normal.maxItems = 11;
457 	MenuSetPostEnterFunc(menu, PostEnterLoadTemplateNames, &gTrue, false);
458 	MenuSetPostInputFunc(menu, PostInputSaveTemplate, data);
459 	MenuSetCustomDisplay(menu, SaveTemplateDisplayTitle, data);
460 	return menu;
461 }
462 
CheckReenableLoadMenu(menu_t * menu,void * data)463 static void CheckReenableLoadMenu(menu_t *menu, void *data)
464 {
465 	menu_t *loadMenu = MenuGetSubmenuByName(menu, "Load");
466 	UNUSED(data);
467 	assert(loadMenu);
468 	loadMenu->isDisabled = PlayerTemplateGetById(&gPlayerTemplates, 0) == NULL;
469 }
470 static menu_t *CreateCustomizeMenu(
471 	const char *name, PlayerSelectMenuData *data);
472 static void ShuffleAppearance(void *data);
PlayerSelectMenusCreate(PlayerSelectMenu * menu,int numPlayers,int player,const int playerUID,EventHandlers * handlers,GraphicsDevice * graphics,const NameGen * ng)473 void PlayerSelectMenusCreate(
474 	PlayerSelectMenu *menu, int numPlayers, int player, const int playerUID,
475 	EventHandlers *handlers, GraphicsDevice *graphics, const NameGen *ng)
476 {
477 	MenuSystem *ms = &menu->ms;
478 	PlayerSelectMenuData *data = &menu->data;
479 	struct vec2i pos, size;
480 	int w = graphics->cachedConfig.Res.x;
481 	int h = graphics->cachedConfig.Res.y;
482 
483 	data->nameMenuSelection = (int)strlen(letters);
484 	data->display.PlayerUID = playerUID;
485 	data->display.currentMenu = &ms->current;
486 	data->display.Dir = DIRECTION_DOWN;
487 	data->nameGenerator = ng;
488 
489 	switch (numPlayers)
490 	{
491 	case 1:
492 		// Single menu, entire screen
493 		pos = svec2i(w / 2, 0);
494 		size = svec2i(w / 2, h);
495 		break;
496 	case 2:
497 		// Two menus, side by side
498 		pos = svec2i(player * w / 2 + w / 4, 0);
499 		size = svec2i(w / 4, h);
500 		break;
501 	case 3:
502 	case 4:
503 		// Four corners
504 		pos = svec2i((player & 1) * w / 2 + w / 4, (player / 2) * h / 2);
505 		size = svec2i(w / 4, h / 2);
506 		break;
507 	default:
508 		CASSERT(false, "not implemented");
509 		pos = svec2i(w / 2, 0);
510 		size = svec2i(w / 2, h);
511 		break;
512 	}
513 	MenuSystemInit(ms, handlers, graphics, pos, size);
514 	ms->align = MENU_ALIGN_LEFT;
515 	ms->root = ms->current = MenuCreateNormal("", "", MENU_TYPE_NORMAL, 0);
516 	MenuSetPostInputFunc(ms->root, PostInputRotatePlayer, &data->display);
517 	MenuAddSubmenu(
518 		ms->root,
519 		MenuCreateCustom("Name", DrawNameMenu, HandleInputNameMenu, data));
520 
521 	MenuAddSubmenu(ms->root, CreateCustomizeMenu("Customize...", data));
522 	MenuAddSubmenu(
523 		ms->root, MenuCreateVoidFunc("Shuffle", ShuffleAppearance, data));
524 
525 	MenuAddSubmenu(ms->root, CreateUseTemplateMenu("Load", data));
526 	MenuAddSubmenu(ms->root, CreateSaveTemplateMenu("Save", data));
527 
528 	MenuAddSubmenu(ms->root, MenuCreateSeparator(""));
529 	MenuAddSubmenu(
530 		ms->root, MenuCreateNormal("Done", "", MENU_TYPE_NORMAL, 0));
531 	// Select "Done"
532 	ms->root->u.normal.index = (int)ms->root->u.normal.subMenus.size - 1;
533 	MenuAddExitType(ms, MENU_TYPE_RETURN);
534 	MenuSystemAddCustomDisplay(ms, MenuDisplayPlayer, &data->display);
535 	MenuSystemAddCustomDisplay(
536 		ms, MenuDisplayPlayerControls, &data->display.PlayerUID);
537 
538 	// Detect when there have been new player templates created,
539 	// to re-enable the load menu
540 	CheckReenableLoadMenu(ms->root, NULL);
541 	MenuSetPostEnterFunc(ms->root, CheckReenableLoadMenu, NULL, false);
542 }
CreateCustomizeMenu(const char * name,PlayerSelectMenuData * data)543 static menu_t *CreateCustomizeMenu(
544 	const char *name, PlayerSelectMenuData *data)
545 {
546 	menu_t *menu = MenuCreateNormal(name, "", MENU_TYPE_NORMAL, 0);
547 
548 	MenuAddSubmenu(menu, CreateFaceMenu(&data->display));
549 	MenuAddSubmenu(menu, CreateHairMenu(&data->display));
550 
551 	MenuAddSubmenu(
552 		menu, CreateColorMenu(
553 				  "Skin", &data->skinData, CHAR_COLOR_SKIN,
554 				  data->display.PlayerUID));
555 	MenuAddSubmenu(
556 		menu, CreateColorMenu(
557 				  "Hair", &data->hairData, CHAR_COLOR_HAIR,
558 				  data->display.PlayerUID));
559 	MenuAddSubmenu(
560 		menu, CreateColorMenu(
561 				  "Arms", &data->armsData, CHAR_COLOR_ARMS,
562 				  data->display.PlayerUID));
563 	MenuAddSubmenu(
564 		menu, CreateColorMenu(
565 				  "Body", &data->bodyData, CHAR_COLOR_BODY,
566 				  data->display.PlayerUID));
567 	MenuAddSubmenu(
568 		menu, CreateColorMenu(
569 				  "Legs", &data->legsData, CHAR_COLOR_LEGS,
570 				  data->display.PlayerUID));
571 	MenuAddSubmenu(
572 		menu, CreateColorMenu(
573 				  "Feet", &data->feetData, CHAR_COLOR_FEET,
574 				  data->display.PlayerUID));
575 
576 	MenuAddSubmenu(menu, MenuCreateSeparator(""));
577 	MenuAddSubmenu(menu, MenuCreateBack("Back"));
578 
579 	MenuSetPostInputFunc(menu, PostInputRotatePlayer, &data->display);
580 	MenuSetPostEnterFunc(
581 		menu, CheckReenableHairHatMenu, &data->display, false);
582 
583 	return menu;
584 }
ShuffleAppearance(void * data)585 static void ShuffleAppearance(void *data)
586 {
587 	PlayerSelectMenuData *pData = data;
588 	char buf[512];
589 	NameGenMake(pData->nameGenerator, buf);
590 	PlayerData *p = PlayerDataGetByUID(pData->display.PlayerUID);
591 	strcpy(p->name, buf);
592 	Character *c = &p->Char;
593 	CharacterShuffleAppearance(c);
594 }
595 
PostInputRotatePlayer(menu_t * menu,int cmd,void * data)596 static void PostInputRotatePlayer(menu_t *menu, int cmd, void *data)
597 {
598 	UNUSED(menu);
599 	MenuDisplayPlayerData *d = data;
600 	// Rotate player using left/right keys
601 	const int dx = (cmd & CMD_LEFT) ? 1 : ((cmd & CMD_RIGHT) ? -1 : 0);
602 	if (dx != 0)
603 	{
604 		d->Dir = (direction_e)CLAMP_OPPOSITE(
605 			(int)d->Dir + dx, DIRECTION_UP, DIRECTION_UPLEFT);
606 		char buf[CDOGS_PATH_MAX];
607 		const PlayerData *p = PlayerDataGetByUID(d->PlayerUID);
608 		sprintf(buf, "footsteps/%s", p->Char.Class->Footsteps);
609 		SoundPlay(&gSoundDevice, StrSound(buf));
610 	}
611 }
CheckReenableHairHatMenu(menu_t * menu,void * data)612 static void CheckReenableHairHatMenu(menu_t *menu, void *data)
613 {
614 	menu_t *hairMenu = MenuGetSubmenuByName(menu, "Hair/hat");
615 	CASSERT(hairMenu, "cannot find menu");
616 	MenuDisplayPlayerData *d = data;
617 	const PlayerData *p = PlayerDataGetByUID(d->PlayerUID);
618 	hairMenu->isDisabled = !p->Char.Class->HasHair;
619 }
620