guide pages
[carveJwlIkooP6JGAAIwe30JlM.git] / menu.c
1 #pragma once
2 #include "skaterift.h"
3 #include "menu.h"
4 #include "model.h"
5 #include "entity.h"
6 #include "input.h"
7 #include "world_map.h"
8 #include "ent_miniworld.h"
9 #include "audio.h"
10 #include "workshop.h"
11 #include "gui.h"
12 #include "control_overlay.h"
13 #include "network.h"
14 #include "shaders/model_menu.h"
15
16 struct global_menu menu = { .skip_starter = 0 };
17
18 void menu_at_begin(void)
19 {
20 if( menu.skip_starter ) return;
21
22 skaterift.activity = k_skaterift_menu;
23 menu.page = k_menu_page_starter;
24 }
25
26 void menu_init(void)
27 {
28 vg_console_reg_var( "skip_starter_menu", &menu.skip_starter,
29 k_var_dtype_i32, VG_VAR_PERSISTENT );
30 }
31
32 void menu_open( enum menu_page page )
33 {
34 skaterift.activity = k_skaterift_menu;
35
36 if( page != k_menu_page_any )
37 {
38 menu.page = page;
39 }
40 }
41
42 bool menu_viewing_map(void)
43 {
44 return (skaterift.activity == k_skaterift_menu) &&
45 (menu.page == k_menu_page_main) &&
46 (menu.main_index == k_menu_main_map);
47 }
48
49 static void menu_decor_select( ui_rect rect )
50 {
51 ui_px b = vg_ui.font->sx, hb = b/2;
52 ui_rect a0 = { rect[0] - 20 - hb, rect[1] + rect[3]/2 - hb, b,b },
53 a1 = { rect[0] + rect[2] + 20 + hb, rect[1] + rect[3]/2 - hb, b,b };
54
55 ui_text( a0, "\x95", 1, k_ui_align_middle_center, 0 );
56 ui_text( a1, "\x93", 1, k_ui_align_middle_center, 0 );
57 }
58
59 static void menu_standard_widget( ui_rect inout_panel, ui_rect rect, ui_px s )
60 {
61 ui_split( inout_panel, k_ui_axis_h, vg_ui.font->sy*s*2,
62 8, rect, inout_panel );
63 }
64
65 static bool menu_slider( ui_rect inout_panel, bool select, const char *label,
66 const f32 disp_min, const f32 disp_max, f32 *value,
67 const char *format )
68 {
69 ui_rect rect, box;
70 menu_standard_widget( inout_panel, rect, 1 );
71 ui_label( rect, label, 1, 8, box );
72
73 f32 t;
74 enum ui_button_state state = ui_slider_base( box, 0, 1, value, &t ),
75 mask_using =
76 k_ui_button_holding_inside |
77 k_ui_button_holding_outside |
78 k_ui_button_click,
79 mask_brighter = mask_using | k_ui_button_hover;
80
81 if( vg_input.display_input_method == k_input_method_controller )
82 {
83 if( select )
84 {
85 f32 m = axis_state( k_sraxis_mbrowse_h );
86 if( fabsf(m) > 0.5f )
87 {
88 *value += m * vg.time_frame_delta * (1.0f/2.0f);
89 *value = vg_clampf( *value, 0, 1 );
90 }
91
92 menu_decor_select( rect );
93 state |= k_ui_button_hover;
94 }
95 else
96 state = k_ui_button_none;
97 }
98
99 ui_rect line = { box[0], box[1], t * (f32)box[2], box[3] };
100 ui_fill( line, state&mask_brighter? GUI_COL_ACTIVE: GUI_COL_NORM );
101 ui_fill( (ui_rect){ box[0]+line[2], box[1], box[2]-line[2], box[3] },
102 GUI_COL_DARK );
103
104 ui_outline( box, 1, state? GUI_COL_HI: GUI_COL_ACTIVE, 0 );
105 ui_slider_text( box, format, disp_min + (*value)*( disp_max-disp_min ) );
106
107 return (state & mask_using) && 1;
108 }
109
110 static bool menu_button( ui_rect inout_panel, bool select, const char *text )
111 {
112 ui_rect rect;
113 menu_standard_widget( inout_panel, rect, 1 );
114
115 enum ui_button_state state = k_ui_button_none;
116
117 if( vg_input.display_input_method == k_input_method_controller )
118 {
119 if( select )
120 {
121 menu_decor_select( rect );
122
123 if( button_down( k_srbind_maccept ) )
124 state = k_ui_button_click;
125 }
126 }
127 else
128 {
129 state = ui_button_base( rect );
130 select = 0;
131 }
132
133 if( state == k_ui_button_click )
134 {
135 ui_fill( rect, GUI_COL_DARK );
136 }
137 else if( state == k_ui_button_holding_inside )
138 {
139 ui_fill( rect, GUI_COL_DARK );
140 }
141 else if( state == k_ui_button_holding_outside )
142 {
143 ui_fill( rect, GUI_COL_DARK );
144 ui_outline( rect, 1, GUI_COL_CLICK, 0 );
145 }
146 else if( state == k_ui_button_hover )
147 {
148 ui_fill( rect, GUI_COL_ACTIVE );
149 ui_outline( rect, 1, GUI_COL_CLICK, 0 );
150 }
151 else
152 {
153 ui_fill( rect, select? GUI_COL_ACTIVE: GUI_COL_NORM );
154 if( select )
155 ui_outline(rect, 1, GUI_COL_HI, 0 );
156 }
157
158 ui_text( rect, text, 1, k_ui_align_middle_center, 0 );
159
160 if( state == k_ui_button_click )
161 {
162 audio_lock();
163 audio_oneshot( &audio_ui[0], 1.0f, 0.0f );
164 audio_unlock();
165 return 1;
166 }
167 else return 0;
168 }
169
170 static bool menu_checkbox( ui_rect inout_panel, bool select,
171 const char *str_label, i32 *data )
172 {
173 ui_rect rect, label, box;
174 menu_standard_widget( inout_panel, rect, 1 );
175
176 ui_split( rect, k_ui_axis_v, -rect[3], 0, label, box );
177 ui_text( label, str_label, k_ui_scale, k_ui_align_middle_left, 0 );
178
179 enum ui_button_state state = k_ui_button_none;
180
181 if( vg_input.display_input_method == k_input_method_controller )
182 {
183 if( select )
184 {
185 menu_decor_select( rect );
186
187 if( button_down( k_srbind_maccept ) )
188 {
189 *data = (*data) ^ 0x1;
190 state = k_ui_button_click;
191 }
192 }
193 }
194 else
195 {
196 state = ui_checkbox_base( box, data );
197 select = 0;
198 }
199
200 if( state == k_ui_button_holding_inside )
201 {
202 ui_fill( box, GUI_COL_ACTIVE );
203 ui_outline( box, 1, GUI_COL_CLICK, 0 );
204 }
205 else if( state == k_ui_button_holding_outside )
206 {
207 ui_fill( box, GUI_COL_DARK );
208 ui_outline( box, 1, GUI_COL_CLICK, 0 );
209 }
210 else if( state == k_ui_button_hover )
211 {
212 ui_fill( box, GUI_COL_ACTIVE );
213 ui_outline( box, 1, GUI_COL_HI, 0 );
214 }
215 else
216 {
217 ui_fill( box, select? GUI_COL_ACTIVE: GUI_COL_DARK );
218 ui_outline( box, 1, select? GUI_COL_HI: GUI_COL_NORM, 0 );
219 }
220
221 if( *data )
222 {
223 ui_rect_pad( box, (ui_px[2]){8,8} );
224 ui_fill( box, GUI_COL_HI );
225 }
226
227 if( state == k_ui_button_click )
228 {
229 audio_lock();
230 audio_oneshot( &audio_ui[0], 1.0f, 0.0f );
231 audio_unlock();
232 return 1;
233 }
234 else return 0;
235 }
236
237 static void menu_heading( ui_rect inout_panel, const char *label, u32 colour )
238 {
239 ui_rect rect;
240 menu_standard_widget( inout_panel, rect, 1 );
241
242 rect[0] -= 8;
243 rect[2] += 16;
244
245 u32 c0 = ui_opacity( GUI_COL_DARK, 0.36f ),
246 c1 = ui_opacity( GUI_COL_DARK, 0.5f );
247
248 struct ui_vert *vs = ui_fill( rect, c0 );
249
250 vs[0].colour = c1;
251 vs[1].colour = c1;
252
253 rect[1] += 4;
254 ui_text( rect, label, 1, k_ui_align_middle_center, 1 );
255 rect[0] += 1;
256 rect[1] -= 1;
257 ui_text( rect, label, 1, k_ui_align_middle_center, colour? colour:
258 ui_colour(k_ui_blue+k_ui_brighter) );
259 }
260
261 static u32 medal_colour( u32 flags )
262 {
263 if( flags & k_ent_route_flag_achieve_gold )
264 return ui_colour( k_ui_yellow );
265 else if( flags & k_ent_route_flag_achieve_silver )
266 return ui_colour( k_ui_fg );
267 else return 0;
268 }
269
270 static i32 menu_nav( i32 *p_row, int mv, i32 max )
271 {
272 i32 row_prev = *p_row;
273
274 if( mv < 0 ) *p_row = vg_min( max, (*p_row) +1 );
275 if( mv > 0 ) *p_row = vg_max( 0, (*p_row) -1 );
276
277 if( *p_row != row_prev )
278 {
279 audio_lock();
280 audio_oneshot( &audio_ui[3], 1.0f, 0.0f );
281 audio_unlock();
282 }
283
284 return *p_row;
285 }
286
287 static void menu_try_find_cam( i32 id )
288 {
289 world_instance *world = &world_static.instances[0];
290 for( u32 i=0; i<mdl_arrcount(&world->ent_npc); i ++ )
291 {
292 ent_npc *fnpc = mdl_arritm( &world->ent_npc, i );
293 if( (fnpc->id == 50) && (fnpc->context == id) )
294 {
295 if( mdl_entity_id_type(fnpc->camera) == k_ent_camera )
296 {
297 u32 index = mdl_entity_id_id( fnpc->camera );
298 menu.bg_cam = mdl_arritm( &world->ent_camera, index );
299 menu.bg_blur = 0;
300 }
301 }
302 }
303 }
304
305 static void menu_link_modal( const char *url )
306 {
307 menu.web_link = url;
308
309 /* Only reset the choice of browser if 'No' was selected. */
310 if( menu.web_choice == 2 )
311 menu.web_choice = 0;
312 }
313
314 void menu_gui(void)
315 {
316 if( button_down( k_srbind_mopen ) )
317 {
318 if( skaterift.activity == k_skaterift_default )
319 {
320 menu_open( k_menu_page_main );
321 return;
322 }
323 }
324
325 if( skaterift.activity != k_skaterift_menu )
326 return;
327
328 menu.bg_blur = 1;
329 menu.bg_cam = NULL;
330
331 /* get buttons inputs
332 * -------------------------------------------------------------------*/
333 int ml = button_press( k_srbind_mleft ),
334 mr = button_press( k_srbind_mright ),
335 mu = button_press( k_srbind_mup ),
336 md = button_press( k_srbind_mdown ),
337 mh = ml-mr,
338 mv = mu-md,
339 enter = button_down( k_srbind_maccept );
340
341 /* TAB CONTROL */
342 u8 lb_down = 0, rb_down = 0;
343 vg_exec_input_program( k_vg_input_type_button_u8,
344 (vg_input_op[]){
345 vg_joy_button, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, vg_end
346 }, &rb_down );
347 vg_exec_input_program( k_vg_input_type_button_u8,
348 (vg_input_op[]){
349 vg_joy_button, SDL_CONTROLLER_BUTTON_LEFTSHOULDER, vg_end
350 }, &lb_down );
351
352 int mtab = (i32)rb_down - (i32)lb_down;
353
354 if( menu.repeater > 0.0f )
355 {
356 menu.repeater -= vg_minf( vg.time_frame_delta, 0.5f );
357 mv = 0;
358 mh = 0;
359 mtab = 0;
360 }
361 else
362 {
363 if( mv || mh || mtab )
364 menu.repeater += 0.2f;
365 }
366
367 if( vg_input.display_input_method == k_input_method_kbm )
368 {
369 vg_ui.wants_mouse = 1;
370 }
371
372 if( skaterift.activity != k_skaterift_menu ) return;
373
374
375 if( menu.web_link )
376 {
377 menu_try_find_cam( 3 );
378
379 ui_rect panel = { 0,0, 800, 200 },
380 screen = { 0,0, vg.window_x,vg.window_y };
381 ui_rect_center( screen, panel );
382 ui_fill( panel, GUI_COL_DARK );
383 ui_outline( panel, 1, GUI_COL_NORM, 0 );
384 ui_rect_pad( panel, (ui_px[]){8,8} );
385
386 ui_rect title;
387 ui_split( panel, k_ui_axis_h, 28*2, 0, title, panel );
388 ui_font_face( &vgf_default_title );
389 ui_text( title, "Open Link?", 1, k_ui_align_middle_center, 0 );
390
391 ui_split( panel, k_ui_axis_h, 28, 0, title, panel );
392 ui_font_face( &vgf_default_large );
393 ui_text( title, menu.web_link, 1, k_ui_align_middle_center, 0 );
394
395 ui_rect end = { panel[0], panel[1] + panel[3] - 48, panel[2], 48 };
396
397 ui_rect a,b,c;
398 ui_split_ratio( end, k_ui_axis_v, 2.0/3.0, 2, a, c );
399 ui_split_ratio( a, k_ui_axis_v, 1.0/2.0, 2, a, b );
400
401 i32 R = menu_nav( &menu.web_choice, mh, 2 );
402
403 if( menu_button( a, R==0, "Steam Overlay" ) )
404 {
405 if( steam_ready )
406 {
407 ISteamFriends *hSteamFriends = SteamAPI_SteamFriends();
408 SteamAPI_ISteamFriends_ActivateGameOverlayToWebPage( hSteamFriends,
409 menu.web_link,
410 k_EActivateGameOverlayToWebPageMode_Default );
411 menu.web_link = NULL;
412 }
413 }
414
415 if( menu_button( b, R==1, "Web Browser" ) )
416 {
417 char buf[512];
418 vg_str str;
419 vg_strnull( &str, buf, sizeof(buf) );
420 #ifdef _WIN32
421 vg_strcat( &str, "start " );
422 #else
423 vg_strcat( &str, "xdg-open " );
424 #endif
425 vg_strcat( &str, menu.web_link );
426
427 if( vg_strgood(&str) )
428 system( buf );
429
430 menu.web_link = NULL;
431 }
432
433 if( menu_button( c, R==2, "No" ) || button_down( k_srbind_mback ) )
434 {
435 audio_lock();
436 audio_oneshot( &audio_ui[3], 1.0f, 0.0f );
437 audio_unlock();
438 menu.web_link = NULL;
439 }
440
441 goto menu_draw;
442 }
443
444
445 if( vg.settings_open )
446 {
447 if( button_down( k_srbind_mback ) )
448 {
449 audio_lock();
450 audio_oneshot( &audio_ui[3], 1.0f, 0.0f );
451 audio_unlock();
452 vg_settings_close();
453 srinput.state = k_input_state_resume;
454 }
455
456 return;
457 }
458
459 if( menu.page == k_menu_page_credits )
460 {
461 ui_rect panel = { 0,0, 600, 400 },
462 screen = { 0,0, vg.window_x,vg.window_y };
463 ui_rect_center( screen, panel );
464 ui_fill( panel, GUI_COL_DARK );
465 ui_outline( panel, 1, GUI_COL_NORM, 0 );
466 ui_rect_pad( panel, (ui_px[]){8,8} );
467
468 ui_rect title;
469 ui_split( panel, k_ui_axis_h, 28*2, 0, title, panel );
470 ui_font_face( &vgf_default_title );
471 ui_text( title, "Skate Rift - Credits", 1, k_ui_align_middle_center, 0 );
472
473 ui_split( panel, k_ui_axis_h, 28, 0, title, panel );
474 ui_font_face( &vgf_default_large );
475 ui_text( title, "Mt.Zero Software", 1, k_ui_align_middle_center, 0 );
476
477 ui_split( panel, k_ui_axis_h, 8, 0, title, panel );
478 ui_split( panel, k_ui_axis_h, 28*2, 0, title, panel );
479 ui_font_face( &vgf_default_title );
480 ui_text( title, "Free Software", 1, k_ui_align_middle_center, 0 );
481
482 ui_split( panel, k_ui_axis_h, 8, 0, title, panel );
483 ui_font_face( &vgf_default_large );
484 ui_text( panel,
485 "Sam Lantinga - SDL2 - libsdl.org\n"
486 "Hunter WB - Anyascii\n"
487 "David Herberth - GLAD\n"
488 "Dominic Szablewski - QOI - qoiformat.org\n"
489 "Sean Barrett - stb_image, stb_vorbis,\n"
490 " stb_include\n"
491 "Khronos Group - OpenGL\n"
492 , 1, k_ui_align_left, 0 );
493
494 ui_rect end = { panel[0], panel[1] + panel[3] - 64, panel[2], 64 };
495
496 if( menu_button( end, 1, "Back" ) || button_down( k_srbind_mback ) )
497 {
498 menu.page = k_menu_page_main;
499 }
500
501 goto menu_draw;
502 }
503 else if( menu.page == k_menu_page_starter )
504 {
505 i32 R = menu_nav( &menu.intro_row, mv, 3 );
506 ui_rect panel = { 0,0, 600, 400 },
507 screen = { 0,0, vg.window_x,vg.window_y };
508 ui_rect_center( screen, panel );
509 ui_fill( panel, ui_opacity( GUI_COL_DARK, 0.35f ) );
510 ui_outline( panel, 1, GUI_COL_NORM, 0 );
511 ui_rect_pad( panel, (ui_px[]){8,8} );
512
513 ui_rect title;
514 ui_split( panel, k_ui_axis_h, 28*2, 0, title, panel );
515 ui_font_face( &vgf_default_title );
516 ui_text( title, "Welcome to Skate Rift", 1, k_ui_align_middle_center, 0 );
517
518 ui_split( panel, k_ui_axis_h, 28, 0, title, panel );
519 ui_font_face( &vgf_default_large );
520
521 menu_checkbox( panel, R == 0,
522 "Show controls overlay (good for new players)",
523 &control_overlay.enabled );
524 menu_checkbox( panel, R == 1, "Auto connect to global server",
525 &network_client.auto_connect );
526
527 ui_rect end = { panel[0], panel[1] + panel[3] - 100, panel[2], 100 };
528 menu_checkbox( end, R == 2, "Don't show this again", &menu.skip_starter );
529 if( menu_button( end, R == 3, "OK" ) )
530 {
531 menu.page = k_menu_page_main;
532 skaterift.activity = k_skaterift_default;
533 }
534
535 menu_try_find_cam( 3 );
536 goto menu_draw;
537 }
538 else if( menu.page == k_menu_page_premium )
539 {
540 i32 R = menu_nav( &menu.prem_row, mh, 1 );
541 ui_rect panel = { 0,0, 600, 400 },
542 screen = { 0,0, vg.window_x,vg.window_y };
543 ui_rect_center( screen, panel );
544 ui_fill( panel, ui_opacity( GUI_COL_DARK, 0.35f ) );
545 ui_outline( panel, 1, GUI_COL_NORM, 0 );
546 ui_rect_pad( panel, (ui_px[]){8,8} );
547
548 ui_rect title;
549 ui_split( panel, k_ui_axis_h, 28*2, 0, title, panel );
550 ui_font_face( &vgf_default_title );
551 ui_text( title, "Content is in the full game.",
552 1, k_ui_align_middle_center, 0 );
553
554 ui_split( panel, k_ui_axis_h, 28, 0, title, panel );
555 ui_font_face( &vgf_default_large );
556
557 ui_rect end = { panel[0], panel[1] + panel[3] - 48, panel[2], 48 }, a,b;
558 ui_split_ratio( end, k_ui_axis_v, 0.5f, 2, a, b );
559
560 if( menu_button( a, R == 0, "Store Page" ) )
561 {
562 if( steam_ready )
563 SteamAPI_ISteamFriends_ActivateGameOverlayToStore(
564 SteamAPI_SteamFriends(), 2103940, k_EOverlayToStoreFlag_None);
565 }
566
567 if( menu_button( b, R == 1, "Nah" ) || button_down( k_srbind_mback ) )
568 {
569 audio_lock();
570 audio_oneshot( &audio_ui[3], 1.0f, 0.0f );
571 audio_unlock();
572 skaterift.activity = k_skaterift_default;
573 return;
574 }
575
576 goto menu_draw;
577 }
578
579 /* TOP BAR
580 * -------------------------------------------------------------------*/
581
582 ui_font_face( &vgf_default_title );
583 ui_px height = vg_ui.font->ch + 16;
584 ui_rect topbar = { 0, 0, vg.window_x, height };
585
586 const char *opts[] = {
587 [k_menu_main_main] = "Menu",
588 [k_menu_main_map] = "Map",
589 [k_menu_main_settings ] = "Settings",
590 [k_menu_main_guide ] = "Guides"
591 };
592
593 if( mtab )
594 {
595 menu.main_index += mtab;
596
597 if( menu.main_index == -1 )
598 menu.main_index ++;
599
600 if( menu.main_index == vg_list_size(opts) )
601 menu.main_index --;
602
603 audio_lock();
604 audio_oneshot( &audio_ui[3], 1.0f, 0.0f );
605 audio_unlock();
606 }
607
608 ui_px x = 0, spacer = 8;
609 for( u32 draw=0; draw<2; draw ++ )
610 {
611 if( vg_input.display_input_method == k_input_method_controller )
612 {
613 if( draw )
614 {
615 ui_rect inf_lb = { x, 0, 32, height };
616 ui_fill( inf_lb, lb_down? GUI_COL_NORM: GUI_COL_NORM );
617 ui_text( inf_lb, "\x91", 1, k_ui_align_middle_center, 0 );
618 }
619 x += 32 + spacer;
620 }
621
622 for( i32 i=0; i<vg_list_size(opts); i ++ )
623 {
624 ui_rect box = { x, 0, ui_text_line_width(opts[i]) + 32, height };
625
626 if( draw )
627 {
628 enum ui_button_state state = ui_button_base( box );
629 if( state == k_ui_button_click )
630 {
631 ui_fill( box, GUI_COL_DARK );
632 menu.main_index = i;
633 }
634 else if( state == k_ui_button_holding_inside )
635 {
636 ui_fill( box, GUI_COL_DARK );
637 }
638 else if( state == k_ui_button_holding_outside )
639 {
640 ui_fill( box, GUI_COL_DARK );
641 ui_outline( box, 1, GUI_COL_CLICK, 0 );
642 }
643 else if( state == k_ui_button_hover )
644 {
645 ui_fill( box, GUI_COL_NORM );
646 ui_outline( box, 1, GUI_COL_ACTIVE, 0 );
647 }
648 else
649 ui_fill( box, i==menu.main_index? GUI_COL_ACTIVE: GUI_COL_NORM );
650
651 ui_text( box, opts[i], 1, k_ui_align_middle_center, 0 );
652 }
653
654 x += box[2] + spacer;
655 }
656
657 if( vg_input.display_input_method == k_input_method_controller )
658 {
659 if( draw )
660 {
661 ui_rect inf_rb = { x, 0, 32, height };
662 ui_fill( inf_rb, rb_down? GUI_COL_NORM: GUI_COL_NORM );
663 ui_text( inf_rb, "\x92", 1, k_ui_align_middle_center, 0 );
664 }
665 x += 32;
666 }
667
668 if( draw )
669 ui_fill( (ui_rect){ x+8,0, vg.window_x-(x+8),height }, GUI_COL_NORM );
670 else
671 {
672 x = vg.window_x/2 - x/2;
673 ui_fill( (ui_rect){ 0, 0, x-8, height }, GUI_COL_NORM );
674 }
675 }
676
677 if( menu.main_index == k_menu_main_map )
678 {
679 menu.bg_blur = 0;
680 ui_font_face( &vgf_default_large );
681 ui_rect title = { vg.window_x/2 - 512/2, height+8, 512, 64 };
682
683 ui_px x = 8,
684 y = height+8;
685
686 struct ui_vert *vs =
687 ui_fill( (ui_rect){ x,y, 0,height },
688 world_static.active_instance? GUI_COL_DARK: GUI_COL_ACTIVE );
689
690 char buf[64];
691 vg_str str;
692 vg_strnull( &str, buf, sizeof(buf) );
693 vg_strcat( &str, "Hub World" );
694
695 if( world_static.active_instance )
696 {
697 vg_strcat( &str, " (" );
698 vg_input_string( &str, input_button_list[k_srbind_mhub], 1 );
699 vg_strcatch( &str, '\x06' );
700 vg_strcatch( &str, '\x00' );
701 vg_strcat( &str, "\x1B[0m)" );
702 }
703
704 ui_px w = ui_text( (ui_rect){ x+8, y, 1000, height }, buf, 1,
705 k_ui_align_middle_left, 0 );
706 w *= vg_ui.font->sx;
707 x += w + 16;
708
709 vs[1].co[0] = x + 8;
710 vs[2].co[0] = x;
711
712 x += 2;
713
714 world_instance *world = &world_static.instances[1];
715 if( world->status == k_world_status_loaded )
716 {
717 const char *world_name =
718 mdl_pstr( &world->meta, world->info.pstr_name );
719
720 vg_strnull( &str, buf, sizeof(buf) );
721 vg_strcat( &str, world_name );
722
723 if( !world_static.active_instance &&
724 (vg_input.display_input_method == k_input_method_controller) )
725 {
726 vg_strcat( &str, " (" );
727 vg_input_string( &str, input_button_list[k_srbind_mhub], 1 );
728 vg_strcatch( &str, '\x06' );
729 vg_strcatch( &str, '\x00' );
730 vg_strcat( &str, "\x1B[0m)" );
731 }
732
733 vs = ui_fill( (ui_rect){ x,y, 1000,height },
734 world_static.active_instance? GUI_COL_ACTIVE: GUI_COL_DARK );
735 w = ui_text( (ui_rect){ x+16,y, 1000,height }, buf,
736 1, k_ui_align_middle_left, 0 );
737
738 w = w*vg_ui.font->sx + 8*3;
739 x += w;
740
741 if( button_down( k_srbind_mhub ) ||
742 ui_button_base( (ui_rect){0,y,x,height} ) == k_ui_button_click )
743 {
744 world_switch_instance( world_static.active_instance ^ 0x1 );
745 skaterift.activity = k_skaterift_default;
746 world_map.view_ready = 0;
747 }
748
749 vs[0].co[0] += 8;
750 vs[1].co[0] = x + 8;
751 vs[2].co[0] = x;
752 }
753
754 x = 8;
755 y += 8 + height;
756
757 if( world_static.active_instance )
758 {
759 ui_rect stat_panel = { x,y, 256,vg.window_y-y };
760 u32 c0 = ui_opacity( GUI_COL_DARK, 0.36f );
761 struct ui_vert *vs = ui_fill( stat_panel, c0 );
762
763 ui_rect_pad( stat_panel, (ui_px[2]){8,0} );
764
765 for( u32 i=0; i<mdl_arrcount( &world->ent_region ); i ++ )
766 {
767 ent_region *region = mdl_arritm( &world->ent_region, i );
768
769 if( !region->zone_volume )
770 continue;
771
772 const char *title = mdl_pstr( &world->meta, region->pstr_title );
773 ui_font_face( &vgf_default_large );
774
775 ui_rect title_box;
776 menu_standard_widget( stat_panel, title_box, 1 );
777
778 stat_panel[0] += 16;
779 stat_panel[2] -= 16;
780 ui_font_face( &vgf_default_small );
781
782 ent_volume *volume = mdl_arritm(&world->ent_volume,
783 mdl_entity_id_id(region->zone_volume));
784
785 u32 combined = k_ent_route_flag_achieve_gold |
786 k_ent_route_flag_achieve_silver;
787
788 char buf[128];
789 vg_str str;
790
791 for( u32 j=0; j<mdl_arrcount(&world->ent_route); j ++ )
792 {
793 ent_route *route = mdl_arritm(&world->ent_route, j );
794
795 v3f local;
796 m4x3_mulv( volume->to_local, route->board_transform[3], local );
797 if( !((fabsf(local[0]) <= 1.0f) &&
798 (fabsf(local[1]) <= 1.0f) &&
799 (fabsf(local[2]) <= 1.0f)) )
800 {
801 continue;
802 }
803
804 combined &= route->flags;
805
806 vg_strnull( &str, buf, sizeof(buf) );
807 vg_strcat( &str, "(Race) " );
808 vg_strcat( &str, mdl_pstr(&world->meta, route->pstr_name));
809
810 if( route->flags & k_ent_route_flag_achieve_silver )
811 vg_strcat( &str, " \xb3");
812 if( route->flags & k_ent_route_flag_achieve_gold )
813 vg_strcat( &str, "\xb3");
814
815 ui_rect r;
816 ui_standard_widget( stat_panel, r, 1 );
817 ui_text( r, buf, 1, k_ui_align_middle_left,
818 medal_colour(route->flags) );
819 }
820
821 for( u32 j=0; j<mdl_arrcount(&world->ent_challenge); j ++ )
822 {
823 ent_challenge *challenge = mdl_arritm( &world->ent_challenge, j );
824
825 v3f local;
826 m4x3_mulv( volume->to_local, challenge->transform.co, local );
827 if( !((fabsf(local[0]) <= 1.0f) &&
828 (fabsf(local[1]) <= 1.0f) &&
829 (fabsf(local[2]) <= 1.0f)) )
830 {
831 continue;
832 }
833
834 vg_strnull( &str, buf, sizeof(buf) );
835 vg_strcat( &str, mdl_pstr(&world->meta, challenge->pstr_alias));
836
837 u32 flags = 0x00;
838 if( challenge->status )
839 {
840 flags |= k_ent_route_flag_achieve_gold;
841 flags |= k_ent_route_flag_achieve_silver;
842 vg_strcat( &str, " \xb3\xb3" );
843 }
844
845 combined &= flags;
846
847 ui_rect r;
848 ui_standard_widget( stat_panel, r, 1 );
849 ui_text( r, buf, 1, k_ui_align_middle_left, medal_colour(flags) );
850 }
851
852 stat_panel[0] -= 16;
853 stat_panel[2] += 16;
854
855 u32 title_col = 0;
856 if( combined & k_ent_route_flag_achieve_gold )
857 title_col = ui_colour( k_ui_yellow );
858 else if( combined & k_ent_route_flag_achieve_silver )
859 title_col = ui_colour( k_ui_fg );
860
861 menu_heading( title_box, title, title_col );
862 }
863
864 vs[2].co[1] = stat_panel[1];
865 vs[3].co[1] = stat_panel[1];
866 }
867 }
868 else
869 {
870 if( button_down( k_srbind_mback ) )
871 {
872 audio_lock();
873 audio_oneshot( &audio_ui[3], 1.0f, 0.0f );
874 audio_unlock();
875 skaterift.activity = k_skaterift_default;
876 return;
877 }
878
879 ui_rect list0 = { vg.window_x/2 - 512/2, height+32,
880 512, vg.window_y-height-64 }, list;
881 rect_copy( list0, list );
882 ui_rect_pad( list, (ui_px[2]){8,8} );
883
884 /* MAIN / MAIN
885 * -------------------------------------------------------------------*/
886
887 if( menu.main_index == k_menu_main_main )
888 {
889 i32 R = menu_nav( &menu.main_row, mv, 2 );
890
891 if( menu_button( list, R == 0, "Resume" ) )
892 {
893 skaterift.activity = k_skaterift_default;
894 return;
895 }
896
897 if( menu_button( list, R == 1, "Credits" ) )
898 {
899 menu.page = k_menu_page_credits;
900 }
901
902 ui_rect end = { list[0], list[1]+list[3]-64, list[2], 72 };
903 if( menu_button( end, R == 2, "Quit Game" ) )
904 {
905 vg.window_should_close = 1;
906 }
907 }
908 else if( menu.main_index == k_menu_main_settings )
909 {
910 ui_fill( list0, ui_opacity( GUI_COL_DARK, 0.36f ) );
911 ui_outline( list0, 1, GUI_COL_NORM, 0 );
912 i32 R = menu_nav( &menu.settings_row, mv, 8 );
913
914 ui_font_face( &vgf_default_large );
915 list[1] -= 8;
916 menu_heading( list, "Game", 0 );
917 menu_checkbox( list, R == 0, "Show controls overlay",
918 &control_overlay.enabled );
919 menu_checkbox( list, R == 1, "Auto connect to global server",
920 &network_client.auto_connect );
921
922 menu_heading( list, "Audio/Video", 0 );
923 menu_slider( list, R == 2, "Volume", 0, 100,
924 &vg_audio.external_global_volume, "%.f%%" );
925 menu_slider( list, R == 3, "Resolution", 0, 100,
926 &k_render_scale, "%.f%%" );
927 menu_checkbox( list, R == 4, "Motion Blur", &k_blur_effect );
928
929 menu_heading( list, "Camera", 0 );
930 menu_slider( list, R == 5, "Fov", 97, 135,
931 &k_fov, "%.1f\xb0" );
932 menu_slider( list, R == 6, "Cam Height", -0.4f, +1.4f,
933 &k_cam_height, vg_lerpf(-0.4f,1.4f,k_cam_height)>=0.0f?
934 "+%.2fm": "%.2fm" );
935 menu_checkbox( list, R == 7, "Invert Y Axis", &k_invert_y );
936
937
938 ui_rect end = { list[0], list[1]+list[3]-64, list[2], 72 };
939 ui_font_face( &vgf_default_small );
940 menu_heading( end, "Advanced", 0 );
941 if( menu_button( end, R == 8, "Open Engine Settings" ) )
942 {
943 vg_settings_open();
944 }
945 }
946 else if( menu.main_index == k_menu_main_guide )
947 {
948 list0[0] = 8;
949 list[0] = 16;
950
951 ui_px w = 700,
952 marg = list0[0]+list0[2],
953 pw = vg.window_x - marg,
954 infx = pw/2 - w/2 + marg;
955 ui_rect inf = { infx, height +32, w, 800 };
956
957 struct ui_vert *vs = NULL;
958
959 if( menu.guide_sel && (menu.guide_sel <= 3) )
960 {
961 vs = ui_fill( inf, ui_opacity( GUI_COL_DARK, 0.20f ) );
962 ui_rect_pad( inf, (ui_px[]){8,8} );
963 }
964
965 ui_fill( list0, ui_opacity( GUI_COL_DARK, 0.36f ) );
966 ui_outline( list0, 1, GUI_COL_NORM, 0 );
967 i32 R = menu_nav( &menu.guides_row, mv, 6 );
968
969 ui_font_face( &vgf_default_large );
970 list[1] -= 8;
971 menu_heading( list, "Info", 0 );
972 if( menu.guide_sel == 1 )
973 {
974 menu_try_find_cam( 1 );
975
976 ui_rect title;
977 ui_split( inf, k_ui_axis_h, 28*2, 0, title, inf );
978 ui_font_face( &vgf_default_title );
979 ui_text( title, "Where to go", 1, k_ui_align_middle_center, 0 );
980
981 ui_split( inf, k_ui_axis_h, 28, 0, title, inf );
982 ui_font_face( &vgf_default_large );
983 ui_text( inf,
984 "Visit the sandcastles built by John Cockroach to be\n"
985 "transported into the real location. Use the map in the\n"
986 "menu to return back this hub island.\n"
987 "\n"
988 "You can begin training at the Volcano Island, where you\n"
989 "can learn movement skills. Then travel to Mt.Zero Island\n"
990 "or Bort Downtown to test them out. Finally the most\n"
991 "challenging course can be taken on at the Valley.\n"
992 "\n"
993 "Also visit the Steam Workshop for community made\n"
994 "locations to skate!"
995 , 1, k_ui_align_left, 0 );
996
997 vs[2].co[1] = vs[0].co[1] + 84 + vgf_default_large.sy*11 + 16;
998 vs[3].co[1] = vs[2].co[1];
999 }
1000 if( menu_button( list, R == 0, "Where to go" ) ) menu.guide_sel = 1;
1001
1002 if( menu.guide_sel == 3 )
1003 {
1004 menu_try_find_cam( 2 );
1005
1006 ui_rect title;
1007 ui_split( inf, k_ui_axis_h, 28*2, 0, title, inf );
1008 ui_font_face( &vgf_default_title );
1009 ui_text( title, "Online", 1, k_ui_align_middle_center, 0 );
1010
1011 ui_split( inf, k_ui_axis_h, 28, 0, title, inf );
1012 ui_font_face( &vgf_default_large );
1013 ui_text( inf,
1014 "Connection to the global server is managed by this radar\n"
1015 "dish in the hub island. Come back here to turn the\n"
1016 "connection on or off.\n"
1017 "\n"
1018 "You'll then see other players or your friends if you go\n"
1019 "to the same location, and your highscores will be saved\n"
1020 "onto the scoreboards."
1021 , 1, k_ui_align_left, 0 );
1022
1023 vs[2].co[1] = vs[0].co[1] + 84 + vgf_default_large.sy*7 + 16;
1024 vs[3].co[1] = vs[2].co[1];
1025 }
1026 if( menu_button( list, R == 1, "Playing Online" ) ) menu.guide_sel = 3;
1027
1028 menu_heading( list, "Controls", 0 );
1029 if( menu_button( list, R == 2, "Skating \xb2" ) )
1030 {
1031 menu.guide_sel = 0;
1032 menu_link_modal(
1033 "https://skaterift.com/index.php?page=skating" );
1034 }
1035 if( menu.guide_sel == 0 || menu.guide_sel > 3 ) menu_try_find_cam( 3 );
1036
1037 if( menu_button( list, R == 3, "Tricks \xb2" ) )
1038 {
1039 menu.guide_sel = 0;
1040 menu_link_modal(
1041 "https://skaterift.com/index.php?page=tricks" );
1042 }
1043
1044 menu_heading( list, "Workshop", 0 );
1045 if( menu_button( list, R == 4, "Create a Board \xb2" ) )
1046 {
1047 menu.guide_sel = 0;
1048 menu_link_modal(
1049 "https://skaterift.com/index.php?page=workshop_board" );
1050 }
1051 if( menu_button( list, R == 5, "Create a World \xb2" ) )
1052 {
1053 menu.guide_sel = 0;
1054 menu_link_modal(
1055 "https://skaterift.com/index.php?page=workshop_world" );
1056 }
1057 if( menu_button( list, R == 6, "Create a Playermodel \xb2" ) )
1058 {
1059 menu.guide_sel = 0;
1060 menu_link_modal(
1061 "https://skaterift.com/index.php?page=workshop_player" );
1062 }
1063 }
1064 }
1065
1066 menu_draw:
1067
1068 vg_ui.frosting = 0.015f;
1069 ui_flush( k_ui_shader_colour, vg.window_x, vg.window_y );
1070 vg_ui.frosting = 0.0f;
1071
1072 ui_font_face( &vgf_default_small );
1073 }