Merge branch 'master' into terencehill/dynamic_hud
authorterencehill <piuntn@gmail.com>
Wed, 23 Mar 2016 21:58:50 +0000 (22:58 +0100)
committerterencehill <piuntn@gmail.com>
Wed, 23 Mar 2016 21:58:50 +0000 (22:58 +0100)
Conflicts:
qcsrc/client/view.qc

_hud_common.cfg
qcsrc/client/autocvars.qh
qcsrc/client/hud/hud.qc
qcsrc/client/hud/hud.qh
qcsrc/client/hud/hud_config.qc
qcsrc/client/main.qc
qcsrc/client/view.qc
qcsrc/menu/xonotic/dialog_settings_game_hud.qc

index 70900762f7a3cb1b5da162db9fcc2e34cf986461..90a363f7a0b6483aedc29f54a75cd98f4c72b5f8 100644 (file)
@@ -63,6 +63,14 @@ alias hud_panel_radar_maximized "cl_cmd hud radar"
 // other hud cvars
 seta hud_panel_update_interval 2 "how often (in seconds) common panel cvars are reloaded"
 
+seta hud_dynamic_follow 1 "HUD moves around following player's movement (effect shared with cl_followmodel, can be enabled independently from it though)"
+seta hud_dynamic_follow_scale 0.01 "HUD following scale"
+
+seta hud_dynamic_shake 1 "shake the HUD when hurt"
+seta hud_dynamic_shake_damage_max 90 "damage value at which the HUD shake effect is maximum"
+seta hud_dynamic_shake_damage_min 10 "damage value at which the HUD shake effect is minimum"
+seta hud_dynamic_shake_scale 0.4 "HUD shake scale"
+
 seta hud_showbinds 1   "what to show in the HUD to indicate certain keys to press: 0 display commands, 1 bound keys, 2 both"
 seta hud_showbinds_limit 2     "maximum number of bound keys to show for a command. 0 for unlimited"
 set _hud_showbinds_reload 0    "set it to 1 to reload binds if you changed any. It is reset to 0 automatically"
index ac02e4c1d00c67ac79946ba435d60251adbc3719..f7db0e30a0b8e0fa51ad3171ddb758e7b0f660d4 100644 (file)
@@ -370,6 +370,7 @@ float autocvar_hud_shownames_offset;
 string autocvar_hud_skin;
 float autocvar_menu_mouse_speed;
 string autocvar_menu_skin;
+float autocvar_r_drawviewmodel;
 int autocvar_r_fakelight;
 int autocvar_r_fullbright;
 float autocvar_r_letterbox;
index fcfa24203cd15907925220e0d874802bd0b2266e..6b6f4d230ab0a7a379272d9e8c527bdbd2ada095 100644 (file)
@@ -461,6 +461,78 @@ void HUD_Reset()
                HUD_Mod_CTF_Reset();
 }
 
+float autocvar_hud_dynamic_shake;
+float autocvar_hud_dynamic_shake_damage_max;
+float autocvar_hud_dynamic_shake_damage_min;
+float autocvar_hud_dynamic_shake_scale;
+float hud_dynamic_shake_x[10] = {0,    1, -0.7,  0.5, -0.3,  0.2, -0.1,  0.1,  0.0, 0};
+float hud_dynamic_shake_y[10] = {0,  0.4,  0.8, -0.2, -0.6,  0.0,  0.3,  0.1, -0.1, 0};
+bool Hud_Shake_Update()
+{
+       if(time - hud_dynamic_shake_time < 0)
+               return false;
+
+       float anim_speed = 17 + 9 * hud_dynamic_shake_factor;
+       float elapsed_time = (time - hud_dynamic_shake_time) * anim_speed;
+       int i = floor(elapsed_time);
+       if(i >= 9)
+               return false;
+
+       float f = elapsed_time - i;
+       hud_dynamic_shake_realofs.x = (1 - f) * hud_dynamic_shake_x[i] + f * hud_dynamic_shake_x[i+1];
+       hud_dynamic_shake_realofs.y = (1 - f) * hud_dynamic_shake_y[i] + f * hud_dynamic_shake_y[i+1];
+       hud_dynamic_shake_realofs.z = 0;
+       hud_dynamic_shake_realofs *= hud_dynamic_shake_factor * autocvar_hud_dynamic_shake_scale;
+       hud_dynamic_shake_realofs.x = bound(-0.1, hud_dynamic_shake_realofs.x, 0.1) * vid_conwidth;
+       hud_dynamic_shake_realofs.y = bound(-0.1, hud_dynamic_shake_realofs.y, 0.1) * vid_conheight;
+       return true;
+}
+
+void calc_followmodel_ofs(entity view);
+void Hud_Dynamic_Frame()
+{
+       hud_dynamic_ofs = '0 0 0';
+       if (autocvar_hud_dynamic_follow)
+       {
+               entity view = CSQCModel_server2csqc(player_localentnum - 1);
+               calc_followmodel_ofs(view);
+               hud_dynamic_ofs -= cl_followmodel_ofs * autocvar_hud_dynamic_follow_scale;
+       }
+
+       if (fabs(hud_dynamic_ofs.x) < 0.001) hud_dynamic_ofs.x = 0;
+       if (fabs(hud_dynamic_ofs.y) < 0.001) hud_dynamic_ofs.y = 0;
+       if (fabs(hud_dynamic_ofs.z) < 0.001) hud_dynamic_ofs.z = 0;
+       hud_dynamic_ofs.x = bound(-0.1, hud_dynamic_ofs.x, 0.1);
+       hud_dynamic_ofs.y = bound(-0.1, hud_dynamic_ofs.y, 0.1);
+       hud_dynamic_ofs.z = bound(-0.1, hud_dynamic_ofs.z, 0.1);
+
+       float health = STAT(HEALTH);
+       if(autocvar_hud_dynamic_shake > 0 && !autocvar__hud_configure && health > 0)
+       {
+               if(hud_dynamic_shake_factor == -1) // don't allow the effect for this frame
+                       hud_dynamic_shake_factor = 0;
+               else
+               {
+                       float new_hud_dynamic_shake_factor = 0;
+                       if(prev_health - health >= autocvar_hud_dynamic_shake_damage_min && autocvar_hud_dynamic_shake_damage_max > autocvar_hud_dynamic_shake_damage_min)
+                       {
+                               float m = max(autocvar_hud_dynamic_shake_damage_min, 1);
+                               new_hud_dynamic_shake_factor = (prev_health - health - m) / (autocvar_hud_dynamic_shake_damage_max - m);
+                               if(new_hud_dynamic_shake_factor >= 1)
+                                       new_hud_dynamic_shake_factor = 1;
+                               if(new_hud_dynamic_shake_factor >= hud_dynamic_shake_factor)
+                               {
+                                       hud_dynamic_shake_factor = new_hud_dynamic_shake_factor;
+                                       hud_dynamic_shake_time = time;
+                               }
+                       }
+                       if(hud_dynamic_shake_factor)
+                               if(!Hud_Shake_Update())
+                                       hud_dynamic_shake_factor = 0;
+               }
+       }
+}
+
 void HUD_Main()
 {
        int i;
@@ -491,6 +563,8 @@ void HUD_Main()
                return;
        }
 
+       Hud_Dynamic_Frame();
+
        // Drawing stuff
        if (hud_skin_prev != autocvar_hud_skin)
        {
index 7664abc2f165359ad1b0a849f8feda2e3b322069..3141fa75faf418c89da20b68d7be4d7ce7c4d71f 100644 (file)
@@ -155,6 +155,19 @@ float chat_sizey;
 
 float current_player;
 
+float autocvar_hud_dynamic_follow;
+float autocvar_hud_dynamic_follow_scale;
+
+vector hud_dynamic_shake_realofs;
+float hud_dynamic_shake_factor;
+float hud_dynamic_shake_time;
+
+// shared across viewmodel effects and dynamic hud code
+vector cl_followmodel_ofs;
+float cl_followmodel_time;
+
+vector hud_dynamic_ofs;
+
 float stringwidth_colors(string s, vector theSize);
 float stringwidth_nocolors(string s, vector theSize);
 void HUD_Panel_DrawProgressBar(vector theOrigin, vector theSize, string pic, float length_ratio, bool vertical, float baralign, vector theColor, float theAlpha, int drawflag);
@@ -393,6 +406,15 @@ REGISTER_HUD_PANEL(QUICKMENU,       HUD_QuickMenu,      quickmenu,      PANEL_CO
                panel_bg_padding = panel.current_panel_bg_padding;                                                          \
                panel_fg_alpha = panel.current_panel_fg_alpha * hud_fade_alpha;                                             \
        }                                                                                                               \
+       if(hud_dynamic_shake_factor > 0) panel_pos += hud_dynamic_shake_realofs; \
+       if(hud_dynamic_ofs.y) panel_pos.x += hud_dynamic_ofs.y * vid_conwidth; \
+       if(hud_dynamic_ofs.z) panel_pos.y += hud_dynamic_ofs.z * vid_conheight; \
+       if(hud_dynamic_ofs.x) { \
+               panel_size.x += hud_dynamic_ofs.x * panel_size.x; \
+               panel_size.y += hud_dynamic_ofs.x * panel_size.y; \
+               panel_pos.x += hud_dynamic_ofs.x * (panel_pos.x - vid_conwidth * 0.5); \
+               panel_pos.y += hud_dynamic_ofs.x * (panel_pos.y - vid_conheight * 0.5); \
+       } \
 } MACRO_END
 
 #define HUD_Panel_UpdatePosSize() MACRO_BEGIN {                                                                     \
index 07166105e5f6f043d99d65ccb57e95da60275e35..23d975f6ce00706468e2aa83b58e95b458c3adaa 100644 (file)
@@ -1262,6 +1262,7 @@ void HUD_Configure_Frame()
                        menu_enabled = 0;
                if(autocvar_hud_cursormode)
                        setcursormode(0);
+               hud_dynamic_shake_factor = -1;
        }
 }
 
index 8b6e585288ec54530db29d28101617b4e1483b72..bdffae574e8efa44a3ba203be2bc510e21e0ca1d 100644 (file)
@@ -537,6 +537,7 @@ NET_HANDLE(ENT_CLIENT_CLIENTDATA, bool isnew)
                // clear race stuff
                race_laptime = 0;
                race_checkpointtime = 0;
+               hud_dynamic_shake_factor = -1;
        }
        if (autocvar_hud_panel_healtharmor_progressbar_gfx)
        {
index 8d001cb5920979590328368c366d56ab5eb9c434..8aeac192633cdf5a84ef341f308a3dd221f65064 100644 (file)
@@ -114,117 +114,152 @@ float autocvar_cl_leanmodel_lowpass = 0.05;
        highpass(value.z, frac, ref_store.z, ref_out.z); \
 } MACRO_END
 
-void viewmodel_animate(entity this)
+void calc_followmodel_ofs(entity view)
 {
-       static float prevtime;
-       float frametime = (time - prevtime);
-       prevtime = time;
+       if(cl_followmodel_time == time)
+               return; // cl_followmodel_ofs already calculated for this frame
 
-       if (autocvar_chase_active) return;
-       if (STAT(HEALTH) <= 0) return;
+       float frac;
+       vector gunorg = '0 0 0';
+       static vector vel_average;
+       static vector gunorg_prev = '0 0 0';
+       static vector gunorg_adjustment_highpass;
+       static vector gunorg_adjustment_lowpass;
+
+       vector vel;
+       if (autocvar_cl_followmodel_velocity_absolute)
+               vel = view.velocity;
+       else
+       {
+               vector forward = '0 0 0', right = '0 0 0', up = '0 0 0';
+               MAKEVECTORS(makevectors, view_angles, forward, right, up);
+               vel.x = view.velocity * forward;
+               vel.y = view.velocity * right * -1;
+               vel.z = view.velocity * up;
+       }
 
-       entity view = CSQCModel_server2csqc(player_localentnum - 1);
+       vel.x = bound(vel_average.x - autocvar_cl_followmodel_limit, vel.x, vel_average.x + autocvar_cl_followmodel_limit);
+       vel.y = bound(vel_average.y - autocvar_cl_followmodel_limit, vel.y, vel_average.y + autocvar_cl_followmodel_limit);
+       vel.z = bound(vel_average.z - autocvar_cl_followmodel_limit, vel.z, vel_average.z + autocvar_cl_followmodel_limit);
 
-       bool clonground = !(view.anim_implicit_state & ANIMIMPLICITSTATE_INAIR);
-       static bool oldonground;
-       static float hitgroundtime;
-       if (clonground)
+       frac = avg_factor(autocvar_cl_followmodel_velocity_lowpass);
+       lowpass3(vel, frac, vel_average, gunorg);
+
+       gunorg *= -autocvar_cl_followmodel_speed * 0.042;
+
+       // perform highpass/lowpass on the adjustment vectors (turning velocity into acceleration!)
+       // trick: we must do the lowpass LAST, so the lowpass vector IS the final vector!
+       frac = avg_factor(autocvar_cl_followmodel_highpass);
+       highpass3(gunorg, frac, gunorg_adjustment_highpass, gunorg);
+       frac = avg_factor(autocvar_cl_followmodel_lowpass);
+       lowpass3(gunorg, frac, gunorg_adjustment_lowpass, gunorg);
+
+       if (autocvar_cl_followmodel_velocity_absolute)
        {
-               float f = time; // cl.movecmd[0].time
-               if (!oldonground)
-                       hitgroundtime = f;
+               vector fixed_gunorg;
+               vector forward = '0 0 0', right = '0 0 0', up = '0 0 0';
+               MAKEVECTORS(makevectors, view_angles, forward, right, up);
+               fixed_gunorg.x = gunorg * forward;
+               fixed_gunorg.y = gunorg * right * -1;
+               fixed_gunorg.z = gunorg * up;
+               gunorg = fixed_gunorg;
        }
-       oldonground = clonground;
 
+       cl_followmodel_ofs = gunorg;
+       cl_followmodel_time = time;
+}
 
-       bool teleported = view.csqcmodel_teleported;
-
+vector leanmodel_ofs(entity view)
+{
        float frac;
-       if(autocvar_cl_followmodel)
-       {
-               vector gunorg = '0 0 0';
-               static vector vel_average;
-               static vector gunorg_prev = '0 0 0';
-               static vector gunorg_adjustment_highpass;
-               static vector gunorg_adjustment_lowpass;
+       vector gunangles = '0 0 0';
+       static vector gunangles_prev = '0 0 0';
+       static vector gunangles_highpass = '0 0 0';
+       static vector gunangles_adjustment_highpass;
+       static vector gunangles_adjustment_lowpass;
 
-               vector vel;
-               if(autocvar_cl_followmodel_velocity_absolute)
-                       vel = view.velocity;
-               else
-               {
-                       vector forward = '0 0 0', right = '0 0 0', up = '0 0 0';
-                       MAKEVECTORS(makevectors, view_angles, forward, right, up);
-                       vel.x = view.velocity * forward;
-                       vel.y = view.velocity * right * -1;
-                       vel.z = view.velocity * up;
-               }
+       if (view.csqcmodel_teleported)
+               gunangles_prev = view_angles;
 
-               vel.x = bound(vel_average.x - autocvar_cl_followmodel_limit, vel.x, vel_average.x + autocvar_cl_followmodel_limit);
-               vel.y = bound(vel_average.y - autocvar_cl_followmodel_limit, vel.y, vel_average.y + autocvar_cl_followmodel_limit);
-               vel.z = bound(vel_average.z - autocvar_cl_followmodel_limit, vel.z, vel_average.z + autocvar_cl_followmodel_limit);
+       // in the highpass, we _store_ the DIFFERENCE to the actual view angles...
+       gunangles_highpass += gunangles_prev;
+       PITCH(gunangles_highpass) += 360 * floor((PITCH(view_angles) - PITCH(gunangles_highpass)) / 360 + 0.5);
+       YAW(gunangles_highpass) += 360 * floor((YAW(view_angles) - YAW(gunangles_highpass)) / 360 + 0.5);
+       ROLL(gunangles_highpass) += 360 * floor((ROLL(view_angles) - ROLL(gunangles_highpass)) / 360 + 0.5);
+       frac = avg_factor(autocvar_cl_leanmodel_highpass1);
+       highpass2_limited(view_angles, frac, autocvar_cl_leanmodel_limit, gunangles_highpass, gunangles);
+       gunangles_prev = view_angles;
+       gunangles_highpass -= gunangles_prev;
 
-               frac = avg_factor(autocvar_cl_followmodel_velocity_lowpass);
-               lowpass3(vel, frac, vel_average, gunorg);
+       PITCH(gunangles) *= -autocvar_cl_leanmodel_speed;
+       YAW(gunangles) *= -autocvar_cl_leanmodel_speed;
 
-               gunorg *= -autocvar_cl_followmodel_speed * 0.042;
+       // we assume here: PITCH = 0, YAW = 1, ROLL = 2
+       frac = avg_factor(autocvar_cl_leanmodel_highpass);
+       highpass2(gunangles, frac, gunangles_adjustment_highpass, gunangles);
+       frac = avg_factor(autocvar_cl_leanmodel_lowpass);
+       lowpass2(gunangles, frac, gunangles_adjustment_lowpass, gunangles);
 
-               // perform highpass/lowpass on the adjustment vectors (turning velocity into acceleration!)
-               // trick: we must do the lowpass LAST, so the lowpass vector IS the final vector!
-               frac = avg_factor(autocvar_cl_followmodel_highpass);
-               highpass3(gunorg, frac, gunorg_adjustment_highpass, gunorg);
-               frac = avg_factor(autocvar_cl_followmodel_lowpass);
-               lowpass3(gunorg, frac, gunorg_adjustment_lowpass, gunorg);
+       gunangles.x = -gunangles.x; // pitch was inverted, now that actually matters
 
-               if(autocvar_cl_followmodel_velocity_absolute)
-               {
-                       vector fixed_gunorg;
-                       vector forward = '0 0 0', right = '0 0 0', up = '0 0 0';
-                       MAKEVECTORS(makevectors, view_angles, forward, right, up);
-                       fixed_gunorg.x = gunorg * forward;
-                       fixed_gunorg.y = gunorg * right * -1;
-                       fixed_gunorg.z = gunorg * up;
-                       gunorg = fixed_gunorg;
-               }
+       return gunangles;
+}
 
-               this.origin += gunorg;
+vector bobmodel_ofs(entity view)
+{
+       bool clonground = !(view.anim_implicit_state & ANIMIMPLICITSTATE_INAIR);
+       static bool oldonground;
+       static float hitgroundtime;
+       if (clonground)
+       {
+               float f = time; // cl.movecmd[0].time
+               if (!oldonground)
+                       hitgroundtime = f;
        }
+       oldonground = clonground;
 
-       if(autocvar_cl_leanmodel)
+       // calculate for swinging gun model
+       // the gun bobs when running on the ground, but doesn't bob when you're in the air.
+       vector gunorg = '0 0 0';
+       static float bobmodel_scale = 0;
+       static float time_ofs = 0; // makes the effect always restart in the same way
+       if (clonground)
        {
-               vector gunangles = '0 0 0';
-               static vector gunangles_prev = '0 0 0';
-               static vector gunangles_highpass = '0 0 0';
-               static vector gunangles_adjustment_highpass;
-               static vector gunangles_adjustment_lowpass;
+               if (time - hitgroundtime > 0.05)
+                       bobmodel_scale = min(1, bobmodel_scale + frametime * 5);
+       }
+       else
+               bobmodel_scale = max(0, bobmodel_scale - frametime * 5);
 
-               if (teleported)
-                       gunangles_prev = view_angles;
+       float xyspeed = bound(0, vlen(vec2(view.velocity)), 400);
+       if (bobmodel_scale && xyspeed)
+       {
+               float bspeed = xyspeed * 0.01 * autocvar_cl_viewmodel_scale * bobmodel_scale;
+               float s = (time - time_ofs) * autocvar_cl_bobmodel_speed;
+               gunorg.y = bspeed * autocvar_cl_bobmodel_side * sin(s);
+               gunorg.z = bspeed * autocvar_cl_bobmodel_up * cos(s * 2);
+       }
+       else
+               time_ofs = time;
 
-               // in the highpass, we _store_ the DIFFERENCE to the actual view angles...
-               gunangles_highpass += gunangles_prev;
-               PITCH(gunangles_highpass) += 360 * floor((PITCH(view_angles) - PITCH(gunangles_highpass)) / 360 + 0.5);
-               YAW(gunangles_highpass) += 360 * floor((YAW(view_angles) - YAW(gunangles_highpass)) / 360 + 0.5);
-               ROLL(gunangles_highpass) += 360 * floor((ROLL(view_angles) - ROLL(gunangles_highpass)) / 360 + 0.5);
-               frac = avg_factor(autocvar_cl_leanmodel_highpass1);
-               highpass2_limited(view_angles, frac, autocvar_cl_leanmodel_limit, gunangles_highpass, gunangles);
-               gunangles_prev = view_angles;
-               gunangles_highpass -= gunangles_prev;
+       return gunorg;
+}
 
-               PITCH(gunangles) *= -autocvar_cl_leanmodel_speed;
-               YAW(gunangles) *= -autocvar_cl_leanmodel_speed;
+void viewmodel_animate(entity this)
+{
+       if (autocvar_chase_active) return;
+       if (STAT(HEALTH) <= 0) return;
 
-               // we assume here: PITCH = 0, YAW = 1, ROLL = 2
-               frac = avg_factor(autocvar_cl_leanmodel_highpass);
-               highpass2(gunangles, frac, gunangles_adjustment_highpass, gunangles);
-               frac = avg_factor(autocvar_cl_leanmodel_lowpass);
-               lowpass2(gunangles, frac, gunangles_adjustment_lowpass, gunangles);
+       entity view = CSQCModel_server2csqc(player_localentnum - 1);
 
-               gunangles.x = -gunangles.x; // pitch was inverted, now that actually matters
-               this.angles += gunangles;
+       if (autocvar_cl_followmodel)
+       {
+               calc_followmodel_ofs(view);
+               this.origin += cl_followmodel_ofs;
        }
 
-       float xyspeed = bound(0, vlen(vec2(view.velocity)), 400);
+       if (autocvar_cl_leanmodel)
+               this.angles += leanmodel_ofs(view);
 
        // vertical view bobbing code
        // TODO: cl_bob
@@ -238,31 +273,7 @@ void viewmodel_animate(entity this)
 
        // gun model bobbing code
        if (autocvar_cl_bobmodel)
-       {
-               // calculate for swinging gun model
-               // the gun bobs when running on the ground, but doesn't bob when you're in the air.
-               static float bobmodel_scale = 0;
-               static float time_ofs = 0; // makes the effect always restart in the same way
-               if (clonground)
-               {
-                       if (time - hitgroundtime > 0.05)
-                               bobmodel_scale = min(1, bobmodel_scale + frametime * 5);
-               }
-               else
-                       bobmodel_scale = max(0, bobmodel_scale - frametime * 5);
-               if(bobmodel_scale && xyspeed)
-               {
-                       float bspeed = xyspeed * 0.01 * autocvar_cl_viewmodel_scale * bobmodel_scale;
-                       float s = (time - time_ofs) * autocvar_cl_bobmodel_speed;
-                       vector gunorg = '0 0 0';
-                       gunorg.y = bspeed * autocvar_cl_bobmodel_side * sin(s);
-                       gunorg.z = bspeed * autocvar_cl_bobmodel_up * cos(s * 2);
-
-                       this.origin += gunorg;
-               }
-               else
-                       time_ofs = time;
-       }
+               this.origin += bobmodel_ofs(view);
 }
 
 .vector viewmodel_origin, viewmodel_angles;
@@ -272,7 +283,7 @@ void viewmodel_animate(entity this)
 
 void viewmodel_draw(entity this)
 {
-       if(!activeweapon)
+       if(!activeweapon || !autocvar_r_drawviewmodel)
                return;
        int mask = (intermission || (STAT(HEALTH) <= 0) || autocvar_chase_active) ? 0 : MASK_NORMAL;
        float a = this.alpha;
index becefc6607f2f8202cbe7963eefdaa3e80963630..6525e2779c5139f1e80b278eeff797dd85d09976 100644 (file)
@@ -158,6 +158,11 @@ void XonoticGameHUDSettingsTab_fill(entity me)
        me.TR(me);
                me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Damage overlay:")));
                me.TD(me, 1, 2, e = makeXonoticSlider(0, 1, 0.05, "hud_damage"));
+       me.TR(me);
+               me.TD(me, 1, 3, e = makeXonoticCheckBox_T(0, "hud_dynamic_follow", _("Dynamic HUD"),
+                       _("HUD moves around following player's movement")));
+       me.TR(me);
+               me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "hud_dynamic_shake", _("Shake the HUD when hurt")));
        me.TR(me);
        me.TR(me);
                me.TDempty(me, 0.5);