]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/client/View.qc
Use vec3 and get rid of FL2VEC -- This function didn't exist when I first
[xonotic/xonotic-data.pk3dir.git] / qcsrc / client / View.qc
index a0233e5eaab36e5095eefb3f530eab631f81db41..d58704803a78808625a1795f8f92ad6137fc0dca 100644 (file)
@@ -1,10 +1,5 @@
 entity porto;
 vector polyline[16];
-float Q3SURFACEFLAG_SLICK = 2; // low friction surface
-float DPCONTENTS_SOLID = 1; // blocks player movement
-float DPCONTENTS_BODY = 32; // blocks player movement
-float DPCONTENTS_CORPSE = 64; // blocks player movement
-float DPCONTENTS_PLAYERCLIP = 256; // blocks player movement
 void Porto_Draw()
 {
        vector p, dir, ang, q, nextdir;
@@ -132,8 +127,8 @@ vector GetCurrentFov(float fov)
        else if(autocvar_cl_spawnzoom && zoomin_effect)
        {
                float spawnzoomfactor = bound(1, autocvar_cl_spawnzoom_factor, 16);
-               
-               current_viewzoom += (autocvar_cl_spawnzoom_speed * (spawnzoomfactor - current_viewzoom) * drawframetime); 
+
+               current_viewzoom += (autocvar_cl_spawnzoom_speed * (spawnzoomfactor - current_viewzoom) * drawframetime);
                current_viewzoom = bound(1 / spawnzoomfactor, current_viewzoom, 1);
                if(current_viewzoom == 1) { zoomin_effect = 0; }
        }
@@ -166,26 +161,30 @@ vector GetCurrentFov(float fov)
                setsensitivityscale(pow(current_viewzoom, 1 - zoomsensitivity));
        else
                setsensitivityscale(1);
-               
+
        makevectors(view_angles);
 
        if(autocvar_cl_velocityzoom && autocvar_cl_velocityzoom_type) // _type = 0 disables velocity zoom too
        {
-               v = pmove_vel;
-               if(csqcplayer)
-                       v = csqcplayer.velocity;
-
-               switch(autocvar_cl_velocityzoom_type)
+               if(intermission) { curspeed = 0; }
+               else
                {
-                       case 3: curspeed = max(0, v_forward * v); break;
-                       case 2: curspeed = (v_forward * v); break;
-                       case 1: default: curspeed = vlen(v); break;
+                       v = pmove_vel;
+                       if(csqcplayer)
+                               v = csqcplayer.velocity;
+
+                       switch(autocvar_cl_velocityzoom_type)
+                       {
+                               case 3: curspeed = max(0, v_forward * v); break;
+                               case 2: curspeed = (v_forward * v); break;
+                               case 1: default: curspeed = vlen(v); break;
+                       }
                }
-               
+
                velocityzoom = bound(0, drawframetime / max(0.000000001, autocvar_cl_velocityzoom_time), 1); // speed at which the zoom adapts to player velocity
                avgspeed = avgspeed * (1 - velocityzoom) + (curspeed / autocvar_cl_velocityzoom_speed) * velocityzoom;
                velocityzoom = exp(float2range11(avgspeed * -autocvar_cl_velocityzoom / 1) * 1);
-               
+
                //print(ftos(avgspeed), " avgspeed, ", ftos(curspeed), " curspeed, ", ftos(velocityzoom), " return\n"); // for debugging
        }
        else
@@ -200,6 +199,17 @@ vector GetCurrentFov(float fov)
        return '1 0 0' * fovx + '0 1 0' * fovy;
 }
 
+vector GetOrthoviewFOV(vector ov_worldmin, vector ov_worldmax, vector ov_mid, vector ov_org)
+{
+       float fovx, fovy;
+       float width = (ov_worldmax_x - ov_worldmin_x);
+       float height = (ov_worldmax_y - ov_worldmin_y);
+       float distance_to_middle_of_world = vlen(ov_mid - ov_org);
+       fovx = atan2(width/2, distance_to_middle_of_world) / M_PI * 360.0;
+       fovy = atan2(height/2, distance_to_middle_of_world) / M_PI * 360.0;
+       return '1 0 0' * fovx + '0 1 0' * fovy;
+}
+
 // this function must match W_SetupShot!
 float zoomscript_caught;
 
@@ -214,6 +224,9 @@ string wcross_name_goal_prev, wcross_name_goal_prev_prev;
 float wcross_resolution_goal_prev, wcross_resolution_goal_prev_prev;
 float wcross_name_changestarttime, wcross_name_changedonetime;
 float wcross_name_alpha_goal_prev, wcross_name_alpha_goal_prev_prev;
+
+float wcross_ring_prev;
+
 entity trueaim;
 entity trueaim_rifle;
 
@@ -347,8 +360,8 @@ void PostInit(void);
 void CSQC_Demo_Camera();
 float HUD_WouldDrawScoreboard();
 float camera_mode;
-float CAMERA_FREE = 1;
-float CAMERA_CHASE = 2;
+const float CAMERA_FREE = 1;
+const float CAMERA_CHASE = 2;
 float reticle_type;
 string NextFrameCommand;
 void CSQC_SPIDER_HUD();
@@ -381,9 +394,13 @@ vector damage_blurpostprocess, content_blurpostprocess;
 
 float checkfail[16];
 
+float rainbow_last_flicker;
+vector rainbow_prev_color;
+
 #define BUTTON_3 4
 #define BUTTON_4 8
 float cl_notice_run();
+float prev_myteam;
 void CSQC_UpdateView(float w, float h)
 {
        entity e;
@@ -414,13 +431,6 @@ void CSQC_UpdateView(float w, float h)
        button_attack2 = (input_buttons & BUTTON_3);
        button_zoom = (input_buttons & BUTTON_4);
 
-       // FIXME do we need this hack?
-       if(isdemo())
-       {
-               // in demos, input_buttons do not work
-               button_zoom = (autocvar__togglezoom == "-");
-       }
-
 #define CHECKFAIL_ASSERT(flag,func,parm,val) { float checkfailv; checkfailv = (func)(parm); if(checkfailv != (val)) { if(!checkfail[(flag)]) localcmd(sprintf("\ncmd checkfail %s %s %d %d\n", #func, parm, val, checkfailv)); checkfail[(flag)] = 1; } } ENDS_WITH_CURLY_BRACE
        CHECKFAIL_ASSERT(0, cvar_type, "\{100}\{105}\{118}\{48}\{95}\{101}\{118}\{97}\{100}\{101}", 0);
        CHECKFAIL_ASSERT(1, cvar_type, "\{97}\{97}\{95}\{101}\{110}\{97}\{98}\{108}\{101}", 0);
@@ -449,15 +459,49 @@ void CSQC_UpdateView(float w, float h)
 #endif
                myteam = GetPlayerColor(player_localentnum - 1);
 
+       if(myteam != prev_myteam)
+       {
+               myteamcolors = colormapPaletteColor(myteam, 1);
+               for(i = 0; i < HUD_PANEL_NUM; ++i)
+                       hud_panel[i].update_time = time;
+               prev_myteam = myteam;
+       }
+
        ticrate = getstatf(STAT_MOVEVARS_TICRATE) * getstatf(STAT_MOVEVARS_TIMESCALE);
 
+       float is_dead = (getstati(STAT_HEALTH) <= 0);
+
+       // FIXME do we need this hack?
+       if(isdemo())
+       {
+               // in demos, input_buttons do not work
+               button_zoom = (autocvar__togglezoom == "-");
+       }
+       else if(button_zoom
+               && autocvar_cl_unpress_zoom_on_death
+               && (spectatee_status >= 0)
+               && (is_dead || intermission))
+       {
+               // no zoom while dead or in intermission please
+               localcmd("-zoom\n");
+               button_zoom = FALSE;
+       }
+
        // event chase camera
        if(autocvar_chase_active <= 0) // greater than 0 means it's enabled manually, and this code is skipped
        {
-               if(spectatee_status >= 0 && (autocvar_cl_eventchase_death && getstati(STAT_HEALTH) <= 0 && !intermission) || intermission)
+               if(((spectatee_status >= 0 && (autocvar_cl_eventchase_death && is_dead)) || intermission) && !autocvar_cl_orthoview)
                {
                        // make special vector since we can't use view_origin (It is one frame old as of this code, it gets set later with the results this code makes.)
-                       vector current_view_origin = ((csqcplayer ? csqcplayer.origin : pmove_org) + autocvar_cl_eventchase_viewoffset);
+                       vector current_view_origin = (csqcplayer ? csqcplayer.origin : pmove_org);
+
+                       // detect maximum viewoffset and use it
+                       if(autocvar_cl_eventchase_viewoffset)
+                       {
+                               WarpZone_TraceLine(current_view_origin, current_view_origin + autocvar_cl_eventchase_viewoffset + ('0 0 1' * autocvar_cl_eventchase_maxs_z), MOVE_WORLDONLY, self);
+                               if(trace_fraction == 1) { current_view_origin += autocvar_cl_eventchase_viewoffset; }
+                               else { current_view_origin_z += max(0, (trace_endpos_z - current_view_origin_z) - autocvar_cl_eventchase_maxs_z); }
+                       }
 
                        // We must enable chase_active to get a third person view (weapon viewmodel hidden and own player model showing).
                        // Ideally, there should be another way to enable third person cameras, such as through setproperty()
@@ -515,6 +559,60 @@ void CSQC_UpdateView(float w, float h)
        WarpZone_FixView();
        //WarpZone_FixPMove();
 
+       vector ov_org = '0 0 0';
+       vector ov_mid = '0 0 0';
+       vector ov_worldmin = '0 0 0';
+       vector ov_worldmax = '0 0 0';
+       if(autocvar_cl_orthoview)
+       {
+               ov_worldmin = mi_picmin;
+               ov_worldmax = mi_picmax;
+
+               float ov_width = (ov_worldmax_x - ov_worldmin_x);
+               float ov_height = (ov_worldmax_y - ov_worldmin_y);
+               float ov_distance = (autocvar_cl_orthoview_resolution * max(ov_width, ov_height));
+
+               ov_mid = ((ov_worldmax + ov_worldmin) * 0.5);
+               ov_org = vec3(ov_mid_x, ov_mid_y, (ov_mid_z + ov_distance));
+
+               float ov_nearest = vlen(ov_org - vec3(
+                       bound(ov_worldmin_x, ov_org_x, ov_worldmax_x),
+                       bound(ov_worldmin_y, ov_org_y, ov_worldmax_y),
+                       bound(ov_worldmin_z, ov_org_z, ov_worldmax_z)
+               ));
+
+               float ov_furthest = 0;
+               float dist = 0;
+
+               if((dist = vlen(vec3(ov_worldmin_x, ov_worldmin_y, ov_worldmin_z) - ov_org)) > ov_furthest) { ov_furthest = dist; }
+               if((dist = vlen(vec3(ov_worldmax_x, ov_worldmin_y, ov_worldmin_z) - ov_org)) > ov_furthest) { ov_furthest = dist; }
+               if((dist = vlen(vec3(ov_worldmin_x, ov_worldmax_y, ov_worldmin_z) - ov_org)) > ov_furthest) { ov_furthest = dist; }
+               if((dist = vlen(vec3(ov_worldmin_x, ov_worldmin_y, ov_worldmax_z) - ov_org)) > ov_furthest) { ov_furthest = dist; }
+               if((dist = vlen(vec3(ov_worldmax_x, ov_worldmax_y, ov_worldmin_z) - ov_org)) > ov_furthest) { ov_furthest = dist; }
+               if((dist = vlen(vec3(ov_worldmin_x, ov_worldmax_y, ov_worldmax_z) - ov_org)) > ov_furthest) { ov_furthest = dist; }
+               if((dist = vlen(vec3(ov_worldmax_x, ov_worldmin_y, ov_worldmax_z) - ov_org)) > ov_furthest) { ov_furthest = dist; }
+               if((dist = vlen(vec3(ov_worldmax_x, ov_worldmax_y, ov_worldmax_z) - ov_org)) > ov_furthest) { ov_furthest = dist; }
+
+               cvar_set("r_nearclip", ftos(ov_nearest));
+               cvar_set("r_farclip_base", ftos(ov_furthest));
+               cvar_set("r_farclip_world", "0");
+               cvar_set("r_novis", "1");
+               cvar_set("r_useportalculling", "0");
+               cvar_set("r_useinfinitefarclip", "0");
+
+               setproperty(VF_ORIGIN, ov_org);
+               setproperty(VF_ANGLES, '90 0 0');
+
+               #if 0
+               print(sprintf("OrthoView: org = %s, angles = %s, distance = %f, nearest = %f, furthest = %f\n",
+                       vtos(ov_org),
+                       vtos(getpropertyvec(VF_ANGLES)),
+                       ov_distance,
+                       ov_nearest,
+                       ov_furthest));
+               #endif
+       }
+
        // Render the Scene
        view_origin = getpropertyvec(VF_ORIGIN);
        view_angles = getpropertyvec(VF_ANGLES);
@@ -604,11 +702,24 @@ void CSQC_UpdateView(float w, float h)
                HUD_InitScores();
        }
 
-       if(last_switchweapon != switchweapon) {
+       if(last_switchweapon != switchweapon)
+       {
                weapontime = time;
                last_switchweapon = switchweapon;
+               if(button_zoom && autocvar_cl_unpress_zoom_on_weapon_switch)
+               {
+                       localcmd("-zoom\n");
+                       button_zoom = FALSE;
+               }
+               if(autocvar_cl_unpress_attack_on_weapon_switch)
+               {
+                       localcmd("-fire\n");
+                       localcmd("-fire2\n");
+                       button_attack2 = FALSE;
+               }
        }
-       if(last_activeweapon != activeweapon) {
+       if(last_activeweapon != activeweapon)
+       {
                last_activeweapon = activeweapon;
 
                e = get_weaponinfo(activeweapon);
@@ -643,7 +754,8 @@ void CSQC_UpdateView(float w, float h)
        vid_conheight = autocvar_vid_conheight;
        vid_pixelheight = autocvar_vid_pixelheight;
 
-       setproperty(VF_FOV, GetCurrentFov(fov));
+       if(autocvar_cl_orthoview) { setproperty(VF_FOV, GetOrthoviewFOV(ov_worldmin, ov_worldmax, ov_mid, ov_org)); }
+       else { setproperty(VF_FOV, GetCurrentFov(fov)); }
 
        // Camera for demo playback
        if(camera_active)
@@ -699,7 +811,7 @@ void CSQC_UpdateView(float w, float h)
        drawstring('0 0 0', "", '1 1 0', '1 1 1', 0, 0);
 
        if(autocvar_r_fakelight >= 2 || autocvar_r_fullbright)
-       if not(serverflags & SERVERFLAG_ALLOW_FULLBRIGHT)
+       if (!(serverflags & SERVERFLAG_ALLOW_FULLBRIGHT))
        {
                // apply night vision effect
                vector tc_00, tc_01, tc_10, tc_11;
@@ -751,20 +863,20 @@ void CSQC_UpdateView(float w, float h)
                R_PolygonVertex(autocvar_vid_conheight * '0 1 0', tc_01, rgb, a);
                R_EndPolygon();
        }
-        
+
        // Draw the aiming reticle for weapons that use it
        // reticle_type is changed to the item we are zooming / aiming with, to decide which reticle to use
        // It must be a persisted float for fading out to work properly (you let go of the zoom button for
        // the view to go back to normal, so reticle_type would become 0 as we fade out)
-       if(spectatee_status || getstati(STAT_HEALTH) <= 0 || hud != HUD_NORMAL)
+       if(spectatee_status || is_dead || hud != HUD_NORMAL)
                reticle_type = 0; // prevent reticle from showing during the respawn zoom effect or for spectators
-       else if(activeweapon == WEP_NEX && (button_zoom || zoomscript_caught) || activeweapon == WEP_RIFLE && (button_zoom || zoomscript_caught) || activeweapon == WEP_MINSTANEX && (button_zoom || zoomscript_caught))
+       else if((activeweapon == WEP_NEX || activeweapon == WEP_RIFLE || activeweapon == WEP_MINSTANEX) && (button_zoom || zoomscript_caught))
                reticle_type = 2; // nex zoom
        else if(button_zoom || zoomscript_caught)
                reticle_type = 1; // normal zoom
-       else if(activeweapon == WEP_NEX && button_attack2 || activeweapon == WEP_RIFLE && button_attack2)
+       else if((activeweapon == WEP_NEX) && button_attack2)
                reticle_type = 2; // nex zoom
-    
+
        if(reticle_type && autocvar_cl_reticle)
        {
                if(autocvar_cl_reticle_stretch)
@@ -976,13 +1088,13 @@ void CSQC_UpdateView(float w, float h)
                        old_bluralpha = 0;
                }
 
-               // edge detection postprocess handling done second (used by hud_powerup) 
+               // edge detection postprocess handling done second (used by hud_powerup)
                float sharpen_intensity = 0, strength_finished = getstatf(STAT_STRENGTH_FINISHED), invincible_finished = getstatf(STAT_INVINCIBLE_FINISHED);
                if (strength_finished - time > 0) { sharpen_intensity += (strength_finished - time); }
                if (invincible_finished - time > 0) { sharpen_intensity += (invincible_finished - time); }
-               
+
                sharpen_intensity = bound(0, ((getstati(STAT_HEALTH) > 0) ? sharpen_intensity : 0), 5); // Check to see if player is alive (if not, set 0) - also bound to fade out starting at 5 seconds.
-               
+
                if(autocvar_hud_powerup && sharpen_intensity > 0)
                {
                        if(sharpen_intensity != old_sharpen_intensity) // reduce cvar_set spam as much as possible
@@ -1025,16 +1137,16 @@ void CSQC_UpdateView(float w, float h)
        if(hit_time > nextsound_hit_time && autocvar_cl_hitsound)
        {
                if(time - hit_time < MAX_TIME_DIFF) // don't play the sound if it's too old.
-                       sound(world, CH_INFO, "misc/hit.wav", VOL_BASE, ATTN_NONE);
-                       
+                       sound(world, CH_INFO, "misc/hit.wav", VOL_BASE, ATTEN_NONE);
+
                nextsound_hit_time = time + autocvar_cl_hitsound_antispam_time;
        }
        typehit_time = getstatf(STAT_TYPEHIT_TIME);
-       if(typehit_time > nextsound_typehit_time) 
+       if(typehit_time > nextsound_typehit_time)
        {
                if(time - typehit_time < MAX_TIME_DIFF) // don't play the sound if it's too old.
-                       sound(world, CH_INFO, "misc/typehit.wav", VOL_BASE, ATTN_NONE);
-                       
+                       sound(world, CH_INFO, "misc/typehit.wav", VOL_BASE, ATTEN_NONE);
+
                nextsound_typehit_time = time + autocvar_cl_hitsound_antispam_time;
        }
 
@@ -1056,11 +1168,11 @@ void CSQC_UpdateView(float w, float h)
                                CSQC_common_hud();
 
                // crosshair goes VERY LAST
-               if(!scoreboard_active && !camera_active && intermission != 2 && spectatee_status != -1 && hud == HUD_NORMAL) 
+               if(!scoreboard_active && !camera_active && intermission != 2 && spectatee_status != -1 && hud == HUD_NORMAL)
                {
-                       if not(autocvar_crosshair_enabled) // main toggle for crosshair rendering
+                       if (!autocvar_crosshair_enabled) // main toggle for crosshair rendering
                                return;
-                               
+
                        string wcross_style;
                        float wcross_alpha, wcross_resolution;
                        wcross_style = autocvar_crosshair;
@@ -1102,7 +1214,8 @@ void CSQC_UpdateView(float w, float h)
                        string wcross_wep = "", wcross_name;
                        float wcross_scale, wcross_blur;
 
-                       if (autocvar_crosshair_per_weapon || autocvar_crosshair_color_per_weapon) {
+                       if (autocvar_crosshair_per_weapon || (autocvar_crosshair_color_special == 1))
+                       {
                                e = get_weaponinfo(switchingweapon);
                                if (e && e.netname != "")
                                {
@@ -1122,56 +1235,82 @@ void CSQC_UpdateView(float w, float h)
                                        }
                                }
                        }
-                       if(wcross_wep != "" && autocvar_crosshair_color_per_weapon)
-                               wcross_color = stov(cvar_string(strcat("crosshair_", wcross_wep, "_color")));
-                       else if(autocvar_crosshair_color_by_health)
-                       {
-                               float x = getstati(STAT_HEALTH);
 
-                               //x = red
-                               //y = green
-                               //z = blue
-
-                               wcross_color_z = 0;
+                       //print(sprintf("crosshair style: %s\n", wcross_style));
+                       wcross_name = strcat("gfx/crosshair", wcross_style);
 
-                               if(x > 200)
-                               {
-                                       wcross_color_x = 0;
-                                       wcross_color_y = 1;
-                               }
-                               else if(x > 150)
-                               {
-                                       wcross_color_x = 0.4 - (x-150)*0.02 * 0.4;
-                                       wcross_color_y = 0.9 + (x-150)*0.02 * 0.1;
-                               }
-                               else if(x > 100)
-                               {
-                                       wcross_color_x = 1 - (x-100)*0.02 * 0.6;
-                                       wcross_color_y = 1 - (x-100)*0.02 * 0.1;
-                                       wcross_color_z = 1 - (x-100)*0.02;
-                               }
-                               else if(x > 50)
+                       // MAIN CROSSHAIR COLOR DECISION
+                       switch(autocvar_crosshair_color_special)
+                       {
+                               case 1: // crosshair_color_per_weapon
                                {
-                                       wcross_color_x = 1;
-                                       wcross_color_y = 1;
-                                       wcross_color_z = 0.2 + (x-50)*0.02 * 0.8;
+                                       if(wcross_wep != "")
+                                       {
+                                               wcross_color = stov(cvar_string(sprintf("crosshair_%s_color", wcross_wep)));
+                                               break;
+                                       }
+                                       else { goto normalcolor; }
                                }
-                               else if(x > 20)
+
+                               case 2: // crosshair_color_by_health
                                {
-                                       wcross_color_x = 1;
-                                       wcross_color_y = (x-20)*90/27/100;
-                                       wcross_color_z = (x-20)*90/27/100 * 0.2;
+                                       float x = getstati(STAT_HEALTH);
+
+                                       //x = red
+                                       //y = green
+                                       //z = blue
+
+                                       wcross_color_z = 0;
+
+                                       if(x > 200)
+                                       {
+                                               wcross_color_x = 0;
+                                               wcross_color_y = 1;
+                                       }
+                                       else if(x > 150)
+                                       {
+                                               wcross_color_x = 0.4 - (x-150)*0.02 * 0.4;
+                                               wcross_color_y = 0.9 + (x-150)*0.02 * 0.1;
+                                       }
+                                       else if(x > 100)
+                                       {
+                                               wcross_color_x = 1 - (x-100)*0.02 * 0.6;
+                                               wcross_color_y = 1 - (x-100)*0.02 * 0.1;
+                                               wcross_color_z = 1 - (x-100)*0.02;
+                                       }
+                                       else if(x > 50)
+                                       {
+                                               wcross_color_x = 1;
+                                               wcross_color_y = 1;
+                                               wcross_color_z = 0.2 + (x-50)*0.02 * 0.8;
+                                       }
+                                       else if(x > 20)
+                                       {
+                                               wcross_color_x = 1;
+                                               wcross_color_y = (x-20)*90/27/100;
+                                               wcross_color_z = (x-20)*90/27/100 * 0.2;
+                                       }
+                                       else
+                                       {
+                                               wcross_color_x = 1;
+                                               wcross_color_y = 0;
+                                       }
+                                       break;
                                }
-                               else
+
+                               case 3: // crosshair_color_rainbow
                                {
-                                       wcross_color_x = 1;
-                                       wcross_color_y = 0;
+                                       if(time >= rainbow_last_flicker)
+                                       {
+                                               rainbow_prev_color = randomvec() * autocvar_crosshair_color_special_rainbow_brightness;
+                                               rainbow_last_flicker = time + autocvar_crosshair_color_special_rainbow_delay;
+                                       }
+                                       wcross_color = rainbow_prev_color;
+                                       break;
                                }
+                               :normalcolor
+                               default: { wcross_color = stov(autocvar_crosshair_color); break; }
                        }
-                       else
-                               wcross_color = stov(autocvar_crosshair_color);
-
-                       wcross_name = strcat("gfx/crosshair", wcross_style);
 
                        if(autocvar_crosshair_effect_scalefade)
                        {
@@ -1186,12 +1325,12 @@ void CSQC_UpdateView(float w, float h)
                        if(autocvar_crosshair_pickup)
                        {
                                float stat_pickup_time = getstatf(STAT_LAST_PICKUP);
-                               
+
                                if(pickup_crosshair_time < stat_pickup_time)
                                {
                                        if(time - stat_pickup_time < MAX_TIME_DIFF) // don't trigger the animation if it's too old
                                                pickup_crosshair_size = 1;
-                                               
+
                                        pickup_crosshair_time = stat_pickup_time;
                                }
 
@@ -1205,13 +1344,13 @@ void CSQC_UpdateView(float w, float h)
 
                        if(autocvar_crosshair_hitindication)
                        {
-                               vector hitindication_color = ((autocvar_crosshair_color_per_weapon) ? stov(autocvar_crosshair_hitindication_per_weapon_color) : stov(autocvar_crosshair_hitindication_color));
-                               
+                               vector hitindication_color = ((autocvar_crosshair_color_special == 1) ? stov(autocvar_crosshair_hitindication_per_weapon_color) : stov(autocvar_crosshair_hitindication_color));
+
                                if(hitindication_crosshair_time < hit_time)
                                {
                                        if(time - hit_time < MAX_TIME_DIFF) // don't trigger the animation if it's too old
                                                hitindication_crosshair_size = 1;
-                                               
+
                                        hitindication_crosshair_time = hit_time;
                                }
 
@@ -1231,9 +1370,7 @@ void CSQC_UpdateView(float w, float h)
                        if(shottype == SHOTTYPE_HITTEAM)
                                wcross_scale /= autocvar_crosshair_hittest; // is not queried if hittest is 0
 
-                       f = autocvar_crosshair_effect_speed;
-                       if(f < 0)
-                               f *= -2 * g_weaponswitchdelay; // anim starts when weapon has been lowered and new weapon comes up
+                       f = fabs(autocvar_crosshair_effect_time);
                        if(wcross_scale != wcross_scale_goal_prev || wcross_alpha != wcross_alpha_goal_prev || wcross_color != wcross_color_goal_prev)
                        {
                                wcross_changedonetime = time + f;
@@ -1308,12 +1445,12 @@ void CSQC_UpdateView(float w, float h)
                                        // handle the values
                                        if (autocvar_crosshair_ring && activeweapon == WEP_NEX && nex_charge && autocvar_crosshair_ring_nex) // ring around crosshair representing velocity-dependent damage for the nex
                                        {
-                                               if (nex_chargepool || use_nex_chargepool) { 
-                                                       use_nex_chargepool = 1; 
+                                               if (nex_chargepool || use_nex_chargepool) {
+                                                       use_nex_chargepool = 1;
                                                        ring_inner_value = nex_chargepool;
-                                               } else { 
+                                               } else {
                                                        nex_charge_movingavg = (1 - autocvar_crosshair_ring_nex_currentcharge_movingavg_rate) * nex_charge_movingavg + autocvar_crosshair_ring_nex_currentcharge_movingavg_rate * nex_charge;
-                                                       ring_inner_value = bound(0, autocvar_crosshair_ring_nex_currentcharge_scale * (nex_charge - nex_charge_movingavg), 1); 
+                                                       ring_inner_value = bound(0, autocvar_crosshair_ring_nex_currentcharge_scale * (nex_charge - nex_charge_movingavg), 1);
                                                }
 
                                                ring_inner_alpha = autocvar_crosshair_ring_nex_inner_alpha;
@@ -1326,7 +1463,7 @@ void CSQC_UpdateView(float w, float h)
                                                ring_rgb = wcross_color;
                                                ring_image = "gfx/crosshair_ring_nexgun.tga";
                                        }
-                                       else if (autocvar_crosshair_ring && activeweapon == WEP_MINE_LAYER && minelayer_maxmines && autocvar_crosshair_ring_minelayer) 
+                                       else if (autocvar_crosshair_ring && activeweapon == WEP_MINE_LAYER && minelayer_maxmines && autocvar_crosshair_ring_minelayer)
                                        {
                                                ring_value = bound(0, getstati(STAT_LAYED_MINES) / minelayer_maxmines, 1); // if you later need to use the count of bullets in another place, then add a float for it. For now, no need to.
                                                ring_alpha = autocvar_crosshair_ring_minelayer_alpha;
@@ -1341,7 +1478,7 @@ void CSQC_UpdateView(float w, float h)
                                                ring_image = "gfx/crosshair_ring.tga";
                                        }
 
-                                       if(autocvar_crosshair_ring_reload && weapon_clipsize) // forces there to be only an ammo ring 
+                                       if(autocvar_crosshair_ring_reload && weapon_clipsize) // forces there to be only an ammo ring
                                        {
                                                ring_value = bound(0, weapon_clipload / weapon_clipsize, 1);
                                                ring_scale = autocvar_crosshair_ring_reload_size;
@@ -1357,11 +1494,24 @@ void CSQC_UpdateView(float w, float h)
                                        }
 
                                        // if in weapon switch animation, fade ring out/in
-                                       if(g_weaponswitchdelay > 0)
+                                       if(autocvar_crosshair_effect_time > 0)
                                        {
-                                               f = (time - wcross_name_changestarttime) / g_weaponswitchdelay;
-                                               if(f > 0 && f < 2)
-                                                       ring_alpha *= fabs(1 - f);
+                                               f = (time - wcross_name_changestarttime) / autocvar_crosshair_effect_time;
+                                               if (!(f < 1))
+                                               {
+                                                       wcross_ring_prev = ((ring_image) ? TRUE : FALSE);
+                                               }
+
+                                               if(wcross_ring_prev)
+                                               {
+                                                       if(f < 1)
+                                                               ring_alpha *= fabs(1 - bound(0, f, 1));
+                                               }
+                                               else
+                                               {
+                                                       if(f < 1)
+                                                               ring_alpha *= bound(0, f, 1);
+                                               }
                                        }
 
                                        if (autocvar_crosshair_ring_inner && ring_inner_value) // lets draw a ring inside a ring so you can ring while you ring
@@ -1413,10 +1563,10 @@ void CSQC_UpdateView(float w, float h)
                                {
                                        vector wcross_color_old;
                                        wcross_color_old = wcross_color;
-                                       
+
                                        if((autocvar_crosshair_dot_color_custom) && (autocvar_crosshair_dot_color != "0"))
                                                wcross_color = stov(autocvar_crosshair_dot_color);
-                                               
+
                                        CROSSHAIR_DRAW(wcross_resolution * autocvar_crosshair_dot_size, "gfx/crosshairdot.tga", f * autocvar_crosshair_dot_alpha);
                                        // FIXME why don't we use wcross_alpha here?cl_notice_run();
                                        wcross_color = wcross_color_old;
@@ -1480,9 +1630,9 @@ void CSQC_UpdateView(float w, float h)
 
        if(autocvar__hud_configure)
                HUD_Panel_Mouse();
-    
+
     if(hud && !intermission)
-    {        
+    {
         if(hud == HUD_SPIDERBOT)
             CSQC_SPIDER_HUD();
         else if(hud == HUD_WAKIZASHI)
@@ -1494,9 +1644,9 @@ void CSQC_UpdateView(float w, float h)
         else if(hud == HUD_BUMBLEBEE_GUN)
             CSQC_BUMBLE_GUN_HUD();
     }
-       
+
        cl_notice_run();
-       
+
        // let's reset the view back to normal for the end
        setproperty(VF_MIN, '0 0 0');
        setproperty(VF_SIZE, '1 0 0' * w + '0 1 0' * h);