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