]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge remote-tracking branch 'origin/master' into samual/combined_updates
authorSamual Lenks <samual@xonotic.org>
Sun, 24 Nov 2013 03:50:18 +0000 (22:50 -0500)
committerSamual Lenks <samual@xonotic.org>
Sun, 24 Nov 2013 03:50:18 +0000 (22:50 -0500)
Conflicts:
qcsrc/client/View.qc
qcsrc/menu/xonotic/dialog_multiplayer_create.c
qcsrc/menu/xonotic/dialog_multiplayer_media_demo.c
qcsrc/menu/xonotic/dialog_multiplayer_playersetup_crosshair.c
qcsrc/menu/xonotic/dialog_multiplayer_profile.c
qcsrc/menu/xonotic/dialog_settings_audio.c
qcsrc/menu/xonotic/dialog_settings_effects.c
qcsrc/menu/xonotic/dialog_settings_game_view.c
qcsrc/menu/xonotic/dialog_settings_input.c
qcsrc/menu/xonotic/dialog_settings_user.c
qcsrc/menu/xonotic/dialog_settings_video.c
qcsrc/menu/xonotic/mainwindow.c
qcsrc/menu/xonotic/serverlist.c
qcsrc/menu/xonotic/util.qc

22 files changed:
1  2 
defaultXonotic.cfg
qcsrc/client/View.qc
qcsrc/common/util.qc
qcsrc/dpdefs/menudefs.qc
qcsrc/menu/menu.qc
qcsrc/menu/xonotic/dialog_multiplayer_create.c
qcsrc/menu/xonotic/dialog_multiplayer_media_demo.c
qcsrc/menu/xonotic/dialog_multiplayer_profile.c
qcsrc/menu/xonotic/dialog_settings_audio.c
qcsrc/menu/xonotic/dialog_settings_effects.c
qcsrc/menu/xonotic/dialog_settings_game_hud.c
qcsrc/menu/xonotic/dialog_settings_game_hudconfirm.c
qcsrc/menu/xonotic/dialog_settings_game_model.c
qcsrc/menu/xonotic/dialog_settings_game_view.c
qcsrc/menu/xonotic/dialog_settings_game_weapons.c
qcsrc/menu/xonotic/dialog_settings_input.c
qcsrc/menu/xonotic/dialog_settings_user.c
qcsrc/menu/xonotic/dialog_settings_video.c
qcsrc/menu/xonotic/mainwindow.c
qcsrc/menu/xonotic/serverlist.c
qcsrc/menu/xonotic/util.qc
qcsrc/server/cl_client.qc

diff --combined defaultXonotic.cfg
index 783fe06148a313c4f733b760c189921ae24769a7,217aab90a0c0603720b7870fb3ce70fb95fa03ad..53bbc5ab16b747d489f77be7f81eb67d9e11dc7f
@@@ -60,8 -60,7 +60,8 @@@ seta cl_reticle_stretch 0 "whether to s
  seta cl_reticle_item_nex 1 "draw aiming reticle for the nex weapon's zoom, 0 disables and values between 0 and 1 change alpha"
  seta cl_reticle_item_normal 1 "draw reticle when zooming with the zoom button, 0 disables and values between 0 and 1 change alpha"
  fov 90
 -seta cl_velocityzoom 0        "velocity based zooming of fov, negative values zoom out"
 +seta cl_velocityzoom 0        "velocity based zooming of fov"
 +seta cl_velocityzoom_factor 0 "factor of fov zooming (negative values zoom out)"
  seta cl_velocityzoom_type 3 "how to factor in speed, 1 = all velocity in all directions, 2 = velocity only in forward direction (can be negative), 3 = velocity only in forward direction (limited to forward only)"
  seta cl_velocityzoom_speed 1000 "target speed for fov factoring"
  seta cl_velocityzoom_time 0.2 "time value for averaging speed values"
@@@ -401,7 -400,6 +401,6 @@@ pausable 
  set g_spawnshieldtime 1 "number of seconds you are invincible after you spawned, this shield is lost after you fire"
  set g_antilag 2       "AntiLag (0 = no AntiLag, 1 = verified client side hit scan, 2 = server side hit scan in the past, 3 = unverified client side hit scan)"
  set g_antilag_nudge 0 "don't touch"
- set g_antilag_bullets 1 "Bullets AntiLag (0 = no AntiLag, 1 = server side hit scan in the past) - DO NOT TOUCH (severely changes weapon balance)"
  set g_shootfromclient 2 "let client decide if it has the gun left or right; if set to 2, center handedness is allowed; see also cl_gunalign"
  set g_shootfromeye 0 "shots are fired from your eye/crosshair; visual gun position can still be influenced by cl_gunalign 1 and 2"
  set g_shootfromcenter 0 "weapon gets moved to the center, shots still come from the barrel of your weapon; visual gun position can still be influenced by cl_gunalign 1 and 2"
@@@ -979,28 -977,6 +978,28 @@@ seta menu_slist_showfull 1 "show server
  seta menu_slist_showempty 1 "show servers even if they are no empty and have no opponents to play against"
  seta menu_slist_modfilter "" // set to either: !modname or modname. modname of = means "same as we are running now".
  
 +// other serverlist cvars
 +seta menu_slist_categories 0
 +seta menu_slist_categories_onlyifmultiple 1
 +seta menu_slist_purethreshold 0
 +seta menu_slist_modimpurity 0
 +seta menu_slist_recommendations 3
 +seta menu_slist_recommendations_maxping 150
 +seta menu_slist_recommendations_minfreeslots 1
 +seta menu_slist_recommendations_minhumans 0
 +seta menu_slist_recommendations_purethreshold -1
 +
 +// serverlist category override cvars
 +seta menu_slist_categories_CAT_FAVORITED_override ""
 +seta menu_slist_categories_CAT_RECOMMENDED_override ""
 +seta menu_slist_categories_CAT_NORMAL_override ""
 +seta menu_slist_categories_CAT_SERVERS_override "CAT_NORMAL"
 +seta menu_slist_categories_CAT_XPM_override "CAT_NORMAL"
 +seta menu_slist_categories_CAT_MODIFIED_override ""
 +seta menu_slist_categories_CAT_OVERKILL_override ""
 +seta menu_slist_categories_CAT_MINSTAGIB_override ""
 +seta menu_slist_categories_CAT_DEFRAG_override ""
 +
  seta menu_weaponarena ""
  
  seta menu_maxplayers 16 "maxplayers value when the menu starts a game"
@@@ -1437,7 -1413,7 +1436,7 @@@ sv_cullentities_trace 
  r_cullentities_trace 0
  
  // less "lagging" of other players, but also less PL tolerant... let's try this
- sv_clmovement_inputtimeout 0.04 // more than 1, less than 2 server frames
+ sv_clmovement_inputtimeout 0.066 // slightly less than 2 frames, so only one frame can be compensated
  
  // exact gloss looks better, e.g. on g-23
  r_shadow_glossexact 1
diff --combined qcsrc/client/View.qc
index 7a13f9562e5ae9f4fb6ec946c74cc653904aa0bc,8c9d59dd91f9a77e421d746a0b8193a8c449fb0f..3718e8139df581249862514575f28b3dfe7ad42d
@@@ -127,8 -127,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; }
        }
                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
 +      if(autocvar_cl_velocityzoom && autocvar_cl_velocityzoom_type && autocvar_cl_velocityzoom_factor) // _type = 0 disables velocity zoom too
        {
                if(intermission) { curspeed = 0; }
                else
                                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);
 +              velocityzoom = exp(float2range11(avgspeed * -autocvar_cl_velocityzoom_factor / 1) * 1);
-               
                //print(ftos(avgspeed), " avgspeed, ", ftos(curspeed), " curspeed, ", ftos(velocityzoom), " return\n"); // for debugging
        }
        else
@@@ -745,7 -745,7 +745,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;
                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
                reticle_type = 1; // normal zoom
        else if((activeweapon == WEP_NEX) && button_attack2)
                reticle_type = 2; // nex zoom
-     
        if(reticle_type && autocvar_cl_reticle)
        {
                if(autocvar_cl_reticle_stretch)
                        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
        {
                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, 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, ATTEN_NONE);
-                       
                nextsound_typehit_time = time + autocvar_cl_hitsound_antispam_time;
        }
  
                                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;
                        // 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);
                        wcross_origin_z = 0;
 -                      if(autocvar_crosshair_hittest)
 +                      
 +                      if(
 +                              autocvar_crosshair_hittest
 +                              &&
 +                              (
 +                                      autocvar_crosshair_hittest_blur
 +                                      ||
 +                                      autocvar_crosshair_hittest_scale
 +                                      ||
 +                                      autocvar_crosshair_hittest_showimpact
 +                              )
 +                      )
                        {
                                vector wcross_oldorigin;
                                wcross_oldorigin = wcross_origin;
                                if(!autocvar_crosshair_hittest_showimpact)
                                        wcross_origin = wcross_oldorigin;
                        }
 -                      else
 -                              shottype = SHOTTYPE_HITWORLD;
 +                      else { shottype = SHOTTYPE_HITWORLD; }
  
                        vector wcross_color = '0 0 0', wcross_size = '0 0 0';
                        string wcross_wep = "", wcross_name;
                        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;
                                }
  
                        if(autocvar_crosshair_hitindication)
                        {
                                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;
                                }
  
                        }
  
                        if(shottype == SHOTTYPE_HITENEMY)
 -                              wcross_scale *= autocvar_crosshair_hittest; // is not queried if hittest is 0
 +                              wcross_scale *= autocvar_crosshair_hittest_scale; // is not queried if hittest is 0
                        if(shottype == SHOTTYPE_HITTEAM)
 -                              wcross_scale /= autocvar_crosshair_hittest; // is not queried if hittest is 0
 +                              wcross_scale /= autocvar_crosshair_hittest_scale; // is not queried if hittest is 0
  
                        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)
                                        // 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;
                                                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;
                                                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;
                                        if(autocvar_crosshair_effect_time > 0)
                                        {
                                                f = (time - wcross_name_changestarttime) / autocvar_crosshair_effect_time;
-                                               if not(f < 1)
+                                               if (!(f < 1))
                                                {
                                                        wcross_ring_prev = ((ring_image) ? TRUE : FALSE);
                                                }
-                                               
                                                if(wcross_ring_prev)
                                                {
                                                        if(f < 1)
                                {
                                        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;
  
        if(autocvar__hud_configure)
                HUD_Panel_Mouse();
-     
      if(hud && !intermission)
-     {        
+     {
          if(hud == HUD_SPIDERBOT)
              CSQC_SPIDER_HUD();
          else if(hud == HUD_WAKIZASHI)
          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);
diff --combined qcsrc/common/util.qc
index f9b96031ab0a481d6d219f455b1f20c625a1d681,d1503a7b6b8bae9ea53c37c7bce2717ac64931f4..dcf1142c45c729c04cec476dd8dcf90097e9f799
@@@ -241,7 -241,7 +241,7 @@@ vector colormapPaletteColor(float c, fl
  string fstrunzone(string s)
  {
        string sc;
-       if not(s)
+       if (!s)
                return s;
        sc = strcat(s, "");
        strunzone(s);
@@@ -408,22 -408,6 +408,22 @@@ void buf_save(float buf, string pFilena
        fclose(fh);
  }
  
 +string format_time(float seconds)
 +{
 +      float days, hours, minutes;
 +      seconds = floor(seconds + 0.5);
 +        days = floor(seconds / 864000);
 +        seconds -= days * 864000;
 +        hours = floor(seconds / 36000);
 +        seconds -= hours * 36000;
 +      minutes = floor(seconds / 600);
 +      seconds -= minutes * 600;
 +        if (days > 0)
 +                return sprintf(_("%d days, %02d:%02d:%02d"), days, hours, minutes, seconds);
 +        else
 +                return sprintf(_("%02d:%02d:%02d"), hours, minutes, seconds);
 +}
 +
  string mmsss(float tenths)
  {
        float minutes;
@@@ -882,7 -866,7 +882,7 @@@ float cvar_settemp(string tmp_cvar, str
  
        created_saved_value = 0;
  
-       if not(tmp_cvar || tmp_value)
+       if (!(tmp_cvar || tmp_value))
        {
                dprint("Error: Invalid usage of cvar_settemp(string, string); !\n");
                return 0;
@@@ -1492,7 -1476,7 +1492,7 @@@ float isGametypeInFilter(float gt, floa
                if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
                if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
                {
-                       if not(subpattern4)
+                       if (!subpattern4)
                                return 0;
                        if(strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
                                return 0;
@@@ -1728,7 -1712,7 +1728,7 @@@ void check_unacceptable_compiler_bugs(
                error("fteqcc bug introduced with revision 3178 detected. Please upgrade fteqcc to a later revision, downgrade fteqcc to revision 3177, or pester Spike until he fixes it. You can set _allow_unacceptable_compiler_bugs 1 to skip this check, but expect stuff to be horribly broken then.");
  
        string s = "";
-       if not(s)
+       if (!s)
                error("The empty string counts as false. We do not want that!");
  }
  
@@@ -2068,7 -2052,7 +2068,7 @@@ float get_model_parameters(string m, fl
        }
        get_model_parameters_fixbone = 0;
  
-       if not(m)
+       if (!m)
                return 1;
  
        if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
@@@ -2527,7 -2511,7 +2527,7 @@@ float cubic_speedfunc_is_sane(float sta
  
           Therefore: there is an inflection point iff:
           e outside (3 - s)/2 .. 3 - s*2
-          
           in other words, if (s,e) in triangle (1,1)(0,3)(0,1.5) or in triangle (1,1)(3,0)(1.5,0)
        */
  }
@@@ -2667,15 -2651,15 +2667,15 @@@ float Announcer_PickNumber(float type, 
                        switch(num)
                        {
                                case 10: return ANNCE_NUM_GAMESTART_10;
-                               case 9:  return ANNCE_NUM_GAMESTART_9; 
-                               case 8:  return ANNCE_NUM_GAMESTART_8; 
-                               case 7:  return ANNCE_NUM_GAMESTART_7; 
-                               case 6:  return ANNCE_NUM_GAMESTART_6; 
-                               case 5:  return ANNCE_NUM_GAMESTART_5; 
-                               case 4:  return ANNCE_NUM_GAMESTART_4; 
-                               case 3:  return ANNCE_NUM_GAMESTART_3; 
-                               case 2:  return ANNCE_NUM_GAMESTART_2; 
-                               case 1:  return ANNCE_NUM_GAMESTART_1; 
+                               case 9:  return ANNCE_NUM_GAMESTART_9;
+                               case 8:  return ANNCE_NUM_GAMESTART_8;
+                               case 7:  return ANNCE_NUM_GAMESTART_7;
+                               case 6:  return ANNCE_NUM_GAMESTART_6;
+                               case 5:  return ANNCE_NUM_GAMESTART_5;
+                               case 4:  return ANNCE_NUM_GAMESTART_4;
+                               case 3:  return ANNCE_NUM_GAMESTART_3;
+                               case 2:  return ANNCE_NUM_GAMESTART_2;
+                               case 1:  return ANNCE_NUM_GAMESTART_1;
                        }
                        break;
                }
                        switch(num)
                        {
                                case 10: return ANNCE_NUM_IDLE_10;
-                               case 9:  return ANNCE_NUM_IDLE_9; 
-                               case 8:  return ANNCE_NUM_IDLE_8; 
-                               case 7:  return ANNCE_NUM_IDLE_7; 
-                               case 6:  return ANNCE_NUM_IDLE_6; 
-                               case 5:  return ANNCE_NUM_IDLE_5; 
-                               case 4:  return ANNCE_NUM_IDLE_4; 
-                               case 3:  return ANNCE_NUM_IDLE_3; 
-                               case 2:  return ANNCE_NUM_IDLE_2; 
-                               case 1:  return ANNCE_NUM_IDLE_1; 
+                               case 9:  return ANNCE_NUM_IDLE_9;
+                               case 8:  return ANNCE_NUM_IDLE_8;
+                               case 7:  return ANNCE_NUM_IDLE_7;
+                               case 6:  return ANNCE_NUM_IDLE_6;
+                               case 5:  return ANNCE_NUM_IDLE_5;
+                               case 4:  return ANNCE_NUM_IDLE_4;
+                               case 3:  return ANNCE_NUM_IDLE_3;
+                               case 2:  return ANNCE_NUM_IDLE_2;
+                               case 1:  return ANNCE_NUM_IDLE_1;
                        }
                        break;
                }
                        switch(num)
                        {
                                case 10: return ANNCE_NUM_KILL_10;
-                               case 9:  return ANNCE_NUM_KILL_9; 
-                               case 8:  return ANNCE_NUM_KILL_8; 
-                               case 7:  return ANNCE_NUM_KILL_7; 
-                               case 6:  return ANNCE_NUM_KILL_6; 
-                               case 5:  return ANNCE_NUM_KILL_5; 
-                               case 4:  return ANNCE_NUM_KILL_4; 
-                               case 3:  return ANNCE_NUM_KILL_3; 
-                               case 2:  return ANNCE_NUM_KILL_2; 
-                               case 1:  return ANNCE_NUM_KILL_1; 
+                               case 9:  return ANNCE_NUM_KILL_9;
+                               case 8:  return ANNCE_NUM_KILL_8;
+                               case 7:  return ANNCE_NUM_KILL_7;
+                               case 6:  return ANNCE_NUM_KILL_6;
+                               case 5:  return ANNCE_NUM_KILL_5;
+                               case 4:  return ANNCE_NUM_KILL_4;
+                               case 3:  return ANNCE_NUM_KILL_3;
+                               case 2:  return ANNCE_NUM_KILL_2;
+                               case 1:  return ANNCE_NUM_KILL_1;
                        }
                        break;
                }
                        switch(num)
                        {
                                case 10: return ANNCE_NUM_RESPAWN_10;
-                               case 9:  return ANNCE_NUM_RESPAWN_9; 
-                               case 8:  return ANNCE_NUM_RESPAWN_8; 
-                               case 7:  return ANNCE_NUM_RESPAWN_7; 
-                               case 6:  return ANNCE_NUM_RESPAWN_6; 
-                               case 5:  return ANNCE_NUM_RESPAWN_5; 
-                               case 4:  return ANNCE_NUM_RESPAWN_4; 
-                               case 3:  return ANNCE_NUM_RESPAWN_3; 
-                               case 2:  return ANNCE_NUM_RESPAWN_2; 
-                               case 1:  return ANNCE_NUM_RESPAWN_1; 
+                               case 9:  return ANNCE_NUM_RESPAWN_9;
+                               case 8:  return ANNCE_NUM_RESPAWN_8;
+                               case 7:  return ANNCE_NUM_RESPAWN_7;
+                               case 6:  return ANNCE_NUM_RESPAWN_6;
+                               case 5:  return ANNCE_NUM_RESPAWN_5;
+                               case 4:  return ANNCE_NUM_RESPAWN_4;
+                               case 3:  return ANNCE_NUM_RESPAWN_3;
+                               case 2:  return ANNCE_NUM_RESPAWN_2;
+                               case 1:  return ANNCE_NUM_RESPAWN_1;
                        }
                        break;
                }
                        switch(num)
                        {
                                case 10: return ANNCE_NUM_ROUNDSTART_10;
-                               case 9:  return ANNCE_NUM_ROUNDSTART_9; 
-                               case 8:  return ANNCE_NUM_ROUNDSTART_8; 
-                               case 7:  return ANNCE_NUM_ROUNDSTART_7; 
-                               case 6:  return ANNCE_NUM_ROUNDSTART_6; 
-                               case 5:  return ANNCE_NUM_ROUNDSTART_5; 
-                               case 4:  return ANNCE_NUM_ROUNDSTART_4; 
-                               case 3:  return ANNCE_NUM_ROUNDSTART_3; 
-                               case 2:  return ANNCE_NUM_ROUNDSTART_2; 
-                               case 1:  return ANNCE_NUM_ROUNDSTART_1; 
+                               case 9:  return ANNCE_NUM_ROUNDSTART_9;
+                               case 8:  return ANNCE_NUM_ROUNDSTART_8;
+                               case 7:  return ANNCE_NUM_ROUNDSTART_7;
+                               case 6:  return ANNCE_NUM_ROUNDSTART_6;
+                               case 5:  return ANNCE_NUM_ROUNDSTART_5;
+                               case 4:  return ANNCE_NUM_ROUNDSTART_4;
+                               case 3:  return ANNCE_NUM_ROUNDSTART_3;
+                               case 2:  return ANNCE_NUM_ROUNDSTART_2;
+                               case 1:  return ANNCE_NUM_ROUNDSTART_1;
                        }
                        break;
                }
                        switch(num)
                        {
                                case 10: return ANNCE_NUM_10;
-                               case 9:  return ANNCE_NUM_9; 
-                               case 8:  return ANNCE_NUM_8; 
-                               case 7:  return ANNCE_NUM_7; 
-                               case 6:  return ANNCE_NUM_6; 
-                               case 5:  return ANNCE_NUM_5; 
-                               case 4:  return ANNCE_NUM_4; 
-                               case 3:  return ANNCE_NUM_3; 
-                               case 2:  return ANNCE_NUM_2; 
-                               case 1:  return ANNCE_NUM_1; 
+                               case 9:  return ANNCE_NUM_9;
+                               case 8:  return ANNCE_NUM_8;
+                               case 7:  return ANNCE_NUM_7;
+                               case 6:  return ANNCE_NUM_6;
+                               case 5:  return ANNCE_NUM_5;
+                               case 4:  return ANNCE_NUM_4;
+                               case 3:  return ANNCE_NUM_3;
+                               case 2:  return ANNCE_NUM_2;
+                               case 1:  return ANNCE_NUM_1;
                        }
                        break;
                }
diff --combined qcsrc/dpdefs/menudefs.qc
index d976973253cb3251865edcb86da2d439c48cf6aa,21093868ece7d0b93e290587c1c24637e0657368..0d6c253709540b5f4f82ce8a9d50d7472a2dd21f
@@@ -18,7 -18,6 +18,7 @@@ void(float keynr, float ascii) m_keydow
  void(float width, float height) m_draw;
  void(float mode) m_toggle;
  void() m_shutdown;
 +// optional: float(float) m_gethostcachecategory;
  
  /////////////////////////////////////////////////////////
  // sys constants
@@@ -304,7 -303,7 +304,7 @@@ float      drawstring(vector position, strin
  float drawcolorcodedstring(vector position, string text, vector scale, float alpha, float flag) = #467;
  
  vector        drawcolorcodedstring2(vector position, string text, vector scale, vector rgb, float alpha, float flag) = #467;
-  
  float drawpic(vector position, string pic, vector size, vector rgb, float alpha, float flag) = #456;
  
  float drawfill(vector position, vector size, vector rgb, float alpha, float flag) = #457;
@@@ -563,11 -562,8 +563,11 @@@ void     resethostcachemasks(void) = #615
  void  sethostcachemaskstring(float mask, float fld, string str, float op) = #616;
  void  sethostcachemasknumber(float mask, float fld, float num, float op) = #617;
  void  resorthostcache(void) = #618;
 -void  sethostcachesort(float fld, float descending) = #619;
 -void  refreshhostcache(void) = #620;
 +float SLSF_DESCENDING = 1;
 +float SLSF_FAVORITES = 2;
 +float SLSF_CATEGORIES = 4;
 +void  sethostcachesort(float fld, float slsf) = #619;
 +void  refreshhostcache(...) = #620;  // optional boolean argument "clear_list"
  float gethostcachenumber(float fld, float hostnr) = #621;
  float gethostcacheindexforkey(string key) = #622;
  void  addwantedhostcachekey(string key) = #623;
diff --combined qcsrc/menu/menu.qc
index 99e8ea7d63c506b9d741aa5536f31352edac22c3,d5eff6e4935d89f7309b8e3856e0bfece0f1c13b..3e42dbaafbe792b54ac39fadb70ad2db3ca00a8b
@@@ -67,7 -67,7 +67,7 @@@ void m_init(
                for(i = 0; ; ++i)
                {
                        s = getgamedirinfo(i, GETGAMEDIRINFO_NAME);
-                       if not(s)
+                       if (!s)
                                break;
                        dprint(s, ": ", getgamedirinfo(i, GETGAMEDIRINFO_DESCRIPTION));
                }
@@@ -77,8 -77,6 +77,8 @@@
        CALL_ACCUMULATED_FUNCTION(RegisterWeapons);
        CALL_ACCUMULATED_FUNCTION(RegisterGametypes);
  
 +      RegisterSLCategories();
 +
        float ddsload = cvar("r_texture_dds_load");
        float texcomp = cvar("gl_texturecompression");
        updateCompression();
@@@ -93,8 -91,6 +93,8 @@@
                        m_hide();
                cvar_set("_menu_initialized", "1");
        }
 +
 +        PlayerInfo_Details();
  }
  
  const float MENU_ASPECT = 1.25; // 1280x1024
@@@ -994,7 -990,7 +994,7 @@@ void m_goto(string itemname
                for(e = NULL; (e = find(e, name, itemname)); )
                        if(e.classname != "vtbl")
                                break;
-                               
                if((e) && (!e.requiresConnection || (gamestatus & (GAME_ISSERVER | GAME_CONNECTED))))
                {
                        m_hide();
index 6d0bd906292b7e6b53dc81adf7755274be3e8f5a,60f52e1fa47ac649da8f0bb457f5eff79fe4e9d6..98295796f62ba2f1646f5bb84674e94ab7d06d00
@@@ -4,8 -4,8 +4,8 @@@ CLASS(XonoticServerCreateTab) EXTENDS(X
        METHOD(XonoticServerCreateTab, gameTypeChangeNotify, void(entity))
        ATTRIB(XonoticServerCreateTab, title, string, _("Create"))
        ATTRIB(XonoticServerCreateTab, intendedWidth, float, 0.9)
 -      ATTRIB(XonoticServerCreateTab, rows, float, 22)
 +      ATTRIB(XonoticServerCreateTab, rows, float, 23)
-       ATTRIB(XonoticServerCreateTab, columns, float, 6.2) // added extra .2 for center space 
+       ATTRIB(XonoticServerCreateTab, columns, float, 6.2) // added extra .2 for center space
  
        ATTRIB(XonoticServerCreateTab, mapListBox, entity, NULL)
        ATTRIB(XonoticServerCreateTab, sliderFraglimit, entity, NULL)
@@@ -30,7 -30,6 +30,7 @@@ void XonoticServerCreateTab_fill(entit
  {
        entity e, e0;
  
 +      me.TR(me);
        me.TR(me);
                me.TD(me, 1, 3, e = makeXonoticTextLabel(0, _("Game type:")));
        me.TR(me);
                        e.onClickEntity = main.advancedDialog;
                        main.advancedDialog.refilterEntity = me.mapListBox;
  
 -      me.gotoRC(me, 0, 3.2); me.setFirstColumn(me, me.currentColumn);
 +      me.gotoRC(me, 1, 3.2); me.setFirstColumn(me, me.currentColumn);
                me.mapListBox = makeXonoticMapList();
                me.TD(me, 1, 3, e = makeXonoticTextLabel(0, _("Map list:")));
                        makeCallback(e, me.mapListBox, me.mapListBox.refilterCallback);
        me.TR(me);
 -              me.TD(me, me.rows - 4, 3, me.mapListBox);
 +              me.TD(me, me.rows - 5, 3, me.mapListBox);
        me.gotoRC(me, me.rows - 3, 3.5);
                me.TDempty(me, 0.25);
                me.TD(me, 1, 1.125, e = makeXonoticButton(_("Select all"), '0 0 0'));
index 0004ce477e893bf5c68516fe9ba8bc01f3d977eb,0000000000000000000000000000000000000000..edd15c67cec8a76111a7fda84565e32b00e7bfb5
mode 100644,000000..100644
--- /dev/null
@@@ -1,62 -1,0 +1,62 @@@
-       ATTRIB(XonoticDemoBrowserTab, name, string, "DemoBrowser")      
 +#ifdef INTERFACE
 +CLASS(XonoticDemoBrowserTab) EXTENDS(XonoticTab)
 +      METHOD(XonoticDemoBrowserTab, fill, void(entity))
 +      ATTRIB(XonoticDemoBrowserTab, title, string, _("Demo"))
 +      ATTRIB(XonoticDemoBrowserTab, intendedWidth, float, 0.9)
 +      ATTRIB(XonoticDemoBrowserTab, rows, float, 21)
 +      ATTRIB(XonoticDemoBrowserTab, columns, float, 6.5)
-                       
++      ATTRIB(XonoticDemoBrowserTab, name, string, "DemoBrowser")
 +ENDCLASS(XonoticDemoBrowserTab)
 +entity makeXonoticDemoBrowserTab();
 +void Demo_Confirm(entity me, entity btn);
 +#endif
 +
 +#ifdef IMPLEMENTATION
 +void DemoConfirm_Check_Gamestatus(entity me, entity btn)
 +{
 +      if not(gamestatus & (GAME_CONNECTED | GAME_ISSERVER)) // we're not in a match, lets watch the demo
 +      {
 +              //TimeDemo_Click;
 +              //StartDemo_Click;
 +              return;
 +      }
 +      else // already in a match, player has to confirm
 +      {
 +              Demo_Confirm(me, btn);
 +      }
 +}
 +
 +entity makeXonoticDemoBrowserTab()
 +{
 +      entity me;
 +      me = spawnXonoticDemoBrowserTab();
 +      me.configureDialog(me);
 +      return me;
 +}
 +void XonoticDemoBrowserTab_fill(entity me)
 +{
 +      entity e, dlist;
 +
 +      me.TR(me);
 +              me.TD(me, 1, 4, e = makeXonoticCheckBox(0, "cl_autodemo", _("Automatically record demos while playing")));
 +      me.TR(me);
 +      me.TR(me);
 +              me.TD(me, 1, 0.5, e = makeXonoticTextLabel(0, _("Filter:")));
 +              me.TD(me, 1, 6, e = makeXonoticInputBox(0, string_null));
 +                      dlist = makeXonoticDemoList();
 +                      e.onChange = DemoList_Filter_Change;
 +                      e.onChangeEntity = dlist;
 +                      dlist.controlledTextbox = e;
-               
++
 +      me.TR(me);
 +              me.TD(me, me.rows - 4, me.columns, dlist);
++
 +      me.gotoRC(me, me.rows - 1, 0);
 +              me.TD(me, 1, me.columns / 2, e = makeXonoticButton(_("Timedemo"), '0 0 0'));
 +                      e.onClick = DemoConfirm_Check_Gamestatus;
 +                      e.onClickEntity = dlist;
 +              me.TD(me, 1, me.columns / 2, e = makeXonoticButton(ZCTX(_("DEMO^Play")), '0 0 0'));
 +                      e.onClick = DemoConfirm_Check_Gamestatus;
 +                      e.onClickEntity = dlist;
 +}
 +#endif
index 668d808bf02a81fa87daf8e885793553e2aa0a4c,0000000000000000000000000000000000000000..b50cb9951c0221550834a41a353e2abf9712df79
mode 100644,000000..100644
--- /dev/null
@@@ -1,214 -1,0 +1,214 @@@
-       ATTRIB(XonoticProfileTab, columns, float, 6.2) // added extra .2 for center space 
 +#ifdef INTERFACE
 +CLASS(XonoticProfileTab) EXTENDS(XonoticTab)
 +      METHOD(XonoticProfileTab, fill, void(entity))
 +      METHOD(XonoticProfileTab, draw, void(entity))
 +      ATTRIB(XonoticProfileTab, title, string, _("Profile"))
 +      ATTRIB(XonoticProfileTab, intendedWidth, float, 0.9)
 +      ATTRIB(XonoticProfileTab, rows, float, 23)
-               //me.TD(me, 1, 1, e0 = makeXonoticTextLabel(0, string_null)); 
++      ATTRIB(XonoticProfileTab, columns, float, 6.2) // added extra .2 for center space
 +      ATTRIB(XonoticProfileTab, playerNameLabel, entity, NULL)
 +      ATTRIB(XonoticProfileTab, playerNameLabelAlpha, float, 0)
 +ENDCLASS(XonoticProfileTab)
 +entity makeXonoticProfileTab();
 +#endif
 +
 +#ifdef IMPLEMENTATION
 +entity makeXonoticProfileTab()
 +{
 +      entity me;
 +      me = spawnXonoticProfileTab();
 +      me.configureDialog(me);
 +      return me;
 +}
 +void XonoticProfileTab_draw(entity me)
 +{
 +      if(cvar_string("_cl_name") == "Player")
 +              me.playerNameLabel.alpha = ((mod(time * 2, 2) < 1) ? 1 : 0);
 +      else
 +              me.playerNameLabel.alpha = me.playerNameLabelAlpha;
 +      SUPER(XonoticProfileTab).draw(me);
 +}
 +void XonoticProfileTab_fill(entity me)
 +{
 +      entity e, pms, label, box;
 +      float i;
 +
 +      me.TR(me);
 +      me.TR(me);
 +              me.TD(me, 1, 0.5, me.playerNameLabel = makeXonoticTextLabel(0, _("Name:")));
 +                      me.playerNameLabelAlpha = me.playerNameLabel.alpha;
 +              me.TD(me, 1, 2.5, label = makeXonoticTextLabel(0, string_null));
 +                      label.allowCut = 1;
 +                      label.allowColors = 1;
 +                      label.alpha = 1;
 +      me.TR(me);
 +              me.TD(me, 1, 3.0, box = makeXonoticInputBox(1, "_cl_name"));
 +                      box.forbiddenCharacters = "\r\n\\\"$"; // don't care, isn't getting saved
 +                      box.maxLength = -127; // negative means encoded length in bytes
 +                      box.saveImmediately = 1;
 +                      box.enableClearButton = 0;
 +                      label.textEntity = box;
 +      me.TR(me);
 +              me.TD(me, 5, 1, e = makeXonoticColorpicker(box));
 +              me.TD(me, 5, 2, e = makeXonoticCharmap(box));
 +      me.TR(me);
 +      me.TR(me);
 +      me.TR(me);
 +      me.TR(me);
 +      me.TR(me);
 +      
 +      // Statistic Stuff -Debugger
 +      me.TR(me);
 +      me.TDempty(me, 0.5);
 +      me.TD(me, 1, 2, e = makeXonoticTextLabel(0.5, _("Player Statistics:")));
 +      me.TR(me);
 +      me.TR(me);
 +      me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Join time:")));
 +      me.TR(me);
 +      me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Total playing time:")));
 +      me.TR(me);
 +      me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Last played:")));
 +      me.TR(me);
 +      me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Games played:")));
 +      me.TR(me);
 +      me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Win / Losses:")));
 +      me.TR(me);
 +      me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Kills / Deaths:")));
 +      me.TR(me);
 +      me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("CTF elo:")));
 +      me.TR(me);
 +      me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("DM elo:")));
 +      me.TR(me);
 +      me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("TDM elo:")));
 +      me.TR(me);
 +      me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("DUEL elo:")));
 +      me.TR(me);
 +      
 +
 +      me.gotoRC(me, 1, 3.2); me.setFirstColumn(me, me.currentColumn);
 +              me.TDempty(me, 1);
 +              me.TD(me, 1, 3, e = makeXonoticTextLabel(0.5, _("Model:")));
 +      me.TR(me);
 +              pms = makeXonoticPlayerModelSelector();
 +              me.TD(me, 1, 0.3, e = makeXonoticButton("<<", '0 0 0'));
 +                      e.onClick = PlayerModelSelector_Prev_Click;
 +                      e.onClickEntity = pms;
 +              me.TD(me, 13, 2.4, pms);
 +              me.TD(me, 1, 0.3, e = makeXonoticButton(">>", '0 0 0'));
 +                      e.onClick = PlayerModelSelector_Next_Click;
 +                      e.onClickEntity = pms;
 +
 +      me.gotoRC(me, 15, 3.533); me.setFirstColumn(me, me.currentColumn);
 +      me.TR(me);
 +              me.TD(me, 1, 1, e = makeXonoticTextLabel(0.5, _("Glowing color:")));
 +              for(i = 0; i < 15; ++i)
 +              {
 +                      if(mod(i, 5) == 0)
 +                              me.TR(me);
 +                      me.TDNoMargin(me, 1, 0.2, e = makeXonoticColorButton(1, 0, i), '0 1 0');
 +              }
 +      me.gotoRC(me, 15, 4.866); me.setFirstColumn(me, me.currentColumn);
 +      me.TR(me);
 +              me.TD(me, 1, 1, e = makeXonoticTextLabel(0.5, _("Detail color:")));
 +              for(i = 0; i < 15; ++i)
 +              {
 +                      if(mod(i, 5) == 0)
 +                              me.TR(me);
 +                      me.TDNoMargin(me, 1, 0.2, e = makeXonoticColorButton(2, 1, i), '0 1 0');
 +              }
 +
 +      /*
 +      // crosshair_enabled: 0 = no crosshair options, 1 = no crosshair selection, but everything else enabled, 2 = all crosshair options enabled
 +      // FIXME: In the future, perhaps make one global crosshair_type cvar which has 0 for disabled, 1 for custom, 2 for per weapon, etc?
 +      me.gotoRC(me, 0, 3.2); me.setFirstColumn(me, me.currentColumn);
 +              me.TD(me, 1, 3, e = makeXonoticRadioButton(3, "crosshair_enabled", "0", _("No crosshair")));
 +      me.TR(me);
 +              me.TD(me, 1, 3, e = makeXonoticRadioButton(3, "crosshair_per_weapon", string_null, _("Per weapon crosshair")));
 +              makeMulti(e, "crosshair_enabled");
 +      me.TR(me);
 +              me.TD(me, 1, 3, e = makeXonoticRadioButton(3, "crosshair_enabled", "2", _("Custom crosshair")));
 +      me.TR(me);
 +              me.TDempty(me, 0.1);
 +              for(i = 1; i <= 14; ++i) {
 +                      me.TDNoMargin(me, 1, 2 / 14, e = makeXonoticCrosshairButton(4, i), '1 1 0');
 +                              setDependentAND(e, "crosshair_per_weapon", 0, 0, "crosshair_enabled", 1, 2);
 +              }
 +              // show a larger preview of the selected crosshair
 +              me.TDempty(me, 0.1);
 +              me.TDNoMargin(me, 3, 0.8, e = makeXonoticCrosshairButton(7, -1), '1 1 0'); // crosshair -1 makes this a preview
 +                      setDependentAND(e, "crosshair_per_weapon", 0, 0, "crosshair_enabled", 1, 2);
 +      me.TR(me);
 +              me.TDempty(me, 0.1);
 +              for(i = 15; i <= 28; ++i) {
 +                      me.TDNoMargin(me, 1, 2 / 14, e = makeXonoticCrosshairButton(4, i), '1 1 0');
 +                              setDependentAND(e, "crosshair_per_weapon", 0, 0, "crosshair_enabled", 1, 2);
 +              }
 +      me.TR(me);
 +      me.TR(me);
 +              me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Crosshair size:")));
 +                      setDependent(e, "crosshair_enabled", 1, 2);
 +              me.TD(me, 1, 2, e = makeXonoticSlider(0.1, 1.0, 0.01, "crosshair_size"));
 +                      setDependent(e, "crosshair_enabled", 1, 2);
 +      me.TR(me);
 +              me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Crosshair alpha:")));
 +                      setDependent(e, "crosshair_enabled", 1, 2);
 +              me.TD(me, 1, 2, e = makeXonoticSlider(0, 1, 0.1, "crosshair_alpha"));
 +                      setDependent(e, "crosshair_enabled", 1, 2);
 +      me.TR(me);
 +      me.TR(me);
 +              me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Crosshair color:")));
 +                      setDependent(e, "crosshair_enabled", 1, 2);
 +              me.TD(me, 1, 1, e = makeXonoticRadioButton(5, "crosshair_color_special", "1", _("Per weapon")));
 +                      setDependent(e, "crosshair_enabled", 1, 2);
 +              me.TD(me, 1, 1, e = makeXonoticRadioButton(5, "crosshair_color_special", "2", _("By health")));
 +                      setDependent(e, "crosshair_enabled", 1, 2);
 +      me.TR(me);
 +              me.TDempty(me, 0.1);
 +              me.TD(me, 1, 0.9, e = makeXonoticRadioButton(5, "crosshair_color_special", "0", _("Custom")));
 +                      setDependent(e, "crosshair_enabled", 1, 2);
 +              me.TD(me, 2, 2, e = makeXonoticColorpickerString("crosshair_color", "crosshair_color"));
 +                      setDependentAND(e, "crosshair_color_special", 0, 0, "crosshair_enabled", 1, 2);
 +      me.TR(me);
 +      me.TR(me);
 +      me.TR(me);
 +              me.TDempty(me, 0.5);
 +              me.TD(me, 1, 2, e = makeXonoticButton(_("Other crosshair settings"), '0 0 0'));
 +                      e.onClick = DialogOpenButton_Click;
 +                      e.onClickEntity = main.crosshairDialog;
 +              setDependent(e, "crosshair_enabled", 1, 2);
 +              // TODO: show status of crosshair dot and hittest and pickups and such here with text
 +      me.TR(me);
 +      me.TR(me);
 +              me.TDempty(me, 0.5);
 +              me.TD(me, 1, 2, e = makeXonoticButton(_("Model settings"), '0 0 0'));
 +                      e.onClick = DialogOpenButton_Click;
 +                      e.onClickEntity = main.modelDialog;
 +              // TODO: show csqc model settings like forcemyplayer and deglowing/ghosting bodies with text here
 +      me.TR(me);
 +              me.TDempty(me, 0.5);
 +              me.TD(me, 1, 2, e = makeXonoticButton(_("View settings"), '0 0 0'));
 +                      e.onClick = DialogOpenButton_Click;
 +                      e.onClickEntity = main.viewDialog;
 +              // TODO: show fov and other settings with text here
 +      me.TR(me);
 +              me.TDempty(me, 0.5);
 +              me.TD(me, 1, 2, e = makeXonoticButton(_("Weapon settings"), '0 0 0'));
 +                      e.onClick = DialogOpenButton_Click;
 +                      e.onClickEntity = main.weaponsDialog;
 +              // I don't really think this is useful as is, and especially it doesn't look very clean...
 +              // In the future, if ALL of these buttons had some information, then it would be justified/clean
-               // TODO: show hud config name with text here 
++              //me.TD(me, 1, 1, e0 = makeXonoticTextLabel(0, string_null));
 +              //      e0.textEntity = main.weaponsDialog;
 +              //      e0.allowCut = 1;
 +      me.TR(me);
 +              me.TDempty(me, 0.5);
 +              me.TD(me, 1, 2, e = makeXonoticButton(_("HUD settings"), '0 0 0'));
 +                      e.onClick = DialogOpenButton_Click;
 +                      e.onClickEntity = main.hudDialog;
++              // TODO: show hud config name with text here
 +
 +      */
 +      me.gotoRC(me, me.rows - 1, 0);
 +              me.TD(me, 1, me.columns, makeXonoticCommandButton(_("Apply immediately"), '0 0 0', "color -1 -1;name \"$_cl_name\";sendcvar cl_weaponpriority;sendcvar cl_autoswitch;sendcvar cl_forceplayermodels;sendcvar cl_forceplayermodelsfromxonotic;playermodel $_cl_playermodel;playerskin $_cl_playerskin", COMMANDBUTTON_APPLY));
 +}
 +#endif
index 0701b914743beed8d2f4a351d854ff3e44a7823d,f3c27915e781206f53fe4543614ab741497fc3bf..03c08da55071f0b370d2b5fefcf48f46f2632aae
@@@ -3,8 -3,8 +3,8 @@@ CLASS(XonoticAudioSettingsTab) EXTENDS(
        METHOD(XonoticAudioSettingsTab, fill, void(entity))
        ATTRIB(XonoticAudioSettingsTab, title, string, _("Audio"))
        ATTRIB(XonoticAudioSettingsTab, intendedWidth, float, 0.9)
 -      ATTRIB(XonoticAudioSettingsTab, rows, float, 17)
 +      ATTRIB(XonoticAudioSettingsTab, rows, float, 15.5)
-       ATTRIB(XonoticAudioSettingsTab, columns, float, 6.2) // added extra .2 for center space 
+       ATTRIB(XonoticAudioSettingsTab, columns, float, 6.2) // added extra .2 for center space
  ENDCLASS(XonoticAudioSettingsTab)
  entity makeXonoticAudioSettingsTab();
  #endif
@@@ -97,7 -97,7 +97,7 @@@ void XonoticAudioSettingsTab_fill(entit
                me.TD(me, 1, 3, makeXonoticCheckBox(0, "menu_snd_attenuation_method", _("New style sound attenuation")));
        me.TR(me);
                me.TD(me, 1, 3, makeXonoticCheckBox(0, "snd_mutewhenidle", _("Mute sounds when not active")));
-       
        me.gotoRC(me, 0, 3.2); me.setFirstColumn(me, me.currentColumn);
                me.TD(me, 1, 1, makeXonoticTextLabel(0, _("Frequency:")));
                me.TD(me, 1, 2, e = makeXonoticTextSlider("snd_speed"));
                if(cvar("developer"))
                        me.TD(me, 1, 3, makeXonoticCheckBox(0, "showsound", _("Debug info about sounds")));
  
 -      me.gotoRC(me, me.rows - 1, 0);
 +      me.gotoRC(me, me.rows - 1.25, 0);
                me.TD(me, 1, me.columns, makeXonoticCommandButton(_("Apply immediately"), '0 0 0', "snd_restart; snd_attenuation_method_$menu_snd_attenuation_method; sendcvar cl_hitsound; sendcvar cl_autotaunt; sendcvar cl_voice_directional; sendcvar cl_voice_directional_taunt_attenuation", COMMANDBUTTON_APPLY));
  }
  #endif
index 486fedb1882715b00b898717bd53d0fb538334bf,a963d1e3099862fb485fff19d842f5f5206522b8..c6c2ffcea5f839fea3b815dfe41ae318de824831
@@@ -3,8 -3,8 +3,8 @@@ CLASS(XonoticEffectsSettingsTab) EXTEND
        METHOD(XonoticEffectsSettingsTab, fill, void(entity))
        ATTRIB(XonoticEffectsSettingsTab, title, string, _("Effects"))
        ATTRIB(XonoticEffectsSettingsTab, intendedWidth, float, 0.9)
 -      ATTRIB(XonoticEffectsSettingsTab, rows, float, 17)
 +      ATTRIB(XonoticEffectsSettingsTab, rows, float, 15.5)
-       ATTRIB(XonoticEffectsSettingsTab, columns, float, 6.2) // added extra .2 for center space 
+       ATTRIB(XonoticEffectsSettingsTab, columns, float, 6.2) // added extra .2 for center space
  ENDCLASS(XonoticEffectsSettingsTab)
  entity makeXonoticEffectsSettingsTab();
  float updateCompression();
@@@ -47,7 -47,8 +47,7 @@@ void XonoticEffectsSettingsTab_fill(ent
                if(cvar("developer"))
                        me.TD(me, 1, 5 / n, e = makeXonoticCommandButton(ZCTX(_("PRE^Ultimate")), '0.5 0 0', "exec effects-ultimate.cfg", 0));
  
 -      me.TR(me);
 -      me.TR(me);
 +      me.gotoRC(me, 1.5, 0);
                me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Geometry detail:")));
                me.TD(me, 1, 2, e = makeXonoticTextSlider("r_subdivisions_tolerance"));
                        e.addValue(e, ZCTX(_("DET^Lowest")), "16");
                        setDependentAND(e, "vid_gl20", 1, 1, "r_water", 1, 1);
        me.TR(me);
        me.TR(me);
 -              me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Particles quality:")));
 -              me.TD(me, 1, 2, e = makeXonoticSlider(0.2, 1.0, 0.1, "cl_particles_quality"));
 -      me.TR(me);
 -              me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Particles distance:")));
 -              me.TD(me, 1, 2, e = makeXonoticSlider(500, 2000, 100, "r_drawparticles_drawdistance"));
 +              me.TD(me, 1, 1, e = makeXonoticCheckBox(0, "cl_decals", _("Decals")));
 +              me.TD(me, 1, 2, e = makeXonoticCheckBox(0, "cl_decals_models", _("Decals on models")));
 +                      setDependent(e, "cl_decals", 1, 1);
        me.TR(me);
 -      me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Damage effects:")));
 -              me.TD(me, 1, 2, e = makeXonoticTextSlider("cl_damageeffect"));
 -                      e.addValue(e, ZCTX(_("DMGPRTCLS^Disabled")), "0");
 -                      e.addValue(e, ZCTX(_("DMGPRTCLS^Skeletal")), "1");
 -                      e.addValue(e, ZCTX(_("DMGPRTCLS^All")), "2");
 -                      e.configureXonoticTextSliderValues(e);
 +              me.TDempty(me, 0.2);
 +              me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Distance:")));
 +                      setDependent(e, "cl_decals", 1, 1);
 +              me.TD(me, 1, 2, e = makeXonoticSlider(200, 500, 20, "r_drawdecals_drawdistance"));
 +                      setDependent(e, "cl_decals", 1, 1);
        me.TR(me);
 -              me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "cl_spawn_point_particles", _("Particle effects for spawnpoints")));
 -              makeMulti(e, "cl_spawn_event_particles");
 +              me.TDempty(me, 0.2);
 +          me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Time:")));
 +              setDependent(e, "cl_decals", 1, 1);
 +          me.TD(me, 1, 2, e = makeXonoticSlider(1, 20, 1, "cl_decals_time"));
 +              setDependent(e, "cl_decals", 1, 1);
  
 -      me.gotoRC(me, 2, 3.2); me.setFirstColumn(me, me.currentColumn);
 +      me.gotoRC(me, 1.5, 3.2); me.setFirstColumn(me, me.currentColumn);
                me.TD(me, 1, 3, e = makeXonoticRadioButton(1, "r_coronas", "0", _("No dynamic lighting")));
        me.TR(me);
                me.TD(me, 1, 3, e = makeXonoticRadioButton(1, "gl_flashblend", string_null, _("Fake corona lighting")));
                me.TD(me, 1, 2, s);
        me.TR(me);
        me.TR(me);
 -              me.TD(me, 1, 1, e = makeXonoticCheckBox(0, "cl_decals", _("Decals")));
 -              me.TD(me, 1, 2, e = makeXonoticCheckBox(0, "cl_decals_models", _("Decals on models")));
 -                      setDependent(e, "cl_decals", 1, 1);
 +              me.TD(me, 1, 1, e = makeXonoticCheckBox(0, "cl_particles", _("Particles")));
 +              me.TD(me, 1, 2, e = makeXonoticCheckBox(0, "cl_spawn_point_particles", _("Spawnpoint effects")));
 +                      makeMulti(e, "cl_spawn_event_particles");
 +                      setDependent(e, "cl_particles", 1, 1);
        me.TR(me);
                me.TDempty(me, 0.2);
 -              me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Distance:")));
 -                      setDependent(e, "cl_decals", 1, 1);
 -              me.TD(me, 1, 2, e = makeXonoticSlider(200, 500, 20, "r_drawdecals_drawdistance"));
 -                      setDependent(e, "cl_decals", 1, 1);
 -      me.TR(me);
 -              me.TDempty(me, 0.2);
 -          me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Time:")));
 -              setDependent(e, "cl_decals", 1, 1);
 -          me.TD(me, 1, 2, e = makeXonoticSlider(1, 20, 1, "cl_decals_time"));
 -              setDependent(e, "cl_decals", 1, 1);
 +              me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Quality:")));
 +                      setDependent(e, "cl_particles", 1, 1);
 +              me.TD(me, 1, 2, e = makeXonoticParticlesSlider());
 +                      setDependent(e, "cl_particles", 1, 1);
  
 -      me.gotoRC(me, me.rows - 1, 0);
 +      me.gotoRC(me, me.rows - 1.25, 0);
                me.TD(me, 1, me.columns, makeXonoticCommandButton(_("Apply immediately"), '0 0 0', "vid_restart", COMMANDBUTTON_APPLY));
  }
  #endif
index e53f99bc1fec29b5a6b90f46cf30aa51f3c9c585,0000000000000000000000000000000000000000..8d969fd7a3ef35fce3a0ba58cdc2bd3208d3e008
mode 100644,000000..100644
--- /dev/null
@@@ -1,103 -1,0 +1,103 @@@
-       if not(gamestatus & (GAME_CONNECTED | GAME_ISSERVER)) // we're not in a match, ask the player if they want to start one anyway
 +#ifdef INTERFACE
 +CLASS(XonoticHUDDialog) EXTENDS(XonoticDialog)
 +      METHOD(XonoticHUDDialog, toString, string(entity))
 +      METHOD(XonoticHUDDialog, fill, void(entity))
 +      METHOD(XonoticHUDDialog, showNotify, void(entity))
 +      ATTRIB(XonoticHUDDialog, title, string, _("HUD settings"))
 +      ATTRIB(XonoticHUDDialog, color, vector, SKINCOLOR_DIALOG_HUD)
 +      ATTRIB(XonoticHUDDialog, intendedWidth, float, 0.5)
 +      ATTRIB(XonoticHUDDialog, rows, float, 18)
 +      ATTRIB(XonoticHUDDialog, columns, float, 3)
 +ENDCLASS(XonoticHUDDialog)
 +void HUDSetup_Start(entity me, entity btn);
 +#endif
 +
 +#ifdef IMPLEMENTATION
 +void HUDSetup_Check_Gamestatus(entity me, entity btn)
 +{
-       return "hi"; // TODO: show hud config name with text here 
++      if(!(gamestatus & (GAME_CONNECTED | GAME_ISSERVER))) // we're not in a match, ask the player if they want to start one anyway
 +      {
 +              DialogOpenButton_Click(me, main.hudconfirmDialog);
 +      }
 +      else // already in a match, lets just cut to the point and open up the hud editor directly
 +      {
 +              HUDSetup_Start(me, btn);
 +      }
 +}
 +void XonoticHUDDialog_showNotify(entity me)
 +{
 +      loadAllCvars(me);
 +}
 +string XonoticHUDDialog_toString(entity me)
 +{
-       
++      return "hi"; // TODO: show hud config name with text here
 +}
 +void XonoticHUDDialog_fill(entity me)
 +{
 +      entity e;
-       
++
 +      me.TR(me);
 +              me.TD(me, 1, 3, e = makeXonoticTextLabel(0, _("Damage:")));
 +      me.TR(me);
 +              me.TDempty(me, 0.2);
 +              me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Overlay:")));
 +              me.TD(me, 1, 2, e = makeXonoticSlider(0, 1, 0.05, "hud_damage"));
 +      me.TR(me);
 +              me.TDempty(me, 0.2);
 +              me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Factor:")));
 +              setDependent(e, "hud_damage", 0.001, 100);
 +              me.TD(me, 1, 2, e = makeXonoticSlider(0.025, 0.1, 0.025, "hud_damage_factor"));
 +              setDependent(e, "hud_damage", 0.001, 100);
 +      me.TR(me);
 +              me.TDempty(me, 0.2);
 +              me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Fade rate:")));
 +              setDependent(e, "hud_damage", 0.001, 100);
 +              me.TD(me, 1, 2, e = makeXonoticSlider(0.25, 1, 0.05, "hud_damage_fade_rate"));
 +              setDependent(e, "hud_damage", 0.001, 100);
 +      me.TR(me);
-       
++
 +      me.TR(me);
 +              me.TD(me, 1, 3, e = makeXonoticCheckBox(1, "cl_hidewaypoints", _("Waypoints")));
 +      me.TR(me);
 +              me.TDempty(me, 0.2);
 +              me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Scale:")));
 +              setDependent(e, "cl_hidewaypoints", 0, 0);
 +              me.TD(me, 1, 2, e = makeXonoticSlider(0.5, 1.5, 0.05, "g_waypointsprite_scale"));
 +              setDependent(e, "cl_hidewaypoints", 0, 0);
 +      me.TR(me);
 +              me.TDempty(me, 0.2);
 +              me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Alpha:")));
 +              setDependent(e, "cl_hidewaypoints", 0, 0);
 +              me.TD(me, 1, 2, e = makeXonoticSlider(0.1, 1, 0.05, "g_waypointsprite_alpha"));
 +              setDependent(e, "cl_hidewaypoints", 0, 0);
 +      me.TR(me);
 +              me.TDempty(me, 0.2);
 +              me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Edge offset:")));
 +              setDependent(e, "cl_hidewaypoints", 0, 0);
 +              me.TD(me, 1, 2, e = makeXonoticSlider(0, 0.3, 0.01, "g_waypointsprite_edgeoffset_bottom"));
 +              makeMulti(e, "g_waypointsprite_edgeoffset_top g_waypointsprite_edgeoffset_left g_waypointsprite_edgeoffset_right");
 +              setDependent(e, "cl_hidewaypoints", 0, 0);
 +      me.TR(me);
-               // TODO: show hud config name with text here 
-               
++
 +      me.TR(me);
 +              me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "hud_shownames", _("Show names above players")));
 +      me.TR(me);
 +              me.TDempty(me, 0.2);
 +              me.TD(me, 1, 2.8, e = makeXonoticCheckBoxEx(25, 0, "hud_shownames_crosshairdistance", _("Only when near crosshair")));
 +      me.TR(me);
 +              me.TDempty(me, 0.2);
 +              me.TD(me, 1, 2.8, e = makeXonoticCheckBox(0, "hud_shownames_status", _("Display health and armor")));
 +      me.TR(me);
 +      me.TR(me);
 +              me.TDempty(me, 0.5);
 +              me.TD(me, 1, 2, e = makeXonoticButton(_("Enter HUD editor"), '0 0 0'));
 +                      e.onClick = HUDSetup_Check_Gamestatus;
 +                      e.onClickEntity = me;
- #endif
++              // TODO: show hud config name with text here
++
 +      me.gotoRC(me, me.rows - 1, 0);
 +              me.TD(me, 1, me.columns, e = makeXonoticButton(_("OK"), '0 0 0'));
 +                      e.onClick = Dialog_Close;
 +                      e.onClickEntity = me;
 +}
++#endif
index b06dc9207854e96fd62303ed29961524e900b033,0000000000000000000000000000000000000000..7749a148d96a5a6e77d5bb2c57123c19b106d528
mode 100644,000000..100644
--- /dev/null
@@@ -1,40 -1,0 +1,40 @@@
-       if not(gamestatus & (GAME_CONNECTED | GAME_ISSERVER))
 +#ifdef INTERFACE
 +CLASS(XonoticHUDConfirmDialog) EXTENDS(XonoticDialog)
 +      METHOD(XonoticHUDConfirmDialog, fill, void(entity))
 +      ATTRIB(XonoticHUDConfirmDialog, title, string, _("Enter HUD editor"))
 +      ATTRIB(XonoticHUDConfirmDialog, color, vector, SKINCOLOR_DIALOG_HUDCONFIRM)
 +      ATTRIB(XonoticHUDConfirmDialog, intendedWidth, float, 0.5)
 +      ATTRIB(XonoticHUDConfirmDialog, rows, float, 4)
 +      ATTRIB(XonoticHUDConfirmDialog, columns, float, 2)
 +ENDCLASS(XonoticHUDConfirmDialog)
 +#endif
 +
 +#ifdef IMPLEMENTATION
 +void HUDSetup_Start(entity me, entity btn)
 +{
-               
++      if (!(gamestatus & (GAME_CONNECTED | GAME_ISSERVER)))
 +              localcmd("map hudsetup/hudsetup", "\n");
 +      else
 +              localcmd("togglemenu 0\n");
++
 +      localcmd("_hud_configure 1", "\n");
 +}
 +
 +void XonoticHUDConfirmDialog_fill(entity me)
 +{
 +      entity e;
 +
 +      me.TR(me);
 +              me.TD(me, 1, 2, e = makeXonoticTextLabel(0.5, _("In order for the HUD editor to show, you must first be in game.")));
 +      me.TR(me);
 +              me.TD(me, 1, 2, e = makeXonoticTextLabel(0.5, _("Do you wish to start a local game to set up the HUD?")));
 +      me.TR(me);
 +      me.TR(me);
 +              me.TD(me, 1, 1, e = makeXonoticButton(ZCTX(_("HDCNFRM^Yes")), '1 0 0'));
 +                      e.onClick = HUDSetup_Start;
 +                      e.onClickEntity = me;
 +              me.TD(me, 1, 1, e = makeXonoticButton(ZCTX(_("HDCNFRM^No")), '0 1 0'));
 +                      e.onClick = Dialog_Close;
 +                      e.onClickEntity = me;
 +}
 +#endif
index c55d4d488ba4ebcf37f2fd096fcf27a81e5c79ba,0000000000000000000000000000000000000000..d1cdade772a2c77f63dfed302b9a2fcf0cdd6fd8
mode 100644,000000..100644
--- /dev/null
@@@ -1,51 -1,0 +1,51 @@@
-       
 +#ifdef INTERFACE
 +CLASS(XonoticModelDialog) EXTENDS(XonoticDialog)
 +      METHOD(XonoticModelDialog, toString, string(entity))
 +      METHOD(XonoticModelDialog, fill, void(entity))
 +      METHOD(XonoticModelDialog, showNotify, void(entity))
 +      ATTRIB(XonoticModelDialog, title, string, _("Model settings"))
 +      ATTRIB(XonoticModelDialog, color, vector, SKINCOLOR_DIALOG_MODEL)
 +      ATTRIB(XonoticModelDialog, intendedWidth, float, 0.5)
 +      ATTRIB(XonoticModelDialog, rows, float, 7)
 +      ATTRIB(XonoticModelDialog, columns, float, 3)
 +ENDCLASS(XonoticModelDialog)
 +#endif
 +
 +#ifdef IMPLEMENTATION
 +void XonoticModelDialog_showNotify(entity me)
 +{
 +      loadAllCvars(me);
 +}
 +string XonoticModelDialog_toString(entity me)
 +{
 +      return "hi"; // TODO: show csqc model settings like forcemyplayer and deglowing/ghosting bodies with text here
 +}
 +void XonoticModelDialog_fill(entity me)
 +{
 +      entity e;
-               
++
 +      me.TR(me);
 +              me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Body fading:")));
 +              me.TD(me, 1, 2, e = makeXonoticSlider(0, 2, 0.2, "cl_deathglow"));
 +      me.TR(me);
 +              me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Gibs:")));
 +              me.TD(me, 1, 2, e = makeXonoticTextSlider("cl_nogibs"));
 +                      e.addValue(e, ZCTX(_("GIBS^None")), "1");
 +                      e.addValue(e, ZCTX(_("GIBS^Few")), "0.75");
 +                      e.addValue(e, ZCTX(_("GIBS^Many")), "0.5");
 +                      e.addValue(e, ZCTX(_("GIBS^Lots")), "0");
 +                      e.configureXonoticTextSliderValues(e);
 +                      setDependent(e, "cl_gentle", 0, 0);
 +      me.TR(me);
 +      me.TR(me);
 +              me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "cl_forceplayermodels", _("Force player models to mine")));
 +      me.TR(me);
 +              me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "cl_forceplayercolors", _("Force player colors to mine")));
 +      me.TR(me);
++
 +      me.gotoRC(me, me.rows - 1, 0);
 +              me.TD(me, 1, me.columns, e = makeXonoticButton(_("OK"), '0 0 0'));
 +                      e.onClick = Dialog_Close;
 +                      e.onClickEntity = me;
 +}
 +#endif
index 8ff33daf18ba7520b7ad39121a88375019d402db,0000000000000000000000000000000000000000..51ec3f43a92e886e3cd51cdd7f346e227114dec3
mode 100644,000000..100644
--- /dev/null
@@@ -1,134 -1,0 +1,134 @@@
-       
 +#ifdef INTERFACE
 +CLASS(XonoticGameViewSettingsTab) EXTENDS(XonoticTab)
 +      //METHOD(XonoticGameCrosshairSettingsTab, toString, string(entity))
 +      METHOD(XonoticGameViewSettingsTab, fill, void(entity))
 +      METHOD(XonoticGameViewSettingsTab, showNotify, void(entity))
 +      ATTRIB(XonoticGameViewSettingsTab, title, string, _("View"))
 +      ATTRIB(XonoticGameViewSettingsTab, intendedWidth, float, 0.9)
 +      ATTRIB(XonoticGameViewSettingsTab, rows, float, 14)
 +      ATTRIB(XonoticGameViewSettingsTab, columns, float, 6.2)
 +ENDCLASS(XonoticGameViewSettingsTab)
 +entity makeXonoticGameViewSettingsTab();
 +#endif
 +
 +#ifdef IMPLEMENTATION
 +void XonoticGameViewSettingsTab_showNotify(entity me)
 +{
 +      loadAllCvars(me);
 +}
 +entity makeXonoticGameViewSettingsTab()
 +{
 +      entity me;
 +      me = spawnXonoticGameViewSettingsTab();
 +      me.configureDialog(me);
 +      return me;
 +}
 +
 +void clippedspectatingclick(entity me, entity checkbox)
 +{
 +      if(gamestatus & (GAME_CONNECTED | GAME_ISSERVER))
 +              localcmd("sendcvar cl_clippedspectating\n");
 +      
 +      CheckBox_Click(me, checkbox);
 +}
 +
 +void XonoticGameViewSettingsTab_fill(entity me)
 +{
 +      entity e;
++
 +      me.TR(me);
 +              me.TD(me, 1, 3, e = makeXonoticRadioButton(1, "chase_active", "0", _("1st person perspective")));
 +              makeMulti(e, "crosshair_hittest_showimpact");
 +      me.TR(me);
 +              me.TDempty(me, 0.2);
 +              me.TD(me, 1, 2.8, e = makeXonoticCheckBoxEx(0.05, 0, "cl_bobfall", _("Smooth the view when landing from a jump")));
 +              setDependent(e, "chase_active", -1, 0);
 +      me.TR(me);
 +              me.TDempty(me, 0.2);
 +              me.TD(me, 1, 2.8, e = makeXonoticCheckBoxEx(0.05, 0, "cl_smoothviewheight", _("Smooth the view while crouching")));
 +              setDependent(e, "chase_active", -1, 0);
 +      me.TR(me);
 +              me.TDempty(me, 0.2);
 +              me.TD(me, 1, 2.8, e = makeXonoticCheckBoxEx(1, 0, "v_idlescale", _("View waving while idle")));
 +              setDependent(e, "chase_active", -1, 0);
 +      me.TR(me);
 +              me.TDempty(me, 0.2);
 +              me.TD(me, 1, 2.8, e = makeXonoticCheckBoxEx(0.01, 0, "cl_bob", _("View bobbing while walking around")));
 +              makeMulti(e, "cl_bob2");
 +              setDependent(e, "chase_active", -1, 0);
 +      me.TR(me);
 +      me.TR(me);
 +              me.TD(me, 1, 3, e = makeXonoticRadioButton(1, "chase_active", "1", _("3rd person perspective")));
 +              makeMulti(e, "crosshair_hittest_showimpact");
 +      me.TR(me);
 +              me.TDempty(me, 0.2);
 +              me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Back distance")));
 +              setDependent(e, "chase_active", 1, 1);
 +              me.TD(me, 1, 2, e = makeXonoticSlider(10, 100, 1, "chase_back"));
 +              setDependent(e, "chase_active", 1, 1);
 +      me.TR(me);
 +              me.TDempty(me, 0.2);
 +              me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Up distance")));
 +              setDependent(e, "chase_active", 1, 1);
 +              me.TD(me, 1, 2, e = makeXonoticSlider(10, 50, 1, "chase_up"));
 +              setDependent(e, "chase_active", 1, 1);
 +      me.TR(me);
 +      me.TR(me);
 +              me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "cl_eventchase_death", _("Slide to third person perspective upon death")));
 +              setDependent(e, "chase_active", -1, 0);
 +      me.TR(me);
 +              me.TD(me, 1, 3, e = makeXonoticCheckBox(1, "cl_clippedspectating", _("Allow passing through walls while spectating")));
 +                      e.onClick = clippedspectatingclick;
 +                      e.onClickEntity = e;
 +              // todo: onclick, do sendcvar if connected
 +      
 +      me.gotoRC(me, 0, 3.2); me.setFirstColumn(me, me.currentColumn);
 +              me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Field of view:")));
 +              me.TD(me, 1, 2, e = makeXonoticSlider(60, 130, 5, "fov"));
 +      me.TR(me);
 +      me.TR(me);
 +              //me.TDempty(me, 0.2);
 +              me.TD(me, 1, 1, e = makeXonoticTextLabel(0, ZCTX(_("ZOOM^Zoom factor:"))));
 +              me.TD(me, 1, 2, e = makeXonoticSlider(2, 16, 0.5, "cl_zoomfactor"));
 +      me.TR(me);
 +              //me.TDempty(me, 0.2);
 +              me.TD(me, 1, 1, e = makeXonoticTextLabel(0, ZCTX(_("ZOOM^Zoom speed:"))));
 +              me.TD(me, 1, 2, e = makeXonoticTextSlider("cl_zoomspeed"));
 +                      e.addValue(e, "1", "1"); // Samual: for() loop doesn't work here, even though it would make sense.
 +                      e.addValue(e, "2", "2");
 +                      e.addValue(e, "3", "3");
 +                      e.addValue(e, "4", "4");
 +                      e.addValue(e, "5", "5");
 +                      e.addValue(e, "6", "6");
 +                      e.addValue(e, "7", "7");
 +                      e.addValue(e, "8", "8");
 +                      e.addValue(e, ZCTX(_("ZOOM^Instant")), "-1");
 +                      e.configureXonoticTextSliderValues(e);
 +      me.TR(me);
 +              //me.TDempty(me, 0.2);
 +              me.TD(me, 1, 1, e = makeXonoticTextLabel(0, ZCTX(_("ZOOM^Zoom sensitivity:"))));
 +              me.TD(me, 1, 2, e = makeXonoticSlider(0, 1, 0.1, "cl_zoomsensitivity"));
 +      me.TR(me);
 +      me.TR(me);
 +              me.TD(me, 1, 1, e = makeXonoticCheckBox(0, "cl_velocityzoom", _("Velocity zoom")));
 +              me.TD(me, 1, 2, e = makeXonoticCheckBoxEx(3, 1, "cl_velocityzoom_type", _("Forward movement only")));
 +                      setDependent(e, "cl_velocityzoom", 1, 1);
 +      me.TR(me);
 +              me.TDempty(me, 0.2);
 +              me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, ZCTX(_("VZOOM^Factor"))));
 +                      setDependentAND(e, "cl_velocityzoom", 1, 1, "cl_velocityzoom_type", 1, 3);
 +              me.TD(me, 1, 2, e = makeXonoticSlider(-1, 1, 0.1, "cl_velocityzoom_factor"));
 +                      setDependentAND(e, "cl_velocityzoom", 1, 1, "cl_velocityzoom_type", 1, 3);
 +      me.TR(me);
 +      me.TR(me);
 +              //me.TDempty(me, 0.2);
 +              me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "cl_reticle", _("Display reticle 2D overlay while zooming")));
 +      me.TR(me);
 +              //me.TDempty(me, 0.2);
 +              me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "cl_unpress_zoom_on_death", _("Release zoom when you die or respawn")));
 +                      makeMulti(e, "cl_unpress_zoom_on_spawn");
 +      me.TR(me);
 +              //me.TDempty(me, 0.2);
 +              me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "cl_unpress_zoom_on_weapon_switch", _("Release zoom when you switch weapons")));
 +}
 +#endif
index fa3549154b0e807eddc63391b04154c1baa9db65,0000000000000000000000000000000000000000..cfdaf8fdfc0b4cc42f36f72e60a7f1ab17f24f9b
mode 100644,000000..100644
--- /dev/null
@@@ -1,78 -1,0 +1,78 @@@
-                       
 +#ifdef INTERFACE
 +CLASS(XonoticWeaponsDialog) EXTENDS(XonoticDialog)
 +      METHOD(XonoticWeaponsDialog, toString, string(entity))
 +      METHOD(XonoticWeaponsDialog, fill, void(entity))
 +      METHOD(XonoticWeaponsDialog, showNotify, void(entity))
 +      ATTRIB(XonoticWeaponsDialog, title, string, _("Weapon settings"))
 +      ATTRIB(XonoticWeaponsDialog, color, vector, SKINCOLOR_DIALOG_WEAPONS)
 +      ATTRIB(XonoticWeaponsDialog, intendedWidth, float, 0.7)
 +      ATTRIB(XonoticWeaponsDialog, rows, float, 12)
 +      ATTRIB(XonoticWeaponsDialog, columns, float, 5.2)
 +      ATTRIB(XonoticWeaponsDialog, weaponsList, entity, NULL)
 +ENDCLASS(XonoticWeaponsDialog)
 +#endif
 +
 +#ifdef IMPLEMENTATION
 +void XonoticWeaponsDialog_showNotify(entity me)
 +{
 +        loadAllCvars(me);
 +}
 +string XonoticWeaponsDialog_toString(entity me)
 +{
 +      return me.weaponsList.toString(me.weaponsList);
 +}
 +void XonoticWeaponsDialog_fill(entity me)
 +{
 +      entity e;
 +
 +      me.TR(me);
 +              me.TD(me, 1, 2, makeXonoticTextLabel(0, _("Weapon priority list:")));
 +      me.TR(me);
 +              me.TD(me, 8, 2, e = me.weaponsList = makeXonoticWeaponsList());
 +      me.gotoRC(me, 9, 0);
 +              me.TD(me, 1, 1, e = makeXonoticButton(_("Up"), '0 0 0'));
 +                      e.onClick = WeaponsList_MoveUp_Click;
 +                      e.onClickEntity = me.weaponsList;
 +              me.TD(me, 1, 1, e = makeXonoticButton(_("Down"), '0 0 0'));
 +                      e.onClick = WeaponsList_MoveDown_Click;
 +                      e.onClickEntity = me.weaponsList;
-                       
++
 +      me.gotoRC(me, 0, 2.2); me.setFirstColumn(me, me.currentColumn);
 +              me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "cl_weaponpriority_useforcycling", _("Use priority list for weapon cycling")));
 +      me.TR(me);
 +              me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "cl_autoswitch", _("Auto switch weapons on pickup")));
 +      me.TR(me);
 +      me.TR(me);
 +              me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "r_drawviewmodel", _("Draw 1st person weapon model")));
 +      me.TR(me);
 +              me.TDempty(me, 0.2);
 +              me.TD(me, 1, 0.9, e = makeXonoticRadioButton(1, "cl_gunalign", "4", _("Left align")));
 +                      setDependent(e, "r_drawviewmodel", 1, 1);
 +              me.TD(me, 1, 0.9, e = makeXonoticRadioButton(1, "cl_gunalign", "1", _("Center")));
 +                      setDependent(e, "r_drawviewmodel", 1, 1);
 +              me.TD(me, 1, 1.0, e = makeXonoticRadioButton(1, "cl_gunalign", "3", _("Right align")));
 +                      setDependent(e, "r_drawviewmodel", 1, 1);
 +      me.TR(me);
 +      me.TR(me);
 +              me.TDempty(me, 0.2);
 +              me.TD(me, 1, 2.8, e = makeXonoticCheckBox(0, "cl_followmodel", _("Gun model swaying")));
 +              makeMulti(e, "cl_leanmodel");
 +              setDependent(e, "r_drawviewmodel", 1, 1);
 +      me.TR(me);
 +              me.TDempty(me, 0.2);
 +              me.TD(me, 1, 2.8, e = makeXonoticCheckBox(0, "cl_bobmodel", _("Gun model bobbing")));
 +              setDependent(e, "r_drawviewmodel", 1, 1);
 +      //me.TR(me);
 +      //me.TR(me);
 +      //      me.TDempty(me, 0.2);
 +      //      me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, ZCTX(_("VWMDL^Scale"))));
 +      //      setDependent(e, "r_drawviewmodel", 1, 1);
 +      //      me.TD(me, 1, 2, e = makeXonoticSlider(0.1, 2, 0.1, "cl_viewmodel_scale"));
 +      //      setDependent(e, "r_drawviewmodel", 1, 1);
++
 +      me.gotoRC(me, me.rows - 1, 0);
 +              me.TD(me, 1, me.columns, e = makeXonoticButton(_("OK"), '0 0 0'));
 +                      e.onClick = Dialog_Close;
 +                      e.onClickEntity = me;
 +}
 +#endif
index 603a49ca2a4c04edcfb9eb663fb45930ef52d6f6,d8554be4f7af38285bcb259d87ec22a894cd2284..58ab3f55536e653ab0ac687aead47f806360372e
@@@ -3,8 -3,8 +3,8 @@@ CLASS(XonoticInputSettingsTab) EXTENDS(
        METHOD(XonoticInputSettingsTab, fill, void(entity))
        ATTRIB(XonoticInputSettingsTab, title, string, _("Input"))
        ATTRIB(XonoticInputSettingsTab, intendedWidth, float, 0.9)
 -      ATTRIB(XonoticInputSettingsTab, rows, float, 17)
 +      ATTRIB(XonoticInputSettingsTab, rows, float, 15.5)
-       ATTRIB(XonoticInputSettingsTab, columns, float, 6.2) // added extra .2 for center space 
+       ATTRIB(XonoticInputSettingsTab, columns, float, 6.2) // added extra .2 for center space
  ENDCLASS(XonoticInputSettingsTab)
  entity makeXonoticInputSettingsTab();
  #endif
@@@ -84,9 -84,9 +84,9 @@@ void XonoticInputSettingsTab_fill(entit
        me.TR(me);
                me.TDempty(me, 0.2);
                me.TD(me, 1, 2.8, e = makeXonoticCheckBox(0, "m_accelerate", _("Enable built in mouse acceleration")));
-               
-       
 -      me.gotoRC(me, me.rows - 1, 0);
 +      me.gotoRC(me, me.rows - 1.25, 0);
                me.TD(me, 1, me.columns, makeXonoticCommandButton(_("Apply immediately"), '0 0 0', "sendcvar cl_movement_track_canjump", COMMANDBUTTON_APPLY));
  }
  #endif
index 03f5ee80ae5f8d9db9a33436957c8b198cbc6835,5d6a32d563586bde88a96ef9f0479e2f106bbef0..911410b94491e46a4ad95ee95e215bc6344ccea7
@@@ -3,7 -3,7 +3,7 @@@ CLASS(XonoticUserSettingsTab) EXTENDS(X
        METHOD(XonoticUserSettingsTab, fill, void(entity))
        ATTRIB(XonoticUserSettingsTab, title, string, _("User"))
        ATTRIB(XonoticUserSettingsTab, intendedWidth, float, 0.9)
 -      ATTRIB(XonoticUserSettingsTab, rows, float, 17)
 +      ATTRIB(XonoticUserSettingsTab, rows, float, 15.5)
        ATTRIB(XonoticUserSettingsTab, columns, float, 5)
  ENDCLASS(XonoticUserSettingsTab)
  entity makeXonoticUserSettingsTab();
@@@ -26,8 -26,8 +26,8 @@@ void XonoticUserSettingsTab_fill(entit
        me.TR(me);
                me.TD(me, 1, 2, e = makeXonoticTextLabel(0, _("Menu skins:")));
        me.TR(me);
 -              me.TD(me, me.rows - 2, 2, sk = makeXonoticSkinList());
 -      me.gotoRC(me, me.rows - 1, 0);
 +              me.TD(me, me.rows - 2.5, 2, sk = makeXonoticSkinList());
 +      me.gotoRC(me, me.rows - 1.5, 0);
                me.TD(me, 1, 2, e = makeXonoticButton(_("Set skin"), '0 0 0'));
                        e.onClick = SetSkin_Click;
                        e.onClickEntity = sk;
@@@ -60,7 -60,7 +60,7 @@@
                me.TD(me, 1, 1.5, e = makeXonoticButton(_("Set font"), '0 0 0'));
                        e.onClick = SetLanguage_Click;
                        e.onClickEntity = sk;*/
-                       
        me.gotoRC(me, 0, 2.85); me.setFirstColumn(me, me.currentColumn);
                me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Text language:")));
        me.TR(me);
@@@ -74,7 -74,7 +74,7 @@@
                me.TD(me, 1, 1.5, e = makeXonoticButton(_("Set language"), '0 0 0'));
                        e.onClick = SetLanguage_Click;
                        e.onClickEntity = sk;
-               
        me.gotoRC(me, 9, 2.2); me.setFirstColumn(me, me.currentColumn);
                me.TD(me, 1, 2.8, e = makeXonoticCheckBox(0, "cl_gentle", _("Disable gore effects and harsh language")));
        me.TR(me);
@@@ -83,8 -83,8 +83,8 @@@
        me.TR(me);
                me.TD(me, 1, 2.8, e = makeXonoticCheckBox(0, "cl_allow_uid2name", _("Allow player statistics to use your nickname")));
                setDependent(e, "cl_allow_uidtracking", 1, 1);
-               
 -      me.gotoRC(me, me.rows - 3, 2.6);
 +      me.gotoRC(me, me.rows - 2, 2.6);
                me.TD(me, 1, 2, makeXonoticCommandButton(_("Apply immediately"), '0 0 0', "sendcvar cl_gentle; sendcvar cl_allow_uidtracking; sendcvar cl_allow_uid2name;", COMMANDBUTTON_APPLY));
  
  }
index 2af99e6d548f120b0b76ffb1a4c00fda8ff86c2f,db728d601494407d9aa58077b594d73c48999823..fe600ed07a5d780eb993e9fa2268db67e173ae99
@@@ -3,8 -3,8 +3,8 @@@ CLASS(XonoticVideoSettingsTab) EXTENDS(
        METHOD(XonoticVideoSettingsTab, fill, void(entity))
        ATTRIB(XonoticVideoSettingsTab, title, string, _("Video"))
        ATTRIB(XonoticVideoSettingsTab, intendedWidth, float, 0.9)
 -      ATTRIB(XonoticVideoSettingsTab, rows, float, 17)
 +      ATTRIB(XonoticVideoSettingsTab, rows, float, 15.5)
-       ATTRIB(XonoticVideoSettingsTab, columns, float, 6.2) // added extra .2 for center space 
+       ATTRIB(XonoticVideoSettingsTab, columns, float, 6.2) // added extra .2 for center space
        ATTRIB(XonoticVideoSettingsTab, name, string, "videosettings")
  ENDCLASS(XonoticVideoSettingsTab)
  entity makeXonoticVideoSettingsTab();
@@@ -47,10 -47,8 +47,10 @@@ void XonoticVideoSettingsTab_fill(entit
        me.TR(me);
                me.TD(me, 1, 1, e = makeXonoticCheckBox(0, "vid_fullscreen", _("Full screen")));
                me.TD(me, 1, 2, e = makeXonoticCheckBox(0, "vid_vsync", _("Vertical Synchronization")));
-               
        me.TR(me);
 +              if(cvar("developer"))
 +                      { me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "v_flipped", _("Flip view horizontally"))); }
        me.TR(me);
                me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Anisotropy:")));
                me.TD(me, 1, 2, e = makeXonoticTextSlider("gl_texture_anisotropy"));
@@@ -72,7 -70,7 +72,7 @@@
        me.TR(me);
                me.TD(me, 1, 3, e = makeXonoticCheckBoxEx(2, 0, "r_viewfbo", _("High-quality frame buffer")));
                        setDependent(e, "vid_samples", 1, 1);
-               
        me.TR(me);
        me.TR(me);
                me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Depth first:")));
                me.TR(me);
                        me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "r_trippy", _("Trippy vertices (easter egg)")));
                                setDependent(e, "vid_gl20", 1, 1);
 -              me.TR(me);
 -                      me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "v_flipped", _("Flip view horizontally")));
        }
  
 -      me.gotoRC(me, me.rows - 1, 0);
 +      me.gotoRC(me, me.rows - 1.25, 0);
                me.TD(me, 1, me.columns, makeXonoticCommandButton(_("Apply immediately"), '0 0 0', "vid_width $_menu_vid_width; vid_height $_menu_vid_height; vid_pixelheight $_menu_vid_pixelheight; vid_desktopfullscreen $_menu_vid_desktopfullscreen; menu_cmd update_conwidths_before_vid_restart; vid_restart; menu_cmd sync", COMMANDBUTTON_APPLY));
  }
  #endif
index 7983e0bef562592d7eed27372f71f21a05122beb,0af90bbe270f8b43356e33c19da14b30ff773e52..b1388b70c6cc5e1c95f74d0af71b029edf871c4a
@@@ -11,16 -11,14 +11,16 @@@ CLASS(MainWindow) EXTENDS(ModalControll
        ATTRIB(MainWindow, winnerDialog, entity, NULL)
        ATTRIB(MainWindow, serverInfoDialog, entity, NULL)
        ATTRIB(MainWindow, cvarsDialog, entity, NULL)
 +      ATTRIB(MainWindow, screenshotViewerDialog, entity, NULL)
        ATTRIB(MainWindow, viewDialog, entity, NULL)
        ATTRIB(MainWindow, modelDialog, entity, NULL)
 -      ATTRIB(MainWindow, crosshairDialog, entity, NULL)
        ATTRIB(MainWindow, hudDialog, entity, NULL)
        ATTRIB(MainWindow, hudconfirmDialog, entity, NULL)
        ATTRIB(MainWindow, mainNexposee, entity, NULL)
        ATTRIB(MainWindow, fadedAlpha, float, SKINALPHA_BEHIND)
        ATTRIB(MainWindow, dialogToShow, entity, NULL)
 +      ATTRIB(MainWindow, notificationDialog, entity, NULL)
 +      ATTRIB(MainWindow, democonfirmDialog, entity, NULL)
  ENDCLASS(MainWindow)
  #endif
  
@@@ -52,61 -50,61 +52,61 @@@ void MainWindow_configureMainWindow(ent
        me.firstRunDialog = i = spawnXonoticFirstRunDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-       
-       
        // hud_configure dialogs
        i = spawnXonoticHUDExitDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-       
        i = spawnXonoticHUDNotificationDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-       
        i = spawnXonoticHUDAmmoDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-       
        i = spawnXonoticHUDHealthArmorDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-       
        i = spawnXonoticHUDChatDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-       
        i = spawnXonoticHUDModIconsDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-       
        i = spawnXonoticHUDPowerupsDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-       
        i = spawnXonoticHUDPressedKeysDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-       
        i = spawnXonoticHUDRaceTimerDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-       
        i = spawnXonoticHUDRadarDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-       
        i = spawnXonoticHUDScoreDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-       
        i = spawnXonoticHUDTimerDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-       
        i = spawnXonoticHUDVoteDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-       
        i = spawnXonoticHUDWeaponsDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
        i = spawnXonoticHUDInfoMessagesDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
 -
 +      
        i = spawnXonoticHUDPhysicsDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
 +      
 +      me.screenshotViewerDialog = i = spawnXonoticScreenshotViewerDialog();
 +      i.configureDialog(i);
 +      me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
  
        i = spawnXonoticHUDCenterprintDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-       
-       
        // dialogs used by settings
        me.userbindEditDialog = i = spawnXonoticUserbindEditDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-       
        me.cvarsDialog = i = spawnXonoticCvarsDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-       
-       
        // dialog used by singleplayer
        me.winnerDialog = i = spawnXonoticWinnerDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-       
-       
        // dialog used by multiplayer/join
        me.serverInfoDialog = i = spawnXonoticServerInfoDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
 -
 -
 +      
 +      me.democonfirmDialog = i = spawnXonoticDemoConfirmDialog();
 +      i.configureDialog(i);
 +      me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
 +      
 +      
        // dialogs used by multiplayer/create
        me.mapInfoDialog = i = spawnXonoticMapInfoDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-       
        me.advancedDialog = i = spawnXonoticAdvancedDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
        me.mutatorsDialog = i = spawnXonoticMutatorsDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-       
  
 -
 -      // dialogs used by multiplayer/player setup
 -      me.crosshairDialog = i = spawnXonoticCrosshairDialog();
 -      i.configureDialog(i);
 -      me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
 -
 +      // dialogs used by multiplayer/player setup     
        me.hudDialog = i = spawnXonoticHUDDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
        me.hudconfirmDialog = i = spawnXonoticHUDConfirmDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-       
        me.modelDialog = i = spawnXonoticModelDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-       
 -      me.viewDialog = i = spawnXonoticViewDialog();
 +      me.weaponsDialog = i = spawnXonoticWeaponsDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-       
 -      me.weaponsDialog = i = spawnXonoticWeaponsDialog();
 +      me.notificationDialog = i = spawnXonoticNotificationDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
  
        // mutator dialogs
        i = spawnXonoticSandboxToolsDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z * SKINALPHA_DIALOG_SANDBOXTOOLS);
-       
-       
        // miscellaneous dialogs
        i = spawnXonoticTeamSelectDialog();
        i.configureDialog(i);
        me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-       
-       
        // main dialogs/windows
        me.mainNexposee = n = spawnXonoticNexposee();
        /*
                i.configureDialog(i);
                n.addItemCentered(n, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
                n.setNexposee(n, i, SKINPOSITION_DIALOG_SINGLEPLAYER, SKINALPHAS_MAINMENU_x, SKINALPHAS_MAINMENU_y);
-               
                i = spawnXonoticMultiplayerDialog();
                i.configureDialog(i);
                n.addItemCentered(n, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
                n.addItemCentered(n, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
                n.setNexposee(n, i, SKINPOSITION_DIALOG_QUIT, SKINALPHAS_MAINMENU_x, SKINALPHAS_MAINMENU_y);
                n.pullNexposee(n, i, eY * (SKINHEIGHT_TITLE * SKINFONTSIZE_TITLE / conheight));
-               
        me.addItem(me, n, '0 0 0', '1 1 0', SKINALPHAS_MAINMENU_z);
        me.moveItemAfter(me, n, NULL);
  
index e656b963e2ca7bf9e65432d4e75a4431b69ce32c,cb6b0fbcdc5641366c363a25b6159dfd9c3cdfa5..e2a0e6c1249f401847c51e27b40ebef94bd935cd
@@@ -7,7 -7,6 +7,7 @@@ CLASS(XonoticServerList) EXTENDS(Xonoti
        METHOD(XonoticServerList, clickListBoxItem, void(entity, float, vector))
        METHOD(XonoticServerList, resizeNotify, void(entity, vector, vector, vector, vector))
        METHOD(XonoticServerList, keyDown, float(entity, float, float, float))
 +      METHOD(XonoticServerList, toggleFavorite, void(entity, string))
  
        ATTRIB(XonoticServerList, iconsSizeFactor, float, 0.85)
  
@@@ -36,7 -35,7 +36,7 @@@
        ATTRIB(XonoticServerList, ipAddressBox, entity, NULL)
        ATTRIB(XonoticServerList, favoriteButton, entity, NULL)
        ATTRIB(XonoticServerList, nextRefreshTime, float, 0)
 -      METHOD(XonoticServerList, refreshServerList, void(entity, float)) // refresh mode: 0 = just reparametrize, 1 = send new requests, 2 = clear
 +      METHOD(XonoticServerList, refreshServerList, void(entity, float)) // refresh mode: REFRESHSERVERLIST_*
        ATTRIB(XonoticServerList, needsRefresh, float, 1)
        METHOD(XonoticServerList, focusEnter, void(entity))
        METHOD(XonoticServerList, positionSortButton, void(entity, entity, float, float, string, void(entity, entity)))
  
        ATTRIB(XonoticServerList, seenIPv4, float, 0)
        ATTRIB(XonoticServerList, seenIPv6, float, 0)
 +      ATTRIB(XonoticServerList, categoriesHeight, float, 1.25)
 +
 +      METHOD(XonoticServerList, getTotalHeight, float(entity))
 +      METHOD(XonoticServerList, getItemAtPos, float(entity, float))
 +      METHOD(XonoticServerList, getItemStart, float(entity, float))
 +      METHOD(XonoticServerList, getItemHeight, float(entity, float))
  ENDCLASS(XonoticServerList)
  entity makeXonoticServerList();
  
 +#ifndef IMPLEMENTATION
 +float autocvar_menu_slist_categories;
 +float autocvar_menu_slist_categories_onlyifmultiple; 
 +float autocvar_menu_slist_purethreshold;
 +float autocvar_menu_slist_modimpurity;
 +float autocvar_menu_slist_recommendations;
 +float autocvar_menu_slist_recommendations_maxping;
 +float autocvar_menu_slist_recommendations_minfreeslots; 
 +float autocvar_menu_slist_recommendations_minhumans;
 +float autocvar_menu_slist_recommendations_purethreshold; 
 +
 +// server cache fields
 +#define SLIST_FIELDS \
 +      SLIST_FIELD(CNAME,       "cname") \
 +      SLIST_FIELD(PING,        "ping") \
 +      SLIST_FIELD(GAME,        "game") \
 +      SLIST_FIELD(MOD,         "mod") \
 +      SLIST_FIELD(MAP,         "map") \
 +      SLIST_FIELD(NAME,        "name") \
 +      SLIST_FIELD(MAXPLAYERS,  "maxplayers") \
 +      SLIST_FIELD(NUMPLAYERS,  "numplayers") \
 +      SLIST_FIELD(NUMHUMANS,   "numhumans") \
 +      SLIST_FIELD(NUMBOTS,     "numbots") \
 +      SLIST_FIELD(PROTOCOL,    "protocol") \
 +      SLIST_FIELD(FREESLOTS,   "freeslots") \
 +      SLIST_FIELD(PLAYERS,     "players") \
 +      SLIST_FIELD(QCSTATUS,    "qcstatus") \
 +      SLIST_FIELD(CATEGORY,    "category") \
 +      SLIST_FIELD(ISFAVORITE,  "isfavorite")
 +
 +#define SLIST_FIELD(suffix,name) float SLIST_FIELD_##suffix;
 +SLIST_FIELDS
 +#undef SLIST_FIELD
 +
 +const float REFRESHSERVERLIST_RESORT = 0;    // sort the server list again to update for changes to e.g. favorite status, categories
 +const float REFRESHSERVERLIST_REFILTER = 1;  // ..., also update filter and sort criteria
 +const float REFRESHSERVERLIST_ASK = 2;       // ..., also suggest querying servers now
 +const float REFRESHSERVERLIST_RESET = 3;     // ..., also clear the list first
 +
 +// function declarations
 +float IsServerInList(string list, string srv);
 +#define IsFavorite(srv) IsServerInList(cvar_string("net_slist_favorites"), srv)
 +#define IsPromoted(srv) IsServerInList(_Nex_ExtResponseSystem_PromotedServers, srv)
 +#define IsRecommended(srv) IsServerInList(_Nex_ExtResponseSystem_RecommendedServers, srv)
 +
 +entity RetrieveCategoryEnt(float catnum);
 +
 +float CheckCategoryOverride(float cat);
 +float CheckCategoryForEntry(float entry); 
 +float m_gethostcachecategory(float entry) { return CheckCategoryOverride(CheckCategoryForEntry(entry)); }
 +
 +void RegisterSLCategories();
 +
  void ServerList_Connect_Click(entity btn, entity me);
 +void ServerList_Categories_Click(entity box, entity me);
  void ServerList_ShowEmpty_Click(entity box, entity me);
  void ServerList_ShowFull_Click(entity box, entity me);
  void ServerList_Filter_Change(entity box, entity me);
@@@ -127,114 -66,47 +127,114 @@@ void ServerList_Favorite_Click(entity b
  void ServerList_Info_Click(entity btn, entity me);
  void ServerList_Update_favoriteButton(entity btn, entity me);
  
 -#ifndef IMPLEMENTATION
 -float SLIST_FIELD_CNAME;
 -float SLIST_FIELD_PING;
 -float SLIST_FIELD_GAME;
 -float SLIST_FIELD_MOD;
 -float SLIST_FIELD_MAP;
 -float SLIST_FIELD_NAME;
 -float SLIST_FIELD_MAXPLAYERS;
 -float SLIST_FIELD_NUMPLAYERS;
 -float SLIST_FIELD_NUMHUMANS;
 -float SLIST_FIELD_NUMBOTS;
 -float SLIST_FIELD_PROTOCOL;
 -float SLIST_FIELD_FREESLOTS;
 -float SLIST_FIELD_PLAYERS;
 -float SLIST_FIELD_QCSTATUS;
 -float SLIST_FIELD_ISFAVORITE;
 -#endif
 +// fields for category entities
 +#define MAX_CATEGORIES 9
 +#define CATEGORY_FIRST 1
 +entity categories[MAX_CATEGORIES];
 +float category_ent_count;
 +.string cat_name;
 +.string cat_string;
 +.string cat_enoverride_string;
 +.string cat_dioverride_string;
 +.float cat_enoverride;
 +.float cat_dioverride;
 +
 +// fields for drawing categories
 +float category_name[MAX_CATEGORIES];
 +float category_item[MAX_CATEGORIES];
 +float category_draw_count;
 +
 +#define SLIST_CATEGORIES \
 +      SLIST_CATEGORY(CAT_FAVORITED,    "",            "",             ZCTX(_("SLCAT^Favorites"))) \
 +      SLIST_CATEGORY(CAT_RECOMMENDED,  "",            "",             ZCTX(_("SLCAT^Recommended"))) \
 +      SLIST_CATEGORY(CAT_NORMAL,       "",            "CAT_SERVERS",  ZCTX(_("SLCAT^Normal Servers"))) \
 +      SLIST_CATEGORY(CAT_SERVERS,      "CAT_NORMAL",  "CAT_SERVERS",  ZCTX(_("SLCAT^Servers"))) \
 +      SLIST_CATEGORY(CAT_XPM,          "CAT_NORMAL",  "CAT_SERVERS",  ZCTX(_("SLCAT^Competitive Mode"))) \
 +      SLIST_CATEGORY(CAT_MODIFIED,     "",            "CAT_SERVERS",  ZCTX(_("SLCAT^Modified Servers"))) \
 +      SLIST_CATEGORY(CAT_OVERKILL,     "",            "CAT_SERVERS",  ZCTX(_("SLCAT^Overkill Mode"))) \
 +      SLIST_CATEGORY(CAT_MINSTAGIB,    "",            "CAT_SERVERS",  ZCTX(_("SLCAT^MinstaGib Mode"))) \
 +      SLIST_CATEGORY(CAT_DEFRAG,       "",            "CAT_SERVERS",  ZCTX(_("SLCAT^Defrag Mode")))
 +
 +#define SLIST_CATEGORY_AUTOCVAR(name) autocvar_menu_slist_categories_##name##_override
 +#define SLIST_CATEGORY(name,enoverride,dioverride,str) \
 +      float name; \
 +      var string SLIST_CATEGORY_AUTOCVAR(name) = enoverride;
 +SLIST_CATEGORIES
 +#undef SLIST_CATEGORY
  
  #endif
 -
 +#endif
  #ifdef IMPLEMENTATION
 -void ServerList_UpdateFieldIDs()
 +
 +void RegisterSLCategories()
  {
 -      SLIST_FIELD_CNAME = gethostcacheindexforkey( "cname" );
 -      SLIST_FIELD_PING = gethostcacheindexforkey( "ping" );
 -      SLIST_FIELD_GAME = gethostcacheindexforkey( "game" );
 -      SLIST_FIELD_MOD = gethostcacheindexforkey( "mod" );
 -      SLIST_FIELD_MAP = gethostcacheindexforkey( "map" );
 -      SLIST_FIELD_NAME = gethostcacheindexforkey( "name" );
 -      SLIST_FIELD_MAXPLAYERS = gethostcacheindexforkey( "maxplayers" );
 -      SLIST_FIELD_NUMPLAYERS = gethostcacheindexforkey( "numplayers" );
 -      SLIST_FIELD_NUMHUMANS = gethostcacheindexforkey( "numhumans" );
 -      SLIST_FIELD_NUMBOTS = gethostcacheindexforkey( "numbots" );
 -      SLIST_FIELD_PROTOCOL = gethostcacheindexforkey( "protocol" );
 -      SLIST_FIELD_FREESLOTS = gethostcacheindexforkey( "freeslots" );
 -      SLIST_FIELD_PLAYERS = gethostcacheindexforkey( "players" );
 -      SLIST_FIELD_QCSTATUS = gethostcacheindexforkey( "qcstatus" );
 -      SLIST_FIELD_ISFAVORITE = gethostcacheindexforkey( "isfavorite" );
 -}
 -
 -float IsFavorite(string srv)
 +      entity cat;
 +      #define SLIST_CATEGORY(name,enoverride,dioverride,str) \
 +              SET_FIELD_COUNT(name, CATEGORY_FIRST, category_ent_count) \
 +              CHECK_MAX_COUNT(name, MAX_CATEGORIES, category_ent_count, "SLIST_CATEGORY") \
 +              cat = spawn(); \
 +              categories[name - 1] = cat; \
 +              cat.classname = "slist_category"; \
 +              cat.cat_name = strzone(#name); \
 +              cat.cat_enoverride_string = strzone(SLIST_CATEGORY_AUTOCVAR(name)); \
 +              cat.cat_dioverride_string = strzone(dioverride); \
 +              cat.cat_string = strzone(str);
 +      SLIST_CATEGORIES
 +      #undef SLIST_CATEGORY
 +
 +      float i, x, catnum;
 +      string s;
 +
 +      #define PROCESS_OVERRIDE(override_string,override_field) \
 +              for(i = 0; i < category_ent_count; ++i) \
 +              { \
 +                      s = categories[i].override_string; \
 +                      if((s != "") && (s != categories[i].cat_name)) \
 +                      { \
 +                              catnum = 0; \
 +                              for(x = 0; x < category_ent_count; ++x) \
 +                              { if(categories[x].cat_name == s) { \
 +                                      catnum = (x+1); \
 +                                      break; \
 +                              } } \
 +                              if(catnum) \
 +                              { \
 +                                      strunzone(categories[i].override_string); \
 +                                      categories[i].override_field = catnum; \
 +                                      continue; \
 +                              } \
 +                              else \
 +                              { \
 +                                      print(sprintf( \
 +                                              "RegisterSLCategories(): Improper override '%s' for category '%s'!\n", \
 +                                              s, \
 +                                              categories[i].cat_name \
 +                                      )); \
 +                              } \
 +                      } \
 +                      strunzone(categories[i].override_string); \
 +                      categories[i].override_field = 0; \
 +              }
 +      PROCESS_OVERRIDE(cat_enoverride_string, cat_enoverride)
 +      PROCESS_OVERRIDE(cat_dioverride_string, cat_dioverride)
 +      #undef PROCESS_OVERRIDE
 +}
 +
 +// Supporting Functions
 +entity RetrieveCategoryEnt(float catnum)
 +{
 +      if((catnum > 0) && (catnum <= category_ent_count))
 +      {
 +              return categories[catnum - 1];
 +      }
 +      else
 +      {
 +              error(sprintf("RetrieveCategoryEnt(%d): Improper category number!\n", catnum));
 +              return world;
 +      }
 +}
 +
 +float IsServerInList(string list, string srv)
  {
        string p;
        float i, n;
        if(srv == "")
                return FALSE;
        p = crypto_getidfp(srv);
 -      n = tokenize_console(cvar_string("net_slist_favorites"));
 +      n = tokenize_console(list);
        for(i = 0; i < n; ++i)
        {
                if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
        return FALSE;
  }
  
 -void ToggleFavorite(string srv)
 +float CheckCategoryOverride(float cat)
 +{
 +      entity catent = RetrieveCategoryEnt(cat);
 +      if(catent)
 +      {
 +              float override = (autocvar_menu_slist_categories ? catent.cat_enoverride : catent.cat_dioverride); 
 +              if(override) { return override; }
 +              else { return cat; }
 +      }
 +      else
 +      {
 +              error(sprintf("CheckCategoryOverride(%d): Improper category number!\n", cat));
 +              return cat;
 +      }
 +}
 +
 +float CheckCategoryForEntry(float entry)
 +{
 +      string s, k, v, modtype = "";
 +      float j, m, impure = 0, freeslots = 0, sflags = 0;
 +      s = gethostcachestring(SLIST_FIELD_QCSTATUS, entry);
 +      m = tokenizebyseparator(s, ":");
 +
 +      for(j = 2; j < m; ++j)
 +      {
 +              if(argv(j) == "") { break; }
 +              k = substring(argv(j), 0, 1);
 +              v = substring(argv(j), 1, -1);
 +              switch(k)
 +              {
 +                      case "P": { impure = stof(v); break; }
 +                      case "S": { freeslots = stof(v); break; }
 +                      case "F": { sflags = stof(v); break; }
 +                      case "M": { modtype = strtolower(v); break; }
 +              }
 +      }
 +
 +      if(modtype != "xonotic") { impure += autocvar_menu_slist_modimpurity; }
 +
 +      // check if this server is favorited
 +      if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, entry)) { return CAT_FAVORITED; }
 +
 +      // now check if it's recommended
 +      if(autocvar_menu_slist_recommendations)
 +      {
 +              string cname = gethostcachestring(SLIST_FIELD_CNAME, entry);
 +              
 +              if(IsPromoted(cname)) { return CAT_RECOMMENDED; }
 +              else
 +              {
 +                      float recommended = 0;
 +                      if(autocvar_menu_slist_recommendations & 1)
 +                      {
 +                              if(IsRecommended(cname)) { ++recommended; }
 +                              else { --recommended; }
 +                      }
 +                      if(autocvar_menu_slist_recommendations & 2)
 +                      {
 +                              if(
 +                                      ///// check for minimum free slots
 +                                      (freeslots >= autocvar_menu_slist_recommendations_minfreeslots)
 +                                      
 +                                      && // check for purity requirement
 +                                      (
 +                                              (autocvar_menu_slist_recommendations_purethreshold < 0)
 +                                              ||
 +                                              (impure <= autocvar_menu_slist_recommendations_purethreshold)
 +                                      )
 +                                      
 +                                      && // check for minimum amount of humans
 +                                      (
 +                                              gethostcachenumber(SLIST_FIELD_NUMHUMANS, entry)
 +                                              >=
 +                                              autocvar_menu_slist_recommendations_minhumans
 +                                      )
 +                                      
 +                                      && // check for maximum latency
 +                                      (
 +                                              gethostcachenumber(SLIST_FIELD_PING, entry)
 +                                              <=
 +                                              autocvar_menu_slist_recommendations_maxping
 +                                      )
 +                              )
 +                                      { ++recommended; }
 +                              else
 +                                      { --recommended; }
 +                      }
 +                      if(recommended > 0) { return CAT_RECOMMENDED; }
 +              }
 +      }
 +
 +      // if not favorited or recommended, check modname
 +      if(modtype != "xonotic")
 +      {
 +              switch(modtype)
 +              {
 +                      // old servers which don't report their mod name are considered modified now
 +                      case "": { return CAT_MODIFIED; }
 +                      
 +                      case "xpm": { return CAT_XPM; } 
 +                      case "minstagib": { return CAT_MINSTAGIB; }
 +                      case "overkill": { return CAT_OVERKILL; }
 +                      //case "nix": { return CAT_NIX; }
 +                      //case "newtoys": { return CAT_NEWTOYS; }
 +
 +                      // "cts" is allowed as compat, xdf is replacement
 +                      case "cts": 
 +                      case "xdf": { return CAT_DEFRAG; }
 +                      
 +                      default: { dprint(sprintf("Found strange mod type: %s\n", modtype)); return CAT_MODIFIED; }
 +              }
 +      }
 +
 +      // must be normal or impure server
 +      return ((impure > autocvar_menu_slist_purethreshold) ? CAT_MODIFIED : CAT_NORMAL);
 +}
 +
 +void XonoticServerList_toggleFavorite(entity me, string srv)
  {
        string s, s0, s1, s2, srv_resolved, p;
        float i, n, f;
                f = 1;
                --i;
        }
-       
        if(!f)
        {
                s1 = "";
                        cvar_set("net_slist_favorites", strcat(s, s1, srv));
        }
  
 -      resorthostcache();
 +      me.refreshServerList(me, REFRESHSERVERLIST_RESORT);
  }
  
  void ServerList_Update_favoriteButton(entity btn, entity me)
  {
 -      if(IsFavorite(me.ipAddressBox.text))
 -              me.favoriteButton.setText(me.favoriteButton, _("Remove"));
 -      else
 -              me.favoriteButton.setText(me.favoriteButton, _("Bookmark"));
 +      me.favoriteButton.setText(me.favoriteButton,
 +              (IsFavorite(me.ipAddressBox.text) ?
 +                      _("Remove") : _("Favorite")
 +              )
 +      );
  }
  
  entity makeXonoticServerList()
@@@ -449,12 -203,8 +449,12 @@@ void XonoticServerList_configureXonotic
  {
        me.configureXonoticListBox(me);
  
 -      ServerList_UpdateFieldIDs();
 +      // update field ID's
 +      #define SLIST_FIELD(suffix,name) SLIST_FIELD_##suffix = gethostcacheindexforkey(name);
 +      SLIST_FIELDS
 +      #undef SLIST_FIELD
  
 +      // clear list
        me.nItems = 0;
  }
  void XonoticServerList_setSelected(entity me, float i)
  }
  void XonoticServerList_refreshServerList(entity me, float mode)
  {
 -      // 0: just reparametrize
 -      // 1: also ask for new servers
 -      // 2: clear
        //print("refresh of type ", ftos(mode), "\n");
 -      /* if(mode == 2) // borken
 -      {
 -              // clear list
 -              localcmd("net_slist\n");
 -              me.needsRefresh = 1; // net_slist kills sort order, so we need to restore it later
 -      }
 -      else */
 +
 +      if(mode >= REFRESHSERVERLIST_REFILTER)
        {
 -              float m, o, i, n; // moin moin
 +              float m, i, n;
 +              float listflags = 0;
                string s, typestr, modstr;
 +
                s = me.filterString;
  
                m = strstrofs(s, ":", 0);
                        sethostcachemaskstring(++m, SLIST_FIELD_PLAYERS, s, SLIST_TEST_CONTAINS);
                        sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(s, ":"), SLIST_TEST_STARTSWITH);
                }
 -              o = 2; // favorites first
 -              if(me.currentSortOrder < 0)
 -                      o |= 1; // descending
 -              sethostcachesort(me.currentSortField, o);
 -              resorthostcache();
 -              if(mode >= 1)
 -                      refreshhostcache();
 +
 +              // sorting flags
 +              //listflags |= SLSF_FAVORITES;
 +              listflags |= SLSF_CATEGORIES;
 +              if(me.currentSortOrder < 0) { listflags |= SLSF_DESCENDING; }
 +              sethostcachesort(me.currentSortField, listflags);
        }
 +      
 +      resorthostcache();
 +      if(mode >= REFRESHSERVERLIST_ASK)
 +              refreshhostcache(mode >= REFRESHSERVERLIST_RESET);
  }
  void XonoticServerList_focusEnter(entity me)
  {
                return;
        }
        me.nextRefreshTime = time + 10;
 -      me.refreshServerList(me, 1);
 +      me.refreshServerList(me, REFRESHSERVERLIST_ASK);
  }
 +
  void XonoticServerList_draw(entity me)
  {
        float i, found, owned;
                _Nex_ExtResponseSystem_BannedServersNeedsRefresh = 0;
        }
  
 +      if(_Nex_ExtResponseSystem_PromotedServersNeedsRefresh)
 +      {
 +              if(!me.needsRefresh)
 +                      me.needsRefresh = 3;
 +              _Nex_ExtResponseSystem_PromotedServersNeedsRefresh = 0;
 +      }
 +
 +      if(_Nex_ExtResponseSystem_RecommendedServersNeedsRefresh)
 +      {
 +              if(!me.needsRefresh)
 +                      me.needsRefresh = 3;
 +              _Nex_ExtResponseSystem_RecommendedServersNeedsRefresh = 0;
 +      }
 +
        if(me.currentSortField == -1)
        {
                me.setSortOrder(me, SLIST_FIELD_PING, +1);
 -              me.refreshServerList(me, 2);
 +              me.refreshServerList(me, REFRESHSERVERLIST_RESET);
        }
        else if(me.needsRefresh == 1)
        {
        else if(me.needsRefresh == 2)
        {
                me.needsRefresh = 0;
 -              me.refreshServerList(me, 0);
 +              me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
 +      }
 +      else if(me.needsRefresh == 3)
 +      {
 +              me.needsRefresh = 0;
 +              me.refreshServerList(me, REFRESHSERVERLIST_RESORT);
        }
  
        owned = ((me.selectedServer == me.ipAddressBox.text) && (me.ipAddressBox.text != ""));
  
 -      me.nItems = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
 +      for(i = 0; i < category_draw_count; ++i) { category_name[i] = -1; category_item[i] = -1; }
 +      category_draw_count = 0;
 +
 +      if(autocvar_menu_slist_categories >= 0) // if less than 0, don't even draw a category heading for favorites
 +      {
 +              float itemcount = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
 +              me.nItems = itemcount;
 +              
 +              //float visible = floor(me.scrollPos / me.itemHeight);
 +              // ^ unfortunately no such optimization can be made-- we must process through the
 +              // entire list, otherwise there is no way to know which item is first in its category.
 +
 +              // binary search method suggested by div
 +              float x;
 +              float begin = 0;
 +              for(x = 1; x <= category_ent_count; ++x) {
 +                      float first = begin;
 +                      float last = (itemcount - 1);
 +                      if (first > last) {
 +                              // List is empty.
 +                              break;
 +                      }
 +                      float catf = gethostcachenumber(SLIST_FIELD_CATEGORY, first);
 +                      float catl = gethostcachenumber(SLIST_FIELD_CATEGORY, last);
 +                      if (catf > x) {
 +                              // The first one is already > x.
 +                              // Therefore, category x does not exist.
 +                              // Higher numbered categories do exist though.
 +                      } else if (catl < x) {
 +                              // The last one is < x.
 +                              // Thus this category - and any following -
 +                              // don't exist.
 +                              break;
 +                      } else if (catf == x) {
 +                              // Starts at first. This breaks the loop
 +                              // invariant in the binary search and thus has
 +                              // to be handled separately.
 +                              if(gethostcachenumber(SLIST_FIELD_CATEGORY, first) != x)
 +                                      error("Category mismatch I");
 +                              if(first > 0)
 +                                      if(gethostcachenumber(SLIST_FIELD_CATEGORY, first - 1) == x)
 +                                              error("Category mismatch II");
 +                              category_name[category_draw_count] = x;
 +                              category_item[category_draw_count] = first;
 +                              ++category_draw_count;
 +                              begin = first + 1;
 +                      } else {
 +                              // At this point, catf <= x < catl, thus
 +                              // catf < catl, thus first < last.
 +                              // INVARIANTS:
 +                              // last - first >= 1
 +                              // catf == gethostcachenumber(SLIST_FIELD_CATEGORY(first)
 +                              // catl == gethostcachenumber(SLIST_FIELD_CATEGORY(last)
 +                              // catf < x
 +                              // catl >= x
 +                              while (last - first > 1) {
 +                                      float middle = floor((first + last) / 2);
 +                                      // By loop condition, middle != first && middle != last.
 +                                      float cat = gethostcachenumber(SLIST_FIELD_CATEGORY, middle);
 +                                      if (cat >= x) {
 +                                              last = middle;
 +                                              catl = cat;
 +                                      } else {
 +                                              first = middle;
 +                                              catf = cat;
 +                                      }
 +                              }
 +                              if (catl == x) {
 +                                      if(gethostcachenumber(SLIST_FIELD_CATEGORY, last) != x)
 +                                              error("Category mismatch III");
 +                                      if(last > 0)
 +                                              if(gethostcachenumber(SLIST_FIELD_CATEGORY, last - 1) == x)
 +                                                      error("Category mismatch IV");
 +                                      category_name[category_draw_count] = x;
 +                                      category_item[category_draw_count] = last;
 +                                      ++category_draw_count;
 +                                      begin = last + 1; // already scanned through these, skip 'em
 +                              }
 +                              else
 +                                      begin = last; // already scanned through these, skip 'em
 +                      }
 +              }
 +              if(autocvar_menu_slist_categories_onlyifmultiple && (category_draw_count == 1))
 +              {
 +                      category_name[0] = -1;
 +                      category_item[0] = -1;
 +                      category_draw_count = 0;
 +                      me.nItems = itemcount;
 +              }
 +      }
 +      else { me.nItems = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT); }
  
        me.connectButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
        me.infoButton.disabled = ((me.nItems == 0) || !owned);
        if(me.selectedServer)
        {
                for(i = 0; i < me.nItems; ++i)
 +              {
                        if(gethostcachestring(SLIST_FIELD_CNAME, i) == me.selectedServer)
                        {
                                if(i != me.selectedItem)
                                found = 1;
                                break;
                        }
 +              }
        }
        if(!found)
 +      {
                if(me.nItems > 0)
                {
                        if(me.selectedItem >= me.nItems)
                                strunzone(me.selectedServer);
                        me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, me.selectedItem));
                }
 -
 +      }
 +      
        if(owned)
        {
                if(me.selectedServer != me.ipAddressBox.text)
@@@ -832,16 -471,7 +832,16 @@@ void ServerList_Filter_Change(entity bo
                me.filterString = strzone(box.text);
        else
                me.filterString = string_null;
 -      me.refreshServerList(me, 0);
 +      me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
 +
 +      me.ipAddressBox.setText(me.ipAddressBox, "");
 +      me.ipAddressBox.cursorPos = 0;
 +      me.ipAddressBoxFocused = -1;
 +}
 +void ServerList_Categories_Click(entity box, entity me)
 +{
 +      box.setChecked(box, autocvar_menu_slist_categories = !autocvar_menu_slist_categories);
 +      me.refreshServerList(me, REFRESHSERVERLIST_RESORT);
  
        me.ipAddressBox.setText(me.ipAddressBox, "");
        me.ipAddressBox.cursorPos = 0;
  void ServerList_ShowEmpty_Click(entity box, entity me)
  {
        box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
 -      me.refreshServerList(me, 0);
 +      me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
  
        me.ipAddressBox.setText(me.ipAddressBox, "");
        me.ipAddressBox.cursorPos = 0;
  void ServerList_ShowFull_Click(entity box, entity me)
  {
        box.setChecked(box, me.filterShowFull = !me.filterShowFull);
 -      me.refreshServerList(me, 0);
 +      me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
  
        me.ipAddressBox.setText(me.ipAddressBox, "");
        me.ipAddressBox.cursorPos = 0;
@@@ -880,7 -510,7 +880,7 @@@ void XonoticServerList_setSortOrder(ent
        if(me.selectedServer)
                strunzone(me.selectedServer);
        me.selectedServer = string_null;
 -      me.refreshServerList(me, 0);
 +      me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
  }
  void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
  {
@@@ -936,11 -566,10 +936,11 @@@ void XonoticServerList_resizeNotify(ent
  }
  void ServerList_Connect_Click(entity btn, entity me)
  {
 -      if(me.ipAddressBox.text == "")
 -              localcmd("connect ", me.selectedServer, "\n");
 -      else
 -              localcmd("connect ", me.ipAddressBox.text, "\n");
 +      localcmd(sprintf("connect %s\n",
 +              ((me.ipAddressBox.text != "") ?
 +                      me.ipAddressBox.text : me.selectedServer
 +              )
 +      ));
  }
  void ServerList_Favorite_Click(entity btn, entity me)
  {
        ipstr = netaddress_resolve(me.ipAddressBox.text, 26000);
        if(ipstr != "")
        {
 -              ToggleFavorite(me.ipAddressBox.text);
 +              me.toggleFavorite(me, me.ipAddressBox.text);
                me.ipAddressBoxFocused = -1;
        }
  }
  void ServerList_Info_Click(entity btn, entity me)
  {
 -      main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, me.selectedItem);
 +      if (me.nItems != 0)
 +              main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, me.selectedItem);
        DialogOpenButton_Click(me, main.serverInfoDialog);
  }
  void XonoticServerList_clickListBoxItem(entity me, float i, vector where)
@@@ -979,48 -607,6 +979,48 @@@ void XonoticServerList_drawListBoxItem(
        float m, pure, freeslots, j, sflags;
        string s, typestr, versionstr, k, v, modname;
  
 +      //print(sprintf("time: %f, i: %d, item: %d, nitems: %d\n", time, i, item, me.nItems));
 +
 +      vector oldscale = draw_scale;
 +      vector oldshift = draw_shift;
 +#define SET_YRANGE(start,end) \
 +      draw_scale = boxToGlobalSize(eX * 1 + eY * (end - start), oldscale); \
 +      draw_shift = boxToGlobal(eY * start, oldshift, oldscale);
 +
 +      for (j = 0; j < category_draw_count; ++j) {
 +              // Matches exactly the headings with increased height.
 +              if (i == category_item[j])
 +                      break;
 +      }
 +
 +      if (j < category_draw_count)
 +      {
 +              entity catent = RetrieveCategoryEnt(category_name[j]);
 +              if(catent)
 +              {
 +                      SET_YRANGE(
 +                              (me.categoriesHeight - 1) / (me.categoriesHeight + 1),
 +                              me.categoriesHeight / (me.categoriesHeight + 1)
 +                      );
 +                      draw_Text(
 +                              eY * me.realUpperMargin
 +                              +
 +#if 0
 +                              eX * (me.columnNameOrigin + (me.columnNameSize - draw_TextWidth(catent.cat_string, 0, me.realFontSize)) * 0.5),
 +                              catent.cat_string,
 +#else
 +                              eX * (me.columnNameOrigin),
 +                              strcat(catent.cat_string, ":"),
 +#endif
 +                              me.realFontSize,
 +                              '1 1 1',
 +                              SKINALPHA_TEXT,
 +                              0
 +                      );
 +                      SET_YRANGE(me.categoriesHeight / (me.categoriesHeight + 1), 1);
 +              }
 +      }
 +      
        if(isSelected)
                draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
  
                theAlpha = SKINALPHA_SERVERLIST_FULL;
        else if(freeslots == 0)
                theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
-       else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, i))
+       else if (!gethostcachenumber(SLIST_FIELD_NUMHUMANS, i))
                theAlpha = SKINALPHA_SERVERLIST_EMPTY;
        else
                theAlpha = 1;
        // 4: AES recommended and will be used
        // 5: AES required
  
 -      {
 -              vector iconSize = '0 0 0';
 -              iconSize_y = me.realFontSize_y * me.iconsSizeFactor;
 -              iconSize_x = me.realFontSize_x * me.iconsSizeFactor;
 -
 -              vector iconPos = '0 0 0';
 -              iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5;
 -              iconPos_y = (1 - iconSize_y) * 0.5;
 +      // --------------
 +      //  RENDER ICONS
 +      // --------------
 +      vector iconSize = '0 0 0';
 +      iconSize_y = me.realFontSize_y * me.iconsSizeFactor;
 +      iconSize_x = me.realFontSize_x * me.iconsSizeFactor;
  
 -              string n;
 +      vector iconPos = '0 0 0';
 +      iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5;
 +      iconPos_y = (1 - iconSize_y) * 0.5;
  
 -              if (!(me.seenIPv4 && me.seenIPv6))
 -              {
 -                      iconPos_x += iconSize_x * 0.5;
 -              }
 -              else if(me.seenIPv4 && me.seenIPv6)
 -              {
 -                      n = string_null;
 -                      if(isv6)
 -                              draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), 0); // PRECACHE_PIC_MIPMAP
 -                      else if(isv4)
 -                              draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), 0); // PRECACHE_PIC_MIPMAP
 -                      if(n)
 -                              draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
 -                      iconPos_x += iconSize_x;
 -              }
 +      string n;
  
-       if not(me.seenIPv4 && me.seenIPv6)
 -              if(q > 0)
 -              {
 -                      draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), 0); // PRECACHE_PIC_MIPMAP
++      if (!(me.seenIPv4 && me.seenIPv6))
 +      {
 +              iconPos_x += iconSize_x * 0.5;
 +      }
 +      else if(me.seenIPv4 && me.seenIPv6)
 +      {
 +              n = string_null;
 +              if(isv6)
 +                      draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), 0); // PRECACHE_PIC_MIPMAP
 +              else if(isv4)
 +                      draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), 0); // PRECACHE_PIC_MIPMAP
 +              if(n)
                        draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
 -              }
                iconPos_x += iconSize_x;
 +      }
  
 -              if(modname == "Xonotic")
 -              {
 -                      if(pure == 0)
 -                      {
 -                              draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_pure1"), PRECACHE_PIC_MIPMAP);
 -                              draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
 -                      }
 -              }
 -              else
 -              {
 -                      draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_", modname), PRECACHE_PIC_MIPMAP);
 -                      if(draw_PictureSize(n) == '0 0 0')
 -                              draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_"), PRECACHE_PIC_MIPMAP);
 -                      if(pure == 0)
 -                              draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
 -                      else
 -                              draw_Picture(iconPos, n, iconSize, '1 1 1', SKINALPHA_SERVERLIST_ICON_NONPURE);
 -              }
 -              iconPos_x += iconSize_x;
 +      if(q > 0)
 +      {
 +              draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), 0); // PRECACHE_PIC_MIPMAP
 +              draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
 +      }
 +      iconPos_x += iconSize_x;
  
 -              if(sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS))
 +      if(modname == "Xonotic")
 +      {
 +              if(pure == 0)
                {
 -                      draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_stats1"), 0); // PRECACHE_PIC_MIPMAP
 +                      draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_pure1"), PRECACHE_PIC_MIPMAP);
                        draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
                }
 -              iconPos_x += iconSize_x;
        }
 +      else
 +      {
 +              draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_", modname), PRECACHE_PIC_MIPMAP);
 +              if(draw_PictureSize(n) == '0 0 0')
 +                      draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_"), PRECACHE_PIC_MIPMAP);
 +              if(pure == 0)
 +                      draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
 +              else
 +                      draw_Picture(iconPos, n, iconSize, '1 1 1', SKINALPHA_SERVERLIST_ICON_NONPURE);
 +      }
 +      iconPos_x += iconSize_x;
  
 +      if(sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS))
 +      {
 +              draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_stats1"), 0); // PRECACHE_PIC_MIPMAP
 +              draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
 +      }
 +      iconPos_x += iconSize_x;
 +      
 +      // --------------
 +      //  RENDER TEXT
 +      // --------------
 +      
 +      // ping
        s = ftos(p);
        draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
 +
 +      // server name
        s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, i), me.columnNameSize, 0, me.realFontSize);
        draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
 +
 +      // server map
        s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, i), me.columnMapSize, 0, me.realFontSize);
        draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
 +
 +      // server gametype
        s = draw_TextShortenToWidth(typestr, me.columnTypeSize, 0, me.realFontSize);
        draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
 +
 +      // server playercount
        s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, i)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, i)));
        draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
  }
  
  float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
  {
 -      float i;
        vector org, sz;
  
        org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
        }
        else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
        {
 -              i = me.selectedItem;
 -              if(i < me.nItems)
 +              if(me.nItems != 0)
                {
 -                      ToggleFavorite(me.selectedServer);
 +                      me.toggleFavorite(me, me.selectedServer);
                        me.ipAddressBoxFocused = -1;
                        return 1;
                }
        else
                return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);
  }
 +
 +float XonoticServerList_getTotalHeight(entity me) {
 +      float num_normal_rows = me.nItems;
 +      float num_headers = category_draw_count;
 +      return me.itemHeight * (num_normal_rows + me.categoriesHeight * num_headers);
 +}
 +float XonoticServerList_getItemAtPos(entity me, float pos) {
 +      pos = pos / me.itemHeight;
 +      float i;
 +      for (i = category_draw_count - 1; i >= 0; --i) {
 +              float itemidx = category_item[i];
 +              float itempos = i * me.categoriesHeight + category_item[i];
 +              if (pos >= itempos + me.categoriesHeight + 1)
 +                      return itemidx + 1 + floor(pos - (itempos + me.categoriesHeight + 1));
 +              if (pos >= itempos)
 +                      return itemidx;
 +      }
 +      // No category matches? Note that category 0 is... 0. Therefore no headings exist at all.
 +      return floor(pos);
 +}
 +float XonoticServerList_getItemStart(entity me, float item) {
 +      float i;
 +      for (i = category_draw_count - 1; i >= 0; --i) {
 +              float itemidx = category_item[i];
 +              float itempos = i * me.categoriesHeight + category_item[i];
 +              if (item >= itemidx + 1)
 +                      return (itempos + me.categoriesHeight + 1 + item - (itemidx + 1)) * me.itemHeight;
 +              if (item >= itemidx)
 +                      return itempos * me.itemHeight;
 +      }
 +      // No category matches? Note that category 0 is... 0. Therefore no headings exist at all.
 +      return item * me.itemHeight;
 +}
 +float XonoticServerList_getItemHeight(entity me, float item) {
 +      float i;
 +      for (i = 0; i < category_draw_count; ++i) {
 +              // Matches exactly the headings with increased height.
 +              if (item == category_item[i])
 +                      return me.itemHeight * (me.categoriesHeight + 1);
 +      }
 +      return me.itemHeight;
 +}
 +
  #endif
index dd022eb6996059f6a29ddf612b6bc915a816abc0,0f8e341e74a782d75ec951d273e5d397ef4dd512..85d55b47aa2ed421b301ac8c23d34e130b8c3de3
@@@ -284,11 -284,6 +284,11 @@@ void URI_Get_Callback(float id, float s
        }
  }
  
 +void DisableServerBackwardsCompatibility()
 +{
 +      cvar_set("gameversion_min", ftos(100 * floor(cvar("gameversion") / 100)));
 +}
 +
  void UpdateNotification_URI_Get_Callback(float id, float status, string data)
  {
        float n;
        else
                n = tokenizebyseparator(data, "\n");
  
 -      if(n >= 1)
 +      float i;
 +      string s; 
 +      
 +      string un_version = "";
 +      string un_download = "";
 +      string un_url = "";
 +      string un_bannedservers = "";
 +      string un_emergency_pk3s = "";
 +      string un_promoted = "";
 +      string un_recommended = "";
 +      string un_compatexpire = "";
 +      
 +      for(i = 0; i < n; ++i)
        {
 -              _Nex_ExtResponseSystem_UpdateTo = argv(0);
 -
 -              if(vercmp(cvar_string("g_xonoticversion"), _Nex_ExtResponseSystem_UpdateTo) >= 0)
 +              s = substring(argv(i), 2, -1);
 +              if(s == "") { continue; } // ignore empty lines
 +              
 +              switch(substring(argv(i), 0, 1))
                {
 -                      _Nex_ExtResponseSystem_UpdateTo = ""; // no update needed
 -              }
 -              else
 -              {
 -                      // update needed
 -                      if(n >= 2)
 -                              print(sprintf(_("Update can be downloaded at:\n%s\n"), argv(1)));
 -                      if(n >= 3)
 -                              _Nex_ExtResponseSystem_UpdateToURL = strzone(argv(2));
 +                      #define APPEND_TO_STRING(list,sep,add) ((list) = (((list) != "") ? strcat(list, sep, add) : (add)))
 +                      case "V":
 +                      {
 +                              un_version = s;
 +                              break;
 +                      }
 +                      case "C":
 +                      {
 +                              un_compatexpire = s;
 +                              break;
 +                      }
 +                      case "D":
 +                      {
 +                              un_download = s;
 +                              break;
 +                      }
 +                      case "U":
 +                      {
 +                              un_url = s;
 +                              break;
 +                      }
 +                      case "B":
 +                      {
 +                              APPEND_TO_STRING(un_bannedservers, " ", s);
 +                              break;
 +                      }
 +                      case "E":
 +                      {
 +                              if(cvar("menu_updatecheck_getpacks"))
 +                                      APPEND_TO_STRING(un_emergency_pk3s, " ", s);
 +                              break;
 +                      }
 +                      case "P":
 +                      {
 +                              APPEND_TO_STRING(un_promoted, " ", s);
 +                              break;
 +                      }
 +                      case "R":
 +                      {
 +                              APPEND_TO_STRING(un_recommended, " ", s);
 +                              break;
 +                      }
                }
 +      }
  
 -              _Nex_ExtResponseSystem_UpdateTo = strzone(_Nex_ExtResponseSystem_UpdateTo);
 -
 -              if(n >= 4)
 +      if(un_version != "")
 +      {
 +              if(vercmp(cvar_string("g_xonoticversion"), un_version) < 0)
                {
 -                      _Nex_ExtResponseSystem_BannedServers = strzone(argv(3));
 -                      _Nex_ExtResponseSystem_BannedServersNeedsRefresh = 1;
 +                      // update needed
 +                      _Nex_ExtResponseSystem_UpdateTo = strzone(un_version);
 +                      if(un_download) { print(sprintf(_("Update can be downloaded at:\n%s\n"), un_download)); }
 +                      if(un_url) { _Nex_ExtResponseSystem_UpdateToURL = strzone(un_url); }
 +                      DisableServerBackwardsCompatibility();
                }
 -
 -              if(n >= 5)
 +              else if(cvar_string("g_xonoticversion") == un_version)
                {
 -                      if(cvar("menu_updatecheck_getpacks"))
 +                      if(un_compatexpire != "")
                        {
 -                              _Nex_ExtResponseSystem_Packs = strzone(argv(4));
 -                              _Nex_ExtResponseSystem_PacksStep = 1;
 +                              string curdate = strftime(FALSE, "%Y%m%d%H%M%S");
 +                              if (strcmp(curdate, un_compatexpire) >= 0)
 +                                      DisableServerBackwardsCompatibility();
                        }
                }
        }
 +      
 +      if(un_emergency_pk3s != "")
 +      {
 +              _Nex_ExtResponseSystem_Packs = strzone(un_emergency_pk3s);
 +              _Nex_ExtResponseSystem_PacksStep = 1;
 +      }
 +
 +      if(un_promoted != "")
 +      {
 +              _Nex_ExtResponseSystem_PromotedServers = strzone(un_promoted);
 +              _Nex_ExtResponseSystem_PromotedServersNeedsRefresh = 1;
 +      }
 +
 +      if(un_recommended != "")
 +      {
 +              _Nex_ExtResponseSystem_RecommendedServers = strzone(un_recommended);
 +              _Nex_ExtResponseSystem_RecommendedServersNeedsRefresh = 1;
 +      }
  }
  
  // END OF URI SYSTEM ////////////////////////////////////////////////////////
@@@ -439,8 -366,36 +439,8 @@@ void updateCheck(
  
                        // for privacy, munge the start count a little
                        startcnt = floor((floor(startcnt / 10) + random()) * 10);
 -                      uri = sprintf("http://www.xonotic.org/dl/checkupdate.txt?version=%s&cnt=%d", uri_escape(cvar_string("g_xonoticversion")), startcnt);
 -
 -#ifdef CVAR_POPCON
 -                      float cvar_handle, popcon_handle;
 -                      float n, i, j;
 -                      string k, s;
 -                      cvar_handle = buf_create();
 -                      buf_cvarlist(cvar_handle, "", "");
 -                      n = buf_getsize(cvar_handle);
 -                      popcon_handle = buf_create();
 -                      for(i= 0, j = 0; i < n; ++i)
 -                      {
 -                              k = bufstr_get(cvar_handle, i);
 -                              if(!(cvar_type(k) & CVAR_TYPEFLAG_SAVED))
 -                                      continue;
 -                              s = sprintf("%s=%d", uri_escape(k), cvar_string(k) != cvar_defstring(k));
 -                              bufstr_set(popcon_handle, j, s);
 -                              ++j;
 -                      }
 -                      buf_del(cvar_handle);
 -                      uri_postbuf(
 -                              uri, URI_GET_UPDATENOTIFICATION,
 -                              "application/x-www-form-urlencoded",
 -                              "&",
 -                              popcon_handle
 -                      );
 -                      buf_del(popcon_handle);
 -#else
 +                      uri = sprintf("http://update.xonotic.org/checkupdate.txt?version=%s&cnt=%d", uri_escape(cvar_string("g_xonoticversion")), startcnt);
                        uri_get(uri, URI_GET_UPDATENOTIFICATION);
 -#endif
                }
        }
  
@@@ -494,7 -449,7 +494,7 @@@ float preMenuInit(
                boxA = '0.05 0.5 0' + 0.25 * sz_y * eY;
                boxB = '0.95 0.5 0' + 1.25 * sz_y * eY;
                draw_Fill(boxA, boxB - boxA, '1 1 1', 1);
-               
                boxA += sz * 0.1;
                boxB -= sz * 0.1;
                draw_Fill(boxA, boxB - boxA, '0.1 0.1 0.1', 1);
@@@ -552,7 -507,7 +552,7 @@@ void preMenuDraw(
                draw_CenterText(mid - 1 * line, l1, fs, '1 0 0', 1, 0);
                draw_CenterText(mid - 0 * line, l2, fs, '0 0 1', 1, 0);
        }
-       if not(campaign_name_previous)
+       if (!campaign_name_previous)
                campaign_name_previous = strzone(strcat(campaign_name, "x")); // force unequal
        if(campaign_name == campaign_name_previous)
        {
@@@ -661,13 -616,13 +661,13 @@@ float GameType_GetID(float cnt
  {
        float i;
        i = 0;
-       
        #define GAMETYPE(id) if(i++ == cnt) return id;
        GAMETYPES
        #undef GAMETYPE
  
        unused_float = i;
-       
        return 0;
  }
  
@@@ -675,31 -630,31 +675,31 @@@ float GameType_GetCount(
  {
        float i;
        i = 0;
-       
        #define GAMETYPE(id) ++i;
        GAMETYPES
        #undef GAMETYPE
-       
        return i;
  }
  
  string GameType_GetName(float cnt)
  {
        float i = GameType_GetID(cnt);
-       
        if(i)
                return MapInfo_Type_ToText(i);
-       
        return "";
  }
  
  string GameType_GetIcon(float cnt)
  {
        float i = GameType_GetID(cnt);
-       
        if(i)
                return strcat("gametype_", MapInfo_Type_ToString(i));
-       
        return "";
  }
  
  {
        float i = GameType_GetID(cnt);
        string s = _MapInfo_GetDefaultEx(i);
-       
        if(i)
        {
                if(strstrofs(s, "teams", 0) >= 0)
                else
                        return _("free for all");
        }
-       
        return _("tuba for all");
  }*/
  
index ff82a4ddf731eec3ce8b58016d9d1b2d99322a10,65443f4782992f51f3528ebcbbc36300f98f8d47..92fca7b7580e32421e053481ba8e97e213769c2d
@@@ -180,7 -180,7 +180,7 @@@ void PutObserverInServer (void
  
        WaypointSprite_PlayerDead();
  
-       if not(g_ca)  // don't reset teams when moving a ca player to the spectators
+       if (!g_ca)  // don't reset teams when moving a ca player to the spectators
                self.team = -1;  // move this as it is needed to log the player spectating in eventlog
  
        if(self.killcount != -666)
@@@ -984,7 -984,7 +984,7 @@@ float PlayerInIDList(entity p, string i
        string s;
  
        // NOTE: we do NOT check crypto_keyfp here, an unsigned ID is fine too for this
-       if not(p.crypto_idfp)
+       if (!p.crypto_idfp)
                return 0;
  
        // this function allows abbreviated player IDs too!
@@@ -1039,7 -1039,6 +1039,7 @@@ void ClientConnect (void
                player_count = 0;
        }
  
 +        PlayerInfo_Basic(self);
        PlayerScore_Attach(self);
        ClientData_Attach();
        accuracy_init(self);
@@@ -1250,7 -1249,7 +1250,7 @@@ void ClientDisconnect (void
        if(self.vehicle)
            vehicles_exit(VHEF_RELESE);
  
-       if not(IS_CLIENT(self))
+       if (!IS_CLIENT(self))
        {
                print("Warning: ClientDisconnect without ClientConnect\n");
                return;
@@@ -1431,7 -1430,7 +1431,7 @@@ void player_powerups (void
        Fire_ApplyDamage(self);
        Fire_ApplyEffect(self);
  
-       if not(g_minstagib)
+       if (!g_minstagib)
        {
                if (self.items & IT_STRENGTH)
                {
@@@ -1614,7 -1613,7 +1614,7 @@@ void player_regen (void
                        self.event_damage(self, self, 1, DEATH_ROT, self.origin, '0 0 0');
        }
  
-       if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
+       if (!(self.items & IT_UNLIMITED_WEAPON_AMMO))
                self.ammo_fuel = CalcRotRegen(self.ammo_fuel, minf, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, regen_mod * frametime * (time > self.pauseregen_finished) * ((self.items & IT_FUEL_REGEN) != 0), maxf, autocvar_g_balance_fuel_rot, autocvar_g_balance_fuel_rotlinear, rot_mod * frametime * (time > self.pauserotfuel_finished), limitf);
  }
  
@@@ -1767,7 -1766,7 +1767,7 @@@ float SpectateUpdate() 
        if (self == self.enemy)
                return 0;
  
-       if not(IS_PLAYER(self.enemy))
+       if (!IS_PLAYER(self.enemy))
                return 0;
  
        SpectateCopy(self.enemy);
@@@ -1862,7 -1861,7 +1862,7 @@@ float SpectatePrev(
  {
        // NOTE: chain order is from the highest to the lower entnum (unlike find)
        other = findchain(classname, "player");
-       if not(other) // no player
+       if (!other) // no player
                return FALSE;
  
        entity first = other;
                do { other = other.chain; }
                while(other && other.team != self.team);
  
-               if not(other)
+               if (!other)
                {
                        other = first;
                        while(other.team != self.team)
@@@ -2136,7 -2135,7 +2136,7 @@@ void SpectatorThink(
  
  void PlayerUseKey()
  {
-       if not(IS_PLAYER(self))
+       if (!IS_PLAYER(self))
                return;
  
        if(self.vehicle)
@@@ -2604,7 -2603,7 +2604,7 @@@ void PlayerPostThink (void
  
        if((g_cts || g_race) && self.cvar_cl_allow_uidtracking == 1 && self.cvar_cl_allow_uid2name == 1)
        {
-               if not(self.stored_netname)
+               if (!self.stored_netname)
                        self.stored_netname = strzone(uid2name(self.crypto_idfp));
                if(self.stored_netname != self.netname)
                {