]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/client/view.qc
Merge remote-tracking branch 'origin/terencehill/bot_waypoints'
[xonotic/xonotic-data.pk3dir.git] / qcsrc / client / view.qc
index 6648b7ecbf557922991c0e7373caebefc3eef5b9..5172731ffb32017af4246591362dc5ec10b9a517 100644 (file)
@@ -1,5 +1,7 @@
 #include "view.qh"
 
+#include "autocvars.qh"
+#include "miscfunctions.qh"
 #include "announcer.qh"
 #include "hud/_mod.qh"
 #include "mapvoting.qh"
@@ -7,27 +9,32 @@
 #include "hud/panel/scoreboard.qh"
 #include "hud/panel/quickmenu.qh"
 
-#include "mutators/events.qh"
+#include <client/mutators/_mod.qh>
 
 #include <common/animdecide.qh>
+#include <common/deathtypes/all.qh>
 #include <common/ent_cs.qh>
 #include <common/anim.qh>
 #include <common/constants.qh>
 #include <common/net_linked.qh>
+#include <common/net_notice.qh>
 #include <common/debug.qh>
 #include <common/mapinfo.qh>
 #include <common/gamemodes/_mod.qh>
 #include <common/physics/player.qh>
 #include <common/stats.qh>
-#include <common/triggers/target/music.qh>
+#include <common/mapobjects/target/music.qh>
 #include <common/teams.qh>
+#include <common/wepent.qh>
 
 #include <common/weapons/weapon/tuba.qh>
 
 #include <common/vehicles/all.qh>
 #include <common/weapons/_all.qh>
-#include <common/mutators/mutator/overkill/okvortex.qh>
+#include <common/mutators/mutator/overkill/oknex.qh>
+#include <common/mutators/mutator/waypoints/all.qh>
 #include <common/viewloc.qh>
+#include <common/mapobjects/trigger/viewloc.qh>
 #include <common/minigames/cl_minigames.qh>
 #include <common/minigames/cl_minigames_hud.qh>
 
@@ -41,6 +48,7 @@
 #define EFMASK_CHEAP (EF_ADDITIVE | EF_DOUBLESIDED | EF_FULLBRIGHT | EF_NODEPTHTEST | EF_NODRAW | EF_NOSHADOW | EF_SELECTABLE | EF_TELEPORT_BIT)
 
 float autocvar_cl_viewmodel_scale;
+float autocvar_cl_viewmodel_alpha;
 
 bool autocvar_cl_bobmodel;
 float autocvar_cl_bobmodel_speed;
@@ -63,10 +71,9 @@ float autocvar_cl_leanmodel_highpass = 0.2;
 float autocvar_cl_leanmodel_lowpass = 0.05;
 
 #define avg_factor(avg_time) (1 - exp(-frametime / max(0.001, avg_time)))
-#define lowpass(value, frac, ref_store, ret) MACRO_BEGIN \
-{ \
-       ret = ref_store = ref_store * (1 - frac) + (value) * frac; \
-} MACRO_END
+
+#define lowpass(value, frac, ref_store, ret) \
+       ret = ref_store = ref_store * (1 - frac) + (value) * frac;
 
 #define lowpass_limited(value, frac, limit, ref_store, ret) MACRO_BEGIN \
 { \
@@ -291,12 +298,9 @@ void viewmodel_draw(entity this)
        if(!this.activeweapon || !autocvar_r_drawviewmodel)
                return;
        int mask = (intermission || (STAT(HEALTH) <= 0) || autocvar_chase_active) ? 0 : MASK_NORMAL;
-       float a = this.alpha;
-       static bool wasinvehicle;
+       float a = ((autocvar_cl_viewmodel_alpha) ? bound(-1, autocvar_cl_viewmodel_alpha, this.m_alpha) : this.m_alpha);
        bool invehicle = player_localentnum > maxclients;
        if (invehicle) a = -1;
-       else if (wasinvehicle) a = 1;
-       wasinvehicle = invehicle;
        Weapon wep = this.activeweapon;
        int c = entcs_GetClientColors(current_player);
        vector g = weaponentity_glowmod(wep, NULL, c, this);
@@ -313,6 +317,7 @@ void viewmodel_draw(entity this)
                e.csqcmodel_effects = fx;
                CSQCModel_Effects_Apply(e);
        }
+       if(a >= 0)
        {
                string name = wep.mdl;
                string newname = wep.wr_viewmodel(wep, this);
@@ -366,7 +371,35 @@ STATIC_INIT(viewmodel) {
        viewmodels[slot] = new(viewmodel);
 }
 
-void Porto_Draw(entity this);
+float showfps_prevfps;
+float showfps_prevfps_time;
+int showfps_framecounter;
+
+void fpscounter_update()
+{
+       if(!STAT(SHOWFPS))
+               return;
+
+       float currentTime = gettime(GETTIME_REALTIME);
+       showfps_framecounter += 1;
+       if(currentTime - showfps_prevfps_time > STAT(SHOWFPS))
+       {
+               showfps_prevfps = showfps_framecounter/(currentTime - showfps_prevfps_time);
+               showfps_framecounter = 0;
+               showfps_prevfps_time = currentTime;
+
+               int channel = MSG_C2S;
+               WriteHeader(channel, fpsreport);
+               WriteShort(channel, bound(0, rint(showfps_prevfps), 65535)); // prevent insane fps values
+       }
+}
+
+STATIC_INIT(fpscounter_init)
+{
+       float currentTime = gettime(GETTIME_REALTIME);
+       showfps_prevfps_time = currentTime; // we must initialize it to avoid an instant low frame sending
+}
+
 STATIC_INIT(Porto)
 {
        entity e = new_pure(porto);
@@ -392,6 +425,10 @@ void Porto_Draw(entity this)
 
                vector pos = view_origin;
                vector dir = view_forward;
+               makevectors(((autocvar_chase_active) ? warpzone_save_view_angles : view_angles));
+               pos += v_right * -wepent.movedir.y
+                       +  v_up * wepent.movedir.z;
+
                if (wepent.angles_held_status)
                {
                        makevectors(wepent.angles_held);
@@ -456,9 +493,8 @@ vector GetCurrentFov(float fov)
        if(zoomfactor < 1 || zoomfactor > 30)
                zoomfactor = 2.5;
        zoomspeed = autocvar_cl_zoomspeed;
-       if(zoomspeed >= 0)
-       if(zoomspeed < 0.5 || zoomspeed > 16)
-                       zoomspeed = 3.5;
+       if (zoomspeed >= 0 && (zoomspeed < 0.5 || zoomspeed > 16))
+               zoomspeed = 3.5;
 
        zoomdir = button_zoom;
 
@@ -467,9 +503,14 @@ vector GetCurrentFov(float fov)
                for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
                {
                        entity wepent = viewmodels[slot];
-                       if(wepent.switchweapon == wepent.activeweapon)
-                       if((wepent.activeweapon == WEP_VORTEX && !WEP_CVAR(vortex, secondary)) || (wepent.activeweapon == WEP_RIFLE && !WEP_CVAR(rifle, secondary)) || (wepent.activeweapon == WEP_OVERKILL_VORTEX && !WEP_CVAR(okvortex, secondary))) // do NOT use switchweapon here
-                               zoomdir += button_attack2;
+                       if(wepent.switchweapon != wepent.activeweapon)
+                               continue;
+                       Weapon wep = wepent.activeweapon;
+                       if(wep != WEP_Null && wep.wr_zoomdir)
+                       {
+                               bool do_zoom = wep.wr_zoomdir(wep); // TODO: merge this with wr_zoom?
+                               zoomdir += do_zoom;
+                       }
                }
        }
        if(spectatee_status > 0 || isdemo())
@@ -486,7 +527,11 @@ vector GetCurrentFov(float fov)
 
        if(zoomdir) { zoomin_effect = 0; }
 
-       if(camera_active)
+       if (spectatee_status > 0 && STAT(CAMERA_SPECTATOR) == 2)
+       {
+               current_viewzoom = 1;
+       }
+       else if (camera_active)
        {
                current_viewzoom = min(1, current_viewzoom + drawframetime);
        }
@@ -530,10 +575,10 @@ vector GetCurrentFov(float fov)
 
        if(autocvar_cl_velocityzoom_enabled && autocvar_cl_velocityzoom_type) // _type = 0 disables velocity zoom too
        {
-               if(intermission) { curspeed = 0; }
+               if (intermission || (spectatee_status > 0 && STAT(CAMERA_SPECTATOR) == 2))
+                       curspeed = 0;
                else
                {
-
                        makevectors(view_angles);
                        v = pmove_vel;
                        if(csqcplayer)
@@ -588,6 +633,8 @@ vector GetOrthoviewFOV(vector ov_worldmin, vector ov_worldmax, vector ov_mid, ve
 // this function must match W_SetupShot!
 float zoomscript_caught;
 
+bool minigame_wasactive;
+
 vector wcross_origin;
 float wcross_scale_prev, wcross_alpha_prev;
 vector wcross_color_prev;
@@ -660,7 +707,7 @@ float TrueAimCheck(entity wepent)
                case WEP_MORTAR: // toss curve
                        return SHOTTYPE_HITWORLD;
                case WEP_VORTEX:
-               case WEP_OVERKILL_VORTEX:
+               case WEP_OVERKILL_NEX:
                case WEP_VAPORIZER:
                        mv = MOVE_NORMAL;
                        break;
@@ -729,8 +776,6 @@ float TrueAimCheck(entity wepent)
        return SHOTTYPE_HITWORLD;
 }
 
-void PostInit();
-void CSQC_Demo_Camera();
 float camera_mode;
 const float CAMERA_FREE = 1;
 const float CAMERA_CHASE = 2;
@@ -758,32 +803,41 @@ vector liquidcolor_prev;
 
 float eventchase_current_distance;
 float eventchase_running;
-bool WantEventchase(entity this)
+int WantEventchase(entity this)
 {
        if(autocvar_cl_orthoview)
-               return false;
+               return 0;
        if(STAT(GAME_STOPPED) || intermission)
-               return true;
+               return 1;
        if(this.viewloc)
-               return true;
+               return 1;
        if(spectatee_status >= 0)
        {
                if(hud != HUD_NORMAL && (autocvar_cl_eventchase_vehicle || spectatee_status > 0))
-                       return true;
+                       return 1;
                if(MUTATOR_CALLHOOK(WantEventchase, this))
-                       return true;
+                       return 1;
+               if(autocvar_cl_eventchase_frozen && STAT(FROZEN))
+                       return 1;
                if(autocvar_cl_eventchase_death && (STAT(HEALTH) <= 0))
                {
                        if(autocvar_cl_eventchase_death == 2)
                        {
                                // don't stop eventchase once it's started (even if velocity changes afterwards)
                                if(this.velocity == '0 0 0' || eventchase_running)
-                                       return true;
+                                       return 1;
                        }
-                       else return true;
+                       else return 1;
+               }
+               if (spectatee_status > 0 && autocvar_cl_eventchase_spectated_change)
+               {
+                       if (time <= spectatee_status_changed_time + min(3, autocvar_cl_eventchase_spectated_change_time))
+                               return 1;
+                       else if (eventchase_running)
+                               return -1; // disable chase_active while eventchase is still enabled so to avoid a glicth
                }
        }
-       return false;
+       return 0;
 }
 
 void HUD_Crosshair_Vehicle(entity this)
@@ -907,7 +961,8 @@ vector crosshair_getcolor(entity this, float health_stat)
 
                case 2: // crosshair_color_by_health
                {
-                       float hp = health_stat;
+                       vector v = healtharmor_maxdamage(health_stat, STAT(ARMOR), armorblockpercent, DEATH_WEAPON.m_id);
+                       float hp = floor(v.x + 1);
 
                        //x = red
                        //y = green
@@ -973,7 +1028,7 @@ void HUD_Crosshair(entity this)
        float f, i, j;
        vector v;
        if(!scoreboard_active && !camera_active && intermission != 2 && !STAT(GAME_STOPPED) &&
-               spectatee_status != -1 && !csqcplayer.viewloc && !MUTATOR_CALLHOOK(DrawCrosshair) &&
+               spectatee_status != -1 && (!csqcplayer.viewloc || (!spectatee_status && (csqcplayer.viewloc.spawnflags & VIEWLOC_FREEAIM))) && !MUTATOR_CALLHOOK(DrawCrosshair) &&
                !HUD_MinigameMenu_IsOpened() )
        {
                if (!autocvar_crosshair_enabled) // main toggle for crosshair rendering
@@ -991,6 +1046,8 @@ void HUD_Crosshair(entity this)
                string wcross_style;
                float wcross_alpha, wcross_resolution;
                wcross_style = autocvar_crosshair;
+               if (csqcplayer.viewloc && (csqcplayer.viewloc.spawnflags & VIEWLOC_FREEAIM) && autocvar_crosshair_2d != "")
+                       wcross_style = autocvar_crosshair_2d;
                if (wcross_style == "0")
                        return;
                wcross_resolution = autocvar_crosshair_size;
@@ -1004,7 +1061,10 @@ void HUD_Crosshair(entity this)
                float shottype;
 
                // wcross_origin = '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
-               wcross_origin = project_3d_to_2d(view_origin + max_shot_distance * view_forward);
+               if(csqcplayer.viewloc && (csqcplayer.viewloc.spawnflags & VIEWLOC_FREEAIM))
+                       wcross_origin = viewloc_mousepos;
+               else
+                       wcross_origin = project_3d_to_2d(view_origin + max_shot_distance * view_forward);
                wcross_origin.z = 0;
                if(autocvar_crosshair_hittest)
                {
@@ -1175,54 +1235,49 @@ void HUD_Crosshair(entity this)
 
                                ring_scale = autocvar_crosshair_ring_size;
 
-                               float weapon_clipload, weapon_clipsize;
-                               weapon_clipload = STAT(WEAPON_CLIPLOAD);
-                               weapon_clipsize = STAT(WEAPON_CLIPSIZE);
-
-                               float vortex_charge, vortex_chargepool;
-                               vortex_charge = STAT(VORTEX_CHARGE);
-                               vortex_chargepool = STAT(VORTEX_CHARGEPOOL);
+                               entity wepent = viewmodels[0]; // TODO: unhardcode
 
-                               float okvortex_charge, okvortex_chargepool;
-                               okvortex_charge = STAT(OVERKILL_VORTEX_CHARGE);
-                               okvortex_chargepool = STAT(OVERKILL_VORTEX_CHARGEPOOL);
+                               int weapon_clipload = wepent.clip_load;
+                               int weapon_clipsize = wepent.clip_size;
 
-                               float arc_heat = STAT(ARC_HEAT);
+                               float arc_heat = wepent.arc_heat_percent;
+                               float vcharge = wepent.vortex_charge;
+                               float vchargepool = wepent.vortex_chargepool_ammo;
+                               float oknex_charge_ = wepent.oknex_charge;
+                               float oknex_chargepool_ = wepent.oknex_chargepool_ammo;
 
                                if(vortex_charge_movingavg == 0) // this should only happen if we have just loaded up the game
-                                       vortex_charge_movingavg = vortex_charge;
-
-                               entity wepent = viewmodels[0]; // TODO: unhardcode
+                                       vortex_charge_movingavg = vcharge;
 
                                // handle the values
-                               if (autocvar_crosshair_ring && wepent.activeweapon == WEP_VORTEX && vortex_charge && autocvar_crosshair_ring_vortex) // ring around crosshair representing velocity-dependent damage for the vortex
+                               if (autocvar_crosshair_ring && wepent.activeweapon == WEP_VORTEX && vcharge && autocvar_crosshair_ring_vortex) // ring around crosshair representing velocity-dependent damage for the vortex
                                {
-                                       if (vortex_chargepool || use_vortex_chargepool) {
+                                       if (vchargepool || use_vortex_chargepool) {
                                                use_vortex_chargepool = 1;
-                                               ring_inner_value = vortex_chargepool;
+                                               ring_inner_value = vchargepool;
                                        } else {
-                                               vortex_charge_movingavg = (1 - autocvar_crosshair_ring_vortex_currentcharge_movingavg_rate) * vortex_charge_movingavg + autocvar_crosshair_ring_vortex_currentcharge_movingavg_rate * vortex_charge;
-                                               ring_inner_value = bound(0, autocvar_crosshair_ring_vortex_currentcharge_scale * (vortex_charge - vortex_charge_movingavg), 1);
+                                               vortex_charge_movingavg = (1 - autocvar_crosshair_ring_vortex_currentcharge_movingavg_rate) * vortex_charge_movingavg + autocvar_crosshair_ring_vortex_currentcharge_movingavg_rate * vcharge;
+                                               ring_inner_value = bound(0, autocvar_crosshair_ring_vortex_currentcharge_scale * (vcharge - vortex_charge_movingavg), 1);
                                        }
 
                                        ring_inner_alpha = autocvar_crosshair_ring_vortex_inner_alpha;
-                                       ring_inner_rgb = eX * autocvar_crosshair_ring_vortex_inner_color_red + eY * autocvar_crosshair_ring_vortex_inner_color_green + eZ * autocvar_crosshair_ring_vortex_inner_color_blue;
+                                       ring_inner_rgb = vec3(autocvar_crosshair_ring_vortex_inner_color_red, autocvar_crosshair_ring_vortex_inner_color_green, autocvar_crosshair_ring_vortex_inner_color_blue);
                                        ring_inner_image = "gfx/crosshair_ring_inner.tga";
 
                                        // draw the outer ring to show the current charge of the weapon
-                                       ring_value = vortex_charge;
+                                       ring_value = vcharge;
                                        ring_alpha = autocvar_crosshair_ring_vortex_alpha;
                                        ring_rgb = wcross_color;
                                        ring_image = "gfx/crosshair_ring_nexgun.tga";
                                }
-                               else if (autocvar_crosshair_ring && (wepent.activeweapon == WEP_OVERKILL_VORTEX) && okvortex_charge && autocvar_crosshair_ring_vortex)
+                               else if (autocvar_crosshair_ring && (wepent.activeweapon == WEP_OVERKILL_NEX) && oknex_charge_ && autocvar_crosshair_ring_vortex)
                                {
-                                       if (okvortex_chargepool || use_vortex_chargepool) {
+                                       if (oknex_chargepool_ || use_vortex_chargepool) {
                                                use_vortex_chargepool = 1;
-                                               ring_inner_value = okvortex_chargepool;
+                                               ring_inner_value = oknex_chargepool_;
                                        } else {
-                                               vortex_charge_movingavg = (1 - autocvar_crosshair_ring_vortex_currentcharge_movingavg_rate) * vortex_charge_movingavg + autocvar_crosshair_ring_vortex_currentcharge_movingavg_rate * okvortex_charge;
-                                               ring_inner_value = bound(0, autocvar_crosshair_ring_vortex_currentcharge_scale * (okvortex_charge - vortex_charge_movingavg), 1);
+                                               vortex_charge_movingavg = (1 - autocvar_crosshair_ring_vortex_currentcharge_movingavg_rate) * vortex_charge_movingavg + autocvar_crosshair_ring_vortex_currentcharge_movingavg_rate * oknex_charge_;
+                                               ring_inner_value = bound(0, autocvar_crosshair_ring_vortex_currentcharge_scale * (oknex_charge_ - vortex_charge_movingavg), 1);
                                        }
 
                                        ring_inner_alpha = autocvar_crosshair_ring_vortex_inner_alpha;
@@ -1230,21 +1285,21 @@ void HUD_Crosshair(entity this)
                                        ring_inner_image = "gfx/crosshair_ring_inner.tga";
 
                                        // draw the outer ring to show the current charge of the weapon
-                                       ring_value = okvortex_charge;
+                                       ring_value = oknex_charge_;
                                        ring_alpha = autocvar_crosshair_ring_vortex_alpha;
                                        ring_rgb = wcross_color;
                                        ring_image = "gfx/crosshair_ring_nexgun.tga";
                                }
                                else if (autocvar_crosshair_ring && wepent.activeweapon == WEP_MINE_LAYER && WEP_CVAR(minelayer, limit) && autocvar_crosshair_ring_minelayer)
                                {
-                                       ring_value = bound(0, STAT(LAYED_MINES) / WEP_CVAR(minelayer, limit), 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_value = bound(0, wepent.minelayer_mines / WEP_CVAR(minelayer, limit), 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;
                                        ring_rgb = wcross_color;
                                        ring_image = "gfx/crosshair_ring.tga";
                                }
-                               else if (wepent.activeweapon == WEP_HAGAR && STAT(HAGAR_LOAD) && autocvar_crosshair_ring_hagar)
+                               else if (wepent.activeweapon == WEP_HAGAR && wepent.hagar_load && autocvar_crosshair_ring_hagar)
                                {
-                                       ring_value = bound(0, STAT(HAGAR_LOAD) / WEP_CVAR_SEC(hagar, load_max), 1);
+                                       ring_value = bound(0, wepent.hagar_load / WEP_CVAR_SEC(hagar, load_max), 1);
                                        ring_alpha = autocvar_crosshair_ring_hagar_alpha;
                                        ring_rgb = wcross_color;
                                        ring_image = "gfx/crosshair_ring.tga";
@@ -1302,20 +1357,21 @@ void HUD_Crosshair(entity this)
 
 #define CROSSHAIR_DO_BLUR(M,sz,wcross_name,wcross_alpha) \
                        MACRO_BEGIN { \
+                               vector scaled_sz = sz * wcross_size; \
                                if(wcross_blur > 0) \
                                { \
                                        for(i = -2; i <= 2; ++i) \
                                        for(j = -2; j <= 2; ++j) \
-                                       M(i,j,sz,wcross_name,wcross_alpha*0.04); \
+                                       M(i,j,sz,scaled_sz,wcross_name,wcross_alpha*0.04); \
                                } \
                                else \
                                { \
-                                       M(0,0,sz,wcross_name,wcross_alpha); \
+                                       M(0,0,sz,scaled_sz,wcross_name,wcross_alpha); \
                                } \
                        } MACRO_END
 
-#define CROSSHAIR_DRAW_SINGLE(i,j,sz,wcross_name,wcross_alpha) \
-                       drawpic(wcross_origin - ('0.5 0 0' * (sz * wcross_size.x + i * wcross_blur) + '0 0.5 0' * (sz * wcross_size.y + j * wcross_blur)), wcross_name, sz * wcross_size, wcross_color, wcross_alpha, DRAWFLAG_NORMAL)
+#define CROSSHAIR_DRAW_SINGLE(i,j,sz,scaled_sz,wcross_name,wcross_alpha) \
+                       drawpic(wcross_origin - ('0.5 0 0' * (scaled_sz.x + i * wcross_blur) + '0 0.5 0' * (scaled_sz.y + j * wcross_blur)), wcross_name, scaled_sz, wcross_color, wcross_alpha, DRAWFLAG_NORMAL)
 
 #define CROSSHAIR_DRAW(sz,wcross_name,wcross_alpha) \
                        CROSSHAIR_DO_BLUR(CROSSHAIR_DRAW_SINGLE,sz,wcross_name,wcross_alpha)
@@ -1357,12 +1413,8 @@ void HUD_Crosshair(entity this)
                wcross_scale_goal_prev = 0;
                wcross_alpha_goal_prev = 0;
                wcross_changedonetime = 0;
-               if(wcross_name_goal_prev)
-                       strunzone(wcross_name_goal_prev);
-               wcross_name_goal_prev = string_null;
-               if(wcross_name_goal_prev_prev)
-                       strunzone(wcross_name_goal_prev_prev);
-               wcross_name_goal_prev_prev = string_null;
+               strfree(wcross_name_goal_prev);
+               strfree(wcross_name_goal_prev_prev);
                wcross_name_changestarttime = 0;
                wcross_name_changedonetime = 0;
                wcross_name_alpha_goal_prev = 0;
@@ -1372,6 +1424,75 @@ void HUD_Crosshair(entity this)
        }
 }
 
+const int MAX_SPECIALCOMMAND = 15;
+vector specialcommand_slots[MAX_SPECIALCOMMAND];
+vector specialcommand_colors[MAX_SPECIALCOMMAND];
+const float SPECIALCOMMAND_SPEED = 150;
+const float SPECIALCOMMAND_TURNSPEED = 2;
+const float SPECIALCOMMAND_SIZE = 0.025;
+const float SPECIALCOMMAND_CHANCE = 0.35;
+float sc_spawntime, sc_changetime;
+vector sc_color = '1 1 1';
+void SpecialCommand()
+{
+       if(!STAT(MOVEVARS_SPECIALCOMMAND))
+               return;
+
+       if(time >= sc_changetime)
+       {
+               sc_changetime = time + 1;
+               sc_color = randomvec() * 1.5;
+               sc_color.x = bound(0.2, sc_color.x, 0.75);
+               sc_color.y = bound(0.2, sc_color.y, 0.75);
+               sc_color.z = bound(0.2, sc_color.z, 0.75);
+       }
+       drawfill('0 0 0', vec2(vid_conwidth, vid_conheight), sc_color, autocvar_hud_colorflash_alpha * bound(0.1, sc_changetime - time, 0.3), DRAWFLAG_ADDITIVE);
+
+       if(!precache_pic("gfx/smile"))
+               return; // damn party poopers
+
+       for(int j = MAX_SPECIALCOMMAND - 1; j >= 0; --j)
+       {
+               vector slot = specialcommand_slots[j];
+               if(slot.y)
+                       slot.y += SPECIALCOMMAND_SPEED * frametime;
+               //if(slot.z)
+                       //slot.z = sin(SPECIALCOMMAND_TURNSPEED * M_PI * time);
+               if(slot.y >= vid_conheight)
+                       slot = '0 0 0';
+
+               if(slot == '0 0 0')
+               {
+                       if(random() <= SPECIALCOMMAND_CHANCE && time > sc_spawntime) // low chance to spawn!
+                       {
+                               slot.x = bound(0, (random() * vid_conwidth + 1), vid_conwidth);
+                               slot.y = 1; // start it off 0 so we can use it
+                               slot.z = floor(random() * Weapons_MAX);
+                               sc_spawntime = time + bound(0.4, random(), 0.75); // prevent spawning another one for this amount of time!
+                               vector newcolor = randomvec() * 2;
+                               newcolor.x = bound(0.4, newcolor.x, 1);
+                               newcolor.y = bound(0.4, newcolor.y, 1);
+                               newcolor.z = bound(0.4, newcolor.z, 1);
+                               specialcommand_colors[j] = newcolor;
+                       }
+               }
+               else
+               {
+                       vector splash_size = '0 0 0';
+                       splash_size.x = max(vid_conwidth, vid_conheight) * SPECIALCOMMAND_SIZE;
+                       splash_size.y = max(vid_conwidth, vid_conheight) * SPECIALCOMMAND_SIZE;
+                       entity wep = Weapons_from(slot.z);
+                       if(wep == WEP_Null)
+                               drawpic(vec2(slot), "gfx/smile", vec2(splash_size), specialcommand_colors[j], 0.95, DRAWFLAG_NORMAL);
+                       else
+                               drawpic_skin(vec2(slot), wep.model2, vec2(splash_size), specialcommand_colors[j], 0.95, DRAWFLAG_NORMAL);
+                       //drawrotpic(vec2(slot), slot.z, "gfx/smile", vec2(splash_size), vec2(splash_size) / 2, specialcommand_colors[j], 0.95, DRAWFLAG_NORMAL);
+               }
+
+               specialcommand_slots[j] = slot;
+       }
+}
+
 void HUD_Draw(entity this)
 {
        // if we don't know gametype and scores yet avoid drawing the scoreboard
@@ -1386,36 +1507,40 @@ void HUD_Draw(entity this)
        if(!intermission)
        if (MUTATOR_CALLHOOK(HUD_Draw_overlay))
        {
-               drawfill('0 0 0', eX * vid_conwidth + eY * vid_conheight, M_ARGV(0, vector), autocvar_hud_colorflash_alpha * M_ARGV(1, float), DRAWFLAG_ADDITIVE);
+               drawfill('0 0 0', vec2(vid_conwidth, vid_conheight), M_ARGV(0, vector), autocvar_hud_colorflash_alpha * M_ARGV(1, float), DRAWFLAG_ADDITIVE);
        }
        else if(STAT(FROZEN))
        {
-               drawfill('0 0 0', eX * vid_conwidth + eY * vid_conheight, ((STAT(REVIVE_PROGRESS)) ? ('0.25 0.90 1' + ('1 0 0' * STAT(REVIVE_PROGRESS)) + ('0 1 1' * STAT(REVIVE_PROGRESS) * -1)) : '0.25 0.90 1'), autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE);
+               vector col = '0.25 0.90 1';
+               if(STAT(REVIVE_PROGRESS))
+                       col += vec3(STAT(REVIVE_PROGRESS), -STAT(REVIVE_PROGRESS), -STAT(REVIVE_PROGRESS));
+               drawfill('0 0 0', vec2(vid_conwidth, vid_conheight), col, autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE);
        }
 
        HUD_Scale_Enable();
        if(!intermission)
        if(STAT(NADE_TIMER) && autocvar_cl_nade_timer) // give nade top priority, as it's a matter of life and death
        {
-               DrawCircleClippedPic(eX * 0.5 * vid_conwidth + eY * 0.6 * vid_conheight, 0.1 * vid_conheight, "gfx/crosshair_ring.tga", STAT(NADE_TIMER), '0.25 0.90 1' + ('1 0 0' * STAT(NADE_TIMER)) - ('0 1 1' * STAT(NADE_TIMER)), autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE);
-               drawstring_aspect(eY * 0.64 * vid_conheight, ((autocvar_cl_nade_timer == 2) ? _("Nade timer") : ""), eX * vid_conwidth + eY * 0.025 * vid_conheight, '1 1 1', 1, DRAWFLAG_NORMAL);
+               vector col = '0.25 0.90 1' + vec3(STAT(NADE_TIMER), -STAT(NADE_TIMER), -STAT(NADE_TIMER));
+               DrawCircleClippedPic(vec2(0.5 * vid_conwidth, 0.6 * vid_conheight), 0.1 * vid_conheight, "gfx/crosshair_ring.tga", STAT(NADE_TIMER), col, autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE);
+               drawstring_aspect(eY * 0.64 * vid_conheight, ((autocvar_cl_nade_timer == 2) ? _("Nade timer") : ""), vec2(vid_conwidth, 0.025 * vid_conheight), '1 1 1', 1, DRAWFLAG_NORMAL);
        }
        else if(STAT(CAPTURE_PROGRESS))
        {
-               DrawCircleClippedPic(eX * 0.5 * vid_conwidth + eY * 0.6 * vid_conheight, 0.1 * vid_conheight, "gfx/crosshair_ring.tga", STAT(CAPTURE_PROGRESS), '0.25 0.90 1', autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE);
-               drawstring_aspect(eY * 0.64 * vid_conheight, _("Capture progress"), eX * vid_conwidth + eY * 0.025 * vid_conheight, '1 1 1', 1, DRAWFLAG_NORMAL);
+               DrawCircleClippedPic(vec2(0.5 * vid_conwidth, 0.6 * vid_conheight), 0.1 * vid_conheight, "gfx/crosshair_ring.tga", STAT(CAPTURE_PROGRESS), '0.25 0.90 1', autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE);
+               drawstring_aspect(eY * 0.64 * vid_conheight, _("Capture progress"), vec2(vid_conwidth, 0.025 * vid_conheight), '1 1 1', 1, DRAWFLAG_NORMAL);
        }
        else if(STAT(REVIVE_PROGRESS))
        {
-               DrawCircleClippedPic(eX * 0.5 * vid_conwidth + eY * 0.6 * vid_conheight, 0.1 * vid_conheight, "gfx/crosshair_ring.tga", STAT(REVIVE_PROGRESS), '0.25 0.90 1', autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE);
-               drawstring_aspect(eY * 0.64 * vid_conheight, _("Revival progress"), eX * vid_conwidth + eY * 0.025 * vid_conheight, '1 1 1', 1, DRAWFLAG_NORMAL);
+               DrawCircleClippedPic(vec2(0.5 * vid_conwidth, 0.6 * vid_conheight), 0.1 * vid_conheight, "gfx/crosshair_ring.tga", STAT(REVIVE_PROGRESS), '0.25 0.90 1', autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE);
+               drawstring_aspect(eY * 0.64 * vid_conheight, _("Revival progress"), vec2(vid_conwidth, 0.025 * vid_conheight), '1 1 1', 1, DRAWFLAG_NORMAL);
        }
        HUD_Scale_Disable();
 
        if(autocvar_r_letterbox == 0)
                if(autocvar_viewsize < 120)
                {
-                       if(!(gametype == MAPINFO_TYPE_RACE || gametype == MAPINFO_TYPE_CTS))
+                       if(!(ISGAMETYPE(RACE) || ISGAMETYPE(CTS)))
                                Accuracy_LoadLevels();
 
                        HUD_Main();
@@ -1423,11 +1548,70 @@ void HUD_Draw(entity this)
                }
 
        // crosshair goes VERY LAST
+       SpecialCommand();
        UpdateDamage();
        HUD_Crosshair(this);
        HitSound();
 }
 
+void ViewLocation_Mouse()
+{
+       if(spectatee_status)
+               return; // don't draw it as spectator!
+
+       viewloc_mousepos += getmousepos() * autocvar_menu_mouse_speed;
+       viewloc_mousepos.x = bound(0, viewloc_mousepos.x, vid_conwidth);
+       viewloc_mousepos.y = bound(0, viewloc_mousepos.y, vid_conheight);
+
+       //float cursor_alpha = 1 - autocvar__menu_alpha;
+       //draw_cursor(viewloc_mousepos, '0.5 0.5 0', "/cursor_move", '1 1 1', cursor_alpha);
+}
+
+void HUD_Cursor_Show()
+{
+       float cursor_alpha = 1 - autocvar__menu_alpha;
+       if(cursor_type == CURSOR_NORMAL)
+               draw_cursor_normal(mousepos, '1 1 1', cursor_alpha);
+       else if(cursor_type == CURSOR_MOVE)
+               draw_cursor(mousepos, '0.5 0.5 0', "/cursor_move", '1 1 1', cursor_alpha);
+       else if(cursor_type == CURSOR_RESIZE)
+               draw_cursor(mousepos, '0.5 0.5 0', "/cursor_resize", '1 1 1', cursor_alpha);
+       else if(cursor_type == CURSOR_RESIZE2)
+               draw_cursor(mousepos, '0.5 0.5 0', "/cursor_resize2", '1 1 1', cursor_alpha);
+}
+
+void HUD_Mouse(entity player)
+{
+       if(autocvar__menu_alpha == 1)
+               return;
+
+       if(!cursor_active)
+       {
+               if(player.viewloc && (player.viewloc.spawnflags & VIEWLOC_FREEAIM))
+                       ViewLocation_Mouse(); // NOTE: doesn't use cursormode
+               return;
+       }
+
+       if(!autocvar_hud_cursormode)
+               update_mousepos();
+
+       if(autocvar__hud_configure)
+               HUD_Panel_Mouse();
+       else
+       {
+               if (HUD_MinigameMenu_IsOpened())
+                       HUD_Minigame_Mouse();
+               if (QuickMenu_IsOpened())
+                       QuickMenu_Mouse();
+               if (HUD_Radar_Clickable())
+                       HUD_Radar_Mouse();
+       }
+
+       prevMouseClicked = mouseClicked;
+
+       HUD_Cursor_Show();
+}
+
 bool ov_enabled;
 float oldr_nearclip;
 float oldr_farclip_base;
@@ -1436,15 +1620,12 @@ float oldr_novis;
 float oldr_useportalculling;
 float oldr_useinfinitefarclip;
 
-void cl_notice_run();
-
 float prev_myteam;
 int lasthud;
 float vh_notice_time;
-void WaypointSprite_Load();
 void CSQC_UpdateView(entity this, float w, float h)
 {
-    TC(int, w); TC(int, h);
+       TC(int, w); TC(int, h);
        entity e;
        float fov;
        float f;
@@ -1525,6 +1706,11 @@ void CSQC_UpdateView(entity this, float w, float h)
                button_zoom = false;
        }
 
+       // abused multiple places below
+       entity local_player = ((csqcplayer) ? csqcplayer : CSQCModel_server2csqc(player_localentnum - 1));
+       if(!local_player)
+               local_player = this; // fall back!
+
        // event chase camera
        if(autocvar_chase_active <= 0) // greater than 0 means it's enabled manually, and this code is skipped
        {
@@ -1562,7 +1748,8 @@ void CSQC_UpdateView(entity this, float w, float h)
                        }
                }
 
-               if(WantEventchase(this))
+               int eventchase = WantEventchase(this);
+               if (eventchase)
                {
                        vector current_view_origin_override = '0 0 0';
                        vector view_offset_override = '0 0 0';
@@ -1576,10 +1763,6 @@ void CSQC_UpdateView(entity this, float w, float h)
                        }
                        eventchase_running = true;
 
-                       entity local_player = ((csqcplayer) ? csqcplayer : CSQCModel_server2csqc(player_localentnum - 1));
-                       if(!local_player)
-                               local_player = this; // fall back!
-
                        // 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);
                        if (custom_eventchase)
@@ -1644,7 +1827,8 @@ void CSQC_UpdateView(entity this, float w, float h)
                        if(!local_player.viewloc)
                                setproperty(VF_ANGLES, WarpZone_TransformVAngles(WarpZone_trace_transform, view_angles));
                }
-               else if(autocvar_chase_active < 0) // time to disable chase_active if it was set by this code
+
+               if (eventchase <= 0 && autocvar_chase_active < 0) // time to disable chase_active if it was set by this code
                {
                        eventchase_running = false;
                        cvar_set("chase_active", "0");
@@ -1733,7 +1917,7 @@ void CSQC_UpdateView(entity this, float w, float h)
                ov_enabled = true;
 
                #if 0
-               LOG_INFOF("OrthoView: org = %s, angles = %s, distance = %f, nearest = %f, furthest = %f\n",
+               LOG_INFOF("OrthoView: org = %s, angles = %s, distance = %f, nearest = %f, furthest = %f",
                        vtos(ov_org),
                        vtos(getpropertyvec(VF_ANGLES)),
                        ov_distance,
@@ -1763,10 +1947,7 @@ void CSQC_UpdateView(entity this, float w, float h)
        // Render the Scene
        view_origin = getpropertyvec(VF_ORIGIN);
        view_angles = getpropertyvec(VF_ANGLES);
-       makevectors(view_angles);
-       view_forward = v_forward;
-       view_right = v_right;
-       view_up = v_up;
+       MAKEVECTORS(makevectors, view_angles, view_forward, view_right, view_up);
 
 #ifdef BLURTEST
        if(time > blurtest_time0 && time < blurtest_time1)
@@ -1789,6 +1970,7 @@ void CSQC_UpdateView(entity this, float w, float h)
 
        TargetMusic_Advance();
        Fog_Force();
+       fpscounter_update();
 
        if(drawtime == 0)
                drawframetime = 0.01666667; // when we don't know fps yet, we assume 60fps
@@ -1834,6 +2016,20 @@ void CSQC_UpdateView(entity this, float w, float h)
                }
        }
 
+       if(active_minigame && HUD_MinigameMenu_IsOpened())
+       {
+               if(!minigame_wasactive)
+               {
+                       localcmd("+button14\n");
+                       minigame_wasactive = true;
+               }
+       }
+       else if(minigame_wasactive)
+       {
+               localcmd("-button14\n");
+               minigame_wasactive = false;
+       }
+
        ColorTranslateMode = autocvar_cl_stripcolorcodes;
 
        for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
@@ -1934,7 +2130,7 @@ void CSQC_UpdateView(entity this, float w, float h)
 
        IL_EACH(g_drawables, it.draw, it.draw(it));
 
-       addentities(MASK_NORMAL | MASK_ENGINE | MASK_ENGINEVIEWMODELS);
+       addentities(MASK_NORMAL | MASK_ENGINE | MASK_ENGINEVIEWMODELS); // TODO: .health is used in cl_deathfade (a feature we have turned off currently)
        renderscene();
 
        // now switch to 2D drawing mode by calling a 2D drawing function
@@ -2011,7 +2207,7 @@ void CSQC_UpdateView(entity this, float w, float h)
                // 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 || is_dead || hud != HUD_NORMAL)
+               if(spectatee_status || is_dead || hud != HUD_NORMAL || local_player.viewloc)
                {
                        // no zoom reticle while dead
                        reticle_type = 0;
@@ -2066,7 +2262,7 @@ void CSQC_UpdateView(entity this, float w, float h)
 
 
        // improved polyblend
-       if(autocvar_hud_contents)
+       if(autocvar_hud_contents && !MUTATOR_CALLHOOK(HUD_Contents))
        {
                float contentalpha_temp, incontent, liquidalpha, contentfadetime;
                vector liquidcolor;
@@ -2111,7 +2307,7 @@ void CSQC_UpdateView(entity this, float w, float h)
                contentavgalpha = contentavgalpha * (1 - contentalpha_temp) + incontent * contentalpha_temp;
 
                if(contentavgalpha)
-                       drawfill('0 0 0', eX * vid_conwidth + eY * vid_conheight, liquidcolor_prev, contentavgalpha * liquidalpha_prev, DRAWFLAG_NORMAL);
+                       drawfill('0 0 0', vec2(vid_conwidth, vid_conheight), liquidcolor_prev, contentavgalpha * liquidalpha_prev, DRAWFLAG_NORMAL);
 
                if(autocvar_hud_postprocessing)
                {
@@ -2187,13 +2383,13 @@ void CSQC_UpdateView(entity this, float w, float h)
                        if(autocvar_cl_gentle_damage == 2)
                        {
                                if(myhealth_flash < pain_threshold) // only randomize when the flash is gone
-                                       myhealth_gentlergb = eX * random() + eY * random() + eZ * random();
+                                       myhealth_gentlergb = randomvec();
                        }
                        else
                                myhealth_gentlergb = stov(autocvar_hud_damage_gentle_color);
 
                        if(myhealth_flash_temp > 0)
-                               drawfill('0 0 0', eX * vid_conwidth + eY * vid_conheight, myhealth_gentlergb, autocvar_hud_damage_gentle_alpha_multiplier * bound(0, myhealth_flash_temp, 1) * autocvar_hud_damage, DRAWFLAG_NORMAL);
+                               drawfill('0 0 0', vec2(vid_conwidth, vid_conheight), myhealth_gentlergb, autocvar_hud_damage_gentle_alpha_multiplier * bound(0, myhealth_flash_temp, 1) * autocvar_hud_damage, DRAWFLAG_NORMAL);
                }
                else if(myhealth_flash_temp > 0)
                        drawpic(splash_pos, "gfx/blood", splash_size, stov(autocvar_hud_damage_color), bound(0, myhealth_flash_temp, 1) * autocvar_hud_damage, DRAWFLAG_NORMAL);
@@ -2269,7 +2465,7 @@ void CSQC_UpdateView(entity this, float w, float h)
        else if(cvar("r_glsl_postprocess") == 2)
                cvar_set("r_glsl_postprocess", "0");
 
-       /*if(gametype == MAPINFO_TYPE_CTF)
+       /*if(ISGAMETYPE(CTF))
          {
          ctf_view();
          } else */
@@ -2277,7 +2473,7 @@ void CSQC_UpdateView(entity this, float w, float h)
        // draw 2D entities
        IL_EACH(g_drawables_2d, it.draw2d, it.draw2d(it));
        Draw_ShowNames_All();
-#ifdef DEBUGDRAW
+#if ENABLE_DEBUGDRAW
        Debug_Draw();
 #endif
 
@@ -2318,14 +2514,7 @@ void CSQC_UpdateView(entity this, float w, float h)
                cvar_set("vid_conheight", h0);
        }
 
-       if(autocvar__hud_configure)
-               HUD_Panel_Mouse();
-       else if (HUD_MinigameMenu_IsOpened() || active_minigame)
-               HUD_Minigame_Mouse();
-       else if(QuickMenu_IsOpened())
-               QuickMenu_Mouse();
-       else
-               HUD_Radar_Mouse();
+       HUD_Mouse(local_player);
 
        cl_notice_run();
        unpause_update();