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