6 #include "world_render.h"
9 #include "shaders/menu.h"
10 #include "vg_steam_friends.h"
11 #include "submodules/tinydir/tinydir.h"
13 VG_STATIC mdl_context menu_model
;
14 VG_STATIC glmesh menu_glmesh
;
15 VG_STATIC m4x3f menu_mdl_mtx
;
16 VG_STATIC
float menu_opacity
= 0.0f
;
17 VG_STATIC
float menu_input_cooldown
= 0.0f
;
18 VG_STATIC
float menu_fov_target
= 97.0f
;
19 VG_STATIC v2f menu_extra_angles
;
21 VG_STATIC
int cl_menu
= 0,
24 VG_STATIC
int menu_enabled(void){ return cl_menu
; }
26 VG_STATIC
const char *playermodels
[] = { "ch_new", "ch_jordan", "ch_outlaw" };
28 vg_tex2d tex_menu
= { .path
= "textures/menu.qoi",.flags
= VG_TEXTURE_NEAREST
};
30 VG_STATIC
struct input_binding input_menu_h
,
35 input_menu_toggle_kbm
;
37 VG_STATIC
void menu_btn_quit( int event
);
38 VG_STATIC
void menu_btn_skater( int event
);
39 VG_STATIC
void menu_btn_blur( int event
);
40 VG_STATIC
void menu_btn_fuckoff( int event
);
41 VG_STATIC
void menu_btn_reset( int event
);
42 VG_STATIC
void menu_btn_map( int event
);
43 VG_STATIC
void menu_btn_settings( int event
);
45 VG_STATIC mdl_node
*menu_pnode_fov_slider
,
48 *menu_pnode_vol_slider
,
72 k_menu_page_main
= 0x1,
73 k_menu_page_skater
= 0x2,
74 k_menu_page_quit
= 0x4,
75 k_menu_page_settings
= 0x8,
76 k_menu_page_map
= 0x10
79 struct menu_btn_userdata
85 VG_STATIC
int menu_settings_if( struct menu_btn_userdata ud
)
87 if( game_menu
.page
& k_menu_page_settings
)
89 int *ptr
= ud
.ptr_generic
;
96 VG_STATIC
int menu_vis( struct menu_btn_userdata ud
)
98 if( ud
.i
& game_menu
.page
)
104 VG_STATIC
int menu_controller( struct menu_btn_userdata ud
)
106 if( (game_menu
.page
& (k_menu_page_main
|k_menu_page_settings
))
107 && (ud
.i
== menu_display_controller
) )
112 VG_STATIC
int menu_controller_inf( struct menu_btn_userdata ud
)
114 if( (game_menu
.page
& k_menu_page_settings
)
115 && (ud
.i
== menu_display_controller
) )
124 int (*fn_visibility
)( struct menu_btn_userdata ud
);
125 struct menu_btn_userdata user
;
127 void (*fn_press
)( int event
);
137 VG_STATIC menu_buttons
[] =
140 "text_quit", menu_vis
, {.i
=k_menu_page_main
|k_menu_page_quit
},
141 .fn_press
= menu_btn_quit
,
142 .ld
="text_reset", .lr
="text_settings", .ll
="text_map"
145 "text_quitty", menu_vis
, {.i
=k_menu_page_quit
}
148 "text_yes", menu_vis
, {.i
=k_menu_page_quit
},
149 .fn_press
= menu_btn_fuckoff
152 "text_reset", menu_vis
, {.i
=k_menu_page_main
},
153 .fn_press
= menu_btn_reset
,
154 .lu
="text_quit", .ld
="text_skater", .ll
="text_map", .lr
="text_settings"
157 "text_skater", menu_vis
, {.i
=k_menu_page_main
|k_menu_page_skater
},
158 .fn_press
= menu_btn_skater
,
159 .lu
="text_reset", .ll
="text_map", .lr
="text_settings"
162 "text_map", menu_vis
, {.i
=k_menu_page_main
},
163 .fn_press
= menu_btn_map
,
167 "text_settings", menu_vis
, {.i
=k_menu_page_main
|k_menu_page_settings
},
168 .fn_press
= menu_btn_settings
,
172 "skater_left", menu_vis
, {k_menu_page_skater
}
175 "skater_right", menu_vis
, {k_menu_page_skater
}
179 "fov_slider", menu_vis
, {k_menu_page_settings
},
182 { "fov_info", menu_vis
, {k_menu_page_settings
} },
185 "vol_slider", menu_vis
, {k_menu_page_settings
},
188 { "vol_info", menu_vis
, {k_menu_page_settings
} },
191 "text_blur", menu_vis
, {k_menu_page_settings
},
192 .fn_press
= menu_btn_blur
,
193 .lu
="fov_slider", .ld
="vol_slider"
196 "text_blur_check", menu_settings_if
, {.ptr_generic
=&cl_blur
}
199 { "ctr_xbox", menu_controller_inf
, {k_menu_controller_type_xbox
}},
200 { "ctr_xbox_text", menu_controller_inf
, {k_menu_controller_type_xbox
}},
201 { "ctr_steam", menu_controller_inf
, {k_menu_controller_type_steam
}},
202 { "ctr_steam_text", menu_controller_inf
, {k_menu_controller_type_steam
}},
203 { "ctr_deck", menu_controller_inf
, {k_menu_controller_type_steam_deck
}},
204 { "ctr_deck_text", menu_controller_inf
, {k_menu_controller_type_steam_deck
}},
205 { "ctr_ps", menu_controller_inf
, {k_menu_controller_type_playstation
}},
206 { "ctr_ps_text", menu_controller_inf
, {k_menu_controller_type_playstation
}},
207 { "ctr_kbm", menu_controller_inf
, {k_menu_controller_type_keyboard
}},
208 { "ctr_kbm_text", menu_controller_inf
, {k_menu_controller_type_keyboard
}},
210 "text_paused", menu_vis
, {k_menu_page_main
}
214 VG_STATIC
int menu_get_loc( const char *loc
)
216 for( int i
=0; i
<vg_list_size(menu_buttons
); i
++ )
217 if( !strcmp( menu_buttons
[i
].name
, loc
) )
224 VG_STATIC
void menu_btn_reset( int event
)
226 reset_player(0,NULL
);
227 world_routes_clear();
233 VG_STATIC
void menu_btn_fuckoff( int event
)
235 vg
.window_should_close
= 1;
238 VG_STATIC
void menu_btn_quit( int event
)
240 game_menu
.page
= k_menu_page_quit
;
241 game_menu
.loc
= menu_get_loc( "text_yes" );
244 VG_STATIC
void menu_btn_settings( int event
)
246 game_menu
.page
= k_menu_page_settings
;
247 game_menu
.loc
= menu_get_loc( "fov_slider" );
250 VG_STATIC
void menu_btn_skater( int event
)
252 game_menu
.page
= k_menu_page_skater
;
255 VG_STATIC
void menu_btn_blur( int event
)
260 VG_STATIC
void menu_btn_map( int event
)
262 game_menu
.page
= k_menu_page_map
;
263 game_menu
.map_count
= 0;
264 game_menu
.selected_map
= 0;
267 tinydir_open( &dir
, "maps" );
269 while( dir
.has_next
)
272 tinydir_readfile( &dir
, &file
);
276 struct menu_map_file
*mf
= &game_menu
.maps_list
[ game_menu
.map_count
];
278 vg_strncpy( file
.name
, mf
->name
,
279 vg_list_size(game_menu
.maps_list
[0].name
)-1 );
281 game_menu
.map_count
++;
282 if( game_menu
.map_count
== vg_list_size(game_menu
.maps_list
) )
286 tinydir_next( &dir
);
292 VG_STATIC
void menu_crap_ui(void)
294 if( cl_menu
&& (game_menu
.page
== k_menu_page_map
) )
297 box
[0] = vg
.window_x
/2 - 150;
298 box
[1] = vg
.window_y
/2 - 300;
302 ui_fill_rect( box
, 0xa0000000 );
304 if( game_menu
.map_count
== 0 )
306 ui_text( (ui_rect
){ vg
.window_x
/2, box
[1]+8, 0,0 }, "No maps found", 1,
307 k_text_align_center
);
311 ui_rect_pad( box
, 4 );
314 for( int i
=0; i
<game_menu
.map_count
; i
++ )
316 struct menu_map_file
*mf
= &game_menu
.maps_list
[ i
];
318 ui_fill_rect( box
, game_menu
.selected_map
== i
? 0xa0ffffff:
320 ui_text( (ui_rect
){ vg
.window_x
/2, box
[1]+2, 0,0 },
321 mf
->name
, 1, k_text_align_center
);
328 VG_STATIC
void steam_on_game_overlay( CallbackMsg_t
*msg
)
330 GameOverlayActivated_t
*inf
= (GameOverlayActivated_t
*)msg
->m_pubParam
;
331 vg_info( "Steam game overlay activated; pausing\n" );
336 game_menu
.page
= k_menu_page_main
;
337 game_menu
.loc
= menu_get_loc( "text_skater" );
341 VG_STATIC
void menu_init(void)
343 vg_create_unnamed_input( &input_menu_h
, k_input_type_axis
);
344 vg_create_unnamed_input( &input_menu_v
, k_input_type_axis
);
345 vg_create_unnamed_input( &input_menu_back
, k_input_type_button
);
346 vg_create_unnamed_input( &input_menu_press
, k_input_type_button
);
347 vg_create_unnamed_input( &input_menu_toggle
, k_input_type_button
);
348 vg_create_unnamed_input( &input_menu_toggle_kbm
, k_input_type_button
);
350 vg_apply_bind_str( &input_menu_h
, "", "gp-ls-h" );
351 vg_apply_bind_str( &input_menu_h
, "+", "right" );
352 vg_apply_bind_str( &input_menu_h
, "-", "left" );
353 vg_apply_bind_str( &input_menu_v
, "", "-gp-ls-v" );
354 vg_apply_bind_str( &input_menu_v
, "+", "up" );
355 vg_apply_bind_str( &input_menu_v
, "-", "down" );
356 vg_apply_bind_str( &input_menu_press
, "", "gp-a" );
357 vg_apply_bind_str( &input_menu_press
, "", "\2enter" );
358 vg_apply_bind_str( &input_menu_back
, "", "gp-b" );
359 vg_apply_bind_str( &input_menu_back
, "", "\2escape" );
360 vg_apply_bind_str( &input_menu_toggle_kbm
, "", "\2escape" );
361 vg_apply_bind_str( &input_menu_toggle
, "", "\2gp-menu" );
363 vg_linear_clear( vg_mem
.scratch
);
365 mdl_open( &menu_model
, "models/rs_menu.mdl" );
366 mdl_load_metadata( &menu_model
, vg_mem
.rtmemory
);
367 mdl_load_mesh_data( &menu_model
, vg_mem
.scratch
);
368 mdl_invert_uv_coordinates( &menu_model
);
369 mdl_close( &menu_model
);
371 vg_acquire_thread_sync();
373 mdl_unpack_glmesh( &menu_model
, &menu_glmesh
);
374 vg_tex2d_init( (vg_tex2d
*[]){ &tex_menu
}, 1 );
376 vg_release_thread_sync();
378 for( int i
=0; i
<vg_list_size(menu_buttons
); i
++ )
380 struct menu_button
*btn
= &menu_buttons
[i
];
381 btn
->pnode
= mdl_node_from_name( &menu_model
, btn
->name
);
385 vg_info( "info: %s\n", btn
->name
);
386 vg_fatal_exit_loop( "Menu programming error" );
390 menu_pnode_fov_max
= mdl_node_from_name( &menu_model
, "fov_slider_max" );
391 menu_pnode_fov_min
= mdl_node_from_name( &menu_model
, "fov_slider_min" );
392 menu_pnode_fov_slider
= mdl_node_from_name( &menu_model
, "fov_slider" );
393 menu_pnode_vol_max
= mdl_node_from_name( &menu_model
, "vol_slider_max" );
394 menu_pnode_vol_min
= mdl_node_from_name( &menu_model
, "vol_slider_min" );
395 menu_pnode_vol_slider
= mdl_node_from_name( &menu_model
, "vol_slider" );
397 shader_menu_register();
400 steam_register_callback( k_iGameOverlayActivated
, steam_on_game_overlay
);
404 VG_STATIC
void menu_run_directional(void)
406 struct menu_button
*btn
= &menu_buttons
[ game_menu
.loc
];
408 if( vg_input_button_down( &input_menu_press
) )
413 audio_play_oneshot( &audio_ui
[0], 1.0f
);
421 if( menu_input_cooldown
<= 0.0f
)
423 v2f dir
= { input_menu_h
.axis
.value
,
424 -input_menu_v
.axis
.value
};
426 if( v2_length2( dir
) > 0.8f
*0.8f
)
428 const char *link
= NULL
;
430 if( fabsf(dir
[0]) > fabsf(dir
[1]) )
432 if( dir
[0] > 0.0f
) link
= btn
->lr
;
437 if( dir
[1] > 0.0f
) link
= btn
->ld
;
443 game_menu
.loc
= menu_get_loc( link
);
444 menu_input_cooldown
= 0.25f
;
450 VG_STATIC
int menu_page_should_backout(void)
452 return vg_input_button_down( &input_menu_back
);
455 VG_STATIC
void menu_close(void)
459 game_menu
.loc
= menu_get_loc( "text_skater" );
462 VG_STATIC
void menu_page_main(void)
464 if( menu_page_should_backout() )
470 menu_fov_target
= 112.0f
;
471 menu_run_directional();
474 VG_STATIC
void menu_page_map(void)
476 if( menu_page_should_backout() )
478 game_menu
.page
= k_menu_page_main
;
479 game_menu
.loc
= menu_get_loc( "text_map" );
482 if( game_menu
.map_count
> 0 )
484 float v
= input_menu_v
.axis
.value
;
485 if( (fabsf(v
) > 0.7f
) && (menu_input_cooldown
<= 0.0f
) )
488 audio_play_oneshot( &audio_rewind
[4], 1.0f
);
493 game_menu
.selected_map
--;
495 if( game_menu
.selected_map
< 0 )
496 game_menu
.selected_map
= game_menu
.map_count
-1;
498 menu_input_cooldown
= 0.25f
;
502 game_menu
.selected_map
++;
504 if( game_menu
.selected_map
>= game_menu
.map_count
)
505 game_menu
.selected_map
= 0;
507 menu_input_cooldown
= 0.25f
;
511 if( vg_input_button_down( &input_menu_press
) )
515 strcpy( temp
, "maps/" );
516 strcat( temp
, game_menu
.maps_list
[game_menu
.selected_map
].name
);
518 world_change_world( 1, (const char *[]){ temp
} );
523 menu_fov_target
= 80.0f
;
526 VG_STATIC
void menu_page_quit(void)
528 if( menu_page_should_backout() )
530 game_menu
.page
= k_menu_page_main
;
531 game_menu
.loc
= menu_get_loc( "text_quit" );
534 menu_fov_target
= 90.0f
;
535 menu_run_directional();
538 VG_STATIC
void menu_page_skater(void)
540 float h
= input_menu_h
.axis
.value
;
541 menu_fov_target
= 97.0f
;
543 if( menu_page_should_backout() )
545 game_menu
.page
= k_menu_page_main
;
546 game_menu
.loc
= menu_get_loc( "text_skater" );
550 if( (fabsf(h
) > 0.7f
) && (menu_input_cooldown
<= 0.0f
) )
553 audio_play_oneshot( &audio_rewind
[4], 1.0f
);
559 if( cl_playermdl_id
< 0 )
562 int li
= menu_get_loc( "skater_left" );
564 menu_buttons
[li
].fsize
= 0.4f
;
565 menu_buttons
[li
].falpha
= 1.0f
;
567 menu_input_cooldown
= 0.25f
;
572 if( cl_playermdl_id
> 2 )
575 int ri
= menu_get_loc( "skater_left" );
577 menu_buttons
[ri
].fsize
= 0.4f
;
578 menu_buttons
[ri
].falpha
= 1.0f
;
580 menu_input_cooldown
= 0.25f
;
585 VG_STATIC
void menu_slider( float *value
, float min
, float max
,
586 mdl_node
*slider
, mdl_node
*pmin
, mdl_node
*pmax
)
588 float h
= input_menu_h
.axis
.value
;
590 if( fabsf(h
) > 0.04f
)
591 *value
+= h
* vg
.frame_delta
;
592 *value
= vg_clampf( *value
, min
, max
);
594 v3_lerp( pmin
->co
, pmax
->co
, *value
, slider
->co
);
597 VG_STATIC
void menu_page_settings(void)
599 menu_run_directional();
601 if( game_menu
.loc
== menu_get_loc( "fov_slider" ) )
603 menu_slider( &cl_fov
, 0.0f
, 1.0f
,
604 menu_pnode_fov_slider
, menu_pnode_fov_min
,
605 menu_pnode_fov_max
);
607 menu_fov_target
= vg_lerpf( 97.0f
, 135.0f
, cl_fov
) * 0.8f
;
609 else if( game_menu
.loc
== menu_get_loc( "vol_slider" ) )
611 menu_slider( &cl_volume
, 0.0f
, 1.0f
,
612 menu_pnode_vol_slider
, menu_pnode_vol_min
,
613 menu_pnode_vol_max
);
616 if( menu_page_should_backout() )
618 game_menu
.page
= k_menu_page_main
;
619 game_menu
.loc
= menu_get_loc( "text_settings" );
624 VG_STATIC
void menu_update(void)
626 vg_input_update( 1, &input_menu_h
);
627 vg_input_update( 1, &input_menu_v
);
628 vg_input_update( 1, &input_menu_back
);
629 vg_input_update( 1, &input_menu_press
);
630 vg_input_update( 1, &input_menu_toggle
);
631 vg_input_update( 1, &input_menu_toggle_kbm
);
633 int toggle_gp
= vg_input_button_down( &input_menu_toggle
),
634 toggle_kb
= vg_input_button_down( &input_menu_toggle_kbm
),
637 if( toggle_gp
|| toggle_kb
)
656 if( !wait_for_a_sec
&& cl_menu
)
658 if( game_menu
.page
== k_menu_page_main
)
660 else if( game_menu
.page
== k_menu_page_skater
)
662 else if( game_menu
.page
== k_menu_page_quit
)
664 else if( game_menu
.page
== k_menu_page_settings
)
665 menu_page_settings();
666 else if( game_menu
.page
== k_menu_page_map
)
670 struct menu_button
*btn
= &menu_buttons
[ game_menu
.loc
];
678 v3f
*mtx
= player
.mdl
.sk
.final_mtx
[player
.mdl
.id_head
];
679 m3x3_mulv( mtx
, (v3f
){-1.0f
,0.0f
,0.0f
}, lookdir
);
682 v3_normalize( lookdir
);
688 v3_copy(player
.mdl
.ragdoll
[ player
.mdl
.id_hip
-1 ].rb
.co
, center_rough
);
692 v3_add( player
.camera_pos
, player
.visual_transform
[3], center_rough
);
693 v3_muls( center_rough
, 0.5f
, center_rough
);
696 v3_muladds( center_rough
, lookdir
, 1.5f
, pos
);
697 v3_add( (v3f
){ 0.0f
,0.8f
,0.0f
}, pos
, pos
);
700 angles
[0] = -atan2f( lookdir
[0], lookdir
[2] );
702 /* setup model matrix */
704 q_axis_angle( qmenu_mdl
, (v3f
){0.0f
,1.0f
,0.0f
}, -angles
[0] );
706 q_m3x3( qmenu_mdl
, menu_mdl_mtx
);
707 v3_copy( center_rough
, menu_mdl_mtx
[3] );
713 v3_sub( btn
->pnode
->co
, (v3f
){ 0.0f
,1.5f
,-1.5f
}, delta
);
714 v3_normalize( delta
);
716 float y
= atan2f( delta
[0], delta
[2] ),
720 menu_extra_angles
[0] = vg_lerpf( menu_extra_angles
[0], y
, dt
);
721 menu_extra_angles
[1] = vg_lerpf( menu_extra_angles
[1], p
, dt
);
723 v2_muladds( angles
, menu_extra_angles
, 0.8f
, angles
);
724 angles
[0] = fmodf( angles
[0], VG_TAUf
);
729 main_camera
.angles
[0] =
730 vg_alerpf( main_camera
.angles
[0], angles
[0], menu_opacity
);
731 main_camera
.angles
[1] =
732 vg_lerpf ( main_camera
.angles
[1], angles
[1], menu_opacity
);
733 v3_lerp( main_camera
.pos
, pos
, menu_opacity
, main_camera
.pos
);
735 camera_update_transform( &main_camera
);
738 float dt
= vg
.frame_delta
* 6.0f
;
739 menu_opacity
= vg_lerpf( menu_opacity
, cl_menu
&&!cl_menu_go_away
, dt
);
741 if( menu_opacity
<= 0.01f
)
747 vg
.time_rate
= 1.0-(double)menu_opacity
;
751 menu_input_cooldown
-= vg
.frame_delta
;
755 /* https://iquilezles.org/articles/functions/ */
756 float expSustainedImpulse( float x
, float f
, float k
)
758 float s
= fmaxf(x
-f
,0.0f
);
759 return fminf( x
*x
/(f
*f
), 1.0f
+(2.0f
/f
)*s
*expf(-k
*s
));
762 VG_STATIC
void menu_render( camera
*cam
)
765 glDisable(GL_DEPTH_TEST
);
766 glBlendFunc( GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
767 glBlendEquation(GL_FUNC_ADD
);
769 shader_blitcolour_use();
770 shader_blitcolour_uColour( (v4f
){ 0.1f
, 0.1f
, 0.3f
, menu_opacity
*0.5f
} );
773 glEnable( GL_DEPTH_TEST
);
774 glDisable( GL_BLEND
);
779 shader_menu_uColour( (v4f
){ 1.0f
,1.0f
,1.0f
,1.0f
} );
780 shader_menu_uTexMain( 1 );
781 vg_tex2d_bind( &tex_menu
, 1 );
783 shader_menu_uPv( cam
->mtx
.pv
);
784 shader_menu_uPvmPrev( cam
->mtx_prev
.pv
);
785 mesh_bind( &menu_glmesh
);
787 for( int i
=0; i
<vg_list_size(menu_buttons
); i
++ )
789 struct menu_button
*btn
= &menu_buttons
[i
];
790 float talpha
= i
==game_menu
.loc
? 1.0f
: 0.0f
,
791 tsize0
= btn
->fn_visibility( btn
->user
)? 1.0f
: 0.0f
,
792 tsize1
= i
==game_menu
.loc
? 0.07f
: 0.0f
,
793 tsize
= tsize0
+tsize1
;
795 btn
->falpha
= vg_lerpf( btn
->falpha
, talpha
, vg
.frame_delta
* 14.0f
);
796 btn
->fsize
= vg_lerpf( btn
->fsize
, tsize
, vg
.frame_delta
* 3.0f
);
799 v4f vselected
= {0.95f
*1.3f
,0.45f
*1.3f
,0.095f
*1.3f
, 1.0f
},
800 vnormal
= {1.0f
,1.0f
,1.0f
, 1.0f
},
803 v4_lerp( vnormal
, vselected
, btn
->falpha
, vcurrent
);
804 shader_menu_uColour( vcurrent
);
808 mdl_node_transform( btn
->pnode
, mtx
);
809 m4x3_mul( menu_mdl_mtx
, mtx
, mtx
);
810 m4x3_identity( mtx_size
);
811 m4x3_scale( mtx_size
, expSustainedImpulse( btn
->fsize
, 0.5f
, 8.7f
) );
812 m4x3_mul( mtx
, mtx_size
, mtx
);
813 shader_menu_uMdl( mtx
);
815 for( int j
=0; j
<btn
->pnode
->submesh_count
; j
++ )
818 &menu_model
.submesh_buffer
[ btn
->pnode
->submesh_start
+j
];
819 mdl_draw_submesh( sm
);
824 for( int i=0; i<menu_model->node_count; i++ )
826 mdl_node *pnode = mdl_node_from_id( menu_model, i );
828 for( int j=0; j<pnode->submesh_count; j++ )
831 mdl_submesh_from_id( menu_model, pnode->submesh_start+j );
833 mdl_node_transform( pnode, mtx );
834 m4x3_mul( menu_mdl_mtx, mtx, mtx );
835 shader_menu_uMdl( mtx );
837 mdl_draw_submesh( sm );