#include maps\mp\_utility; #include common_scripts\utility; CONST_default_radial_radius = 8; // default distance between radial button and center CONST_radial_center_extrude_dist = 40; // this is the distance between the floating radial center and the oberver's eye CONST_direct_travel = 1; // no path, directly zoom to position CONST_view_travel_unit_dist = 1200; // distance unit per travel interval CONST_view_travel_unit_time = 1; // in seconds per travel interval CONST_blur_strength = 3; // blur strength during view travel, sine curved over travel duration init() { precacheShellShock( "frag_grenade_mp" ); radial_button_definitions(); // define radial button sets and buttons radial_init(); // setup radial button mechanism view_path_setup(); // setup view flight paths player_init(); } // ==================================================================================== // == inits == // ==================================================================================== radial_button_definitions() { newRadialButtonGroup( "main", "player_view1_start", "player_view1_end" ); // Main menu's buttons: bWeapons_a = newRadialButton( "main", "Primary Weapon", "radial_weapons_primary", ::action_weapons_primary ); bWeapons_b = newRadialButton( "main", "Secondary Weapon", "radial_weapons_secondary", ::action_weapons_secondary ); bGears = newRadialButton( "main", "Gears", "radial_gears", ::action_gears ); bKillStreaks= newRadialButton( "main", "Kill Streaks", "radial_killstreaks", ::action_killstreak ); bLeadboards = newRadialButton( "main", "Leaderboards", "radial_leaderboards", ::action_leaderboards ); // newRadialButtonGroup( "gears", "player_view2_start", "player_view2_end" ); newRadialButtonGroup( "weapons_primary", "player_view3_start", "player_view3_end" ); newRadialButtonGroup( "weapons_secondary", "player_view3_start", "player_view3_end" ); newRadialButtonGroup( "killstreak", "player_view4_start", "player_view4_end" ); newRadialButtonGroup( "leaderboards", "player_view5_start", "player_view5_end" ); } radial_init() { // calculate start & end angles of all buttons for range selection foreach ( button_group in level.radial_button_group ) { // sort buttons by angle so we can calculate mid angles in sequence sort_buttons_by_angle( button_group ); for ( i = 0; i < button_group.size; i ++ ) { if ( isdefined( button_group[ i + 1 ] ) ) { mid_angle = getMidAngle( button_group[ i ].pos_angle, button_group[ i + 1 ].pos_angle ); button_group[ i ].end_angle = mid_angle; button_group[ i + 1 ].start_angle = mid_angle; } else { mid_angle = getMidAngle( button_group[ i ].pos_angle, button_group[ 0 ].pos_angle ) + 180; // +180 to mirror angle if ( mid_angle > 360 ) mid_angle -= 360; button_group[ i ].end_angle = mid_angle; button_group[ 0 ].start_angle = mid_angle; } } } // monitors thread updateSelectedButton(); thread watchSelectButtonPress(); thread watchBackButtonPress(); thread debug_toggle(); } debug_toggle() { level endon( "game_ended" ); level.crib_debug = 1; while ( 1 ) { if ( !isdefined( level.observer ) ) { wait 0.05; continue; } button_reset = true; while ( !( level.observer buttonPressed( "BUTTON_Y" ) ) ) wait 0.05; level.observer playsound("mouse_click"); if ( button_reset ) { level.crib_debug *= -1; button_reset = false; } while ( level.observer buttonPressed( "BUTTON_Y" ) ) wait 0.05; } } player_init() { level thread onPlayerConnect(); level thread return_hud(); } return_hud() { level waittill( "game_ended" ); setdvar( "cg_draw2d", 1 ); } onPlayerConnect() { level waittill("connected", player); player thread readyPlayer(); player waittill( "spawned_player" ); wait 1; player takeallweapons(); setdvar( "cg_draw2d", 0 ); if ( !isdefined( player ) ) return; else level.observer = player; player thread get_right_stick_angle(); zoom_to_radial_menu( "main" ); // fly to the first radial menu } readyPlayer() { self endon( "disconnect" ); team = "autoassign"; while(!isdefined(self.pers["team"])) wait .05; self notify("menuresponse", game["menu_team"], team); wait 0.5; classes = getArrayKeys( level.classMap ); okclasses = []; for ( i = 0; i < classes.size; i++ ) { if ( !isSubStr( classes[i], "custom" ) ) okclasses[ okclasses.size ] = classes[i]; } assert( okclasses.size ); while( 1 ) { class = okclasses[ 0 ]; self notify("menuresponse", "changeclass", class); self waittill( "spawned_player" ); wait ( 0.10 ); } } // ==================================================================================== // == Radial Mechanics == // ==================================================================================== get_right_stick_angle() { // self is user level endon( "game_ended" ); self endon( "disconnect" ); while ( 1 ) { rs_vec = self GetNormalizedMovement(); rs_angles = vectortoangles( rs_vec ); level.rs_angle = int( rs_angles[1] ); wait 0.05; // update rate } } newRadialButtonGroup( group_name, view_start, view_end ) { if ( isdefined( level.radial_button_group ) && level.radial_button_group.size ) assertex( !isdefined( level.radial_button_group[ group_name ] ), "Radial button group: " + group_name + " is already defined." ); player_view_ent = getent( view_end, "targetname" ); assertex( isdefined( player_view_ent ), "Missing player view entity, can not setup radial menu in space" ); extruded_vec = vector_multiply( VectorNormalize( AnglesToForward( player_view_ent.angles ) ), CONST_radial_center_extrude_dist ); level.radial_button_group[ group_name ] = []; level.radial_button_group_info[ group_name ][ "view_start" ] = view_start; level.radial_button_group_info[ group_name ][ "view_pos" ] = player_view_ent.origin + extruded_vec; level.radial_button_group_info[ group_name ][ "player_view_pos" ] = player_view_ent.origin; level.radial_button_group_info[ group_name ][ "view_angles" ] = player_view_ent.angles; } newRadialButton( button_group, button_label, button_ent_name, action_func ) { assertex( isdefined( level.radial_button_group[ button_group ] ), "Radial button group: " + button_group + " does not exist." ); ent = getent( button_ent_name, "targetname" ); new_button_angle = getRadialAngleFromEnt( button_group, ent ); button = spawnstruct(); button.pos = ent.origin; button.label = button_label; button.font_size = 1; button.font_color = ( 0.5, 0.5, 1 ); button.pos_angle = new_button_angle; button.action_func = action_func; button.radius_pos = CONST_default_radial_radius; level.radial_button_group[ button_group ][ level.radial_button_group[ button_group ].size ] = button; return button; } updateSelectedButton() { level endon( "game_ended" ); while ( 1 ) { if ( !isdefined( level.radial_button_current_group ) ) { wait 0.05; continue; } last_active_button = level.active_button; foreach ( button in level.radial_button_group[ level.radial_button_current_group ] ) { if ( isInRange( button.start_angle, button.end_angle ) ) level.active_button = button; else button.font_color = ( 0.5, 0.5, 1 ); } if ( isdefined ( level.active_button ) ) { level.active_button.font_color = ( 1, 1, 0.5 ); if ( isdefined( last_active_button ) && last_active_button != level.active_button ) level.observer playsound("mouse_over"); } wait 0.05; } } watchSelectButtonPress() { level endon( "game_ended" ); while ( 1 ) { if ( !isdefined( level.observer ) ) { wait 0.05; continue; } button_reset = true; while ( !( level.observer buttonPressed( "BUTTON_A" ) ) ) wait 0.05; level.observer playsound("mouse_click"); if ( isdefined( level.active_button ) && button_reset ) { level.active_button notify( "select_button_pressed" ); [[level.active_button.action_func]](); button_reset = false; } while ( level.observer buttonPressed( "BUTTON_A" ) ) wait 0.05; } } watchBackButtonPress() { level endon( "game_ended" ); while ( 1 ) { if ( !isdefined( level.observer ) ) { wait 0.05; continue; } button_reset = true; while ( !( level.observer buttonPressed( "BUTTON_X" ) ) ) wait 0.05; level.observer playsound("mouse_click"); if ( button_reset ) { action_back(); button_reset = false; } while ( level.observer buttonPressed( "BUTTON_X" ) ) wait 0.05; } } sort_buttons_by_angle( button_group ) { // button_group is actual array // bubble sort buttons for ( i = 0; i < button_group.size - 1; i++ ) { for ( j = 0; j < button_group.size - 1 - i; j++ ) { if ( button_group[ j + 1 ].pos_angle < button_group[ j ].pos_angle ) button_switch( button_group[ j ], button_group[ j + 1 ] ); } } } button_switch( button1, button2 ) { temp_pos = button1.pos; temp_label = button1.label; temp_pos_angle = button1.pos_angle; temp_action_func = button1.action_func; temp_radius_pos = button1.radius_pos; button1.pos = button2.pos; button1.label = button2.label; button1.pos_angle = button2.pos_angle; button1.action_func = button2.action_func; button1.radius_pos = button2.radius_pos; button2.pos = temp_pos; button2.label = temp_label; button2.pos_angle = temp_pos_angle; button2.action_func = temp_action_func; button2.radius_pos = temp_radius_pos; } draw_radial_buttons( button_group ) { foreach ( button in level.radial_button_group[ button_group ] ) button thread draw_radial_button( button_group ); } //print3d(, , , , , ) draw_radial_button( button_group ) { level endon( "game_ended" ); self endon( "remove_button" ); floating_origin = level.radial_button_group_info[ button_group ][ "view_pos" ]; button_radial_pos = floating_origin + radial_angle_to_vector( self.pos_angle, 4 ); while ( 1 ) { //line( level.radial_button_group_info[ button_group ][ "view_pos" ], self.pos, ( 0, 1, 0 ), 0.05, false ); range_color = ( 1, 0, 0 ); if ( isInRange( self.start_angle, self.end_angle ) ) range_color = ( 1, 1, 0 ); print3d( self.pos, self.label, self.font_color, 0.75, self.font_size, 1 ); if ( isdefined( level.crib_debug ) && level.crib_debug > 0 ) { print3d( button_radial_pos, ".("+int(self.pos_angle)+")", range_color, 0.75, 0.05, 1 ); line( floating_origin, floating_origin + radial_angle_to_vector( self.start_angle, 2 ), range_color, 0.05 ); line( floating_origin + radial_angle_to_vector( self.start_angle, 2 ), floating_origin + radial_angle_to_vector( self.end_angle, 2 ), range_color, 0.05 ); // right stick debug ling r_radial_pos = floating_origin + radial_angle_to_vector( level.rs_angle, 2 ); line( floating_origin, r_radial_pos, ( 1, 1, 1 ), 0.05 ); } print3d( floating_origin - ( 0, 0, 4.5 ), "(A)=Select (X)=Back", (1, 1, 1), 0.5, 0.05, 1 ); wait 0.05; } } Zoom_To_Radial_Menu( button_group, reverse ) { level.active_button = undefined; assertex( isdefined( level.observer ), "Missing observer (connected player), can not attach player to view path" ); if ( isdefined( level.radial_button_current_group ) && level.radial_button_current_group != "" ) { level.radial_button_previous_group = level.radial_button_current_group; } else { level.radial_button_previous_group = "main"; level.radial_button_current_group = "main"; } foreach ( button in level.radial_button_group[ level.radial_button_previous_group ] ) button notify( "remove_button" ); //iPrintLnBold( "flying to: " + button_group ); if ( isdefined( reverse ) && reverse ) level.observer go_path_by_targetname_reverse( level.radial_button_group_info[ level.radial_button_previous_group ][ "view_start" ], button_group ); else level.observer go_path_by_targetname( level.radial_button_group_info[ button_group ][ "view_start" ] ); level thread draw_radial_buttons( button_group ); level.radial_button_current_group = button_group; } // ==================================================================================== // == Radial menu - math == // ==================================================================================== // edit function with care, returns orientation-sensistive angles getRadialAngleFromEnt( button_group, ent ) { assertex( isdefined( level.radial_button_group[ button_group ] ), "getRadialAngleFromEnt: Radial button group does not exist." ); assertex( isdefined( ent ), "getRadialAngleFromEnt: Missing entity to be measured." ); rAngle = level.radial_button_group_info[ button_group ][ "view_angles" ]; rPos = level.radial_button_group_info[ button_group ][ "view_pos" ]; rPos += vector_multiply( VectorNormalize( AnglesToForward( rAngle ) ), CONST_radial_center_extrude_dist ); rForward = AnglesToForward( rAngle ); rUpwardNorm = VectorNormalize( AnglesToUp( rAngle ) ); eAngle = ent.angles; ePos = ent.origin; projNorm = VectorNormalize( VectorFromLineToPoint( rPos, ( rPos + rForward ), ePos ) ); radial_angle = Acos( VectorDot( projNorm, rUpwardNorm ) ); // vector mirroring if ( VectorDot( AnglesToRight( rAngle ), projNorm ) < 0 ) radial_angle = 360 - radial_angle; return radial_angle; } // converts projected angle into player's view plane into a vector radial_angle_to_vector( angle, scaler ) { b_angles = ( 270 - ( angle ), 0 , 0 ); // 270 degrees offset to face the player b_vec = AnglesToForward( b_angles ); b_vec_norm = VectorNormalize( b_vec ); b_vec_final = vector_multiply( b_vec_norm, scaler ); return b_vec_final; } getMidAngle( a1, a2 ) { // 0 -> 360 domain mid_angle = ( ( a1 + a2 + 720 ) / 2 ) - 360; return mid_angle; } isInRange( start_angle, end_angle ) { inside_big_angle = ( level.rs_angle > start_angle && level.rs_angle < 360 ); inside_small_angle = ( level.rs_angle > 0 && level.rs_angle < end_angle ); if ( start_angle > end_angle ) in_range = ( inside_big_angle || inside_small_angle ); else in_range = ( level.rs_angle > start_angle && level.rs_angle < end_angle ); return in_range; } // ==================================================================================== // == Button action functions == // ==================================================================================== // close radial buttons action_back() { //if ( isdefined( level.radial_button_previous_group ) && level.radial_button_previous_group != "" ) // zoom_to_radial_menu( level.radial_button_previous_group ); /*else*/ if ( isdefined( level.radial_button_current_group ) && level.radial_button_current_group != "main" ) zoom_to_radial_menu( "main", true ); else return; } // ==== main ==== action_weapons_primary() { iPrintLnBold( "action_weapons_primary" ); zoom_to_radial_menu( "weapons_primary" ); } action_weapons_secondary() { iPrintLnBold( "action_weapons_secondary" ); zoom_to_radial_menu( "weapons_secondary" ); } action_gears() { iPrintLnBold( "action_gears" ); zoom_to_radial_menu( "gears" ); } action_killstreak() { iPrintLnBold( "action_killstreak" ); zoom_to_radial_menu( "killstreak" ); } action_leaderboards() { iPrintLnBold( "action_leaderboards" ); zoom_to_radial_menu( "leaderboards" ); } // ==================================================================================== // == Pathing functions == // ==================================================================================== view_path_setup() { // setup all paths level.view_paths = []; // build paths build_path_by_targetname( "player_view1_start" ); build_path_by_targetname( "player_view2_start" ); build_path_by_targetname( "player_view3_start" ); build_path_by_targetname( "player_view4_start" ); build_path_by_targetname( "player_view5_start" ); } build_path_by_targetname( path_name ) { level.view_paths[ path_name ] = []; path_node = getent( path_name, "targetname" ); level.view_paths[ path_name ][ level.view_paths[ path_name ].size ] = path_node; while( isdefined( path_node ) && isdefined( path_node.target ) ) { next_node = getent( path_node.target, "targetname" ); level.view_paths[ path_name ][ level.view_paths[ path_name ].size ] = next_node; path_node = next_node; } } go_path_by_targetname( path_name ) { // self is player if ( !isdefined( level.dummy_mover ) ) { start_node = level.view_paths[ path_name ][ 0 ]; level.dummy_mover = spawn( "script_model", start_node.origin ); level.dummy_mover.angles = start_node.angles; //self AllowedStances( "stand" ); self setOrigin( level.dummy_mover.origin - ( 0, 0, 65 ) ); self linkTo( level.dummy_mover ); wait 0.05; self setplayerangles ( level.dummy_mover.angles ); self thread force_player_angles(); } /* travel_time = 1; dist = 0; foreach ( idx, node in level.view_paths[ path_name ] ) { if ( isdefined( level.view_paths[ path_name ][ idx + 1 ] ) ) dist += abs( distance( level.view_paths[ path_name ][ idx ].origin, level.view_paths[ path_name ][ idx + 1 ].origin ) ); }*/ travel_speed = CONST_view_travel_unit_time; total_distance = abs( distance( level.dummy_mover.origin, level.view_paths[ path_name ][ level.view_paths[ path_name ].size - 1 ].origin ) ); travel_speed *= total_distance / CONST_view_travel_unit_dist; travel_speed = max( travel_speed, 0.1 ); // due to repeated button presses, the travel distance can be cut to 0 travel speed at times. blur_time = travel_speed; if ( !CONST_direct_travel ) blur_time *= travel_speed * ( level.view_paths[ path_name ].size + 1 ); self thread blur_sine( CONST_blur_strength, blur_time ); foreach ( idx, node in level.view_paths[ path_name ] ) { //travel_speed = travel_time * ( abs( distance( level.dummy_mover.origin, node.origin ) ) / dist ); //travel_speed += 0.05; if ( CONST_direct_travel ) { if ( idx != level.view_paths[ path_name ].size - 1 ) continue; } //level.dummy_mover MoveTo( node.origin, travel_speed ); level.dummy_mover MoveTo( node.origin, travel_speed, travel_speed * 0.5, 0 ); level.dummy_mover RotateTo( node.angles, travel_speed, travel_speed * 0.5, 0); wait travel_speed; } } go_path_by_targetname_reverse( path_name, back_to_button_group ) { assertex( isdefined( level.dummy_mover ), "go_path_by_targetname_reverse called before go_path_by_targetname" ); travel_speed = CONST_view_travel_unit_time; total_distance = abs( distance( level.dummy_mover.origin, level.radial_button_group_info[ back_to_button_group ][ "player_view_pos" ] ) ); travel_speed *= total_distance / CONST_view_travel_unit_dist; travel_speed = max( travel_speed, 0.1 ); // due to repeated button presses, the travel distance can be cut to 0 travel speed at times. blur_time = travel_speed; if ( !CONST_direct_travel ) blur_time *= travel_speed * ( level.view_paths[ path_name ].size + 1 ); self thread blur_sine( CONST_blur_strength, blur_time ); if ( !CONST_direct_travel ) { for ( idx = level.view_paths[ path_name ].size - 1; idx >= 0; idx-- ) { node = level.view_paths[ path_name ][ idx ]; level.dummy_mover MoveTo( node.origin, travel_speed ); level.dummy_mover RotateTo( node.angles, travel_speed ); //self thread travel_view_fx( travel_speed ); wait travel_speed; } } self thread blur_sine( CONST_blur_strength, travel_speed ); pos = level.radial_button_group_info[ back_to_button_group ][ "player_view_pos" ]; angle = level.radial_button_group_info[ back_to_button_group ][ "view_angles" ]; level.dummy_mover MoveTo( pos, travel_speed, travel_speed * 0.5, 0 ); level.dummy_mover RotateTo( angle, travel_speed, travel_speed * 0.5, 0 ); wait travel_speed; } travel_view_fx( time ) { self setblurforplayer( 20, ( time + 0.2 )/2 ); self setblurforplayer( 0, ( time + 0.2 )/2 ); self shellshock( "frag_grenade_mp", time + 0.2 ); } blur_sine( strength, time ) { time_scaled = int( time/0.05 ); for( i = 0; i < time_scaled; i ++ ) { fraction = ( i / ( time_scaled ) ); cos_fraction= sin( 180 * fraction ); blur_amount = strength * cos_fraction; setdvar( "r_blur", blur_amount ); wait 0.05; } setdvar( "r_blur", 0 ); } force_player_angles() { level endon( "game_ended" ); self endon( "disconnect" ); level.dummy_mover endon( "remove_dummy" ); while ( 1 ) { self setplayerangles ( level.dummy_mover.angles ); wait 0.05; } }