]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into TimePath/experiments/csqc_prediction
authorTimePath <andrew.hardaker1995@gmail.com>
Mon, 2 Feb 2015 08:53:04 +0000 (19:53 +1100)
committerTimePath <andrew.hardaker1995@gmail.com>
Mon, 2 Feb 2015 08:59:48 +0000 (19:59 +1100)
Conflicts:
qcsrc/menu/classes.c
qcsrc/menu/item/modalcontroller.qc
qcsrc/menu/menu.qh
qcsrc/menu/xonotic/maplist.qc
qcsrc/server/command/banning.qc
qcsrc/server/ipban.qc
qcsrc/server/miscfunctions.qc

38 files changed:
1  2 
defaultXonotic.cfg
qcsrc/common/playerstats.qc
qcsrc/common/weapons/weapons.qc
qcsrc/common/weapons/weapons.qh
qcsrc/dpdefs/progsdefs.qh
qcsrc/menu/command/menu_cmd.qc
qcsrc/menu/item.qc
qcsrc/menu/item/button.qc
qcsrc/menu/item/checkbox.qc
qcsrc/menu/item/dialog.qc
qcsrc/menu/item/inputbox.qc
qcsrc/menu/item/listbox.qc
qcsrc/menu/item/modalcontroller.qc
qcsrc/menu/item/nexposee.qc
qcsrc/menu/item/slider.qc
qcsrc/menu/menu.qc
qcsrc/menu/menu.qh
qcsrc/menu/xonotic/colorpicker.qc
qcsrc/menu/xonotic/colorpicker_string.qc
qcsrc/menu/xonotic/dialog_multiplayer_profile.qc
qcsrc/menu/xonotic/dialog_settings_audio.qc
qcsrc/menu/xonotic/dialog_settings_game_crosshair.qc
qcsrc/menu/xonotic/dialog_singleplayer_winner.qc
qcsrc/menu/xonotic/gametypelist.qc
qcsrc/menu/xonotic/keybinder.qc
qcsrc/menu/xonotic/languagelist.qc
qcsrc/menu/xonotic/maplist.qc
qcsrc/menu/xonotic/playerlist.qc
qcsrc/menu/xonotic/serverlist.qc
qcsrc/menu/xonotic/skinlist.qc
qcsrc/server/cl_client.qc
qcsrc/server/command/banning.qc
qcsrc/server/command/common.qc
qcsrc/server/command/common.qh
qcsrc/server/command/vote.qc
qcsrc/server/ipban.qc
qcsrc/server/miscfunctions.qh
qcsrc/server/t_items.qc

diff --combined defaultXonotic.cfg
index 3b1d205d20f9919b61374c61878649f6a4dfe3b8,034b3bb32d9df4fe1f3139dad707e1742af9c2c7..f598c3c76b361fdb6fe0e3cbc1f0285206e7ec17
@@@ -52,6 -52,7 +52,7 @@@ mod_q3bsp_lightmapmergepower 
  // player defaults
  _cl_color "112.211" // same effect as 112, but menuqc can detect this as the default and not intentionally set
  _cl_name ""
+ seta _cl_gender 0 "storage cvar for current player gender (0 = undisclosed, 1 = male, 2 = female)"
  _cl_playermodel models/player/erebus.iqm
  _cl_playerskin 0
  
@@@ -295,7 -296,6 +296,7 @@@ set sv_fraginfo_stats 1 "Enable statist
  
  // use default physics
  set sv_friction_on_land 0
 +set sv_friction_slick 0.5
  
  set sv_player_viewoffset "0 0 35" "view offset of the player model"
  set sv_player_mins "-16 -16 -24" "playermodel mins"
@@@ -787,12 -787,12 +788,12 @@@ set g_banned_list_idmode "1"    "when set
  
  r_labelsprites_scale 0.40625 // labels sprites get displayed at 0.5x from 640x480 to 1280x1024, and at 1x from 1600x1200 onwards
  
- exec binds-default.cfg
+ exec binds-xonotic.cfg
  
  // we must change its default from 1.0 to 1 to be consistent with menuqc
  set slowmo 1
  
- seta menu_skin "luminos"
+ seta menu_skin "luma"
  set menu_slowmo 1
  seta menu_sounds 0 "enables menu sound effects. 1 enables click sounds, 2 also enables hover sounds"
  seta menu_tooltips 1 "menu tooltips: 0 disabled, 1 enabled, 2 also shows cvar or console command (when available) changed or executed by the item"
@@@ -1407,7 -1407,7 +1408,7 @@@ exec _hud_common.cf
  exec _hud_descriptions.cfg
  // exec the default skin config
  // please add any new cvars into the hud_save script in qcsrc/client/hud_config.qc for consistency
- exec hud_luminos.cfg
+ exec hud_luma.cfg
  
  
  // ... and now that everything is configured/aliased, we can do some things:
index 27a7bb6ec2cd2f7d8238e0f27e424adb4cd792a5,65516a4ea15c67d4b4751f9f25ab2a1067199ea3..5890fb89e17085271e6061684e3792a6bcc0a735
@@@ -1,18 -1,3 +1,18 @@@
 +#if defined(CSQC)
 +#elif defined(MENUQC)
 +#elif defined(SVQC)
 +      #include "../dpdefs/progsdefs.qh"
 +    #include "../dpdefs/dpextensions.qh"
 +    #include "constants.qh"
 +    #include "util.qh"
 +    #include "urllib.qh"
 +    #include "weapons/weapons.qh"
 +    #include "../server/weapons/accuracy.qh"
 +    #include "../server/defs.qh"
 +    #include "playerstats.qh"
 +    #include "../server/scores.qh"
 +#endif
 +
  #ifdef SVQC
  void PlayerStats_Prematch(void)
  {
@@@ -109,11 -94,12 +109,11 @@@ float PlayerStats_GameReport_Event(stri
  
  void PlayerStats_GameReport_Accuracy(entity p)
  {
 -      entity w;
 -      float i;
 +      int i;
  
        for(i = WEP_FIRST; i <= WEP_LAST; ++i)
        {
 -              w = get_weaponinfo(i);
 +              entity w = get_weaponinfo(i);
  
                #define ACCMAC(suffix,field) \
                        PS_GR_P_ADDVAL(p, sprintf("acc-%s-%s", w.netname, suffix), p.accuracy.(field[i-1]));
@@@ -205,7 -191,7 +205,7 @@@ void PlayerStats_GameReport(float finis
  
        if(autocvar_g_playerstats_gamereport_uri != "")
        {
 -              PlayerStats_GameReport_DelayMapVote = TRUE;
 +              PlayerStats_GameReport_DelayMapVote = true;
                url_multi_fopen(
                        autocvar_g_playerstats_gamereport_uri,
                        FILE_APPEND,
        }
        else
        {
 -              PlayerStats_GameReport_DelayMapVote = FALSE;
 +              PlayerStats_GameReport_DelayMapVote = false;
                db_close(PS_GR_OUT_DB);
                PS_GR_OUT_DB = -1;
        }
@@@ -229,7 -215,7 +229,7 @@@ void PlayerStats_GameReport_Init() // i
  
        if(PS_GR_OUT_DB >= 0)
        {
 -              PlayerStats_GameReport_DelayMapVote = TRUE;
 +              PlayerStats_GameReport_DelayMapVote = true;
  
                serverflags |= SERVERFLAG_PLAYERSTATS;
  
                PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD);
                PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM);
        }
 -      else { PlayerStats_GameReport_DelayMapVote = FALSE; }
 +      else { PlayerStats_GameReport_DelayMapVote = false; }
  }
  
  void PlayerStats_GameReport_Handler(entity fh, entity pass, float status)
                {
                        // url_fclose has finished
                        dprint("Player stats written\n");
 -                      PlayerStats_GameReport_DelayMapVote = FALSE;
 +                      PlayerStats_GameReport_DelayMapVote = false;
                        if(PS_GR_OUT_DB >= 0)
                        {
                                db_close(PS_GR_OUT_DB);
                default:
                {
                        print("Player stats writing failed: ", ftos(status), "\n");
 -                      PlayerStats_GameReport_DelayMapVote = FALSE;
 +                      PlayerStats_GameReport_DelayMapVote = false;
                        if(PS_GR_OUT_DB >= 0)
                        {
                                db_close(PS_GR_OUT_DB);
@@@ -516,7 -502,7 +516,7 @@@ void PlayerStats_PlayerBasic_Handler(en
                        #endif
                        url_fputs(fh, sprintf("l %s\n", cvar_string("_menu_prvm_language"))); // language
                        url_fputs(fh, sprintf("c %s\n", cvar_string("_menu_prvm_country"))); // country
-                       url_fputs(fh, sprintf("g %s\n", cvar_string("_menu_prvm_gender"))); // gender
+                       url_fputs(fh, sprintf("g %s\n", cvar_string("_cl_gender"))); // gender
                        url_fputs(fh, sprintf("n %s\n", cvar_string("_cl_name"))); // name
                        url_fputs(fh, sprintf("m %s %s\n", cvar_string("_cl_playermodel"), cvar_string("_cl_playerskin"))); // model/skin
                        */url_fputs(fh, "\n");
@@@ -916,7 -902,7 +916,7 @@@ void PlayerInfo_ready(entity fh, entit
  #ifdef MENUQC
                          url_fputs(fh, sprintf("l %s\n", cvar_string("_menu_prvm_language"))); // language
                          url_fputs(fh, sprintf("c %s\n", cvar_string("_menu_prvm_country"))); // country
-                         url_fputs(fh, sprintf("g %s\n", cvar_string("_menu_prvm_gender"))); // gender
+                         url_fputs(fh, sprintf("g %s\n", cvar_string("_cl_gender"))); // gender
                          url_fputs(fh, sprintf("n %s\n", cvar_string("_cl_name"))); // name
                          url_fputs(fh, sprintf("m %s %s\n", cvar_string("_cl_playermodel"), cvar_string("_cl_playerskin"))); // model/skin
  #endif
@@@ -1038,7 -1024,7 +1038,7 @@@ void PlayerInfo_Details(
  
  #ifdef CSQC
  /*
 - * FIXME - crypto_* builtin functions missing in CSQC (csprogsdefs.qc:885)
 + * FIXME - crypto_* builtin functions missing in CSQC (csprogsdefs.qh:885)
  void PlayerInfo_Details()
  {
          print("-- Getting detailed PlayerInfo for local player (CSQC)\n");
index b9079a58f74dc46d61da003ea0607c76313f59c8,284d9811df50c44c98205ca041a659162079493d..e0869559be204d30b4dab4f4d03de7f938d292ce
@@@ -1,56 -1,3 +1,56 @@@
 +#ifndef WEAPONS_C
 +#define WEAPONS_C
 +
 +#if defined(CSQC)
 +      #include "../../dpdefs/csprogsdefs.qh"
 +      #include "../../client/defs.qh"
 +      #include "../constants.qh"
 +      #include "../stats.qh"
 +      #include "../../warpzonelib/anglestransform.qh"
 +      #include "../../warpzonelib/mathlib.qh"
 +      #include "../../warpzonelib/common.qh"
 +      #include "../../warpzonelib/client.qh"
 +      #include "../util.qh"
 +      #include "../buffs.qh"
 +      #include "weapons.qh"
 +      #include "../../client/autocvars.qh"
 +      #include "../deathtypes.qh"
 +      #include "../../csqcmodellib/interpolate.qh"
 +      #include "../../client/movetypes.qh"
 +      #include "../../client/main.qh"
 +      #include "../../csqcmodellib/cl_model.qh"
 +#elif defined(MENUQC)
 +#elif defined(SVQC)
 +      #include "../../dpdefs/progsdefs.qh"
 +    #include "../../dpdefs/dpextensions.qh"
 +    #include "../../warpzonelib/anglestransform.qh"
 +    #include "../../warpzonelib/mathlib.qh"
 +    #include "../../warpzonelib/common.qh"
 +    #include "../../warpzonelib/util_server.qh"
 +    #include "../../warpzonelib/server.qh"
 +    #include "../constants.qh"
 +    #include "../stats.qh"
 +    #include "../teams.qh"
 +    #include "../util.qh"
 +    #include "../buffs.qh"
 +    #include "../monsters/monsters.qh"
 +    #include "config.qh"
 +    #include "weapons.qh"
 +    #include "../../server/weapons/csqcprojectile.qh"
 +    #include "../../server/weapons/tracing.qh"
 +    #include "../../server/t_items.qh"
 +    #include "../../server/autocvars.qh"
 +    #include "../../server/constants.qh"
 +    #include "../../server/defs.qh"
 +    #include "../notifications.qh"
 +    #include "../deathtypes.qh"
 +    #include "../../server/mutators/mutators_include.qh"
 +    #include "../mapinfo.qh"
 +    #include "../../server/command/common.qh"
 +    #include "../../csqcmodellib/sv_model.qh"
 +    #include "../../server/portals.qh"
 +    #include "../../server/g_hook.qh"
 +#endif
  #ifndef MENUQC
  #include "calculations.qc"
  #endif
@@@ -98,7 -45,7 +98,7 @@@ void WriteWepSet(float dst, WepSet w
  #elif WEP_MAXCOUNT > 24
        WriteInt48_t(dst, w);
  #else
 -      WriteInt24_t(dst, w_x);
 +      WriteInt24_t(dst, w.x);
  #endif
  }
  #endif
  WepSet WepSet_GetFromStat()
  {
        WepSet w = '0 0 0';
 -      w_x = getstati(STAT_WEAPONS);
 +      w.x = getstati(STAT_WEAPONS);
  #if WEP_MAXCOUNT > 24
 -      w_y = getstati(STAT_WEAPONS2);
 +      w.y = getstati(STAT_WEAPONS2);
  #if WEP_MAXCOUNT > 48
 -      w_z = getstati(STAT_WEAPONS3);
 +      w.z = getstati(STAT_WEAPONS3);
  #endif
  #endif
        return w;
@@@ -128,7 -75,7 +128,7 @@@ WepSet ReadWepSet(
  #endif
  
  void register_weapon(
 -      float id,
 +      int id,
        WepSet bit,
        float(float) func,
        .float ammotype,
        e.netname = refname;
        e.message = wepname;
  
-       #ifndef MENUQC
+       #ifdef CSQC
        func(WR_INIT);
        #endif
  }
@@@ -194,14 -141,14 +194,14 @@@ void register_weapons_done(
        dummy_weapon_info.w_crosshair_size = 1;
        dummy_weapon_info.model2 = "";
  
 -      float i;
 +      int i;
        weaponorder_byid = "";
        for(i = WEP_MAXCOUNT; i >= 1; --i)
                if(weapon_info[i-1])
                        weaponorder_byid = strcat(weaponorder_byid, " ", ftos(i));
        weaponorder_byid = strzone(substring(weaponorder_byid, 1, strlen(weaponorder_byid) - 1));
  }
 -entity get_weaponinfo(float id)
 +entity get_weaponinfo(int id)
  {
        entity w;
        if(id < WEP_FIRST || id > WEP_LAST)
@@@ -232,7 -179,7 +232,7 @@@ string W_UndeprecateName(string s
        switch ( s )
        {
                case "nex"            : return "vortex";
 -              case "rocketlauncher" : return "devastator"; 
 +              case "rocketlauncher" : return "devastator";
                case "laser"          : return "blaster";
                case "minstanex"      : return "vaporizer";
                case "grenadelauncher": return "mortar";
@@@ -262,14 -209,14 +262,14 @@@ string W_NumberWeaponOrder(string order
  
  float W_FixWeaponOrder_BuildImpulseList_buf[WEP_MAXCOUNT];
  string W_FixWeaponOrder_BuildImpulseList_order;
 -void W_FixWeaponOrder_BuildImpulseList_swap(float i, float j, entity pass)
 +void W_FixWeaponOrder_BuildImpulseList_swap(int i, int j, entity pass)
  {
        float h;
        h = W_FixWeaponOrder_BuildImpulseList_buf[i];
        W_FixWeaponOrder_BuildImpulseList_buf[i] = W_FixWeaponOrder_BuildImpulseList_buf[j];
        W_FixWeaponOrder_BuildImpulseList_buf[j] = h;
  }
 -float W_FixWeaponOrder_BuildImpulseList_cmp(float i, float j, entity pass)
 +float W_FixWeaponOrder_BuildImpulseList_cmp(int i, int j, entity pass)
  {
        entity e1, e2;
        float d;
  }
  string W_FixWeaponOrder_BuildImpulseList(string o)
  {
 -      float i;
 +      int i;
        W_FixWeaponOrder_BuildImpulseList_order = o;
        for(i = WEP_FIRST; i <= WEP_LAST; ++i)
                W_FixWeaponOrder_BuildImpulseList_buf[i - WEP_FIRST] = i;
@@@ -344,7 -291,7 +344,7 @@@ string GetAmmoPicture(.float ammotype
  }
  
  #ifdef CSQC
 -.float GetAmmoFieldFromNum(float i)
 +.float GetAmmoFieldFromNum(int i)
  {
        switch(i)
        {
        }
  }
  
 -float GetAmmoStat(.float ammotype)
 +int GetAmmoStat(.float ammotype)
  {
        switch(ammotype)
        {
        }
  }
  #endif
 +#endif
index 2835c47d97e87cfef08be0a9c44183a608bbf260,dca226f42018ec13421d15cc6b2329137ca96b8c..7ac8a9e8dfa09ddacc034db56f0debb6a7014c07
@@@ -1,47 -1,44 +1,47 @@@
 +#ifndef WEAPONS_H
 +#define WEAPONS_H
 +
  #ifndef MENUQC
  #include "calculations.qh"
  #endif
  
 -const float MAX_SHOT_DISTANCE = 32768;
 +const int MAX_SHOT_DISTANCE = 32768;
  
  // weapon pickup ratings for bot logic
 -const float BOT_PICKUP_RATING_LOW  =  2500;
 -const float BOT_PICKUP_RATING_MID  =  5000;
 -const float BOT_PICKUP_RATING_HIGH = 10000;
 +const int BOT_PICKUP_RATING_LOW  =  2500;
 +const int BOT_PICKUP_RATING_MID  =  5000;
 +const int BOT_PICKUP_RATING_HIGH = 10000;
  
  // weapon flags
 -const float WEP_TYPE_OTHER          =  0x00; // not for damaging people
 -const float WEP_TYPE_SPLASH         =  0x01; // splash damage
 -const float WEP_TYPE_HITSCAN        =  0x02; // hitscan
 -const float WEP_TYPEMASK            =  0x0F;
 -const float WEP_FLAG_CANCLIMB       =  0x10; // can be used for movement
 -const float WEP_FLAG_NORMAL         =  0x20; // in "most weapons" set
 -const float WEP_FLAG_HIDDEN         =  0x40; // hides from menu
 -const float WEP_FLAG_RELOADABLE     =  0x80; // can has reload
 -const float WEP_FLAG_SUPERWEAPON    = 0x100; // powerup timer
 -const float WEP_FLAG_MUTATORBLOCKED = 0x200; // hides from impulse 99 etc. (mutators are allowed to clear this flag)
 +const int WEP_TYPE_OTHER          =  0x00; // not for damaging people
 +const int WEP_TYPE_SPLASH         =  0x01; // splash damage
 +const int WEP_TYPE_HITSCAN        =  0x02; // hitscan
 +const int WEP_TYPEMASK            =  0x0F;
 +const int WEP_FLAG_CANCLIMB       =  0x10; // can be used for movement
 +const int WEP_FLAG_NORMAL         =  0x20; // in "most weapons" set
 +const int WEP_FLAG_HIDDEN         =  0x40; // hides from menu
 +const int WEP_FLAG_RELOADABLE     =  0x80; // can has reload
 +const int WEP_FLAG_SUPERWEAPON    = 0x100; // powerup timer
 +const int WEP_FLAG_MUTATORBLOCKED = 0x200; // hides from impulse 99 etc. (mutators are allowed to clear this flag)
  
  // weapon requests
 -const float WR_SETUP          =  1; // (SERVER) setup weapon data
 -const float WR_THINK          =  2; // (SERVER) logic to run every frame
 -const float WR_CHECKAMMO1     =  3; // (SERVER) checks ammo for weapon primary
 -const float WR_CHECKAMMO2     =  4; // (SERVER) checks ammo for weapon second
 -const float WR_AIM            =  5; // (SERVER) runs bot aiming code for this weapon
 -const float WR_INIT           =  6; // (BOTH)   precaches models/sounds used by this weapon, also sets up weapon properties
 -const float WR_SUICIDEMESSAGE =  7; // (SERVER) notification number for suicide message (may inspect w_deathtype for details)
 -const float WR_KILLMESSAGE    =  8; // (SERVER) notification number for kill message (may inspect w_deathtype for details)
 -const float WR_RELOAD         =  9; // (SERVER) handles reloading for weapon
 -const float WR_RESETPLAYER    = 10; // (SERVER) clears fields that the weapon may use
 -const float WR_IMPACTEFFECT   = 11; // (CLIENT) impact effect for weapon explosion
 -const float WR_PLAYERDEATH    = 12; // (SERVER) called whenever a player dies
 -const float WR_GONETHINK      = 13; // (SERVER) logic to run when weapon is lost
 -const float WR_CONFIG         = 14; // (ALL)    dump weapon cvars to config in data directory (see: sv_cmd dumpweapons)
 -const float WR_ZOOMRETICLE    = 15; // (CLIENT) weapon specific zoom reticle
 -const float WR_DROP           = 16; // (SERVER) the weapon is dropped
 -const float WR_PICKUP         = 17; // (SERVER) a weapon is picked up
 +const int WR_SETUP          =  1; // (SERVER) setup weapon data
 +const int WR_THINK          =  2; // (SERVER) logic to run every frame
 +const int WR_CHECKAMMO1     =  3; // (SERVER) checks ammo for weapon primary
 +const int WR_CHECKAMMO2     =  4; // (SERVER) checks ammo for weapon second
 +const int WR_AIM            =  5; // (SERVER) runs bot aiming code for this weapon
 +const int WR_INIT           =  6; // (BOTH)   precaches models/sounds used by this weapon, also sets up weapon properties
 +const int WR_SUICIDEMESSAGE =  7; // (SERVER) notification number for suicide message (may inspect w_deathtype for details)
 +const int WR_KILLMESSAGE    =  8; // (SERVER) notification number for kill message (may inspect w_deathtype for details)
 +const int WR_RELOAD         =  9; // (SERVER) handles reloading for weapon
 +const int WR_RESETPLAYER    = 10; // (SERVER) clears fields that the weapon may use
 +const int WR_IMPACTEFFECT   = 11; // (CLIENT) impact effect for weapon explosion
 +const int WR_PLAYERDEATH    = 12; // (SERVER) called whenever a player dies
 +const int WR_GONETHINK      = 13; // (SERVER) logic to run when weapon is lost
 +const int WR_CONFIG         = 14; // (ALL)    dump weapon cvars to config in data directory (see: sv_cmd dumpweapons)
 +const int WR_ZOOMRETICLE    = 15; // (CLIENT) weapon specific zoom reticle
 +const int WR_DROP           = 16; // (SERVER) the weapon is dropped
 +const int WR_PICKUP         = 17; // (SERVER) a weapon is picked up
  
  // variables:
  string weaponorder_byid;
@@@ -61,14 -58,14 +61,15 @@@ WepSet ReadWepSet()
  // weapon name macros
  #define WEP_FIRST 1
  #define WEP_MAXCOUNT 24 // Increase as needed. Can be up to three times as much.
 -float WEP_COUNT;
 -float WEP_LAST;
 +int WEP_COUNT;
 +int WEP_LAST;
  WepSet WEPSET_ALL;
  WepSet WEPSET_SUPERWEAPONS;
  
  // functions:
  entity get_weaponinfo(float id);
  string W_FixWeaponOrder(string order, float complete);
++string W_UndeprecateName(string s);
  string W_NameWeaponOrder(string order);
  string W_NumberWeaponOrder(string order);
  string W_FixWeaponOrder_BuildImpulseList(string o);
@@@ -79,8 -76,8 +80,8 @@@ void W_RandomWeapons(entity e, float n)
  string GetAmmoPicture(.float ammotype);
  
  #ifdef CSQC
 -.float GetAmmoFieldFromNum(float i);
 -float GetAmmoStat(.float ammotype);
 +.float GetAmmoFieldFromNum(int i);
 +int GetAmmoStat(.float ammotype);
  #endif
  
  // ammo types
@@@ -155,12 -152,12 +156,12 @@@ void register_weapons_done()
  
  // entity properties of weaponinfo:
  // fields which are explicitly/manually set are marked with "M", fields set automatically are marked with "A"
 -.float weapon;              // M: WEP_id    // WEP_...
 +.int weapon;                // M: WEP_id    // WEP_...
  .WepSet weapons;            // A: WEPSET_id // WEPSET_...
  .float(float) weapon_func;  // M: function  // w_...
  ..float ammo_field;         // M: ammotype  // main ammo field
 -.float impulse;             // M: impulse   // weapon impulse
 -.float spawnflags;          // M: flags     // WEPSPAWNFLAG_... combined
 +.int impulse;               // M: impulse   // weapon impulse
 +.int spawnflags;            // M: flags     // WEPSPAWNFLAG_... combined
  .float bot_pickupbasevalue; // M: rating    // bot weapon priority
  .vector wpcolor;            // M: color     // waypointsprite color
  .string wpmodel;            // A: wpn-id    // wpn- sprite name
  
  // note: the fabs call is just there to hide "if result is constant" warning
  #define REGISTER_WEAPON_2(id,bit,function,ammotype,impulse,flags,rating,color,modelname,simplemdl,crosshair,wepimg,refname,wepname) \
 -      float id; \
 +      int id; \
        WepSet bit; \
        float function(float); \
        void RegisterWeapons_##id() \
  #undef REGISTER_WEAPON
  
  ACCUMULATE_FUNCTION(RegisterWeapons, register_weapons_done);
 +#endif
index 30ebe1bdfb350c318f1d08171ea7e3dc032d2ebe,0000000000000000000000000000000000000000..fe632ce8d97b8be2bc2aed7b87dc9581407f688a
mode 100644,000000..100644
--- /dev/null
@@@ -1,508 -1,0 +1,511 @@@
 +#ifndef PROGSDEFS_H
 +#define PROGSDEFS_H
 +
 +/*
 +==============================================================================
 +
 +                      SOURCE FOR GLOBALVARS_T C STRUCTURE
 +                      MUST NOT BE MODIFIED, OR CRC ERRORS WILL APPEAR
 +
 +==============================================================================
 +*/
 +
 +//
 +// system globals
 +//
 +entity                self;
 +entity                other;
 +entity                world;
 +float         time;
 +float         frametime;
 +
 +float         force_retouch;          // force all entities to touch triggers
 +                                                              // next frame.  this is needed because
 +                                                              // non-moving things don't normally scan
 +                                                              // for triggers, and when a trigger is
 +                                                              // created (like a teleport trigger), it
 +                                                              // needs to catch everything.
 +                                                              // decremented each frame, so set to 2
 +                                                              // to guarantee everything is touched
 +string                mapname;
 +
 +float         deathmatch;
 +float         coop;
 +float         teamplay;
 +
 +int                   serverflags;            // propagated from level to level, used to
 +                                                              // keep track of completed episodes
 +
 +float         total_secrets;
 +float         total_monsters;
 +
 +float         found_secrets;          // number of secrets found
 +float         killed_monsters;        // number of monsters killed
 +
 +
 +// spawnparms are used to encode information about clients across server
 +// level changes
 +float         parm1, parm2, parm3, parm4, parm5, parm6, parm7, parm8, parm9, parm10, parm11, parm12, parm13, parm14, parm15, parm16;
 +
 +//
 +// global variables set by built in functions
 +//
 +vector                v_forward, v_up, v_right;       // set by makevectors()
 +
 +// set by traceline / tracebox
 +float         trace_allsolid;
 +float         trace_startsolid;
 +float         trace_fraction;
 +vector                trace_endpos;
 +vector                trace_plane_normal;
 +float         trace_plane_dist;
 +entity                trace_ent;
 +float         trace_inopen;
 +float         trace_inwater;
 +
 +entity                msg_entity;                             // destination of single entity writes
 +
 +//
 +// required prog functions
 +//
 +void()                main;                                           // only for testing
 +
 +void()                StartFrame;
 +
 +void()                PlayerPreThink;
 +void()                PlayerPostThink;
 +
 +void()                ClientKill;
++#ifdef DP_EXT_PRECONNECT
++void()                ClientPreConnect;
++#endif
 +void()                ClientConnect;
 +void()                PutClientInServer;              // call after setting the parm1... parms
 +void()                ClientDisconnect;
 +
 +void()                SetNewParms;                    // called when a client first connects to
 +                                                                      // a server. sets parms so they can be
 +                                                                      // saved off for restarts
 +
 +void()                SetChangeParms;                 // call to set parms for self so they can
 +                                                                      // be saved for a level transition
 +
 +
 +//================================================
 +void          end_sys_globals;                // flag for structure dumping
 +//================================================
 +
 +/*
 +==============================================================================
 +
 +                      SOURCE FOR ENTVARS_T C STRUCTURE
 +                      MUST NOT BE MODIFIED, OR CRC ERRORS WILL APPEAR
 +
 +==============================================================================
 +*/
 +
 +//
 +// system fields (*** = do not set in prog code, maintained by C code)
 +//
 +.int          modelindex;             // *** model index in the precached list
 +.vector               absmin, absmax; // *** origin + mins / maxs
 +
 +.float                ltime;                  // local time for entity
 +.float                movetype;
 +.float                solid;
 +
 +.vector               origin;                 // ***
 +.vector               oldorigin;              // ***
 +.vector               velocity;
 +.vector               angles;
 +.vector               avelocity;
 +
 +.vector               punchangle;             // temp angle adjust from damage or recoil
 +
 +.string               classname;              // spawn function
 +.string               model;
 +.int          frame;
 +.int          skin;
 +.int          effects;
 +
 +.vector               mins, maxs;             // bounding box extents reletive to origin
 +.vector               size;                   // maxs - mins
 +
 +.void()               touch;
 +.void()               use;
 +.void()               think;
 +.void()               blocked;                // for doors or plats, called when can't push other
 +
 +.float                nextthink;
 +.entity               groundentity;
 +
 +// stats
 +.float                health;
 +.float                frags;
 +.int          weapon;                 // one of the IT_SHOTGUN, etc flags
 +.string               weaponmodel;
 +.float                weaponframe;
 +.float                currentammo;
 +.float                ammo_shells, ammo_nails, ammo_rockets, ammo_cells;
 +
 +.int          items;                  // bit flags
 +
 +.float                takedamage;
 +.entity               chain;
 +.float                deadflag;
 +
 +.vector               view_ofs;                       // add to origin to get eye point
 +
 +
 +.float                button0;                // fire
 +.float                button1;                // use
 +.float                button2;                // jump
 +
 +.float                impulse;                // weapon changes
 +
 +.float                fixangle;
 +.vector               v_angle;                // view / targeting angle for players
 +.float                idealpitch;             // calculated pitch angle for lookup up slopes
 +
 +
 +.string               netname;
 +
 +.entity       enemy;
 +
 +.int          flags;
 +
 +.int          colormap;
 +.float                team;
 +
 +.float                max_health;             // players maximum health is stored here
 +
 +.float                teleport_time;  // don't back up
 +
 +.float                armortype;              // save this fraction of incoming damage
 +.float                armorvalue;
 +
 +.float                waterlevel;             // 0 = not in, 1 = feet, 2 = wast, 3 = eyes
 +.float                watertype;              // a contents value
 +
 +.float                ideal_yaw;
 +.float                yaw_speed;
 +
 +.entity               aiment;
 +
 +.entity       goalentity;             // a movetarget or an enemy
 +
 +.int          spawnflags;
 +
 +.string               target;
 +.string               targetname;
 +
 +// damage is accumulated through a frame. and sent as one single
 +// message, so the super shotgun doesn't generate huge messages
 +.float                dmg_take;
 +.float                dmg_save;
 +.entity               dmg_inflictor;
 +
 +.entity               owner;          // who launched a missile
 +.vector               movedir;        // mostly for doors, but also used for waterjump
 +
 +.string               message;                // trigger messages
 +
 +.float                sounds;         // either a cd track number or sound number
 +
 +.string               noise, noise1, noise2, noise3;  // contains names of wavs to play
 +
 +//================================================
 +void          end_sys_fields;                 // flag for structure dumping
 +//================================================
 +
 +/*
 +==============================================================================
 +
 +                              CONSTANT DEFINITIONS
 +
 +==============================================================================
 +*/
 +
 +
 +//
 +// constants
 +//
 +
 +// edict.flags
 +const int FL_FLY                              = 1;
 +const int FL_SWIM                             = 2;
 +const int FL_CLIENT                           = 8;    // set for all client edicts
 +const int FL_INWATER                  = 16;   // for enter / leave water splash
 +const int FL_MONSTER                  = 32;
 +const int FL_GODMODE                  = 64;   // player cheat
 +const int FL_NOTARGET                 = 128;  // player cheat
 +const int FL_ITEM                             = 256;  // extra wide size for bonus items
 +const int FL_ONGROUND                 = 512;  // standing on something
 +const int FL_PARTIALGROUND            = 1024; // not all corners are valid
 +const int FL_WATERJUMP                        = 2048; // player jumping out of water
 +const int FL_JUMPRELEASED             = 4096; // for jump debouncing
 +
 +// edict.movetype values
 +const int MOVETYPE_NONE                       = 0;    // never moves
 +//const int   MOVETYPE_ANGLENOCLIP= 1;
 +//const int   MOVETYPE_ANGLECLIP      = 2;
 +const int MOVETYPE_WALK                       = 3;    // players only
 +const int MOVETYPE_STEP                       = 4;    // discrete, not real time unless fall
 +const int MOVETYPE_FLY                        = 5;
 +const int MOVETYPE_TOSS                       = 6;    // gravity
 +const int MOVETYPE_PUSH                       = 7;    // no clip to world, push and crush
 +const int MOVETYPE_NOCLIP             = 8;
 +const int MOVETYPE_FLYMISSILE = 9;    // fly with extra size against monsters
 +const int MOVETYPE_BOUNCE             = 10;
 +const int MOVETYPE_BOUNCEMISSILE= 11; // bounce with extra size
 +
 +// edict.solid values
 +const int SOLID_NOT                           = 0;    // no interaction with other objects
 +const int SOLID_TRIGGER                       = 1;    // touch on edge, but not blocking
 +const int SOLID_BBOX                  = 2;    // touch on edge, block
 +const int SOLID_SLIDEBOX              = 3;    // touch on edge, but not an onground
 +const int SOLID_BSP                           = 4;    // bsp clip, touch on edge, block
 +
 +// range values
 +const int RANGE_MELEE                 = 0;
 +const int RANGE_NEAR                  = 1;
 +const int RANGE_MID                           = 2;
 +const int RANGE_FAR                           = 3;
 +
 +// deadflag values
 +
 +const int DEAD_NO                             = 0;
 +const int DEAD_DYING                  = 1;
 +const int DEAD_DEAD                           = 2;
 +const int DEAD_RESPAWNABLE            = 3;
 +const int DEAD_RESPAWNING             = 4; // dead, waiting for buttons to be released
 +
 +// takedamage values
 +
 +const int DAMAGE_NO                           = 0;
 +const int DAMAGE_YES                  = 1;
 +const int DAMAGE_AIM                  = 2;
 +
 +// items
 +const int IT_AXE                              = 4096;
 +const int IT_SHOTGUN                  = 1;
 +const int IT_SUPER_SHOTGUN            = 2;
 +const int IT_NAILGUN                  = 4;
 +const int IT_SUPER_NAILGUN            = 8;
 +const int IT_GRENADE_LAUNCHER = 16;
 +const int IT_ROCKET_LAUNCHER  = 32;
 +const int IT_LIGHTNING                        = 64;
 +const int IT_EXTRA_WEAPON             = 128;
 +
 +//const int IT_SHELLS                 = 256;
 +//const int IT_NAILS                  = 512;
 +//const int IT_ROCKETS                        = 1024;
 +//const int IT_CELLS                  = 2048;
 +
 +const int IT_ARMOR1                           = 8192;
 +const int IT_ARMOR2                           = 16384;
 +const int IT_ARMOR3                           = 32768;
 +const int IT_SUPERHEALTH              = 65536;
 +
 +//const int IT_KEY1                           = 131072;
 +//const int IT_KEY2                           = 262144;
 +
 +const int IT_INVISIBILITY             = 524288;
 +const int IT_INVULNERABILITY  = 1048576;
 +const int IT_SUIT                             = 2097152;
 +const int IT_QUAD                             = 4194304;
 +
 +// point content values
 +
 +const int CONTENT_EMPTY                       = -1;
 +const int CONTENT_SOLID                       = -2;
 +const int CONTENT_WATER                       = -3;
 +const int CONTENT_SLIME                       = -4;
 +const int CONTENT_LAVA                        = -5;
 +const int CONTENT_SKY                 = -6;
 +
 +const int STATE_TOP                           = 0;
 +const int STATE_BOTTOM                        = 1;
 +const int STATE_UP                            = 2;
 +const int STATE_DOWN                  = 3;
 +
 +const vector VEC_ORIGIN               = '0 0 0';
 +const vector VEC_HULL_MIN             = '-16 -16 -24';
 +const vector VEC_HULL_MAX             = '16 16 32';
 +
 +const vector VEC_HULL2_MIN            = '-32 -32 -24';
 +const vector VEC_HULL2_MAX            = '32 32 64';
 +
 +// protocol bytes
 +const int SVC_TEMPENTITY              = 23;
 +const int SVC_KILLEDMONSTER           = 27;
 +const int SVC_FOUNDSECRET             = 28;
 +const int SVC_INTERMISSION            = 30;
 +const int SVC_FINALE                  = 31;
 +const int SVC_CDTRACK                 = 32;
 +const int SVC_SELLSCREEN              = 33;
 +
 +
 +const int TE_SPIKE                            = 0;
 +const int TE_SUPERSPIKE                       = 1;
 +const int TE_GUNSHOT                  = 2;
 +const int TE_EXPLOSION                        = 3;
 +const int TE_TAREXPLOSION             = 4;
 +const int TE_LIGHTNING1                       = 5;
 +const int TE_LIGHTNING2                       = 6;
 +const int TE_WIZSPIKE                 = 7;
 +const int TE_KNIGHTSPIKE              = 8;
 +const int TE_LIGHTNING3                       = 9;
 +const int TE_LAVASPLASH                       = 10;
 +const int TE_TELEPORT                 = 11;
 +
 +// sound channels
 +// channel 0 never willingly overrides
 +// other channels (1-7) allways override a playing sound on that channel
 +const int CHAN_AUTO                           = 0;
 +const int CHAN_WEAPON                 = 1;
 +const int CHAN_VOICE                  = 2;
 +const int CHAN_ITEM                           = 3;
 +const int CHAN_BODY                           = 4;
 +
 +const int ATTN_NONE                           = 0;
 +const int ATTN_NORM                           = 1;
 +const int ATTN_IDLE                           = 2;
 +const int ATTN_STATIC                 = 3;
 +
 +// update types
 +
 +const int UPDATE_GENERAL              = 0;
 +const int UPDATE_STATIC                       = 1;
 +const int UPDATE_BINARY                       = 2;
 +const int UPDATE_TEMP                 = 3;
 +
 +// entity effects
 +
 +const int EF_BRIGHTFIELD              = 1;
 +const int EF_MUZZLEFLASH              = 2;
 +const int EF_BRIGHTLIGHT              = 4;
 +const int EF_DIMLIGHT                         = 8;
 +
 +
 +// messages
 +const int MSG_BROADCAST                       = 0;            // unreliable to all
 +const int MSG_ONE                             = 1;            // reliable to one (msg_entity)
 +const int MSG_ALL                             = 2;            // reliable to all
 +const int MSG_INIT                            = 3;            // write to the init string
 +
 +//===========================================================================
 +
 +//
 +// builtin functions
 +//
 +
 +void(vector ang)      makevectors             = #1;           // sets v_forward, etc globals
 +void(entity e, vector o) setorigin    = #2;
 +void(entity e, string m) setmodel     = #3;           // set movetype and solid first
 +void(entity e, vector min, vector max) setsize = #4;
 +// #5 was removed
 +void() break_to_debugger                                              = #6;
 +float() random                                                = #7;           // returns 0 - 1
 +void(entity e, float chan, string samp, float vol, float atten) sound = #8;
 +vector(vector v) normalize                    = #9;
 +void(string e, ...) error                             = #10;
 +void(string e, ...) objerror                          = #11;
 +float(vector v) vlen                          = #12;
 +float(vector v) vectoyaw                      = #13;
 +entity() spawn                                                = #14;
 +void(entity e) remove                         = #15;
 +
 +// sets trace_* globals
 +// nomonsters can be:
 +// An entity will also be ignored for testing if forent == test,
 +// forent->owner == test, or test->owner == forent
 +// a forent of world is ignored
 +void(vector v1, vector v2, float nomonsters, entity forent) traceline = #16;
 +
 +entity() checkclient                          = #17;  // returns a client to look for
 +entity(entity start, .string fld, string match) find = #18;
 +string(string s) precache_sound               = #19;
 +string(string s) precache_model               = #20;
 +void(entity client, string s, ...)stuffcmd = #21;
 +entity(vector org, float rad) findradius = #22;
 +void(string s, ...) bprint                            = #23;
 +void(entity client, string s, ...) sprint = #24;
 +void(string s, ...) dprint                            = #25;
 +string(float f) ftos                          = #26;
 +string(vector v) vtos                         = #27;
 +void() coredump                                               = #28;          // prints all edicts
 +void() traceon                                                = #29;          // turns statment trace on
 +void() traceoff                                               = #30;
 +void(entity e) eprint                         = #31;          // prints an entire edict
 +float(float yaw, float dist) walkmove = #32;  // returns true or false
 +// #33 was removed
 +float() droptofloor= #34;     // true if landed on floor
 +void(float style, string value) lightstyle = #35;
 +float(float v) rint                                   = #36;          // round to nearest int
 +float(float v) floor                          = #37;          // largest integer <= v
 +float(float v) ceil                                   = #38;          // smallest integer >= v
 +// #39 was removed
 +float(entity e) checkbottom                   = #40;          // true if self is on ground
 +float(vector v) pointcontents         = #41;          // returns a CONTENT_*
 +// #42 was removed
 +float(float f) fabs = #43;
 +vector(entity e, float speed) aim = #44;              // returns the shooting vector
 +float(string s) cvar = #45;                                           // return cvar.value
 +void(string s, ...) localcmd = #46;                                   // put string into local que
 +entity(entity e) nextent = #47;                                       // for looping through all ents
 +void(vector o, vector d, float color, float count) particle = #48;// start a particle effect
 +void() ChangeYaw = #49;                                               // turn towards self.ideal_yaw
 +                                                                                      // at self.yaw_speed
 +// #50 was removed
 +vector(vector v) vectoangles                  = #51;
 +
 +//
 +// direct client message generation
 +//
 +void(float to, float f) WriteByte             = #52;
 +void(float to, float f) WriteChar             = #53;
 +void(float to, float f) WriteShort            = #54;
 +void(float to, float f) WriteLong             = #55;
 +void(float to, float f) WriteCoord            = #56;
 +void(float to, float f) WriteAngle            = #57;
 +void(float to, string s, ...) WriteString     = #58;
 +void(float to, entity s) WriteEntity  = #59;
 +
 +//
 +// broadcast client message generation
 +//
 +
 +// void(float f) bWriteByte           = #59;
 +// void(float f) bWriteChar           = #60;
 +// void(float f) bWriteShort          = #61;
 +// void(float f) bWriteLong           = #62;
 +// void(float f) bWriteCoord          = #63;
 +// void(float f) bWriteAngle          = #64;
 +// void(string s) bWriteString        = #65;
 +// void(entity e) bWriteEntity = #66;
 +
 +void(float step) movetogoal                           = #67;
 +
 +string(string s) precache_file                = #68;  // no effect except for -copy
 +void(entity e) makestatic             = #69;
 +void(string s) changelevel = #70;
 +
 +//#71 was removed
 +
 +void(string name, string value) cvar_set = #72;       // sets cvar.value
 +
 +void(entity client, string s, ...) centerprint = #73; // sprint, but in middle
 +
 +void(vector pos, string samp, float vol, float atten) ambientsound = #74;
 +
 +string(string s) precache_model2      = #75;          // registered version only
 +string(string s) precache_sound2      = #76;          // registered version only
 +string(string s) precache_file2               = #77;          // registered version only
 +
 +void(entity e) setspawnparms          = #78;          // set parm1... to the
 +                                                                                              // values at level start
 +                                                                                              // for coop respawn
 +
 +//============================================================================
 +#endif
index bc044a2aa585b54d4ed915f7b403e5943ff6d983,89086406699a995153b8df9a4f7e293b36945859..93825cd56098d73e2ebe53aa0aee97041fcaf03c
@@@ -1,7 -1,3 +1,7 @@@
 +#include "menu_cmd.qh"
 +
 +#include "../../common/command/generic.qh"
 +
  string _dumptree_space;
  void _dumptree_open(entity pass, entity me)
  {
@@@ -89,7 -85,10 +89,10 @@@ void GameCommand(string theCommand
                                }
                }
                else if(argc == 2 && !isdemo()) // don't allow this command in demos
+               {
+                       m_play_click_sound(MENU_SOUND_OPEN);
                        m_goto(strcat(filter, argv(1))); // switch to a menu item
+               }
                if(filter)
                        strunzone(filter);
                return;
diff --combined qcsrc/menu/item.qc
index d055b1a051d535dbfd18736751eb2357c25003a0,0000000000000000000000000000000000000000..d0bd40b03884c30d4e83c6727e82d89ac23019a3
mode 100644,000000..100644
--- /dev/null
@@@ -1,134 -1,0 +1,137 @@@
 +#ifdef INTERFACE
 +CLASS(Item) EXTENDS(Object)
 +      METHOD(Item, draw, void(entity))
 +      METHOD(Item, keyDown, float(entity, float, float, float))
 +      METHOD(Item, keyUp, float(entity, float, float, float))
 +      METHOD(Item, mouseMove, float(entity, vector))
 +      METHOD(Item, mousePress, float(entity, vector))
 +      METHOD(Item, mouseDrag, float(entity, vector))
 +      METHOD(Item, mouseRelease, float(entity, vector))
 +      METHOD(Item, focusEnter, void(entity))
 +      METHOD(Item, focusLeave, void(entity))
 +      METHOD(Item, resizeNotify, void(entity, vector, vector, vector, vector))
 +      METHOD(Item, relinquishFocus, void(entity))
 +      METHOD(Item, showNotify, void(entity))
 +      METHOD(Item, hideNotify, void(entity))
 +      METHOD(Item, toString, string(entity))
 +      METHOD(Item, destroy, void(entity))
 +      ATTRIB(Item, focused, float, 0)
 +      ATTRIB(Item, focusable, float, 0)
++      ATTRIB(Item, allowFocusSound, float, 0)
 +      ATTRIB(Item, parent, entity, NULL)
 +      ATTRIB(Item, preferredFocusPriority, float, 0)
 +      ATTRIB(Item, origin, vector, '0 0 0')
 +      ATTRIB(Item, size, vector, '0 0 0')
 +      ATTRIB(Item, tooltip, string, string_null)
 +ENDCLASS(Item)
 +#endif
 +
 +#ifdef IMPLEMENTATION
 +void Item_destroy(entity me)
 +{
 +      // free memory associated with me
 +}
 +
 +void Item_relinquishFocus(entity me)
 +{
 +      if(me.parent)
 +              if(me.parent.instanceOfContainer)
 +                      me.parent.setFocus(me.parent, NULL);
 +}
 +
 +void Item_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
 +{
 +      me.origin = absOrigin;
 +      me.size = absSize;
 +}
 +
 +float autocvar_menu_showboxes;
 +void Item_draw(entity me)
 +{
 +      if(autocvar_menu_showboxes)
 +      {
 +              vector rgb = '1 0 1';
 +              float a = fabs(autocvar_menu_showboxes);
 +
 +              // don't draw containers and border images
 +              if(me.instanceOfContainer || me.instanceOfBorderImage)
 +              {
 +                      rgb = '0 0 0';
 +                      a = 0;
 +              }
 +
 +#if 0
 +              // hack to detect multi drawing
 +              float r = random() * 3;
 +              if(r >= 2)
 +                      rgb = '1 0 0';
 +              else if(r >= 1)
 +                      rgb = '0 1 0';
 +              else
 +                      rgb = '0 0 1';
 +#endif
 +              if(autocvar_menu_showboxes < 0)
 +              {
 +                      draw_Fill('0 0 0', '0.5 0.5 0', rgb, a);
 +                      draw_Fill('0.5 0.5 0', '0.5 0.5 0', rgb, a);
 +              }
 +              if(autocvar_menu_showboxes > 0)
 +              {
 +                      draw_Fill('0 0 0', '1 1 0', rgb, a);
 +              }
 +      }
 +}
 +
 +void Item_showNotify(entity me)
 +{
 +}
 +
 +void Item_hideNotify(entity me)
 +{
 +}
 +
 +float Item_keyDown(entity me, float scan, float ascii, float shift)
 +{
 +      return 0; // unhandled
 +}
 +
 +float Item_keyUp(entity me, float scan, float ascii, float shift)
 +{
 +      return 0; // unhandled
 +}
 +
 +float Item_mouseMove(entity me, vector pos)
 +{
 +      return 0; // unhandled
 +}
 +
 +float Item_mousePress(entity me, vector pos)
 +{
 +      return 0; // unhandled
 +}
 +
 +float Item_mouseDrag(entity me, vector pos)
 +{
 +      return 0; // unhandled
 +}
 +
 +float Item_mouseRelease(entity me, vector pos)
 +{
 +      return 0; // unhandled
 +}
 +
 +void Item_focusEnter(entity me)
 +{
++      if(me.allowFocusSound)
++              m_play_focus_sound();
 +}
 +
 +void Item_focusLeave(entity me)
 +{
 +}
 +
 +string Item_toString(entity me)
 +{
 +      return string_null;
 +}
 +#endif
index f6ba208f5373c073519c3ba1cd0b9f7ef8ae26ae,0000000000000000000000000000000000000000..52e58238c86916248549bfb6c87a917913705098
mode 100644,000000..100644
--- /dev/null
@@@ -1,173 -1,0 +1,178 @@@
-       METHOD(Button, focusEnter, void(entity))
 +#ifdef INTERFACE
 +CLASS(Button) EXTENDS(Label)
 +      METHOD(Button, configureButton, void(entity, string, float, string))
 +      METHOD(Button, draw, void(entity))
 +      METHOD(Button, showNotify, void(entity))
 +      METHOD(Button, resizeNotify, void(entity, vector, vector, vector, vector))
 +      METHOD(Button, keyDown, float(entity, float, float, float))
 +      METHOD(Button, mousePress, float(entity, vector))
 +      METHOD(Button, mouseDrag, float(entity, vector))
 +      METHOD(Button, mouseRelease, float(entity, vector))
-                       if(cvar("menu_sounds"))
-                               localsound("sound/misc/menu2.wav");
++      METHOD(Button, playClickSound, void(entity))
 +      ATTRIB(Button, onClick, void(entity, entity), func_null)
 +      ATTRIB(Button, onClickEntity, entity, NULL)
 +      ATTRIB(Button, src, string, string_null)
 +      ATTRIB(Button, srcSuffix, string, string_null)
 +      ATTRIB(Button, src2, string, string_null) // is centered, same aspect, and stretched to label size
 +      ATTRIB(Button, src2scale, float, 1)
 +      ATTRIB(Button, srcMulti, float, 1) // 0: button square left, text right; 1: button stretched, text over it
 +      ATTRIB(Button, buttonLeftOfText, float, 0)
 +      ATTRIB(Button, focusable, float, 1)
++      ATTRIB(Button, allowFocusSound, float, 1)
 +      ATTRIB(Button, pressed, float, 0)
 +      ATTRIB(Button, clickTime, float, 0)
 +      ATTRIB(Button, disabled, float, 0)
 +      ATTRIB(Button, disabledAlpha, float, 0.3)
 +      ATTRIB(Button, forcePressed, float, 0)
 +      ATTRIB(Button, color, vector, '1 1 1')
 +      ATTRIB(Button, colorC, vector, '1 1 1')
 +      ATTRIB(Button, colorF, vector, '1 1 1')
 +      ATTRIB(Button, colorD, vector, '1 1 1')
 +      ATTRIB(Button, color2, vector, '1 1 1')
 +      ATTRIB(Button, alpha2, float, 1)
 +
 +      ATTRIB(Button, origin, vector, '0 0 0')
 +      ATTRIB(Button, size, vector, '0 0 0')
 +ENDCLASS(Button)
 +#endif
 +
 +#ifdef IMPLEMENTATION
 +void Button_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
 +{
 +      if(me.srcMulti)
 +              me.keepspaceLeft = 0;
 +      else
 +              me.keepspaceLeft = min(0.8, absSize.y / absSize.x);
 +      SUPER(Button).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
 +}
 +void Button_configureButton(entity me, string txt, float sz, string gfx)
 +{
 +      SUPER(Button).configureLabel(me, txt, sz, me.srcMulti ? 0.5 : 0);
 +      me.src = gfx;
 +}
 +float Button_keyDown(entity me, float key, float ascii, float shift)
 +{
 +      if(key == K_ENTER || key == K_SPACE || key == K_KP_ENTER)
 +      {
++              me.playClickSound(me);
 +              me.clickTime = 0.1; // delayed for effect
 +              return 1;
 +      }
 +      return 0;
 +}
 +float Button_mouseDrag(entity me, vector pos)
 +{
 +      me.pressed = 1;
 +      if(pos.x < 0) me.pressed = 0;
 +      if(pos.y < 0) me.pressed = 0;
 +      if(pos.x >= 1) me.pressed = 0;
 +      if(pos.y >= 1) me.pressed = 0;
 +      return 1;
 +}
 +float Button_mousePress(entity me, vector pos)
 +{
 +      me.mouseDrag(me, pos); // verify coordinates
 +      return 1;
 +}
 +float Button_mouseRelease(entity me, vector pos)
 +{
 +      me.mouseDrag(me, pos); // verify coordinates
 +      if(me.pressed)
 +      {
 +              if (!me.disabled)
 +              {
- void Button_focusEnter(entity me)
- {
-       if(cvar("menu_sounds") > 1)
-               localsound("sound/misc/menu1.wav");
-       SUPER(Button).focusEnter(me);
- }
++                      me.playClickSound(me);
 +                      if(me.onClick)
 +                              me.onClick(me, me.onClickEntity);
 +              }
 +              me.pressed = 0;
 +      }
 +      return 1;
 +}
 +void Button_showNotify(entity me)
 +{
 +      me.focusable = !me.disabled;
 +}
 +void Button_draw(entity me)
 +{
 +      vector bOrigin, bSize;
 +      float save;
 +
 +      me.focusable = !me.disabled;
 +
 +      save = draw_alpha;
 +      if(me.disabled)
 +              draw_alpha *= me.disabledAlpha;
 +
 +      if(me.src)
 +      {
 +              if(me.srcMulti)
 +              {
 +                      bOrigin = '0 0 0';
 +                      bSize = '1 1 0';
 +                      if(me.disabled)
 +                              draw_ButtonPicture(bOrigin, strcat(me.src, "_d", me.srcSuffix), bSize, me.colorD, 1);
 +                      else if(me.forcePressed || me.pressed || me.clickTime > 0)
 +                              draw_ButtonPicture(bOrigin, strcat(me.src, "_c", me.srcSuffix), bSize, me.colorC, 1);
 +                      else if(me.focused)
 +                              draw_ButtonPicture(bOrigin, strcat(me.src, "_f", me.srcSuffix), bSize, me.colorF, 1);
 +                      else
 +                              draw_ButtonPicture(bOrigin, strcat(me.src, "_n", me.srcSuffix), bSize, me.color, 1);
 +              }
 +              else
 +              {
 +                      if(me.realFontSize_y == 0)
 +                      {
 +                              bOrigin = '0 0 0';
 +                              bSize = '1 1 0';
 +                      }
 +                      else
 +                      {
 +                              bOrigin = eY * (0.5 * (1 - me.realFontSize.y)) + eX * (0.5 * (me.keepspaceLeft - me.realFontSize.x));
 +                              bSize = me.realFontSize;
 +                      }
 +                      if(me.disabled)
 +                              draw_Picture(bOrigin, strcat(me.src, "_d", me.srcSuffix), bSize, me.colorD, 1);
 +                      else if(me.forcePressed || me.pressed || me.clickTime > 0)
 +                              draw_Picture(bOrigin, strcat(me.src, "_c", me.srcSuffix), bSize, me.colorC, 1);
 +                      else if(me.focused)
 +                              draw_Picture(bOrigin, strcat(me.src, "_f", me.srcSuffix), bSize, me.colorF, 1);
 +                      else
 +                              draw_Picture(bOrigin, strcat(me.src, "_n", me.srcSuffix), bSize, me.color, 1);
 +              }
 +      }
 +      if(me.src2)
 +      {
 +              bOrigin = me.keepspaceLeft * eX;
 +              bSize = eY + eX * (1 - me.keepspaceLeft);
 +
 +              bOrigin += bSize * (0.5 - 0.5 * me.src2scale);
 +              bSize = bSize * me.src2scale;
 +
 +              draw_Picture(bOrigin, me.src2, bSize, me.color2, me.alpha2);
 +      }
 +
 +      draw_alpha = save;
 +
 +      if(me.clickTime > 0 && me.clickTime <= frametime)
 +      {
 +              // keyboard click timer expired? Fire the event then.
 +              if (!me.disabled)
 +                      if(me.onClick)
 +                              me.onClick(me, me.onClickEntity);
 +      }
 +      me.clickTime -= frametime;
 +
 +      SUPER(Button).draw(me);
 +}
++void Dialog_Close(entity button, entity me);
++void Button_playClickSound(entity me)
++{
++      if(me.onClick == DialogOpenButton_Click)
++              m_play_click_sound(MENU_SOUND_OPEN);
++      else if(me.onClick == Dialog_Close)
++              m_play_click_sound(MENU_SOUND_CLOSE);
++      else
++              m_play_click_sound(MENU_SOUND_EXECUTE);
++}
 +#endif
index 94f67ba709ef7422b1d0b3a76e488b1b9f8f5acf,0000000000000000000000000000000000000000..2540cc846fdbddf80ef44a5d8f4bd022e7f1dee8
mode 100644,000000..100644
--- /dev/null
@@@ -1,48 -1,0 +1,53 @@@
 +#ifdef INTERFACE
 +void CheckBox_Click(entity me, entity other);
 +CLASS(CheckBox) EXTENDS(Button)
 +      METHOD(CheckBox, configureCheckBox, void(entity, string, float, string))
 +      METHOD(CheckBox, draw, void(entity))
++      METHOD(CheckBox, playClickSound, void(entity))
 +      METHOD(CheckBox, toString, string(entity))
 +      METHOD(CheckBox, setChecked, void(entity, float))
 +      ATTRIB(CheckBox, useDownAsChecked, float, 0)
 +      ATTRIB(CheckBox, checked, float, 0)
 +      ATTRIB(CheckBox, onClick, void(entity, entity), CheckBox_Click)
 +      ATTRIB(CheckBox, srcMulti, float, 0)
 +      ATTRIB(CheckBox, disabled, float, 0)
 +ENDCLASS(CheckBox)
 +#endif
 +
 +#ifdef IMPLEMENTATION
 +void CheckBox_setChecked(entity me, float val)
 +{
 +      me.checked = val;
 +}
 +void CheckBox_Click(entity me, entity other)
 +{
 +      me.setChecked(me, !me.checked);
 +}
 +string CheckBox_toString(entity me)
 +{
 +      return strcat(SUPER(CheckBox).toString(me), ", ", me.checked ? "checked" : "unchecked");
 +}
 +void CheckBox_configureCheckBox(entity me, string txt, float sz, string gfx)
 +{
 +      me.configureButton(me, txt, sz, gfx);
 +      me.align = 0;
 +}
 +void CheckBox_draw(entity me)
 +{
 +      float s;
 +      s = me.pressed;
 +      if(me.useDownAsChecked)
 +      {
 +              me.srcSuffix = string_null;
 +              me.forcePressed = me.checked;
 +      }
 +      else
 +              me.srcSuffix = (me.checked ? "1" : "0");
 +      me.pressed = s;
 +      SUPER(CheckBox).draw(me);
 +}
++void CheckBox_playClickSound(entity me)
++{
++      m_play_click_sound(MENU_SOUND_SELECT);
++}
 +#endif
index 62c74406ed92860cc95a07ad06aae11489d91cae,0000000000000000000000000000000000000000..1723f27cb8e5335691741ca3df38a883f3798e74
mode 100644,000000..100644
--- /dev/null
@@@ -1,191 -1,0 +1,192 @@@
 +// Note: this class is called Dialog, but it can also handle a tab under the following conditions:
 +// - isTabRoot is 0
 +// - backgroundImage is the tab's background
 +// - closable is 0
 +// - rootDialog is 0
 +// - title is ""
 +// - marginTop is
 +// - intendedHeight ends up to be the tab's actual height, or at least close
 +// - titleFontSize is 0
 +// - marginTop cancels out as much of titleHeight as needed (that is, it should be actualMarginTop - titleHeight)
 +// To ensure the latter, you best create all tabs FIRST and insert the tabbed
 +// control to your dialog THEN - with the right height
 +//
 +// a subclass may help with using this as a tab
 +
 +#ifdef INTERFACE
 +CLASS(Dialog) EXTENDS(InputContainer)
 +      METHOD(Dialog, configureDialog, void(entity)) // no runtime configuration, all parameters are given in the code!
 +      METHOD(Dialog, fill, void(entity)) // to be overridden by user to fill the dialog with controls
 +      METHOD(Dialog, keyDown, float(entity, float, float, float))
 +      METHOD(Dialog, close, void(entity))
 +      METHOD(Dialog, addItemSimple, void(entity, float, float, float, float, entity, vector))
 +
 +      METHOD(Dialog, TD, void(entity, float, float, entity))
 +      METHOD(Dialog, TDNoMargin, void(entity, float, float, entity, vector))
 +      METHOD(Dialog, TDempty, void(entity, float))
 +      METHOD(Dialog, setFirstColumn, void(entity, float))
 +      METHOD(Dialog, TR, void(entity))
 +      METHOD(Dialog, gotoRC, void(entity, float, float))
 +
 +      ATTRIB(Dialog, isTabRoot, float, 1)
 +      ATTRIB(Dialog, closeButton, entity, NULL)
 +      ATTRIB(Dialog, intendedHeight, float, 0)
 +      ATTRIB(Dialog, itemOrigin, vector, '0 0 0')
 +      ATTRIB(Dialog, itemSize, vector, '0 0 0')
 +      ATTRIB(Dialog, itemSpacing, vector, '0 0 0')
 +      ATTRIB(Dialog, currentRow, float, 0)
 +      ATTRIB(Dialog, currentColumn, float, 0)
 +      ATTRIB(Dialog, firstColumn, float, 0)
 +
 +      // to be customized
 +      ATTRIB(Dialog, closable, float, 1)
 +      ATTRIB(Dialog, title, string, "Form1") // ;)
 +      ATTRIB(Dialog, color, vector, '1 0.5 1')
 +      ATTRIB(Dialog, intendedWidth, float, 0)
 +      ATTRIB(Dialog, rows, float, 3)
 +      ATTRIB(Dialog, columns, float, 2)
 +
 +      ATTRIB(Dialog, marginTop, float, 0) // pixels
 +      ATTRIB(Dialog, marginBottom, float, 0) // pixels
 +      ATTRIB(Dialog, marginLeft, float, 0) // pixels
 +      ATTRIB(Dialog, marginRight, float, 0) // pixels
 +      ATTRIB(Dialog, columnSpacing, float, 0) // pixels
 +      ATTRIB(Dialog, rowSpacing, float, 0) // pixels
 +      ATTRIB(Dialog, rowHeight, float, 0) // pixels
 +      ATTRIB(Dialog, titleHeight, float, 0) // pixels
 +      ATTRIB(Dialog, titleFontSize, float, 0) // pixels; if 0, title causes no margin
 +      ATTRIB(Dialog, zoomedOutTitleBarPosition, float, 0)
 +      ATTRIB(Dialog, zoomedOutTitleBar, float, 0)
 +
 +      ATTRIB(Dialog, requiresConnection, float, 0) // set to true if the dialog requires a connection to be opened
 +
 +      ATTRIB(Dialog, backgroundImage, string, string_null)
 +      ATTRIB(Dialog, borderLines, float, 1)
 +      ATTRIB(Dialog, closeButtonImage, string, string_null)
 +
 +      ATTRIB(Dialog, frame, entity, NULL)
 +ENDCLASS(Dialog)
 +#endif
 +
 +#ifdef IMPLEMENTATION
 +void Dialog_Close(entity button, entity me)
 +{
 +      me.close(me);
 +}
 +
 +void Dialog_fill(entity me)
 +{
 +}
 +
 +void Dialog_addItemSimple(entity me, float row, float col, float rowspan, float colspan, entity e, vector v)
 +{
 +      vector o, s;
 +      o = me.itemOrigin + eX * ( col          * me.itemSpacing.x) + eY * ( row          * me.itemSpacing.y);
 +      s = me.itemSize   + eX * ((colspan - 1) * me.itemSpacing.x) + eY * ((rowspan - 1) * me.itemSpacing.y);
 +      o.x -= 0.5 * (me.itemSpacing.x - me.itemSize.x) * v.x;
 +      s.x +=       (me.itemSpacing.x - me.itemSize.x) * v.x;
 +      o.y -= 0.5 * (me.itemSpacing.y - me.itemSize.y) * v.y;
 +      s.y +=       (me.itemSpacing.y - me.itemSize.y) * v.y;
 +      me.addItem(me, e, o, s, 1);
 +}
 +
 +void Dialog_gotoRC(entity me, float row, float col)
 +{
 +      me.currentRow = row;
 +      me.currentColumn = col;
 +}
 +
 +void Dialog_TR(entity me)
 +{
 +      me.currentRow += 1;
 +      me.currentColumn = me.firstColumn;
 +}
 +
 +void Dialog_TD(entity me, float rowspan, float colspan, entity e)
 +{
 +      me.addItemSimple(me, me.currentRow, me.currentColumn, rowspan, colspan, e, '0 0 0');
 +      me.currentColumn += colspan;
 +}
 +
 +void Dialog_TDNoMargin(entity me, float rowspan, float colspan, entity e, vector v)
 +{
 +      me.addItemSimple(me, me.currentRow, me.currentColumn, rowspan, colspan, e, v);
 +      me.currentColumn += colspan;
 +}
 +
 +void Dialog_setFirstColumn(entity me, float col)
 +{
 +      me.firstColumn = col;
 +}
 +
 +void Dialog_TDempty(entity me, float colspan)
 +{
 +      me.currentColumn += colspan;
 +}
 +
 +void Dialog_configureDialog(entity me)
 +{
 +      float absWidth, absHeight;
 +
 +      me.frame = spawnBorderImage();
 +      me.frame.configureBorderImage(me.frame, me.title, me.titleFontSize, me.color, me.backgroundImage, me.borderLines * me.titleHeight);
 +      me.frame.zoomedOutTitleBarPosition = me.zoomedOutTitleBarPosition;
 +      me.frame.zoomedOutTitleBar = me.zoomedOutTitleBar;
 +      me.frame.alpha = me.alpha;
 +      me.addItem(me, me.frame, '0 0 0', '1 1 0', 1);
 +
 +      if (!me.titleFontSize)
 +              me.titleHeight = 0; // no title bar
 +
 +      absWidth = me.intendedWidth * conwidth;
 +      absHeight = me.borderLines * me.titleHeight + me.marginTop + me.rows * me.rowHeight + (me.rows - 1) * me.rowSpacing + me.marginBottom;
 +      me.itemOrigin  = eX * (me.marginLeft / absWidth)
 +                     + eY * ((me.borderLines * me.titleHeight + me.marginTop) / absHeight);
 +      me.itemSize    = eX * ((1 - (me.marginLeft + me.marginRight + me.columnSpacing * (me.columns - 1)) / absWidth) / me.columns)
 +                     + eY * (me.rowHeight / absHeight);
 +      me.itemSpacing = me.itemSize
 +                     + eX * (me.columnSpacing / absWidth)
 +                     + eY * (me.rowSpacing / absHeight);
 +      me.intendedHeight = absHeight / conheight;
 +      me.currentRow = -1;
 +      me.currentColumn = -1;
 +
 +      me.fill(me);
 +
 +      if(me.closable && me.borderLines > 0)
 +      {
 +              entity closebutton;
 +              closebutton = me.closeButton = me.frame.closeButton = spawnButton();
 +              closebutton.configureButton(closebutton, "", 0, me.closeButtonImage);
 +              closebutton.onClick = Dialog_Close; closebutton.onClickEntity = me;
 +              closebutton.srcMulti = 0;
 +              me.addItem(me, closebutton, '0 0 0', '1 1 0', 1); // put it as LAST
 +      }
 +}
 +
 +void Dialog_close(entity me)
 +{
 +      if(me.parent.instanceOfNexposee)
 +      {
 +              ExposeeCloseButton_Click(me, me.parent);
 +      }
 +      else if(me.parent.instanceOfModalController)
 +      {
 +              DialogCloseButton_Click(me, me);
 +      }
 +}
 +
 +float Dialog_keyDown(entity me, float key, float ascii, float shift)
 +{
 +      if(me.closable)
 +      {
 +              if(key == K_ESCAPE)
 +              {
++                      m_play_click_sound(MENU_SOUND_CLOSE);
 +                      me.close(me);
 +                      return 1;
 +              }
 +      }
 +      return SUPER(Dialog).keyDown(me, key, ascii, shift);
 +}
 +#endif
index d3f68152e3fc8812736a54ea62dc8e44b4636036,0000000000000000000000000000000000000000..275b20046def9e411a8211d231fc55a3117e162e
mode 100644,000000..100644
--- /dev/null
@@@ -1,391 -1,0 +1,390 @@@
- void InputBox_Clear_Click(entity btn, entity me);
 +#ifdef INTERFACE
 +CLASS(InputBox) EXTENDS(Label)
 +      METHOD(InputBox, configureInputBox, void(entity, string, float, float, string))
 +      METHOD(InputBox, draw, void(entity))
 +      METHOD(InputBox, setText, void(entity, string))
 +      METHOD(InputBox, enterText, void(entity, string))
 +      METHOD(InputBox, keyDown, float(entity, float, float, float))
 +      METHOD(InputBox, mouseMove, float(entity, vector))
 +      METHOD(InputBox, mouseRelease, float(entity, vector))
 +      METHOD(InputBox, mousePress, float(entity, vector))
 +      METHOD(InputBox, mouseDrag, float(entity, vector))
 +      METHOD(InputBox, showNotify, void(entity))
 +      METHOD(InputBox, resizeNotify, void(entity, vector, vector, vector, vector))
 +
 +      ATTRIB(InputBox, src, string, string_null)
 +
 +      ATTRIB(InputBox, cursorPos, float, 0) // characters
 +      ATTRIB(InputBox, scrollPos, float, 0) // widths
 +
 +      ATTRIB(InputBox, focusable, float, 1)
++      ATTRIB(InputBox, allowFocusSound, float, 1)
 +      ATTRIB(InputBox, disabled, float, 0)
 +      ATTRIB(InputBox, lastChangeTime, float, 0)
 +      ATTRIB(InputBox, dragScrollTimer, float, 0)
 +      ATTRIB(InputBox, dragScrollPos, vector, '0 0 0')
 +      ATTRIB(InputBox, pressed, float, 0)
 +      ATTRIB(InputBox, editColorCodes, float, 1)
 +      ATTRIB(InputBox, forbiddenCharacters, string, "")
 +      ATTRIB(InputBox, color, vector, '1 1 1')
 +      ATTRIB(InputBox, colorF, vector, '1 1 1')
 +      ATTRIB(InputBox, maxLength, float, 255) // if negative, it counts bytes, not chars
 +
 +      ATTRIB(InputBox, enableClearButton, float, 1)
 +      ATTRIB(InputBox, clearButton, entity, NULL)
 +      ATTRIB(InputBox, cb_width, float, 0)
 +      ATTRIB(InputBox, cb_pressed, float, 0)
 +      ATTRIB(InputBox, cb_focused, float, 0)
 +      ATTRIB(InputBox, cb_color, vector, '1 1 1')
 +      ATTRIB(InputBox, cb_colorF, vector, '1 1 1')
 +      ATTRIB(InputBox, cb_colorC, vector, '1 1 1')
 +ENDCLASS(InputBox)
- void InputBox_Clear_Click(entity btn, entity me)
- {
-       me.setText(me, "");
- }
 +#endif
 +
 +#ifdef IMPLEMENTATION
 +void InputBox_configureInputBox(entity me, string theText, float theCursorPos, float theFontSize, string gfx)
 +{
 +      SUPER(InputBox).configureLabel(me, theText, theFontSize, 0.0);
 +      me.src = gfx;
 +      me.cursorPos = theCursorPos;
 +}
 +void InputBox_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
 +{
 +      SUPER(InputBox).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
 +      if (me.enableClearButton)
 +      {
 +              me.cb_width = absSize.y / absSize.x;
 +              me.cb_offset = bound(-1, me.cb_offset, 0) * me.cb_width; // bound to range -1, 0
 +              me.keepspaceRight = me.keepspaceRight - me.cb_offset + me.cb_width;
 +      }
 +}
 +
 +void InputBox_setText(entity me, string txt)
 +{
 +      if(me.text)
 +              strunzone(me.text);
 +      SUPER(InputBox).setText(me, strzone(txt));
 +}
 +
-               InputBox_Clear_Click(world, me);
 +float over_ClearButton(entity me, vector pos)
 +{
 +      if (pos.x >= 1 + me.cb_offset - me.cb_width)
 +      if (pos.x < 1 + me.cb_offset)
 +      if (pos.y >= 0)
 +      if (pos.y < 1)
 +              return 1;
 +      return 0;
 +}
 +
 +float InputBox_mouseMove(entity me, vector pos)
 +{
 +      if (me.enableClearButton)
 +      {
 +              if (over_ClearButton(me, pos))
 +              {
 +                      me.cb_focused = 1;
 +                      return 1;
 +              }
 +              me.cb_focused = 0;
 +      }
 +      return 1;
 +}
 +
 +float InputBox_mouseDrag(entity me, vector pos)
 +{
 +      float p;
 +      if(me.pressed)
 +      {
 +              me.dragScrollPos = pos;
 +              p = me.scrollPos + pos.x - me.keepspaceLeft;
 +              me.cursorPos = draw_TextLengthUpToWidth(me.text, p, 0, me.realFontSize);
 +              me.lastChangeTime = time;
 +      }
 +      else if (me.enableClearButton)
 +      {
 +              if (over_ClearButton(me, pos))
 +              {
 +                      me.cb_pressed = 1;
 +                      return 1;
 +              }
 +      }
 +      me.cb_pressed = 0;
 +      return 1;
 +}
 +
 +float InputBox_mousePress(entity me, vector pos)
 +{
 +      if (me.enableClearButton)
 +      if (over_ClearButton(me, pos))
 +      {
 +              me.cb_pressed = 1;
 +              return 1;
 +      }
 +      me.dragScrollTimer = time;
 +      me.pressed = 1;
 +      return InputBox_mouseDrag(me, pos);
 +}
 +
 +float InputBox_mouseRelease(entity me, vector pos)
 +{
 +      if(me.cb_pressed)
 +      if (over_ClearButton(me, pos))
 +      {
++              m_play_click_sound(MENU_SOUND_CLEAR);
++              me.setText(me, "");
 +              me.cb_pressed = 0;
 +              return 1;
 +      }
 +      float r = InputBox_mouseDrag(me, pos);
 +      //reset cb_pressed after mouseDrag, mouseDrag could set cb_pressed in this case:
 +      //mouse press out of the clear button, drag and then mouse release over the clear button
 +      me.cb_pressed = 0;
 +      me.pressed = 0;
 +      return r;
 +}
 +
 +void InputBox_enterText(entity me, string ch)
 +{
 +      float i;
 +      for(i = 0; i < strlen(ch); ++i)
 +              if(strstrofs(me.forbiddenCharacters, substring(ch, i, 1), 0) > -1)
 +                      return;
 +      if(me.maxLength > 0)
 +      {
 +              if(strlen(ch) + strlen(me.text) > me.maxLength)
 +                      return;
 +      }
 +      else if(me.maxLength < 0)
 +      {
 +              if(u8_strsize(ch) + u8_strsize(me.text) > -me.maxLength)
 +                      return;
 +      }
 +      me.setText(me, strcat(substring(me.text, 0, me.cursorPos), ch, substring(me.text, me.cursorPos, strlen(me.text) - me.cursorPos)));
 +      me.cursorPos += strlen(ch);
 +}
 +
 +float InputBox_keyDown(entity me, float key, float ascii, float shift)
 +{
 +      me.lastChangeTime = time;
 +      me.dragScrollTimer = time;
 +      if(ascii >= 32 && ascii != 127)
 +      {
 +              me.enterText(me, chr(ascii));
 +              return 1;
 +      }
 +      switch(key)
 +      {
 +              case K_KP_LEFTARROW:
 +              case K_LEFTARROW:
 +                      me.cursorPos -= 1;
 +                      return 1;
 +              case K_KP_RIGHTARROW:
 +              case K_RIGHTARROW:
 +                      me.cursorPos += 1;
 +                      return 1;
 +              case K_KP_HOME:
 +              case K_HOME:
 +                      me.cursorPos = 0;
 +                      return 1;
 +              case K_KP_END:
 +              case K_END:
 +                      me.cursorPos = strlen(me.text);
 +                      return 1;
 +              case K_BACKSPACE:
 +                      if(me.cursorPos > 0)
 +                      {
 +                              me.cursorPos -= 1;
 +                              me.setText(me, strcat(substring(me.text, 0, me.cursorPos), substring(me.text, me.cursorPos + 1, strlen(me.text) - me.cursorPos - 1)));
 +                      }
 +                      return 1;
 +              case K_KP_DEL:
 +              case K_DEL:
 +                      if(shift & S_CTRL)
++                      {
++                              m_play_click_sound(MENU_SOUND_CLEAR);
 +                              me.setText(me, "");
++                      }
 +                      else
 +                              me.setText(me, strcat(substring(me.text, 0, me.cursorPos), substring(me.text, me.cursorPos + 1, strlen(me.text) - me.cursorPos - 1)));
 +                      return 1;
 +      }
 +      return 0;
 +}
 +
 +void InputBox_draw(entity me)
 +{
 +      string CURSOR = "_";
 +      float cursorPosInWidths, totalSizeInWidths;
 +
 +      if(me.pressed)
 +              me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
 +
 +      if(me.recalcPos)
 +              me.recalcPositionWithText(me, me.text);
 +
 +      me.focusable = !me.disabled;
 +      if(me.disabled)
 +              draw_alpha *= me.disabledAlpha;
 +
 +      if(me.src)
 +      {
 +              if(me.focused && !me.disabled)
 +                      draw_ButtonPicture('0 0 0', strcat(me.src, "_f"), '1 1 0', me.colorF, 1);
 +              else
 +                      draw_ButtonPicture('0 0 0', strcat(me.src, "_n"), '1 1 0', me.color, 1);
 +      }
 +
 +      me.cursorPos = bound(0, me.cursorPos, strlen(me.text));
 +      cursorPosInWidths = draw_TextWidth(substring(me.text, 0, me.cursorPos), 0, me.realFontSize);
 +      totalSizeInWidths = draw_TextWidth(strcat(me.text, CURSOR), 0, me.realFontSize);
 +
 +      if(me.dragScrollTimer < time)
 +      {
 +              float save;
 +              save = me.scrollPos;
 +              me.scrollPos = bound(cursorPosInWidths - (0.875 - me.keepspaceLeft - me.keepspaceRight), me.scrollPos, cursorPosInWidths - 0.125);
 +              if(me.scrollPos != save)
 +                      me.dragScrollTimer = time + 0.2;
 +      }
 +      me.scrollPos = min(me.scrollPos, totalSizeInWidths - (1 - me.keepspaceRight - me.keepspaceLeft));
 +      me.scrollPos = max(0, me.scrollPos);
 +
 +      draw_SetClipRect(eX * me.keepspaceLeft, eX * (1 - me.keepspaceLeft - me.keepspaceRight) + eY);
 +      if(me.editColorCodes)
 +      {
 +              string ch, ch2;
 +              float i, n;
 +              vector theColor;
 +              float theAlpha;    //float theVariableAlpha;
 +              vector p;
 +              vector theTempColor;
 +              float component;
 +
 +              p = me.realOrigin - eX * me.scrollPos;
 +              theColor = '1 1 1';
 +              theAlpha = 1;    //theVariableAlpha = 1; // changes when ^ax found
 +
 +              n = strlen(me.text);
 +              for(i = 0; i < n; ++i)
 +              {
 +                      ch = substring(me.text, i, 1);
 +                      if(ch == "^")
 +                      {
 +                              float w;
 +                              ch2 = substring(me.text, i+1, 1);
 +                              w = draw_TextWidth(strcat(ch, ch2), 0, me.realFontSize);
 +                              if(ch2 == "^")
 +                              {
 +                                      draw_Fill(p, eX * w + eY * me.realFontSize.y, '1 1 1', 0.5);
 +                                      draw_Text(p + eX * 0.25 * w, "^", me.realFontSize, theColor, theAlpha, 0);
 +                              }
 +                              else if(ch2 == "0" || stof(ch2)) // digit?
 +                              {
 +                                      switch(stof(ch2))
 +                                      {
 +                                              case 0: theColor = '0 0 0'; theAlpha = 1; break;
 +                                              case 1: theColor = '1 0 0'; theAlpha = 1; break;
 +                                              case 2: theColor = '0 1 0'; theAlpha = 1; break;
 +                                              case 3: theColor = '1 1 0'; theAlpha = 1; break;
 +                                              case 4: theColor = '0 0 1'; theAlpha = 1; break;
 +                                              case 5: theColor = '0 1 1'; theAlpha = 1; break;
 +                                              case 6: theColor = '1 0 1'; theAlpha = 1; break;
 +                                              case 7: theColor = '1 1 1'; theAlpha = 1; break;
 +                                              case 8: theColor = '1 1 1'; theAlpha = 0.5; break;
 +                                              case 9: theColor = '0.5 0.5 0.5'; theAlpha = 1; break;
 +                                      }
 +                                      draw_Fill(p, eX * w + eY * me.realFontSize.y, '1 1 1', 0.5);
 +                                      draw_Text(p, strcat(ch, ch2), me.realFontSize, theColor, theAlpha, 0);
 +                              }
 +                              else if(ch2 == "x") // ^x found
 +                              {
 +                                      theColor = '1 1 1';
 +
 +                                      component = HEXDIGIT_TO_DEC(substring(me.text, i+2, 1));
 +                                      if (component >= 0) // ^xr found
 +                                      {
 +                                              theTempColor.x = component/15;
 +
 +                                              component = HEXDIGIT_TO_DEC(substring(me.text, i+3, 1));
 +                                              if (component >= 0) // ^xrg found
 +                                              {
 +                                                      theTempColor.y = component/15;
 +
 +                                                      component = HEXDIGIT_TO_DEC(substring(me.text, i+4, 1));
 +                                                      if (component >= 0) // ^xrgb found
 +                                                      {
 +                                                              theTempColor.z = component/15;
 +                                                              theColor = theTempColor;
 +                                                              w = draw_TextWidth(substring(me.text, i, 5), 0, me.realFontSize);
 +
 +                                                              draw_Fill(p, eX * w + eY * me.realFontSize.y, '1 1 1', 0.5);
 +                                                              draw_Text(p, substring(me.text, i, 5), me.realFontSize, theColor, 1, 0);    // theVariableAlpha instead of 1 using alpha tags ^ax
 +                                                              i += 3;
 +                                                      }
 +                                                      else
 +                                                      {
 +                                                              // blue missing
 +                                                              w = draw_TextWidth(substring(me.text, i, 4), 0, me.realFontSize);
 +                                                              draw_Fill(p, eX * w + eY * me.realFontSize.y, eZ, 0.5);
 +                                                              draw_Text(p, substring(me.text, i, 4), me.realFontSize, '1 1 1', theAlpha, 0);
 +                                                              i += 2;
 +                                                      }
 +                                              }
 +                                              else
 +                                              {
 +                                                      // green missing
 +                                                      w = draw_TextWidth(substring(me.text, i, 3), 0, me.realFontSize);
 +                                                      draw_Fill(p, eX * w + eY * me.realFontSize.y, eY, 0.5);
 +                                                      draw_Text(p, substring(me.text, i, 3), me.realFontSize, '1 1 1', theAlpha, 0);
 +                                                      i += 1;
 +                                              }
 +                                      }
 +                                      else
 +                                      {
 +                                              // red missing
 +                                              //w = draw_TextWidth(substring(me.text, i, 2), 0) * me.realFontSize_x;
 +                                              draw_Fill(p, eX * w + eY * me.realFontSize.y, eX, 0.5);
 +                                              draw_Text(p, substring(me.text, i, 2), me.realFontSize, '1 1 1', theAlpha, 0);
 +                                      }
 +                              }
 +                              else
 +                              {
 +                                      draw_Fill(p, eX * w + eY * me.realFontSize.y, '1 1 1', 0.5);
 +                                      draw_Text(p, strcat(ch, ch2), me.realFontSize, theColor, theAlpha, 0);
 +                              }
 +                              p += w * eX;
 +                              ++i;
 +                              continue;
 +                      }
 +                      draw_Text(p, ch, me.realFontSize, theColor, theAlpha, 0); // TODO theVariableAlpha
 +                      p += eX * draw_TextWidth(ch, 0, me.realFontSize);
 +              }
 +      }
 +      else
 +              draw_Text(me.realOrigin - eX * me.scrollPos, me.text, me.realFontSize, '1 1 1', 1, 0);
 +
 +      if(!me.focused || (time - me.lastChangeTime) < floor(time - me.lastChangeTime) + 0.5)
 +              draw_Text(me.realOrigin + eX * (cursorPosInWidths - me.scrollPos), CURSOR, me.realFontSize, '1 1 1', 1, 0);
 +
 +      draw_ClearClip();
 +
 +      if (me.enableClearButton)
 +      if (me.text != "")
 +      {
 +              if(me.focused && me.cb_pressed)
 +                      draw_Picture(eX * (1 + me.cb_offset - me.cb_width), strcat(me.cb_src, "_c"), eX * me.cb_width + eY, me.cb_colorC, 1);
 +              else if(me.focused && me.cb_focused)
 +                      draw_Picture(eX * (1 + me.cb_offset - me.cb_width), strcat(me.cb_src, "_f"), eX * me.cb_width + eY, me.cb_colorF, 1);
 +              else
 +                      draw_Picture(eX * (1 + me.cb_offset - me.cb_width), strcat(me.cb_src, "_n"), eX * me.cb_width + eY, me.cb_color, 1);
 +      }
 +
 +      // skipping SUPER(InputBox).draw(me);
 +      Item_draw(me);
 +}
 +
 +void InputBox_showNotify(entity me)
 +{
 +      me.focusable = !me.disabled;
 +}
 +#endif
index 2f2979509e37d6df91b3d4129bb24da44f9ba767,0000000000000000000000000000000000000000..178b12b9a047ea3645cb89ca8fa09326ddb692f2
mode 100644,000000..100644
--- /dev/null
@@@ -1,402 -1,0 +1,403 @@@
 +#ifdef INTERFACE
 +CLASS(ListBox) EXTENDS(Item)
 +      METHOD(ListBox, resizeNotify, void(entity, vector, vector, vector, vector))
 +      METHOD(ListBox, configureListBox, void(entity, float, float))
 +      METHOD(ListBox, draw, void(entity))
 +      METHOD(ListBox, keyDown, float(entity, float, float, float))
 +      METHOD(ListBox, mousePress, float(entity, vector))
 +      METHOD(ListBox, mouseDrag, float(entity, vector))
 +      METHOD(ListBox, mouseRelease, float(entity, vector))
 +      METHOD(ListBox, focusLeave, void(entity))
 +      ATTRIB(ListBox, focusable, float, 1)
++      ATTRIB(ListBox, allowFocusSound, float, 1)
 +      ATTRIB(ListBox, selectedItem, float, 0)
 +      ATTRIB(ListBox, size, vector, '0 0 0')
 +      ATTRIB(ListBox, origin, vector, '0 0 0')
 +      ATTRIB(ListBox, scrollPos, float, 0) // measured in window heights, fixed when needed
 +      ATTRIB(ListBox, previousValue, float, 0)
 +      ATTRIB(ListBox, pressed, float, 0) // 0 = normal, 1 = scrollbar dragging, 2 = item dragging, 3 = released
 +      ATTRIB(ListBox, pressOffset, float, 0)
 +
 +      METHOD(ListBox, updateControlTopBottom, void(entity))
 +      ATTRIB(ListBox, controlTop, float, 0)
 +      ATTRIB(ListBox, controlBottom, float, 0)
 +      ATTRIB(ListBox, controlWidth, float, 0)
 +      ATTRIB(ListBox, dragScrollTimer, float, 0)
 +      ATTRIB(ListBox, dragScrollPos, vector, '0 0 0')
 +
 +      ATTRIB(ListBox, src, string, string_null) // scrollbar
 +      ATTRIB(ListBox, color, vector, '1 1 1')
 +      ATTRIB(ListBox, color2, vector, '1 1 1')
 +      ATTRIB(ListBox, colorC, vector, '1 1 1')
 +      ATTRIB(ListBox, colorF, vector, '1 1 1')
 +      ATTRIB(ListBox, tolerance, vector, '0 0 0') // drag tolerance
 +      ATTRIB(ListBox, scrollbarWidth, float, 0) // pixels
 +      ATTRIB(ListBox, nItems, float, 42)
 +      ATTRIB(ListBox, itemHeight, float, 0)
 +      ATTRIB(ListBox, colorBG, vector, '0 0 0')
 +      ATTRIB(ListBox, alphaBG, float, 0)
 +
 +      ATTRIB(ListBox, lastClickedItem, float, -1)
 +      ATTRIB(ListBox, lastClickedTime, float, 0)
 +
 +      METHOD(ListBox, drawListBoxItem, void(entity, float, vector, float)) // item number, width/height, selected
 +      METHOD(ListBox, clickListBoxItem, void(entity, float, vector)) // item number, relative clickpos
 +      METHOD(ListBox, doubleClickListBoxItem, void(entity, float, vector)) // item number, relative clickpos
 +      METHOD(ListBox, setSelected, void(entity, float))
 +
 +      METHOD(ListBox, getLastFullyVisibleItemAtScrollPos, float(entity, float))
 +      METHOD(ListBox, getFirstFullyVisibleItemAtScrollPos, float(entity, float))
 +
 +      // NOTE: override these four methods if you want variable sized list items
 +      METHOD(ListBox, getTotalHeight, float(entity))
 +      METHOD(ListBox, getItemAtPos, float(entity, float))
 +      METHOD(ListBox, getItemStart, float(entity, float))
 +      METHOD(ListBox, getItemHeight, float(entity, float))
 +      // NOTE: if getItemAt* are overridden, it may make sense to cache the
 +      // start and height of the last item returned by getItemAtPos and fast
 +      // track returning their properties for getItemStart and getItemHeight.
 +      // The "hot" code path calls getItemAtPos first, then will query
 +      // getItemStart and getItemHeight on it soon.
 +      // When overriding, the following consistency rules must hold:
 +      // getTotalHeight() == SUM(getItemHeight(i), i, 0, me.nItems-1)
 +      // getItemStart(i+1) == getItemStart(i) + getItemHeight(i)
 +      //   for 0 <= i < me.nItems-1
 +      // getItemStart(0) == 0
 +      // getItemStart(getItemAtPos(p)) <= p
 +      //   if p >= 0
 +      // getItemAtPos(p) == 0
 +      //   if p < 0
 +      // getItemStart(getItemAtPos(p)) + getItemHeight(getItemAtPos(p)) > p
 +      //   if p < getTotalHeigt()
 +      // getItemAtPos(p) == me.nItems - 1
 +      //   if p >= getTotalHeight()
 +ENDCLASS(ListBox)
 +#endif
 +
 +#ifdef IMPLEMENTATION
 +void ListBox_setSelected(entity me, float i)
 +{
 +      me.selectedItem = bound(0, i, me.nItems - 1);
 +}
 +void ListBox_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
 +{
 +      SUPER(ListBox).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
 +      me.controlWidth = me.scrollbarWidth / absSize.x;
 +}
 +void ListBox_configureListBox(entity me, float theScrollbarWidth, float theItemHeight)
 +{
 +      me.scrollbarWidth = theScrollbarWidth;
 +      me.itemHeight = theItemHeight;
 +}
 +
 +float ListBox_getTotalHeight(entity me)
 +{
 +      return me.nItems * me.itemHeight;
 +}
 +float ListBox_getItemAtPos(entity me, float pos)
 +{
 +      return floor(pos / me.itemHeight);
 +}
 +float ListBox_getItemStart(entity me, float i)
 +{
 +      return me.itemHeight * i;
 +}
 +float ListBox_getItemHeight(entity me, float i)
 +{
 +      return me.itemHeight;
 +}
 +
 +float ListBox_getLastFullyVisibleItemAtScrollPos(entity me, float pos)
 +{
 +      return me.getItemAtPos(me, pos + 1.001) - 1;
 +}
 +float ListBox_getFirstFullyVisibleItemAtScrollPos(entity me, float pos)
 +{
 +      return me.getItemAtPos(me, pos - 0.001) + 1;
 +}
 +float ListBox_keyDown(entity me, float key, float ascii, float shift)
 +{
 +      me.dragScrollTimer = time;
 +      if(key == K_MWHEELUP)
 +      {
 +              me.scrollPos = max(me.scrollPos - 0.5, 0);
 +              me.setSelected(me, min(me.selectedItem, me.getLastFullyVisibleItemAtScrollPos(me, me.scrollPos)));
 +      }
 +      else if(key == K_MWHEELDOWN)
 +      {
 +              me.scrollPos = min(me.scrollPos + 0.5, me.getTotalHeight(me) - 1);
 +              me.setSelected(me, max(me.selectedItem, me.getFirstFullyVisibleItemAtScrollPos(me, me.scrollPos)));
 +      }
 +      else if(key == K_PGUP || key == K_KP_PGUP)
 +      {
 +              float i = me.selectedItem;
 +              float a = me.getItemHeight(me, i);
 +              for (;;)
 +              {
 +                      --i;
 +                      if (i < 0)
 +                              break;
 +                      a += me.getItemHeight(me, i);
 +                      if (a >= 1)
 +                              break;
 +              }
 +              me.setSelected(me, i + 1);
 +      }
 +      else if(key == K_PGDN || key == K_KP_PGDN)
 +      {
 +              float i = me.selectedItem;
 +              float a = me.getItemHeight(me, i);
 +              for (;;)
 +              {
 +                      ++i;
 +                      if (i >= me.nItems)
 +                              break;
 +                      a += me.getItemHeight(me, i);
 +                      if (a >= 1)
 +                              break;
 +              }
 +              me.setSelected(me, i - 1);
 +      }
 +      else if(key == K_UPARROW || key == K_KP_UPARROW)
 +              me.setSelected(me, me.selectedItem - 1);
 +      else if(key == K_DOWNARROW || key == K_KP_DOWNARROW)
 +              me.setSelected(me, me.selectedItem + 1);
 +      else if(key == K_HOME || key == K_KP_HOME)
 +      {
 +              me.scrollPos = 0;
 +              me.setSelected(me, 0);
 +      }
 +      else if(key == K_END || key == K_KP_END)
 +      {
 +              me.scrollPos = max(0, me.getTotalHeight(me) - 1);
 +              me.setSelected(me, me.nItems - 1);
 +      }
 +      else
 +              return 0;
 +      return 1;
 +}
 +float ListBox_mouseDrag(entity me, vector pos)
 +{
 +      float hit;
 +      float i;
 +      me.updateControlTopBottom(me);
 +      me.dragScrollPos = pos;
 +      if(me.pressed == 1)
 +      {
 +              hit = 1;
 +              if(pos.x < 1 - me.controlWidth - me.tolerance.y * me.controlWidth) hit = 0;
 +              if(pos.y < 0 - me.tolerance.x) hit = 0;
 +              if(pos.x >= 1 + me.tolerance.y * me.controlWidth) hit = 0;
 +              if(pos.y >= 1 + me.tolerance.x) hit = 0;
 +              if(hit)
 +              {
 +                      // calculate new pos to v
 +                      float d;
 +                      d = (pos.y - me.pressOffset) / (1 - (me.controlBottom - me.controlTop)) * (me.getTotalHeight(me) - 1);
 +                      me.scrollPos = me.previousValue + d;
 +              }
 +              else
 +                      me.scrollPos = me.previousValue;
 +              me.scrollPos = min(me.scrollPos, me.getTotalHeight(me) - 1);
 +              me.scrollPos = max(me.scrollPos, 0);
 +              i = min(me.selectedItem, me.getLastFullyVisibleItemAtScrollPos(me, me.scrollPos));
 +              i = max(i, ListBox_getFirstFullyVisibleItemAtScrollPos(me, me.scrollPos));
 +              me.setSelected(me, i);
 +      }
 +      else if(me.pressed == 2)
 +      {
 +              me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos.y));
 +      }
 +      return 1;
 +}
 +float ListBox_mousePress(entity me, vector pos)
 +{
 +      if(pos.x < 0) return 0;
 +      if(pos.y < 0) return 0;
 +      if(pos.x >= 1) return 0;
 +      if(pos.y >= 1) return 0;
 +      me.dragScrollPos = pos;
 +      me.updateControlTopBottom(me);
 +      me.dragScrollTimer = time;
 +      if(pos.x >= 1 - me.controlWidth)
 +      {
 +              // if hit, set me.pressed, otherwise scroll by one page
 +              if(pos.y < me.controlTop)
 +              {
 +                      // page up
 +                      me.scrollPos = max(me.scrollPos - 1, 0);
 +                      me.setSelected(me, min(me.selectedItem, ListBox_getLastFullyVisibleItemAtScrollPos(me, me.scrollPos)));
 +              }
 +              else if(pos.y > me.controlBottom)
 +              {
 +                      // page down
 +                      me.scrollPos = min(me.scrollPos + 1, me.getTotalHeight(me) - 1);
 +                      me.setSelected(me, max(me.selectedItem, ListBox_getFirstFullyVisibleItemAtScrollPos(me, me.scrollPos)));
 +              }
 +              else
 +              {
 +                      me.pressed = 1;
 +                      me.pressOffset = pos.y;
 +                      me.previousValue = me.scrollPos;
 +              }
 +      }
 +      else
 +      {
 +              // continue doing that while dragging (even when dragging outside). When releasing, forward the click to the then selected item.
 +              me.pressed = 2;
 +              // an item has been clicked. Select it, ...
 +              me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos.y));
 +      }
 +      return 1;
 +}
 +float ListBox_mouseRelease(entity me, vector pos)
 +{
 +      if(me.pressed == 1)
 +      {
 +              // slider dragging mode
 +              // in that case, nothing happens on releasing
 +      }
 +      else if(me.pressed == 2)
 +      {
 +              me.pressed = 3; // do that here, so setSelected can know the mouse has been released
 +              // item dragging mode
 +              // select current one one last time...
 +              me.setSelected(me, me.getItemAtPos(me, me.scrollPos + pos.y));
 +              // and give it a nice click event
 +              if(me.nItems > 0)
 +              {
 +                      vector where = globalToBox(pos, eY * (me.getItemStart(me, me.selectedItem) - me.scrollPos), eX * (1 - me.controlWidth) + eY * me.getItemHeight(me, me.selectedItem));
 +
 +                      if((me.selectedItem == me.lastClickedItem) && (time < me.lastClickedTime + 0.3))
 +                              me.doubleClickListBoxItem(me, me.selectedItem, where);
 +                      else
 +                              me.clickListBoxItem(me, me.selectedItem, where);
 +
 +                      me.lastClickedItem = me.selectedItem;
 +                      me.lastClickedTime = time;
 +              }
 +      }
 +      me.pressed = 0;
 +      return 1;
 +}
 +void ListBox_focusLeave(entity me)
 +{
 +      // Reset the var pressed in case listbox loses focus
 +      // by a mouse click on an item of the list
 +      // for example showing a dialog on right click
 +      me.pressed = 0;
 +}
 +void ListBox_updateControlTopBottom(entity me)
 +{
 +      float f;
 +      // scrollPos is in 0..1 and indicates where the "page" currently shown starts.
 +      if(me.getTotalHeight(me) <= 1)
 +      {
 +              // we don't need no stinkin' scrollbar, we don't need no view control...
 +              me.controlTop = 0;
 +              me.controlBottom = 1;
 +              me.scrollPos = 0;
 +      }
 +      else
 +      {
 +              if(frametime) // only do this in draw frames
 +              {
 +                      if(me.dragScrollTimer < time)
 +                      {
 +                              float save;
 +                              save = me.scrollPos;
 +                              // if selected item is below listbox, increase scrollpos so it is in
 +                              me.scrollPos = max(me.scrollPos, me.getItemStart(me, me.selectedItem) + me.getItemHeight(me, me.selectedItem) - 1);
 +                              // if selected item is above listbox, decrease scrollpos so it is in
 +                              me.scrollPos = min(me.scrollPos, me.getItemStart(me, me.selectedItem));
 +                              if(me.scrollPos != save)
 +                                      me.dragScrollTimer = time + 0.2;
 +                      }
 +              }
 +              // if scroll pos is below end of list, fix it
 +              me.scrollPos = min(me.scrollPos, me.getTotalHeight(me) - 1);
 +              // if scroll pos is above beginning of list, fix it
 +              me.scrollPos = max(me.scrollPos, 0);
 +              // now that we know where the list is scrolled to, find out where to draw the control
 +              me.controlTop = max(0, me.scrollPos / me.getTotalHeight(me));
 +              me.controlBottom = min((me.scrollPos + 1) / me.getTotalHeight(me), 1);
 +
 +              float minfactor;
 +              minfactor = 2 * me.controlWidth / me.size.y * me.size.x;
 +              f = me.controlBottom - me.controlTop;
 +              if(f < minfactor) // FIXME good default?
 +              {
 +                      // f * X + 1 * (1-X) = minfactor
 +                      // (f - 1) * X + 1 = minfactor
 +                      // (f - 1) * X = minfactor - 1
 +                      // X = (minfactor - 1) / (f - 1)
 +                      f = (minfactor - 1) / (f - 1);
 +                      me.controlTop = me.controlTop * f + 0 * (1 - f);
 +                      me.controlBottom = me.controlBottom * f + 1 * (1 - f);
 +              }
 +      }
 +}
 +void ListBox_draw(entity me)
 +{
 +      float i;
 +      vector absSize, fillSize = '0 0 0';
 +      vector oldshift, oldscale;
 +      if(me.pressed == 2)
 +              me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
 +      me.updateControlTopBottom(me);
 +      fillSize.x = (1 - me.controlWidth);
 +      if(me.alphaBG)
 +              draw_Fill('0 0 0', '0 1 0' + fillSize, me.colorBG, me.alphaBG);
 +      if(me.controlWidth)
 +      {
 +              draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, me.color2, 1);
 +              if(me.getTotalHeight(me) > 1)
 +              {
 +                      vector o, s;
 +                      o = eX * (1 - me.controlWidth) + eY * me.controlTop;
 +                      s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
 +                      if(me.pressed == 1)
 +                              draw_VertButtonPicture(o, strcat(me.src, "_c"), s, me.colorC, 1);
 +                      else if(me.focused)
 +                              draw_VertButtonPicture(o, strcat(me.src, "_f"), s, me.colorF, 1);
 +                      else
 +                              draw_VertButtonPicture(o, strcat(me.src, "_n"), s, me.color, 1);
 +              }
 +      }
 +      draw_SetClip();
 +      oldshift = draw_shift;
 +      oldscale = draw_scale;
 +      float y;
 +      i = me.getItemAtPos(me, me.scrollPos);
 +      y = me.getItemStart(me, i) - me.scrollPos;
 +      for (; i < me.nItems && y < 1; ++i)
 +      {
 +              draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
 +              vector relSize = eX * (1 - me.controlWidth) + eY * me.getItemHeight(me, i);
 +              absSize = boxToGlobalSize(relSize, me.size);
 +              draw_scale = boxToGlobalSize(relSize, oldscale);
 +              me.drawListBoxItem(me, i, absSize, (me.selectedItem == i));
 +              y += relSize.y;
 +      }
 +      draw_ClearClip();
 +
 +      draw_shift = oldshift;
 +      draw_scale = oldscale;
 +      SUPER(ListBox).draw(me);
 +}
 +
 +void ListBox_clickListBoxItem(entity me, float i, vector where)
 +{
 +      // template method
 +}
 +
 +void ListBox_doubleClickListBoxItem(entity me, float i, vector where)
 +{
 +      // template method
 +}
 +
 +void ListBox_drawListBoxItem(entity me, float i, vector absSize, float selected)
 +{
 +      draw_Text('0 0 0', sprintf(_("Item %d"), i), eX * (8 / absSize.x) + eY * (8 / absSize.y), (selected ? '0 1 0' : '1 1 1'), 1, 0);
 +}
 +#endif
index 38332fd742128bf70c8d2a0e9a1128b65b36b9df,0000000000000000000000000000000000000000..bff21707906c9919927ce8557a5934aacbaa4bff
mode 100644,000000..100644
--- /dev/null
@@@ -1,293 -1,0 +1,305 @@@
-               f = (e.ModalController_factor = min(1, e.ModalController_factor + df));
-               if(e.ModalController_state)
-                       if(f < 1)
-                               animating = 1;
-               if(f < 1)
-               {
-                       prevFactor   = (1 - f) / (1 - f + df);
-                       targetFactor =     df  / (1 - f + df);
-               }
-               else
-               {
-                       prevFactor = 0;
-                       targetFactor = 1;
-               }
 +#ifdef INTERFACE
 +CLASS(ModalController) EXTENDS(Container)
 +      METHOD(ModalController, resizeNotify, void(entity, vector, vector, vector, vector))
 +      METHOD(ModalController, draw, void(entity))
 +      METHOD(ModalController, showChild, void(entity, entity, vector, vector, float))
 +      METHOD(ModalController, hideChild, void(entity, entity, float))
 +      METHOD(ModalController, hideAll, void(entity, float))
 +      METHOD(ModalController, addItem, void(entity, entity, vector, vector, float))
 +      METHOD(ModalController, addTab, void(entity, entity, entity))
 +
 +      METHOD(ModalController, initializeDialog, void(entity, entity))
 +
 +      METHOD(ModalController, switchState, void(entity, entity, float, float))
 +      ATTRIB(ModalController, origin, vector, '0 0 0')
 +      ATTRIB(ModalController, size, vector, '0 0 0')
 +      ATTRIB(ModalController, previousButton, entity, NULL)
 +      ATTRIB(ModalController, fadedAlpha, float, 0.3)
 +ENDCLASS(ModalController)
 +
 +.entity tabSelectingButton;
 +.vector origin;
 +.vector size;
 +void TabButton_Click(entity button, entity tab); // assumes a button has set the above fields to its own absolute origin, its size, and the tab to activate
 +void DialogOpenButton_Click(entity button, entity tab); // assumes a button has set the above fields to its own absolute origin, its size, and the tab to activate
 +void DialogOpenButton_Click_withCoords(entity button, entity tab, vector theOrigin, vector theSize);
 +void DialogCloseButton_Click(entity button, entity tab); // assumes a button has set the above fields to the tab to close
 +#endif
 +
 +#ifdef IMPLEMENTATION
 +
 +// modal dialog controller
 +// handles a stack of dialog elements
 +// each element can have one of the following states:
 +//   0: hidden (fading out)
 +//   1: visible (zooming in)
 +//   2: greyed out (inactive)
 +// While an animation is running, no item has focus. When an animation is done,
 +// the topmost item gets focus.
 +// The items are assumed to be added in overlapping order, that is, the lowest
 +// window must get added first.
 +//
 +// Possible uses:
 +// - to control a modal dialog:
 +//   - show modal dialog: me.showChild(me, childItem, buttonAbsOrigin, buttonAbsSize, 0) // childItem also gets focus
 +//   - dismiss modal dialog: me.hideChild(me, childItem, 0) // childItem fades out and relinquishes focus
 +//   - show first screen in m_show: me.hideAll(me, 1); me.showChild(me, me.firstChild, '0 0 0', '0 0 0', 1);
 +// - to show a temporary dialog instead of the menu (teamselect): me.hideAll(me, 1); me.showChild(me, teamSelectDialog, '0 0 0', '0 0 0', 1);
 +// - as a tabbed dialog control:
 +//   - to initialize: me.hideAll(me, 1); me.showChild(me, me.firstChild, '0 0 0', '0 0 0', 1);
 +//   - to show a tab: me.hideChild(me, currentTab, 0); me.showChild(me, newTab, buttonAbsOrigin, buttonAbsSize, 0);
 +
 +.vector ModalController_initialSize;
 +.vector ModalController_initialOrigin;
 +.vector ModalController_initialFontScale;
 +.float ModalController_initialAlpha;
 +.vector ModalController_buttonSize;
 +.vector ModalController_buttonOrigin;
 +.float ModalController_state;
 +.float ModalController_factor;
 +.entity ModalController_controllingButton;
 +
 +void ModalController_initializeDialog(entity me, entity root)
 +{
 +      me.hideAll(me, 1);
 +      me.showChild(me, root, '0 0 0', '0 0 0', 1); // someone else animates for us
 +}
 +
 +void TabButton_Click(entity button, entity tab)
 +{
 +      if(tab.ModalController_state == 1)
 +              return;
 +      tab.parent.hideAll(tab.parent, 0);
 +      button.forcePressed = 1;
 +      tab.ModalController_controllingButton = button;
 +      tab.parent.showChild(tab.parent, tab, button.origin, button.size, 0);
 +}
 +
 +void DialogOpenButton_Click(entity button, entity tab)
 +{
 +      DialogOpenButton_Click_withCoords(button, tab, button.origin, button.size);
 +}
 +
 +void DialogOpenButton_Click_withCoords(entity button, entity tab, vector theOrigin, vector theSize)
 +{
 +      if(tab.ModalController_state)
 +              return;
 +      if(button)
 +              button.forcePressed = 1;
 +      if(tab.parent.focusedChild)
 +              tab.parent.focusedChild.saveFocus(tab.parent.focusedChild);
 +      tab.ModalController_controllingButton = button;
 +      tab.parent.showChild(tab.parent, tab, theOrigin, theSize, 0);
 +}
 +
 +void DialogCloseButton_Click(entity button, entity tab)
 +{
 +      tab.parent.hideChild(tab.parent, tab, 0);
 +}
 +
 +void ModalController_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
 +{
 +      me.resizeNotifyLie(me, relOrigin, relSize, absOrigin, absSize, ModalController_initialOrigin, ModalController_initialSize, ModalController_initialFontScale);
 +}
 +
 +void ModalController_switchState(entity me, entity other, float state, float skipAnimation)
 +{
 +      float previousState;
 +      previousState = other.ModalController_state;
 +      if(state == previousState && !skipAnimation)
 +              return;
 +      other.ModalController_state = state;
 +      switch(state)
 +      {
 +              case 0:
 +                      other.ModalController_factor = 1 - other.Container_alpha / other.ModalController_initialAlpha;
 +                      // fading out
 +                      break;
 +              case 1:
 +                      other.ModalController_factor = other.Container_alpha / other.ModalController_initialAlpha;
 +                      if(previousState == 0 && !skipAnimation)
 +                      {
 +                              other.Container_origin = other.ModalController_buttonOrigin;
 +                              other.Container_size = other.ModalController_buttonSize;
 +                      }
 +                      // zooming in
 +                      break;
 +              case 2:
 +                      other.ModalController_factor = bound(0, (1 - other.Container_alpha / other.ModalController_initialAlpha) / me.fadedAlpha, 1);
 +                      // fading out halfway
 +                      break;
 +      }
 +      if(skipAnimation)
 +              other.ModalController_factor = 1;
 +}
 +
 +void ModalController_draw(entity me)
 +{
 +      entity e;
 +      entity front;
 +      float animating;
 +      float f; // animation factor
 +      float df; // animation step size
 +      float prevFactor, targetFactor;
 +      vector targetOrigin, targetSize; float targetAlpha;
 +      vector fs;
 +      animating = 0;
 +
 +      front = world;
 +      for(e = me.firstChild; e; e = e.nextSibling)
 +              if(e.ModalController_state)
 +              {
 +                      if(front)
++                      {
 +                              me.switchState(me, front, 2, 0);
++                              if(front.ModalController_factor < 1)
++                                      animating = 1;
++                      }
 +                      front = e;
 +              }
 +      if(front)
++      {
 +              me.switchState(me, front, 1, 0);
++              if(front.ModalController_factor < 1)
++                      animating = 1;
++      }
++
++      if(front && front.Container_alpha == front.ModalController_initialAlpha)
++              goto update_done; // update isn't needed, everything stay as is
 +
 +      df = frametime * 3; // animation speed
 +
 +      for(e = me.firstChild; e; e = e.nextSibling)
 +      {
-                       if(f < 1)
-                               animating = 1;
 +              if(e.ModalController_state == 2)
 +              {
 +                      // fading out partially
 +                      targetOrigin = e.Container_origin; // stay as is
 +                      targetSize = e.Container_size; // stay as is
 +                      targetAlpha = me.fadedAlpha * e.ModalController_initialAlpha;
 +              }
 +              else if(e.ModalController_state == 1)
 +              {
 +                      // zooming in
 +                      targetOrigin = e.ModalController_initialOrigin;
 +                      targetSize = e.ModalController_initialSize;
 +                      targetAlpha = e.ModalController_initialAlpha;
 +              }
 +              else
 +              {
 +                      // fading out
-                       e.Container_origin = e.Container_origin * prevFactor + targetOrigin * targetFactor;
-                       e.Container_size   = e.Container_size   * prevFactor + targetSize   * targetFactor;
-                       me.setAlphaOf(me, e, e.Container_alpha  * prevFactor + targetAlpha  * targetFactor);
 +                      targetOrigin = e.Container_origin; // stay as is
 +                      targetSize = e.Container_size; // stay as is
 +                      targetAlpha = 0;
 +              }
 +
++              f = (e.ModalController_factor = min(1, e.ModalController_factor + df));
 +              if(f == 1)
 +              {
++                      prevFactor = 0;
++                      targetFactor = 1;
 +                      e.Container_origin = targetOrigin;
 +                      e.Container_size = targetSize;
 +                      me.setAlphaOf(me, e, targetAlpha);
 +              }
 +              else
 +              {
-               fs = globalToBoxSize(e.Container_size, e.ModalController_initialSize);
-               e.Container_fontscale_x = fs.x * e.ModalController_initialFontScale.x;
-               e.Container_fontscale_y = fs.y * e.ModalController_initialFontScale.y;
++                      prevFactor = (1 - f) / (1 - f + df);
++                      if(!e.ModalController_state) // optimize code and avoid precision errors
++                              me.setAlphaOf(me, e, e.Container_alpha  * prevFactor);
++                      else
++                      {
++                              targetFactor = df / (1 - f + df);
++
++                              if(e.ModalController_state == 1)
++                              {
++                                      e.Container_origin = e.Container_origin * prevFactor + targetOrigin * targetFactor;
++                                      e.Container_size   = e.Container_size   * prevFactor + targetSize   * targetFactor;
++                              }
++                              me.setAlphaOf(me, e, e.Container_alpha  * prevFactor + targetAlpha  * targetFactor);
++                      }
 +              }
 +              // assume: o == to * f_prev + X * (1 - f_prev)
 +              // make:   o' = to * f  + X * (1 - f)
 +              // -->
 +              // X == (o - to * f_prev) / (1 - f_prev)
 +              // o' = to * f + (o - to * f_prev) / (1 - f_prev) * (1 - f)
 +              // --> (maxima)
 +              // o' = (to * (f - f_prev) + o * (1 - f)) / (1 - f_prev)
 +
++              if(e.ModalController_state == 1)
++              {
++                      fs = globalToBoxSize(e.Container_size, e.ModalController_initialSize);
++                      e.Container_fontscale_x = fs.x * e.ModalController_initialFontScale.x;
++                      e.Container_fontscale_y = fs.y * e.ModalController_initialFontScale.y;
++              }
 +      }
++      :update_done
++
 +      if(animating || !me.focused)
 +              me.setFocus(me, NULL);
 +      else
 +              me.setFocus(me, front);
 +      SUPER(ModalController).draw(me);
 +}
 +
 +void ModalController_addTab(entity me, entity other, entity tabButton)
 +{
 +      me.addItem(me, other, '0 0 0', '1 1 1', 1);
 +      tabButton.onClick = TabButton_Click;
 +      tabButton.onClickEntity = other;
 +      other.tabSelectingButton = tabButton;
 +      if(other == me.firstChild)
 +      {
 +              tabButton.forcePressed = 1;
 +              other.ModalController_controllingButton = tabButton;
 +              me.showChild(me, other, '0 0 0', '0 0 0', 1);
 +      }
 +}
 +
 +void ModalController_addItem(entity me, entity other, vector theOrigin, vector theSize, float theAlpha)
 +{
 +      SUPER(ModalController).addItem(me, other, theOrigin, theSize, (other == me.firstChild) ? theAlpha : 0);
 +      other.ModalController_initialFontScale = other.Container_fontscale;
 +      other.ModalController_initialSize = other.Container_size;
 +      other.ModalController_initialOrigin = other.Container_origin;
 +      other.ModalController_initialAlpha = theAlpha; // hope Container never modifies this
 +      if(other.ModalController_initialFontScale == '0 0 0')
 +              other.ModalController_initialFontScale = '1 1 0';
 +}
 +
 +void ModalController_showChild(entity me, entity other, vector theOrigin, vector theSize, float skipAnimation)
 +{
 +      if(other.ModalController_state == 0 || skipAnimation)
 +      {
 +              me.setFocus(me, NULL);
 +              if(!skipAnimation)
 +              {
 +                      other.ModalController_buttonOrigin = globalToBox(theOrigin, me.origin, me.size);
 +                      other.ModalController_buttonSize = globalToBoxSize(theSize, me.size);
 +              }
 +              me.switchState(me, other, 1, skipAnimation);
 +      } // zoom in from button (factor increases)
 +}
 +
 +void ModalController_hideAll(entity me, float skipAnimation)
 +{
 +      entity e;
 +      for(e = me.firstChild; e; e = e.nextSibling)
 +              me.hideChild(me, e, skipAnimation);
 +}
 +
 +void ModalController_hideChild(entity me, entity other, float skipAnimation)
 +{
 +      if(other.ModalController_state || skipAnimation)
 +      {
 +              me.setFocus(me, NULL);
 +              me.switchState(me, other, 0, skipAnimation);
 +              if(other.ModalController_controllingButton)
 +              {
 +                      other.ModalController_controllingButton.forcePressed = 0;
 +                      other.ModalController_controllingButton = NULL;
 +              }
 +      } // just alpha fade out (factor increases and decreases alpha)
 +}
 +#endif
index 79a294a3c90df26c618f9656c9888f9c6e65fa50,0000000000000000000000000000000000000000..1413039851ee7fcde86e3869d8778c9d2fc72614
mode 100644,000000..100644
--- /dev/null
@@@ -1,363 -1,0 +1,367 @@@
 +#ifdef INTERFACE
 +CLASS(Nexposee) EXTENDS(Container)
 +      METHOD(Nexposee, draw, void(entity))
 +      METHOD(Nexposee, keyDown, float(entity, float, float, float))
 +      METHOD(Nexposee, keyUp, float(entity, float, float, float))
 +      METHOD(Nexposee, mousePress, float(entity, vector))
 +      METHOD(Nexposee, mouseMove, float(entity, vector))
 +      METHOD(Nexposee, mouseRelease, float(entity, vector))
 +      METHOD(Nexposee, mouseDrag, float(entity, vector))
 +      METHOD(Nexposee, resizeNotify, void(entity, vector, vector, vector, vector))
 +      METHOD(Nexposee, focusEnter, void(entity))
 +      METHOD(Nexposee, close, void(entity))
 +
 +      ATTRIB(Nexposee, animationState, float, -1)
 +      ATTRIB(Nexposee, animationFactor, float, 0)
 +      ATTRIB(Nexposee, selectedChild, entity, NULL)
 +      ATTRIB(Nexposee, mouseFocusedChild, entity, NULL)
 +      METHOD(Nexposee, addItem, void(entity, entity, vector, vector, float))
 +      METHOD(Nexposee, calc, void(entity))
 +      METHOD(Nexposee, setNexposee, void(entity, entity, vector, float, float))
 +      ATTRIB(Nexposee, mousePosition, vector, '0 0 0')
 +      METHOD(Nexposee, pullNexposee, void(entity, entity, vector))
 +ENDCLASS(Nexposee)
 +
 +void ExposeeCloseButton_Click(entity button, entity other); // un-exposees the current state
 +#endif
 +
 +// animation states:
 +//   0 = thumbnails seen
 +//   1 = zooming in
 +//   2 = zoomed in
 +//   3 = zooming out
 +// animation factor: 0 = minimum theSize, 1 = maximum theSize
 +
 +#ifdef IMPLEMENTATION
 +
 +.vector Nexposee_initialSize;
 +.vector Nexposee_initialFontScale;
 +.vector Nexposee_initialOrigin;
 +.float Nexposee_initialAlpha;
 +
 +.vector Nexposee_smallSize;
 +.vector Nexposee_smallOrigin;
 +.float Nexposee_smallAlpha;
 +.float Nexposee_mediumAlpha;
 +.vector Nexposee_scaleCenter;
 +.vector Nexposee_align;
 +.float Nexposee_animationFactor;
 +
 +void Nexposee_close(entity me)
 +{
 +      // user must override this
 +}
 +
 +void ExposeeCloseButton_Click(entity button, entity other)
 +{
 +      other.selectedChild = other.focusedChild;
 +      other.setFocus(other, NULL);
 +      other.animationState = 3;
 +}
 +
 +void Nexposee_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
 +{
 +      me.calc(me);
 +      me.resizeNotifyLie(me, relOrigin, relSize, absOrigin, absSize, Nexposee_initialOrigin, Nexposee_initialSize, Nexposee_initialFontScale);
 +}
 +
 +void Nexposee_Calc_Scale(entity me, float scale)
 +{
 +      entity e;
 +      for(e = me.firstChild; e; e = e.nextSibling)
 +      {
 +              e.Nexposee_smallOrigin = (e.Nexposee_initialOrigin - e.Nexposee_scaleCenter) * scale + e.Nexposee_scaleCenter;
 +              e.Nexposee_smallSize = e.Nexposee_initialSize * scale;
 +              if(e.Nexposee_align.x > 0)
 +                      e.Nexposee_smallOrigin_x = 1 - e.Nexposee_align.x * scale;
 +              if(e.Nexposee_align.x < 0)
 +                      e.Nexposee_smallOrigin_x = -e.Nexposee_smallSize.x + e.Nexposee_align.x * scale;
 +              if(e.Nexposee_align.y > 0)
 +                      e.Nexposee_smallOrigin_y = 1 - e.Nexposee_align.y * scale;
 +              if(e.Nexposee_align.y < 0)
 +                      e.Nexposee_smallOrigin_y = -e.Nexposee_smallSize.y + e.Nexposee_align.y * scale;
 +      }
 +}
 +
 +void Nexposee_calc(entity me)
 +{
 +      /*
 +       * patented by Apple
 +       * can't put that here ;)
 +       */
 +      float scale;
 +      entity e, e2;
 +      vector emins, emaxs, e2mins, e2maxs;
 +
 +      for(scale = 0.7;; scale *= 0.99)
 +      {
 +              Nexposee_Calc_Scale(me, scale);
 +
 +              for(e = me.firstChild; e; e = e.nextSibling)
 +              {
 +                      emins = e.Nexposee_smallOrigin;
 +                      emaxs = emins + e.Nexposee_smallSize;
 +                      for(e2 = e.nextSibling; e2; e2 = e2.nextSibling)
 +                      {
 +                              e2mins = e2.Nexposee_smallOrigin;
 +                              e2maxs = e2mins + e2.Nexposee_smallSize;
 +
 +                              // two intervals [amins, amaxs] and [bmins, bmaxs] overlap if:
 +                              //   amins < bmins < amaxs < bmaxs
 +                              // for which suffices
 +                              //   bmins < amaxs
 +                              //   amins < bmaxs
 +                              if((e2mins.x - emaxs.x) * (emins.x - e2maxs.x) > 0) // x overlap
 +                                      if((e2mins.y - emaxs.y) * (emins.y - e2maxs.y) > 0) // y overlap
 +                                      {
 +                                              goto have_overlap;
 +                                      }
 +                      }
 +              }
 +
 +              break;
 +:have_overlap
 +      }
 +
 +      scale *= 0.95;
 +
 +      Nexposee_Calc_Scale(me, scale);
 +}
 +
 +void Nexposee_setNexposee(entity me, entity other, vector scalecenter, float a0, float a1)
 +{
 +      other.Nexposee_scaleCenter = scalecenter;
 +      other.Nexposee_smallAlpha = a0;
 +      me.setAlphaOf(me, other, a0);
 +      other.Nexposee_mediumAlpha = a1;
 +}
 +
 +void Nexposee_draw(entity me)
 +{
 +      float a;
 +      float a0;
 +      entity e;
 +      float f;
 +      vector fs;
 +
 +      if(me.animationState == -1)
 +      {
 +              me.animationState = 0;
 +      }
 +
 +      f = min(1, frametime * 5);
 +      switch(me.animationState)
 +      {
 +              case 0:
 +                      me.animationFactor = 0;
 +                      break;
 +              case 1:
 +                      me.animationFactor += f;
 +                      if(me.animationFactor >= 1)
 +                      {
 +                              me.animationFactor = 1;
 +                              me.animationState = 2;
 +                              SUPER(Nexposee).setFocus(me, me.selectedChild);
 +                      }
 +                      break;
 +              case 2:
 +                      me.animationFactor = 1;
 +                      break;
 +              case 3:
 +                      me.animationFactor -= f;
 +                      me.mouseFocusedChild = me.itemFromPoint(me, me.mousePosition);
 +                      if(me.animationFactor <= 0)
 +                      {
 +                              me.animationFactor = 0;
 +                              me.animationState = 0;
 +                              me.selectedChild = me.mouseFocusedChild;
 +                      }
 +                      break;
 +      }
 +
 +      f = min(1, frametime * 10);
 +      for(e = me.firstChild; e; e = e.nextSibling)
 +      {
 +              if(e == me.selectedChild)
 +              {
 +                      e.Container_origin = e.Nexposee_smallOrigin * (1 - me.animationFactor) + e.Nexposee_initialOrigin * me.animationFactor;
 +                      e.Container_size = e.Nexposee_smallSize * (1 - me.animationFactor) + e.Nexposee_initialSize * me.animationFactor;
 +                      e.Nexposee_animationFactor = me.animationFactor;
 +                      a0 = e.Nexposee_mediumAlpha;
 +                      if(me.animationState == 3)
 +                              if(e != me.mouseFocusedChild)
 +                                      a0 = e.Nexposee_smallAlpha;
 +                      a = a0 * (1 - me.animationFactor) + me.animationFactor;
 +              }
 +              else
 +              {
 +                      // minimum theSize counts
 +                      e.Container_origin = e.Nexposee_smallOrigin;
 +                      e.Container_size = e.Nexposee_smallSize;
 +                      e.Nexposee_animationFactor = 0;
 +                      a = e.Nexposee_smallAlpha * (1 - me.animationFactor);
 +              }
 +              me.setAlphaOf(me, e, e.Container_alpha * (1 - f) + a * f);
 +
 +              fs = globalToBoxSize(e.Container_size, e.Nexposee_initialSize);
 +              e.Container_fontscale_x = fs.x * e.Nexposee_initialFontScale.x;
 +              e.Container_fontscale_y = fs.y * e.Nexposee_initialFontScale.y;
 +      }
 +
 +      SUPER(Nexposee).draw(me);
 +}
 +
 +float Nexposee_mousePress(entity me, vector pos)
 +{
 +      if(me.animationState == 0)
 +      {
 +              me.mouseFocusedChild = NULL;
 +              Nexposee_mouseMove(me, pos);
 +              if(me.mouseFocusedChild)
 +              {
++                      m_play_click_sound(MENU_SOUND_OPEN);
 +                      me.animationState = 1;
 +                      SUPER(Nexposee).setFocus(me, NULL);
 +              }
 +              else
 +                      me.close(me);
 +              return 1;
 +      }
 +      else if(me.animationState == 2)
 +      {
 +              if (!(SUPER(Nexposee).mousePress(me, pos)))
 +              {
++                      m_play_click_sound(MENU_SOUND_CLOSE);
 +                      me.animationState = 3;
 +                      SUPER(Nexposee).setFocus(me, NULL);
 +              }
 +              return 1;
 +      }
 +      return 0;
 +}
 +
 +float Nexposee_mouseRelease(entity me, vector pos)
 +{
 +      if(me.animationState == 2)
 +              return SUPER(Nexposee).mouseRelease(me, pos);
 +      return 0;
 +}
 +
 +float Nexposee_mouseDrag(entity me, vector pos)
 +{
 +      if(me.animationState == 2)
 +              return SUPER(Nexposee).mouseDrag(me, pos);
 +      return 0;
 +}
 +
 +float Nexposee_mouseMove(entity me, vector pos)
 +{
 +      entity e;
 +      me.mousePosition = pos;
 +      e = me.mouseFocusedChild;
 +      me.mouseFocusedChild = me.itemFromPoint(me, pos);
 +      if(me.animationState == 2)
 +              return SUPER(Nexposee).mouseMove(me, pos);
 +      if(me.animationState == 0)
 +      {
 +              if(me.mouseFocusedChild)
 +                      if(me.mouseFocusedChild != e || me.mouseFocusedChild != me.selectedChild)
 +                              me.selectedChild = me.mouseFocusedChild;
 +              return 1;
 +      }
 +      return 0;
 +}
 +
 +float Nexposee_keyUp(entity me, float scan, float ascii, float shift)
 +{
 +      if(me.animationState == 2)
 +              return SUPER(Nexposee).keyUp(me, scan, ascii, shift);
 +      return 0;
 +}
 +
 +float Nexposee_keyDown(entity me, float scan, float ascii, float shift)
 +{
 +      float nexposeeKey = 0;
 +      if(me.animationState == 2)
 +              if(SUPER(Nexposee).keyDown(me, scan, ascii, shift))
 +                      return 1;
 +      if(scan == K_TAB)
 +      {
 +              if(me.animationState == 0)
 +              {
 +                      if(shift & S_SHIFT)
 +                      {
 +                              if(me.selectedChild)
 +                                      me.selectedChild = me.selectedChild.prevSibling;
 +                              if (!me.selectedChild)
 +                                      me.selectedChild = me.lastChild;
 +                      }
 +                      else
 +                      {
 +                              if(me.selectedChild)
 +                                      me.selectedChild = me.selectedChild.nextSibling;
 +                              if (!me.selectedChild)
 +                                      me.selectedChild = me.firstChild;
 +                      }
 +              }
 +      }
 +      switch(me.animationState)
 +      {
 +              default:
 +              case 0:
 +              case 3:
 +                      nexposeeKey = ((scan == K_SPACE) || (scan == K_ENTER) || (scan == K_KP_ENTER));
 +                      break;
 +              case 1:
 +              case 2:
 +                      nexposeeKey = (scan == K_ESCAPE);
 +                      break;
 +      }
 +      if(nexposeeKey)
 +      {
 +              switch(me.animationState)
 +              {
 +                      default:
 +                      case 0:
 +                      case 3:
++                              m_play_click_sound(MENU_SOUND_OPEN);
 +                              me.animationState = 1;
 +                              break;
 +                      case 1:
 +                      case 2:
++                              m_play_click_sound(MENU_SOUND_CLOSE);
 +                              me.animationState = 3;
 +                              break;
 +              }
 +              if(me.focusedChild)
 +                      me.selectedChild = me.focusedChild;
 +              if (!me.selectedChild)
 +                      me.animationState = 0;
 +              SUPER(Nexposee).setFocus(me, NULL);
 +              return 1;
 +      }
 +      return 0;
 +}
 +
 +void Nexposee_addItem(entity me, entity other, vector theOrigin, vector theSize, float theAlpha)
 +{
 +      SUPER(Nexposee).addItem(me, other, theOrigin, theSize, theAlpha);
 +      other.Nexposee_initialFontScale = other.Container_fontscale;
 +      other.Nexposee_initialSize = other.Container_size;
 +      other.Nexposee_initialOrigin = other.Container_origin;
 +      other.Nexposee_initialAlpha = other.Container_alpha;
 +      if(other.Nexposee_initialFontScale == '0 0 0')
 +              other.Nexposee_initialFontScale = '1 1 0';
 +}
 +
 +void Nexposee_focusEnter(entity me)
 +{
 +      if(me.animationState == 2)
 +              SUPER(Nexposee).setFocus(me, me.selectedChild);
 +}
 +
 +void Nexposee_pullNexposee(entity me, entity other, vector theAlign)
 +{
 +      other.Nexposee_align = theAlign;
 +}
 +#endif
index f97871b22a58ebda0a46770a4f5f9ca41eb09ae9,0000000000000000000000000000000000000000..2c74f6131c8f06e40e4a530bcb3720e2e2612613
mode 100644,000000..100644
--- /dev/null
@@@ -1,285 -1,0 +1,301 @@@
-       METHOD(Slider, focusEnter, void(entity))
 +// Note:
 +//   to use this, you FIRST call configureSliderVisuals, then configureSliderValues
 +#ifdef INTERFACE
 +CLASS(Slider) EXTENDS(Label)
 +      METHOD(Slider, resizeNotify, void(entity, vector, vector, vector, vector))
 +      METHOD(Slider, configureSliderVisuals, void(entity, float, float, float, string))
 +      METHOD(Slider, configureSliderValues, void(entity, float, float, float, float, float, float))
 +      METHOD(Slider, draw, void(entity))
 +      METHOD(Slider, keyDown, float(entity, float, float, float))
++      METHOD(Slider, keyUp, float(entity, float, float, float))
 +      METHOD(Slider, mousePress, float(entity, vector))
 +      METHOD(Slider, mouseDrag, float(entity, vector))
 +      METHOD(Slider, mouseRelease, float(entity, vector))
-       // TODO more keys
 +      METHOD(Slider, valueToText, string(entity, float))
 +      METHOD(Slider, toString, string(entity))
 +      METHOD(Slider, setValue, void(entity, float))
 +      METHOD(Slider, setSliderValue, void(entity, float))
 +      METHOD(Slider, showNotify, void(entity))
 +      ATTRIB(Slider, src, string, string_null)
 +      ATTRIB(Slider, focusable, float, 1)
++      ATTRIB(Slider, allowFocusSound, float, 1)
 +      ATTRIB(Slider, value, float, 0)
 +      ATTRIB(Slider, animated, float, 1)
 +      ATTRIB(Slider, sliderValue, float, 0)
 +      ATTRIB(Slider, valueMin, float, 0)
 +      ATTRIB(Slider, valueMax, float, 0)
 +      ATTRIB(Slider, valueStep, float, 0)
 +      ATTRIB(Slider, valueDigits, float, 0)
 +      ATTRIB(Slider, valueKeyStep, float, 0)
 +      ATTRIB(Slider, valuePageStep, float, 0)
 +      ATTRIB(Slider, valueDisplayMultiplier, float, 1.0)
 +      ATTRIB(Slider, textSpace, float, 0)
 +      ATTRIB(Slider, controlWidth, float, 0)
 +      ATTRIB(Slider, pressed, float, 0)
 +      ATTRIB(Slider, pressOffset, float, 0)
 +      ATTRIB(Slider, previousValue, float, 0)
 +      ATTRIB(Slider, tolerance, vector, '0 0 0')
 +      ATTRIB(Slider, disabled, float, 0)
 +      ATTRIB(Slider, color, vector, '1 1 1')
 +      ATTRIB(Slider, color2, vector, '1 1 1')
 +      ATTRIB(Slider, colorD, vector, '1 1 1')
 +      ATTRIB(Slider, colorC, vector, '1 1 1')
 +      ATTRIB(Slider, colorF, vector, '1 1 1')
 +      ATTRIB(Slider, disabledAlpha, float, 0.3)
 +ENDCLASS(Slider)
 +#endif
 +
 +#ifdef IMPLEMENTATION
 +void Slider_setValue(entity me, float val)
 +{
 +      if (me.animated) {
 +              anim.removeObjAnim(anim, me);
 +              makeHostedEasing(me, Slider_setSliderValue, easingQuadInOut, 1, me.sliderValue, val);
 +      } else {
 +              me.setSliderValue(me, val);
 +      }
 +      me.value = val;
 +}
 +void Slider_setSliderValue(entity me, float val)
 +{
 +      me.sliderValue = val;
 +}
 +string Slider_toString(entity me)
 +{
 +      return sprintf("%d (%s)", me.value, me.valueToText(me, me.value));
 +}
 +void Slider_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
 +{
 +      SUPER(Slider).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
 +      me.controlWidth = absSize.y / absSize.x;
 +}
 +string Slider_valueToText(entity me, float val)
 +{
 +      if(almost_in_bounds(me.valueMin, val, me.valueMax))
 +              return ftos_decimals(val * me.valueDisplayMultiplier, me.valueDigits);
 +      return "";
 +}
 +void Slider_configureSliderVisuals(entity me, float sz, float theAlign, float theTextSpace, string gfx)
 +{
 +      SUPER(Slider).configureLabel(me, string_null, sz, theAlign);
 +      me.textSpace = theTextSpace;
 +      me.keepspaceLeft = (theTextSpace == 0) ? 0 : (1 - theTextSpace);
 +      me.src = gfx;
 +}
 +void Slider_configureSliderValues(entity me, float theValueMin, float theValue, float theValueMax, float theValueStep, float theValueKeyStep, float theValuePageStep)
 +{
 +      me.value = theValue;
 +      me.sliderValue = theValue;
 +      me.valueStep = theValueStep;
 +      me.valueMin = theValueMin;
 +      me.valueMax = theValueMax;
 +      me.valueKeyStep = theValueKeyStep;
 +      me.valuePageStep = theValuePageStep;
 +      me.valueDigits = 3;
 +      if(fabs(floor(me.valueStep * 100 + 0.5) - (me.valueStep * 100)) < 0.01) // about a whole number of 100ths
 +              me.valueDigits = 2;
 +      if(fabs(floor(me.valueStep * 10 + 0.5) - (me.valueStep * 10)) < 0.01) // about a whole number of 10ths
 +              me.valueDigits = 1;
 +      if(fabs(floor(me.valueStep * 1 + 0.5) - (me.valueStep * 1)) < 0.01) // about a whole number
 +              me.valueDigits = 0;
 +}
 +float Slider_keyDown(entity me, float key, float ascii, float shift)
 +{
 +      float inRange;
 +      if(me.disabled)
 +              return 0;
 +      inRange = (almost_in_bounds(me.valueMin, me.value, me.valueMax));
 +      if(key == K_LEFTARROW || key == K_KP_LEFTARROW || key == K_MWHEELDOWN)
 +      {
 +              if(inRange)
 +                      me.setValue(me, median(me.valueMin, me.value - me.valueKeyStep, me.valueMax));
 +              else
 +                      me.setValue(me, me.valueMax);
 +              return 1;
 +      }
 +      if(key == K_RIGHTARROW || key == K_KP_RIGHTARROW || key == K_MWHEELUP)
 +      {
 +              if(inRange)
 +                      me.setValue(me, median(me.valueMin, me.value + me.valueKeyStep, me.valueMax));
 +              else
 +                      me.setValue(me, me.valueMin);
 +              return 1;
 +      }
 +      if(key == K_PGDN || key == K_KP_PGDN)
 +      {
 +              if(inRange)
 +                      me.setValue(me, median(me.valueMin, me.value - me.valuePageStep, me.valueMax));
 +              else
 +                      me.setValue(me, me.valueMax);
 +              return 1;
 +      }
 +      if(key == K_PGUP || key == K_KP_PGUP)
 +      {
 +              if(inRange)
 +                      me.setValue(me, median(me.valueMin, me.value + me.valuePageStep, me.valueMax));
 +              else
 +                      me.setValue(me, me.valueMin);
 +              return 1;
 +      }
 +      if(key == K_HOME || key == K_KP_HOME)
 +      {
 +              me.setValue(me, me.valueMin);
 +              return 1;
 +      }
 +      if(key == K_END || key == K_KP_END)
 +      {
 +              me.setValue(me, me.valueMax);
 +              return 1;
 +      }
-       if(cvar("menu_sounds"))
-               localsound("sound/misc/menu2.wav");
++      // TODO more keys (NOTE also add them to Slider_keyUp)
++      return 0;
++}
++float Slider_keyUp(entity me, float key, float ascii, float shift)
++{
++      if(me.disabled)
++              return 0;
++      switch(key)
++      {
++              case K_LEFTARROW:
++              case K_KP_LEFTARROW:
++              case K_RIGHTARROW:
++              case K_KP_RIGHTARROW:
++              case K_PGUP:
++              case K_KP_PGUP:
++              case K_PGDN:
++              case K_KP_PGDN:
++              case K_HOME:
++              case K_KP_HOME:
++              case K_END:
++              case K_KP_END:
++                      m_play_click_sound(MENU_SOUND_SLIDE);
++      }
 +      return 0;
 +}
 +float Slider_mouseDrag(entity me, vector pos)
 +{
 +      float hit;
 +      float v, animed;
 +      if(me.disabled)
 +              return 0;
 +
 +      anim.removeObjAnim(anim, me);
 +      animed = me.animated;
 +      me.animated = false;
 +
 +      if(me.pressed)
 +      {
 +              hit = 1;
 +              if(pos.x < 0 - me.tolerance.x) hit = 0;
 +              if(pos.y < 0 - me.tolerance.y) hit = 0;
 +              if(pos.x >= 1 - me.textSpace + me.tolerance.x) hit = 0;
 +              if(pos.y >= 1 + me.tolerance.y) hit = 0;
 +              if(hit)
 +              {
 +                      v = median(0, (pos.x - me.pressOffset - 0.5 * me.controlWidth) / (1 - me.textSpace - me.controlWidth), 1) * (me.valueMax - me.valueMin) + me.valueMin;
 +                      if(me.valueStep)
 +                              v = floor(0.5 + v / me.valueStep) * me.valueStep;
 +                      me.setValue(me, v);
 +              }
 +              else
 +                      me.setValue(me, me.previousValue);
 +      }
 +
 +      me.animated = animed;
 +
 +      return 1;
 +}
 +float Slider_mousePress(entity me, vector pos)
 +{
 +      float controlCenter;
 +      if(me.disabled)
 +              return 0;
 +      if(pos.x < 0) return 0;
 +      if(pos.y < 0) return 0;
 +      if(pos.x >= 1 - me.textSpace) return 0;
 +      if(pos.y >= 1) return 0;
 +      controlCenter = (me.value - me.valueMin) / (me.valueMax - me.valueMin) * (1 - me.textSpace - me.controlWidth) + 0.5 * me.controlWidth;
 +      if(fabs(pos.x - controlCenter) <= 0.5 * me.controlWidth)
 +      {
 +              me.pressed = 1;
 +              me.pressOffset = pos.x - controlCenter;
 +              me.previousValue = me.value;
 +              //me.mouseDrag(me, pos);
 +      }
 +      else
 +      {
 +              float clickValue, pageValue, inRange;
 +              clickValue = median(0, (pos.x - me.pressOffset - 0.5 * me.controlWidth) / (1 - me.textSpace - me.controlWidth), 1) * (me.valueMax - me.valueMin) + me.valueMin;
 +              inRange = (almost_in_bounds(me.valueMin, me.value, me.valueMax));
 +              if(pos.x < controlCenter)
 +              {
 +                      pageValue = me.value - me.valuePageStep;
 +                      if(me.valueStep)
 +                              clickValue = floor(clickValue / me.valueStep) * me.valueStep;
 +                      pageValue = max(pageValue, clickValue);
 +                      if(inRange)
 +                              me.setValue(me, median(me.valueMin, pageValue, me.valueMax));
 +                      else
 +                              me.setValue(me, me.valueMax);
 +              }
 +              else
 +              {
 +                      pageValue = me.value + me.valuePageStep;
 +                      if(me.valueStep)
 +                              clickValue = ceil(clickValue / me.valueStep) * me.valueStep;
 +                      pageValue = min(pageValue, clickValue);
 +                      if(inRange)
 +                              me.setValue(me, median(me.valueMin, pageValue, me.valueMax));
 +                      else
 +                              me.setValue(me, me.valueMax);
 +              }
 +              if(pageValue == clickValue)
 +              {
 +                      controlCenter = (me.value - me.valueMin) / (me.valueMax - me.valueMin) * (1 - me.textSpace - me.controlWidth) + 0.5 * me.controlWidth;
 +                      me.pressed = 1;
 +                      me.pressOffset = pos.x - controlCenter;
 +                      me.previousValue = me.value;
 +                      //me.mouseDrag(me, pos);
 +              }
 +      }
 +      return 1;
 +}
 +float Slider_mouseRelease(entity me, vector pos)
 +{
 +      me.pressed = 0;
 +      if(me.disabled)
 +              return 0;
- void Slider_focusEnter(entity me)
- {
-       if(cvar("menu_sounds") > 1)
-               localsound("sound/misc/menu1.wav");
-       SUPER(Slider).focusEnter(me);
- }
++      m_play_click_sound(MENU_SOUND_SLIDE);
 +      return 1;
 +}
 +void Slider_showNotify(entity me)
 +{
 +      me.focusable = !me.disabled;
 +}
 +void Slider_draw(entity me)
 +{
 +      float controlLeft;
 +      float save;
 +      me.focusable = !me.disabled;
 +      save = draw_alpha;
 +      if(me.disabled)
 +              draw_alpha *= me.disabledAlpha;
 +      draw_ButtonPicture('0 0 0', strcat(me.src, "_s"), eX * (1 - me.textSpace) + eY, me.color2, 1);
 +      if(almost_in_bounds(me.valueMin, me.sliderValue, me.valueMax))
 +      {
 +              controlLeft = (me.sliderValue - me.valueMin) / (me.valueMax - me.valueMin) * (1 - me.textSpace - me.controlWidth);
 +              if(me.disabled)
 +                      draw_Picture(eX * controlLeft, strcat(me.src, "_d"), eX * me.controlWidth + eY, me.colorD, 1);
 +              else if(me.pressed)
 +                      draw_Picture(eX * controlLeft, strcat(me.src, "_c"), eX * me.controlWidth + eY, me.colorC, 1);
 +              else if(me.focused)
 +                      draw_Picture(eX * controlLeft, strcat(me.src, "_f"), eX * me.controlWidth + eY, me.colorF, 1);
 +              else
 +                      draw_Picture(eX * controlLeft, strcat(me.src, "_n"), eX * me.controlWidth + eY, me.color, 1);
 +      }
 +      me.setText(me, me.valueToText(me, me.value));
 +      draw_alpha = save;
 +      SUPER(Slider).draw(me);
 +      me.text = string_null; // TEMPSTRING!
 +}
 +#endif
diff --combined qcsrc/menu/menu.qc
index e9f3538ff5d2ec968f6c270a4441b5da31d9d1eb,85aa3a640e319a055b56926f053ec58d72c12558..e348796a0f89747b89e16fa315684d4ae0edddc7
@@@ -1,8 -1,3 +1,8 @@@
 +#if defined(CSQC)
 +#elif defined(MENUQC)
 +#elif defined(SVQC)
 +#endif
 +
  ///////////////////////////////////////////////
  // Menu Source File
  ///////////////////////
@@@ -12,7 -7,7 +12,7 @@@
  
  float mouseButtonsPressed;
  vector menuMousePos;
 -float menuShiftState;
 +int menuShiftState;
  float menuPrevTime;
  float menuAlpha;
  float menuLogoAlpha;
@@@ -199,7 -194,7 +199,7 @@@ void m_init_delayed(
        }
        fclose(fh);
  
 -      glob = search_begin(strcat(draw_currentSkin, "/*.tga"), TRUE, TRUE);
 +      glob = search_begin(strcat(draw_currentSkin, "/*.tga"), true, true);
        if(glob >= 0)
        {
                n = search_getsize(glob);
@@@ -269,7 -264,7 +269,7 @@@ void m_keydown(float key, float ascii
        {
                // detect a click outside of the game window
                vector p = getmousepos();
 -              if(p_x < 0 || p_x > realconwidth || p_y < 0 || p_y > realconheight)
 +              if(p.x < 0 || p.x > realconwidth || p.y < 0 || p.y > realconheight)
                {
                        ++mouseButtonsPressed;
                        return;
@@@ -319,10 -314,13 +319,10 @@@ void draw_Picture_Aligned(vector algn, 
        float width_is_larger;
  
        sz = draw_PictureSize(img);
 -      width_is_larger = (sz_x * draw_scale_y >= sz_y * draw_scale_x);
 -      isz_w = '1 0 0' + '0 1 0' * ((sz_y / sz_x) * (draw_scale_x / draw_scale_y));
 -      isz_h = '0 1 0' + '1 0 0' * ((sz_x / sz_y) * (draw_scale_y / draw_scale_x));
 +      width_is_larger = (sz.x * draw_scale.y >= sz.y * draw_scale.x);
 +      isz_w = '1 0 0' + '0 1 0' * ((sz.y / sz.x) * (draw_scale.x / draw_scale.y));
 +      isz_h = '0 1 0' + '1 0 0' * ((sz.x / sz.y) * (draw_scale.y / draw_scale.x));
  
 -#ifdef GMQCC
 -      isz = '0 0 0';
 -#endif
        switch(scalemode)
        {
                default:
                        break;
        }
  
 -      org = eX * (algn_x * (1 - isz_x)) + eY * (algn_y * (1 - isz_y));
 +      org = eX * (algn.x * (1 - isz.x)) + eY * (algn.y * (1 - isz.y));
        draw_Picture(org, img, isz, '1 1 1', a);
  }
  
@@@ -357,7 -355,7 +357,7 @@@ void drawBackground(string img, float a
        string c;
        float scalemode;
  
 -      v_z = 0;
 +      v.z = 0;
  
        scalemode = SCALEMODE_CROP;
  
                        case "h": scalemode = SCALEMODE_HEIGHT; goto nopic;
                        case "w": scalemode = SCALEMODE_WIDTH; goto nopic;
                        case "s": scalemode = SCALEMODE_STRETCH; goto nopic;
 -                      case "1": case "4": case "7": v_x = 0.0; break;
 -                      case "2": case "5": case "8": v_x = 0.5; break;
 -                      case "3": case "6": case "9": v_x = 1.0; break;
 -                      default: v_x = random(); break;
 +                      case "1": case "4": case "7": v.x = 0.0; break;
 +                      case "2": case "5": case "8": v.x = 0.5; break;
 +                      case "3": case "6": case "9": v.x = 1.0; break;
 +                      default: v.x = random(); break;
                }
                switch(c)
                {
 -                      case "7": case "8": case "9": v_y = 0.0; break;
 -                      case "4": case "5": case "6": v_y = 0.5; break;
 -                      case "1": case "2": case "3": v_y = 1.0; break;
 -                      default: v_y = random(); break;
 +                      case "7": case "8": case "9": v.y = 0.0; break;
 +                      case "4": case "5": case "6": v.y = 0.5; break;
 +                      case "1": case "2": case "3": v.y = 1.0; break;
 +                      default: v.y = random(); break;
                }
                if(l == 0)
                        draw_Picture_Aligned(v, scalemode, img, a);
@@@ -409,68 -407,68 +409,68 @@@ string menuTooltipText
  float menuTooltipState; // 0: static, 1: fading in, 2: fading out
  float m_testmousetooltipbox(vector pos)
  {
 -      if(pos_x >= menuTooltipOrigin_x && pos_x < menuTooltipOrigin_x + menuTooltipSize_x)
 -      if(pos_y >= menuTooltipOrigin_y && pos_y < menuTooltipOrigin_y + menuTooltipSize_y)
 -              return FALSE;
 -      return TRUE;
 +      if(pos.x >= menuTooltipOrigin.x && pos.x < menuTooltipOrigin.x + menuTooltipSize.x)
 +      if(pos.y >= menuTooltipOrigin.y && pos.y < menuTooltipOrigin.y + menuTooltipSize.y)
 +              return false;
 +      return true;
  }
  float m_testtooltipbox(vector tooltippos)
  {
 -      if(tooltippos_x < 0)
 -              return FALSE;
 -      if(tooltippos_y < 0)
 -              return FALSE;
 -      if(tooltippos_x + menuTooltipSize_x > 1)
 -              return FALSE;
 -      if(tooltippos_y + menuTooltipSize_y > 1)
 -              return FALSE;
 +      if(tooltippos.x < 0)
 +              return false;
 +      if(tooltippos.y < 0)
 +              return false;
 +      if(tooltippos.x + menuTooltipSize.x > 1)
 +              return false;
 +      if(tooltippos.y + menuTooltipSize.y > 1)
 +              return false;
        menuTooltipOrigin = tooltippos;
 -      return TRUE;
 +      return true;
  }
  float m_allocatetooltipbox(vector pos)
  {
        vector avoidplus, avoidminus;
        vector v;
  
 -      avoidplus_x = (SKINAVOID_TOOLTIP_x + SKINSIZE_CURSOR_x - SKINOFFSET_CURSOR_x * SKINSIZE_CURSOR_x) / conwidth;
 -      avoidplus_y = (SKINAVOID_TOOLTIP_y + SKINSIZE_CURSOR_y - SKINOFFSET_CURSOR_y * SKINSIZE_CURSOR_y) / conheight;
 -      avoidplus_z = 0;
 +      avoidplus.x = (SKINAVOID_TOOLTIP_x + SKINSIZE_CURSOR_x - SKINOFFSET_CURSOR_x * SKINSIZE_CURSOR_x) / conwidth;
 +      avoidplus.y = (SKINAVOID_TOOLTIP_y + SKINSIZE_CURSOR_y - SKINOFFSET_CURSOR_y * SKINSIZE_CURSOR_y) / conheight;
 +      avoidplus.z = 0;
  
 -      avoidminus_x = (SKINAVOID_TOOLTIP_x + SKINOFFSET_CURSOR_x * SKINSIZE_CURSOR_x) / conwidth + menuTooltipSize_x;
 -      avoidminus_y = (SKINAVOID_TOOLTIP_y + SKINOFFSET_CURSOR_y * SKINSIZE_CURSOR_y) / conheight + menuTooltipSize_y;
 -      avoidminus_z = 0;
 +      avoidminus.x = (SKINAVOID_TOOLTIP_x + SKINOFFSET_CURSOR_x * SKINSIZE_CURSOR_x) / conwidth + menuTooltipSize.x;
 +      avoidminus.y = (SKINAVOID_TOOLTIP_y + SKINOFFSET_CURSOR_y * SKINSIZE_CURSOR_y) / conheight + menuTooltipSize.y;
 +      avoidminus.z = 0;
  
        // bottom right
        v = pos + avoidplus;
        if(m_testtooltipbox(v))
 -              return TRUE;
 +              return true;
  
        // bottom center
 -      v_x = pos_x - menuTooltipSize_x * 0.5;
 +      v.x = pos.x - menuTooltipSize.x * 0.5;
        if(m_testtooltipbox(v))
 -              return TRUE;
 +              return true;
  
        // bottom left
 -      v_x = pos_x - avoidminus_x;
 +      v.x = pos.x - avoidminus.x;
        if(m_testtooltipbox(v))
 -              return TRUE;
 +              return true;
  
        // top left
 -      v_y = pos_y - avoidminus_y;
 +      v.y = pos.y - avoidminus.y;
        if(m_testtooltipbox(v))
 -              return TRUE;
 +              return true;
  
        // top center
 -      v_x = pos_x - menuTooltipSize_x * 0.5;
 +      v.x = pos.x - menuTooltipSize.x * 0.5;
        if(m_testtooltipbox(v))
 -              return TRUE;
 +              return true;
  
        // top right
 -      v_x = pos_x + avoidplus_x;
 +      v.x = pos.x + avoidplus.x;
        if(m_testtooltipbox(v))
 -              return TRUE;
 +              return true;
  
 -      return FALSE;
 +      return false;
  }
  entity m_findtooltipitem(entity root, vector pos)
  {
@@@ -578,7 -576,7 +578,7 @@@ void m_tooltip(vector pos
                                        menuTooltipState = 1;
                                        menuTooltipItem = it;
  
 -                                      menuTooltipOrigin_x = -1; // unallocated
 +                                      menuTooltipOrigin.x = -1; // unallocated
  
                                        if (menuTooltipText)
                                                strunzone(menuTooltipText);
                                        {
                                                s = getWrappedLine(SKINWIDTH_TOOLTIP, fontsize, draw_TextWidth_WithoutColors);
                                                ++i;
 -                                              f = draw_TextWidth(s, FALSE, fontsize);
 +                                              f = draw_TextWidth(s, false, fontsize);
                                                if(f > w)
                                                        w = f;
                                        }
 -                                      menuTooltipSize_x = w + 2 * (SKINMARGIN_TOOLTIP_x / conwidth);
 -                                      menuTooltipSize_y = i * fontsize_y + 2 * (SKINMARGIN_TOOLTIP_y / conheight);
 -                                      menuTooltipSize_z = 0;
 +                                      menuTooltipSize.x = w + 2 * (SKINMARGIN_TOOLTIP_x / conwidth);
 +                                      menuTooltipSize.y = i * fontsize.y + 2 * (SKINMARGIN_TOOLTIP_y / conheight);
 +                                      menuTooltipSize.z = 0;
                                }
                                break;
                        case 1:
                                menuTooltipItem = world; // reload tooltip next frame
                        menu_tooltips_old = menu_tooltips;
                }
 -              else if(menuTooltipOrigin_x < 0) // unallocated?
 +              else if(menuTooltipOrigin.x < 0) // unallocated?
                        m_allocatetooltipbox(pos);
  
 -              if(menuTooltipOrigin_x >= 0)
 +              if(menuTooltipOrigin.x >= 0)
                {
                        // draw the tooltip!
                        p = SKINBORDER_TOOLTIP;
 -                      p_x *= 1 / conwidth;
 -                      p_y *= 1 / conheight;
 +                      p.x *= 1 / conwidth;
 +                      p.y *= 1 / conheight;
                        draw_BorderPicture(menuTooltipOrigin, SKINGFX_TOOLTIP, menuTooltipSize, '1 1 1', menuTooltipAlpha, p);
                        p = menuTooltipOrigin;
 -                      p_x += SKINMARGIN_TOOLTIP_x / conwidth;
 -                      p_y += SKINMARGIN_TOOLTIP_y / conheight;
 +                      p.x += SKINMARGIN_TOOLTIP_x / conwidth;
 +                      p.y += SKINMARGIN_TOOLTIP_y / conheight;
                        getWrappedLine_remaining = menuTooltipText;
                        while(getWrappedLine_remaining)
                        {
                                s = getWrappedLine(SKINWIDTH_TOOLTIP, fontsize, draw_TextWidth_WithoutColors);
 -                              draw_Text(p, s, fontsize, SKINCOLOR_TOOLTIP, SKINALPHA_TOOLTIP * menuTooltipAlpha, FALSE);
 -                              p_y += fontsize_y;
 +                              draw_Text(p, s, fontsize, SKINCOLOR_TOOLTIP, SKINALPHA_TOOLTIP * menuTooltipAlpha, false);
 +                              p.y += fontsize.y;
                        }
                }
        }
@@@ -761,7 -759,7 +761,7 @@@ void m_draw(float width, float height
                {
                        draw_reset_full();
                        draw_Fill('0 0 0', '1 1 0', SKINCOLOR_BACKGROUND, 1);
 -                      drawBackground(SKINGFX_BACKGROUND, bound(0, menuLogoAlpha, 1), SKINALIGN_BACKGROUND, TRUE);
 +                      drawBackground(SKINGFX_BACKGROUND, bound(0, menuLogoAlpha, 1), SKINALIGN_BACKGROUND, true);
                        draw_reset_cropped();
                        if(menuAlpha <= 0 && SKINALPHA_CURSOR_INTRO > 0)
                        {
                if(menuAlpha > 0)
                {
                        draw_reset_full();
 -                      drawBackground(SKINGFX_BACKGROUND_INGAME, menuAlpha * SKINALPHA_BACKGROUND_INGAME, SKINALIGN_BACKGROUND_INGAME, FALSE);
 +                      drawBackground(SKINGFX_BACKGROUND_INGAME, menuAlpha * SKINALPHA_BACKGROUND_INGAME, SKINALIGN_BACKGROUND_INGAME, false);
                        draw_reset_cropped();
                }
        }
                                maxpos = globalToBox(eX * (realconwidth - 1) + eY * (realconheight - 1), draw_shift, draw_scale);
                                dMouse = globalToBoxSize(dMouse, draw_scale);
                                menuMousePos += dMouse * cvar("menu_mouse_speed");
 -                              menuMousePos_x = bound(minpos_x, menuMousePos_x, maxpos_x);
 -                              menuMousePos_y = bound(minpos_y, menuMousePos_y, maxpos_y);
 +                              menuMousePos.x = bound(minpos.x, menuMousePos.x, maxpos.x);
 +                              menuMousePos.y = bound(minpos.y, menuMousePos.y, maxpos.y);
                                if(mouseButtonsPressed)
                                        main.mouseDrag(main, menuMousePos);
                                else
@@@ -958,8 -956,8 +958,8 @@@ void m_setpointerfocus(entity wnd
                if(focus)
                {
                        menuMousePos = focus.origin + 0.5 * focus.size;
 -                      menuMousePos_x *= 1 / conwidth;
 -                      menuMousePos_y *= 1 / conheight;
 +                      menuMousePos.x *= 1 / conwidth;
 +                      menuMousePos.y *= 1 / conheight;
                        if(wnd.focused) // why does this never happen?
                                m_focus_item_chain(wnd, focus);
                }
@@@ -1003,3 -1001,20 +1003,20 @@@ void m_goto(string itemname
                }
        }
  }
+ float menuLastFocusSoundTime;
+ void m_play_focus_sound()
+ {
+       if(cvar("menu_sounds") > 1)
+               if(time - menuLastFocusSoundTime > 0.25)
+               {
+                       localsound(MENU_SOUND_FOCUS);
+                       menuLastFocusSoundTime = time;
+               }
+ }
+ void m_play_click_sound(string soundfile)
+ {
+       if(cvar("menu_sounds"))
+               localsound(soundfile);
+ }
diff --combined qcsrc/menu/menu.qh
index 2aa2b5d6c95074d5a147f3266e29ff18369b2eae,6f36a074e05311361c71b174afa796a03adeb02b..00f6017aa06b3bdf4b2143fbd132cd4dac4885e3
@@@ -1,17 -1,3 +1,17 @@@
 +#ifndef MENU_H
 +#define MENU_H
 +
 +#include "draw.qh"
 +#include "skin.qh"
 +
 +#include "oo/base.qh"
 +
 +#include "xonotic/util.qh"
 +
 +#include "../common/constants.qh"
 +#include "../common/test.qh"
 +#include "../common/util.qh"
 +
  #define localcmd cmd
  
  #define NULL (null_entity)
  
  // constants
  
 -const float GAME_ISSERVER     = 1;
 -const float GAME_CONNECTED    = 2;
 -const float GAME_DEVELOPER    = 4;
 +const int GAME_ISSERVER       = 1;
 +const int GAME_CONNECTED      = 2;
 +const int GAME_DEVELOPER      = 4;
  
  // prototypes
  
  float Menu_Active;
 -float gamestatus;
 +int gamestatus;
  
 -const float S_SHIFT = 1;
 -const float S_CTRL = 2;
 -const float S_ALT = 4;
 +const int S_SHIFT = 1;
 +const int S_CTRL = 2;
 +const int S_ALT = 4;
  
  float frametime;
  float time;
@@@ -52,4 -38,17 +52,18 @@@ void preMenuDraw(); // this is run befo
  void postMenuDraw(); // this is run just after the menu is drawn (or not). Useful to draw something over everything else.
  
  void m_sync();
+ // sounds
+ const string MENU_SOUND_CLEAR   = "sound/menu/clear.wav";
+ const string MENU_SOUND_CLOSE   = "sound/menu/close.wav";
+ const string MENU_SOUND_EXECUTE = "sound/menu/execute.wav";
+ const string MENU_SOUND_FOCUS   = "sound/menu/focus.wav";
+ const string MENU_SOUND_OPEN    = "sound/menu/open.wav";
+ const string MENU_SOUND_SELECT  = "sound/menu/select.wav";
+ const string MENU_SOUND_SLIDE   = "sound/menu/slide.wav";
+ const string MENU_SOUND_WINNER  = "sound/menu/winner.wav";
+ void m_play_focus_sound();
+ void m_play_click_sound(string soundfile);
 +#endif
index 6634728f30103573984c3f44ae7c36af3126fec1,0000000000000000000000000000000000000000..f16ab0ebd45fd0a16a0f941e06cfb9766941fe69
mode 100644,000000..100644
--- /dev/null
@@@ -1,174 -1,0 +1,175 @@@
 +#ifdef INTERFACE
 +CLASS(XonoticColorpicker) EXTENDS(Image)
 +      METHOD(XonoticColorpicker, configureXonoticColorpicker, void(entity, entity))
 +      METHOD(XonoticColorpicker, mousePress, float(entity, vector))
 +      METHOD(XonoticColorpicker, mouseRelease, float(entity, vector))
 +      METHOD(XonoticColorpicker, mouseDrag, float(entity, vector))
 +      ATTRIB(XonoticColorpicker, controlledTextbox, entity, NULL)
 +      ATTRIB(XonoticColorpicker, image, string, SKINGFX_COLORPICKER)
 +      ATTRIB(XonoticColorpicker, imagemargin, vector, SKINMARGIN_COLORPICKER)
 +      ATTRIB(XonoticColorpicker, focusable, float, 1)
 +      METHOD(XonoticColorpicker, focusLeave, void(entity))
 +      METHOD(XonoticColorpicker, keyDown, float(entity, float, float, float))
 +      METHOD(XonoticColorpicker, draw, void(entity))
 +ENDCLASS(XonoticColorpicker)
 +entity makeXonoticColorpicker(entity theTextbox);
 +#endif
 +
 +#ifdef IMPLEMENTATION
 +entity makeXonoticColorpicker(entity theTextbox)
 +{
 +      entity me;
 +      me = spawnXonoticColorpicker();
 +      me.configureXonoticColorpicker(me, theTextbox);
 +      return me;
 +}
 +
 +void XonoticColorpicker_configureXonoticColorpicker(entity me, entity theTextbox)
 +{
 +      me.controlledTextbox = theTextbox;
 +      me.configureImage(me, me.image);
 +}
 +
 +float XonoticColorpicker_mousePress(entity me, vector coords)
 +{
 +      me.mouseDrag(me, coords);
 +      return 1;
 +}
 +
 +// must match hslimage.c
 +vector hslimage_color(vector v, vector margin)
 +{
 +    v_x = (v.x - margin.x) / (1 - 2 * margin.x);
 +    v_y = (v.y - margin.y) / (1 - 2 * margin.y);
 +    if(v.x < 0) v_x = 0;
 +    if(v.y < 0) v_y = 0;
 +    if(v.x > 1) v_x = 1;
 +    if(v.y > 1) v_y = 1;
 +    if(v.y > 0.875) // grey bar
 +        return hsl_to_rgb(eZ * v.x);
 +    else
 +        return hsl_to_rgb(v.x * 6 * eX + eY + v.y / 0.875 * eZ);
 +}
 +
 +vector color_hslimage(vector v, vector margin)
 +{
 +      vector pos = '0 0 0';
 +      v = rgb_to_hsl(v);
 +      if (v.y)
 +      {
 +              pos_x = v.x / 6;
 +              pos_y = v.z * 0.875;
 +      }
 +      else // grey scale
 +      {
 +              pos_x = v.z;
 +              pos_y = 0.875 + 0.07;
 +      }
 +      pos_x = margin.x + pos.x * (1 - 2 * margin.x);
 +      pos_y = margin.y + pos.y * (1 - 2 * margin.y);
 +      return pos;
 +}
 +
 +float XonoticColorpicker_mouseDrag(entity me, vector coords)
 +{
 +      float i, carets;
 +      for (;;)
 +      {
 +              i = me.controlledTextbox.cursorPos;
 +              if(i >= 2)
 +              {
 +                      if(substring(me.controlledTextbox.text, i-2, 1) == "^")
 +                      {
 +                              carets = 1;
 +                              while (i - 2 - carets >= 0 && substring(me.controlledTextbox.text, i - 2 - carets, 1) == "^")
 +                                      ++carets;
 +                              if (carets & 1)
 +                                      if(strstrofs("0123456789", substring(me.controlledTextbox.text, i-1, 1), 0) >= 0)
 +                                      {
 +                                              me.controlledTextbox.keyDown(me.controlledTextbox, K_BACKSPACE, 8, 0);
 +                                              me.controlledTextbox.keyDown(me.controlledTextbox, K_BACKSPACE, 8, 0);
 +                                              continue;
 +                                      }
 +                      }
 +              }
 +
 +              if(i >= 5)
 +              {
 +                      if(substring(me.controlledTextbox.text, i-5, 2) == "^x")
 +                      {
 +                              carets = 1;
 +                              while (i - 5 - carets >= 0 && substring(me.controlledTextbox.text, i - 5 - carets, 1) == "^")
 +                                      ++carets;
 +                              if (carets & 1)
 +                                      if(strstrofs("0123456789abcdefABCDEF", substring(me.controlledTextbox.text, i-3, 1), 0) >= 0)
 +                                              if(strstrofs("0123456789abcdefABCDEF", substring(me.controlledTextbox.text, i-2, 1), 0) >= 0)
 +                                                      if(strstrofs("0123456789abcdefABCDEF", substring(me.controlledTextbox.text, i-1, 1), 0) >= 0)
 +                                                      {
 +                                                              me.controlledTextbox.keyDown(me.controlledTextbox, K_BACKSPACE, 8, 0);
 +                                                              me.controlledTextbox.keyDown(me.controlledTextbox, K_BACKSPACE, 8, 0);
 +                                                              me.controlledTextbox.keyDown(me.controlledTextbox, K_BACKSPACE, 8, 0);
 +                                                              me.controlledTextbox.keyDown(me.controlledTextbox, K_BACKSPACE, 8, 0);
 +                                                              me.controlledTextbox.keyDown(me.controlledTextbox, K_BACKSPACE, 8, 0);
 +                                                              continue;
 +                                                      }
 +                      }
 +              }
 +              break;
 +      }
 +
 +      if(substring(me.controlledTextbox.text, i-1, 1) == "^")
 +      {
 +              carets = 1;
 +              while (i - 1 - carets >= 0 && substring(me.controlledTextbox.text, i - 1 - carets, 1) == "^")
 +                      ++carets;
 +              if (carets & 1)
 +                      me.controlledTextbox.enterText(me.controlledTextbox, "^"); // escape previous caret
 +      }
 +
 +      vector margin;
 +      margin = me.imagemargin;
 +      if(coords.x >= margin.x)
 +      if(coords.y >= margin.y)
 +      if(coords.x <= 1 - margin.x)
 +      if(coords.y <= 1 - margin.y)
 +              me.controlledTextbox.enterText(me.controlledTextbox, rgb_to_hexcolor(hslimage_color(coords, margin)));
 +
 +      return 1;
 +}
 +
 +float XonoticColorpicker_mouseRelease(entity me, vector coords)
 +{
++      m_play_click_sound(MENU_SOUND_SLIDE);
 +      me.mouseDrag(me, coords);
 +      return 1;
 +}
 +
 +void XonoticColorpicker_focusLeave(entity me)
 +{
 +      me.controlledTextbox.saveCvars(me.controlledTextbox);
 +}
 +float XonoticColorpicker_keyDown(entity me, float key, float ascii, float shift)
 +{
 +      return me.controlledTextbox.keyDown(me.controlledTextbox, key, ascii, shift);
 +}
 +void XonoticColorpicker_draw(entity me)
 +{
 +      SUPER(XonoticColorpicker).draw(me);
 +
 +      float B, C, aC;
 +      C = cvar("r_textcontrast");
 +      B = cvar("r_textbrightness");
 +
 +      // for this to work, C/(1-B) must be in 0..1
 +      // B must be < 1
 +      // C must be < 1-B
 +
 +      B = bound(0, B, 1);
 +      C = bound(0, C, 1-B);
 +
 +      aC = 1 - C / (1 - B);
 +
 +      draw_Picture(me.imgOrigin, strcat(me.src, "_m"), me.imgSize, '0 0 0', aC);
 +      draw_Picture(me.imgOrigin, strcat(me.src, "_m"), me.imgSize, me.color, B);
 +}
 +#endif
index f0e3e6da09e234c962b0d14f8520a2327f4ae32c,0000000000000000000000000000000000000000..458a72c01a6fe1dd47ced6e06c2dffb4ddfe41e3
mode 100644,000000..100644
--- /dev/null
@@@ -1,122 -1,0 +1,123 @@@
 +#ifdef INTERFACE
 +CLASS(XonoticColorpickerString) EXTENDS(Image)
 +      METHOD(XonoticColorpickerString, configureXonoticColorpickerString, void(entity, string, string))
 +      METHOD(XonoticColorpickerString, mousePress, float(entity, vector))
 +      METHOD(XonoticColorpickerString, mouseRelease, float(entity, vector))
 +      METHOD(XonoticColorpickerString, mouseDrag, float(entity, vector))
 +      ATTRIB(XonoticColorpickerString, cvarName, string, string_null)
 +      METHOD(XonoticColorPickerString, loadCvars, void(entity))
 +      METHOD(XonoticColorPickerString, saveCvars, void(entity))
 +      ATTRIB(XonoticColorpickerString, prevcoords, vector, '0 0 0')
 +      ATTRIB(XonoticColorpickerString, image, string, SKINGFX_COLORPICKER)
 +      ATTRIB(XonoticColorpickerString, imagemargin, vector, SKINMARGIN_COLORPICKER)
 +      ATTRIB(XonoticColorpickerString, focusable, float, 1)
 +      METHOD(XonoticColorpickerString, draw, void(entity))
 +      ATTRIB(XonoticColorpickerString, disabledAlpha, float, 0.3)
 +ENDCLASS(XonoticColorpickerString)
 +entity makeXonoticColorpickerString(string theCvar, string theDefaultCvar);
 +#endif
 +
 +#ifdef IMPLEMENTATION
 +entity makeXonoticColorpickerString(string theCvar, string theDefaultCvar)
 +{
 +      entity me;
 +      me = spawnXonoticColorpickerString();
 +      me.configureXonoticColorpickerString(me, theCvar, theDefaultCvar);
 +      return me;
 +}
 +
 +void XonoticColorpickerString_configureXonoticColorpickerString(entity me, string theCvar, string theDefaultCvar)
 +{
 +      me.cvarName = theCvar;
 +      me.configureImage(me, me.image);
 +      if(theCvar)
 +      {
 +              me.cvarName = theCvar;
 +              me.tooltip = getZonedTooltipForIdentifier(theCvar);
 +              me.loadCvars(me);
 +      }
 +}
 +
 +void XonoticColorPickerString_loadCvars(entity me)
 +{
 +      if (!me.cvarName)
 +              return;
 +
 +      if(substring(me.cvarName, -1, 1) == "_")
 +      {
 +              me.prevcoords = color_hslimage(
 +                      eX * cvar(strcat(me.cvarName, "red")) +
 +                      eY * cvar(strcat(me.cvarName, "green")) +
 +                      eZ * cvar(strcat(me.cvarName, "blue")),
 +                      me.imagemargin);
 +      }
 +      else
 +              me.prevcoords = color_hslimage(stov(cvar_string(me.cvarName)), me.imagemargin);
 +}
 +
 +void XonoticColorPickerString_saveCvars(entity me)
 +{
 +      if (!me.cvarName)
 +              return;
 +
 +      if(substring(me.cvarName, -1, 1) == "_")
 +      {
 +              vector v = hslimage_color(me.prevcoords, me.imagemargin);
 +              cvar_set(strcat(me.cvarName, "red"), ftos(v.x));
 +              cvar_set(strcat(me.cvarName, "green"), ftos(v.y));
 +              cvar_set(strcat(me.cvarName, "blue"), ftos(v.z));
 +      }
 +      else
 +              cvar_set(me.cvarName, sprintf("%v", hslimage_color(me.prevcoords, me.imagemargin)));
 +}
 +
 +float XonoticColorpickerString_mousePress(entity me, vector coords)
 +{
 +      me.mouseDrag(me, coords);
 +      return 1;
 +}
 +
 +float XonoticColorpickerString_mouseDrag(entity me, vector coords)
 +{
 +      if(me.disabled)
 +              return 0;
 +      vector margin;
 +      margin = me.imagemargin;
 +      if(coords.x >= margin.x)
 +      if(coords.y >= margin.y)
 +      if(coords.x <= 1 - margin.x)
 +      if(coords.y <= 1 - margin.y)
 +      {
 +              me.prevcoords = coords;
 +              me.saveCvars(me);
 +      }
 +
 +      return 1;
 +}
 +
 +float XonoticColorpickerString_mouseRelease(entity me, vector coords)
 +{
++      m_play_click_sound(MENU_SOUND_SLIDE);
 +      me.mouseDrag(me, coords);
 +      return 1;
 +}
 +
 +void XonoticColorpickerString_draw(entity me)
 +{
 +      float save;
 +      save = draw_alpha;
 +      if(me.disabled)
 +              draw_alpha *= me.disabledAlpha;
 +
 +      SUPER(XonoticColorpickerString).draw(me);
 +
 +      vector sz;
 +      sz = draw_PictureSize(strcat(me.src, "_selected"));
 +      sz = globalToBoxSize(sz, draw_scale);
 +
 +      if(!me.disabled)
 +              draw_Picture(me.imgOrigin + me.prevcoords - 0.5 * sz, strcat(me.src, "_selected"), sz, '1 1 1', 1);
 +
 +      draw_alpha = save;
 +}
 +#endif
index bf891ac9f7ea962d283767ccb62f43e9ed513c2d,0000000000000000000000000000000000000000..1adfb015eff1d85e192034eab19ce93c93f351cb
mode 100644,000000..100644
--- /dev/null
@@@ -1,164 -1,0 +1,164 @@@
-               me.TD(me, 1, 2, e = makeXonoticTextSlider("cl_gender"));
 +#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)
 +      ATTRIB(XonoticProfileTab, columns, float, 6.1) // added extra .2 for center space
 +      ATTRIB(XonoticProfileTab, playerNameLabel, entity, NULL)
 +      ATTRIB(XonoticProfileTab, playerNameLabelAlpha, float, SKINALPHA_HEADER)
 +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;
 +
 +      // ==============
 +      //  NAME SECTION
 +      // ==============
 +      me.gotoRC(me, 0.5, 0);
 +              me.TD(me, 1, 3, me.playerNameLabel = makeXonoticHeaderLabel(_("Name")));
 +
 +      me.gotoRC(me, 1.5, 0);
 +              me.TD(me, 1, 3, label = makeXonoticTextLabel(0.5, string_null));
 +                      label.allowCut = 1;
 +                      label.allowColors = 1;
 +                      label.alpha = 1;
 +                      label.isBold = true;
 +                      label.fontSize = SKINFONTSIZE_TITLE;
 +
 +      me.gotoRC(me, 2.5, 0);
 +              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));
 +
 +      // ===============
 +      //  MODEL SECTION
 +      // ===============
 +      //me.gotoRC(me, 0.5, 3.1); me.setFirstColumn(me, me.currentColumn); // TOP RIGHT
 +      //me.gotoRC(me, 9, 3.1); me.setFirstColumn(me, me.currentColumn); // BOTTOM RIGHT
 +      me.gotoRC(me, 9, 0); me.setFirstColumn(me, me.currentColumn); // BOTTOM LEFT
 +              me.TD(me, 1, 3, e = makeXonoticHeaderLabel(_("Model")));
 +
 +      me.TR(me);
 +              //me.TDempty(me, 0); // MODEL LEFT, COLOR RIGHT
 +              me.TDempty(me, 1); // MODEL RIGHT, COLOR LEFT
 +              pms = makeXonoticPlayerModelSelector();
 +              me.TD(me, 1, 0.3, e = makeXonoticButton("<<", '0 0 0'));
 +                      e.onClick = PlayerModelSelector_Prev_Click;
 +                      e.onClickEntity = pms;
 +              me.TD(me, 11.5, 1.4, pms);
 +              me.TD(me, 1, 0.3, e = makeXonoticButton(">>", '0 0 0'));
 +                      e.onClick = PlayerModelSelector_Next_Click;
 +                      e.onClickEntity = pms;
 +
 +      //me.setFirstColumn(me, me.currentColumn + 2); // MODEL LEFT, COLOR RIGHT
 +      me.gotoRC(me, me.currentRow, 0); me.setFirstColumn(me, me.currentColumn); // MODEL RIGHT, COLOR LEFT
 +      me.TR(me);
 +              me.TD(me, 1, 1, e = makeXonoticHeaderLabel(_("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.TR(me);
 +      me.TR(me);
 +              me.TD(me, 1, 1, e = makeXonoticHeaderLabel(_("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');
 +              }
 +
 +      // ====================
 +      //  STATISTICS SECTION
 +      // ====================
 +      me.gotoRC(me, 0.5, 3.1); me.setFirstColumn(me, me.currentColumn); // TOP RIGHT
 +      //me.gotoRC(me, 9, 3.1); me.setFirstColumn(me, me.currentColumn); // BOTTOM RIGHT
 +      //me.gotoRC(me, 9, 0); me.setFirstColumn(me, me.currentColumn); // BOTTOM LEFT
 +              me.TD(me, 1, 3, e = makeXonoticHeaderLabel(_("Statistics")));
 +
 +      me.TR(me);
 +              me.TDempty(me, 0.25);
 +              me.TD(me, 1, 2.5, e = makeXonoticCheckBox(0, "cl_allow_uidtracking", _("Allow player statistics to track your client")));
 +      me.TR(me);
 +              me.TDempty(me, 0.25);
 +              me.TD(me, 1, 2.5, e = makeXonoticCheckBox(0, "cl_allow_uid2name", _("Allow player statistics to use your nickname")));
 +              setDependent(e, "cl_allow_uidtracking", 1, 1);
 +      me.gotoRC(me, 4, 3.1); // TOP RIGHT
 +      //me.gotoRC(me, 12.5, 3.1); // BOTTOM RIGHT
 +      //me.gotoRC(me, 12.5, 0); // BOTTOM LEFT
 +              me.TDempty(me, 0.25);
 +              me.TD(me, 9, 2.5, statslist = makeXonoticStatsList());
 +              //setDependent(statslist, "cl_allow_uidtracking", 1, 1);
 +
 +      // =================
 +      //  COUNTRY SECTION
 +      // =================
 +      me.gotoRC(me, 16, 3.1); me.setFirstColumn(me, me.currentColumn); // BOTTOM SECTION, TOP POS
 +      //me.gotoRC(me, 13.5, 3.1); me.setFirstColumn(me, me.currentColumn); // BOTTOM SECTION, TOP POS
 +      //me.gotoRC(me, 0.5, 3.1); me.setFirstColumn(me, me.currentColumn); // TOP SECTION, TOP POS
 +              me.TD(me, 1, 3, e = makeXonoticHeaderLabel(_("Country")));
 +
 +      me.TR(me);
 +              me.TDempty(me, 0.5);
 +              me.TD(me, 4.5, 2, e = makeXonoticLanguageList()); // todo: cl_country: create proper country list
 +
 +
 +      // ================
 +      //  GENDER SECTION
 +      // ================
 +      me.gotoRC(me, 13.5, 3.1); me.setFirstColumn(me, me.currentColumn); // BOTTOM SECTION, TOP POS
 +      //me.gotoRC(me, 19.5, 3.1); me.setFirstColumn(me, me.currentColumn); // BOTTOM SECTION, BOTTOM POS
 +      //me.gotoRC(me, 6.5, 3.1); me.setFirstColumn(me, me.currentColumn); // TOP SECTION, BOTTOM POS
 +      #if 0
 +              me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Gender:")));
-                       me.TD(me, 1, GENDERWIDTH_ITEM, e = makeXonoticRadioButton(3, "cl_gender", "2", _("Female")));
-                       me.TD(me, 1, GENDERWIDTH_ITEM, e = makeXonoticRadioButton(3, "cl_gender", "1", _("Male")));
-                       me.TD(me, 1, GENDERWIDTH_ITEM, e = makeXonoticRadioButton(3, "cl_gender", "0", _("Undisclosed")));
++              me.TD(me, 1, 2, e = makeXonoticTextSlider("_cl_gender"));
 +                      e.addValue(e, ZCTX(_("GENDER^Undisclosed")), "0");
 +                      e.addValue(e, ZCTX(_("GENDER^Female")), "1");
 +                      e.addValue(e, ZCTX(_("GENDER^Male")), "2");
 +                      e.configureXonoticTextSliderValues(e);
 +      #else
 +                      me.TD(me, 1, 3, e = makeXonoticHeaderLabel(_("Gender")));
 +              me.TR(me);
 +                      #define GENDERWIDTH_OFFSET 0.25
 +                      #define GENDERWIDTH_LENGTH 2.5
 +                      #define GENDERWIDTH_ITEM (GENDERWIDTH_LENGTH / 3)
 +                      me.TDempty(me, GENDERWIDTH_OFFSET);
++                      me.TD(me, 1, GENDERWIDTH_ITEM, e = makeXonoticRadioButton(3, "_cl_gender", "2", _("Female")));
++                      me.TD(me, 1, GENDERWIDTH_ITEM, e = makeXonoticRadioButton(3, "_cl_gender", "1", _("Male")));
++                      me.TD(me, 1, GENDERWIDTH_ITEM, e = makeXonoticRadioButton(3, "_cl_gender", "0", _("Undisclosed")));
 +      #endif
 +
 +      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 81624fdef0ddaf4e4aa8b461ff3189c4d60fbc0c,0000000000000000000000000000000000000000..cccaa2652818b5bbf91dc9bfe9c204a33f85bdcf
mode 100644,000000..100644
--- /dev/null
@@@ -1,166 -1,0 +1,170 @@@
-               me.TD(me, 1, 3, makeXonoticCheckBoxEx(2, 0, "menu_sounds", _("Menu sounds")));
 +#ifdef INTERFACE
 +CLASS(XonoticAudioSettingsTab) EXTENDS(XonoticTab)
 +      METHOD(XonoticAudioSettingsTab, fill, void(entity))
 +      ATTRIB(XonoticAudioSettingsTab, title, string, _("Audio"))
 +      ATTRIB(XonoticAudioSettingsTab, intendedWidth, float, 0.9)
 +      ATTRIB(XonoticAudioSettingsTab, rows, float, 15.5)
 +      ATTRIB(XonoticAudioSettingsTab, columns, float, 6.2) // added extra .2 for center space
++      ATTRIB(XonoticAudioSettingsTab, hiddenMenuSoundsSlider, entity, NULL)
 +ENDCLASS(XonoticAudioSettingsTab)
 +entity makeXonoticAudioSettingsTab();
 +#endif
 +
 +#ifdef IMPLEMENTATION
 +entity makeXonoticAudioSettingsTab()
 +{
 +      entity me;
 +      me = spawnXonoticAudioSettingsTab();
 +      me.configureDialog(me);
 +      return me;
 +}
 +
 +void XonoticAudioSettingsTab_fill(entity me)
 +{
 +      entity e, s;
 +
 +      me.TR(me);
 +              s = makeXonoticDecibelsSlider(-40, 0, 0.4, "mastervolume");
 +              me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Master:")));
 +              me.TD(me, 1, 2, s);
 +      me.TR(me);
 +              me.TDempty(me, 0.2);
 +              s = makeXonoticDecibelsSlider(-40, 0, 0.4, "bgmvolume");
 +              makeMulti(s, "snd_channel8volume");
 +              me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Music:")));
 +              me.TD(me, 1, 2, s);
 +              setDependentStringNotEqual(e, "mastervolume", "0");
 +              setDependentStringNotEqual(s, "mastervolume", "0");
 +      me.TR(me);
 +              me.TDempty(me, 0.2);
 +              s = makeXonoticDecibelsSlider(-40, 0, 0.4, "snd_staticvolume");
 +              makeMulti(s, "snd_channel9volume");
 +              me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, ZCTX(_("VOL^Ambient:"))));
 +              me.TD(me, 1, 2, s);
 +              setDependentStringNotEqual(e, "mastervolume", "0");
 +              setDependentStringNotEqual(s, "mastervolume", "0");
 +      me.TR(me);
 +              me.TDempty(me, 0.2);
 +              s = makeXonoticDecibelsSlider(-40, 0, 0.4, "snd_channel0volume");
 +              me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Info:")));
 +              me.TD(me, 1, 2, s);
 +              setDependentStringNotEqual(e, "mastervolume", "0");
 +              setDependentStringNotEqual(s, "mastervolume", "0");
 +      me.TR(me);
 +              me.TDempty(me, 0.2);
 +              s = makeXonoticDecibelsSlider(-40, 0, 0.4, "snd_channel3volume");
 +              me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Items:")));
 +              me.TD(me, 1, 2, s);
 +              setDependentStringNotEqual(e, "mastervolume", "0");
 +              setDependentStringNotEqual(s, "mastervolume", "0");
 +      me.TR(me);
 +              me.TDempty(me, 0.2);
 +              s = makeXonoticDecibelsSlider(-40, 0, 0.4, "snd_channel6volume");
 +              me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Pain:")));
 +              me.TD(me, 1, 2, s);
 +              setDependentStringNotEqual(e, "mastervolume", "0");
 +              setDependentStringNotEqual(s, "mastervolume", "0");
 +      me.TR(me);
 +              me.TDempty(me, 0.2);
 +              s = makeXonoticDecibelsSlider(-40, 0, 0.4, "snd_channel7volume");
 +              me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Player:")));
 +              me.TD(me, 1, 2, s);
 +              setDependentStringNotEqual(e, "mastervolume", "0");
 +              setDependentStringNotEqual(s, "mastervolume", "0");
 +      me.TR(me);
 +              me.TDempty(me, 0.2);
 +              s = makeXonoticDecibelsSlider(-40, 0, 0.4, "snd_channel4volume");
 +              me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Shots:")));
 +              me.TD(me, 1, 2, s);
 +              setDependentStringNotEqual(e, "mastervolume", "0");
 +              setDependentStringNotEqual(s, "mastervolume", "0");
 +      me.TR(me);
 +              me.TDempty(me, 0.2);
 +              s = makeXonoticDecibelsSlider(-40, 0, 0.4, "snd_channel2volume");
 +              me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Voice:")));
 +              me.TD(me, 1, 2, s);
 +              setDependentStringNotEqual(e, "mastervolume", "0");
 +              setDependentStringNotEqual(s, "mastervolume", "0");
 +      me.TR(me);
 +              me.TDempty(me, 0.2);
 +              s = makeXonoticDecibelsSlider(-40, 0, 0.4, "snd_channel1volume");
 +              makeMulti(s, "snd_channel5volume"); // @!#%'n Tuba
 +              me.TD(me, 1, 0.8, e = makeXonoticTextLabel(0, _("Weapons:")));
 +              me.TD(me, 1, 2, s);
 +              setDependentStringNotEqual(e, "mastervolume", "0");
 +              setDependentStringNotEqual(s, "mastervolume", "0");
 +      me.TR(me);
 +      me.TR(me);
 +              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"));
 +                      e.addValue(e, _("8 kHz"), "8000");
 +                      e.addValue(e, _("11.025 kHz"), "11025");
 +                      e.addValue(e, _("16 kHz"), "16000");
 +                      e.addValue(e, _("22.05 kHz"), "22050");
 +                      e.addValue(e, _("24 kHz"), "24000");
 +                      e.addValue(e, _("32 kHz"), "32000");
 +                      e.addValue(e, _("44.1 kHz"), "44100");
 +                      e.addValue(e, _("48 kHz"), "48000");
 +                      e.configureXonoticTextSliderValues(e);
 +      me.TR(me);
 +              me.TD(me, 1, 1, makeXonoticTextLabel(0, _("Channels:")));
 +              me.TD(me, 1, 2, e = makeXonoticTextSlider("snd_channels"));
 +                      e.addValue(e, _("Mono"), "1");
 +                      e.addValue(e, _("Stereo"), "2");
 +                      e.addValue(e, _("2.1"), "3");
 +                      e.addValue(e, _("4"), "4");
 +                      e.addValue(e, _("5"), "5");
 +                      e.addValue(e, _("5.1"), "6");
 +                      e.addValue(e, _("6.1"), "7");
 +                      e.addValue(e, _("7.1"), "8");
 +                      e.configureXonoticTextSliderValues(e);
 +      me.TR(me);
 +      me.TR(me);
 +              me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "snd_swapstereo", _("Swap stereo output channels")));
 +              setDependent(e, "snd_channels", 1.5, 0.5);
 +      me.TR(me);
 +              me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "snd_spatialization_control", _("Headphone friendly mode")));
 +              setDependent(e, "snd_channels", 1.5, 0.5);
 +      me.TR(me);
 +      me.TR(me);
 +              me.TD(me, 1, 3, makeXonoticCheckBox(0, "cl_hitsound", _("Hit indication sound")));
 +              e.sendCvars = true;
 +      me.TR(me);
 +              me.TD(me, 1, 3, makeXonoticCheckBox(0, "con_chatsound", _("Chat message sound")));
 +      me.TR(me);
++              me.hiddenMenuSoundsSlider = makeXonoticSlider(1, 1, 1, "menu_sounds");
++              me.TD(me, 1, 1.2, makeXonoticSliderCheckBox(0, 1, me.hiddenMenuSoundsSlider, _("Menu sounds")));
++              me.TD(me, 1, 1.8, e = makeXonoticSliderCheckBox(2, 0, me.hiddenMenuSoundsSlider, _("Focus sounds")));
++              setDependent(e, "menu_sounds", 1, 2);
 +      me.TR(me);
 +      me.TR(me);
 +              me.TD(me, 1, 1, makeXonoticTextLabel(0, _("Time announcer:")));
 +              me.TD(me, 1, 2, e = makeXonoticTextSlider("cl_announcer_maptime"));
 +                      e.addValue(e, ZCTX(_("WRN^Disabled")), "0");
 +                      e.addValue(e, _("1 minute"), "1");
 +                      e.addValue(e, _("5 minutes"), "2");
 +                      e.addValue(e, ZCTX(_("WRN^Both")), "3");
 +                      e.configureXonoticTextSliderValues(e);
 +      me.TR(me);
 +              me.TD(me, 1, 1, makeXonoticTextLabel(0, _("Automatic taunts:")));
 +              me.TD(me, 1, 2, e = makeXonoticTextSlider("cl_autotaunt"));
 +                      e.addValue(e, _("Never"), "0");
 +                      e.addValue(e, _("Sometimes"), "0.35");
 +                      e.addValue(e, _("Often"), "0.65");
 +                      e.addValue(e, _("Always"), "1");
 +                      e.configureXonoticTextSliderValues(e);
 +                      e.sendCvars = true;
 +      me.TR(me);
 +      me.TR(me);
 +              if(cvar("developer"))
 +                      me.TD(me, 1, 3, makeXonoticCheckBox(0, "showsound", _("Debug info about sounds")));
 +
 +      me.gotoRC(me, me.rows - 1, 0);
 +              me.TD(me, 1, me.columns, makeXonoticCommandButton(_("Apply immediately"), '0 0 0', "snd_restart; snd_attenuation_method_${menu_snd_attenuation_method}", COMMANDBUTTON_APPLY));
 +}
 +#endif
index 7e3415141682de0500e9aa3c2393ea3f074aa490,0000000000000000000000000000000000000000..0aa38e85af2d472be53c18f53c6d32003c946c86
mode 100644,000000..100644
--- /dev/null
@@@ -1,163 -1,0 +1,168 @@@
 +#ifdef INTERFACE
 +CLASS(XonoticGameCrosshairSettingsTab) EXTENDS(XonoticTab)
 +      //METHOD(XonoticGameCrosshairSettingsTab, toString, string(entity))
 +      METHOD(XonoticGameCrosshairSettingsTab, fill, void(entity))
 +      METHOD(XonoticGameCrosshairSettingsTab, showNotify, void(entity))
 +      ATTRIB(XonoticGameCrosshairSettingsTab, title, string, _("Crosshair"))
 +      ATTRIB(XonoticGameCrosshairSettingsTab, intendedWidth, float, 0.9)
 +      ATTRIB(XonoticGameCrosshairSettingsTab, rows, float, 13)
 +      ATTRIB(XonoticGameCrosshairSettingsTab, columns, float, 6.2)
 +ENDCLASS(XonoticGameCrosshairSettingsTab)
 +entity makeXonoticGameCrosshairSettingsTab();
 +#endif
 +
 +#ifdef IMPLEMENTATION
 +void XonoticGameCrosshairSettingsTab_showNotify(entity me)
 +{
 +      loadAllCvars(me);
 +}
 +entity makeXonoticGameCrosshairSettingsTab()
 +{
 +      entity me;
 +      me = spawnXonoticGameCrosshairSettingsTab();
 +      me.configureDialog(me);
 +      return me;
 +}
 +
 +void XonoticGameCrosshairSettingsTab_fill(entity me)
 +{
 +      entity e;
 +      float i;
 +
 +      // 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.TR(me); //me.gotoRC(me, 0, 3.2); me.setFirstColumn(me, me.currentColumn);
 +              me.TD(me, 1, 1, e = makeXonoticRadioButton(3, "crosshair_enabled", "0", _("No crosshair")));
 +      //me.TR(me);
 +              me.TD(me, 1, 1, e = makeXonoticRadioButton(3, "crosshair_per_weapon", string_null, _("Per weapon")));
 +              makeMulti(e, "crosshair_enabled");
 +      //me.TR(me);
 +              me.TD(me, 1, 1, e = makeXonoticRadioButton(3, "crosshair_enabled", "2", _("Custom")));
 +      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.TDempty(me, 0.1);
++              for(i = 29; i <= 42; ++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.TDempty(me, 0.1);
 +              me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Crosshair size:")));
 +                      setDependent(e, "crosshair_enabled", 1, 2);
 +              me.TD(me, 1, 1.9, e = makeXonoticSlider(0.1, 1.0, 0.01, "crosshair_size"));
 +                      setDependent(e, "crosshair_enabled", 1, 2);
 +      me.TR(me);
 +              me.TDempty(me, 0.1);
 +              me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Crosshair alpha:")));
 +                      setDependent(e, "crosshair_enabled", 1, 2);
 +              me.TD(me, 1, 1.9, e = makeXonoticSlider(0, 1, 0.1, "crosshair_alpha"));
 +                      setDependent(e, "crosshair_enabled", 1, 2);
 +      me.TR(me);
 +              me.TDempty(me, 0.1);
 +              me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Crosshair color:")));
 +                      setDependent(e, "crosshair_enabled", 1, 2);
 +              me.TD(me, 1, 0.9, 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.2);
 +              me.TD(me, 1, 0.8, 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.1);
 +              me.TD(me, 1, 2.9, e = makeXonoticCheckBox(0, "crosshair_ring", _("Use rings to indicate weapon status")));
 +                      makeMulti(e, "crosshair_ring_reload");
 +                      setDependent(e, "crosshair_enabled", 1, 2);
 +      //me.TR(me);
 +      //      me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Ring size:")));
 +      //              setDependentAND(e, "crosshair_ring", 1, 1, "crosshair_enabled", 1, 2);
 +      //      me.TD(me, 1, 2, e = makeXonoticSlider(2, 4, 0.1, "crosshair_ring_size"));
 +      //              setDependentAND(e, "crosshair_ring", 1, 1, "crosshair_enabled", 1, 2);
 +      me.TR(me);
 +              me.TDempty(me, 0.3);
 +              me.TD(me, 1, 0.9, e = makeXonoticTextLabel(0, _("Ring alpha:")));
 +                      setDependentAND(e, "crosshair_ring", 1, 1, "crosshair_enabled", 1, 2);
 +              me.TD(me, 1, 1.8, e = makeXonoticSlider(0.1, 1, 0.1, "crosshair_ring_alpha"));
 +                      setDependentAND(e, "crosshair_ring", 1, 1, "crosshair_enabled", 1, 2);
 +
 +      me.gotoRC(me, 0, 3.2); me.setFirstColumn(me, me.currentColumn);
 +              me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "crosshair_dot", _("Enable center crosshair dot")));
 +              setDependent(e, "crosshair_enabled", 1, 2);
 +      me.TR(me);
 +              me.TDempty(me, 0.1);
 +              me.TD(me, 1, 0.9, e = makeXonoticTextLabel(0, _("Dot size:")));
 +                      setDependentAND(e, "crosshair_dot", 1, 1, "crosshair_enabled", 1, 2);
 +              me.TD(me, 1, 2, e = makeXonoticSlider(0.2, 2, 0.1, "crosshair_dot_size"));
 +                      setDependentAND(e, "crosshair_dot", 1, 1, "crosshair_enabled", 1, 2);
 +      me.TR(me);
 +              me.TDempty(me, 0.1);
 +              me.TD(me, 1, 0.9, e = makeXonoticTextLabel(0, _("Dot alpha:")));
 +                      setDependentAND(e, "crosshair_dot", 1, 1, "crosshair_enabled", 1, 2);
 +              me.TD(me, 1, 2, e = makeXonoticSlider(0.1, 1, 0.1, "crosshair_dot_alpha"));
 +                      setDependentAND(e, "crosshair_dot", 1, 1, "crosshair_enabled", 1, 2);
 +      me.TR(me);
 +              me.TDempty(me, 0.1);
 +              me.TD(me, 1, 0.9, e = makeXonoticTextLabel(0, _("Dot color:")));
 +                      setDependentAND(e, "crosshair_dot", 1, 1, "crosshair_enabled", 1, 2);
 +              me.TD(me, 1, 2, e = makeXonoticRadioButton(1, "crosshair_dot_color_custom", "0", _("Use normal crosshair color")));
 +                      setDependentAND(e, "crosshair_dot", 1, 1, "crosshair_enabled", 1, 2);
 +      me.TR(me);
 +              me.TDempty(me, 0.2);
 +              me.TD(me, 1, 0.8, e = makeXonoticRadioButton(1, "crosshair_dot_color_custom", "1", _("Custom")));
 +                      setDependentAND(e, "crosshair_dot", 1, 1, "crosshair_enabled", 1, 2);
 +              me.TD(me, 2, 2, e = makeXonoticColorpickerString("crosshair_dot_color", "crosshair_dot_color"));
 +                      setDependentAND3(e, "crosshair_dot", 1, 1, "crosshair_enabled", 1, 2, "crosshair_dot_color_custom", 1, 1);
 +      me.TR(me);
 +      me.TR(me);
 +      me.TR(me);
 +              me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "crosshair_effect_scalefade", _("Smooth effects of crosshairs")));
 +                      setDependent(e, "crosshair_enabled", 1, 2);
 +      me.TR(me);
 +              me.TD(me, 1, 3, e = makeXonoticCheckBox(0, "crosshair_hittest_blur", _("Blur crosshair if the shot is obstructed")));
 +                      setDependentAND(e, "crosshair_hittest", 1, 100, "crosshair_enabled", 1, 2);
 +      me.TR(me);
 +              me.TD(me, 1, 3, e = makeXonoticCheckBoxEx(1.25, 0, "crosshair_hittest_scale", _("Enlarge crosshair if targeting an enemy")));
 +                      setDependentAND(e, "crosshair_hittest", 1, 100, "crosshair_enabled", 1, 2);
 +      me.TR(me);
 +              me.TD(me, 1, 3, e = makeXonoticCheckBoxEx(0.5, 0, "crosshair_hitindication", _("Animate crosshair when hitting an enemy")));
 +                      setDependent(e, "crosshair_enabled", 1, 2);
 +      me.TR(me);
 +              me.TD(me, 1, 3, e = makeXonoticCheckBoxEx(0.25, 0, "crosshair_pickup", _("Animate crosshair when picking up an item")));
 +                      setDependent(e, "crosshair_enabled", 1, 2);
 +      /*me.TR(me);
 +              me.TD(me, 1, 1, e = makeXonoticTextLabel(0, _("Hit testing:")));
 +              me.TD(me, 1, 2, e = makeXonoticTextSlider("crosshair_hittest"));
 +                      e.addValue(e, ZCTX(_("HTTST^Disabled")), "0");
 +                      e.addValue(e, ZCTX(_("HTTST^TrueAim")), "1");
 +                      e.addValue(e, ZCTX(_("HTTST^Enemies")), "1.25");
 +                      e.configureXonoticTextSliderValues(e);
 +                      setDependent(e, "crosshair_enabled", 1, 2);*/
 +
 +      /*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 8e584b8a8a259866636932eccce69630a840747a,0000000000000000000000000000000000000000..0d1c05af44a10a89281bc4c668d06dd1827608ba
mode 100644,000000..100644
--- /dev/null
@@@ -1,25 -1,0 +1,30 @@@
 +#ifdef INTERFACE
 +CLASS(XonoticWinnerDialog) EXTENDS(XonoticDialog)
 +      METHOD(XonoticWinnerDialog, fill, void(entity))
++      METHOD(XonoticWinnerDialog, focusEnter, void(entity))
 +      ATTRIB(XonoticWinnerDialog, title, string, _("Winner"))
 +      ATTRIB(XonoticWinnerDialog, color, vector, SKINCOLOR_DIALOG_SINGLEPLAYER)
 +      ATTRIB(XonoticWinnerDialog, intendedWidth, float, 0.32)
 +      ATTRIB(XonoticWinnerDialog, rows, float, 12)
 +      ATTRIB(XonoticWinnerDialog, columns, float, 3)
 +ENDCLASS(XonoticWinnerDialog)
 +#endif
 +
 +#ifdef IMPLEMENTATION
 +void XonoticWinnerDialog_fill(entity me)
 +{
 +      entity e;
 +
 +      me.TR(me);
 +              me.TD(me, me.rows - 2, me.columns, e = makeXonoticImage("/gfx/winner", -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;
 +}
++void XonoticWinnerDialog_focusEnter(entity me)
++{
++      m_play_click_sound(MENU_SOUND_WINNER);
++}
 +#endif
index 45f493bb9b295dde44119e74ce24dddacea7bad5,0000000000000000000000000000000000000000..feb1d89b133d2b4e040db7bd42d2828c500fc89f
mode 100644,000000..100644
--- /dev/null
@@@ -1,125 -1,0 +1,129 @@@
 +#ifdef INTERFACE
 +CLASS(XonoticGametypeList) EXTENDS(XonoticListBox)
 +      METHOD(XonoticGametypeList, configureXonoticGametypeList, void(entity))
 +      ATTRIB(XonoticGametypeList, rowsPerItem, float, 2)
 +      METHOD(XonoticGametypeList, drawListBoxItem, void(entity, float, vector, float))
 +      METHOD(XonoticGametypeList, resizeNotify, void(entity, vector, vector, vector, vector))
 +      METHOD(XonoticGametypeList, setSelected, void(entity, float))
 +      METHOD(XonoticGametypeList, loadCvars, void(entity))
 +      METHOD(XonoticGametypeList, saveCvars, void(entity))
 +      METHOD(XonoticGametypeList, keyDown, float(entity, float, float, float))
++      METHOD(XonoticGametypeList, clickListBoxItem, void(entity, float, vector))
 +
 +      ATTRIB(XonoticGametypeList, realFontSize, vector, '0 0 0')
 +      ATTRIB(XonoticGametypeList, realUpperMargin, float, 0)
 +      ATTRIB(XonoticGametypeList, columnIconOrigin, float, 0)
 +      ATTRIB(XonoticGametypeList, columnIconSize, float, 0)
 +      ATTRIB(XonoticGametypeList, columnNameOrigin, float, 0)
 +      ATTRIB(XonoticGametypeList, columnNameSize, float, 0)
 +ENDCLASS(XonoticGametypeList)
 +entity makeXonoticGametypeList();
 +#endif
 +
 +#ifdef IMPLEMENTATION
 +
 +entity makeXonoticGametypeList(void)
 +{
 +      entity me;
 +      me = spawnXonoticGametypeList();
 +      me.configureXonoticGametypeList(me);
 +      return me;
 +}
 +void XonoticGametypeList_configureXonoticGametypeList(entity me)
 +{
 +      float i;
 +      me.configureXonoticListBox(me);
 +      me.nItems = GameType_GetCount();
 +
 +      // we want the pics mipmapped
 +      for(i = 0; i < GameType_GetCount(); ++i)
 +              draw_PreloadPictureWithFlags(GameType_GetIcon(i), PRECACHE_PIC_MIPMAP);
 +
 +      me.loadCvars(me);
 +}
 +void XonoticGametypeList_setSelected(entity me, float i)
 +{
 +      SUPER(XonoticGametypeList).setSelected(me, i);
 +      me.saveCvars(me);
 +}
 +void XonoticGametypeList_loadCvars(entity me)
 +{
 +      float t;
 +      t = MapInfo_CurrentGametype();
 +      float i;
 +      for(i = 0; i < GameType_GetCount(); ++i)
 +              if(t == GameType_GetID(i))
 +                      break;
 +      if(i >= GameType_GetCount())
 +      {
 +              for(i = 0; i < GameType_GetCount(); ++i)
 +                      if(t == MAPINFO_TYPE_DEATHMATCH)
 +                              break;
 +              if(i >= GameType_GetCount())
 +                      i = 0;
 +      }
 +      me.setSelected(me, i);
 +      // do we need this: me.parent.gameTypeChangeNotify(me.parent); // to make sure
 +}
 +void XonoticGametypeList_saveCvars(entity me)
 +{
 +      float t;
 +      t = GameType_GetID(me.selectedItem);
 +      if(t == MapInfo_CurrentGametype())
 +              return;
 +      MapInfo_SwitchGameType(t);
 +      me.parent.gameTypeChangeNotify(me.parent);
 +}
 +void XonoticGametypeList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
 +{
 +      string s1, s2;
 +
 +      if(isSelected)
 +              draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
 +
 +      draw_Picture(me.columnIconOrigin * eX, GameType_GetIcon(i), me.columnIconSize * eX + eY, '1 1 1', SKINALPHA_LISTBOX_SELECTED);
 +      s1 = GameType_GetName(i);
 +
 +      if(_MapInfo_GetTeamPlayBool(GameType_GetID(i)))
 +              s2 = _("teamplay");
 +      else
 +              s2 = _("free for all");
 +
 +      vector save_fontscale = draw_fontscale;
 +      float f = draw_CondensedFontFactor(strcat(s1, " ", s2), false, me.realFontSize, 1);
 +      draw_fontscale.x *= f;
 +      vector fs = me.realFontSize;
 +      fs.x *= f;
 +      draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s1, fs, '1 1 1', SKINALPHA_TEXT, 0);
 +      draw_Text(me.realUpperMargin * eY + (me.columnNameOrigin + 1.0 * (me.columnNameSize - draw_TextWidth(s2, 0, fs))) * eX, s2, fs, SKINCOLOR_TEXT, SKINALPHA_TEXT, 0);
 +      draw_fontscale = save_fontscale;
 +}
 +void XonoticGametypeList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
 +{
 +      me.itemAbsSize = '0 0 0';
 +      SUPER(XonoticServerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
 +
 +      me.realFontSize_y = me.fontSize / (me.itemAbsSize_y = (absSize.y * me.itemHeight));
 +      me.realFontSize_x = me.fontSize / (me.itemAbsSize_x = (absSize.x * (1 - me.controlWidth)));
 +      me.realUpperMargin = 0.5 * (1 - me.realFontSize.y);
 +      me.columnIconOrigin = 0;
 +      me.columnIconSize = me.itemAbsSize.y / me.itemAbsSize.x;
 +      me.columnNameOrigin = me.columnIconOrigin + me.columnIconSize + (0.5 * me.realFontSize.x);
 +      me.columnNameSize = 1 - me.columnIconSize - (1.5 * me.realFontSize.x);
 +}
 +float XonoticGametypeList_keyDown(entity me, float scan, float ascii, float shift)
 +{
 +      if(scan == K_ENTER || scan == K_KP_ENTER)
 +      {
++              m_play_click_sound(MENU_SOUND_EXECUTE);
 +              me.parent.gameTypeSelectNotify(me.parent);
 +              return 1;
 +      }
 +
 +      return SUPER(XonoticGametypeList).keyDown(me, scan, ascii, shift);
 +}
++void XonoticGametypeList_clickListBoxItem(entity me, float i, vector where)
++{
++      m_play_click_sound(MENU_SOUND_SELECT);
++}
 +#endif
index 73042024b9dcb548b6304fe18c6a7f9c3136c6f2,0000000000000000000000000000000000000000..a083207715250430fc1a9dd465b66c8c68dca477
mode 100644,000000..100644
--- /dev/null
@@@ -1,363 -1,0 +1,365 @@@
-       localcmd("exec binds-default.cfg\n");
 +#ifdef INTERFACE
 +CLASS(XonoticKeyBinder) EXTENDS(XonoticListBox)
 +      METHOD(XonoticKeyBinder, configureXonoticKeyBinder, void(entity))
 +      ATTRIB(XonoticKeyBinder, rowsPerItem, float, 1)
 +      METHOD(XonoticKeyBinder, drawListBoxItem, void(entity, float, vector, float))
 +      METHOD(XonoticKeyBinder, doubleClickListBoxItem, void(entity, float, vector))
 +      METHOD(XonoticKeyBinder, resizeNotify, void(entity, vector, vector, vector, vector))
 +      METHOD(XonoticKeyBinder, setSelected, void(entity, float))
 +      METHOD(XonoticKeyBinder, keyDown, float(entity, float, float, float))
 +      METHOD(XonoticKeyBinder, keyGrabbed, void(entity, float, float))
 +
 +      ATTRIB(XonoticKeyBinder, realFontSize, vector, '0 0 0')
 +      ATTRIB(XonoticKeyBinder, realUpperMargin, float, 0)
 +      ATTRIB(XonoticKeyBinder, columnFunctionOrigin, float, 0)
 +      ATTRIB(XonoticKeyBinder, columnFunctionSize, float, 0)
 +      ATTRIB(XonoticKeyBinder, columnKeysOrigin, float, 0)
 +      ATTRIB(XonoticKeyBinder, columnKeysSize, float, 0)
 +
 +      ATTRIB(XonoticKeyBinder, previouslySelected, float, -1)
 +      ATTRIB(XonoticKeyBinder, inMouseHandler, float, 0)
 +      ATTRIB(XonoticKeyBinder, userbindEditButton, entity, NULL)
 +      ATTRIB(XonoticKeyBinder, keyGrabButton, entity, NULL)
 +      ATTRIB(XonoticKeyBinder, clearButton, entity, NULL)
 +      ATTRIB(XonoticKeyBinder, userbindEditDialog, entity, NULL)
 +      METHOD(XonoticKeyBinder, editUserbind, void(entity, string, string, string))
 +ENDCLASS(XonoticKeyBinder)
 +entity makeXonoticKeyBinder();
 +void KeyBinder_Bind_Change(entity btn, entity me);
 +void KeyBinder_Bind_Clear(entity btn, entity me);
 +void KeyBinder_Bind_Edit(entity btn, entity me);
 +#endif
 +
 +#ifdef IMPLEMENTATION
 +
 +const string KEY_NOT_BOUND_CMD = "// not bound";
 +
 +const float MAX_KEYS_PER_FUNCTION = 2;
 +const float MAX_KEYBINDS = 256;
 +string Xonotic_KeyBinds_Functions[MAX_KEYBINDS];
 +string Xonotic_KeyBinds_Descriptions[MAX_KEYBINDS];
 +float Xonotic_KeyBinds_Count = -1;
 +
 +void Xonotic_KeyBinds_Read()
 +{
 +      float fh;
 +      string s;
 +
 +      Xonotic_KeyBinds_Count = 0;
 +      fh = fopen(language_filename("keybinds.txt"), FILE_READ);
 +      if(fh < 0)
 +              return;
 +      while((s = fgets(fh)))
 +      {
 +              if(tokenize_console(s) != 2)
 +                      continue;
 +              Xonotic_KeyBinds_Functions[Xonotic_KeyBinds_Count] = strzone(argv(0));
 +              Xonotic_KeyBinds_Descriptions[Xonotic_KeyBinds_Count] = strzone(argv(1));
 +              ++Xonotic_KeyBinds_Count;
 +              if(Xonotic_KeyBinds_Count >= MAX_KEYBINDS)
 +                      break;
 +      }
 +      fclose(fh);
 +}
 +
 +entity makeXonoticKeyBinder()
 +{
 +      entity me;
 +      me = spawnXonoticKeyBinder();
 +      me.configureXonoticKeyBinder(me);
 +      return me;
 +}
 +void replace_bind(string from, string to)
 +{
 +      float n, j, k;
 +      n = tokenize(findkeysforcommand(from, 0)); // uses '...' strings
 +      for(j = 0; j < n; ++j)
 +      {
 +              k = stof(argv(j));
 +              if(k != -1)
 +                      localcmd("\nbind \"", keynumtostring(k), "\" \"", to, "\"\n");
 +      }
 +      if(n)
 +              cvar_set("_hud_showbinds_reload", "1");
 +}
 +void XonoticKeyBinder_configureXonoticKeyBinder(entity me)
 +{
 +      me.configureXonoticListBox(me);
 +      if(Xonotic_KeyBinds_Count < 0)
 +              Xonotic_KeyBinds_Read();
 +      me.nItems = Xonotic_KeyBinds_Count;
 +      me.setSelected(me, 0);
 +
 +      // TEMP: Xonotic 0.1 to later
 +      replace_bind("impulse 1", "weapon_group_1");
 +      replace_bind("impulse 2", "weapon_group_2");
 +      replace_bind("impulse 3", "weapon_group_3");
 +      replace_bind("impulse 4", "weapon_group_4");
 +      replace_bind("impulse 5", "weapon_group_5");
 +      replace_bind("impulse 6", "weapon_group_6");
 +      replace_bind("impulse 7", "weapon_group_7");
 +      replace_bind("impulse 8", "weapon_group_8");
 +      replace_bind("impulse 9", "weapon_group_9");
 +      replace_bind("impulse 14", "weapon_group_0");
 +}
 +void XonoticKeyBinder_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
 +{
 +      SUPER(XonoticKeyBinder).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
 +
 +      me.realFontSize_y = me.fontSize / (absSize.y * me.itemHeight);
 +      me.realFontSize_x = me.fontSize / (absSize.x * (1 - me.controlWidth));
 +      me.realUpperMargin = 0.5 * (1 - me.realFontSize.y);
 +
 +      me.columnFunctionOrigin = 0;
 +      me.columnKeysSize = me.realFontSize.x * 12;
 +      me.columnFunctionSize = 1 - me.columnKeysSize - 2 * me.realFontSize.x;
 +      me.columnKeysOrigin = me.columnFunctionOrigin + me.columnFunctionSize + me.realFontSize.x;
 +
 +      if(me.userbindEditButton)
 +              me.userbindEditButton.disabled = (substring(Xonotic_KeyBinds_Descriptions[me.selectedItem], 0, 1) != "$");
 +}
 +void KeyBinder_Bind_Change(entity btn, entity me)
 +{
 +      string func;
 +
 +      func = Xonotic_KeyBinds_Functions[me.selectedItem];
 +      if(func == "")
 +              return;
 +
 +      me.keyGrabButton.forcePressed = 1;
 +      me.clearButton.disabled = 1;
 +      keyGrabber = me;
 +}
 +void XonoticKeyBinder_keyGrabbed(entity me, float key, float ascii)
 +{
 +      float n, j, k, nvalid;
 +      string func;
 +
 +      me.keyGrabButton.forcePressed = 0;
 +      me.clearButton.disabled = 0;
 +
 +      if(key == K_ESCAPE)
 +              return;
 +
 +      // forbid these keys from being bound in the menu
 +      if(key == K_CAPSLOCK || key == K_NUMLOCK)
 +      {
 +              KeyBinder_Bind_Change(me, me);
 +              return;
 +      }
 +
 +      func = Xonotic_KeyBinds_Functions[me.selectedItem];
 +      if(func == "")
 +              return;
 +
 +      n = tokenize(findkeysforcommand(func, 0)); // uses '...' strings
 +      nvalid = 0;
 +      for(j = 0; j < n; ++j)
 +      {
 +              k = stof(argv(j));
 +              if(k != -1)
 +                      ++nvalid;
 +      }
 +      if(nvalid >= MAX_KEYS_PER_FUNCTION)
 +      {
 +              for(j = 0; j < n; ++j)
 +              {
 +                      k = stof(argv(j));
 +                      if(k != -1)
 +                              //localcmd("\nunbind \"", keynumtostring(k), "\"\n");
 +                              localcmd("\nbind \"", keynumtostring(k), "\" \"", KEY_NOT_BOUND_CMD, "\"\n");
 +              }
 +      }
++      m_play_click_sound(MENU_SOUND_SELECT);
 +      localcmd("\nbind \"", keynumtostring(key), "\" \"", func, "\"\n");
 +      localcmd("-zoom\n"); // to make sure we aren't in togglezoom'd state
 +      cvar_set("_hud_showbinds_reload", "1");
 +}
 +void XonoticKeyBinder_editUserbind(entity me, string theName, string theCommandPress, string theCommandRelease)
 +{
 +      string func, descr;
 +
 +      if(!me.userbindEditDialog)
 +              return;
 +
 +      func = Xonotic_KeyBinds_Functions[me.selectedItem];
 +      if(func == "")
 +              return;
 +
 +      descr = Xonotic_KeyBinds_Descriptions[me.selectedItem];
 +      if(substring(descr, 0, 1) != "$")
 +              return;
 +      descr = substring(descr, 1, strlen(descr) - 1);
 +
 +      // Hooray! It IS a user bind!
 +      cvar_set(strcat(descr, "_description"), theName);
 +      cvar_set(strcat(descr, "_press"), theCommandPress);
 +      cvar_set(strcat(descr, "_release"), theCommandRelease);
 +}
 +void KeyBinder_Bind_Edit(entity btn, entity me)
 +{
 +      string func, descr;
 +
 +      if(!me.userbindEditDialog)
 +              return;
 +
 +      func = Xonotic_KeyBinds_Functions[me.selectedItem];
 +      if(func == "")
 +              return;
 +
 +      descr = Xonotic_KeyBinds_Descriptions[me.selectedItem];
 +      if(substring(descr, 0, 1) != "$")
 +              return;
 +      descr = substring(descr, 1, strlen(descr) - 1);
 +
 +      // Hooray! It IS a user bind!
 +      me.userbindEditDialog.loadUserBind(me.userbindEditDialog, cvar_string(strcat(descr, "_description")), cvar_string(strcat(descr, "_press")), cvar_string(strcat(descr, "_release")));
 +
 +      DialogOpenButton_Click(btn, me.userbindEditDialog);
 +}
 +void KeyBinder_Bind_Clear(entity btn, entity me)
 +{
 +      float n, j, k;
 +      string func;
 +
 +      func = Xonotic_KeyBinds_Functions[me.selectedItem];
 +      if(func == "")
 +              return;
 +
 +      n = tokenize(findkeysforcommand(func, 0)); // uses '...' strings
 +      for(j = 0; j < n; ++j)
 +      {
 +              k = stof(argv(j));
 +              if(k != -1)
 +                      //localcmd("\nunbind \"", keynumtostring(k), "\"\n");
 +                      localcmd("\nbind \"", keynumtostring(k), "\" \"", KEY_NOT_BOUND_CMD, "\"\n");
 +      }
++      m_play_click_sound(MENU_SOUND_CLEAR);
 +      localcmd("-zoom\n"); // to make sure we aren't in togglezoom'd state
 +      cvar_set("_hud_showbinds_reload", "1");
 +}
 +void KeyBinder_Bind_Reset_All(entity btn, entity me)
 +{
 +      localcmd("unbindall\n");
++      localcmd("exec binds-xonotic.cfg\n");
 +      localcmd("-zoom\n"); // to make sure we aren't in togglezoom'd state
 +      cvar_set("_hud_showbinds_reload", "1");
 +}
 +void XonoticKeyBinder_doubleClickListBoxItem(entity me, float i, vector where)
 +{
 +      KeyBinder_Bind_Change(NULL, me);
 +}
 +void XonoticKeyBinder_setSelected(entity me, float i)
 +{
 +      // handling of "unselectable" items
 +      i = floor(0.5 + bound(0, i, me.nItems - 1));
 +      if(me.pressed == 0 || me.pressed == 1) // keyboard or scrolling - skip unselectable items
 +      {
 +              if(i > me.previouslySelected)
 +              {
 +                      while((i < me.nItems - 1) && (Xonotic_KeyBinds_Functions[i] == ""))
 +                              ++i;
 +              }
 +              while((i > 0) && (Xonotic_KeyBinds_Functions[i] == ""))
 +                      --i;
 +              while((i < me.nItems - 1) && (Xonotic_KeyBinds_Functions[i] == ""))
 +                      ++i;
 +      }
 +      if(me.pressed == 3) // released the mouse - fall back to last valid item
 +      {
 +              if(Xonotic_KeyBinds_Functions[i] == "")
 +                      i = me.previouslySelected;
 +      }
 +      if(Xonotic_KeyBinds_Functions[i] != "")
 +              me.previouslySelected = i;
 +      if(me.userbindEditButton)
 +              me.userbindEditButton.disabled = (substring(Xonotic_KeyBinds_Descriptions[i], 0, 1) != "$");
 +      SUPER(XonoticKeyBinder).setSelected(me, i);
 +}
 +float XonoticKeyBinder_keyDown(entity me, float key, float ascii, float shift)
 +{
 +      float r;
 +      r = 1;
 +      switch(key)
 +      {
 +              case K_ENTER:
 +              case K_KP_ENTER:
 +              case K_SPACE:
 +                      KeyBinder_Bind_Change(me, me);
 +                      break;
 +              case K_DEL:
 +              case K_KP_DEL:
 +              case K_BACKSPACE:
 +                      KeyBinder_Bind_Clear(me, me);
 +                      break;
 +              default:
 +                      r = SUPER(XonoticKeyBinder).keyDown(me, key, ascii, shift);
 +                      break;
 +      }
 +      return r;
 +}
 +void XonoticKeyBinder_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
 +{
 +      string s;
 +      float j, k, n;
 +      vector theColor;
 +      float theAlpha;
 +      string func, descr;
 +      float extraMargin;
 +
 +      descr = Xonotic_KeyBinds_Descriptions[i];
 +      func = Xonotic_KeyBinds_Functions[i];
 +
 +      if(func == "")
 +      {
 +              theAlpha = 1;
 +              theColor = SKINCOLOR_KEYGRABBER_TITLES;
 +              theAlpha = SKINALPHA_KEYGRABBER_TITLES;
 +              extraMargin = 0;
 +      }
 +      else
 +      {
 +              if(isSelected)
 +              {
 +                      if(keyGrabber == me)
 +                              draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_WAITING, SKINALPHA_LISTBOX_WAITING);
 +                      else
 +                              draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
 +              }
 +              theAlpha = SKINALPHA_KEYGRABBER_KEYS;
 +              theColor = SKINCOLOR_KEYGRABBER_KEYS;
 +              extraMargin = me.realFontSize.x * 0.5;
 +      }
 +
 +      if(substring(descr, 0, 1) == "$")
 +      {
 +              s = substring(descr, 1, strlen(descr) - 1);
 +              descr = cvar_string(strcat(s, "_description"));
 +              if(descr == "")
 +                      descr = s;
 +              if(cvar_string(strcat(s, "_press")) == "")
 +                      if(cvar_string(strcat(s, "_release")) == "")
 +                              theAlpha *= SKINALPHA_DISABLED;
 +      }
 +
 +      s = draw_TextShortenToWidth(descr, me.columnFunctionSize, 0, me.realFontSize);
 +      draw_Text(me.realUpperMargin * eY + extraMargin * eX, s, me.realFontSize, theColor, theAlpha, 0);
 +      if(func != "")
 +      {
 +              n = tokenize(findkeysforcommand(func, 0)); // uses '...' strings
 +              s = "";
 +              for(j = 0; j < n; ++j)
 +              {
 +                      k = stof(argv(j));
 +                      if(k != -1)
 +                      {
 +                              if(s != "")
 +                                      s = strcat(s, ", ");
 +                              s = strcat(s, keynumtostring(k));
 +                      }
 +              }
 +              s = draw_TextShortenToWidth(s, me.columnKeysSize, 0, me.realFontSize);
 +              draw_CenterText(me.realUpperMargin * eY + (me.columnKeysOrigin + 0.5 * me.columnKeysSize) * eX, s, me.realFontSize, theColor, theAlpha, 0);
 +      }
 +}
 +#endif
index 2d43a47e32910587c6508dabab26bacb16aeae14,0000000000000000000000000000000000000000..8aa0d2c077dd4efde44e0b94d249ba0512fc58ea
mode 100644,000000..100644
--- /dev/null
@@@ -1,215 -1,0 +1,218 @@@
-       if(scan == K_ENTER || scan == K_KP_ENTER) {
 +#ifdef INTERFACE
 +CLASS(XonoticLanguageList) EXTENDS(XonoticListBox)
 +      METHOD(XonoticLanguageList, configureXonoticLanguageList, void(entity))
 +      ATTRIB(XonoticLanguageList, rowsPerItem, float, 1)
 +      METHOD(XonoticLanguageList, drawListBoxItem, void(entity, float, vector, float))
 +      METHOD(XonoticLanguageList, resizeNotify, void(entity, vector, vector, vector, vector))
 +      METHOD(XonoticLanguageList, setSelected, void(entity, float))
 +      METHOD(XonoticLanguageList, loadCvars, void(entity))
 +      METHOD(XonoticLanguageList, saveCvars, void(entity))
 +
 +      ATTRIB(XonoticLanguageList, realFontSize, vector, '0 0 0')
 +      ATTRIB(XonoticLanguageList, realUpperMargin, float, 0)
 +      ATTRIB(XonoticLanguageList, columnNameOrigin, float, 0)
 +      ATTRIB(XonoticLanguageList, columnNameSize, float, 0)
 +      ATTRIB(XonoticLanguageList, columnPercentageOrigin, float, 0)
 +      ATTRIB(XonoticLanguageList, columnPercentageSize, float, 0)
 +
 +      METHOD(XonoticLanguageList, doubleClickListBoxItem, void(entity, float, vector))
 +      METHOD(XonoticLanguageList, keyDown, float(entity, float, float, float)) // enter handling
 +
 +      METHOD(XonoticLanguageList, destroy, void(entity))
 +
 +      ATTRIB(XonoticLanguageList, languagelist, float, -1)
 +      METHOD(XonoticLanguageList, getLanguages, void(entity))
 +      METHOD(XonoticLanguageList, setLanguage, void(entity))
 +      METHOD(XonoticLanguageList, languageParameter, string(entity, float, float))
 +
 +      ATTRIB(XonoticLanguageList, name, string, "languageselector") // change this to make it noninteractive (for first run dialog)
 +ENDCLASS(XonoticLanguageList)
 +
 +entity makeXonoticLanguageList();
 +void SetLanguage_Click(entity btn, entity me);
 +#endif
 +
 +#ifdef IMPLEMENTATION
 +
 +const float LANGPARM_ID = 0;
 +const float LANGPARM_NAME = 1;
 +const float LANGPARM_NAME_LOCALIZED = 2;
 +const float LANGPARM_PERCENTAGE = 3;
 +const float LANGPARM_COUNT = 4;
 +
 +entity makeXonoticLanguageList()
 +{
 +      entity me;
 +      me = spawnXonoticLanguageList();
 +      me.configureXonoticLanguageList(me);
 +      return me;
 +}
 +
 +void XonoticLanguageList_configureXonoticLanguageList(entity me)
 +{
 +      me.configureXonoticListBox(me);
 +      me.getLanguages(me);
 +      me.loadCvars(me);
 +}
 +
 +void XonoticLanguageList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
 +{
 +      string s, p;
 +      if(isSelected)
 +              draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
 +
 +      s = me.languageParameter(me, i, LANGPARM_NAME_LOCALIZED);
 +
 +      vector save_fontscale = draw_fontscale;
 +      float f = draw_CondensedFontFactor(s, false, me.realFontSize, 1);
 +      draw_fontscale.x *= f;
 +      vector fs = me.realFontSize;
 +      fs.x *= f;
 +      draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, fs, SKINCOLOR_TEXT, SKINALPHA_TEXT, 0);
 +      draw_fontscale = save_fontscale;
 +
 +      p = me.languageParameter(me, i, LANGPARM_PERCENTAGE);
 +      if(p != "")
 +      {
 +              vector save_fontscale = draw_fontscale;
 +              float f = draw_CondensedFontFactor(p, false, me.realFontSize, 1);
 +              draw_fontscale.x *= f;
 +              vector fs = me.realFontSize;
 +              fs.x *= f;
 +              draw_Text(me.realUpperMargin * eY + (me.columnPercentageOrigin + (me.columnPercentageSize - draw_TextWidth(p, 0, fs))) * eX, p, fs, SKINCOLOR_TEXT, SKINALPHA_TEXT, 0);
 +              draw_fontscale = save_fontscale;
 +      }
 +}
 +
 +void XonoticLanguageList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
 +{
 +      SUPER(XonoticLanguageList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
 +      me.realFontSize_y = me.fontSize / (absSize.y * me.itemHeight);
 +      me.realFontSize_x = me.fontSize / (absSize.x * (1 - me.controlWidth));
 +      me.realUpperMargin = 0.5 * (1 - me.realFontSize.y);
 +      me.columnPercentageSize = me.realFontSize.x * 3;
 +      me.columnPercentageOrigin = 1 - me.columnPercentageSize;
 +      me.columnNameOrigin = 0;
 +      me.columnNameSize = me.columnPercentageOrigin;
 +}
 +
 +void XonoticLanguageList_setSelected(entity me, float i)
 +{
 +      SUPER(XonoticLanguageList).setSelected(me, i);
 +      me.saveCvars(me);
 +}
 +
 +void XonoticLanguageList_loadCvars(entity me)
 +{
 +      string s;
 +      float i, n;
 +      s = cvar_string("_menu_prvm_language");
 +      n = me.nItems;
 +
 +      // default to English
 +      for(i = 0; i < n; ++i)
 +      {
 +              if(me.languageParameter(me, i, LANGPARM_ID) == "en")
 +              {
 +                      me.selectedItem = i;
 +                      break;
 +              }
 +      }
 +
 +        // otherwise, find the language
 +      for(i = 0; i < n; ++i)
 +      {
 +              if(me.languageParameter(me, i, LANGPARM_ID) == s)
 +              {
 +                      me.selectedItem = i;
 +                      break;
 +              }
 +      }
 +
 +      // save it off (turning anything unknown into "en")
 +      me.saveCvars(me);
 +}
 +
 +void XonoticLanguageList_saveCvars(entity me)
 +{
 +      cvar_set("_menu_prvm_language", me.languageParameter(me, me.selectedItem, LANGPARM_ID));
 +}
 +
 +void XonoticLanguageList_doubleClickListBoxItem(entity me, float i, vector where)
 +{
++      m_play_click_sound(MENU_SOUND_EXECUTE);
 +      me.setLanguage(me);
 +}
 +
 +float XonoticLanguageList_keyDown(entity me, float scan, float ascii, float shift)
 +{
++      if(scan == K_ENTER || scan == K_KP_ENTER)
++      {
++              m_play_click_sound(MENU_SOUND_EXECUTE);
 +              me.setLanguage(me);
 +              return 1;
 +      }
 +      else
 +              return SUPER(XonoticLanguageList).keyDown(me, scan, ascii, shift);
 +}
 +
 +void XonoticLanguageList_destroy(entity me)
 +{
 +      buf_del(me.languagelist);
 +}
 +
 +void XonoticLanguageList_getLanguages(entity me)
 +{
 +      float buf, i, n, fh;
 +      string s;
 +
 +      buf = buf_create();
 +
 +      fh = fopen("languages.txt", FILE_READ);
 +      i = 0;
 +      while((s = fgets(fh)))
 +      {
 +              n = tokenize_console(s);
 +              if(n < 3)
 +                      continue;
 +              bufstr_set(buf, i * LANGPARM_COUNT + LANGPARM_ID, argv(0));
 +              bufstr_set(buf, i * LANGPARM_COUNT + LANGPARM_NAME, argv(1));
 +              float k = strstrofs(argv(2), "(", 0);
 +              if(k > 0)
 +              if(substring(argv(2), strlen(argv(2)) - 1, 1) == ")")
 +              {
 +                      string percent = substring(argv(2), k + 1, -2);
 +                      if(percent != "100%")
 +                              bufstr_set(buf, i * LANGPARM_COUNT + LANGPARM_PERCENTAGE, percent);
 +              }
 +              bufstr_set(buf, i * LANGPARM_COUNT + LANGPARM_NAME_LOCALIZED, (k < 0) ? argv(2) : substring(argv(2), 0, k - 1));
 +              ++i;
 +      }
 +      fclose(fh);
 +
 +      me.languagelist = buf;
 +      me.nItems = i;
 +}
 +
 +void XonoticLanguageList_setLanguage(entity me)
 +{
 +      if(prvm_language != cvar_string("_menu_prvm_language"))
 +      {
 +              if(!(gamestatus & GAME_CONNECTED))
 +                      localcmd("\nprvm_language \"$_menu_prvm_language\"; menu_restart; menu_cmd languageselect\n");
 +              else
 +                      DialogOpenButton_Click(me, main.languageWarningDialog);
 +      }
 +}
 +
 +string XonoticLanguageList_languageParameter(entity me, float i, float key)
 +{
 +      return bufstr_get(me.languagelist, i * LANGPARM_COUNT + key);
 +}
 +
 +void SetLanguage_Click(entity btn, entity me)
 +{
 +      me.setLanguage(me);
 +}
 +
 +#endif
index 60720bd54ded2f118a567213c3586e70e4b1bb61,0000000000000000000000000000000000000000..d88ad0e8e6747beddf8296226b33a7d03fad2289
mode 100644,000000..100644
--- /dev/null
@@@ -1,353 -1,0 +1,365 @@@
 +#ifdef INTERFACE
 +CLASS(XonoticMapList) EXTENDS(XonoticListBox)
 +      METHOD(XonoticMapList, configureXonoticMapList, void(entity))
 +      ATTRIB(XonoticMapList, rowsPerItem, float, 4)
 +      METHOD(XonoticMapList, draw, void(entity))
 +      METHOD(XonoticMapList, drawListBoxItem, void(entity, float, vector, float))
 +      METHOD(XonoticMapList, clickListBoxItem, void(entity, float, vector))
 +      METHOD(XonoticMapList, doubleClickListBoxItem, void(entity, float, vector))
 +      METHOD(XonoticMapList, resizeNotify, void(entity, vector, vector, vector, vector))
 +      METHOD(XonoticMapList, refilter, void(entity))
 +      METHOD(XonoticMapList, refilterCallback, void(entity, entity))
 +      METHOD(XonoticMapList, keyDown, float(entity, float, float, float))
 +
 +      ATTRIB(XonoticMapList, realFontSize, vector, '0 0 0')
 +      ATTRIB(XonoticMapList, columnPreviewOrigin, float, 0)
 +      ATTRIB(XonoticMapList, columnPreviewSize, float, 0)
 +      ATTRIB(XonoticMapList, columnNameOrigin, float, 0)
 +      ATTRIB(XonoticMapList, columnNameSize, float, 0)
 +      ATTRIB(XonoticMapList, checkMarkOrigin, vector, '0 0 0')
 +      ATTRIB(XonoticMapList, checkMarkSize, vector, '0 0 0')
 +      ATTRIB(XonoticMapList, realUpperMargin1, float, 0)
 +      ATTRIB(XonoticMapList, realUpperMargin2, float, 0)
 +
 +      ATTRIB(XonoticMapList, lastGametype, float, 0)
 +      ATTRIB(XonoticMapList, lastFeatures, float, 0)
 +
 +      ATTRIB(XonoticMapList, origin, vector, '0 0 0')
 +      ATTRIB(XonoticMapList, itemAbsSize, vector, '0 0 0')
 +
 +      ATTRIB(XonoticMapList, g_maplistCache, string, string_null)
 +      METHOD(XonoticMapList, g_maplistCacheToggle, void(entity, float))
 +      METHOD(XonoticMapList, g_maplistCacheQuery, float(entity, float))
 +
 +      ATTRIB(XonoticMapList, startButton, entity, NULL)
 +
 +      METHOD(XonoticMapList, loadCvars, void(entity))
 +
 +      ATTRIB(XonoticMapList, typeToSearchString, string, string_null)
 +      ATTRIB(XonoticMapList, typeToSearchTime, float, 0)
 +
 +      METHOD(XonoticMapList, destroy, void(entity))
 +
 +      ATTRIB(XonoticListBox, alphaBG, float, 0)
 +ENDCLASS(XonoticMapList)
 +entity makeXonoticMapList();
 +void MapList_All(entity btn, entity me);
 +void MapList_None(entity btn, entity me);
 +void MapList_LoadMap(entity btn, entity me);
 +#endif
 +
 +#ifdef IMPLEMENTATION
 +void XonoticMapList_destroy(entity me)
 +{
 +      MapInfo_Shutdown();
 +}
 +
 +entity makeXonoticMapList()
 +{
 +      entity me;
 +      me = spawnXonoticMapList();
 +      me.configureXonoticMapList(me);
 +      return me;
 +}
 +
 +void XonoticMapList_configureXonoticMapList(entity me)
 +{
 +      me.configureXonoticListBox(me);
 +      me.refilter(me);
 +}
 +
 +void XonoticMapList_loadCvars(entity me)
 +{
 +      me.refilter(me);
 +}
 +
 +float XonoticMapList_g_maplistCacheQuery(entity me, float i)
 +{
 +      return stof(substring(me.g_maplistCache, i, 1));
 +}
 +void XonoticMapList_g_maplistCacheToggle(entity me, float i)
 +{
 +      string a, b, c, s, bspname;
 +      float n;
 +      s = me.g_maplistCache;
 +      if (!s)
 +              return;
 +      b = substring(s, i, 1);
 +      if(b == "0")
 +              b = "1";
 +      else if(b == "1")
 +              b = "0";
 +      else
 +              return; // nothing happens
 +      a = substring(s, 0, i);
 +      c = substring(s, i+1, strlen(s) - (i+1));
 +      strunzone(s);
 +      me.g_maplistCache = strzone(strcat(a, b, c));
 +      // TODO also update the actual cvar
 +      if (!((bspname = MapInfo_BSPName_ByID(i))))
 +              return;
 +      if(b == "1")
 +              cvar_set("g_maplist", strcat(bspname, " ", cvar_string("g_maplist")));
 +      else
 +      {
 +              s = "";
 +              n = tokenize_console(cvar_string("g_maplist"));
 +              for(i = 0; i < n; ++i)
 +                      if(argv(i) != bspname)
 +                              s = strcat(s, " ", argv(i));
 +              cvar_set("g_maplist", substring(s, 1, strlen(s) - 1));
 +      }
 +}
 +
 +void XonoticMapList_draw(entity me)
 +{
 +      if(me.startButton)
 +              me.startButton.disabled = ((me.selectedItem < 0) || (me.selectedItem >= me.nItems));
 +      SUPER(XonoticMapList).draw(me);
 +}
 +
 +void XonoticMapList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
 +{
 +      me.itemAbsSize = '0 0 0';
 +      SUPER(XonoticMapList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
 +
 +      me.realFontSize_y = me.fontSize / (me.itemAbsSize_y = (absSize.y * me.itemHeight));
 +      me.realFontSize_x = me.fontSize / (me.itemAbsSize_x = (absSize.x * (1 - me.controlWidth)));
 +      me.realUpperMargin1 = 0.5 * (1 - 2.5 * me.realFontSize.y);
 +      me.realUpperMargin2 = me.realUpperMargin1 + 1.5 * me.realFontSize.y;
 +
 +      me.columnPreviewOrigin = 0;
 +      me.columnPreviewSize = me.itemAbsSize.y / me.itemAbsSize.x * 4 / 3;
 +      me.columnNameOrigin = me.columnPreviewOrigin + me.columnPreviewSize + me.realFontSize.x;
 +      me.columnNameSize = 1 - me.columnPreviewSize - 2 * me.realFontSize.x;
 +
 +      me.checkMarkSize = (eX * (me.itemAbsSize.y / me.itemAbsSize.x) + eY) * 0.5;
 +      me.checkMarkOrigin = eY + eX * (me.columnPreviewOrigin + me.columnPreviewSize) - me.checkMarkSize;
 +}
 +
 +void XonoticMapList_clickListBoxItem(entity me, float i, vector where)
 +{
 +      if(where.x <= me.columnPreviewOrigin + me.columnPreviewSize)
 +              if(where.x >= 0)
++              {
++                      m_play_click_sound(MENU_SOUND_SELECT);
 +                      me.g_maplistCacheToggle(me, i);
++              }
 +}
 +
 +void XonoticMapList_doubleClickListBoxItem(entity me, float i, vector where)
 +{
 +      if(where.x >= me.columnNameOrigin)
 +              if(where.x <= 1)
 +              {
 +                      // pop up map info screen
++                      m_play_click_sound(MENU_SOUND_OPEN);
 +                      main.mapInfoDialog.loadMapInfo(main.mapInfoDialog, i, me);
 +                      DialogOpenButton_Click_withCoords(NULL, main.mapInfoDialog, me.origin + eX * (me.columnNameOrigin * me.size.x) + eY * ((me.itemHeight * i - me.scrollPos) * me.size.y), eY * me.itemAbsSize.y + eX * (me.itemAbsSize.x * me.columnNameSize));
 +              }
 +}
 +
 +void XonoticMapList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
 +{
 +      // layout: Ping, Map name, Map name, NP, TP, MP
 +      string s;
 +      float theAlpha;
 +      float included;
 +
 +      if(!MapInfo_Get_ByID(i))
 +              return;
 +
 +      included = me.g_maplistCacheQuery(me, i);
 +      if(included || isSelected)
 +              theAlpha = SKINALPHA_MAPLIST_INCLUDEDFG;
 +      else
 +              theAlpha = SKINALPHA_MAPLIST_NOTINCLUDEDFG;
 +
 +      if(isSelected)
 +              draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
 +      else if(included)
 +              draw_Fill('0 0 0', '1 1 0', SKINCOLOR_MAPLIST_INCLUDEDBG, SKINALPHA_MAPLIST_INCLUDEDBG);
 +
 +      if(draw_PictureSize(strcat("/maps/", MapInfo_Map_bspname)) == '0 0 0')
 +              draw_Picture(me.columnPreviewOrigin * eX, "nopreview_map", me.columnPreviewSize * eX + eY, '1 1 1', theAlpha);
 +      else
 +              draw_Picture(me.columnPreviewOrigin * eX, strcat("/maps/", MapInfo_Map_bspname), me.columnPreviewSize * eX + eY, '1 1 1', theAlpha);
 +
 +      if(included)
 +              draw_Picture(me.checkMarkOrigin, "checkmark", me.checkMarkSize, '1 1 1', 1);
 +      s = draw_TextShortenToWidth(strdecolorize(MapInfo_Map_titlestring), me.columnNameSize, 0, me.realFontSize);
 +      draw_Text(me.realUpperMargin1 * eY + (me.columnNameOrigin + 0.00 * (me.columnNameSize - draw_TextWidth(s, 0, me.realFontSize))) * eX, s, me.realFontSize, SKINCOLOR_MAPLIST_TITLE, theAlpha, 0);
 +      s = draw_TextShortenToWidth(strdecolorize(MapInfo_Map_author), me.columnNameSize, 0,  me.realFontSize);
 +      draw_Text(me.realUpperMargin2 * eY + (me.columnNameOrigin + 1.00 * (me.columnNameSize - draw_TextWidth(s, 0, me.realFontSize))) * eX, s, me.realFontSize, SKINCOLOR_MAPLIST_AUTHOR, theAlpha, 0);
 +
 +      MapInfo_ClearTemps();
 +}
 +
 +void XonoticMapList_refilter(entity me)
 +{
 +      float i, j, n;
 +      string s;
 +      float gt, f;
 +      gt = MapInfo_CurrentGametype();
 +      f = MapInfo_CurrentFeatures();
 +      MapInfo_FilterGametype(gt, f, MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 0);
 +      me.nItems = MapInfo_count;
 +      for(i = 0; i < MapInfo_count; ++i)
 +              draw_PreloadPicture(strcat("/maps/", MapInfo_BSPName_ByID(i)));
 +      if(me.g_maplistCache)
 +              strunzone(me.g_maplistCache);
 +      s = "0";
 +      for(i = 1; i < MapInfo_count; i *= 2)
 +              s = strcat(s, s);
 +      n = tokenize_console(cvar_string("g_maplist"));
 +      for(i = 0; i < n; ++i)
 +      {
 +              j = MapInfo_FindName(argv(i));
 +              if(j >= 0)
 +                      s = strcat(
 +                              substring(s, 0, j),
 +                              "1",
 +                              substring(s, j+1, MapInfo_count - (j+1))
 +                      );
 +      }
 +      me.g_maplistCache = strzone(s);
 +      if(gt != me.lastGametype || f != me.lastFeatures)
 +      {
 +              me.lastGametype = gt;
 +              me.lastFeatures = f;
 +              me.setSelected(me, 0);
 +      }
 +}
 +
 +void XonoticMapList_refilterCallback(entity me, entity cb)
 +{
 +      me.refilter(me);
 +}
 +
 +void MapList_All(entity btn, entity me)
 +{
 +      float i;
 +      string s;
 +      MapInfo_FilterGametype(MAPINFO_TYPE_ALL, 0, 0, MapInfo_ForbiddenFlags(), 0); // all
 +      s = "";
 +      for(i = 0; i < MapInfo_count; ++i)
 +              s = strcat(s, " ", MapInfo_BSPName_ByID(i));
 +      cvar_set("g_maplist", substring(s, 1, strlen(s) - 1));
 +      me.refilter(me);
 +}
 +
 +void MapList_None(entity btn, entity me)
 +{
 +      cvar_set("g_maplist", "");
 +      me.refilter(me);
 +}
 +
 +void MapList_LoadMap(entity btn, entity me)
 +{
 +      string m;
 +      float i;
 +
 +      i = me.selectedItem;
 +
 +      if(btn.parent.instanceOfXonoticMapInfoDialog)
 +      {
 +              i = btn.parent.currentMapIndex;
 +              Dialog_Close(btn, btn.parent);
 +      }
 +
 +      if(i >= me.nItems || i < 0)
 +              return;
 +
 +      m = MapInfo_BSPName_ByID(i);
 +      if (!m)
 +      {
 +              print(_("Huh? Can't play this (m is NULL). Refiltering so this won't happen again.\n"));
 +              me.refilter(me);
 +              return;
 +      }
 +      if(MapInfo_CheckMap(m))
 +      {
 +              localcmd("\nmenu_loadmap_prepare\n");
 +              if(cvar("menu_use_default_hostname"))
 +                      localcmd("hostname \"", sprintf(_("%s's Xonotic Server"), strdecolorize(cvar_string("_cl_name"))), "\"\n");
 +              MapInfo_LoadMap(m, 1);
 +      }
 +      else
 +      {
 +              print(_("Huh? Can't play this (invalid game type). Refiltering so this won't happen again.\n"));
 +              me.refilter(me);
 +              return;
 +      }
 +}
 +
 +float XonoticMapList_keyDown(entity me, float scan, float ascii, float shift)
 +{
 +      string ch, save;
 +      if(me.nItems <= 0)
 +              return SUPER(XonoticMapList).keyDown(me, scan, ascii, shift);
 +      if(scan == K_MOUSE2 || scan == K_SPACE || scan == K_ENTER || scan == K_KP_ENTER)
 +      {
 +              // pop up map info screen
++              m_play_click_sound(MENU_SOUND_OPEN);
 +              main.mapInfoDialog.loadMapInfo(main.mapInfoDialog, me.selectedItem, me);
 +              DialogOpenButton_Click_withCoords(NULL, main.mapInfoDialog, me.origin + eX * (me.columnNameOrigin * me.size.x) + eY * ((me.itemHeight * me.selectedItem - me.scrollPos) * me.size.y), eY * me.itemAbsSize.y + eX * (me.itemAbsSize.x * me.columnNameSize));
 +      }
 +      else if(scan == K_MOUSE3 || scan == K_INS || scan == K_KP_INS)
 +      {
++              m_play_click_sound(MENU_SOUND_SELECT);
 +              me.g_maplistCacheToggle(me, me.selectedItem);
 +      }
 +      else if(ascii == 43) // +
 +      {
 +              if (!me.g_maplistCacheQuery(me, me.selectedItem))
++              {
++                      m_play_click_sound(MENU_SOUND_SELECT);
 +                      me.g_maplistCacheToggle(me, me.selectedItem);
++              }
 +      }
 +      else if(ascii == 45) // -
 +      {
 +              if(me.g_maplistCacheQuery(me, me.selectedItem))
++              {
++                      m_play_click_sound(MENU_SOUND_SELECT);
 +                      me.g_maplistCacheToggle(me, me.selectedItem);
++              }
 +      }
 +      else if(scan == K_BACKSPACE)
 +      {
 +              if(time < me.typeToSearchTime)
 +              {
 +                      save = substring(me.typeToSearchString, 0, strlen(me.typeToSearchString) - 1);
 +                      if(me.typeToSearchString)
 +                              strunzone(me.typeToSearchString);
 +                      me.typeToSearchString = strzone(save);
 +                      me.typeToSearchTime = time + 0.5;
 +                      if(strlen(me.typeToSearchString))
 +                      {
 +                              MapInfo_FindName(me.typeToSearchString);
 +                              if(MapInfo_FindName_firstResult >= 0)
 +                                      me.setSelected(me, MapInfo_FindName_firstResult);
 +                      }
 +              }
 +      }
 +      else if(ascii >= 32 && ascii != 127)
 +      {
 +              ch = chr(ascii);
 +              if(time > me.typeToSearchTime)
 +                      save = ch;
 +              else
 +                      save = strcat(me.typeToSearchString, ch);
 +              if(me.typeToSearchString)
 +                      strunzone(me.typeToSearchString);
 +              me.typeToSearchString = strzone(save);
 +              me.typeToSearchTime = time + 0.5;
 +              MapInfo_FindName(me.typeToSearchString);
 +              if(MapInfo_FindName_firstResult >= 0)
 +                      me.setSelected(me, MapInfo_FindName_firstResult);
 +      }
 +      else
 +              return SUPER(XonoticMapList).keyDown(me, scan, ascii, shift);
 +      return 1;
 +}
 +
 +#endif
index 8daf7302f7d469957763c3d6912564434172e615,0000000000000000000000000000000000000000..d4dc5af05df05e60fa6eaec11eaa50ff45e85365
mode 100644,000000..100644
--- /dev/null
@@@ -1,138 -1,0 +1,139 @@@
 +#ifdef INTERFACE
 +CLASS(XonoticPlayerList) EXTENDS(XonoticListBox)
 +      ATTRIB(XonoticPlayerList, rowsPerItem, float, 1)
 +      METHOD(XonoticPlayerList, resizeNotify, void(entity, vector, vector, vector, vector))
 +      METHOD(XonoticPlayerList, drawListBoxItem, void(entity, float, vector, float))
++      ATTRIB(XonoticPlayerList, allowFocusSound, float, 0)
 +      ATTRIB(XonoticPlayerList, realFontSize, vector, '0 0 0')
 +      ATTRIB(XonoticPlayerList, columnNameOrigin, float, 0)
 +      ATTRIB(XonoticPlayerList, columnNameSize, float, 0)
 +      ATTRIB(XonoticPlayerList, columnScoreOrigin, float, 0)
 +      ATTRIB(XonoticPlayerList, columnScoreSize, float, 0)
 +      ATTRIB(XonoticPlayerList, realUpperMargin, float, 0)
 +      ATTRIB(XonoticPlayerList, origin, vector, '0 0 0')
 +      ATTRIB(XonoticPlayerList, itemAbsSize, vector, '0 0 0')
 +      METHOD(XonoticPlayerList, setPlayerList, void(entity, string))
 +      METHOD(XonoticPlayerList, getPlayerList, string(entity, float, float))
 +      ATTRIB(XonoticPlayerList, playerList, float, -1)
 +ENDCLASS(XonoticPlayerList)
 +entity makeXonoticPlayerList();
 +#endif
 +
 +#ifdef IMPLEMENTATION
 +
 +const float PLAYERPARM_SCORE = 0;
 +const float PLAYERPARM_PING = 1;
 +const float PLAYERPARM_TEAM = 2;
 +const float PLAYERPARM_NAME = 3;
 +const float PLAYERPARM_COUNT = 4;
 +
 +entity makeXonoticPlayerList()
 +{
 +      entity me;
 +      me = spawnXonoticPlayerList();
 +      me.configureXonoticListBox(me);
 +      return me;
 +}
 +
 +void XonoticPlayerList_setPlayerList(entity me, string plist)
 +{
 +      int buf,i,n;
 +      string s;
 +
 +      buf = buf_create();
 +      me.nItems = tokenizebyseparator(plist, "\n");
 +      for(i = 0; i < me.nItems; ++i)
 +      {
 +              bufstr_set(buf, i * PLAYERPARM_COUNT + PLAYERPARM_NAME, argv(i)); // -666 100 "^4Nex ^2Player"
 +      }
 +
 +      for(i = 0; i < me.nItems; ++i)
 +      {
 +              s = bufstr_get(buf, i * PLAYERPARM_COUNT + PLAYERPARM_NAME);
 +              n = tokenize_console(s);
 +
 +              if(n == 4)
 +              {
 +                      bufstr_set(buf, i * PLAYERPARM_COUNT + PLAYERPARM_SCORE, argv(0)); // -666
 +                      bufstr_set(buf, i * PLAYERPARM_COUNT + PLAYERPARM_PING,  argv(1)); // 100
 +                      bufstr_set(buf, i * PLAYERPARM_COUNT + PLAYERPARM_TEAM,  argv(2)); // 0 for spec, else 1, 2, 3, 4
 +                      bufstr_set(buf, i * PLAYERPARM_COUNT + PLAYERPARM_NAME,  argv(3)); // ^4Nex ^2Player
 +              }
 +              else
 +              {
 +                      bufstr_set(buf, i * PLAYERPARM_COUNT + PLAYERPARM_SCORE, argv(0)); // -666
 +                      bufstr_set(buf, i * PLAYERPARM_COUNT + PLAYERPARM_PING,  argv(1)); // 100
 +                      bufstr_set(buf, i * PLAYERPARM_COUNT + PLAYERPARM_TEAM,  "-1");
 +                      bufstr_set(buf, i * PLAYERPARM_COUNT + PLAYERPARM_NAME,  argv(2)); // ^4Nex ^2Player
 +              }
 +      }
 +      me.playerList = buf;
 +}
 +
 +string XonoticPlayerList_getPlayerList(entity me, float i, float key)
 +{
 +      return bufstr_get(me.playerList, i * PLAYERPARM_COUNT + key);
 +}
 +
 +void XonoticPlayerList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
 +{
 +      me.itemAbsSize = '0 0 0';
 +      SUPER(XonoticPlayerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
 +
 +      me.realFontSize_y = me.fontSize / (me.itemAbsSize_y = (absSize.y * me.itemHeight));
 +      me.realFontSize_x = me.fontSize / (me.itemAbsSize_x = (absSize.x * (1 - me.controlWidth)));
 +      me.realUpperMargin = 0.5 * (1 - me.realFontSize.y);
 +
 +      // this list does 1 char left and right margin
 +      me.columnScoreSize = 5 * me.realFontSize.x;
 +      me.columnNameSize = 1 - 3 * me.realFontSize.x - me.columnScoreSize;
 +
 +      me.columnNameOrigin = me.realFontSize.x;
 +      me.columnScoreOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize.x;
 +}
 +
 +void XonoticPlayerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
 +{
 +      string s;
 +      string score;
 +      float t;
 +      vector rgb;
 +
 +      t = stof(me.getPlayerList(me, i, PLAYERPARM_TEAM));
 +      if(t == 1)
 +              rgb = colormapPaletteColor(4, 0);
 +      else if(t == 2)
 +              rgb = colormapPaletteColor(13, 0);
 +      else if(t == 3)
 +              rgb = colormapPaletteColor(12, 0);
 +      else if(t == 4)
 +              rgb = colormapPaletteColor(9, 0);
 +      else
 +              rgb = SKINCOLOR_TEXT;
 +
 +      s = me.getPlayerList(me, i, PLAYERPARM_NAME);
 +      score = me.getPlayerList(me, i, PLAYERPARM_SCORE);
 +
 +      if(substring(score, strlen(score) - 10, 10) == ":spectator")
 +      {
 +              score = _("spectator");
 +      }
 +      else
 +      {
 +              if((t = strstrofs(score, ":", 0)) >= 0)
 +                      score = substring(score, 0, t);
 +              if((t = strstrofs(score, ",", 0)) >= 0)
 +                      score = substring(score, 0, t);
 +
 +              if(stof(score) == -666)
 +                      score = _("spectator");
 +      }
 +
 +      s = draw_TextShortenToWidth(s, me.columnNameSize, 1, me.realFontSize);
 +      draw_Text(me.realUpperMargin2 * eY + (me.columnNameOrigin + 0.00 * (me.columnNameSize - draw_TextWidth(s, 1, me.realFontSize))) * eX, s, me.realFontSize, '1 1 1', 1, 1);
 +
 +      score = draw_TextShortenToWidth(score, me.columnScoreSize, 0, me.realFontSize);
 +      draw_Text(me.realUpperMargin2 * eY + (me.columnScoreOrigin + 1.00 * (me.columnScoreSize - draw_TextWidth(score, 1, me.realFontSize))) * eX, score, me.realFontSize, rgb, 1, 0);
 +}
 +
 +#endif
index db0fbfe411b7e2c123bf818bb972b2a5d3602375,0000000000000000000000000000000000000000..c6c53cc8e01f4afa88df9f5ac838840b7ed1120c
mode 100644,000000..100644
--- /dev/null
@@@ -1,1314 -1,0 +1,1317 @@@
 +#ifdef INTERFACE
 +CLASS(XonoticServerList) EXTENDS(XonoticListBox)
 +      METHOD(XonoticServerList, configureXonoticServerList, void(entity))
 +      ATTRIB(XonoticServerList, rowsPerItem, float, 1)
 +      METHOD(XonoticServerList, draw, void(entity))
 +      METHOD(XonoticServerList, drawListBoxItem, void(entity, float, vector, float))
 +      METHOD(XonoticServerList, doubleClickListBoxItem, 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)
 +
 +      ATTRIB(XonoticServerList, realFontSize, vector, '0 0 0')
 +      ATTRIB(XonoticServerList, realUpperMargin, float, 0)
 +      ATTRIB(XonoticServerList, columnIconsOrigin, float, 0)
 +      ATTRIB(XonoticServerList, columnIconsSize, float, 0)
 +      ATTRIB(XonoticServerList, columnPingOrigin, float, 0)
 +      ATTRIB(XonoticServerList, columnPingSize, float, 0)
 +      ATTRIB(XonoticServerList, columnNameOrigin, float, 0)
 +      ATTRIB(XonoticServerList, columnNameSize, float, 0)
 +      ATTRIB(XonoticServerList, columnMapOrigin, float, 0)
 +      ATTRIB(XonoticServerList, columnMapSize, float, 0)
 +      ATTRIB(XonoticServerList, columnTypeOrigin, float, 0)
 +      ATTRIB(XonoticServerList, columnTypeSize, float, 0)
 +      ATTRIB(XonoticServerList, columnPlayersOrigin, float, 0)
 +      ATTRIB(XonoticServerList, columnPlayersSize, float, 0)
 +
 +      ATTRIB(XonoticServerList, selectedServer, string, string_null) // to restore selected server when needed
 +      METHOD(XonoticServerList, setSelected, void(entity, float))
 +      METHOD(XonoticServerList, setSortOrder, void(entity, float, float))
 +      ATTRIB(XonoticServerList, filterShowEmpty, float, 1)
 +      ATTRIB(XonoticServerList, filterShowFull, float, 1)
 +      ATTRIB(XonoticServerList, filterString, string, string_null)
 +      ATTRIB(XonoticServerList, controlledTextbox, entity, NULL)
 +      ATTRIB(XonoticServerList, ipAddressBox, entity, NULL)
 +      ATTRIB(XonoticServerList, favoriteButton, entity, NULL)
 +      ATTRIB(XonoticServerList, nextRefreshTime, float, 0)
 +      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, sortButton1, entity, NULL)
 +      ATTRIB(XonoticServerList, sortButton2, entity, NULL)
 +      ATTRIB(XonoticServerList, sortButton3, entity, NULL)
 +      ATTRIB(XonoticServerList, sortButton4, entity, NULL)
 +      ATTRIB(XonoticServerList, sortButton5, entity, NULL)
 +      ATTRIB(XonoticServerList, connectButton, entity, NULL)
 +      ATTRIB(XonoticServerList, infoButton, entity, NULL)
 +      ATTRIB(XonoticServerList, currentSortOrder, float, 0)
 +      ATTRIB(XonoticServerList, currentSortField, float, -1)
 +
 +      ATTRIB(XonoticServerList, ipAddressBoxFocused, float, -1)
 +
 +      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);
 +void ServerList_Favorite_Click(entity btn, entity me);
 +void ServerList_Info_Click(entity btn, entity me);
 +void ServerList_Update_favoriteButton(entity btn, entity me);
 +
 +// fields for category entities
 +const float MAX_CATEGORIES = 9;
 +const float 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_INSTAGIB,     "",            "CAT_SERVERS",  ZCTX(_("SLCAT^InstaGib 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; \
 +      string SLIST_CATEGORY_AUTOCVAR(name) = enoverride;
 +SLIST_CATEGORIES
 +#undef SLIST_CATEGORY
 +
 +#endif
 +#endif
 +#ifdef IMPLEMENTATION
 +
 +void RegisterSLCategories()
 +{
 +      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 \
 +                              { \
 +                                      printf( \
 +                                              "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;
 +      srv = netaddress_resolve(srv, 26000);
 +      if(srv == "")
 +              return false;
 +      p = crypto_getidfp(srv);
 +      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)
 +              {
 +                      if(p)
 +                              if(argv(i) == p)
 +                                      return true;
 +              }
 +              else
 +              {
 +                      if(srv == netaddress_resolve(argv(i), 26000))
 +                              return true;
 +              }
 +      }
 +      return false;
 +}
 +
 +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":
 +                      case "instagib": { return CAT_INSTAGIB; }
 +                      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: { dprintf("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;
 +      srv_resolved = netaddress_resolve(srv, 26000);
 +      p = crypto_getidfp(srv_resolved);
 +      s = cvar_string("net_slist_favorites");
 +      n = tokenize_console(s);
 +      f = 0;
 +      for(i = 0; i < n; ++i)
 +      {
 +              if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
 +              {
 +                      if(p)
 +                              if(argv(i) != p)
 +                                      continue;
 +              }
 +              else
 +              {
 +                      if(srv_resolved != netaddress_resolve(argv(i), 26000))
 +                              continue;
 +              }
 +              s0 = s1 = s2 = "";
 +              if(i > 0)
 +                      s0 = substring(s, 0, argv_end_index(i - 1));
 +              if(i < n-1)
 +                      s2 = substring(s, argv_start_index(i + 1), -1);
 +              if(s0 != "" && s2 != "")
 +                      s1 = " ";
 +              cvar_set("net_slist_favorites", strcat(s0, s1, s2));
 +              s = cvar_string("net_slist_favorites");
 +              n = tokenize_console(s);
 +              f = 1;
 +              --i;
 +      }
 +
 +      if(!f)
 +      {
 +              s1 = "";
 +              if(s != "")
 +                      s1 = " ";
 +              if(p)
 +                      cvar_set("net_slist_favorites", strcat(s, s1, p));
 +              else
 +                      cvar_set("net_slist_favorites", strcat(s, s1, srv));
 +      }
 +
 +      me.refreshServerList(me, REFRESHSERVERLIST_RESORT);
 +}
 +
 +void ServerList_Update_favoriteButton(entity btn, entity me)
 +{
 +      me.favoriteButton.setText(me.favoriteButton,
 +              (IsFavorite(me.ipAddressBox.text) ?
 +                      _("Remove") : _("Favorite")
 +              )
 +      );
 +}
 +
 +entity makeXonoticServerList()
 +{
 +      entity me;
 +      me = spawnXonoticServerList();
 +      me.configureXonoticServerList(me);
 +      return me;
 +}
 +void XonoticServerList_configureXonoticServerList(entity me)
 +{
 +      me.configureXonoticListBox(me);
 +
 +      // 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)
 +{
 +      float save;
 +      save = me.selectedItem;
 +      SUPER(XonoticServerList).setSelected(me, i);
 +      /*
 +      if(me.selectedItem == save)
 +              return;
 +      */
 +      if(me.nItems == 0)
 +              return;
 +      if(gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT) != me.nItems)
 +              return; // sorry, it would be wrong
 +
 +      if(me.selectedServer)
 +              strunzone(me.selectedServer);
 +      me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, me.selectedItem));
 +
 +      me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
 +      me.ipAddressBox.cursorPos = strlen(me.selectedServer);
 +      me.ipAddressBoxFocused = -1;
 +}
 +void XonoticServerList_refreshServerList(entity me, float mode)
 +{
 +      //print("refresh of type ", ftos(mode), "\n");
 +
 +      if(mode >= REFRESHSERVERLIST_REFILTER)
 +      {
 +              float m, i, n;
 +              float listflags = 0;
 +              string s, typestr, modstr;
 +
 +              s = me.filterString;
 +
 +              m = strstrofs(s, ":", 0);
 +              if(m >= 0)
 +              {
 +                      typestr = substring(s, 0, m);
 +                      s = substring(s, m + 1, strlen(s) - m - 1);
 +                      while(substring(s, 0, 1) == " ")
 +                              s = substring(s, 1, strlen(s) - 1);
 +              }
 +              else
 +                      typestr = "";
 +
 +              modstr = cvar_string("menu_slist_modfilter");
 +
 +              m = SLIST_MASK_AND - 1;
 +              resethostcachemasks();
 +
 +              // ping: reject negative ping (no idea why this happens in the first place, engine bug)
 +              sethostcachemasknumber(++m, SLIST_FIELD_PING, 0, SLIST_TEST_GREATEREQUAL);
 +
 +              // show full button
 +              if(!me.filterShowFull)
 +              {
 +                      sethostcachemasknumber(++m, SLIST_FIELD_FREESLOTS, 1, SLIST_TEST_GREATEREQUAL); // legacy
 +                      sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, ":S0:", SLIST_TEST_NOTCONTAIN); // g_maxplayers support
 +              }
 +
 +              // show empty button
 +              if(!me.filterShowEmpty)
 +                      sethostcachemasknumber(++m, SLIST_FIELD_NUMHUMANS, 1, SLIST_TEST_GREATEREQUAL);
 +
 +              // gametype filtering
 +              if(typestr != "")
 +                      sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(typestr, ":"), SLIST_TEST_STARTSWITH);
 +
 +              // mod filtering
 +              if(modstr != "")
 +              {
 +                      if(substring(modstr, 0, 1) == "!")
 +                              sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(substring(modstr, 1, strlen(modstr) - 1)), SLIST_TEST_NOTEQUAL);
 +                      else
 +                              sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(modstr), SLIST_TEST_EQUAL);
 +              }
 +
 +              // server banning
 +              n = tokenizebyseparator(_Nex_ExtResponseSystem_BannedServers, " ");
 +              for(i = 0; i < n; ++i)
 +                      if(argv(i) != "")
 +                              sethostcachemaskstring(++m, SLIST_FIELD_CNAME, argv(i), SLIST_TEST_NOTSTARTSWITH);
 +
 +              m = SLIST_MASK_OR - 1;
 +              if(s != "")
 +              {
 +                      sethostcachemaskstring(++m, SLIST_FIELD_NAME, s, SLIST_TEST_CONTAINS);
 +                      sethostcachemaskstring(++m, SLIST_FIELD_MAP, s, SLIST_TEST_CONTAINS);
 +                      sethostcachemaskstring(++m, SLIST_FIELD_PLAYERS, s, SLIST_TEST_CONTAINS);
 +                      sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(s, ":"), SLIST_TEST_STARTSWITH);
 +              }
 +
 +              // 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)
 +{
++      SUPER(XonoticServerList).focusEnter(me);
 +      if(time < me.nextRefreshTime)
 +      {
 +              //print("sorry, no refresh yet\n");
 +              return;
 +      }
 +      me.nextRefreshTime = time + 10;
 +      me.refreshServerList(me, REFRESHSERVERLIST_ASK);
 +}
 +
 +void XonoticServerList_draw(entity me)
 +{
 +      float i, found, owned;
 +
 +      if(_Nex_ExtResponseSystem_BannedServersNeedsRefresh)
 +      {
 +              if(!me.needsRefresh)
 +                      me.needsRefresh = 2;
 +              _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, REFRESHSERVERLIST_RESET);
 +      }
 +      else if(me.needsRefresh == 1)
 +      {
 +              me.needsRefresh = 2; // delay by one frame to make sure "slist" has been executed
 +      }
 +      else if(me.needsRefresh == 2)
 +      {
 +              me.needsRefresh = 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 != ""));
 +
 +      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);
 +      me.favoriteButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
 +
 +      found = 0;
 +      if(me.selectedServer)
 +      {
 +              for(i = 0; i < me.nItems; ++i)
 +              {
 +                      if(gethostcachestring(SLIST_FIELD_CNAME, i) == me.selectedServer)
 +                      {
 +                              me.selectedItem = i;
 +                              found = 1;
 +                              break;
 +                      }
 +              }
 +      }
 +      if(!found)
 +      {
 +              if(me.nItems > 0)
 +              {
 +                      if(me.selectedItem >= me.nItems)
 +                              me.selectedItem = me.nItems - 1;
 +                      if(me.selectedServer)
 +                              strunzone(me.selectedServer);
 +                      me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, me.selectedItem));
 +              }
 +      }
 +
 +      if(owned)
 +      {
 +              if(me.selectedServer != me.ipAddressBox.text)
 +              {
 +                      me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
 +                      me.ipAddressBox.cursorPos = strlen(me.selectedServer);
 +                      me.ipAddressBoxFocused = -1;
 +              }
 +      }
 +
 +      if(me.ipAddressBoxFocused != me.ipAddressBox.focused)
 +      {
 +              if(me.ipAddressBox.focused || me.ipAddressBoxFocused < 0)
 +                      ServerList_Update_favoriteButton(NULL, me);
 +              me.ipAddressBoxFocused = me.ipAddressBox.focused;
 +      }
 +
 +      SUPER(XonoticServerList).draw(me);
 +}
 +void ServerList_PingSort_Click(entity btn, entity me)
 +{
 +      me.setSortOrder(me, SLIST_FIELD_PING, +1);
 +}
 +void ServerList_NameSort_Click(entity btn, entity me)
 +{
 +      me.setSortOrder(me, SLIST_FIELD_NAME, -1); // why?
 +}
 +void ServerList_MapSort_Click(entity btn, entity me)
 +{
 +      me.setSortOrder(me, SLIST_FIELD_MAP, -1); // why?
 +}
 +void ServerList_PlayerSort_Click(entity btn, entity me)
 +{
 +      me.setSortOrder(me, SLIST_FIELD_NUMHUMANS, -1);
 +}
 +void ServerList_TypeSort_Click(entity btn, entity me)
 +{
 +      string s, t;
 +      float i, m;
 +      s = me.filterString;
 +      m = strstrofs(s, ":", 0);
 +      if(m >= 0)
 +      {
 +              s = substring(s, 0, m);
 +              while(substring(s, m+1, 1) == " ") // skip spaces
 +                      ++m;
 +      }
 +      else
 +              s = "";
 +
 +      for(i = 1; ; i *= 2) // 20 modes ought to be enough for anyone
 +      {
 +              t = MapInfo_Type_ToString(i);
 +              if(i > 1)
 +                      if(t == "") // it repeats (default case)
 +                      {
 +                              // no type was found
 +                              // choose the first one
 +                              s = MapInfo_Type_ToString(1);
 +                              break;
 +                      }
 +              if(s == t)
 +              {
 +                      // the type was found
 +                      // choose the next one
 +                      s = MapInfo_Type_ToString(i * 2);
 +                      if(s == "")
 +                              s = MapInfo_Type_ToString(1);
 +                      break;
 +              }
 +      }
 +
 +      if(s != "")
 +              s = strcat(s, ":");
 +      s = strcat(s, substring(me.filterString, m+1, strlen(me.filterString) - m - 1));
 +
 +      me.controlledTextbox.setText(me.controlledTextbox, s);
 +      me.controlledTextbox.keyDown(me.controlledTextbox, K_END, 0, 0);
 +      me.controlledTextbox.keyUp(me.controlledTextbox, K_END, 0, 0);
 +      //ServerList_Filter_Change(me.controlledTextbox, me);
 +}
 +void ServerList_Filter_Change(entity box, entity me)
 +{
 +      if(me.filterString)
 +              strunzone(me.filterString);
 +      if(box.text != "")
 +              me.filterString = strzone(box.text);
 +      else
 +              me.filterString = string_null;
 +      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;
 +      me.ipAddressBoxFocused = -1;
 +}
 +void ServerList_ShowEmpty_Click(entity box, entity me)
 +{
 +      box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
 +      me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
 +
 +      me.ipAddressBox.setText(me.ipAddressBox, "");
 +      me.ipAddressBox.cursorPos = 0;
 +      me.ipAddressBoxFocused = -1;
 +}
 +void ServerList_ShowFull_Click(entity box, entity me)
 +{
 +      box.setChecked(box, me.filterShowFull = !me.filterShowFull);
 +      me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
 +
 +      me.ipAddressBox.setText(me.ipAddressBox, "");
 +      me.ipAddressBox.cursorPos = 0;
 +      me.ipAddressBoxFocused = -1;
 +}
 +void XonoticServerList_setSortOrder(entity me, float fld, float direction)
 +{
 +      if(me.currentSortField == fld)
 +              direction = -me.currentSortOrder;
 +      me.currentSortOrder = direction;
 +      me.currentSortField = fld;
 +      me.sortButton1.forcePressed = (fld == SLIST_FIELD_PING);
 +      me.sortButton2.forcePressed = (fld == SLIST_FIELD_NAME);
 +      me.sortButton3.forcePressed = (fld == SLIST_FIELD_MAP);
 +      me.sortButton4.forcePressed = 0;
 +      me.sortButton5.forcePressed = (fld == SLIST_FIELD_NUMHUMANS);
 +      me.selectedItem = 0;
 +      if(me.selectedServer)
 +              strunzone(me.selectedServer);
 +      me.selectedServer = string_null;
 +      me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
 +}
 +void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
 +{
 +      vector originInLBSpace, sizeInLBSpace;
 +      originInLBSpace = eY * (-me.itemHeight);
 +      sizeInLBSpace = eY * me.itemHeight + eX * (1 - me.controlWidth);
 +
 +      vector originInDialogSpace, sizeInDialogSpace;
 +      originInDialogSpace = boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
 +      sizeInDialogSpace = boxToGlobalSize(sizeInLBSpace, me.Container_size);
 +
 +      btn.Container_origin_x = originInDialogSpace.x + sizeInDialogSpace.x * theOrigin;
 +      btn.Container_size_x   =                         sizeInDialogSpace.x * theSize;
 +      btn.setText(btn, theTitle);
 +      btn.onClick = theFunc;
 +      btn.onClickEntity = me;
 +      btn.resized = 1;
 +}
 +void XonoticServerList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
 +{
 +      SUPER(XonoticServerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
 +
 +      me.realFontSize_y = me.fontSize / (absSize.y * me.itemHeight);
 +      me.realFontSize_x = me.fontSize / (absSize.x * (1 - me.controlWidth));
 +      me.realUpperMargin = 0.5 * (1 - me.realFontSize.y);
 +
 +      me.columnIconsOrigin = 0;
 +      me.columnIconsSize = me.realFontSize.x * 4 * me.iconsSizeFactor;
 +      me.columnPingSize = me.realFontSize.x * 3;
 +      me.columnMapSize = me.realFontSize.x * 10;
 +      me.columnTypeSize = me.realFontSize.x * 4;
 +      me.columnPlayersSize = me.realFontSize.x * 5;
 +      me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - me.columnIconsSize - me.columnTypeSize - 5 * me.realFontSize.x;
 +      me.columnPingOrigin = me.columnIconsOrigin + me.columnIconsSize + me.realFontSize.x;
 +      me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize.x;
 +      me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize.x;
 +      me.columnTypeOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize.x;
 +      me.columnPlayersOrigin = me.columnTypeOrigin + me.columnTypeSize + me.realFontSize.x;
 +
 +      me.positionSortButton(me, me.sortButton1, me.columnPingOrigin, me.columnPingSize, _("Ping"), ServerList_PingSort_Click);
 +      me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, _("Host name"), ServerList_NameSort_Click);
 +      me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, _("Map"), ServerList_MapSort_Click);
 +      me.positionSortButton(me, me.sortButton4, me.columnTypeOrigin, me.columnTypeSize, _("Type"), ServerList_TypeSort_Click);
 +      me.positionSortButton(me, me.sortButton5, me.columnPlayersOrigin, me.columnPlayersSize, _("Players"), ServerList_PlayerSort_Click);
 +
 +      float f;
 +      f = me.currentSortField;
 +      if(f >= 0)
 +      {
 +              me.currentSortField = -1;
 +              me.setSortOrder(me, f, me.currentSortOrder); // force resetting the sort order
 +      }
 +}
 +void ServerList_Connect_Click(entity btn, entity me)
 +{
 +      localcmd(sprintf("connect %s\n",
 +              ((me.ipAddressBox.text != "") ?
 +                      me.ipAddressBox.text : me.selectedServer
 +              )
 +      ));
 +}
 +void ServerList_Favorite_Click(entity btn, entity me)
 +{
 +      string ipstr;
 +      ipstr = netaddress_resolve(me.ipAddressBox.text, 26000);
 +      if(ipstr != "")
 +      {
++              m_play_click_sound(MENU_SOUND_SELECT);
 +              me.toggleFavorite(me, me.ipAddressBox.text);
 +              me.ipAddressBoxFocused = -1;
 +      }
 +}
 +void ServerList_Info_Click(entity btn, entity me)
 +{
 +      if (me.nItems != 0)
 +              main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, me.selectedItem);
 +
 +      vector org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
 +      vector sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
 +      DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
 +}
 +void XonoticServerList_doubleClickListBoxItem(entity me, float i, vector where)
 +{
 +      ServerList_Connect_Click(NULL, me);
 +}
 +void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
 +{
 +      // layout: Ping, Server name, Map name, NP, TP, MP
 +      float p, q;
 +      float isv4, isv6;
 +      vector theColor;
 +      float theAlpha;
 +      float m, pure, freeslots, j, sflags;
 +      string s, typestr, versionstr, k, v, modname;
 +
 +      //printf("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,
 +                              SKINCOLOR_SERVERLIST_CATEGORY,
 +                              SKINALPHA_SERVERLIST_CATEGORY,
 +                              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);
 +
 +      s = gethostcachestring(SLIST_FIELD_QCSTATUS, i);
 +      m = tokenizebyseparator(s, ":");
 +      typestr = "";
 +      if(m >= 2)
 +      {
 +              typestr = argv(0);
 +              versionstr = argv(1);
 +      }
 +      freeslots = -1;
 +      sflags = -1;
 +      modname = "";
 +      pure = 0;
 +      for(j = 2; j < m; ++j)
 +      {
 +              if(argv(j) == "")
 +                      break;
 +              k = substring(argv(j), 0, 1);
 +              v = substring(argv(j), 1, -1);
 +              if(k == "P")
 +                      pure = stof(v);
 +              else if(k == "S")
 +                      freeslots = stof(v);
 +              else if(k == "F")
 +                      sflags = stof(v);
 +              else if(k == "M")
 +                      modname = v;
 +      }
 +
 +#ifdef COMPAT_NO_MOD_IS_XONOTIC
 +      if(modname == "")
 +              modname = "Xonotic";
 +#endif
 +
 +      /*
 +      SLIST_FIELD_MOD = gethostcacheindexforkey("mod");
 +      s = gethostcachestring(SLIST_FIELD_MOD, i);
 +      if(s != "data")
 +              if(modname == "Xonotic")
 +                      modname = s;
 +      */
 +
 +      // list the mods here on which the pure server check actually works
 +      if(modname != "Xonotic")
 +      if(modname != "InstaGib" || modname != "MinstaGib")
 +      if(modname != "CTS")
 +      if(modname != "NIX")
 +      if(modname != "NewToys")
 +              pure = 0;
 +
 +      if(gethostcachenumber(SLIST_FIELD_FREESLOTS, i) <= 0)
 +              theAlpha = SKINALPHA_SERVERLIST_FULL;
 +      else if(freeslots == 0)
 +              theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
 +      else if (!gethostcachenumber(SLIST_FIELD_NUMHUMANS, i))
 +              theAlpha = SKINALPHA_SERVERLIST_EMPTY;
 +      else
 +              theAlpha = 1;
 +
 +      p = gethostcachenumber(SLIST_FIELD_PING, i);
 +      const float PING_LOW = 75;
 +      const float PING_MED = 200;
 +      const float PING_HIGH = 500;
 +      if(p < PING_LOW)
 +              theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
 +      else if(p < PING_MED)
 +              theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
 +      else if(p < PING_HIGH)
 +      {
 +              theColor = SKINCOLOR_SERVERLIST_HIGHPING;
 +              theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
 +      }
 +      else
 +      {
 +              theColor = eX;
 +              theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
 +      }
 +
 +      if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, i))
 +      {
 +              theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
 +              theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
 +      }
 +
 +      s = gethostcachestring(SLIST_FIELD_CNAME, i);
 +
 +      isv4 = isv6 = 0;
 +      if(substring(s, 0, 1) == "[")
 +      {
 +              isv6 = 1;
 +              me.seenIPv6 += 1;
 +      }
 +      else if(strstrofs("0123456789", substring(s, 0, 1), 0) >= 0)
 +      {
 +              isv4 = 1;
 +              me.seenIPv4 += 1;
 +      }
 +
 +      q = stof(substring(crypto_getencryptlevel(s), 0, 1));
 +      if((q <= 0 && cvar("crypto_aeslevel") >= 3) || (q >= 3 && cvar("crypto_aeslevel") <= 0))
 +      {
 +              theColor = SKINCOLOR_SERVERLIST_IMPOSSIBLE;
 +              theAlpha = SKINALPHA_SERVERLIST_IMPOSSIBLE;
 +      }
 +
 +      if(q == 1)
 +      {
 +              if(cvar("crypto_aeslevel") >= 2)
 +                      q |= 4;
 +      }
 +      if(q == 2)
 +      {
 +              if(cvar("crypto_aeslevel") >= 1)
 +                      q |= 4;
 +      }
 +      if(q == 3)
 +              q = 5;
 +      else if(q >= 3)
 +              q -= 2;
 +      // possible status:
 +      // 0: crypto off
 +      // 1: AES possible
 +      // 2: AES recommended but not available
 +      // 3: AES possible and will be used
 +      // 4: AES recommended and will be used
 +      // 5: AES required
 +
 +      // --------------
 +      //  RENDER ICONS
 +      // --------------
 +      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;
 +
 +      string n;
 +
 +      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(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(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(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)
 +{
 +      vector org, sz;
 +
 +      org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
 +      sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
 +
 +      if(scan == K_ENTER || scan == K_KP_ENTER)
 +      {
 +              ServerList_Connect_Click(NULL, me);
 +              return 1;
 +      }
 +      else if(scan == K_MOUSE2 || scan == K_SPACE)
 +      {
 +              if(me.nItems != 0)
 +              {
++                      m_play_click_sound(MENU_SOUND_OPEN);
 +                      main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, me.selectedItem);
 +                      DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
 +                      return 1;
 +              }
 +              return 0;
 +      }
 +      else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
 +      {
 +              if(me.nItems != 0)
 +              {
 +                      me.toggleFavorite(me, me.selectedServer);
 +                      me.ipAddressBoxFocused = -1;
 +                      return 1;
 +              }
 +              return 0;
 +      }
 +      else if(SUPER(XonoticServerList).keyDown(me, scan, ascii, shift))
 +              return 1;
 +      else if(!me.controlledTextbox)
 +              return 0;
 +      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 c0c05a805e67afa480ee67469a438118e528b8f4,0000000000000000000000000000000000000000..9990c83fe162ee2c2ab4f37ea7e1c43a1fcf24ce
mode 100644,000000..100644
--- /dev/null
@@@ -1,196 -1,0 +1,199 @@@
-       if(scan == K_ENTER || scan == K_KP_ENTER) {
 +#ifdef INTERFACE
 +CLASS(XonoticSkinList) EXTENDS(XonoticListBox)
 +      METHOD(XonoticSkinList, configureXonoticSkinList, void(entity))
 +      ATTRIB(XonoticSkinList, rowsPerItem, float, 4)
 +      METHOD(XonoticSkinList, resizeNotify, void(entity, vector, vector, vector, vector))
 +      METHOD(XonoticSkinList, drawListBoxItem, void(entity, float, vector, float))
 +      METHOD(XonoticSkinList, getSkins, void(entity))
 +      METHOD(XonoticSkinList, setSkin, void(entity))
 +      METHOD(XonoticSkinList, loadCvars, void(entity))
 +      METHOD(XonoticSkinList, saveCvars, void(entity))
 +      METHOD(XonoticSkinList, skinParameter, string(entity, float, float))
 +      METHOD(XonoticSkinList, doubleClickListBoxItem, void(entity, float, vector))
 +      METHOD(XonoticSkinList, keyDown, float(entity, float, float, float))
 +      METHOD(XonoticSkinList, destroy, void(entity))
 +
 +      ATTRIB(XonoticSkinList, skinlist, float, -1)
 +      ATTRIB(XonoticSkinList, realFontSize, vector, '0 0 0')
 +      ATTRIB(XonoticSkinList, columnPreviewOrigin, float, 0)
 +      ATTRIB(XonoticSkinList, columnPreviewSize, float, 0)
 +      ATTRIB(XonoticSkinList, columnNameOrigin, float, 0)
 +      ATTRIB(XonoticSkinList, columnNameSize, float, 0)
 +      ATTRIB(XonoticSkinList, realUpperMargin1, float, 0)
 +      ATTRIB(XonoticSkinList, realUpperMargin2, float, 0)
 +      ATTRIB(XonoticSkinList, origin, vector, '0 0 0')
 +      ATTRIB(XonoticSkinList, itemAbsSize, vector, '0 0 0')
 +
 +      ATTRIB(XonoticSkinList, name, string, "skinselector")
 +ENDCLASS(XonoticSkinList)
 +
 +entity makeXonoticSkinList();
 +void SetSkin_Click(entity btn, entity me);
 +#endif
 +
 +#ifdef IMPLEMENTATION
 +
 +const float SKINPARM_NAME = 0;
 +const float SKINPARM_TITLE = 1;
 +const float SKINPARM_AUTHOR = 2;
 +const float SKINPARM_PREVIEW = 3;
 +const float SKINPARM_COUNT = 4;
 +
 +entity makeXonoticSkinList()
 +{
 +      entity me;
 +      me = spawnXonoticSkinList();
 +      me.configureXonoticSkinList(me);
 +      return me;
 +}
 +
 +void XonoticSkinList_configureXonoticSkinList(entity me)
 +{
 +      me.configureXonoticListBox(me);
 +      me.getSkins(me);
 +      me.loadCvars(me);
 +}
 +
 +void XonoticSkinList_loadCvars(entity me)
 +{
 +      string s;
 +      float i, n;
 +      s = cvar_string("menu_skin");
 +      n = me.nItems;
 +      for(i = 0; i < n; ++i)
 +      {
 +              if(me.skinParameter(me, i, SKINPARM_NAME) == s)
 +              {
 +                      me.selectedItem = i;
 +                      break;
 +              }
 +      }
 +}
 +
 +void XonoticSkinList_saveCvars(entity me)
 +{
 +      cvar_set("menu_skin", me.skinParameter(me, me.selectedItem, SKINPARM_NAME));
 +}
 +
 +string XonoticSkinList_skinParameter(entity me, float i, float key)
 +{
 +      return bufstr_get(me.skinlist, i * SKINPARM_COUNT + key);
 +}
 +
 +void XonoticSkinList_getSkins(entity me)
 +{
 +      float glob, buf, i, n, fh;
 +      string s;
 +
 +      buf = buf_create();
 +      glob = search_begin("gfx/menu/*/skinvalues.txt", true, true);
 +      if(glob < 0)
 +      {
 +              me.skinlist = buf;
 +              me.nItems = 0;
 +              return;
 +      }
 +
 +      n = search_getsize(glob);
 +      for(i = 0; i < n; ++i)
 +      {
 +              s = search_getfilename(glob, i);
 +              bufstr_set(buf, i * SKINPARM_COUNT + SKINPARM_NAME, substring(s, 9, strlen(s) - 24)); // the * part
 +              bufstr_set(buf, i * SKINPARM_COUNT + SKINPARM_TITLE, _("<TITLE>"));
 +              bufstr_set(buf, i * SKINPARM_COUNT + SKINPARM_AUTHOR, _("<AUTHOR>"));
 +              if(draw_PictureSize(strcat("/gfx/menu/", substring(s, 9, strlen(s) - 24), "/skinpreview")) == '0 0 0')
 +                      bufstr_set(buf, i * SKINPARM_COUNT + SKINPARM_PREVIEW, "nopreview_menuskin");
 +              else
 +                      bufstr_set(buf, i * SKINPARM_COUNT + SKINPARM_PREVIEW, strcat("/gfx/menu/", substring(s, 9, strlen(s) - 24), "/skinpreview"));
 +              fh = fopen(language_filename(s), FILE_READ);
 +              if(fh < 0)
 +              {
 +                      print("Warning: can't open skinvalues.txt file\n");
 +                      continue;
 +              }
 +              while((s = fgets(fh)))
 +              {
 +                      // these two are handled by skinlist.qc
 +                      if(substring(s, 0, 6) == "title ")
 +                              bufstr_set(buf, i * SKINPARM_COUNT + SKINPARM_TITLE, substring(s, 6, strlen(s) - 6));
 +                      else if(substring(s, 0, 7) == "author ")
 +                              bufstr_set(buf, i * SKINPARM_COUNT + SKINPARM_AUTHOR, substring(s, 7, strlen(s) - 7));
 +              }
 +              fclose(fh);
 +      }
 +
 +      search_end(glob);
 +
 +      me.skinlist = buf;
 +      me.nItems = n;
 +}
 +
 +void XonoticSkinList_destroy(entity me)
 +{
 +      buf_del(me.skinlist);
 +}
 +
 +void XonoticSkinList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
 +{
 +      me.itemAbsSize = '0 0 0';
 +      SUPER(XonoticSkinList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
 +
 +      me.realFontSize_y = me.fontSize / (me.itemAbsSize_y = (absSize.y * me.itemHeight));
 +      me.realFontSize_x = me.fontSize / (me.itemAbsSize_x = (absSize.x * (1 - me.controlWidth)));
 +      me.realUpperMargin1 = 0.5 * (1 - 2.5 * me.realFontSize.y);
 +      me.realUpperMargin2 = me.realUpperMargin1 + 1.5 * me.realFontSize.y;
 +
 +      me.columnPreviewOrigin = 0;
 +      me.columnPreviewSize = me.itemAbsSize.y / me.itemAbsSize.x * 4 / 3;
 +      me.columnNameOrigin = me.columnPreviewOrigin + me.columnPreviewSize + me.realFontSize.x;
 +      me.columnNameSize = 1 - me.columnPreviewSize - 2 * me.realFontSize.x;
 +}
 +
 +void XonoticSkinList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
 +{
 +      string s;
 +
 +      if(isSelected)
 +              draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
 +
 +      s = me.skinParameter(me, i, SKINPARM_PREVIEW);
 +      draw_Picture(me.columnPreviewOrigin * eX, s, me.columnPreviewSize * eX + eY, '1 1 1', 1);
 +
 +      s = me.skinParameter(me, i, SKINPARM_TITLE);
 +      s = draw_TextShortenToWidth(s, me.columnNameSize, 0, me.realFontSize);
 +      draw_Text(me.realUpperMargin1 * eY + (me.columnNameOrigin + 0.00 * (me.columnNameSize - draw_TextWidth(s, 0, me.realFontSize))) * eX, s, me.realFontSize, SKINCOLOR_SKINLIST_TITLE, SKINALPHA_TEXT, 0);
 +
 +      s = me.skinParameter(me, i, SKINPARM_AUTHOR);
 +      s = draw_TextShortenToWidth(s, me.columnNameSize, 0, me.realFontSize);
 +      draw_Text(me.realUpperMargin2 * eY + (me.columnNameOrigin + 1.00 * (me.columnNameSize - draw_TextWidth(s, 0, me.realFontSize))) * eX, s, me.realFontSize, SKINCOLOR_SKINLIST_AUTHOR, SKINALPHA_TEXT, 0);
 +}
 +
 +void XonoticSkinList_setSkin(entity me)
 +{
 +      me.saveCvars(me);
 +      localcmd("\nmenu_restart\nmenu_cmd skinselect\n");
 +}
 +
 +void SetSkin_Click(entity btn, entity me)
 +{
 +      me.setSkin(me);
 +}
 +
 +void XonoticSkinList_doubleClickListBoxItem(entity me, float i, vector where)
 +{
++      m_play_click_sound(MENU_SOUND_EXECUTE);
 +      me.setSkin(me);
 +}
 +
 +float XonoticSkinList_keyDown(entity me, float scan, float ascii, float shift)
 +{
++      if(scan == K_ENTER || scan == K_KP_ENTER)
++      {
++              m_play_click_sound(MENU_SOUND_EXECUTE);
 +              me.setSkin(me);
 +              return 1;
 +      }
 +      else
 +              return SUPER(XonoticSkinList).keyDown(me, scan, ascii, shift);
 +}
 +#endif
index 5f4b771cee40958afada74b9175d780d6e7cfb88,5b4d7b29b8ca8ec4c43d48239d871cfa0e955c47..53ec965030552c0bd76eb2af8811aa53ac90165a
@@@ -1,42 -1,14 +1,42 @@@
 +#include "waypointsprites.qh"
 +
 +#include "cl_impulse.qh"
 +#include "cl_player.qh"
 +#include "ent_cs.qh"
 +#include "g_subs.qh"
 +#include "ipban.qh"
 +#include "miscfunctions.qh"
 +#include "portals.qh"
 +#include "teamplay.qh"
 +#include "playerdemo.qh"
 +#include "secret.qh"
 +
 +#include "bot/bot.qh"
 +#include "bot/navigation.qh"
 +
 +#include "weapons/hitplot.qh"
 +#include "weapons/weaponsystem.qh"
 +
 +#include "../common/net_notice.qh"
 +#include "../common/physics.qh"
 +
 +#include "../common/monsters/sv_monsters.qh"
 +
 +#include "../warpzonelib/server.qh"
 +
 +float c1, c2, c3, c4;
 +
  void send_CSQC_teamnagger() {
        WriteByte(MSG_BROADCAST, SVC_TEMPENTITY);
        WriteByte(MSG_BROADCAST, TE_CSQC_TEAMNAGGER);
  }
  
 -float ClientData_Send(entity to, float sf)
 +float ClientData_Send(entity to, int sf)
  {
        if(to != self.owner)
        {
                error("wtf");
 -              return FALSE;
 +              return false;
        }
  
        entity e;
  
        if(sf & 8)
        {
 -              WriteAngle(MSG_ENTITY, e.v_angle_x);
 -              WriteAngle(MSG_ENTITY, e.v_angle_y);
 +              WriteAngle(MSG_ENTITY, e.v_angle.x);
 +              WriteAngle(MSG_ENTITY, e.v_angle.y);
        }
  
 -      return TRUE;
 +      return true;
  }
  
  void ClientData_Attach()
  {
 -      Net_LinkEntity(self.clientdata = spawn(), FALSE, 0, ClientData_Send);
 +      Net_LinkEntity(self.clientdata = spawn(), false, 0, ClientData_Send);
        self.clientdata.drawonlytoclient = self;
        self.clientdata.owner = self;
  }
@@@ -170,7 -142,7 +170,7 @@@ void PutObserverInServer (void
  
        if(IS_PLAYER(self)) { pointparticles(particleeffectnum("spawn_event_neutral"), self.origin, '0 0 0', 1); }
  
 -      spot = SelectSpawnPoint (TRUE);
 +      spot = SelectSpawnPoint (true);
        if(!spot)
                error("No spawnpoints for observers?!?\n");
        RemoveGrapplingHook(self); // Wazat's Grappling Hook
                if(autocvar_g_chat_nospectators == 1 || (cvar("g_warmup") && !(warmup_stage || gameover) && autocvar_g_chat_nospectators == 2))
                        Send_Notification(NOTIF_ONE_ONLY, self, MSG_INFO, INFO_CHAT_NOSPECTATORS);
  
 -              if(self.just_joined == FALSE) {
 +              if(self.just_joined == false) {
                        LogTeamchange(self.playerid, -1, 4);
                } else
 -                      self.just_joined = FALSE;
 +                      self.just_joined = false;
        }
  
        PlayerScore_Clear(self); // clear scores when needed
        self.spectatortime = time;
  
        self.classname = "observer";
 -      self.iscreature = FALSE;
 +      self.iscreature = false;
        self.teleportable = TELEPORT_SIMPLE;
 -      self.damagedbycontents = FALSE;
 +      self.damagedbycontents = false;
        self.health = -666;
        self.takedamage = DAMAGE_NO;
        self.solid = SOLID_NOT;
        self.deadflag = DEAD_NO;
        self.angles = spot.angles;
        self.angles_z = 0;
 -      self.fixangle = TRUE;
 -      self.crouch = FALSE;
 +      self.fixangle = true;
 +      self.crouch = false;
        self.revival_time = 0;
  
        setorigin (self, (spot.origin + PL_VIEW_OFS)); // offset it so that the spectator spawns higher off the ground, looks better this way
@@@ -300,7 -272,7 +300,7 @@@ void FixPlayermodel(
  
        defaultmodel = "";
        defaultskin = 0;
 -      chmdl = FALSE;
 +      chmdl = false;
  
        if(autocvar_sv_defaultcharacter == 1)
        {
                        m2 = self.maxs;
                        setplayermodel (self, defaultmodel);
                        setsize (self, m1, m2);
 -                      chmdl = TRUE;
 +                      chmdl = true;
                }
  
                oldskin = self.skin;
                        m2 = self.maxs;
                        setplayermodel (self, self.playermodel);
                        setsize (self, m1, m2);
 -                      chmdl = TRUE;
 +                      chmdl = true;
                }
  
                oldskin = self.skin;
@@@ -415,9 -387,9 +415,9 @@@ void PutClientInServer (void
                accuracy_resend(self);
  
                if(self.team < 0)
 -                      JoinBestTeam(self, FALSE, TRUE);
 +                      JoinBestTeam(self, false, true);
  
 -              spot = SelectSpawnPoint (FALSE);
 +              spot = SelectSpawnPoint (false);
                if(!spot)
                {
                        Send_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER, CENTER_JOIN_NOSPAWNS);
                        vehicles_exit(VHEF_RELESE);
  
                self.classname = "player";
 -              self.wasplayer = TRUE;
 -              self.iscreature = TRUE;
 +              self.wasplayer = true;
 +              self.iscreature = true;
                self.teleportable = TELEPORT_NORMAL;
 -              self.damagedbycontents = TRUE;
 +              self.damagedbycontents = true;
                self.movetype = MOVETYPE_WALK;
                self.solid = SOLID_SLIDEBOX;
                self.dphitcontentsmask = DPCONTENTS_BODY | DPCONTENTS_SOLID;
                self.angles_z = 0; // never spawn tilted even if the spot says to
                if(IS_BOT_CLIENT(self))
                        self.v_angle = self.angles;
 -              self.fixangle = TRUE; // turn this way immediately
 +              self.fixangle = true; // turn this way immediately
                self.velocity = '0 0 0';
                self.avelocity = '0 0 0';
                self.punchangle = '0 0 0';
  
                entity spawnevent = spawn();
                spawnevent.owner = self;
 -              Net_LinkEntity(spawnevent, FALSE, 0.5, SpawnEvent_Send);
 +              Net_LinkEntity(spawnevent, false, 0.5, SpawnEvent_Send);
  
                // Cut off any still running player sounds.
                stopsound(self, CH_PLAYER_SINGLE);
                FixPlayermodel();
                self.drawonlytoclient = world;
  
 -              self.crouch = FALSE;
 +              self.crouch = false;
                self.view_ofs = PL_VIEW_OFS;
                setsize (self, PL_MIN, PL_MAX);
                self.spawnorigin = spot.origin;
 -              setorigin (self, spot.origin + '0 0 1' * (1 - self.mins_z - 24));
 +              setorigin (self, spot.origin + '0 0 1' * (1 - self.mins.z - 24));
                // don't reset back to last position, even if new position is stuck in solid
                self.oldorigin = self.origin;
                self.prevorigin = self.origin;
  
                self.event_damage = PlayerDamage;
  
 -              self.bot_attack = TRUE;
 -              self.monster_attack = TRUE;
 -              
 +              self.bot_attack = true;
 +              self.monster_attack = true;
 +
                self.spider_slowness = 0;
  
                self.BUTTON_ATCK = self.BUTTON_JUMP = self.BUTTON_ATCK2 = 0;
                self.colormod = '1 1 1' * autocvar_g_player_brightness;
                self.exteriorweaponentity.alpha = default_weapon_alpha;
  
 -              self.speedrunning = FALSE;
 +              self.speedrunning = false;
  
                //stuffcmd(self, "chase_active 0");
                //stuffcmd(self, "set viewsize $tmpviewsize \n");
                        WEP_ACTION(j, WR_RESETPLAYER);
  
                        // all weapons must be fully loaded when we spawn
 -                      entity e;
 -                      e = get_weaponinfo(j);
 +                      entity e = get_weaponinfo(j);
                        if(e.spawnflags & WEP_FLAG_RELOADABLE) // prevent accessing undefined cvars
                                self.(weapon_load[j]) = e.reloading_ammo;
                }
@@@ -681,7 -654,7 +681,7 @@@ float ClientInit_SendEntity(entity to, 
        WriteByte(MSG_ENTITY, WEP_CVAR_SEC(hagar, load_max)); // hagar max loadable rockets // WEAPONTODO
        WriteCoord(MSG_ENTITY, autocvar_g_trueaim_minrange);
        WriteByte(MSG_ENTITY, WEP_CVAR(porto, secondary)); // WEAPONTODO
 -      return TRUE;
 +      return true;
  }
  
  void ClientInit_CheckUpdate()
@@@ -721,7 -694,7 +721,7 @@@ void ClientInit_Spawn(
        e = spawn();
        e.classname = "clientinit";
        e.think = ClientInit_CheckUpdate;
 -      Net_LinkEntity(e, FALSE, 0, ClientInit_SendEntity);
 +      Net_LinkEntity(e, false, 0, ClientInit_SendEntity);
  
        o = self;
        self = e;
@@@ -780,7 -753,7 +780,7 @@@ void ClientKill_Now_TeamChange(
  {
        if(self.killindicator_teamchange == -1)
        {
 -              JoinBestTeam( self, FALSE, TRUE );
 +              JoinBestTeam( self, false, true );
        }
        else if(self.killindicator_teamchange == -2)
        {
@@@ -1017,6 -990,27 +1017,27 @@@ float PlayerInIDList(entity p, string i
        return 0;
  }
  
+ #ifdef DP_EXT_PRECONNECT
+ /*
+ =============
+ ClientPreConnect
+ Called once (not at each match start) when a client begins a connection to the server
+ =============
+ */
+ void ClientPreConnect (void)
+ {
+       if(autocvar_sv_eventlog)
+       {
+               GameLogEcho(sprintf(":connect:%d:%d:%s",
+                       self.playerid,
+                       num_for_edict(self),
+                       ((IS_REAL_CLIENT(self)) ? self.netaddress : "bot")
+               ));
+       }
+ }
+ #endif
  /*
  =============
  ClientConnect
@@@ -1111,7 -1105,7 +1132,7 @@@ void ClientConnect (void
                if(self.team_forced > 0)
                        self.team_forced = 0;
  
 -      JoinBestTeam(self, FALSE, FALSE); // if the team number is valid, keep it
 +      JoinBestTeam(self, false, false); // if the team number is valid, keep it
  
        if((autocvar_sv_spectate == 1) || autocvar_g_campaign || self.team_forced < 0) {
                self.classname = "observer";
  
        LogTeamchange(self.playerid, self.team, 1);
  
 -      self.just_joined = TRUE;  // stop spamming the eventlog with additional lines when the client connects
 +      self.just_joined = true;  // stop spamming the eventlog with additional lines when the client connects
  
        self.netname_previous = strzone(self.netname);
  
@@@ -1354,7 -1348,7 +1375,7 @@@ void UpdateChatBubble(
                self.chatbubbleentity.nextthink = time;
                setmodel(self.chatbubbleentity, "models/misc/chatbubble.spr"); // precision set below
                //setorigin(self.chatbubbleentity, self.origin + '0 0 15' + self.maxs_z * '0 0 1');
 -              setorigin(self.chatbubbleentity, '0 0 15' + self.maxs_z * '0 0 1');
 +              setorigin(self.chatbubbleentity, '0 0 15' + self.maxs.z * '0 0 1');
                setattachment(self.chatbubbleentity, self, "");  // sticks to moving player better, also conserves bandwidth
                self.chatbubbleentity.mdl = self.chatbubbleentity.model;
                self.chatbubbleentity.model = "";
@@@ -1634,17 -1628,55 +1655,17 @@@ void SetZoomState(float z
  
  void GetPressedKeys(void) {
        MUTATOR_CALLHOOK(GetPressedKeys);
 -      if (self.movement_x > 0) // get if movement keys are pressed
 -      {       // forward key pressed
 -              self.pressedkeys |= KEY_FORWARD;
 -              self.pressedkeys &= ~KEY_BACKWARD;
 -      }
 -      else if (self.movement_x < 0)
 -      {       // backward key pressed
 -              self.pressedkeys |= KEY_BACKWARD;
 -              self.pressedkeys &= ~KEY_FORWARD;
 -      }
 -      else
 -      {       // no x input
 -              self.pressedkeys &= ~KEY_FORWARD;
 -              self.pressedkeys &= ~KEY_BACKWARD;
 -      }
 -
 -      if (self.movement_y > 0)
 -      {       // right key pressed
 -              self.pressedkeys |= KEY_RIGHT;
 -              self.pressedkeys &= ~KEY_LEFT;
 -      }
 -      else if (self.movement_y < 0)
 -      {       // left key pressed
 -              self.pressedkeys |= KEY_LEFT;
 -              self.pressedkeys &= ~KEY_RIGHT;
 -      }
 -      else
 -      {       // no y input
 -              self.pressedkeys &= ~KEY_RIGHT;
 -              self.pressedkeys &= ~KEY_LEFT;
 -      }
 -
 -      if (self.BUTTON_JUMP) // get if jump and crouch keys are pressed
 -              self.pressedkeys |= KEY_JUMP;
 -      else
 -              self.pressedkeys &= ~KEY_JUMP;
 -      if (self.BUTTON_CROUCH)
 -              self.pressedkeys |= KEY_CROUCH;
 -      else
 -              self.pressedkeys &= ~KEY_CROUCH;
 -
 -      if (self.BUTTON_ATCK)
 -              self.pressedkeys |= KEY_ATCK;
 -      else
 -              self.pressedkeys &= ~KEY_ATCK;
 -      if (self.BUTTON_ATCK2)
 -              self.pressedkeys |= KEY_ATCK2;
 -      else
 -              self.pressedkeys &= ~KEY_ATCK2;
 +      #define X(var,bit,flag) (flag ? var |= bit : var &= ~bit)
 +      X(self.pressedkeys, KEY_FORWARD,        PHYS_INPUT_MOVEVALUES(self)_x > 0);
 +      X(self.pressedkeys, KEY_BACKWARD,       PHYS_INPUT_MOVEVALUES(self)_x < 0);
 +      X(self.pressedkeys, KEY_RIGHT,          PHYS_INPUT_MOVEVALUES(self)_y > 0);
 +      X(self.pressedkeys, KEY_LEFT,           PHYS_INPUT_MOVEVALUES(self)_y < 0);
 +
 +      X(self.pressedkeys, KEY_JUMP,           PHYS_INPUT_BUTTON_JUMP(self));
 +      X(self.pressedkeys, KEY_CROUCH,         PHYS_INPUT_BUTTON_CROUCH(self));
 +      X(self.pressedkeys, KEY_ATCK,           PHYS_INPUT_BUTTON_ATCK(self));
 +      X(self.pressedkeys, KEY_ATCK2,          PHYS_INPUT_BUTTON_ATCK2(self));
 +      #undef X
  }
  
  /*
@@@ -1696,7 -1728,7 +1717,7 @@@ void SpectateCopy(entity spectatee) 
        self.frozen = spectatee.frozen;
        self.revive_progress = spectatee.revive_progress;
        if(!self.BUTTON_USE)
 -              self.fixangle = TRUE;
 +              self.fixangle = true;
        setorigin(self, spectatee.origin);
        setsize(self, spectatee.mins, spectatee.maxs);
        SetZoomState(spectatee.zoomstate);
        self.hud = spectatee.hud;
        if(spectatee.vehicle)
      {
 -        self.fixangle = FALSE;
 +        self.fixangle = false;
          //self.velocity = spectatee.vehicle.velocity;
          self.vehicle_health = spectatee.vehicle_health;
          self.vehicle_shield = spectatee.vehicle_shield;
          msg_entity = self;
  
          WriteByte (MSG_ONE, SVC_SETVIEWANGLES);
 -            WriteAngle(MSG_ONE,  spectatee.v_angle_x);
 -            WriteAngle(MSG_ONE,  spectatee.v_angle_y);
 -            WriteAngle(MSG_ONE,  spectatee.v_angle_z);
 +            WriteAngle(MSG_ONE,  spectatee.v_angle.x);
 +            WriteAngle(MSG_ONE,  spectatee.v_angle.y);
 +            WriteAngle(MSG_ONE,  spectatee.v_angle.z);
  
          //WriteByte (MSG_ONE, SVC_SETVIEW);
          //    WriteEntity(MSG_ONE, self);
@@@ -1748,7 -1780,7 +1769,7 @@@ float SpectateUpdate(
  float SpectateSet()
  {
        if(self.enemy.classname != "player")
 -              return FALSE;
 +              return false;
        /*if(self.enemy.vehicle)
        {
  
                if(!SpectateUpdate())
                        PutObserverInServer();
        //}
 -      return TRUE;
 +      return true;
  }
  
  void SetSpectator(entity player, entity spectatee)
@@@ -1843,7 -1875,7 +1864,7 @@@ float SpectatePrev(
        // NOTE: chain order is from the highest to the lower entnum (unlike find)
        other = findchain(classname, "player");
        if (!other) // no player
 -              return FALSE;
 +              return false;
  
        entity first = other;
        // skip players until current spectated player
                        while(other.team != self.team)
                                other = other.chain;
                        if(other == self.enemy)
 -                              return TRUE;
 +                              return true;
                }
        }
        else
@@@ -1914,7 -1946,7 +1935,7 @@@ void LeaveSpectatorMode(
                        nades_RemoveBonus(self);
  
                        if(autocvar_g_campaign || autocvar_g_balance_teams)
 -                              { JoinBestTeam(self, FALSE, TRUE); }
 +                              { JoinBestTeam(self, false, true); }
  
                        if(autocvar_g_campaign)
                                { campaign_bots_may_start = 1; }
  
  /**
   * Determines whether the player is allowed to join. This depends on cvar
 - * g_maxplayers, if it isn't used this function always return TRUE, otherwise
 + * g_maxplayers, if it isn't used this function always return true, otherwise
   * it checks whether the number of currently playing players exceeds g_maxplayers.
   * @return int number of free slots for players, 0 if none
   */
@@@ -2148,16 -2180,16 +2169,16 @@@ float isInvisibleString(string s
                        case 192: // charmap space
                                if (!autocvar_utf8_enable)
                                        break;
 -                              return FALSE;
 +                              return false;
                        case 160: // space in unicode fonts
                        case 0xE000 + 192: // utf8 charmap space
                                if (autocvar_utf8_enable)
                                        break;
                        default:
 -                              return FALSE;
 +                              return false;
                }
        }
 -      return TRUE;
 +      return true;
  }
  
  /*
@@@ -2169,7 -2201,7 +2190,7 @@@ Called every frame for each client befo
  */
  .float usekeypressed;
  void() nexball_setstatus;
 -.float items_added;
 +.int items_added;
  void PlayerPreThink (void)
  {
        WarpZone_PlayerPhysics_FixVAngle();
        {
                self.revive_progress = bound(0, self.revive_progress - frametime * self.revive_speed, 1);
                self.health = max(0, autocvar_g_nades_ice_health + (start_health-autocvar_g_nades_ice_health) * self.revive_progress );
 -              
 +
                if(self.health < 1)
                {
                        if(self.vehicle)
                        // FIXME turn this into CSQC stuff
                        self.v_angle = self.lastV_angle;
                        self.angles = self.lastV_angle;
 -                      self.fixangle = TRUE;
 +                      self.fixangle = true;
                }
  
                if(frametime)
  
                                if(self.vortex_charge > WEP_CVAR(vortex, charge_animlimit))
                                {
 -                                      self.weaponentity_glowmod_x = self.weaponentity_glowmod_x + autocvar_g_weapon_charge_colormod_hdrmultiplier * autocvar_g_weapon_charge_colormod_red_full * (self.vortex_charge - WEP_CVAR(vortex, charge_animlimit)) / (1 - WEP_CVAR(vortex, charge_animlimit));
 -                                      self.weaponentity_glowmod_y = self.weaponentity_glowmod_y + autocvar_g_weapon_charge_colormod_hdrmultiplier * autocvar_g_weapon_charge_colormod_green_full * (self.vortex_charge - WEP_CVAR(vortex, charge_animlimit)) / (1 - WEP_CVAR(vortex, charge_animlimit));
 -                                      self.weaponentity_glowmod_z = self.weaponentity_glowmod_z + autocvar_g_weapon_charge_colormod_hdrmultiplier * autocvar_g_weapon_charge_colormod_blue_full * (self.vortex_charge - WEP_CVAR(vortex, charge_animlimit)) / (1 - WEP_CVAR(vortex, charge_animlimit));
 +                                      self.weaponentity_glowmod_x = self.weaponentity_glowmod.x + autocvar_g_weapon_charge_colormod_hdrmultiplier * autocvar_g_weapon_charge_colormod_red_full * (self.vortex_charge - WEP_CVAR(vortex, charge_animlimit)) / (1 - WEP_CVAR(vortex, charge_animlimit));
 +                                      self.weaponentity_glowmod_y = self.weaponentity_glowmod.y + autocvar_g_weapon_charge_colormod_hdrmultiplier * autocvar_g_weapon_charge_colormod_green_full * (self.vortex_charge - WEP_CVAR(vortex, charge_animlimit)) / (1 - WEP_CVAR(vortex, charge_animlimit));
 +                                      self.weaponentity_glowmod_z = self.weaponentity_glowmod.z + autocvar_g_weapon_charge_colormod_hdrmultiplier * autocvar_g_weapon_charge_colormod_blue_full * (self.vortex_charge - WEP_CVAR(vortex, charge_animlimit)) / (1 - WEP_CVAR(vortex, charge_animlimit));
                                }
                        }
                        else
 -                              self.weaponentity_glowmod = colormapPaletteColor(self.clientcolors & 0x0F, TRUE) * 2;
 +                              self.weaponentity_glowmod = colormapPaletteColor(self.clientcolors & 0x0F, true) * 2;
  
                        player_powerups();
                }
                        do_crouch = 0;
  
                // WEAPONTODO: THIS SHIT NEEDS TO GO EVENTUALLY
 -              // It cannot be predicted by the engine! 
 +              // It cannot be predicted by the engine!
                if((self.weapon == WEP_SHOCKWAVE || self.weapon == WEP_SHOTGUN) && self.weaponentity.wframe == WFRAME_FIRE2 && time < self.weapon_nextthink)
                        do_crouch = 0;
  
                {
                        if (!self.crouch)
                        {
 -                              self.crouch = TRUE;
 +                              self.crouch = true;
                                self.view_ofs = PL_CROUCH_VIEW_OFS;
                                setsize (self, PL_CROUCH_MIN, PL_CROUCH_MAX);
 -                              // setanim(self, self.anim_duck, FALSE, TRUE, TRUE); // this anim is BROKEN anyway
 +                              // setanim(self, self.anim_duck, false, true, true); // this anim is BROKEN anyway
                        }
                }
                else
                {
                        if (self.crouch)
                        {
 -                              tracebox(self.origin, PL_MIN, PL_MAX, self.origin, FALSE, self);
 +                              tracebox(self.origin, PL_MIN, PL_MAX, self.origin, false, self);
                                if (!trace_startsolid)
                                {
 -                                      self.crouch = FALSE;
 +                                      self.crouch = false;
                                        self.view_ofs = PL_VIEW_OFS;
                                        setsize (self, PL_MIN, PL_MAX);
                                }
  
                player_regen();
  
 -              // WEAPONTODO: Add a weapon request for this 
 +              // WEAPONTODO: Add a weapon request for this
                // rot vortex charge to the charge limit
                if(WEP_CVAR(vortex, charge_rot_rate) && self.vortex_charge > WEP_CVAR(vortex, charge_limit) && self.vortex_charge_rottime < time)
                        self.vortex_charge = bound(WEP_CVAR(vortex, charge_limit), self.vortex_charge - WEP_CVAR(vortex, charge_rot_rate) * frametime / W_TICSPERFRAME, 1);
index cddea7dfcef506b1c50a4889585132d8e732fac7,aed01c7cbf9ab7e126e808c432d6baa6b769a07c..9091ae0b783b65953aa4abef156b4e3203ce8040
@@@ -1,14 -1,3 +1,14 @@@
 +#if defined(CSQC)
 +#elif defined(MENUQC)
 +#elif defined(SVQC)
 +      #include "../../dpdefs/dpextensions.qh"
 +    #include "../../common/util.qh"
 +    #include "../../common/command/shared_defs.qh"
 +    #include "../autocvars.qh"
 +    #include "common.qh"
 +    #include "banning.qh"
 +    #include "../ipban.qh"
 +#endif
  // =====================================================
  //  Banning and kicking command code, written by Samual
  //  Last updated: December 29th, 2011
@@@ -80,7 -69,7 +80,7 @@@ void BanCommand_kickban(float request, 
                        if(argc >= 2)
                        {
                                entity client = GetIndexedEntity(argc, 1);
-                               float accepted = VerifyClientEntity(client, true, false);
+                               float accepted = VerifyKickableEntity(client);
                                float reason_arg, bantime, masksize;
                                string reason;
  
@@@ -127,11 -116,11 +127,11 @@@ void BanCommand_mute(float request, flo
                        if(argc >= 2)
                        {
                                entity client = GetFilteredEntity(argv(1));
 -                              float accepted = VerifyClientEntity(client, TRUE, FALSE);
 +                              float accepted = VerifyClientEntity(client, true, false);
  
                                if(accepted > 0)
                                {
 -                                      client.muted = TRUE;
 +                                      client.muted = true;
                                        return;
                                }
                                else
@@@ -215,11 -204,11 +215,11 @@@ void BanCommand_unmute(float request, f
                        if(argc >= 2)
                        {
                                entity client = GetFilteredEntity(argv(1));
 -                              float accepted = VerifyClientEntity(client, TRUE, FALSE);
 +                              float accepted = VerifyClientEntity(client, true, false);
  
                                if(accepted > 0)
                                {
 -                                      client.muted = FALSE;
 +                                      client.muted = false;
                                        return;
                                }
                                else
@@@ -284,7 -273,7 +284,7 @@@ void BanCommand_macro_help(
        #define BAN_COMMAND(name,function,description) \
                { if(strtolower(description) != "") { print("  ^2", name, "^7: ", description, "\n"); } }
  
 -      BAN_COMMANDS(0, 0, "")
 +      BAN_COMMANDS(0, 0, "");
        #undef BAN_COMMAND
  
        return;
  float BanCommand_macro_command(float argc, string command)
  {
        #define BAN_COMMAND(name,function,description) \
 -              { if(name == strtolower(argv(0))) { function; return TRUE; } }
 +              { if(name == strtolower(argv(0))) { function; return true; } }
  
 -      BAN_COMMANDS(CMD_REQUEST_COMMAND, argc, command)
 +      BAN_COMMANDS(CMD_REQUEST_COMMAND, argc, command);
        #undef BAN_COMMAND
  
 -      return FALSE;
 +      return false;
  }
  
  float BanCommand_macro_usage(float argc)
  {
        #define BAN_COMMAND(name,function,description) \
 -              { if(name == strtolower(argv(1))) { function; return TRUE; } }
 +              { if(name == strtolower(argv(1))) { function; return true; } }
  
 -      BAN_COMMANDS(CMD_REQUEST_USAGE, argc, "")
 +      BAN_COMMANDS(CMD_REQUEST_USAGE, argc, "");
        #undef BAN_COMMAND
  
 -      return FALSE;
 +      return false;
  }
  
  void BanCommand_macro_write_aliases(float fh)
        #define BAN_COMMAND(name,function,description) \
                { if(strtolower(description) != "") { CMD_Write_Alias("qc_cmd_sv", name, description); } }
  
 -      BAN_COMMANDS(0, 0, "")
 +      BAN_COMMANDS(0, 0, "");
        #undef BAN_COMMAND
  
        return;
@@@ -334,8 -323,8 +334,8 @@@ float BanCommand(string command
  
        if(BanCommand_macro_command(argc, command)) // continue as usual and scan for normal commands
        {
 -              return TRUE; // handled by one of the above GenericCommand_* functions
 +              return true; // handled by one of the above GenericCommand_* functions
        }
  
 -      return FALSE;
 +      return false;
  }
index eb5450252140199534f708371825b0254d946e57,04ed4b2840491a0bb2026386dc66e9c31215ec60..09314c745631dc119f5b32341c294c1bb488f148
@@@ -1,8 -1,3 +1,8 @@@
 +#include "common.qh"
 +
 +#include "../../common/counting.qh"
 +
 +
  // ====================================================
  //  Shared code for server commands, written by Samual
  //  Last updated: December 27th, 2011
@@@ -26,6 -21,14 +26,14 @@@ string GetCallerName(entity caller
                return admin_name(); //((autocvar_sv_adminnick != "") ? autocvar_sv_adminnick : autocvar_hostname);
  }
  
+ // verify that the client provided is acceptable for kicking
+ float VerifyKickableEntity(entity client)
+ {
+       if (!IS_REAL_CLIENT(client))
+               return CLIENT_NOT_REAL;
+       return CLIENT_ACCEPTABLE;
+ }
  // verify that the client provided is acceptable for use
  float VerifyClientEntity(entity client, float must_be_real, float must_be_bots)
  {
@@@ -55,9 -58,9 +63,9 @@@ string GetClientErrorString(float clien
  float VerifyClientNumber(float tmp_number)
  {
        if((tmp_number < 1) || (tmp_number > maxclients))
 -              return FALSE;
 +              return false;
        else
 -              return TRUE;
 +              return true;
  }
  
  entity GetIndexedEntity(float argc, float start_index)
@@@ -201,7 -204,7 +209,7 @@@ void timeout_handler_think(
  
                                // unlock the view for players so they can move around again
                                FOR_EACH_REALPLAYER(tmp_player)
 -                                      tmp_player.fixangle = FALSE;
 +                                      tmp_player.fixangle = false;
  
                                timeout_handler_reset();
                        }
@@@ -411,7 -414,9 +419,7 @@@ void CommonCommand_records(float reques
        {
                case CMD_REQUEST_COMMAND:
                {
 -                      float i;
 -
 -                      for(i = 0; i < 10; ++i)
 +                      for(int i = 0; i < 10; ++i)
                                if(records_reply[i] != "")
                                        print_to(caller, records_reply[i]);
  
@@@ -459,8 -464,8 +467,8 @@@ void CommonCommand_time(float request, 
                        print_to(caller, strcat("realtime = ", ftos(gettime(GETTIME_REALTIME))));
                        print_to(caller, strcat("hires = ", ftos(gettime(GETTIME_HIRES))));
                        print_to(caller, strcat("uptime = ", ftos(gettime(GETTIME_UPTIME))));
 -                      print_to(caller, strcat("localtime = ", strftime(TRUE, "%a %b %e %H:%M:%S %Z %Y")));
 -                      print_to(caller, strcat("gmtime = ", strftime(FALSE, "%a %b %e %H:%M:%S %Z %Y")));
 +                      print_to(caller, strcat("localtime = ", strftime(true, "%a %b %e %H:%M:%S %Z %Y")));
 +                      print_to(caller, strcat("gmtime = ", strftime(false, "%a %b %e %H:%M:%S %Z %Y")));
                        return;
                }
  
@@@ -663,3 -668,70 +671,3 @@@ void CommonCommand_(float request, enti
        }
  }
  */
 -
 -
 -// ==================================
 -//  Macro system for common commands
 -// ==================================
 -
 -// Do not hard code aliases for these, instead create them in commands.cfg... also: keep in alphabetical order, please ;)
 -#define COMMON_COMMANDS(request,caller,arguments,command) \
 -      COMMON_COMMAND("cvar_changes", CommonCommand_cvar_changes(request, caller), "Prints a list of all changed server cvars") \
 -      COMMON_COMMAND("cvar_purechanges", CommonCommand_cvar_purechanges(request, caller), "Prints a list of all changed gameplay cvars") \
 -      COMMON_COMMAND("info", CommonCommand_info(request, caller, arguments), "Request for unique server information set up by admin") \
 -      COMMON_COMMAND("ladder", CommonCommand_ladder(request, caller), "Get information about top players if supported") \
 -      COMMON_COMMAND("lsmaps", CommonCommand_lsmaps(request, caller), "List maps which can be used with the current game mode") \
 -      COMMON_COMMAND("printmaplist", CommonCommand_printmaplist(request, caller), "Display full server maplist reply") \
 -      COMMON_COMMAND("rankings", CommonCommand_rankings(request, caller), "Print information about rankings") \
 -      COMMON_COMMAND("records", CommonCommand_records(request, caller), "List top 10 records for the current map") \
 -      COMMON_COMMAND("teamstatus", CommonCommand_teamstatus(request, caller), "Show information about player and team scores") \
 -      COMMON_COMMAND("time", CommonCommand_time(request, caller), "Print different formats/readouts of time") \
 -      COMMON_COMMAND("timein", CommonCommand_timein(request, caller), "Resume the game from being paused with a timeout") \
 -      COMMON_COMMAND("timeout", CommonCommand_timeout(request, caller), "Call a timeout which pauses the game for certain amount of time unless unpaused") \
 -      COMMON_COMMAND("vote", VoteCommand(request, caller, arguments, command), "Request an action to be voted upon by players") \
 -      COMMON_COMMAND("who", CommonCommand_who(request, caller, arguments), "Display detailed client information about all players") \
 -      /* nothing */
 -
 -void CommonCommand_macro_help(entity caller)
 -{
 -      #define COMMON_COMMAND(name,function,description) \
 -              { print_to(caller, strcat("  ^2", name, "^7: ", description)); }
 -
 -      COMMON_COMMANDS(0, caller, 0, "")
 -      #undef COMMON_COMMAND
 -
 -      return;
 -}
 -
 -float CommonCommand_macro_command(float argc, entity caller, string command)
 -{
 -      #define COMMON_COMMAND(name,function,description) \
 -              { if(name == strtolower(argv(0))) { function; return TRUE; } }
 -
 -      COMMON_COMMANDS(CMD_REQUEST_COMMAND, caller, argc, command)
 -      #undef COMMON_COMMAND
 -
 -      return FALSE;
 -}
 -
 -float CommonCommand_macro_usage(float argc, entity caller)
 -{
 -      #define COMMON_COMMAND(name,function,description) \
 -              { if(name == strtolower(argv(1))) { function; return TRUE; } }
 -
 -      COMMON_COMMANDS(CMD_REQUEST_USAGE, caller, argc, "")
 -      #undef COMMON_COMMAND
 -
 -      return FALSE;
 -}
 -
 -void CommonCommand_macro_write_aliases(float fh)
 -{
 -      #define COMMON_COMMAND(name,function,description) \
 -              { CMD_Write_Alias("qc_cmd_svcmd", name, description); }
 -
 -      COMMON_COMMANDS(0, world, 0, "")
 -      #undef COMMON_COMMAND
 -
 -      return;
 -}
index e31a428843116e71c04fd81b3c9c2e41d2628b52,589388bbad70d16faff12be3f5ae5dc418f0ce7b..2a03041d90a56d373b98b36943cc28c9448f1d3e
@@@ -1,29 -1,21 +1,29 @@@
 +#ifndef COMMAND_COMMON_H
 +#define COMMAND_COMMON_H
 +
 +#include "vote.qh"
 +
 +#include "../../common/command/generic.qh"
 +#include "../../common/command/shared_defs.qh"
 +
  // ============================================================
  //  Shared declarations for server commands, written by Samual
  //  Last updated: December 30th, 2011
  // ============================================================
  
  // client verification results
 -#define CLIENT_ACCEPTABLE 1
 -#define CLIENT_DOESNT_EXIST -1
 -#define CLIENT_NOT_REAL -2
 -#define CLIENT_NOT_BOT -3
 +const float CLIENT_ACCEPTABLE = 1;
 +const float CLIENT_DOESNT_EXIST = -1;
 +const float CLIENT_NOT_REAL = -2;
 +const float CLIENT_NOT_BOT = -3;
  
  // definitions for timeouts
 -#define TIMEOUT_INACTIVE 0
 -#define TIMEOUT_LEADTIME 1
 -#define TIMEOUT_ACTIVE 2
 +const float TIMEOUT_INACTIVE = 0;
 +const float TIMEOUT_LEADTIME = 1;
 +const float TIMEOUT_ACTIVE = 2;
  
  // timeout which pauses the game by setting the slowmo value extremely low.
 -#define TIMEOUT_SLOWMO_VALUE 0.0001
 +const float TIMEOUT_SLOWMO_VALUE = 0.0001;
  
  // global timeout information declarations
  entity timeout_caller; // contains the entity of the player who started the last timeout
@@@ -43,139 -35,4 +43,142 @@@ void timeout_handler_think()
  void CommonCommand_macro_write_aliases(float fh);
  
  // keep track of the next token to use for argc
 -float next_token;
 +float next_token;
 +
 +// select the proper prefix for usage and other messages
 +string GetCommandPrefix(entity caller);
 +
 +// if client return player nickname, or if server return admin nickname
 +string GetCallerName(entity caller);
 +
++// verify that the client provided is acceptable for kicking
++float VerifyKickableEntity(entity client);
++
 +// verify that the client provided is acceptable for use
 +float VerifyClientEntity(entity client, float must_be_real, float must_be_bots);
 +
 +// if the client is not acceptable, return a string to be used for error messages
 +string GetClientErrorString(float clienterror, string original_input);
 +
 +// is this entity number even in the possible range of entities?
 +float VerifyClientNumber(float tmp_number);
 +
 +entity GetIndexedEntity(float argc, float start_index);
 +
 +// find a player which matches the input string, and return their entity
 +entity GetFilteredEntity(string input);
 +
 +// same thing, but instead return their edict number
 +float GetFilteredNumber(string input);
 +
 +// switch between sprint and print depending on whether the receiver is the server or a player
 +void print_to(entity to, string input);
 +
 +// ==========================================
 +//  Supporting functions for common commands
 +// ==========================================
 +
 +// used by CommonCommand_timeout() and CommonCommand_timein() to handle game pausing and messaging and such.
 +void timeout_handler_reset();
 +
 +void timeout_handler_think();
 +
 +// ===================================================
 +//  Common commands used in both sv_cmd.qc and cmd.qc
 +// ===================================================
 +
 +void CommonCommand_cvar_changes(float request, entity caller);
 +
 +void CommonCommand_cvar_purechanges(float request, entity caller);
 +
 +void CommonCommand_info(float request, entity caller, float argc);
 +
 +void CommonCommand_ladder(float request, entity caller);
 +
 +void CommonCommand_lsmaps(float request, entity caller);
 +
 +void CommonCommand_printmaplist(float request, entity caller);
 +
 +void CommonCommand_rankings(float request, entity caller);
 +
 +void CommonCommand_records(float request, entity caller);
 +
 +void CommonCommand_teamstatus(float request, entity caller);
 +
 +void CommonCommand_time(float request, entity caller);
 +
 +void CommonCommand_timein(float request, entity caller);
 +
 +void CommonCommand_timeout(float request, entity caller);
 +
 +void CommonCommand_who(float request, entity caller, float argc);
 +
 +
 +// ==================================
 +//  Macro system for common commands
 +// ==================================
 +
 +// Do not hard code aliases for these, instead create them in commands.cfg... also: keep in alphabetical order, please ;)
 +#define COMMON_COMMANDS(request,caller,arguments,command) \
 +      COMMON_COMMAND("cvar_changes", CommonCommand_cvar_changes(request, caller), "Prints a list of all changed server cvars") \
 +      COMMON_COMMAND("cvar_purechanges", CommonCommand_cvar_purechanges(request, caller), "Prints a list of all changed gameplay cvars") \
 +      COMMON_COMMAND("info", CommonCommand_info(request, caller, arguments), "Request for unique server information set up by admin") \
 +      COMMON_COMMAND("ladder", CommonCommand_ladder(request, caller), "Get information about top players if supported") \
 +      COMMON_COMMAND("lsmaps", CommonCommand_lsmaps(request, caller), "List maps which can be used with the current game mode") \
 +      COMMON_COMMAND("printmaplist", CommonCommand_printmaplist(request, caller), "Display full server maplist reply") \
 +      COMMON_COMMAND("rankings", CommonCommand_rankings(request, caller), "Print information about rankings") \
 +      COMMON_COMMAND("records", CommonCommand_records(request, caller), "List top 10 records for the current map") \
 +      COMMON_COMMAND("teamstatus", CommonCommand_teamstatus(request, caller), "Show information about player and team scores") \
 +      COMMON_COMMAND("time", CommonCommand_time(request, caller), "Print different formats/readouts of time") \
 +      COMMON_COMMAND("timein", CommonCommand_timein(request, caller), "Resume the game from being paused with a timeout") \
 +      COMMON_COMMAND("timeout", CommonCommand_timeout(request, caller), "Call a timeout which pauses the game for certain amount of time unless unpaused") \
 +      COMMON_COMMAND("vote", VoteCommand(request, caller, arguments, command), "Request an action to be voted upon by players") \
 +      COMMON_COMMAND("who", CommonCommand_who(request, caller, arguments), "Display detailed client information about all players") \
 +      /* nothing */
 +
 +void CommonCommand_macro_help(entity caller)
 +{
 +      #define COMMON_COMMAND(name,function,description) \
 +              { print_to(caller, strcat("  ^2", name, "^7: ", description)); }
 +
 +      COMMON_COMMANDS(0, caller, 0, "");
 +      #undef COMMON_COMMAND
 +
 +      return;
 +}
 +
 +float CommonCommand_macro_command(float argc, entity caller, string command)
 +{
 +      #define COMMON_COMMAND(name,function,description) \
 +              { if(name == strtolower(argv(0))) { function; return true; } }
 +
 +      COMMON_COMMANDS(CMD_REQUEST_COMMAND, caller, argc, command);
 +      #undef COMMON_COMMAND
 +
 +      return false;
 +}
 +
 +float CommonCommand_macro_usage(float argc, entity caller)
 +{
 +      #define COMMON_COMMAND(name,function,description) \
 +              { if(name == strtolower(argv(1))) { function; return true; } }
 +
 +      COMMON_COMMANDS(CMD_REQUEST_USAGE, caller, argc, "");
 +      #undef COMMON_COMMAND
 +
 +      return false;
 +}
 +
 +void CommonCommand_macro_write_aliases(float fh)
 +{
 +      #define COMMON_COMMAND(name,function,description) \
 +              { CMD_Write_Alias("qc_cmd_svcmd", name, description); }
 +
 +      COMMON_COMMANDS(0, world, 0, "");
 +      #undef COMMON_COMMAND
 +
 +      return;
 +}
 +
 +
 +#endif
index 4238f2828e267ca9614f2f46754196735dab6eb5,522ef4b7a4d5f2cda5960f3e3dffd0ee18273e8e..6edccafb156f8a48a40a325ff67438b533c88282
@@@ -1,25 -1,3 +1,25 @@@
 +#if defined(CSQC)
 +#elif defined(MENUQC)
 +#elif defined(SVQC)
 +      #include "../../dpdefs/progsdefs.qh"
 +    #include "../../dpdefs/dpextensions.qh"
 +    #include "../../common/constants.qh"
 +    #include "../../common/util.qh"
 +    #include "../../common/command/shared_defs.qh"
 +    #include "../autocvars.qh"
 +    #include "../constants.qh"
 +    #include "../defs.qh"
 +    #include "../../common/notifications.qh"
 +    #include "../mutators/mutators_include.qh"
 +    #include "../../common/mapinfo.qh"
 +    #include "common.qh"
 +    #include "vote.qh"
 +    #include "../../common/playerstats.qh"
 +    #include "../scores.qh"
 +    #include "../race.qh"
 +    #include "../round_handler.qh"
 +#endif
 +
  // =============================================
  //  Server side voting code, reworked by Samual
  //  Last updated: December 27th, 2011
@@@ -28,7 -6,7 +28,7 @@@
  //  Nagger for players to know status of voting
  float Nagger_SendEntity(entity to, float sendflags)
  {
 -      float nags, i, f, b;
 +      int nags, i, f, b;
        entity e;
        WriteByte(MSG_ENTITY, ENT_CLIENT_NAGGER);
  
                }
        }
  
 -      return TRUE;
 +      return true;
  }
  
  void Nagger_Init()
  {
 -      Net_LinkEntity(nagger = spawn(), FALSE, 0, Nagger_SendEntity);
 +      Net_LinkEntity(nagger = spawn(), false, 0, Nagger_SendEntity);
  }
  
  void Nagger_VoteChanged()
@@@ -328,7 -306,7 +328,7 @@@ void VoteThink(
        if(vote_endtime > 0) // a vote was called
        if(time > vote_endtime) // time is up
        {
 -              VoteCount(FALSE);
 +              VoteCount(false);
        }
  
        return;
@@@ -418,7 -396,7 +418,7 @@@ void reset_map(float dorespawn
  void ReadyRestart_think()
  {
        restart_mapalreadyrestarted = 1;
 -      reset_map(TRUE);
 +      reset_map(true);
        Score_ClearAll();
        remove(self);
  
@@@ -479,7 -457,7 +479,7 @@@ void ReadyRestart_force(
        if(autocvar_sv_timeout) { FOR_EACH_REALPLAYER(tmp_player) { tmp_player.allowed_timeouts = autocvar_sv_timeout_number; } }
  
        //reset map immediately if this cvar is not set
 -      if (!autocvar_sv_ready_restart_after_countdown) { reset_map(TRUE); }
 +      if (!autocvar_sv_ready_restart_after_countdown) { reset_map(true); }
  
        if(autocvar_sv_eventlog) { GameLogEcho(":restart"); }
  }
@@@ -545,10 -523,10 +545,10 @@@ float Votecommand_check_assignment(enti
                || ((!from_server && assignment == VC_ASGNMNT_CLIENTONLY)
                || (from_server && assignment == VC_ASGNMNT_SERVERONLY)))
        {
 -              return TRUE;
 +              return true;
        }
  
 -      return FALSE;
 +      return false;
  }
  
  string VoteCommand_extractcommand(string input, float startpos, float argc)
@@@ -569,9 -547,9 +569,9 @@@ float VoteCommand_checknasty(string vot
                || (strstrofs(vote_command, "\n", 0) >= 0)
                || (strstrofs(vote_command, "\r", 0) >= 0)
                || (strstrofs(vote_command, "$", 0) >= 0))
 -              return FALSE;
 +              return false;
  
 -      return TRUE;
 +      return true;
  }
  
  float VoteCommand_checkinlist(string vote_command, string list)
        string l = strcat(" ", list, " ");
  
        if(strstrofs(l, strcat(" ", vote_command, " "), 0) >= 0)
 -              return TRUE;
 +              return true;
  
 -      return FALSE;
 +      return false;
  }
  
  string ValidateMap(string validated_map, entity caller)
@@@ -621,7 -599,7 +621,7 @@@ float VoteCommand_checkargs(float start
        float checkmate;
  
        if(cmdrestriction == "")
 -              return TRUE;
 +              return true;
  
        ++startpos; // skip command name
  
  
        minargs = stof(cmdrestriction);
        if(argc - startpos < minargs)
 -              return FALSE;
 +              return false;
  
        p = strstrofs(cmdrestriction, ";", 0); // find first semicolon
  
 -      for(;;)
 +      for (;;)
        {
                // we know that at any time, startpos <= argc - minargs
                // so this means: argc-minargs >= startpos >= argc, thus
                                break;
  
                        // otherwise fail
 -                      return FALSE;
 +                      return false;
                }
  
                // cut to next semicolon
                        checkmate = strlen(arg);
                        for(check = 0; check < checkmate; ++check)
                                if(strstrofs(charlist, substring(arg, check, 1), 0) < 0)
 -                                      return FALSE; // not allowed character
 +                                      return false; // not allowed character
                        // all characters are allowed. FINE.
                }
  
                p = q;
        }
  
 -      return TRUE;
 +      return true;
  }
  
  float VoteCommand_parse(entity caller, string vote_command, string vote_list, float startpos, float argc)
                &&
                (strlen(substring(vote_command, argv_start_index(startpos), strlen(vote_command) - argv_start_index(startpos))) > autocvar_sv_vote_limit)
        )
 -              return FALSE;
 +              return false;
  
        if (!VoteCommand_checkinlist(first_command, vote_list))
 -              return FALSE;
 +              return false;
  
        if (!VoteCommand_checkargs(startpos, argc))
 -              return FALSE;
 +              return false;
  
        switch(first_command) // now go through and parse the proper commands to adjust as needed.
        {
                case "kickban": // catch all kick/kickban commands
                {
                        entity victim = GetIndexedEntity(argc, (startpos + 1));
 -                      float accepted = VerifyClientEntity(victim, TRUE, FALSE);
 +                      float accepted = VerifyClientEntity(victim, true, false);
  
                        if(accepted > 0)
                        {
                                vote_parsed_command = strcat(first_command, " # ", ftos(num_for_edict(victim)), " ", command_arguments);
                                vote_parsed_display = strcat("^1", vote_command, " (^7", victim.netname, "^1): ", reason);
                        }
 -                      else { print_to(caller, strcat("vcall: ", GetClientErrorString(accepted, argv(startpos + 1)), ".\n")); return FALSE; }
 +                      else { print_to(caller, strcat("vcall: ", GetClientErrorString(accepted, argv(startpos + 1)), ".\n")); return false; }
  
                        break;
                }
                case "gotomap": // re-direct all map selection commands to gotomap
                {
                        vote_command = ValidateMap(argv(startpos + 1), caller);
 -                      if (!vote_command) { return FALSE; }
 +                      if (!vote_command) { return false; }
                        vote_parsed_command = strcat("gotomap ", vote_command);
                        vote_parsed_display = strzone(strcat("^1", vote_parsed_command));
  
                }
        }
  
 -      return TRUE;
 +      return true;
  }
  
  
@@@ -779,7 -757,7 +779,7 @@@ void VoteCommand_abstain(float request
                                print_to(caller, "^1You abstained from your vote.");
                                caller.vote_selection = VOTE_SELECT_ABSTAIN;
                                msg_entity = caller;
 -                              if(!autocvar_sv_vote_singlecount) { VoteCount(FALSE); }
 +                              if(!autocvar_sv_vote_singlecount) { VoteCount(false); }
                        }
  
                        return;
@@@ -814,6 -792,7 +814,7 @@@ void VoteCommand_call(float request, en
                        else if(!autocvar_sv_vote_gamestart && time < game_starttime) { print_to(caller, "^1Vote calling is not allowed before the match has started."); }
                        else if(vote_called) { print_to(caller, "^1There is already a vote called."); }
                        else if(!spectators_allowed && (caller && !IS_PLAYER(caller))) { print_to(caller, "^1Only players can call a vote."); }
+                       else if(caller && !IS_CLIENT(caller)) { print_to(caller, "^1Only connected clients can vote."); }
                        else if(timeout_status) { print_to(caller, "^1You can not call a vote while a timeout is active."); }
                        else if(caller && (time < caller.vote_waittime)) { print_to(caller, strcat("^1You have to wait ^2", ftos(ceil(caller.vote_waittime - time)), "^1 seconds before you can again call a vote.")); }
                        else if (!VoteCommand_checknasty(vote_command)) { print_to(caller, "^1Syntax error in command, see 'vhelp' for more info."); }
                                bprint("\{1}^2* ^3", GetCallerName(vote_caller), "^2 calls a vote for ", vote_called_display, "\n");
                                if(autocvar_sv_eventlog) { GameLogEcho(strcat(":vote:vcall:", ftos(vote_caller.playerid), ":", vote_called_display)); }
                                Nagger_VoteChanged();
 -                              VoteCount(TRUE); // needed if you are the only one
 +                              VoteCount(true); // needed if you are the only one
                        }
  
                        return;
@@@ -895,7 -874,7 +896,7 @@@ void VoteCommand_master(float request, 
  
                                                else // everything went okay, proceed with giving this player master privilages
                                                {
 -                                                      caller.vote_master = TRUE;
 +                                                      caller.vote_master = true;
                                                        print_to(caller, strcat("Accepted vote master login from ", GetCallerName(caller)));
                                                        bprint("\{1}^2* ^3", GetCallerName(caller), "^2 logged in as ^3master^2\n");
                                                        if(autocvar_sv_eventlog) { GameLogEcho(strcat(":vote:vlogin:", ftos(caller.playerid))); }
                                                        bprint("\{1}^2* ^3", GetCallerName(vote_caller), "^2 calls a vote to become ^3master^2.\n");
                                                        if(autocvar_sv_eventlog) { GameLogEcho(strcat(":vote:vcall:", ftos(vote_caller.playerid), ":", vote_called_display)); }
                                                        Nagger_VoteChanged();
 -                                                      VoteCount(TRUE); // needed if you are the only one
 +                                                      VoteCount(true); // needed if you are the only one
                                                }
  
                                                return;
@@@ -967,7 -946,7 +968,7 @@@ void VoteCommand_no(float request, enti
                                print_to(caller, "^1You rejected the vote.");
                                caller.vote_selection = VOTE_SELECT_REJECT;
                                msg_entity = caller;
 -                              if(!autocvar_sv_vote_singlecount) { VoteCount(FALSE); }
 +                              if(!autocvar_sv_vote_singlecount) { VoteCount(false); }
                        }
  
                        return;
@@@ -1044,7 -1023,7 +1045,7 @@@ void VoteCommand_yes(float request, ent
                                print_to(caller, "^1You accepted the vote.");
                                caller.vote_selection = VOTE_SELECT_ACCEPT;
                                msg_entity = caller;
 -                              if(!autocvar_sv_vote_singlecount) { VoteCount(FALSE); }
 +                              if(!autocvar_sv_vote_singlecount) { VoteCount(false); }
                        }
  
                        return;
@@@ -1110,7 -1089,7 +1111,7 @@@ void VoteCommand_macro_help(entity call
                #define VOTE_COMMAND(name,function,description,assignment) \
                        { if(Votecommand_check_assignment(caller, assignment)) { print_to(caller, strcat("  ^2", name, "^7: ", description)); } }
  
 -              VOTE_COMMANDS(0, caller, 0, "")
 +              VOTE_COMMANDS(0, caller, 0, "");
                #undef VOTE_COMMAND
  
                print_to(caller, strcat("\nUsage:^3 ", command_origin, " vote COMMAND...^7, where possible commands are listed above.\n"));
                #define VOTE_COMMAND(name,function,description,assignment) \
                        { if(Votecommand_check_assignment(caller, assignment)) { if(name == strtolower(argv(2))) { function; return; } } }
  
 -              VOTE_COMMANDS(CMD_REQUEST_USAGE, caller, argc, "")
 +              VOTE_COMMANDS(CMD_REQUEST_USAGE, caller, argc, "");
                #undef VOTE_COMMAND
        }
  
  float VoteCommand_macro_command(entity caller, float argc, string vote_command)
  {
        #define VOTE_COMMAND(name,function,description,assignment) \
 -              { if(Votecommand_check_assignment(caller, assignment)) { if(name == strtolower(argv(1))) { function; return TRUE; } } }
 +              { if(Votecommand_check_assignment(caller, assignment)) { if(name == strtolower(argv(1))) { function; return true; } } }
  
 -      VOTE_COMMANDS(CMD_REQUEST_COMMAND, caller, argc, vote_command)
 +      VOTE_COMMANDS(CMD_REQUEST_COMMAND, caller, argc, vote_command);
        #undef VOTE_COMMAND
  
 -      return FALSE;
 +      return false;
  }
  
  
diff --combined qcsrc/server/ipban.qc
index 81ca387cd726a188f652bfe6f626ddf19127c216,d8a70fe658a238fe1d4e414cfd3164d472ee7a9e..bd9880839d69f33ab220fd54a40eed7f87281460
@@@ -1,16 -1,3 +1,16 @@@
 +#if defined(CSQC)
 +#elif defined(MENUQC)
 +#elif defined(SVQC)
 +      #include "../dpdefs/progsdefs.qh"
 +    #include "../dpdefs/dpextensions.qh"
 +    #include "../common/constants.qh"
 +    #include "../common/util.qh"
 +    #include "autocvars.qh"
 +    #include "defs.qh"
 +    #include "command/banning.qh"
 +    #include "ipban.qh"
 +#endif
 +
  /*
   * Protocol of online ban list:
   *
@@@ -251,7 -238,7 +251,7 @@@ void OnlineBanList_Think(
        remove(self);
  }
  
 -#define BAN_MAX 256
 +const float BAN_MAX = 256;
  float ban_loaded;
  string ban_ip[BAN_MAX];
  float ban_expire[BAN_MAX];
@@@ -289,11 -276,11 +289,11 @@@ void Ban_SaveBans(
  float Ban_Delete(float i)
  {
        if(i < 0)
 -              return FALSE;
 +              return false;
        if(i >= ban_count)
 -              return FALSE;
 +              return false;
        if(ban_expire[i] == 0)
 -              return FALSE;
 +              return false;
        if(ban_expire[i] > 0)
        {
                OnlineBanList_SendUnban(ban_ip[i]);
        ban_expire[i] = 0;
        ban_ip[i] = "";
        Ban_SaveBans();
 -      return TRUE;
 +      return true;
  }
  
  void Ban_LoadBans()
        for(i = 0; i < ban_count; ++i)
                Ban_Delete(i);
        ban_count = 0;
 -      ban_loaded = TRUE;
 +      ban_loaded = true;
        n = tokenize_console(autocvar_g_banned_list);
        if(stof(argv(0)) == 1)
        {
@@@ -373,10 -360,10 +373,10 @@@ float Ban_GetClientIP(entity client
                goto ipv6;
        i2 = strstrofs(s, ".", i1 + 1);
        if(i2 < 0)
 -              return FALSE;
 +              return false;
        i3 = strstrofs(s, ".", i2 + 1);
        if(i3 < 0)
 -              return FALSE;
 +              return false;
        i4 = strstrofs(s, ".", i3 + 1);
        if(i4 >= 0)
                s = substring(s, 0, i4);
        ban_ip2 = substring(s, 0, i2); // 16
        ban_ip3 = substring(s, 0, i3); // 24
        ban_ip4 = strcat1(s); // 32
 -      return TRUE;
 +      return true;
  
  :ipv6
        i1 = strstrofs(s, ":", 0);
        if(i1 < 0)
 -              return FALSE;
 +              return false;
        i1 = strstrofs(s, ":", i1 + 1);
        if(i1 < 0)
 -              return FALSE;
 +              return false;
        i2 = strstrofs(s, ":", i1 + 1);
        if(i2 < 0)
 -              return FALSE;
 +              return false;
        i3 = strstrofs(s, ":", i2 + 1);
        if(i3 < 0)
 -              return FALSE;
 +              return false;
  
        ban_ip1 = strcat(substring(s, 0, i1), "::/32"); // 32
        ban_ip2 = strcat(substring(s, 0, i2), "::/48"); // 48
        else
                ban_ip3 = strcat(substring(s, 0, i2), ":0::/56");
  
 -      return TRUE;
 +      return true;
  }
  
  float Ban_IsClientBanned(entity client, float idx)
        if(!ban_loaded)
                Ban_LoadBans();
        if(!Ban_GetClientIP(client))
 -              return FALSE;
 +              return false;
        if(idx < 0)
        {
                b = 0;
                b = idx;
                e = idx + 1;
        }
 -      ipbanned = FALSE;
 +      ipbanned = false;
        for(i = b; i < e; ++i)
        {
                string s;
                if(time > ban_expire[i])
                        continue;
                s = ban_ip[i];
 -              if(ban_ip1 == s) ipbanned = TRUE;
 -              if(ban_ip2 == s) ipbanned = TRUE;
 -              if(ban_ip3 == s) ipbanned = TRUE;
 -              if(ban_ip4 == s) ipbanned = TRUE;
 -              if(ban_idfp == s) return TRUE;
 +              if(ban_ip1 == s) ipbanned = true;
 +              if(ban_ip2 == s) ipbanned = true;
 +              if(ban_ip3 == s) ipbanned = true;
 +              if(ban_ip4 == s) ipbanned = true;
 +              if(ban_idfp == s) return true;
        }
        if(ipbanned)
        {
                if(!autocvar_g_banned_list_idmode)
 -                      return TRUE;
 +                      return true;
                if (!ban_idfp)
 -                      return TRUE;
 +                      return true;
        }
 -      return FALSE;
 +      return false;
  }
  
  float Ban_MaybeEnforceBan(entity client)
                s = strcat("^1NOTE:^7 banned client ", client.netaddress, " just tried to enter\n");
                dropclient(client);
                bprint(s);
 -              return TRUE;
 +              return true;
        }
 -      return FALSE;
 +      return false;
  }
  
  .float ban_checked;
  float Ban_MaybeEnforceBanOnce(entity client)
  {
        if(client.ban_checked)
 -              return FALSE;
 -      client.ban_checked = TRUE;
 +              return false;
 +      client.ban_checked = true;
-       return Ban_MaybeEnforceBan(self);
+       return Ban_MaybeEnforceBan(client);
  }
  
  string Ban_Enforce(float i, string reason)
  
        // Enforce our new ban
        s = "";
-       FOR_EACH_REALCLIENT(e)
+       FOR_EACH_CLIENTSLOT(e)
+               if (IS_REAL_CLIENT(e))
                if(Ban_IsClientBanned(e, i))
                {
                        if(reason != "")
@@@ -529,7 -517,7 +530,7 @@@ float Ban_Insert(string ip, float banti
                                        if(substring(reason, 0, 1) != "~") // like IRC: unauthenticated banner
                                                OnlineBanList_SendBan(ip, bantime, reason);
  
 -                      return FALSE;
 +                      return false;
                }
  
        // do we have a free slot?
        if(ban_expire[i] > time + bantime)
        {
                print(ip, " could not get banned due to no free ban slot\n");
 -              return FALSE;
 +              return false;
        }
        // okay, insert our new victim as i
        Ban_Delete(i);
                        if(substring(reason, 0, 1) != "~") // like IRC: unauthenticated banner
                                OnlineBanList_SendBan(ip, bantime, reason);
  
 -      return TRUE;
 +      return true;
  }
  
  void Ban_KickBanClient(entity client, float bantime, float masksize, string reason)
index 0c8c105d5c99db0cc571ab8091bf50b19f6bf1db,0000000000000000000000000000000000000000..7cebe9c5d17d59cb213fbd53b2478a19dc601ca3
mode 100644,000000..100644
--- /dev/null
@@@ -1,684 -1,0 +1,687 @@@
 +#ifndef MISCFUNCTIONS_H
 +#define MISCFUNCTIONS_H
 +
 +#include "t_items.qh"
 +
 +#include "mutators/base.qh"
 +#include "mutators/gamemode_race.qh"
 +
 +#include "../common/constants.qh"
 +#include "../common/mapinfo.qh"
 +
 +#ifdef RELEASE
 +#define cvar_string_normal builtin_cvar_string
 +#define cvar_normal builtin_cvar
 +#else
 +string cvar_string_normal(string n)
 +{
 +      if (!(cvar_type(n) & 1))
 +              backtrace(strcat("Attempt to access undefined cvar: ", n));
 +      return builtin_cvar_string(n);
 +}
 +
 +float cvar_normal(string n)
 +{
 +      return stof(cvar_string_normal(n));
 +}
 +#endif
 +#define cvar_set_normal builtin_cvar_set
 +
 +.vector dropped_origin;
 +.void(void) uncustomizeentityforclient;
 +.float uncustomizeentityforclient_set;
 +.float nottargeted;
 +
 +
 +float DistributeEvenly_amount;
 +float DistributeEvenly_totalweight;
 +var void remove(entity e);
 +void objerror(string s);
 +void droptofloor();
 +void() spawnfunc_info_player_deathmatch; // needed for the other spawnpoints
 +void() spawnpoint_use;
 +void() SUB_Remove;
 +
 +void attach_sameorigin(entity e, entity to, string tag);
 +
 +void crosshair_trace(entity pl);
 +
 +void crosshair_trace_plusvisibletriggers(entity pl);
 +
 +void detach_sameorigin(entity e);
 +
 +void follow_sameorigin(entity e, entity to);
 +
 +string formatmessage(string msg);
 +
 +void GameLogEcho(string s);
 +
 +void GameLogInit();
 +
 +void GameLogClose();
 +
 +void GetCvars(float f);
 +
 +string GetMapname();
 +
 +float isPushable(entity e);
 +
 +float LostMovetypeFollow(entity ent);
 +
 +float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance);
 +
 +string NearestLocation(vector p);
 +
 +void play2(entity e, string filename);
 +
 +string playername(entity p);
 +
 +void precache();
 +
 +void remove_safely(entity e);
 +
 +void remove_unsafely(entity e);
 +
 +void SetMovetypeFollow(entity ent, entity e);
 +
 +vector shotorg_adjust_values(vector vecs, float y_is_right, float visual, float algn);
 +
 +void soundto(float dest, entity e, float chan, string samp, float vol, float atten);
 +
 +void stopsound(entity e, float chan);
 +
 +float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma);
 +
 +void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag);
 +
 +void WarpZone_crosshair_trace(entity pl);
 +
 +void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag);
 +
 +
 +#define IFTARGETED if(!self.nottargeted && self.targetname != "")
 +
 +#define ITEM_TOUCH_NEEDKILL() (((trace_dpstartcontents | trace_dphitcontents) & DPCONTENTS_NODROP) || (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY))
 +#define ITEM_DAMAGE_NEEDKILL(dt) (((dt) == DEATH_HURTTRIGGER) || ((dt) == DEATH_SLIME) || ((dt) == DEATH_LAVA) || ((dt) == DEATH_SWAMP))
 +
 +#define PROJECTILE_TOUCH if(WarpZone_Projectile_Touch()) return
 +
 +const string STR_PLAYER = "player";
 +const string STR_SPECTATOR = "spectator";
 +const string STR_OBSERVER = "observer";
 +
 +#define IS_PLAYER(v)                  (v.classname == STR_PLAYER)
 +#define IS_SPEC(v)                            (v.classname == STR_SPECTATOR)
 +#define IS_OBSERVER(v)                        (v.classname == STR_OBSERVER)
 +#define IS_CLIENT(v)                  (v.flags & FL_CLIENT)
 +#define IS_BOT_CLIENT(v)              (clienttype(v) == CLIENTTYPE_BOT)
 +#define IS_REAL_CLIENT(v)             (clienttype(v) == CLIENTTYPE_REAL)
 +#define IS_NOT_A_CLIENT(v)            (clienttype(v) == CLIENTTYPE_NOTACLIENT)
 +
 +#define FOR_EACH_CLIENTSLOT(v) for(v = world; (v = nextent(v)) && (num_for_edict(v) <= maxclients); )
 +#define FOR_EACH_CLIENT(v) FOR_EACH_CLIENTSLOT(v) if(IS_CLIENT(v))
 +#define FOR_EACH_REALCLIENT(v) FOR_EACH_CLIENT(v) if(IS_REAL_CLIENT(v))
 +
 +#define FOR_EACH_PLAYER(v) FOR_EACH_CLIENT(v) if(IS_PLAYER(v))
 +#define FOR_EACH_SPEC(v) FOR_EACH_CLIENT(v) if (!IS_PLAYER(v)) // Samual: shouldn't this be IS_SPEC(v)? and rather create a separate macro to include observers too
 +#define FOR_EACH_REALPLAYER(v) FOR_EACH_REALCLIENT(v) if(IS_PLAYER(v))
 +
 +#define FOR_EACH_MONSTER(v) for(v = world; (v = findflags(v, flags, FL_MONSTER)) != world; )
 +
 +#define CENTER_OR_VIEWOFS(ent) (ent.origin + (IS_PLAYER(ent) ? ent.view_ofs : ((ent.mins + ent.maxs) * 0.5)))
 +
 +// copies a string to a tempstring (so one can strunzone it)
 +string strcat1(string s) = #115; // FRIK_FILE
 +
 +float logfile_open;
 +float logfile;
 +
 +#define strstr strstrofs
 +/*
 +// NOTE: DO NOT USE THIS FUNCTION TOO OFTEN.
 +// IT WILL MOST PROBABLY DESTROY _ALL_ OTHER TEMP
 +// STRINGS AND TAKE QUITE LONG. haystack and needle MUST
 +// BE CONSTANT OR strzoneD!
 +float strstr(string haystack, string needle, float offset)
 +{
 +      float len, endpos;
 +      string found;
 +      len = strlen(needle);
 +      endpos = strlen(haystack) - len;
 +      while(offset <= endpos)
 +      {
 +              found = substring(haystack, offset, len);
 +              if(found == needle)
 +                      return offset;
 +              offset = offset + 1;
 +      }
 +      return -1;
 +}
 +*/
 +
 +const float NUM_NEAREST_ENTITIES = 4;
 +entity nearest_entity[NUM_NEAREST_ENTITIES];
 +float nearest_length[NUM_NEAREST_ENTITIES];
 +
 +
 +//#NO AUTOCVARS START
 +
 +float g_pickup_shells;
 +float g_pickup_shells_max;
 +float g_pickup_nails;
 +float g_pickup_nails_max;
 +float g_pickup_rockets;
 +float g_pickup_rockets_max;
 +float g_pickup_cells;
 +float g_pickup_cells_max;
 +float g_pickup_plasma;
 +float g_pickup_plasma_max;
 +float g_pickup_fuel;
 +float g_pickup_fuel_jetpack;
 +float g_pickup_fuel_max;
 +float g_pickup_armorsmall;
 +float g_pickup_armorsmall_max;
 +float g_pickup_armorsmall_anyway;
 +float g_pickup_armormedium;
 +float g_pickup_armormedium_max;
 +float g_pickup_armormedium_anyway;
 +float g_pickup_armorbig;
 +float g_pickup_armorbig_max;
 +float g_pickup_armorbig_anyway;
 +float g_pickup_armorlarge;
 +float g_pickup_armorlarge_max;
 +float g_pickup_armorlarge_anyway;
 +float g_pickup_healthsmall;
 +float g_pickup_healthsmall_max;
 +float g_pickup_healthsmall_anyway;
 +float g_pickup_healthmedium;
 +float g_pickup_healthmedium_max;
 +float g_pickup_healthmedium_anyway;
 +float g_pickup_healthlarge;
 +float g_pickup_healthlarge_max;
 +float g_pickup_healthlarge_anyway;
 +float g_pickup_healthmega;
 +float g_pickup_healthmega_max;
 +float g_pickup_healthmega_anyway;
 +float g_pickup_ammo_anyway;
 +float g_pickup_weapons_anyway;
 +float g_weaponarena;
 +WepSet g_weaponarena_weapons;
 +float g_weaponarena_random;
 +float g_weaponarena_random_with_blaster;
 +string g_weaponarena_list;
 +float g_weaponspeedfactor;
 +float g_weaponratefactor;
 +float g_weapondamagefactor;
 +float g_weaponforcefactor;
 +float g_weaponspreadfactor;
 +
 +WepSet start_weapons;
 +WepSet start_weapons_default;
 +WepSet start_weapons_defaultmask;
 +int start_items;
 +float start_ammo_shells;
 +float start_ammo_nails;
 +float start_ammo_rockets;
 +float start_ammo_cells;
 +float start_ammo_plasma;
 +float start_ammo_fuel;
 +float start_health;
 +float start_armorvalue;
 +WepSet warmup_start_weapons;
 +WepSet warmup_start_weapons_default;
 +WepSet warmup_start_weapons_defaultmask;
 +#define WARMUP_START_WEAPONS ((g_warmup_allguns == 1) ? (warmup_start_weapons & (weaponsInMap | start_weapons)) : warmup_start_weapons)
 +float warmup_start_ammo_shells;
 +float warmup_start_ammo_nails;
 +float warmup_start_ammo_rockets;
 +float warmup_start_ammo_cells;
 +float warmup_start_ammo_plasma;
 +float warmup_start_ammo_fuel;
 +float warmup_start_health;
 +float warmup_start_armorvalue;
 +float g_weapon_stay;
 +
 +float want_weapon(entity weaponinfo, float allguns) // WEAPONTODO: what still needs done?
 +{
 +      int i = weaponinfo.weapon;
 +      int d = 0;
 +
 +      if (!i)
 +              return 0;
 +
 +      if (g_lms || g_ca || allguns)
 +      {
 +              if(weaponinfo.spawnflags & WEP_FLAG_NORMAL)
 +                      d = true;
 +              else
 +                      d = false;
 +      }
 +      else if (g_cts)
 +              d = (i == WEP_SHOTGUN);
 +      else if (g_nexball)
 +              d = 0; // weapon is set a few lines later
 +      else
 +              d = !(!weaponinfo.weaponstart);
 +
 +      if(g_grappling_hook) // if possible, redirect off-hand hook to on-hand hook
 +              d |= (i == WEP_HOOK);
 +      if(!g_cts && (weaponinfo.spawnflags & WEP_FLAG_MUTATORBLOCKED)) // never default mutator blocked guns
 +              d = 0;
 +
 +      float t = weaponinfo.weaponstartoverride;
 +
 +      //print(strcat("want_weapon: ", weaponinfo.netname, " - d: ", ftos(d), ", t: ", ftos(t), ". \n"));
 +
 +      // bit order in t:
 +      // 1: want or not
 +      // 2: is default?
 +      // 4: is set by default?
 +      if(t < 0)
 +              t = 4 | (3 * d);
 +      else
 +              t |= (2 * d);
 +
 +      return t;
 +}
 +
 +void readplayerstartcvars()
 +{
 +      entity e;
 +      float i, j, t;
 +      string s;
 +
 +      // initialize starting values for players
 +      start_weapons = '0 0 0';
 +      start_weapons_default = '0 0 0';
 +      start_weapons_defaultmask = '0 0 0';
 +      start_items = 0;
 +      start_ammo_shells = 0;
 +      start_ammo_nails = 0;
 +      start_ammo_rockets = 0;
 +      start_ammo_cells = 0;
 +      start_ammo_plasma = 0;
 +      start_health = cvar("g_balance_health_start");
 +      start_armorvalue = cvar("g_balance_armor_start");
 +
 +      g_weaponarena = 0;
 +      g_weaponarena_weapons = '0 0 0';
 +
 +      s = cvar_string("g_weaponarena");
 +      if (s == "0" || s == "")
 +      {
 +              if(g_ca)
 +                      s = "most";
 +      }
 +
 +      if (s == "0" || s == "")
 +      {
 +              // no arena
 +      }
 +      else if (s == "off")
 +      {
 +              // forcibly turn off weaponarena
 +      }
 +      else if (s == "all" || s == "1")
 +      {
 +              g_weaponarena = 1;
 +              g_weaponarena_list = "All Weapons";
 +              for (j = WEP_FIRST; j <= WEP_LAST; ++j)
 +              {
 +                      e = get_weaponinfo(j);
 +                      if (!(e.spawnflags & WEP_FLAG_MUTATORBLOCKED))
 +                              g_weaponarena_weapons |= WepSet_FromWeapon(j);
 +              }
 +      }
 +      else if (s == "most")
 +      {
 +              g_weaponarena = 1;
 +              g_weaponarena_list = "Most Weapons";
 +              for (j = WEP_FIRST; j <= WEP_LAST; ++j)
 +              {
 +                      e = get_weaponinfo(j);
 +                      if (!(e.spawnflags & WEP_FLAG_MUTATORBLOCKED))
 +                              if (e.spawnflags & WEP_FLAG_NORMAL)
 +                                      g_weaponarena_weapons |= WepSet_FromWeapon(j);
 +              }
 +      }
 +      else if (s == "none")
 +      {
 +              g_weaponarena = 1;
 +              g_weaponarena_list = "No Weapons";
 +      }
 +      else
 +      {
 +              g_weaponarena = 1;
 +              t = tokenize_console(s);
 +              g_weaponarena_list = "";
 +              for (i = 0; i < t; ++i)
 +              {
 +                      s = argv(i);
 +                      for (j = WEP_FIRST; j <= WEP_LAST; ++j)
 +                      {
 +                              e = get_weaponinfo(j);
 +                              if (e.netname == s)
 +                              {
 +                                      g_weaponarena_weapons |= WepSet_FromWeapon(j);
 +                                      g_weaponarena_list = strcat(g_weaponarena_list, e.message, " & ");
 +                                      break;
 +                              }
 +                      }
 +                      if (j > WEP_LAST)
 +                      {
 +                              print("The weapon mutator list contains an unknown weapon ", s, ". Skipped.\n");
 +                      }
 +              }
 +              g_weaponarena_list = strzone(substring(g_weaponarena_list, 0, strlen(g_weaponarena_list) - 3));
 +      }
 +
 +      if(g_weaponarena)
 +              g_weaponarena_random = cvar("g_weaponarena_random");
 +      else
 +              g_weaponarena_random = 0;
 +      g_weaponarena_random_with_blaster = cvar("g_weaponarena_random_with_blaster");
 +
 +      if (g_weaponarena)
 +      {
 +              g_weapon_stay = 0; // incompatible
 +              start_weapons = g_weaponarena_weapons;
 +              start_items |= IT_UNLIMITED_AMMO;
 +      }
 +      else
 +      {
 +              for (i = WEP_FIRST; i <= WEP_LAST; ++i)
 +              {
 +                      e = get_weaponinfo(i);
 +                      int w = want_weapon(e, false);
 +                      if(w & 1)
 +                              start_weapons |= WepSet_FromWeapon(i);
 +                      if(w & 2)
 +                              start_weapons_default |= WepSet_FromWeapon(i);
 +                      if(w & 4)
 +                              start_weapons_defaultmask |= WepSet_FromWeapon(i);
 +              }
 +      }
 +
 +      if(!cvar("g_use_ammunition"))
 +              start_items |= IT_UNLIMITED_AMMO;
 +
 +      if(start_items & IT_UNLIMITED_WEAPON_AMMO)
 +      {
 +              start_ammo_shells = 999;
 +              start_ammo_nails = 999;
 +              start_ammo_rockets = 999;
 +              start_ammo_cells = 999;
 +              start_ammo_plasma = 999;
 +              start_ammo_fuel = 999;
 +      }
 +      else
 +      {
 +              start_ammo_shells = cvar("g_start_ammo_shells");
 +              start_ammo_nails = cvar("g_start_ammo_nails");
 +              start_ammo_rockets = cvar("g_start_ammo_rockets");
 +              start_ammo_cells = cvar("g_start_ammo_cells");
 +              start_ammo_plasma = cvar("g_start_ammo_plasma");
 +              start_ammo_fuel = cvar("g_start_ammo_fuel");
 +      }
 +
 +      if (warmup_stage)
 +      {
 +              warmup_start_ammo_shells = start_ammo_shells;
 +              warmup_start_ammo_nails = start_ammo_nails;
 +              warmup_start_ammo_rockets = start_ammo_rockets;
 +              warmup_start_ammo_cells = start_ammo_cells;
 +              warmup_start_ammo_plasma = start_ammo_plasma;
 +              warmup_start_ammo_fuel = start_ammo_fuel;
 +              warmup_start_health = start_health;
 +              warmup_start_armorvalue = start_armorvalue;
 +              warmup_start_weapons = start_weapons;
 +              warmup_start_weapons_default = start_weapons_default;
 +              warmup_start_weapons_defaultmask = start_weapons_defaultmask;
 +
 +              if (!g_weaponarena && !g_ca)
 +              {
 +                      warmup_start_ammo_shells = cvar("g_warmup_start_ammo_shells");
 +                      warmup_start_ammo_nails = cvar("g_warmup_start_ammo_nails");
 +                      warmup_start_ammo_rockets = cvar("g_warmup_start_ammo_rockets");
 +                      warmup_start_ammo_cells = cvar("g_warmup_start_ammo_cells");
 +                      warmup_start_ammo_plasma = cvar("g_warmup_start_ammo_plasma");
 +                      warmup_start_ammo_fuel = cvar("g_warmup_start_ammo_fuel");
 +                      warmup_start_health = cvar("g_warmup_start_health");
 +                      warmup_start_armorvalue = cvar("g_warmup_start_armor");
 +                      warmup_start_weapons = '0 0 0';
 +                      warmup_start_weapons_default = '0 0 0';
 +                      warmup_start_weapons_defaultmask = '0 0 0';
 +                      for (i = WEP_FIRST; i <= WEP_LAST; ++i)
 +                      {
 +                              e = get_weaponinfo(i);
 +                              int w = want_weapon(e, g_warmup_allguns);
 +                              if(w & 1)
 +                                      warmup_start_weapons |= WepSet_FromWeapon(i);
 +                              if(w & 2)
 +                                      warmup_start_weapons_default |= WepSet_FromWeapon(i);
 +                              if(w & 4)
 +                                      warmup_start_weapons_defaultmask |= WepSet_FromWeapon(i);
 +                      }
 +              }
 +      }
 +
 +      if (g_jetpack)
 +              start_items |= IT_JETPACK;
 +
 +      MUTATOR_CALLHOOK(SetStartItems);
 +
 +      if ((start_items & IT_JETPACK) || (g_grappling_hook && (start_weapons & WEPSET_HOOK)))
 +      {
 +              start_items |= IT_FUEL_REGEN;
 +              start_ammo_fuel = max(start_ammo_fuel, cvar("g_balance_fuel_rotstable"));
 +              warmup_start_ammo_fuel = max(warmup_start_ammo_fuel, cvar("g_balance_fuel_rotstable"));
 +      }
 +
 +      WepSet precache_weapons = start_weapons;
 +      if (g_warmup_allguns != 1)
 +              precache_weapons |= warmup_start_weapons;
 +      for (i = WEP_FIRST; i <= WEP_LAST; ++i)
 +      {
 +              e = get_weaponinfo(i);
 +              if(precache_weapons & WepSet_FromWeapon(i))
 +                      WEP_ACTION(i, WR_INIT);
 +      }
 +
 +      start_ammo_shells = max(0, start_ammo_shells);
 +      start_ammo_nails = max(0, start_ammo_nails);
 +      start_ammo_rockets = max(0, start_ammo_rockets);
 +      start_ammo_cells = max(0, start_ammo_cells);
 +      start_ammo_plasma = max(0, start_ammo_plasma);
 +      start_ammo_fuel = max(0, start_ammo_fuel);
 +
 +      warmup_start_ammo_shells = max(0, warmup_start_ammo_shells);
 +      warmup_start_ammo_nails = max(0, warmup_start_ammo_nails);
 +      warmup_start_ammo_rockets = max(0, warmup_start_ammo_rockets);
 +      warmup_start_ammo_cells = max(0, warmup_start_ammo_cells);
 +      warmup_start_ammo_plasma = max(0, warmup_start_ammo_plasma);
 +      warmup_start_ammo_fuel = max(0, warmup_start_ammo_fuel);
 +}
 +
 +float g_bugrigs;
 +float g_bugrigs_planar_movement;
 +float g_bugrigs_planar_movement_car_jumping;
 +float g_bugrigs_reverse_spinning;
 +float g_bugrigs_reverse_speeding;
 +float g_bugrigs_reverse_stopping;
 +float g_bugrigs_air_steering;
 +float g_bugrigs_angle_smoothing;
 +float g_bugrigs_friction_floor;
 +float g_bugrigs_friction_brake;
 +float g_bugrigs_friction_air;
 +float g_bugrigs_accel;
 +float g_bugrigs_speed_ref;
 +float g_bugrigs_speed_pow;
 +float g_bugrigs_steer;
 +
 +float sv_autotaunt;
 +float sv_taunt;
 +
 +string GetGametype(); // g_world.qc
 +void mutators_add(); // mutators.qc
 +void readlevelcvars(void)
 +{
 +      // load mutators
 +      mutators_add();
 +
 +      if(cvar("sv_allow_fullbright"))
 +              serverflags |= SERVERFLAG_ALLOW_FULLBRIGHT;
 +
 +    g_bugrigs = cvar("g_bugrigs");
 +    g_bugrigs_planar_movement = cvar("g_bugrigs_planar_movement");
 +    g_bugrigs_planar_movement_car_jumping = cvar("g_bugrigs_planar_movement_car_jumping");
 +    g_bugrigs_reverse_spinning = cvar("g_bugrigs_reverse_spinning");
 +    g_bugrigs_reverse_speeding = cvar("g_bugrigs_reverse_speeding");
 +    g_bugrigs_reverse_stopping = cvar("g_bugrigs_reverse_stopping");
 +    g_bugrigs_air_steering = cvar("g_bugrigs_air_steering");
 +    g_bugrigs_angle_smoothing = cvar("g_bugrigs_angle_smoothing");
 +    g_bugrigs_friction_floor = cvar("g_bugrigs_friction_floor");
 +    g_bugrigs_friction_brake = cvar("g_bugrigs_friction_brake");
 +    g_bugrigs_friction_air = cvar("g_bugrigs_friction_air");
 +    g_bugrigs_accel = cvar("g_bugrigs_accel");
 +    g_bugrigs_speed_ref = cvar("g_bugrigs_speed_ref");
 +    g_bugrigs_speed_pow = cvar("g_bugrigs_speed_pow");
 +    g_bugrigs_steer = cvar("g_bugrigs_steer");
 +
 +      g_instagib = cvar("g_instagib");
 +
 +      sv_clones = cvar("sv_clones");
 +      sv_foginterval = cvar("sv_foginterval");
 +      g_cloaked = cvar("g_cloaked");
 +      g_footsteps = cvar("g_footsteps");
 +      g_grappling_hook = cvar("g_grappling_hook");
 +      g_jetpack = cvar("g_jetpack");
 +      sv_maxidle = cvar("sv_maxidle");
 +      sv_maxidle_spectatorsareidle = cvar("sv_maxidle_spectatorsareidle");
 +      sv_autotaunt = cvar("sv_autotaunt");
 +      sv_taunt = cvar("sv_taunt");
 +
 +      warmup_stage = cvar("g_warmup");
 +      g_warmup_limit = cvar("g_warmup_limit");
 +      g_warmup_allguns = cvar("g_warmup_allguns");
 +      g_warmup_allow_timeout = cvar("g_warmup_allow_timeout");
 +
 +      if ((g_race && g_race_qualifying == 2) || g_assault || cvar("g_campaign"))
 +              warmup_stage = 0; // these modes cannot work together, sorry
 +
 +      g_pickup_respawntime_weapon = cvar("g_pickup_respawntime_weapon");
 +      g_pickup_respawntime_superweapon = cvar("g_pickup_respawntime_superweapon");
 +      g_pickup_respawntime_ammo = cvar("g_pickup_respawntime_ammo");
 +      g_pickup_respawntime_short = cvar("g_pickup_respawntime_short");
 +      g_pickup_respawntime_medium = cvar("g_pickup_respawntime_medium");
 +      g_pickup_respawntime_long = cvar("g_pickup_respawntime_long");
 +      g_pickup_respawntime_powerup = cvar("g_pickup_respawntime_powerup");
 +      g_pickup_respawntimejitter_weapon = cvar("g_pickup_respawntimejitter_weapon");
 +      g_pickup_respawntimejitter_superweapon = cvar("g_pickup_respawntimejitter_superweapon");
 +      g_pickup_respawntimejitter_ammo = cvar("g_pickup_respawntimejitter_ammo");
 +      g_pickup_respawntimejitter_short = cvar("g_pickup_respawntimejitter_short");
 +      g_pickup_respawntimejitter_medium = cvar("g_pickup_respawntimejitter_medium");
 +      g_pickup_respawntimejitter_long = cvar("g_pickup_respawntimejitter_long");
 +      g_pickup_respawntimejitter_powerup = cvar("g_pickup_respawntimejitter_powerup");
 +
 +      g_weaponspeedfactor = cvar("g_weaponspeedfactor");
 +      g_weaponratefactor = cvar("g_weaponratefactor");
 +      g_weapondamagefactor = cvar("g_weapondamagefactor");
 +      g_weaponforcefactor = cvar("g_weaponforcefactor");
 +      g_weaponspreadfactor = cvar("g_weaponspreadfactor");
 +
 +      g_pickup_shells = cvar("g_pickup_shells");
 +      g_pickup_shells_max = cvar("g_pickup_shells_max");
 +      g_pickup_nails = cvar("g_pickup_nails");
 +      g_pickup_nails_max = cvar("g_pickup_nails_max");
 +      g_pickup_rockets = cvar("g_pickup_rockets");
 +      g_pickup_rockets_max = cvar("g_pickup_rockets_max");
 +      g_pickup_cells = cvar("g_pickup_cells");
 +      g_pickup_cells_max = cvar("g_pickup_cells_max");
 +      g_pickup_plasma = cvar("g_pickup_plasma");
 +      g_pickup_plasma_max = cvar("g_pickup_plasma_max");
 +      g_pickup_fuel = cvar("g_pickup_fuel");
 +      g_pickup_fuel_jetpack = cvar("g_pickup_fuel_jetpack");
 +      g_pickup_fuel_max = cvar("g_pickup_fuel_max");
 +      g_pickup_armorsmall = cvar("g_pickup_armorsmall");
 +      g_pickup_armorsmall_max = cvar("g_pickup_armorsmall_max");
 +      g_pickup_armorsmall_anyway = cvar("g_pickup_armorsmall_anyway");
 +      g_pickup_armormedium = cvar("g_pickup_armormedium");
 +      g_pickup_armormedium_max = cvar("g_pickup_armormedium_max");
 +      g_pickup_armormedium_anyway = cvar("g_pickup_armormedium_anyway");
 +      g_pickup_armorbig = cvar("g_pickup_armorbig");
 +      g_pickup_armorbig_max = cvar("g_pickup_armorbig_max");
 +      g_pickup_armorbig_anyway = cvar("g_pickup_armorbig_anyway");
 +      g_pickup_armorlarge = cvar("g_pickup_armorlarge");
 +      g_pickup_armorlarge_max = cvar("g_pickup_armorlarge_max");
 +      g_pickup_armorlarge_anyway = cvar("g_pickup_armorlarge_anyway");
 +      g_pickup_healthsmall = cvar("g_pickup_healthsmall");
 +      g_pickup_healthsmall_max = cvar("g_pickup_healthsmall_max");
 +      g_pickup_healthsmall_anyway = cvar("g_pickup_healthsmall_anyway");
 +      g_pickup_healthmedium = cvar("g_pickup_healthmedium");
 +      g_pickup_healthmedium_max = cvar("g_pickup_healthmedium_max");
 +      g_pickup_healthmedium_anyway = cvar("g_pickup_healthmedium_anyway");
 +      g_pickup_healthlarge = cvar("g_pickup_healthlarge");
 +      g_pickup_healthlarge_max = cvar("g_pickup_healthlarge_max");
 +      g_pickup_healthlarge_anyway = cvar("g_pickup_healthlarge_anyway");
 +      g_pickup_healthmega = cvar("g_pickup_healthmega");
 +      g_pickup_healthmega_max = cvar("g_pickup_healthmega_max");
 +      g_pickup_healthmega_anyway = cvar("g_pickup_healthmega_anyway");
 +
 +      g_pickup_ammo_anyway = cvar("g_pickup_ammo_anyway");
 +      g_pickup_weapons_anyway = cvar("g_pickup_weapons_anyway");
 +
 +    g_weapon_stay = cvar(strcat("g_", GetGametype(), "_weapon_stay"));
 +    if(!g_weapon_stay)
 +        g_weapon_stay = cvar("g_weapon_stay");
 +
 +      if (!warmup_stage)
 +              game_starttime = time + cvar("g_start_delay");
 +
++    for(int i = WEP_FIRST; i <= WEP_LAST; ++i)
++      WEP_ACTION(i, WR_INIT);
++
 +      readplayerstartcvars();
 +}
 +
 +//#NO AUTOCVARS END
 +
 +
 +// Sound functions
 +//string precache_sound (string s) = #19;
 +// hack
 +float precache_sound_index (string s) = #19;
 +
 +const float SND_VOLUME = 1;
 +const float SND_ATTENUATION = 2;
 +const float SND_LARGEENTITY = 8;
 +const float SND_LARGESOUND = 16;
 +
 +// WARNING: this kills the trace globals
 +#define EXACTTRIGGER_TOUCH if(WarpZoneLib_ExactTrigger_Touch()) return
 +#define EXACTTRIGGER_INIT  WarpZoneLib_ExactTrigger_Init()
 +
 +const float INITPRIO_FIRST                            = 0;
 +const float INITPRIO_GAMETYPE                         = 0;
 +const float INITPRIO_GAMETYPE_FALLBACK        = 1;
 +const float INITPRIO_FINDTARGET               = 10;
 +const float INITPRIO_DROPTOFLOOR              = 20;
 +const float INITPRIO_SETLOCATION              = 90;
 +const float INITPRIO_LINKDOORS                        = 91;
 +const float INITPRIO_LAST                             = 99;
 +
 +.void(void) initialize_entity;
 +.float initialize_entity_order;
 +.entity initialize_entity_next;
 +entity initialize_entity_first;
 +
 +
 +
 +
 +
 +float sound_allowed(float dest, entity e);
 +void InitializeEntity(entity e, void(void) func, float order);
 +void SetCustomizer(entity e, float(void) customizer, void(void) uncustomizer);
 +void Net_LinkEntity(entity e, float docull, float dt, float(entity, float) sendfunc);
 +
 +#endif
diff --combined qcsrc/server/t_items.qc
index c2add840f42e8cb5d780b575543c5cfb7bf6ebb5,d4af7e3fd59bf0e61af5d867e848be6381f6cd83..03af802e71c12d8b762c0e34df606ba8c096ee3d
@@@ -1,33 -1,3 +1,33 @@@
 +#if defined(CSQC)
 +    #include "../dpdefs/csprogsdefs.qh"
 +    #include "../common/util.qh"
 +    #include "../common/buffs.qh"
 +    #include "../common/weapons/weapons.qh"
 +    #include "../client/autocvars.qh"
 +    #include "../client/movetypes.qh"
 +    #include "../client/main.qh"
 +    #include "../csqcmodellib/common.qh"
 +    #include "../csqcmodellib/cl_model.qh"
 +    #include "t_items.qh"
 +#elif defined(MENUQC)
 +#elif defined(SVQC)
 +    #include "../dpdefs/progsdefs.qh"
 +    #include "../dpdefs/dpextensions.qh"
 +    #include "../warpzonelib/util_server.qh"
 +    #include "../common/constants.qh"
 +    #include "../common/util.qh"
 +    #include "../common/monsters/monsters.qh"
 +    #include "../common/weapons/weapons.qh"
 +    #include "weapons/weaponsystem.qh"
 +    #include "t_items.qh"
 +    #include "autocvars.qh"
 +    #include "constants.qh"
 +    #include "defs.qh"
 +    #include "../common/notifications.qh"
 +    #include "../common/deathtypes.qh"
 +    #include "mutators/mutators_include.qh"
 +#endif
 +
  #ifdef CSQC
  void ItemDraw()
  {
@@@ -78,7 -48,7 +78,7 @@@ void ItemDrawSimple(
  
  void ItemRead(float _IsNew)
  {
 -    float sf = ReadByte();
 +    int sf = ReadByte();
  
      if(sf & ISF_LOCATION)
      {
@@@ -242,26 -212,26 +242,26 @@@ float ItemSend(entity to, float sf
        //WriteByte(MSG_ENTITY, self.cnt);
      if(sf & ISF_LOCATION)
      {
 -        WriteCoord(MSG_ENTITY, self.origin_x);
 -        WriteCoord(MSG_ENTITY, self.origin_y);
 -        WriteCoord(MSG_ENTITY, self.origin_z);
 +        WriteCoord(MSG_ENTITY, self.origin.x);
 +        WriteCoord(MSG_ENTITY, self.origin.y);
 +        WriteCoord(MSG_ENTITY, self.origin.z);
      }
  
      if(sf & ISF_ANGLES)
      {
 -        WriteCoord(MSG_ENTITY, self.angles_x);
 -        WriteCoord(MSG_ENTITY, self.angles_y);
 -        WriteCoord(MSG_ENTITY, self.angles_z);
 +        WriteCoord(MSG_ENTITY, self.angles.x);
 +        WriteCoord(MSG_ENTITY, self.angles.y);
 +        WriteCoord(MSG_ENTITY, self.angles.z);
      }
  
      if(sf & ISF_SIZE)
      {
 -        WriteCoord(MSG_ENTITY, self.mins_x);
 -        WriteCoord(MSG_ENTITY, self.mins_y);
 -        WriteCoord(MSG_ENTITY, self.mins_z);
 -        WriteCoord(MSG_ENTITY, self.maxs_x);
 -        WriteCoord(MSG_ENTITY, self.maxs_y);
 -        WriteCoord(MSG_ENTITY, self.maxs_z);
 +        WriteCoord(MSG_ENTITY, self.mins.x);
 +        WriteCoord(MSG_ENTITY, self.mins.y);
 +        WriteCoord(MSG_ENTITY, self.mins.z);
 +        WriteCoord(MSG_ENTITY, self.maxs.x);
 +        WriteCoord(MSG_ENTITY, self.maxs.y);
 +        WriteCoord(MSG_ENTITY, self.maxs.z);
      }
  
      if(sf & ISF_STATUS)
  
      if(sf & ISF_DROP)
      {
 -        WriteCoord(MSG_ENTITY, self.velocity_x);
 -        WriteCoord(MSG_ENTITY, self.velocity_y);
 -        WriteCoord(MSG_ENTITY, self.velocity_z);
 +        WriteCoord(MSG_ENTITY, self.velocity.x);
 +        WriteCoord(MSG_ENTITY, self.velocity.y);
 +        WriteCoord(MSG_ENTITY, self.velocity.z);
      }
  
 -    return TRUE;
 +    return true;
  }
  
  void ItemUpdate(entity item)
@@@ -300,34 -270,34 +300,34 @@@ float have_pickup_item(void
        if(self.flags & FL_POWERUP)
        {
                if(autocvar_g_powerups > 0)
 -                      return TRUE;
 +                      return true;
                if(autocvar_g_powerups == 0)
 -                      return FALSE;
 +                      return false;
        }
        else
        {
                if(autocvar_g_pickup_items > 0)
 -                      return TRUE;
 +                      return true;
                if(autocvar_g_pickup_items == 0)
 -                      return FALSE;
 +                      return false;
                if(g_weaponarena)
                        if(self.weapons || (self.items & IT_AMMO)) // no item or ammo pickups in weaponarena
 -                              return FALSE;
 +                              return false;
        }
 -      return TRUE;
 +      return true;
  }
  
  /*
  float Item_Customize()
  {
        if(self.spawnshieldtime)
 -              return TRUE;
 +              return true;
        if(self.weapons & ~other.weapons)
        {
                self.colormod = '0 0 0';
                self.glowmod = self.colormod;
                self.alpha = 0.5 + 0.5 * g_ghost_items; // halfway more alpha
 -              return TRUE;
 +              return true;
        }
        else
        {
                        self.colormod = stov(autocvar_g_ghost_items_color);
                        self.glowmod = self.colormod;
                        self.alpha = g_ghost_items;
 -                      return TRUE;
 +                      return true;
                }
                else
 -                      return FALSE;
 +                      return false;
        }
  }
  */
@@@ -425,7 -395,7 +425,7 @@@ void Item_Respawn (void
  
        self.think = Item_Think;
        self.nextthink = time;
 -      
 +
        //pointparticles(particleeffectnum("item_respawn"), self.origin + self.mins_z * '0 0 1' + '0 0 48', '0 0 0', 1);
        pointparticles(particleeffectnum("item_respawn"), self.origin + 0.5 * (self.mins + self.maxs), '0 0 0', 1);
  }
@@@ -470,7 -440,7 +470,7 @@@ void Item_RespawnCountdown (void
                        }
                        if(name)
                        {
 -                              WaypointSprite_Spawn(name, 0, 0, self, '0 0 64', world, 0, self, waypointsprite_attached, TRUE, RADARICON_POWERUP, rgb);
 +                              WaypointSprite_Spawn(name, 0, 0, self, '0 0 64', world, 0, self, waypointsprite_attached, true, RADARICON_POWERUP, rgb);
                                if(self.waypointsprite_attached)
                                        WaypointSprite_UpdateBuildFinished(self.waypointsprite_attached, time + ITEM_RESPAWN_TICKS);
                        }
@@@ -538,7 -508,7 +538,7 @@@ void Item_ScheduleInitialRespawn(entit
  float Item_GiveAmmoTo(entity item, entity player, .float ammotype, float ammomax, float mode)
  {
        if (!item.ammotype)
 -              return FALSE;
 +              return false;
  
        if (item.spawnshieldtime)
        {
                }
        }
  
 -      return FALSE;
 +      return false;
  
  :YEAH
        switch(mode)
                default:
                        break;
        }
 -      return TRUE;
 +      return true;
  }
  
  float Item_GiveTo(entity item, entity player)
        float i;
  
        // if nothing happens to player, just return without taking the item
 -      pickedup = FALSE;
 -      _switchweapon = FALSE;
 +      pickedup = false;
 +      _switchweapon = false;
        // in case the player has autoswitch enabled do the following:
        // if the player is using their best weapon before items are given, they
        // probably want to switch to an even better weapon after items are given
        if (player.autoswitch)
        if (player.switchweapon == w_getbestweapon(player))
 -              _switchweapon = TRUE;
 +              _switchweapon = true;
  
        if (!(player.weapons & WepSet_FromWeapon(player.switchweapon)))
 -              _switchweapon = TRUE;
 +              _switchweapon = true;
  
        pickedup |= Item_GiveAmmoTo(item, player, ammo_fuel, g_pickup_fuel_max, ITEM_MODE_FUEL);
        pickedup |= Item_GiveAmmoTo(item, player, ammo_shells, g_pickup_shells_max, ITEM_MODE_NONE);
  
                if (it || (item.spawnshieldtime && item.pickup_anyway))
                {
 -                      pickedup = TRUE;
 +                      pickedup = true;
                        for(i = WEP_FIRST; i <= WEP_LAST; ++i)
                        if(it & WepSet_FromWeapon(i))
                        {
  
        if((it = (item.items - (item.items & player.items)) & IT_PICKUPMASK))
        {
 -              pickedup = TRUE;
 +              pickedup = true;
                player.items |= it;
                Send_Notification(NOTIF_ONE, player, MSG_INFO, INFO_ITEM_WEAPON_GOT, item.netname);
        }
  
        if (item.strength_finished)
        {
 -              pickedup = TRUE;
 +              pickedup = true;
                player.strength_finished = max(player.strength_finished, time) + item.strength_finished;
        }
        if (item.invincible_finished)
        {
 -              pickedup = TRUE;
 +              pickedup = true;
                player.invincible_finished = max(player.invincible_finished, time) + item.invincible_finished;
        }
        if (item.superweapons_finished)
        {
 -              pickedup = TRUE;
 +              pickedup = true;
                player.superweapons_finished = max(player.superweapons_finished, time) + item.superweapons_finished;
        }
  
  
        // always eat teamed entities
        if(item.team)
 -              pickedup = TRUE;
 +              pickedup = true;
  
        if (!pickedup)
                return 0;
@@@ -862,7 -832,7 +862,7 @@@ float weapon_pickupevalfunc(entity play
  float commodity_pickupevalfunc(entity player, entity item)
  {
        float c, i;
 -      float need_shells = FALSE, need_nails = FALSE, need_rockets = FALSE, need_cells = FALSE, need_plasma = FALSE, need_fuel = FALSE;
 +      float need_shells = false, need_nails = false, need_rockets = false, need_cells = false, need_plasma = false, need_fuel = false;
        entity wi;
        c = 0;
  
                        continue;
  
                if(wi.items & IT_SHELLS)
 -                      need_shells = TRUE;
 +                      need_shells = true;
                else if(wi.items & IT_NAILS)
 -                      need_nails = TRUE;
 +                      need_nails = true;
                else if(wi.items & IT_ROCKETS)
 -                      need_rockets = TRUE;
 +                      need_rockets = true;
                else if(wi.items & IT_CELLS)
 -                      need_cells = TRUE;
 +                      need_cells = true;
                else if(wi.items & IT_PLASMA)
 -                      need_plasma = TRUE;
 +                      need_plasma = true;
                else if(wi.items & IT_FUEL)
 -                      need_fuel = TRUE;
 +                      need_fuel = true;
        }
  
        // TODO: figure out if the player even has the weapon this ammo is for?
@@@ -933,7 -903,7 +933,7 @@@ void Item_Damage(entity inflictor, enti
  
  void StartItem (string itemmodel, string pickupsound, float defaultrespawntime, float defaultrespawntimejitter, string itemname, float itemid, float weaponid, float itemflags, float(entity player, entity item) pickupevalfunc, float pickupbasevalue)
  {
 -      startitem_failed = FALSE;
 +      startitem_failed = false;
  
        if(self.model == "")
                self.model = itemmodel;
  
        if(MUTATOR_CALLHOOK(FilterItem)) // error means we do not want the item
        {
 -              startitem_failed = TRUE;
 +              startitem_failed = true;
                remove(self);
                return;
        }
                traceline(self.origin, self.origin, MOVE_NORMAL, self);
                if (trace_dpstartcontents & DPCONTENTS_NODROP)
                {
 -                      startitem_failed = TRUE;
 +                      startitem_failed = true;
                        remove(self);
                        return;
                }
        {
                if(!have_pickup_item())
                {
 -                      startitem_failed = TRUE;
 +                      startitem_failed = true;
                        remove (self);
                        return;
                }
                        else
                                setsize (self, '-16 -16 0', '16 16 32');
  
 -                      // note droptofloor returns FALSE if stuck/or would fall too far
 +                      // note droptofloor returns false if stuck/or would fall too far
                        droptofloor();
                        waypoint_spawnforitem(self);
                }
                {
                        // target_give not yet supported; maybe later
                        print("removed targeted ", self.classname, "\n");
 -                      startitem_failed = TRUE;
 +                      startitem_failed = true;
                        remove (self);
                        return;
                }
                                        error("Mapper sucks.");
                                }
                        }
 -                      self.is_item = TRUE;
 +                      self.is_item = true;
                }
  
                weaponsInMap |= WepSet_FromWeapon(weaponid);
                        self.target = "###item###"; // for finding the nearest item using find()
        }
  
 -      self.bot_pickup = TRUE;
 +      self.bot_pickup = true;
        self.bot_pickupevalfunc = pickupevalfunc;
        self.bot_pickupbasevalue = pickupbasevalue;
        self.mdl = self.model;
        else
                Item_Reset();
  
 -    Net_LinkEntity(self, FALSE, 0, ItemSend);
 -      
 +    Net_LinkEntity(self, false, 0, ItemSend);
 +
        self.SendFlags |= ISF_SIZE;
        if(self.angles)
                self.SendFlags |= ISF_ANGLES;
        // call this hook after everything else has been done
        if(MUTATOR_CALLHOOK(Item_Spawn))
        {
 -              startitem_failed = TRUE;
 +              startitem_failed = true;
                remove(self);
                return;
        }
@@@ -1166,9 -1136,9 +1166,9 @@@ void spawnfunc_item_bullets (void) 
        if(autocvar_sv_q3acompat_machineshotgunswap)
        if(self.classname != "droppedweapon")
        {
 -              weaponswapping = TRUE;
 +              weaponswapping = true;
                spawnfunc_item_shells();
 -              weaponswapping = FALSE;
 +              weaponswapping = false;
                return;
        }
  
@@@ -1201,9 -1171,9 +1201,9 @@@ void spawnfunc_item_shells (void) 
        if(autocvar_sv_q3acompat_machineshotgunswap)
        if(self.classname != "droppedweapon")
        {
 -              weaponswapping = TRUE;
 +              weaponswapping = true;
                spawnfunc_item_bullets();
 -              weaponswapping = FALSE;
 +              weaponswapping = false;
                return;
        }
  
@@@ -1345,6 -1315,7 +1345,7 @@@ void spawnfunc_target_items (void
  {
        float n, i, j;
        entity e;
+       string s;
  
        self.use = target_items_use;
        if(!self.strength_finished)
                                for(j = WEP_FIRST; j <= WEP_LAST; ++j)
                                {
                                        e = get_weaponinfo(j);
-                                       if(argv(i) == e.netname)
+                                       s = W_UndeprecateName(argv(i));
+                                       if(s == e.netname)
                                        {
                                                self.weapons |= WepSet_FromWeapon(j);
                                                if(self.spawnflags == 0 || self.spawnflags == 2)
                else
                {
                        error("invalid spawnflags");
 -#ifdef GMQCC
 -                      itemprefix = string_null;
 -                      valueprefix = string_null;
 -#endif
 +                      itemprefix = valueprefix = string_null;
                }
  
                self.netname = "";
@@@ -1616,10 -1591,10 +1618,10 @@@ float GiveItems(entity e, float beginar
  
        got = 0;
  
 -      _switchweapon = FALSE;
 +      _switchweapon = false;
        if (e.autoswitch)
                if (e.switchweapon == w_getbestweapon(e))
 -                      _switchweapon = TRUE;
 +                      _switchweapon = true;
  
        e.strength_finished = max(0, e.strength_finished - time);
        e.invincible_finished = max(0, e.invincible_finished - time);
                e.superweapons_finished += time;
  
        if (!(e.weapons & WepSet_FromWeapon(e.switchweapon)))
 -              _switchweapon = TRUE;
 +              _switchweapon = true;
        if(_switchweapon)
                W_SwitchWeapon_Force(e, w_getbestweapon(e));