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