Merge branch 'master' into terencehill/translated_keys
authorterencehill <piuntn@gmail.com>
Sun, 21 Apr 2019 15:44:04 +0000 (17:44 +0200)
committerterencehill <piuntn@gmail.com>
Sun, 21 Apr 2019 15:44:04 +0000 (17:44 +0200)
1  2 
qcsrc/client/main.qc
qcsrc/common/util.qc
qcsrc/common/util.qh
qcsrc/menu/xonotic/keybinder.qc

diff --combined qcsrc/client/main.qc
@@@ -8,13 -8,14 +8,14 @@@
  #include <common/effects/all.qh>
  #include <common/effects/all.inc>
  #include "hud/_mod.qh"
+ #include "commands/cl_cmd.qh"
  #include "mapvoting.qh"
- #include "mutators/events.qh"
+ #include <client/mutators/_mod.qh>
  #include "hud/panel/scoreboard.qh"
  #include "hud/panel/quickmenu.qh"
  #include "shownames.qh"
+ #include "view.qh"
  #include <common/t_items.qh>
- #include "wall.qh"
  #include "weapons/projectile.qh"
  #include <common/deathtypes/all.qh>
  #include <common/items/_mod.qh>
@@@ -24,7 -25,7 +25,7 @@@
  #include <common/net_linked.qh>
  #include <common/net_notice.qh>
  #include <common/scores.qh>
- #include <common/triggers/include.qh>
+ #include <common/mapobjects/_mod.qh>
  #include <common/vehicles/all.qh>
  #include <lib/csqcmodel/cl_model.qh>
  #include <lib/csqcmodel/interpolate.qh>
@@@ -91,7 -92,6 +92,6 @@@ void LoadMenuSkinValues(
  // CSQC_Init : Called every time the CSQC code is initialized (essentially at map load)
  // Useful for precaching things
  
- void ConsoleCommand_macro_init();
  void CSQC_Init()
  {
        prvm_language = strzone(cvar_string("prvm_language"));
  
        registercvar("cl_spawn_near_teammate", "1");
  
+       registercvar("cl_weapon_switch_reload", "1");
+       registercvar("cl_weapon_switch_fallback_to_impulse", "1");
        if(autocvar_cl_lockview)
                cvar_set("cl_lockview", "0");
  
@@@ -223,14 -226,16 +226,16 @@@ void Shutdown(
  
        localcmd("\ncl_hook_shutdown\n");
  
+       localcmd("\n-button12\n");
        deactivate_minigame();
        HUD_MinigameMenu_Close(NULL, NULL, NULL);
  }
  
  .float has_team;
float SetTeam(entity o, int Team)
bool SetTeam(entity o, int Team)
  {
-     TC(int, Team);
+       TC(int, Team);
        devassert_once(Team);
        entity tm;
        if(teamplay)
@@@ -343,7 -348,6 +348,6 @@@ void Playerchecker_Think(entity this
        this.nextthink = time + 0.2;
  }
  
- void TrueAim_Init();
  void PostInit()
  {
        entity playerchecker = new_pure(playerchecker);
  // In the case of mouse input after a setcursormode(1) call, nPrimary is xpos, nSecondary is ypos.
  float CSQC_InputEvent(int bInputType, float nPrimary, float nSecondary)
  {
-     TC(int, bInputType);
-       if (HUD_Panel_InputEvent(bInputType, nPrimary, nSecondary))
+       TC(int, bInputType);
+       bool override = false;
+       override |= HUD_Panel_InputEvent(bInputType, nPrimary, nSecondary);
+       if (override)
                return true;
  
-       if (QuickMenu_InputEvent(bInputType, nPrimary, nSecondary))
-               return true;
+       override |= QuickMenu_InputEvent(bInputType, nPrimary, nSecondary);
  
-       if (HUD_Radar_InputEvent(bInputType, nPrimary, nSecondary))
-               return true;
+       override |= HUD_Radar_InputEvent(bInputType, nPrimary, nSecondary);
  
-       if (MapVote_InputEvent(bInputType, nPrimary, nSecondary))
-               return true;
+       override |= MapVote_InputEvent(bInputType, nPrimary, nSecondary);
  
-       if (HUD_Minigame_InputEvent(bInputType, nPrimary, nSecondary))
+       override |= HUD_Minigame_InputEvent(bInputType, nPrimary, nSecondary);
+       if(override)
                return true;
  
        return false;
  // --------------------------------------------------------------------------
  // BEGIN OPTIONAL CSQC FUNCTIONS
  
- void Ent_Remove(entity this);
  void Ent_RemovePlayerScore(entity this)
  {
        if(this.owner) {
@@@ -531,6 -534,7 +534,7 @@@ NET_HANDLE(ENT_CLIENT_CLIENTDATA, bool 
                race_laptime = 0;
                race_checkpointtime = 0;
                hud_dynamic_shake_factor = -1;
+               spectatee_status_changed_time = time;
        }
        if (autocvar_hud_panel_healtharmor_progressbar_gfx)
        {
@@@ -580,14 -584,14 +584,14 @@@ NET_HANDLE(ENT_CLIENT_NAGGER, bool isne
        {
                for(j = 0; j < maxclients; ++j)
                        if(playerslots[j])
-                               playerslots[j].ready = 1;
+                               playerslots[j].ready = true;
                for(i = 1; i <= maxclients; i += 8)
                {
                        f = ReadByte();
                        for(j = i-1, b = BIT(0); b < BIT(8); b <<= 1, ++j)
                                if (!(f & b))
                                        if(playerslots[j])
-                                               playerslots[j].ready = 0;
+                                               playerslots[j].ready = false;
                }
        }
  
@@@ -608,7 -612,7 +612,7 @@@ NET_HANDLE(ENT_CLIENT_ELIMINATEDPLAYERS
        if (sf & 1) {
                for (int j = 0; j < maxclients; ++j) {
                        if (playerslots[j]) {
-                               playerslots[j].eliminated = 1;
+                               playerslots[j].eliminated = true;
                        }
                }
                for (int i = 1; i <= maxclients; i += 8) {
                                if (f & BIT(b)) continue;
                                int j = i - 1 + b;
                                if (playerslots[j]) {
-                                       playerslots[j].eliminated = 0;
+                                       playerslots[j].eliminated = false;
                                }
                        }
                }
@@@ -797,7 -801,7 +801,7 @@@ NET_HANDLE(ENT_CLIENT_SPAWNEVENT, bool 
  // The only parameter reflects if the entity is "new" to the client, meaning it just came into the client's PVS.
  void CSQC_Ent_Update(entity this, bool isnew)
  {
-       this.sourceLoc = __FILE__ ":" STR(__LINE__);
+       this.sourceLoc = __FILE__":"STR(__LINE__);
        int t = ReadByte();
  
        // set up the "time" global for received entities to be correct for interpolation purposes
@@@ -947,7 -951,6 +951,6 @@@ void Fog_Force(
                localcmd(sprintf("\nfog %s\nr_fog_exp2 0\nr_drawfog 1\n", forcefog));
  }
  
- void Gamemode_Init();
  NET_HANDLE(ENT_CLIENT_SCORES_INFO, bool isnew)
  {
        make_pure(this);
@@@ -1144,6 -1147,9 +1147,9 @@@ NET_HANDLE(TE_CSQC_RACE, bool isNew
                        strcpy(race_speedaward_alltimebest_holder, ReadString());
                        strcpy(race_speedaward_alltimebest_unit, GetSpeedUnit(autocvar_hud_panel_physics_speed_unit));
                        break;
+               case RACE_NET_RANKINGS_CNT:
+                       RANKINGS_DISPLAY_CNT = ReadByte();
+                       break;
                case RACE_NET_SERVER_RANKINGS:
                        float prevpos, del;
              int pos = ReadShort();
                        // move other rankings out of the way
              int i;
                        if (prevpos) {
-                               for (i=prevpos-1;i>pos-1;--i) {
+                               int m = min(prevpos, RANKINGS_DISPLAY_CNT);
+                               for (i=m-1; i>pos-1; --i) {
                                        grecordtime[i] = grecordtime[i-1];
                                        strcpy(grecordholder[i], grecordholder[i-1]);
                                }
                        } else if (del) { // a record has been deleted by the admin
-                               for (i=pos-1; i<= RANKINGS_CNT-1; ++i) {
-                                       if (i == RANKINGS_CNT-1) { // clear out last record
+                               for (i=pos-1; i<= RANKINGS_DISPLAY_CNT-1; ++i) {
+                                       if (i == RANKINGS_DISPLAY_CNT-1) { // clear out last record
                                                grecordtime[i] = 0;
                                                strfree(grecordholder[i]);
                                        }
                                        }
                                }
                        } else { // player has no ranked record yet
-                               for (i=RANKINGS_CNT-1;i>pos-1;--i) {
+                               for (i=RANKINGS_DISPLAY_CNT-1;i>pos-1;--i) {
                                        grecordtime[i] = grecordtime[i-1];
                                        strcpy(grecordholder[i], grecordholder[i-1]);
                                }
                        }
  
+                       if (grecordtime[RANKINGS_DISPLAY_CNT]) {
+                               // kick off the player who fell from the last displayed position
+                               grecordtime[RANKINGS_DISPLAY_CNT] = 0;
+                               strfree(grecordholder[RANKINGS_DISPLAY_CNT]);
+                       }
                        // store new ranking
                        strcpy(grecordholder[pos-1], ReadString());
                        grecordtime[pos-1] = ReadInt24_t();
@@@ -1210,7 -1223,8 +1223,8 @@@ NET_HANDLE(TE_CSQC_PINGPLREPORT, bool i
  
  NET_HANDLE(TE_CSQC_WEAPONCOMPLAIN, bool isNew)
  {
-       complain_weapon = ReadByte();
+       int weapon_id = ReadByte();
+       complain_weapon = Weapons_from(weapon_id);
        complain_weapon_type = ReadByte();
        return = true;
  
  
        switch(complain_weapon_type)
        {
-               case 0: Local_Notification(MSG_MULTI, ITEM_WEAPON_NOAMMO, complain_weapon); break;
-               case 1: Local_Notification(MSG_MULTI, ITEM_WEAPON_DONTHAVE, complain_weapon); break;
-               default: Local_Notification(MSG_MULTI, ITEM_WEAPON_UNAVAILABLE, complain_weapon); break;
+               case 0: Local_Notification(MSG_MULTI, ITEM_WEAPON_NOAMMO, weapon_id); break;
+               case 1: Local_Notification(MSG_MULTI, ITEM_WEAPON_DONTHAVE, weapon_id); break;
+               default: Local_Notification(MSG_MULTI, ITEM_WEAPON_UNAVAILABLE, weapon_id); break;
        }
  }
  
@@@ -1247,8 -1261,6 +1261,8 @@@ string _getcommandkey(string cmd_name, 
                                if(!joy_active && substring(key, 0, 3) == "JOY")
                                        continue;
  
 +                              key = translate_key(key);
 +
                                if (keys == "")
                                        keys = key;
                                else
diff --combined qcsrc/common/util.qc
@@@ -2,7 -2,7 +2,7 @@@
  
  #if defined(CSQC)
      #include "constants.qh"
-       #include "../client/mutators/events.qh"
+       #include <client/mutators/_mod.qh>
      #include "mapinfo.qh"
      #include "notifications/all.qh"
        #include "scores.qh"
  #elif defined(MENUQC)
  #elif defined(SVQC)
      #include "constants.qh"
-       #include "../server/mutators/events.qh"
+       #include <server/mutators/_mod.qh>
      #include "notifications/all.qh"
      #include <common/deathtypes/all.qh>
        #include "scores.qh"
      #include "mapinfo.qh"
  #endif
  
+ #ifdef SVQC
+ float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity) // returns the number of traces done, for benchmarking
+ {
+       vector pos, dir, t;
+       float nudge;
+       entity stopentity;
+       //nudge = 2 * cvar("collision_impactnudge"); // why not?
+       nudge = 0.5;
+       dir = normalize(v2 - v1);
+       pos = v1 + dir * nudge;
+       float c;
+       c = 0;
+       for (;;)
+       {
+               if(pos * dir >= v2 * dir)
+               {
+                       // went too far
+                       trace_fraction = 1;
+                       trace_endpos = v2;
+                       return c;
+               }
+               tracebox(pos, mi, ma, v2, nomonsters, forent);
+               ++c;
+               if(c == 50)
+               {
+                       LOG_TRACE("When tracing from ", vtos(v1), " to ", vtos(v2));
+                       LOG_TRACE("  Nudging gets us nowhere at ", vtos(pos));
+                       LOG_TRACE("  trace_endpos is ", vtos(trace_endpos));
+                       LOG_TRACE("  trace distance is ", ftos(vlen(pos - trace_endpos)));
+               }
+               stopentity = trace_ent;
+               if(trace_startsolid)
+               {
+                       // we started inside solid.
+                       // then trace from endpos to pos
+                       t = trace_endpos;
+                       tracebox(t, mi, ma, pos, nomonsters, forent);
+                       ++c;
+                       if(trace_startsolid)
+                       {
+                               // t is still inside solid? bad
+                               // force advance, then, and retry
+                               pos = t + dir * nudge;
+                               // but if we hit an entity, stop RIGHT before it
+                               if(stopatentity && stopentity && stopentity != ignorestopatentity)
+                               {
+                                       trace_ent = stopentity;
+                                       trace_endpos = t;
+                                       trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
+                                       return c;
+                               }
+                       }
+                       else
+                       {
+                               // we actually LEFT solid!
+                               trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
+                               return c;
+                       }
+               }
+               else
+               {
+                       // pos is outside solid?!? but why?!? never mind, just return it.
+                       trace_endpos = pos;
+                       trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
+                       return c;
+               }
+       }
+ }
+ void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity)
+ {
+       tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent, stopatentity, ignorestopatentity);
+ }
+ #endif
  #ifdef GAMEQC
  /*
+ ==================
+ findbetterlocation
+ Returns a point at least 12 units away from walls
+ (useful for explosion animations, although the blast is performed where it really happened)
+ Ripped from DPMod
+ ==================
+ */
+ vector findbetterlocation (vector org, float mindist)
+ {
+       vector vec = mindist * '1 0 0';
+       int c = 0;
+       while (c < 6)
+       {
+               traceline (org, org + vec, true, NULL);
+               vec = vec * -1;
+               if (trace_fraction < 1)
+               {
+                       vector loc = trace_endpos;
+                       traceline (loc, loc + vec, true, NULL);
+                       if (trace_fraction >= 1)
+                               org = loc + vec;
+               }
+               if (c & 1)
+               {
+                       float h = vec.y;
+                       vec.y = vec.x;
+                       vec.x = vec.z;
+                       vec.z = h;
+               }
+               c = c + 1;
+       }
+       return org;
+ }
+ /*
  * Get "real" origin, in worldspace, even if ent is attached to something else.
  */
  vector real_origin(entity ent)
@@@ -95,7 -217,8 +217,8 @@@ void wordwrap_cb(string s, float l, voi
  
        s = strzone(s);
        lleft = l;
-       for (i = 0;i < strlen(s);++i)
+       int len = strlen(s);
+       for (i = 0; i < len; ++i)
        {
                if (substring(s, i, 2) == "\\n")
                {
                        if (lleft > 0)
                        {
                                callback(" ");
-                               lleft = lleft - 1;
+                               --lleft;
                        }
                }
                else
                {
-                       for (j = i+1;j < strlen(s);++j)
+                       for (j = i+1; j < len; ++j)
                                //    ^^ this skips over the first character of a word, which
                                //       is ALWAYS part of the word
                                //       this is safe since if i+1 == strlen(s), i will become
                                lleft = l;
                        }
                        callback(substring(s, i, wlen));
-                       lleft = lleft - wlen;
+                       lleft -= wlen;
                        i = j - 1;
                }
        }
@@@ -336,19 -459,18 +459,18 @@@ STATIC_INIT(compressShortVector
  
        if(cvar("developer"))
        {
-               LOG_INFO("Verifying vector compression table...");
+               LOG_TRACE("Verifying vector compression table...");
                for(i = 0x0F00; i < 0xFFFF; ++i)
                        if(i != compressShortVector(decompressShortVector(i)))
                        {
-                               LOG_INFOF(
+                               LOG_FATALF(
                                    "BROKEN vector compression: %s -> %s -> %s",
                                    ftos(i),
                                    vtos(decompressShortVector(i)),
                                    ftos(compressShortVector(decompressShortVector(i)))
                  );
-                               error("b0rk");
                        }
-               LOG_INFO("Done.");
+               LOG_TRACE("Done.");
        }
  }
  
@@@ -1127,6 -1249,8 +1249,8 @@@ vector healtharmor_applydamage(float a
        vector v;
        if (DEATH_IS(deathtype, DEATH_DROWN))  // Why should armor help here...
                armorblock = 0;
+       if (deathtype & HITTYPE_ARMORPIERCE)
+               armorblock = 0;
        v.y = bound(0, damage * armorblock, a); // save
        v.x = bound(0, damage - v.y, damage); // take
        v.z = 0;
@@@ -1175,11 -1299,19 +1299,19 @@@ float matchacl(string acl, string str
                if(s == t)
                {
                        r = d;
+                       break; // if we found a killing case, apply it! other settings may be allowed in the future, but this one is caught
                }
        }
        return r;
  }
  
+ ERASEABLE
+ void write_String_To_File(int fh, string str, bool alsoprint)
+ {
+       fputs(fh, str);
+       if (alsoprint) LOG_INFO(str);
+ }
  string get_model_datafilename(string m, float sk, string fil)
  {
        if(m)
@@@ -1306,148 -1438,6 +1438,148 @@@ float get_model_parameters(string m, fl
        return 1;
  }
  
 +string translate_key(string key)
 +{
 +      if (prvm_language == "en") return key;
 +
 +      switch(key)
 +      {
 +              case "TAB":                             return _("TAB");
 +              case "ENTER":                   return _("ENTER");
 +              case "ESCAPE":                  return _("ESCAPE");
 +              case "SPACE":                   return _("SPACE");
 +
 +              case "BACKSPACE":               return _("BACKSPACE");
 +              case "UPARROW":                 return _("UPARROW");
 +              case "DOWNARROW":               return _("DOWNARROW");
 +              case "LEFTARROW":               return _("LEFTARROW");
 +              case "RIGHTARROW":              return _("RIGHTARROW");
 +
 +              case "ALT":                             return _("ALT");
 +              case "CTRL":                    return _("CTRL");
 +              case "SHIFT":                   return _("SHIFT");
 +
 +              case "F1":                              return _("F1");
 +              case "F2":                              return _("F2");
 +              case "F3":                              return _("F3");
 +              case "F4":                              return _("F4");
 +              case "F5":                              return _("F5");
 +              case "F6":                              return _("F6");
 +              case "F7":                              return _("F7");
 +              case "F8":                              return _("F8");
 +              case "F9":                              return _("F9");
 +              case "F10":                             return _("F10");
 +              case "F11":                             return _("F11");
 +              case "F12":                             return _("F12");
 +
 +              case "INS":                             return _("INS");
 +              case "DEL":                             return _("DEL");
 +              case "PGDN":                    return _("PGDN");
 +              case "PGUP":                    return _("PGUP");
 +              case "HOME":                    return _("HOME");
 +              case "END":                             return _("END");
 +
 +              case "PAUSE":                   return _("PAUSE");
 +
 +              case "NUMLOCK":                 return _("NUMLOCK");
 +              case "CAPSLOCK":                return _("CAPSLOCK");
 +              case "SCROLLOCK":               return _("SCROLLOCK");
 +      }
 +
 +      if (substring(key, 0, 3) == "KP_")
 +      {
 +              string subkey = substring(key, 3, -1);
 +              if (IS_DIGIT(substring(key, 3, 1))) // check only first digit
 +              {
 +                      return sprintf(_("KP_%d"), stof(subkey));
 +              }
 +
 +              switch(subkey)
 +              {
 +                      case "INS":                             return sprintf(_("KP_%s"), _("INS"));
 +                      case "END":                             return sprintf(_("KP_%s"), _("END"));
 +                      case "DOWNARROW":               return sprintf(_("KP_%s"), _("DOWNARROW"));
 +                      case "PGDN":                    return sprintf(_("KP_%s"), _("PGDN"));
 +                      case "LEFTARROW":               return sprintf(_("KP_%s"), _("LEFTARROW"));
 +                      case "RIGHTARROW":              return sprintf(_("KP_%s"), _("RIGHTARROW"));
 +                      case "HOME":                    return sprintf(_("KP_%s"), _("HOME"));
 +                      case "UPARROW":                 return sprintf(_("KP_%s"), _("UPARROW"));
 +                      case "PGUP":                    return sprintf(_("KP_%s"), _("PGUP"));
 +                      case "PERIOD":                  return sprintf(_("KP_%s"), _("PERIOD"));
 +                      case "DEL":                             return sprintf(_("KP_%s"), _("DEL"));
 +                      case "DIVIDE":                  return sprintf(_("KP_%s"), _("DIVIDE"));
 +                      case "SLASH":                   return sprintf(_("KP_%s"), _("SLASH"));
 +                      case "MULTIPLY":                return sprintf(_("KP_%s"), _("MULTIPLY"));
 +                      case "MINUS":                   return sprintf(_("KP_%s"), _("MINUS"));
 +                      case "PLUS":                    return sprintf(_("KP_%s"), _("PLUS"));
 +                      case "ENTER":                   return sprintf(_("KP_%s"), _("ENTER"));
 +                      case "EQUALS":                  return sprintf(_("KP_%s"), _("EQUALS"));
 +                      default:                                return key;
 +              }
 +      }
 +
 +      if (key == "PRINTSCREEN") return _("PRINTSCREEN");
 +
 +      if (substring(key, 0, 5) == "MOUSE")
 +              return sprintf(_("MOUSE%d"), stof(substring(key, 5, -1)));
 +
 +      if (key == "MWHEELUP") return _("MWHEELUP");
 +      if (key == "MWHEELDOWN") return _("MWHEELDOWN");
 +
 +      if (substring(key, 0,3) == "JOY")
 +              return sprintf(_("JOY%d"), stof(substring(key, 3, -1)));
 +
 +      if (substring(key, 0,3) == "AUX")
 +              return sprintf(_("AUX%d"), stof(substring(key, 3, -1)));
 +
 +      if (substring(key, 0, 4) == "X360_")
 +      {
 +              string subkey = substring(key, 4, -1);
 +              switch(subkey)
 +              {
 +                      case "DPAD_UP":                                 return sprintf(_("X360_%s"), _("DPAD_UP"));
 +                      case "DPAD_DOWN":                               return sprintf(_("X360_%s"), _("DPAD_DOWN"));
 +                      case "DPAD_LEFT":                               return sprintf(_("X360_%s"), _("DPAD_LEFT"));
 +                      case "DPAD_RIGHT":                              return sprintf(_("X360_%s"), _("DPAD_RIGHT"));
 +                      case "START":                                   return sprintf(_("X360_%s"), _("START"));
 +                      case "BACK":                                    return sprintf(_("X360_%s"), _("BACK"));
 +                      case "LEFT_THUMB":                              return sprintf(_("X360_%s"), _("LEFT_THUMB"));
 +                      case "RIGHT_THUMB":                             return sprintf(_("X360_%s"), _("RIGHT_THUMB"));
 +                      case "LEFT_SHOULDER":                   return sprintf(_("X360_%s"), _("LEFT_SHOULDER"));
 +                      case "RIGHT_SHOULDER":                  return sprintf(_("X360_%s"), _("RIGHT_SHOULDER"));
 +                      case "LEFT_TRIGGER":                    return sprintf(_("X360_%s"), _("LEFT_TRIGGER"));
 +                      case "RIGHT_TRIGGER":                   return sprintf(_("X360_%s"), _("RIGHT_TRIGGER"));
 +                      case "LEFT_THUMB_UP":                   return sprintf(_("X360_%s"), _("LEFT_THUMB_UP"));
 +                      case "LEFT_THUMB_DOWN":                 return sprintf(_("X360_%s"), _("LEFT_THUMB_DOWN"));
 +                      case "LEFT_THUMB_LEFT":                 return sprintf(_("X360_%s"), _("LEFT_THUMB_LEFT"));
 +                      case "LEFT_THUMB_RIGHT":                return sprintf(_("X360_%s"), _("LEFT_THUMB_RIGHT"));
 +                      case "RIGHT_THUMB_UP":                  return sprintf(_("X360_%s"), _("RIGHT_THUMB_UP"));
 +                      case "RIGHT_THUMB_DOWN":                return sprintf(_("X360_%s"), _("RIGHT_THUMB_DOWN"));
 +                      case "RIGHT_THUMB_LEFT":                return sprintf(_("X360_%s"), _("RIGHT_THUMB_LEFT"));
 +                      case "RIGHT_THUMB_RIGHT":               return sprintf(_("X360_%s"), _("RIGHT_THUMB_RIGHT"));
 +                      default:                                                return key;
 +              }
 +      }
 +
 +      if (substring(key, 0, 4) == "JOY_")
 +      {
 +              string subkey = substring(key, 4, -1);
 +              switch(subkey)
 +              {
 +                      case "UP":                      return sprintf(_("JOY_%s"), _("UP"));
 +                      case "DOWN":            return sprintf(_("JOY_%s"), _("DOWN"));
 +                      case "LEFT":            return sprintf(_("JOY_%s"), _("LEFT"));
 +                      case "RIGHT":           return sprintf(_("JOY_%s"), _("RIGHT"));
 +                      default:                        return key;
 +              }
 +      }
 +
 +      if (substring(key, 0, 8) == "MIDINOTE")
 +              return sprintf(_("MIDINOTE%d"), stof(substring(key, 8, -1)));
 +
 +      return key;
 +}
 +
  // x-encoding (encoding as zero length invisible string)
  const string XENCODE_2  = "xX";
  const string XENCODE_22 = "0123456789abcdefABCDEF";
diff --combined qcsrc/common/util.qh
@@@ -1,12 -1,33 +1,33 @@@
  #pragma once
  
+ #ifdef SVQC
+       #include <server/autocvars.qh>
+ #endif
+ #ifdef SVQC
+ float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity); // returns the number of traces done, for benchmarking
+ void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity);
+ #endif
  #ifdef GAMEQC
+ /*
+ ==================
+ findbetterlocation
+ Returns a point at least 12 units away from walls
+ (useful for explosion animations, although the blast is performed where it really happened)
+ Ripped from DPMod
+ ==================
+ */
+ vector findbetterlocation (vector org, float mindist);
  vector real_origin(entity ent);
  #endif
  
  #ifdef SVQC
- // temporary array used to dump weapon and turret settings
- const int MAX_CONFIG_SETTINGS = 256;
+ // temporary array used to dump settings for each weapon / turret
+ const int MAX_CONFIG_SETTINGS = 70;
  string config_queue[MAX_CONFIG_SETTINGS];
  #endif
  
@@@ -114,6 -135,8 +135,8 @@@ string getcurrentmod()
  
  float matchacl(string acl, string str); // matches str against ACL acl (with entries +foo*, +foo, +*foo, +*foo*, and same with - for forbidding)
  
+ void write_String_To_File(int fh, string str, bool alsoprint);
  string get_model_datafilename(string mod, float skn, string fil); // skin -1 will return wildcard, mod string_null will also put wildcard there
  string get_model_parameters_modelname;
  float get_model_parameters_modelskin;
@@@ -133,9 -156,6 +156,9 @@@ float get_model_parameters_fixbone
  string get_model_parameters_desc;
  float get_model_parameters(string mod, float skn); // call with string_null to clear; skin -1 means mod is the filename of the txt file and is to be split
  
 +ERASEABLE
 +string translate_key(string key);
 +
  // x-encoding (encoding as zero length invisible string)
  // encodes approx. 14 bits into 5 bytes of color code string
  const float XENCODE_MAX = 21295; // 2*22*22*22-1
@@@ -10,23 -10,29 +10,29 @@@ const string KEY_NOT_BOUND_CMD = "// no
  
  const int MAX_KEYS_PER_FUNCTION = 2;
  const int MAX_KEYBINDS = 256;
- string Xonotic_KeyBinds_Functions[MAX_KEYBINDS];
- string Xonotic_KeyBinds_Descriptions[MAX_KEYBINDS];
- int Xonotic_KeyBinds_Count = -1;
+ string KeyBinds_Functions[MAX_KEYBINDS];
+ string KeyBinds_Descriptions[MAX_KEYBINDS];
+ int KeyBinds_Count = -1;
  
- void Xonotic_KeyBinds_Read()
+ void KeyBinds_Read()
  {
-       Xonotic_KeyBinds_Count = 0;
+       KeyBinds_Count = 0;
  
-       #define KEYBIND_DEF(func, desc) MACRO_BEGIN \
-               if((Xonotic_KeyBinds_Count < MAX_KEYBINDS)) { \
-                       Xonotic_KeyBinds_Functions[Xonotic_KeyBinds_Count] = strzone(func); \
-                       Xonotic_KeyBinds_Descriptions[Xonotic_KeyBinds_Count] = strzone(desc); \
-                       ++Xonotic_KeyBinds_Count; \
+       #define KEYBIND_DEF(func, desc) MACRO_BEGIN \
+               if((KeyBinds_Count < MAX_KEYBINDS)) { \
+                       KeyBinds_Functions[KeyBinds_Count] = strzone(func); \
+                       KeyBinds_Descriptions[KeyBinds_Count] = strzone(desc); \
+                       ++KeyBinds_Count; \
                } \
-       MACRO_END
+       MACRO_END
  
-       KEYBIND_DEF(""                                      , _("Moving"));
+       #define KEYBIND_EMPTY_LINE() KEYBIND_DEF("", "")
+       #define KEYBIND_HEADER(str) KEYBIND_DEF("", str)
+       #define KEYBIND_IS_SPECIAL(func) (substring(func, 0 ,1) == "*")
+       #define KEYBIND_SPECIAL_DEF(key) KEYBIND_DEF(strcat("*", key), "")
+       KEYBIND_HEADER(_("Moving"));
        KEYBIND_DEF("+forward"                              , _("forward"));
        KEYBIND_DEF("+back"                                 , _("backpedal"));
        KEYBIND_DEF("+moveleft"                             , _("strafe left"));
        KEYBIND_DEF("+jump"                                 , _("jump / swim"));
        KEYBIND_DEF("+crouch"                               , _("crouch / sink"));
        KEYBIND_DEF("+hook"                                 , _("off-hand hook"));
-       KEYBIND_DEF("+jetpack"                              , _("jet pack"));
-       KEYBIND_DEF(""                                      , "");
-       KEYBIND_DEF(""                                      , _("Attacking"));
+       KEYBIND_DEF("+jetpack"                              , _("jetpack"));
+       KEYBIND_EMPTY_LINE();
+       KEYBIND_HEADER(_("Attacking"));
        KEYBIND_DEF("+fire"                                 , _("primary fire"));
        KEYBIND_DEF("+fire2"                                , _("secondary fire"));
-       KEYBIND_DEF(""                                      , "");
-       KEYBIND_DEF(""                                      , _("Weapons"));
+       KEYBIND_EMPTY_LINE();
+       KEYBIND_HEADER(_("Weapons"));
        KEYBIND_DEF("weapprev"                              , CTX(_("WEAPON^previous")));
        KEYBIND_DEF("weapnext"                              , CTX(_("WEAPON^next")));
        KEYBIND_DEF("weaplast"                              , CTX(_("WEAPON^previously used")));
@@@ -59,7 -67,7 +67,7 @@@
  
        for(int imp = 1; imp <= 9; ++imp)
        {
-         string w_list = "";
+               string w_list = "";
                ADD_TO_W_LIST(!(it.spawnflags & WEP_FLAG_MUTATORBLOCKED) && !(it.spawnflags & WEP_FLAG_HIDDEN) && !(it.spawnflags & WEP_FLAG_SUPERWEAPON));
                ADD_TO_W_LIST((it.spawnflags & WEP_FLAG_SUPERWEAPON) && !(it.spawnflags & WEP_FLAG_HIDDEN));
                ADD_TO_W_LIST((it.spawnflags & WEP_FLAG_MUTATORBLOCKED) && !(it.spawnflags & WEP_FLAG_HIDDEN));
@@@ -72,8 -80,9 +80,9 @@@
        }
        #undef ADD_TO_W_LIST
  
-       KEYBIND_DEF(""                                      , "");
-       KEYBIND_DEF(""                                      , _("View"));
+       KEYBIND_EMPTY_LINE();
+       KEYBIND_HEADER(_("View"));
        KEYBIND_DEF("+zoom"                                 , _("hold zoom"));
        KEYBIND_DEF("togglezoom"                            , _("toggle zoom"));
        KEYBIND_DEF("+showscores"                           , _("show scores"));
        KEYBIND_DEF("+hud_panel_radar_maximized"            , _("maximize radar"));
        KEYBIND_DEF("toggle chase_active"                   , _("3rd person view"));
        KEYBIND_DEF("spec"                                  , _("enter spectator mode"));
-       KEYBIND_DEF(""                                      , "");
-       KEYBIND_DEF(""                                      , _("Communicate"));
+       KEYBIND_EMPTY_LINE();
+       KEYBIND_HEADER(_("Communicate"));
        KEYBIND_DEF("messagemode"                           , _("public chat"));
        KEYBIND_DEF("messagemode2"                          , _("team chat"));
        KEYBIND_DEF("+con_chat_maximize"                    , _("show chat history"));
        KEYBIND_DEF("vyes"                                  , _("vote YES"));
        KEYBIND_DEF("vno"                                   , _("vote NO"));
        KEYBIND_DEF("ready"                                 , _("ready"));
-       KEYBIND_DEF(""                                      , "");
-       KEYBIND_DEF(""                                      , _("Client"));
+       KEYBIND_EMPTY_LINE();
+       KEYBIND_HEADER(_("Client"));
        KEYBIND_DEF("+show_info"                            , _("server info"));
+       // display the hardcoded shortcut to open the console as it works for all
+       // non-English keyboard layouts, unlike default keys (` and ~)
        KEYBIND_DEF("toggleconsole"                         , _("enter console"));
+               // TODO make it translatable together with all key names
+               KEYBIND_SPECIAL_DEF("SHIFT+ESC");
        KEYBIND_DEF("disconnect"                            , _("disconnect"));
        KEYBIND_DEF("menu_showquitdialog"                   , _("quit"));
-       KEYBIND_DEF(""                                      , "");
-       KEYBIND_DEF(""                                      , _("Teamplay"));
+       KEYBIND_EMPTY_LINE();
+       KEYBIND_HEADER(_("Teamplay"));
        KEYBIND_DEF("messagemode2"                          , _("team chat"));
        KEYBIND_DEF("team_auto"                             , _("auto-join team"));
        KEYBIND_DEF("menu_showteamselect"                   , _("team menu"));
        KEYBIND_DEF("+use"                                  , _("drop key / drop flag"));
-       KEYBIND_DEF(""                                      , "");
-       KEYBIND_DEF(""                                      , _("Misc"));
+       KEYBIND_EMPTY_LINE();
+       KEYBIND_HEADER(_("Misc"));
+       KEYBIND_DEF("kill"                                  , _("respawn"));
        KEYBIND_DEF("quickmenu"                             , _("quick menu"));
        KEYBIND_DEF("menu_showsandboxtools"                 , _("sandbox menu"));
        KEYBIND_DEF("+button8"                              , _("drag object"));
-       KEYBIND_DEF(""                                      , "");
-       KEYBIND_DEF(""                                      , _("User defined"));
+       KEYBIND_EMPTY_LINE();
+       KEYBIND_HEADER(_("User defined"));
  
        for(i = 1; i <= 32; ++i)
                KEYBIND_DEF(strcat("+userbind ", itos(i)), strcat("$userbind", itos(i)));
@@@ -156,10 -175,10 +175,10 @@@ void XonoticKeyBinder_configureXonoticK
  void XonoticKeyBinder_loadKeyBinds(entity me)
  {
        bool force_initial_selection = false;
-       if(Xonotic_KeyBinds_Count < 0) // me.handle not loaded yet?
+       if(KeyBinds_Count < 0) // me.handle not loaded yet?
                force_initial_selection = true;
-       Xonotic_KeyBinds_Read();
-       me.nItems = Xonotic_KeyBinds_Count;
+       KeyBinds_Read();
+       me.nItems = KeyBinds_Count;
        if(force_initial_selection)
                me.setSelected(me, 0);
  }
@@@ -183,10 -202,8 +202,8 @@@ void XonoticKeyBinder_resizeNotify(enti
  }
  void KeyBinder_Bind_Change(entity btn, entity me)
  {
-       string func;
-       func = Xonotic_KeyBinds_Functions[me.selectedItem];
-       if(func == "")
+       string func = KeyBinds_Functions[me.selectedItem];
+       if(func == "" || KEYBIND_IS_SPECIAL(func))
                return;
  
        me.keyGrabButton.forcePressed = 1;
@@@ -197,7 -214,6 +214,6 @@@ void XonoticKeyBinder_keyGrabbed(entit
  {
        int n, j, nvalid;
        float k;
-       string func;
  
        me.keyGrabButton.forcePressed = 0;
        me.clearButton.disabled = 0;
                return;
        }
  
-       func = Xonotic_KeyBinds_Functions[me.selectedItem];
-       if(func == "")
+       string func = KeyBinds_Functions[me.selectedItem];
+       if(func == "" || KEYBIND_IS_SPECIAL(func))
                return;
  
        n = tokenize(findkeysforcommand(func, 0)); // uses '...' strings
  }
  void XonoticKeyBinder_destroy(entity me)
  {
-       if(Xonotic_KeyBinds_Count < 0)
+       if(KeyBinds_Count < 0)
                return;
  
        for(int i = 0; i < MAX_KEYBINDS; ++i)
        {
-               strfree(Xonotic_KeyBinds_Functions[i]);
-               strfree(Xonotic_KeyBinds_Descriptions[i]);
+               strfree(KeyBinds_Functions[i]);
+               strfree(KeyBinds_Descriptions[i]);
        }
-       Xonotic_KeyBinds_Count = 0;
+       KeyBinds_Count = 0;
  }
  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 == "")
+       string func = KeyBinds_Functions[me.selectedItem];
+       if(func == "" || KEYBIND_IS_SPECIAL(func))
                return;
  
-       descr = Xonotic_KeyBinds_Descriptions[me.selectedItem];
+       string descr = KeyBinds_Descriptions[me.selectedItem];
        if(substring(descr, 0, 1) != "$")
                return;
        descr = substring(descr, 1, strlen(descr) - 1);
  }
  void KeyBinder_Bind_Edit(entity btn, entity me)
  {
-       string func, descr;
        if(!me.userbindEditDialog)
                return;
  
-       func = Xonotic_KeyBinds_Functions[me.selectedItem];
-       if(func == "")
+       string func = KeyBinds_Functions[me.selectedItem];
+       if(func == "" || KEYBIND_IS_SPECIAL(func))
                return;
  
-       descr = Xonotic_KeyBinds_Descriptions[me.selectedItem];
+       string descr = KeyBinds_Descriptions[me.selectedItem];
        if(substring(descr, 0, 1) != "$")
                return;
        descr = substring(descr, 1, strlen(descr) - 1);
  void KeyBinder_Bind_Clear(entity btn, entity me)
  {
        float n, j, k;
-       string func;
  
-       func = Xonotic_KeyBinds_Functions[me.selectedItem];
-       if(func == "")
+       string func = KeyBinds_Functions[me.selectedItem];
+       if(func == "" || KEYBIND_IS_SPECIAL(func))
                return;
  
        n = tokenize(findkeysforcommand(func, 0)); // uses '...' strings
@@@ -320,6 -331,7 +331,7 @@@ void KeyBinder_Bind_Reset_All(entity bt
        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");
+       me.close(me);
  }
  void XonoticKeyBinder_doubleClickListBoxItem(entity me, float i, vector where)
  {
@@@ -329,27 -341,32 +341,32 @@@ void XonoticKeyBinder_setSelected(entit
  {
        // handling of "unselectable" items
        i = floor(0.5 + bound(0, i, me.nItems - 1));
+       if (KEYBIND_IS_SPECIAL(KeyBinds_Functions[i]))
+       {
+               SUPER(XonoticKeyBinder).setSelected(me, i - 1);
+               return;
+       }
        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] == ""))
+                       while((i < me.nItems - 1) && (KeyBinds_Functions[i] == ""))
                                ++i;
                }
-               while((i > 0) && (Xonotic_KeyBinds_Functions[i] == ""))
+               while((i > 0) && (KeyBinds_Functions[i] == ""))
                        --i;
-               while((i < me.nItems - 1) && (Xonotic_KeyBinds_Functions[i] == ""))
+               while((i < me.nItems - 1) && (KeyBinds_Functions[i] == ""))
                        ++i;
        }
        if(me.pressed == 3) // released the mouse - fall back to last valid item
        {
-               if(Xonotic_KeyBinds_Functions[i] == "")
+               if(KeyBinds_Functions[i] == "")
                        i = me.previouslySelected;
        }
-       if(Xonotic_KeyBinds_Functions[i] != "")
+       if(KeyBinds_Functions[i] != "")
                me.previouslySelected = i;
        if(me.userbindEditButton)
-               me.userbindEditButton.disabled = (substring(Xonotic_KeyBinds_Descriptions[i], 0, 1) != "$");
+               me.userbindEditButton.disabled = (substring(KeyBinds_Descriptions[i], 0, 1) != "$");
        SUPER(XonoticKeyBinder).setSelected(me, i);
  }
  float XonoticKeyBinder_keyDown(entity me, int key, bool ascii, float shift)
        }
        return r;
  }
 +
  void XonoticKeyBinder_drawListBoxItem(entity me, int i, vector absSize, bool isSelected, bool isFocused)
  {
        string s;
        float k;
        vector theColor;
        float theAlpha;
-       string func, descr;
        float extraMargin;
  
-       descr = Xonotic_KeyBinds_Descriptions[i];
-       func = Xonotic_KeyBinds_Functions[i];
+       string descr = KeyBinds_Descriptions[i];
+       string func = KeyBinds_Functions[i];
  
        if(func == "")
        {
  
        s = draw_TextShortenToWidth(descr, me.columnFunctionSize, 0, me.realFontSize);
        draw_Text(me.realUpperMargin * eY + extraMargin * eX, s, me.realFontSize, theColor, theAlpha, 0);
-       if(func != "")
+       if (func == "")
+               return;
+       s = "";
+       if (KEYBIND_IS_SPECIAL(func))
+               s = substring(func, 1, -1);
+       else
        {
                n = tokenize(findkeysforcommand(func, 0)); // uses '...' strings
-               s = "";
                for(j = 0; j < n; ++j)
                {
                        k = stof(argv(j));
                        {
                                if(s != "")
                                        s = strcat(s, ", ");
 -                              s = strcat(s, keynumtostring(k));
 +                              s = strcat(s, translate_key(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);
        }
+       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);
  }