merge russian translation fixes
authornemo
Tue, 15 Oct 2019 13:42:50 -0400
changeset 15477 923a6a8ae7c1
parent 15474 e0ab70a90718 (diff)
parent 15476 c81b6aaecced (current diff)
child 15478 ea8fce926e4d
merge russian translation fixes
--- a/.hgtags	Sat Sep 28 16:39:02 2019 +0300
+++ b/.hgtags	Tue Oct 15 13:42:50 2019 -0400
@@ -89,3 +89,4 @@
 afc089c39556bdd895892fa36ed47aaec83c3825 0.9.24.1-release
 195208deff1dd3e22d303d4a92c2ba14be3b6623 Hedgewars-iOS-2.1
 5e28098fb59379357a145b73380a1cd3839f643f 0.9.25-release
+3102d95a870e61385ee6951e30dc3be739210093 1.0.0-release
--- a/.travis.yml	Sat Sep 28 16:39:02 2019 +0300
+++ b/.travis.yml	Tue Oct 15 13:42:50 2019 -0400
@@ -52,6 +52,7 @@
 
 before_install: |
   if [ "$TRAVIS_OS_NAME" == "linux" ]; then
+    sudo add-apt-repository ppa:costamagnagianfranco/hedgewars-nightly -y
     sudo apt-get update -qq
   elif [ "$TRAVIS_OS_NAME" == "osx" ]; then
     brew update
@@ -65,18 +66,7 @@
 
 install: |
   if [ "$TRAVIS_OS_NAME" == "linux" ]; then
-    sudo apt-get install -y debhelper cmake dpkg-dev qtbase5-dev qtbase5-private-dev qttools5-dev-tools qttools5-dev libsdl2-dev libsdl2-ttf-dev libsdl2-mixer-dev libsdl2-image-dev libsdl2-net-dev bzip2 ghc libghc-mtl-dev libghc-vector-dev libghc-zlib-dev libghc-random-dev libghc-network-dev libghc-sandi-dev libghc-hslogger-dev libghc-utf8-string-dev libghc-sha-dev libghc-entropy-dev libghc-regex-tdfa-dev libghc-aeson-dev libghc-yaml-dev libghc-text-dev liblua5.1-0-dev fpc fp-compiler fp-units-misc libpng-dev fp-units-gfx libavcodec-dev libavformat-dev libglew1.6-dev
-
-    # for xenial last availible version of libphysfs is 2.0.x, but we need >= 3.0
-    # so... building from sources!
-    wget https://icculus.org/physfs/downloads/physfs-3.0.1.tar.bz2
-    tar -xjf physfs-3.0.1.tar.bz2
-    mkdir physfs-3.0.1-build
-    pushd physfs-3.0.1-build
-    cmake ../physfs-3.0.1
-    make
-    sudo make install
-    popd
+    sudo apt-get install -y cmake debhelper dpkg-dev fp-compiler fp-units-gfx fp-units-misc ghc libavcodec-dev libavformat-dev libghc-aeson-dev libghc-entropy-dev libghc-hslogger-dev libghc-mtl-dev libghc-network-dev libghc-parsec3-dev libghc-random-dev libghc-regex-tdfa-dev libghc-sandi-dev libghc-sha-dev libghc-text-dev libghc-utf8-string-dev libghc-vector-dev libghc-yaml-dev libghc-zlib-dev liblua5.1-dev libphysfs-dev libpng-dev libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-net-dev libsdl2-ttf-dev qtbase5-dev qtbase5-private-dev qttools5-dev qttools5-dev-tools
   elif [ "$TRAVIS_OS_NAME" == "osx" ]; then
     brew install qt5
     brew install fpc glew physfs lua51 sdl2 sdl2_image sdl2_net sdl2_ttf ffmpeg ghc cabal-install
@@ -84,11 +74,11 @@
     # use cabal install haskell deps, pas2c ones are covered by server
     if [[ "$BUILD_ARGS" != *"NOSERVER"* ]]; then
       cabal update
-      cabal install --only-dependencies gameServer/hedgewars-server.cabal
+      cabal install --only-dependencies --cabal-file=gameServer/hedgewars-server.cabal
     fi
     if [[ "$BUILD_ARGS" == *"BUILD_ENGINE_C"* ]]; then
       cabal update
-      cabal install --only-dependencies tools/pas2c/pas2c.cabal
+      cabal install --only-dependencies --cabal-file=tools/pas2c/pas2c.cabal
     fi
     # avoid installing Sparkle, add default unit path
     export BUILD_ARGS="$BUILD_ARGS -DNOAUTOUPDATE=1"
--- a/CMakeLists.txt	Sat Sep 28 16:39:02 2019 +0300
+++ b/CMakeLists.txt	Tue Oct 15 13:42:50 2019 -0400
@@ -54,7 +54,7 @@
 
 
 if(BUILD_ENGINE_C AND NOT NOVIDEOREC)
-    if((CMAKE_BUILD_TYPE MATCHES "RELEASE") OR (CMAKE_BUILD_TYPE MATCHES "RELWITHDEBUGINFO"))
+    if((CMAKE_BUILD_TYPE STREQUAL "Release") OR (CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo"))
         message("NOTE: Video recorder support disabled. It's incompatible with BUILD_ENGINE_C")
         set(BUILD_ENGINE_C ON CACHE STRING "Required for BUILD_ENGINE_JS" FORCE)
     else()
@@ -91,9 +91,9 @@
 #versioning
 set(CPACK_PACKAGE_VERSION_MAJOR 1)
 set(CPACK_PACKAGE_VERSION_MINOR 0)
-set(CPACK_PACKAGE_VERSION_PATCH 0)
-set(HEDGEWARS_PROTO_VER 58)
-if((CMAKE_BUILD_TYPE MATCHES "RELEASE") OR (CMAKE_BUILD_TYPE MATCHES "RELWITHDEBUGINFO"))
+set(CPACK_PACKAGE_VERSION_PATCH 1)
+set(HEDGEWARS_PROTO_VER 60)
+if((CMAKE_BUILD_TYPE STREQUAL "Release") OR (CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo"))
     set(HEDGEWARS_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}")
 else()
     set(HEDGEWARS_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}-dev")
@@ -112,10 +112,9 @@
 
 #when build type is not specified, assume Debug/Release according to build version information
 if(CMAKE_BUILD_TYPE)
-    string(TOUPPER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE)
-    if(NOT((CMAKE_BUILD_TYPE MATCHES "RELEASE") OR
-           (CMAKE_BUILD_TYPE MATCHES "DEBUG") OR
-           (CMAKE_BUILD_TYPE MATCHES "RELWITHDEBINFO")))
+    if(NOT((CMAKE_BUILD_TYPE STREQUAL "Release") OR
+           (CMAKE_BUILD_TYPE STREQUAL "Debug") OR
+           (CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")))
         set(CMAKE_BUILD_TYPE ${default_build_type} CACHE STRING "Build type (Debug/Release/RelWithDebInfo)" FORCE)
         message(STATUS "Unknown build type ${CMAKE_BUILD_TYPE}, using default (${default_build_type})")
     endif()
@@ -168,7 +167,7 @@
 
 #get BUILD_TYPE and enable/disable optimisation
 message(STATUS "Using ${CMAKE_BUILD_TYPE} configuration")
-if(CMAKE_BUILD_TYPE MATCHES "DEBUG")
+if(CMAKE_BUILD_TYPE STREQUAL "Debug")
     list(APPEND haskell_flags "-Wall"       # all warnings
                               "-debug"      # debug mode
                               "-fno-warn-unused-do-bind"
--- a/CREDITS	Sat Sep 28 16:39:02 2019 +0300
+++ b/CREDITS	Tue Oct 15 13:42:50 2019 -0400
@@ -83,6 +83,8 @@
      https://www.freesound.org/people/rombart/sounds/197800/
 - Flamethrower sound originally by AslakHostaker (CC-0), adapted from
      https://freesound.org/people/AslakHostaker/sounds/395039/
+- Dynamite fuse: Based off sound by apolloaiello (CC BY 3.0)
+     https://freesound.org/people/apolloaiello/sounds/329045/
 - Landspray sound originally by Benboncan (CC BY 3.0), remixed from
      https://freesound.org/people/Benboncan/sounds/82390/
 - Portable Portal Device color switching sound by Wuzzy (CC-0)
--- a/ChangeLog.txt	Sat Sep 28 16:39:02 2019 +0300
+++ b/ChangeLog.txt	Tue Oct 15 13:42:50 2019 -0400
@@ -1,6 +1,6 @@
 + features
 * bugfixes
-============== 1.0.0-dev (unreleased) ==============
+====================== 1.0.0 =======================
 Highlights:
  + Campaigns now respect your team identity instead of overwriting it
  + Single missions now support team selection and track your progress
@@ -58,6 +58,8 @@
  * Space Invasion: No longer allow to set start shield above shield limit
  * Battalion, WxW: Crates drop between turns, when appropriate
  * Battalion: Sudden Death effects are now like in the base game
+ * Battalion: Fix incorrect health boost in Highland mode
+ * Battalion: Fix points display not updating properly
  * King Mode: Fix team sometimes not being killed properly if king drowned
  * King Mode: Kill resurrected minions if king is not alive
  * King Mode: Fix whole clan being killed if a king died
@@ -135,6 +137,7 @@
  + Show contour of flying saucer and air mines when in highly opaque water
  + Remove visual clutter in cut scenes
  + Add setting to set default/initial zoom
+ + Render arrow pointing to hog only one if playing with wrapped map
  * Black clan color can now be used without visual problems
  * Fix last 2 characters in demo chat being missing
  * Hide most HUD elements in cinematic mode
@@ -169,6 +172,7 @@
  * Fix force-locked schemes getting unlocked when changing map types
  * Fix possible to select background-only or hidden themes indirectly by changing map type
  * Disallow slash, backslash and colon characters in team and scheme names
+ * Increase user name length from 20 to 40
 
 Sounds and voicepacks:
  + sndYoohoo has been split to sndYoohoo and sndKiss
@@ -395,7 +399,7 @@
 
 Translations:
  + Translations kept up-to-date: German, Polish
- + Major translation updates: Russian, Japanese, Scottish Gaelic, Ukrainian
+ + Major translation updates: Russian, Japanese, Scottish Gaelic, Ukrainian, Italian
 
 Lua API:
  * Deprecation: Setting TurnTimeLeft/ReadyTimeLeft directly is deprecated and will become useless in future. Use the setter functions below
--- a/QTfrontend/CMakeLists.txt	Sat Sep 28 16:39:02 2019 +0300
+++ b/QTfrontend/CMakeLists.txt	Tue Oct 15 13:42:50 2019 -0400
@@ -213,9 +213,9 @@
 
 #when debugging, always prompt a console to see fronted messages
 #TODO: check it doesn't interfere on UNIX
-if(CMAKE_BUILD_TYPE MATCHES "RELEASE" OR CMAKE_BUILD_TYPE MATCHES "RELWITHDEBINFO")
+if(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
     set(console_access "WIN32")
-endif(CMAKE_BUILD_TYPE MATCHES "RELEASE" OR CMAKE_BUILD_TYPE MATCHES "RELWITHDEBINFO")
+endif(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
 
 add_executable(hedgewars ${console_access}
     ${hwfr_src}
--- a/QTfrontend/binds.cpp	Sat Sep 28 16:39:02 2019 +0300
+++ b/QTfrontend/binds.cpp	Tue Oct 15 13:42:50 2019 -0400
@@ -84,7 +84,7 @@
     {"!MULTI",    QT_TRANSLATE_NOOP("binds (combination)", "precise + toggle hedgehog tags"), QT_TRANSLATE_NOOP("binds", "change hedgehog tag types"), NULL, NULL},
     {"!MULTI",    QT_TRANSLATE_NOOP("binds (combination)", "switch + toggle hedgehog tags"), QT_TRANSLATE_NOOP("binds", "toggle hedgehog tag translucency"), NULL, NULL},
 
-    {"!MULTI",    QT_TRANSLATE_NOOP("binds (combination)", "precise + switch + toggle hedgehog tags"), QT_TRANSLATE_NOOP("binds", "toggle HUD"), NULL, NULL},
+    {"!MULTI",    QT_TRANSLATE_NOOP("binds (combination)", "precise + switch + toggle team bars"), QT_TRANSLATE_NOOP("binds", "toggle HUD"), NULL, NULL},
 #ifdef VIDEOREC
     {"record",    "r",          QT_TRANSLATE_NOOP("binds", "record"),          NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Record video:")}
 #endif
Binary file QTfrontend/hedgewars.ico has changed
--- a/QTfrontend/hwform.cpp	Sat Sep 28 16:39:02 2019 +0300
+++ b/QTfrontend/hwform.cpp	Tue Oct 15 13:42:50 2019 -0400
@@ -1250,7 +1250,7 @@
     noRegMsg.setIcon(QMessageBox::Information);
     noRegMsg.setWindowTitle(QMessageBox::tr("Hedgewars - Nick not registered"));
     noRegMsg.setWindowModality(Qt::WindowModal);
-    noRegMsg.setText(tr("Your nickname is not registered.\nTo prevent someone else from using it,\nplease register it at www.hedgewars.org"));
+    noRegMsg.setText(tr("Your nickname is not registered.\nTo be able to rejoin games in progress and\nprevent someone else from using your nickname,\nplease register it at www.hedgewars.org."));
 
     if (!config->passwordHash().isEmpty())
     {
--- a/QTfrontend/res/credits.csv	Sat Sep 28 16:39:02 2019 +0300
+++ b/QTfrontend/res/credits.csv	Tue Oct 15 13:42:50 2019 -0400
@@ -153,6 +153,7 @@
 E,"Italian","Marco Bresciani","m.bresciani@email.it",
 E,"Italian","Gianfranco Costamagna","costamagnagianfranco@yahoo.it",
 E,"Italian",,"enricobe@hotmail.com","Enrico"
+E,"Italian","Pacella Marco Ernesto","pacella389@gmail.com","KIRA"
 E,"Japanese","ADAM Etienne","etienne.adam@gmail.com",
 E,"Japanese","Marco Bresciani","m.bresciani@email.it",
 E,"Korean","Anthony Bellew","anthonyreflected@gmail.com",
--- a/QTfrontend/ui/page/pagegamestats.cpp	Sat Sep 28 16:39:02 2019 +0300
+++ b/QTfrontend/ui/page/pagegamestats.cpp	Tue Oct 15 13:42:50 2019 -0400
@@ -121,7 +121,7 @@
     btnRestart->setFixedHeight(81);
     btnRestart->setStyleSheet("QPushButton{margin-top:24px}");
     btnSave = addButton(":/res/Save.png", bottomLayout, 2, true);
-    btnSave->setWhatsThis(tr("Save"));
+    saveDemoBtnEnabled(true);
     btnSave->setStyleSheet("QPushButton{margin: 24px 0 0 0;}");
 
     return bottomLayout;
@@ -174,6 +174,10 @@
 void PageGameStats::saveDemoBtnEnabled(bool enabled)
 {
     btnSave->setEnabled(enabled);
+    if (enabled)
+        btnSave->setWhatsThis(tr("Save demo"));
+    else
+        btnSave->setWhatsThis(tr("Save demo (unavailable because the /lua command was used)"));
 }
 
 void PageGameStats::renderStats()
--- a/QTfrontend/ui/page/pageoptions.cpp	Sat Sep 28 16:39:02 2019 +0300
+++ b/QTfrontend/ui/page/pageoptions.cpp	Tue Oct 15 13:42:50 2019 -0400
@@ -573,7 +573,7 @@
             groupAccount->layout()->addWidget(labelNN, 0, 0);
 
             editNetNick = new QLineEdit(groupAccount);
-            editNetNick->setMaxLength(20);
+            editNetNick->setMaxLength(40);
             editNetNick->setText(QLineEdit::tr("anonymous"));
             groupAccount->layout()->addWidget(editNetNick, 0, 1);
 
--- a/QTfrontend/ui/widget/keybinder.cpp	Sat Sep 28 16:39:02 2019 +0300
+++ b/QTfrontend/ui/widget/keybinder.cpp	Tue Oct 15 13:42:50 2019 -0400
@@ -209,12 +209,7 @@
         curTable->insertRow(row);
         curTable->setItem(row, 0, nameCell);
         QTableWidgetItem * bindCell;
-        // Check if the bind text is bad. This was discovered after the 1.0.0,
-        // so we need a little workaround.
-        bool is_broken_strbind = cbinds[i].strbind == "precise + switch + toggle hedgehog tags";
-                                                    // ^ should be "precise + switch + toggle team bars"
-        // TODO: Remove is_broken_strbind after 1.0.0 release.
-        if (cbinds[i].action != "!MULTI" && (!is_broken_strbind))
+        if (cbinds[i].action != "!MULTI")
         {
             bindCell = new QTableWidgetItem(comboBox->currentText());
             nameCell->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
@@ -223,19 +218,7 @@
         }
         else
         {
-            // Apply workaround for the broken 1.0.0 strbind
-            // TODO: Remove the workaround after 1.0.0 release and fix binds.cpp accordingly.
-            if (is_broken_strbind)
-            {
-                // We simply construct the string from other strings we *do* have. :-)
-                QString cellText =
-                    HWApplication::translate("binds", "precise aim") + " + " +
-                    HWApplication::translate("binds", "switch") + " + " +
-                    HWApplication::translate("binds", "toggle team bars");
-                bindCell = new QTableWidgetItem(cellText);
-            }
-            else
-                bindCell = new QTableWidgetItem(HWApplication::translate("binds (combination)", cbinds[i].strbind.toUtf8().constData()));
+            bindCell = new QTableWidgetItem(HWApplication::translate("binds (combination)", cbinds[i].strbind.toUtf8().constData()));
             nameCell->setFlags(Qt::NoItemFlags);
             bindCell->setFlags(Qt::NoItemFlags);
             bindCell->setIcon(emptyIcon);
--- a/QTfrontend/util/DataManager.cpp	Sat Sep 28 16:39:02 2019 +0300
+++ b/QTfrontend/util/DataManager.cpp	Tue Oct 15 13:42:50 2019 -0400
@@ -28,7 +28,7 @@
 #include <QSettings>
 #include <QColor>
 
-#include <SDL2/SDL.h>
+#include <SDL.h>
 
 #include "hwconsts.h"
 #include "HWApplication.h"
--- a/cmake_modules/compilerchecks.cmake	Sat Sep 28 16:39:02 2019 +0300
+++ b/cmake_modules/compilerchecks.cmake	Tue Oct 15 13:42:50 2019 -0400
@@ -62,7 +62,7 @@
         endif()
     endif()
 
-    if(CMAKE_BUILD_TYPE MATCHES "RELEASE" OR CMAKE_BUILD_TYPE MATCHES "RELWITHDEBINFO")
+    if(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
         set(CMAKE_REQUIRED_FLAGS "-Wl,--as-needed")
         check_c_compiler_flag("" HAVE_ASNEEDED)
         if(HAVE_ASNEEDED)
--- a/cmake_modules/cpackvars.cmake	Sat Sep 28 16:39:02 2019 +0300
+++ b/cmake_modules/cpackvars.cmake	Tue Oct 15 13:42:50 2019 -0400
@@ -1,6 +1,6 @@
 
 # revision information in cpack-generated names
-if(CMAKE_BUILD_TYPE MATCHES DEBUG)
+if(CMAKE_BUILD_TYPE STREQUAL "Debug")
     set(full_suffix "${HEDGEWARS_VERSION}-r${HEDGEWARS_REVISION}")
 else()
     set(full_suffix "${HEDGEWARS_VERSION}")
@@ -106,6 +106,13 @@
     "^${CMAKE_CURRENT_SOURCE_DIR}/gameServer2"
     "^${CMAKE_CURRENT_SOURCE_DIR}/rust"
     "^${CMAKE_CURRENT_SOURCE_DIR}/qmlfrontend"
+    "^${CMAKE_CURRENT_SOURCE_DIR}/bin/hedgewars"
+    "^${CMAKE_CURRENT_SOURCE_DIR}/bin/hwengine"
+    "^${CMAKE_CURRENT_SOURCE_DIR}/bin/hedgewars-server"
+    "^${CMAKE_CURRENT_SOURCE_DIR}/bin/link\\\\.res"
+    "^${CMAKE_CURRENT_SOURCE_DIR}/bin/ppas\\\\.sh"
+    "^${CMAKE_CURRENT_SOURCE_DIR}/bin/libavwrapper\\\\.*"
+    "^${CMAKE_CURRENT_SOURCE_DIR}/bin/libphyslayer\\\\.*"
 )
 
 include(CPack)
--- a/cmake_modules/revinfo.cmake	Sat Sep 28 16:39:02 2019 +0300
+++ b/cmake_modules/revinfo.cmake	Tue Oct 15 13:42:50 2019 -0400
@@ -17,7 +17,7 @@
     endif()
 
     #let's assume that if you have hg you might be interested in debugging
-    set(default_build_type "DEBUG")
+    set(default_build_type "Debug")
 
     #write down hash and rev for easy picking should hg be missing
     file(WRITE "${CMAKE_SOURCE_DIR}/share/version_info.txt" "Hedgewars versioning information, do not modify\nrev ${HEDGEWARS_REVISION}\nhash ${HEDGEWARS_HASH}\n")
@@ -30,12 +30,12 @@
     set(HEDGEWARS_REVISION "GIT")
 
     #let's assume that if you have git you might be interested in debugging
-    set(default_build_type "DEBUG")
+    set(default_build_type "Debug")
 
     #write down hash and rev for easy picking should hg be missing
     file(WRITE "${CMAKE_SOURCE_DIR}/share/version_info.txt" "Hedgewars versioning information, do not modify\nrev ${HEDGEWARS_REVISION}\nhash ${HEDGEWARS_HASH}\n")
 else()
-    set(default_build_type "RELEASE")
+    set(default_build_type "Release")
     # when compiling outside rev control, fetch revision and hash information from version_info.txt
     find_file(version_info version_info.txt PATH ${CMAKE_SOURCE_DIR}/share)
     if(version_info)
--- a/gameServer/CoreTypes.hs	Sat Sep 28 16:39:02 2019 +0300
+++ b/gameServer/CoreTypes.hs	Tue Oct 15 13:42:50 2019 -0400
@@ -313,8 +313,8 @@
         True
         False
         "<h2><p align=center><a href=\"https://www.hedgewars.org/\">https://www.hedgewars.org/</a></p></h2>"
-        "<font color=yellow><h3 align=center>Hedgewars 0.9.25 is out! Please update.</h3><p align=center><a href=https://hedgewars.org/download.html>Download page here</a></font>"
-        57 -- latestReleaseVersion
+        "<font color=yellow><h3 align=center>Hedgewars 1.0.0 is out! Please update.</h3><p align=center><a href=https://hedgewars.org/download.html>Download page here</a></font>"
+        59 -- latestReleaseVersion
         41 -- earliestCompatibleVersion
         46631
         ""
--- a/gameServer/Utils.hs	Sat Sep 28 16:39:02 2019 +0300
+++ b/gameServer/Utils.hs	Tue Oct 15 13:42:50 2019 -0400
@@ -127,6 +127,8 @@
             , (56, "0.9.25-dev")
             , (57, "0.9.25")
             , (58, "1.0.0-dev")
+            , (59, "1.0.0")
+            , (60, "1.0.1-dev")
             ]
 
 askFromConsole :: B.ByteString -> IO B.ByteString
--- a/hedgewars/uAI.pas	Sat Sep 28 16:39:02 2019 +0300
+++ b/hedgewars/uAI.pas	Tue Oct 15 13:42:50 2019 -0400
@@ -107,6 +107,7 @@
 BotLevel:= Me^.Hedgehog^.BotLevel;
 windSpeed:= hwFloat2Float(cWindSpeed);
 useThisActions:= false;
+Me^.AIHints:= Me^.AIHints and (not aihAmmosChanged);
 
 for i:= 0 to Pred(Targets.Count) do
     if (Targets.ar[i].Score >= 0) and (not StopThinking) then
@@ -432,7 +433,7 @@
     switchCount:= HHHasAmmo(PGear(Me)^.Hedgehog^, amSwitch)
 else switchCount:= 0;
 
-if ((Me^.State and gstAttacked) = 0) or isInMultiShoot or bonuses.activity then
+if ((Me^.State and gstAttacked) = 0) or isInMultiShoot or bonuses.activity or ((Me^.AIHints and aihAmmosChanged) <> 0) then
     if Targets.Count > 0 then
         begin
         // iterate over current team hedgehogs
@@ -478,7 +479,7 @@
             FillBonuses(false);
 
             // Hog has no idea what to do. Use tardis or skip
-            if not bonuses.activity then
+            if (not bonuses.activity) and ((Me^.AIHints and aihAmmosChanged) = 0) then
                 if (((GameFlags and gfInfAttack) <> 0) or (CurrentHedgehog^.MultiShootAttacks = 0)) and (HHHasAmmo(Me^.Hedgehog^, amTardis) > 0) and (CanUseTardis(Me^.Hedgehog^.Gear)) and (random(4) < 3) then
                     // Tardis brings hog to a random place. Perfect for clueless AI
                     begin
@@ -488,6 +489,7 @@
                     end
                 else
                     AddAction(BestActions, aia_Skip, 0, 250, 0, 0);
+            Me^.AIHints := ME^.AIHints and (not aihAmmosChanged);
             end;
 
         end else SDL_Delay(100)
--- a/hedgewars/uAmmos.pas	Sat Sep 28 16:39:02 2019 +0300
+++ b/hedgewars/uAmmos.pas	Tue Oct 15 13:42:50 2019 -0400
@@ -265,7 +265,9 @@
         begin
         PackAmmo(Ammo, Ammoz[AmmoType].Slot);
         CurAmmoType:= amNothing
-        end
+        end;
+if Hedgehog.BotLevel <> 0 then
+    Hedgehog.Gear^.AIHints := Hedgehog.Gear^.AIHints or aihAmmosChanged;
 end;
 
 procedure PackAmmo(Ammo: PHHAmmo; Slot: LongInt);
--- a/hedgewars/uConsts.pas	Sat Sep 28 16:39:02 2019 +0300
+++ b/hedgewars/uConsts.pas	Tue Oct 15 13:42:50 2019 -0400
@@ -321,6 +321,7 @@
     // AI hints to be set for any gear
     aihUsualProcessing    = $00000000; // treat gear as usual
     aihDoesntMatter       = $00000001; // ignore gear in attack calculations and don't intentionally attack it
+    aihAmmosChanged       = $00000002; // set when ammos were changed within this turn but not processed yet
 
     // ammo properties
     ammoprop_Timerable    = $00000001; // can set timer
--- a/hedgewars/uGears.pas	Sat Sep 28 16:39:02 2019 +0300
+++ b/hedgewars/uGears.pas	Tue Oct 15 13:42:50 2019 -0400
@@ -43,6 +43,7 @@
 procedure EndTurnCleanup;
 procedure DrawGears;
 procedure DrawGearsGui;
+procedure DrawFinger;
 procedure FreeGearsList;
 procedure AddMiscGears;
 procedure AssignHHCoords;
@@ -728,6 +729,19 @@
     end;
 end;
 
+procedure DrawFinger;
+var Gear: PGear;
+    x, y: LongInt;
+begin
+if ((CurrentHedgehog <> nil) and (CurrentHedgehog^.Gear <> nil)) then
+    begin
+    Gear:= CurrentHedgehog^.Gear;
+    x:= hwRound(Gear^.X) + WorldDx;
+    y:= hwRound(Gear^.Y) + WorldDy;
+    RenderFinger(Gear, x, y);
+    end;
+end;
+
 procedure FreeGearsList;
 var t, tt: PGear;
 begin
--- a/hedgewars/uGearsHandlersMess.pas	Sat Sep 28 16:39:02 2019 +0300
+++ b/hedgewars/uGearsHandlersMess.pas	Tue Oct 15 13:42:50 2019 -0400
@@ -2348,12 +2348,22 @@
     doStepFallingGear(Gear);
     AllInactive := false;
 
+    if (Gear^.SoundChannel <> -1) and ((Gear^.State and gstDrowning) <> 0) then
+        begin
+        StopSoundChan(Gear^.SoundChannel);
+        Gear^.SoundChannel:= -1;
+        end
+    else if Gear^.SoundChannel = -1 then
+        Gear^.SoundChannel := LoopSound(sndDynamiteFuse);
+    if (Gear^.State and gstDrowning) <> 0 then
+        exit;
     if Gear^.Timer mod 166 = 0 then
         inc(Gear^.Tag);
     if Gear^.Timer = 1000 then // might need better timing
         makeHogsWorry(Gear^.X, Gear^.Y, 75, Gear^.Kind);
     if Gear^.Timer = 0 then
         begin
+        StopSoundChan(Gear^.SoundChannel);
         doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), Gear^.Boom, Gear^.Hedgehog, EXPLAutoSound);
         DeleteGear(Gear);
         exit
--- a/hedgewars/uGearsRender.pas	Sat Sep 28 16:39:02 2019 +0300
+++ b/hedgewars/uGearsRender.pas	Tue Oct 15 13:42:50 2019 -0400
@@ -38,6 +38,7 @@
 procedure RenderGear(Gear: PGear; x, y: LongInt);
 procedure RenderGearTimer(Gear: PGear; x, y: LongInt);
 procedure RenderGearHealth(Gear: PGear; x, y: LongInt);
+procedure RenderFinger(Gear: PGear; ox, oy: LongInt);
 procedure RenderHHGuiExtras(Gear: PGear; ox, oy: LongInt);
 procedure RenderAirMineGuiExtras(Gear: PGear; ox, oy: LongInt);
 procedure DrawHHOrder();
@@ -238,15 +239,12 @@
 
 end;
 
-// Render some informational GUI next to hedgehog, like fuel and alternate weapon
-procedure RenderHHGuiExtras(Gear: PGear; ox, oy: LongInt);
+procedure RenderFinger(Gear: PGear; ox, oy: LongInt);
 var HH: PHedgehog;
-    sx, sy, tx, ty, t, hogLR: LongInt;
+    tx, ty, t: LongInt;
     dAngle: real;
 begin
     HH:= Gear^.Hedgehog;
-    sx:= ox + 1; // this offset is very common
-    sy:= oy - 3;
     if HH^.Unplaced then
         exit;
     if (Gear^.State and gstHHDeath) <> 0 then
@@ -256,7 +254,7 @@
     if (CinematicScript) then
         exit;
 
-    // render finger (pointing arrow)
+    // render finger (arrow pointing to hog)
     if bShowFinger and ((Gear^.State and gstHHDriven) <> 0) then
         begin
         ty := oy - 32;
@@ -294,6 +292,25 @@
         DrawSpriteRotatedF(sprFinger, tx, ty, RealTicks div 32 mod 16, 1, dAngle);
         untint;
         end;
+end;
+
+
+// Render some informational GUI next to hedgehog, like fuel and alternate weapon
+procedure RenderHHGuiExtras(Gear: PGear; ox, oy: LongInt);
+var HH: PHedgehog;
+    sx, sy, hogLR: LongInt;
+begin
+    HH:= Gear^.Hedgehog;
+    sx:= ox + 1; // this offset is very common
+    sy:= oy - 3;
+    if HH^.Unplaced then
+        exit;
+    if (Gear^.State and gstHHDeath) <> 0 then
+        exit;
+    if (Gear^.State and gstHHGone) <> 0 then
+        exit;
+    if (CinematicScript) then
+        exit;
 
     // render crosshair
     if (CrosshairGear <> nil) and (Gear = CrosshairGear) then
@@ -1493,7 +1510,10 @@
                         DrawSpriteRotatedF(sprExplosivesRoll, x, y + 4, 1, 0, Gear^.DirAngle)
                     end;
         gtDynamite: begin
-                    DrawSprite(sprDynamite, x - 16, y - 25, Gear^.Tag and 1, Gear^.Tag shr 1);
+                    if ((Gear^.State and gstDrowning) = 0) then
+                        DrawSprite(sprDynamite, x - 16, y - 25, Gear^.Tag and 1, Gear^.Tag shr 1)
+                    else
+                        DrawSprite(sprDynamiteDefused, x - 16, y - 25, Gear^.Tag and 1, Gear^.Tag shr 1);
                     if (random(3) = 0) and ((Gear^.State and gstDrowning) = 0) then
                         begin
                         vg:= AddVisualGear(hwRound(Gear^.X)+12-(Gear^.Tag shr 1), hwRound(Gear^.Y)-16, vgtStraightShot);
--- a/hedgewars/uSound.pas	Sat Sep 28 16:39:02 2019 +0300
+++ b/hedgewars/uSound.pas	Tue Oct 15 13:42:50 2019 -0400
@@ -332,7 +332,8 @@
             (FileName:                  'Hmm.ogg'; Path: ptVoices; AltPath: ptNone),// sndHmm
             (FileName:                 'Kiss.ogg'; Path: ptSounds; AltPath: ptNone),// sndKiss
             (FileName:              'Flyaway.ogg'; Path: ptVoices; AltPath: ptNone),// sndFlyAway
-            (FileName:           'planewater.ogg'; Path: ptSounds; AltPath: ptNone) // sndPlaneWater
+            (FileName:           'planewater.ogg'; Path: ptSounds; AltPath: ptNone),// sndPlaneWater
+            (FileName:         'dynamitefuse.ogg'; Path: ptSounds; AltPath: ptNone) // sndDynamiteFuse
             );
 
 
@@ -345,11 +346,11 @@
 
     { Adjust for language suffix: Voicepacks can have an optional language suffix.
     It's an underscore followed by an ISO 639-1 or ISO 639-2 language code.
-    The suffix “_qau” is special, it will enable automatic language selection
+    The suffix "_qau" is special, it will enable automatic language selection
     of this voicepack. For example, if team has set Default_qau as voicepack,
     and the player language is Russian, the actual voicepack will be Default_ru,
     provided it can be found on the disk.
-    “qau” is a valid ISO 639-2 language code reserved for local use. }
+    "qau" is a valid ISO 639-2 language code reserved for local use. }
     tmp:= Copy(name, Length(name) - 3, 4);
     if (tmp = '_qau') then
         name:= Copy(name, 1, Length(name) - 4);
--- a/hedgewars/uStats.pas	Sat Sep 28 16:39:02 2019 +0300
+++ b/hedgewars/uStats.pas	Tue Oct 15 13:42:50 2019 -0400
@@ -71,9 +71,9 @@
     HitTargets  : LongWord = 0;         // Target (gtTarget) hits in turn
     AmmoUsedCount : Longword = 0;       // Number of times an ammo has been used this turn
     AmmoDamagingUsed : boolean = false; // true if damaging ammo was used in turn
-    FirstBlood  : boolean = false;      // true if the “First blood” taunt has been used in this game
-    StepFirstBlood : boolean = false;   // true if the “First blood” taunt is to be used this turn
-    LeaveMeAlone : boolean = false;     // true if the “Leave me alone” taunt is to be used this turn
+    FirstBlood  : boolean = false;      // true if the "First blood" taunt has been used in this game
+    StepFirstBlood : boolean = false;   // true if the "First blood" taunt is to be used this turn
+    LeaveMeAlone : boolean = false;     // true if the "Leave me alone" taunt is to be used this turn
     SkippedTurns: LongWord = 0;         // number of skipped turns in game
     isTurnSkipped: boolean = false;     // true if this turn was skipped
     vpHurtSameClan: PVoicepack = nil;   // voicepack of current clan (used for taunts)
--- a/hedgewars/uTeams.pas	Sat Sep 28 16:39:02 2019 +0300
+++ b/hedgewars/uTeams.pas	Tue Oct 15 13:42:50 2019 -0400
@@ -393,6 +393,8 @@
 CurWeapon:= GetCurAmmoEntry(CurrentHedgehog^);
 if CurWeapon^.Count = 0 then
     CurrentHedgehog^.CurAmmoType:= amNothing;
+if CurrentHedgehog^.BotLevel <> 0 then
+    CurrentHedgehog^.Gear^.AIHints:= (CurrentHedgehog^.Gear^.AIHints and (not aihAmmosChanged));
 
 with CurrentHedgehog^ do
     begin
--- a/hedgewars/uTypes.pas	Sat Sep 28 16:39:02 2019 +0300
+++ b/hedgewars/uTypes.pas	Tue Oct 15 13:42:50 2019 -0400
@@ -94,8 +94,8 @@
             sprFlakeL, sprSDFlakeL, sprCloudL, sprSDCloudL, sprCreeper, sprHandCreeper, sprMinigun,
             sprSliderInverted, sprFingerBack, sprFingerBackInv, sprTargetPBack, sprTargetPBackInv,
             sprHealthHud, sprHealthPoisonHud, sprVampHud, sprKarmaHud, sprMedicHud, sprMedicPoisonHud,
-            sprHaloHud, sprInvulnHUD, sprAmPiano, sprHandLandGun, sprFirePunch, sprThroughWrap
-            );
+            sprHaloHud, sprInvulnHUD, sprAmPiano, sprHandLandGun, sprFirePunch, sprThroughWrap,
+            sprDynamiteDefused);
 
     // Gears that interact with other Gears and/or Land
     // first row of gears (<gtExplosives) should be avoided when searching a spawn place
@@ -158,7 +158,7 @@
             sndLandGun, sndCaseImpact, sndExtraDamage, sndFirePunchHit, sndGrenade, sndThisOneIsMine,
             sndWhatThe, sndSoLong, sndOhDear, sndGonnaGetYou, sndDrat, sndBugger, sndAmazing,
             sndBrilliant, sndExcellent, sndFire, sndWatchThis, sndRunAway, sndRevenge, sndCutItOut,
-            sndLeaveMeAlone, sndOuch, sndHmm, sndKiss, sndFlyAway, sndPlaneWater);
+            sndLeaveMeAlone, sndOuch, sndHmm, sndKiss, sndFlyAway, sndPlaneWater, sndDynamiteFuse);
 
     // Available ammo types to be used by hedgehogs
     TAmmoType  = (amNothing, amGrenade, amClusterBomb, amBazooka, amBee, amShotgun, amPickHammer, // 6
@@ -515,7 +515,8 @@
             sidWinner2, sidWinner3, sidWinner4, sidWinner5, sidWinner6,
             sidWinner7, sidWinnerAll, sidTeamGone, sidTeamBack, sidAutoSkip,
             sidFPS, sidLuaParsingOff, sidLuaParsingOn, sidLuaParsingDenied,
-            sidAmmoCount, sidChat, sidChatTeam, sidChatHog, sidUnknownGearValue);
+            sidAmmoCount, sidChat, sidChatTeam, sidChatHog, sidUnknownGearValue,
+            sidVideoRecLuaFail);
 
     TCmdHelpStrId = (
             sidCmdHeaderBasic, sidCmdTogglechat, sidCmdTeam, sidCmdMe,
--- a/hedgewars/uVariables.pas	Sat Sep 28 16:39:02 2019 +0300
+++ b/hedgewars/uVariables.pas	Tue Oct 15 13:42:50 2019 -0400
@@ -852,7 +852,10 @@
             (FileName: 'amShoryuken'; Path: ptHedgehog; AltPath: ptNone; Texture: nil; Surface: nil;
             Width:  32; Height: 32; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpMedium; getDimensions: false; getImageDimensions: true),// sprFirePunch
             (FileName: 'throughWrap'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
-            Width:  16; Height: 13; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpMedium; getDimensions: false; getImageDimensions: true) // sprTroughWrap
+            Width:  16; Height: 13; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpMedium; getDimensions: false; getImageDimensions: true),// sprTroughWrap
+            (FileName: 'dynamiteDefused'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
+            Width:  32; Height: 32; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpMedium; getDimensions: false; getImageDimensions: true) // sprDynamiteDefused
+
             );
 
 
--- a/hedgewars/uVideoRec.pas	Sat Sep 28 16:39:02 2019 +0300
+++ b/hedgewars/uVideoRec.pas	Tue Oct 15 13:42:50 2019 -0400
@@ -48,7 +48,7 @@
 procedure freeModule;
 
 implementation
-uses uVariables, GLunit, SDLh, SysUtils, uUtils, uSound, uIO, uMisc, uTypes, uDebug;
+uses uVariables, GLunit, SDLh, SysUtils, uUtils, uSound, uChat, uIO, uMisc, uTypes, uDebug;
 
 type TAddFileLogRaw = procedure (s: pchar); cdecl;
 const AvwrapperLibName = {$IFDEF WIN32_VCPKG}'avwrapper'{$ELSE}'libavwrapper'{$ENDIF};
@@ -289,8 +289,8 @@
     // Videos don't work if /lua command was used, so we forbid them
     if luaCmdUsed then
         begin
-        // TODO: Show message to player
         PlaySound(sndDenied);
+        AddChatString(#0 + shortstring(trmsg[sidVideoRecLuaFail]));
         AddFileLog('Pre-recording prevented; /lua command was used before');
         exit;
         end;
--- a/hedgewars/uWorld.pas	Sat Sep 28 16:39:02 2019 +0300
+++ b/hedgewars/uWorld.pas	Tue Oct 15 13:42:50 2019 -0400
@@ -1299,6 +1299,10 @@
 
 DrawGearsGui();
 
+// Finger (arrow pointing to hedgehog).
+// NOT wrapped like the other stuff because it might be confusing.
+DrawFinger();
+
 // everything after this ChangeDepth will be drawn outside the screen
 // note: negative parallax gears should last very little for a smooth stereo effect
     ChangeDepth(RM, cStereo_Outside);
@@ -1546,9 +1550,13 @@
         end;
     end
     // in gfInvulnerable mode ...
-    else if (CurrentHedgehog^.Effects[heResurrectable] <> 0) then
-        // show halo for resurrectable hog
-        DrawSprite(sprHaloHud, (cScreenWidth div 2 - CurrentHedgehog^.HealthTagTex^.w - t - 2), i, 0);
+    else
+        begin
+        DrawSprite(sprInvulnHud, cScreenWidth div 2 - 28, i, 0);
+        if (CurrentHedgehog^.Effects[heResurrectable] <> 0) then
+            // show halo for resurrectable hog
+            DrawSprite(sprHaloHud, cScreenWidth div 2 - 30, i - SpritesData[sprHaloHud].Height + 1, 0);
+        end;
     end
 else
     cDemoClockFPSOffsetY:= 0;
--- a/rust/hedgewars-server/src/core/indexslab.rs	Sat Sep 28 16:39:02 2019 +0300
+++ b/rust/hedgewars-server/src/core/indexslab.rs	Tue Oct 15 13:42:50 2019 -0400
@@ -19,6 +19,14 @@
         }
     }
 
+    pub fn get(&self, index: usize) -> Option<&T> {
+        self.data[index].as_ref()
+    }
+
+    pub fn get_mut(&mut self, index: usize) -> Option<&mut T> {
+        self.data[index].as_mut()
+    }
+
     pub fn insert(&mut self, index: usize, value: T) {
         if index >= self.data.len() {
             self.data.reserve(index - self.data.len() + 1);
@@ -41,7 +49,7 @@
         }
     }
 
-    pub fn iter(&self) -> impl Iterator<Item = (usize, &T)> {
+    pub fn iter(&self) -> impl Iterator<Item = (usize, &T)> + Clone {
         self.data
             .iter()
             .enumerate()
--- a/rust/hedgewars-server/src/core/server.rs	Sat Sep 28 16:39:02 2019 +0300
+++ b/rust/hedgewars-server/src/core/server.rs	Tue Oct 15 13:42:50 2019 -0400
@@ -2,23 +2,46 @@
     client::HwClient,
     indexslab::IndexSlab,
     room::HwRoom,
-    types::{ClientId, RoomId},
+    types::{ClientId, RoomId, ServerVar},
 };
 use crate::{protocol::messages::HwProtocolMessage::Greeting, utils};
 
+use crate::core::server::JoinRoomError::WrongProtocol;
 use bitflags::*;
 use log::*;
 use slab;
-use std::{borrow::BorrowMut, iter, num::NonZeroU16};
+use std::{borrow::BorrowMut, collections::HashSet, iter, num::NonZeroU16};
 
 type Slab<T> = slab::Slab<T>;
 
+#[derive(Debug)]
+pub enum CreateRoomError {
+    InvalidName,
+    AlreadyExists,
+}
+
+#[derive(Debug)]
+pub enum JoinRoomError {
+    DoesntExist,
+    WrongProtocol,
+    Full,
+    Restricted,
+}
+
+#[derive(Debug)]
+pub struct UninitializedError();
+#[derive(Debug)]
+pub struct AccessError();
+
 pub struct HwAnteClient {
     pub nick: Option<String>,
     pub protocol_number: Option<NonZeroU16>,
     pub server_salt: String,
     pub is_checker: bool,
     pub is_local_admin: bool,
+    pub is_registered: bool,
+    pub is_admin: bool,
+    pub is_contributor: bool,
 }
 
 pub struct HwAnteroom {
@@ -38,12 +61,15 @@
             server_salt: salt,
             is_checker: false,
             is_local_admin,
+            is_registered: false,
+            is_admin: false,
+            is_contributor: false,
         };
         self.clients.insert(client_id, client);
     }
 
     pub fn remove_client(&mut self, client_id: ClientId) -> Option<HwAnteClient> {
-        let mut client = self.clients.remove(client_id);
+        let client = self.clients.remove(client_id);
         client
     }
 }
@@ -91,6 +117,34 @@
         }
     }
 
+    #[inline]
+    pub fn client(&self, client_id: ClientId) -> &HwClient {
+        &self.clients[client_id]
+    }
+
+    #[inline]
+    pub fn client_mut(&mut self, client_id: ClientId) -> &mut HwClient {
+        &mut self.clients[client_id]
+    }
+
+    #[inline]
+    pub fn room(&self, room_id: RoomId) -> &HwRoom {
+        &self.rooms[room_id]
+    }
+
+    #[inline]
+    pub fn room_mut(&mut self, room_id: RoomId) -> &mut HwRoom {
+        &mut self.rooms[room_id]
+    }
+
+    #[inline]
+    pub fn is_admin(&self, client_id: ClientId) -> bool {
+        self.clients
+            .get(client_id)
+            .map(|c| c.is_admin())
+            .unwrap_or(false)
+    }
+
     pub fn add_client(&mut self, client_id: ClientId, data: HwAnteClient) {
         if let (Some(protocol), Some(nick)) = (data.protocol_number, data.nick) {
             let mut client = HwClient::new(client_id, protocol.get(), nick);
@@ -98,6 +152,13 @@
             #[cfg(not(feature = "official-server"))]
             client.set_is_admin(data.is_local_admin);
 
+            #[cfg(feature = "official-server")]
+            {
+                client.set_is_registered(info.is_registered);
+                client.set_is_admin(info.is_admin);
+                client.set_is_contributor(info.is_contributor);
+            }
+
             self.clients.insert(client_id, client);
         }
     }
@@ -106,8 +167,8 @@
         self.clients.remove(client_id);
     }
 
-    pub fn get_greetings(&self, client_id: ClientId) -> &str {
-        if self.clients[client_id].protocol_number < self.latest_protocol {
+    pub fn get_greetings(&self, client: &HwClient) -> &str {
+        if client.protocol_number < self.latest_protocol {
             &self.greetings.for_old_protocols
         } else {
             &self.greetings.for_latest_protocol
@@ -115,29 +176,123 @@
     }
 
     #[inline]
+    pub fn get_client_nick(&self, client_id: ClientId) -> &str {
+        &self.clients[client_id].nick
+    }
+
+    #[inline]
     pub fn create_room(
         &mut self,
         creator_id: ClientId,
         name: String,
         password: Option<String>,
-    ) -> RoomId {
-        create_room(
-            &mut self.clients[creator_id],
-            &mut self.rooms,
-            name,
-            password,
-        )
+    ) -> Result<(&HwClient, &HwRoom), CreateRoomError> {
+        use CreateRoomError::*;
+        if utils::is_name_illegal(&name) {
+            Err(InvalidName)
+        } else if self.has_room(&name) {
+            Err(AlreadyExists)
+        } else {
+            Ok(create_room(
+                &mut self.clients[creator_id],
+                &mut self.rooms,
+                name,
+                password,
+            ))
+        }
+    }
+
+    pub fn join_room(
+        &mut self,
+        client_id: ClientId,
+        room_id: RoomId,
+    ) -> Result<(&HwClient, &HwRoom, impl Iterator<Item = &HwClient> + Clone), JoinRoomError> {
+        use JoinRoomError::*;
+        let room = &mut self.rooms[room_id];
+        let client = &mut self.clients[client_id];
+
+        if client.protocol_number != room.protocol_number {
+            Err(WrongProtocol)
+        } else if room.is_join_restricted() {
+            Err(Restricted)
+        } else if room.players_number == u8::max_value() {
+            Err(Full)
+        } else {
+            move_to_room(client, room);
+            let room_id = room.id;
+            Ok((
+                &self.clients[client_id],
+                &self.rooms[room_id],
+                self.clients.iter().map(|(_, c)| c),
+            ))
+        }
     }
 
     #[inline]
-    pub fn move_to_room(&mut self, client_id: ClientId, room_id: RoomId) {
-        move_to_room(&mut self.clients[client_id], &mut self.rooms[room_id])
+    pub fn join_room_by_name(
+        &mut self,
+        client_id: ClientId,
+        room_name: &str,
+    ) -> Result<(&HwClient, &HwRoom, impl Iterator<Item = &HwClient> + Clone), JoinRoomError> {
+        use JoinRoomError::*;
+        let room = self.rooms.iter().find(|(_, r)| r.name == room_name);
+        if let Some((_, room)) = room {
+            let room_id = room.id;
+            self.join_room(client_id, room_id)
+        } else {
+            Err(DoesntExist)
+        }
+    }
+
+    #[inline]
+    pub fn set_var(&mut self, client_id: ClientId, var: ServerVar) -> Result<(), AccessError> {
+        if self.clients[client_id].is_admin() {
+            match var {
+                ServerVar::MOTDNew(msg) => self.greetings.for_latest_protocol = msg,
+                ServerVar::MOTDOld(msg) => self.greetings.for_old_protocols = msg,
+                ServerVar::LatestProto(n) => self.latest_protocol = n,
+            }
+            Ok(())
+        } else {
+            Err(AccessError())
+        }
     }
 
+    #[inline]
+    pub fn get_vars(&self, client_id: ClientId) -> Result<[ServerVar; 3], AccessError> {
+        if self.clients[client_id].is_admin() {
+            Ok([
+                ServerVar::MOTDNew(self.greetings.for_latest_protocol.clone()),
+                ServerVar::MOTDOld(self.greetings.for_old_protocols.clone()),
+                ServerVar::LatestProto(self.latest_protocol),
+            ])
+        } else {
+            Err(AccessError())
+        }
+    }
+
+    pub fn get_used_protocols(&self, client_id: ClientId) -> Result<Vec<u16>, AccessError> {
+        if self.clients[client_id].is_admin() {
+            let mut protocols: HashSet<_> = self
+                .clients
+                .iter()
+                .map(|(_, c)| c.protocol_number)
+                .chain(self.rooms.iter().map(|(_, r)| r.protocol_number))
+                .collect();
+            let mut protocols: Vec<_> = protocols.drain().collect();
+            protocols.sort();
+            Ok(protocols)
+        } else {
+            Err(AccessError())
+        }
+    }
+
+    #[inline]
     pub fn has_room(&self, name: &str) -> bool {
         self.find_room(name).is_some()
     }
 
+    #[inline]
     pub fn find_room(&self, name: &str) -> Option<&HwRoom> {
         self.rooms
             .iter()
@@ -234,12 +389,12 @@
     entry.insert(room)
 }
 
-fn create_room(
-    client: &mut HwClient,
-    rooms: &mut Slab<HwRoom>,
+fn create_room<'a, 'b>(
+    client: &'a mut HwClient,
+    rooms: &'b mut Slab<HwRoom>,
     name: String,
     password: Option<String>,
-) -> RoomId {
+) -> (&'a HwClient, &'b HwRoom) {
     let room = allocate_room(rooms);
 
     room.master_id = Some(client.id);
@@ -255,7 +410,7 @@
     client.set_is_ready(true);
     client.set_is_joined_mid_game(false);
 
-    room.id
+    (client, room)
 }
 
 fn move_to_room(client: &mut HwClient, room: &mut HwRoom) {
--- a/rust/hedgewars-server/src/handlers.rs	Sat Sep 28 16:39:02 2019 +0300
+++ b/rust/hedgewars-server/src/handlers.rs	Tue Oct 15 13:42:50 2019 -0400
@@ -9,6 +9,7 @@
 use self::{
     actions::{Destination, DestinationGroup, PendingMessage},
     inanteroom::LoginResult,
+    strings::*,
 };
 use crate::{
     core::{
@@ -32,6 +33,7 @@
 mod inanteroom;
 mod inlobby;
 mod inroom;
+mod strings;
 
 #[derive(PartialEq, Debug)]
 pub struct Sha1Digest([u8; 20]);
@@ -160,6 +162,16 @@
     }
 
     #[inline]
+    pub fn warn(&mut self, message: &str) {
+        self.add(Warning(message.to_string()).send_self());
+    }
+
+    #[inline]
+    pub fn error(&mut self, message: &str) {
+        self.add(Error(message.to_string()).send_self());
+    }
+
+    #[inline]
     pub fn request_io(&mut self, task: IoTask) {
         self.io_tasks.push(task)
     }
@@ -240,7 +252,7 @@
                     LoginResult::Complete => {
                         if let Some(client) = server.anteroom.remove_client(client_id) {
                             server.add_client(client_id, client);
-                            common::join_lobby(server, response);
+                            common::get_lobby_join_data(server, response);
                         }
                     }
                     LoginResult::Exit => {
@@ -262,7 +274,7 @@
                             let master_sign = if client.is_master() { "+" } else { "" };
                             let room_info = match client.room_id {
                                 Some(room_id) => {
-                                    let room = &server.rooms[room_id];
+                                    let room = server.room(room_id);
                                     let status = match room.game_info {
                                         Some(_) if client.teams_in_game == 0 => "(spectating)",
                                         Some(_) => "(playing)",
@@ -284,37 +296,36 @@
                             ];
                             response.add(Info(info).send_self())
                         } else {
-                            response
-                                .add(server_chat("Player is not online.".to_string()).send_self())
+                            response.add(server_chat(USER_OFFLINE.to_string()).send_self())
                         }
                     }
                     HwProtocolMessage::ToggleServerRegisteredOnly => {
-                        if !server.clients[client_id].is_admin() {
-                            response.add(Warning("Access denied.".to_string()).send_self());
+                        if !server.is_admin(client_id) {
+                            response.warn(ACCESS_DENIED);
                         } else {
-                            server.set_is_registered_only(server.is_registered_only());
+                            server.set_is_registered_only(!server.is_registered_only());
                             let msg = if server.is_registered_only() {
-                                "This server no longer allows unregistered players to join."
+                                REGISTERED_ONLY_ENABLED
                             } else {
-                                "This server now allows unregistered players to join."
+                                REGISTERED_ONLY_DISABLED
                             };
                             response.add(server_chat(msg.to_string()).send_all());
                         }
                     }
                     HwProtocolMessage::Global(msg) => {
-                        if !server.clients[client_id].is_admin() {
-                            response.add(Warning("Access denied.".to_string()).send_self());
+                        if !server.is_admin(client_id) {
+                            response.warn(ACCESS_DENIED);
                         } else {
                             response.add(global_chat(msg).send_all())
                         }
                     }
                     HwProtocolMessage::SuperPower => {
-                        if !server.clients[client_id].is_admin() {
-                            response.add(Warning("Access denied.".to_string()).send_self());
+                        let client = server.client_mut(client_id);
+                        if !client.is_admin() {
+                            response.warn(ACCESS_DENIED);
                         } else {
-                            server.clients[client_id].set_has_super_power(true);
-                            response
-                                .add(server_chat("Super power activated.".to_string()).send_self())
+                            client.set_has_super_power(true);
+                            response.add(server_chat(SUPER_POWER.to_string()).send_self())
                         }
                     }
                     HwProtocolMessage::Watch(id) => {
@@ -325,13 +336,10 @@
 
                         #[cfg(not(feature = "official-server"))]
                         {
-                            response.add(
-                                Warning("This server does not support replays!".to_string())
-                                    .send_self(),
-                            );
+                            response.warn(REPLAY_NOT_SUPPORTED);
                         }
                     }
-                    _ => match server.clients[client_id].room_id {
+                    _ => match server.client(client_id).room_id {
                         None => inlobby::handle(server, client_id, response, message),
                         Some(room_id) => {
                             inroom::handle(server, client_id, response, room_id, message)
@@ -374,38 +382,35 @@
     match io_result {
         IoResult::AccountRegistered(is_registered) => {
             if !is_registered && server.is_registered_only() {
-                response.add(
-                    Bye("This server only allows registered users to join.".to_string())
-                        .send_self(),
-                );
+                response.add(Bye(REGISTRATION_REQUIRED.to_string()).send_self());
                 response.remove_client(client_id);
             } else if is_registered {
                 let salt = server.anteroom.clients[client_id].server_salt.clone();
                 response.add(AskPassword(salt).send_self());
             } else if let Some(client) = server.anteroom.remove_client(client_id) {
                 server.add_client(client_id, client);
-                common::join_lobby(server, response);
+                common::get_lobby_join_data(server, response);
             }
         }
         IoResult::Account(Some(info)) => {
             response.add(ServerAuth(format!("{:x}", info.server_hash)).send_self());
-            if let Some(client) = server.anteroom.remove_client(client_id) {
+            if let Some(mut client) = server.anteroom.remove_client(client_id) {
+                client.is_registered = info.is_registered;
+                client.is_admin = info.is_admin;
+                client.is_contributor = info.is_contributor;
                 server.add_client(client_id, client);
-                let client = &mut server.clients[client_id];
-                client.set_is_registered(info.is_registered);
-                client.set_is_admin(info.is_admin);
-                client.set_is_contributor(info.is_contributor);
-                common::join_lobby(server, response);
+                common::get_lobby_join_data(server, response);
             }
         }
         IoResult::Account(None) => {
-            response.add(Error("Authentication failed.".to_string()).send_self());
+            response.error(AUTHENTICATION_FAILED);
             response.remove_client(client_id);
         }
         IoResult::Replay(Some(replay)) => {
-            let protocol = server.clients[client_id].protocol_number;
+            let client = server.client(client_id);
+            let protocol = client.protocol_number;
             let start_msg = if protocol < 58 {
-                RoomJoined(vec![server.clients[client_id].nick.clone()])
+                RoomJoined(vec![client.nick.clone()])
             } else {
                 ReplayStart
             };
@@ -421,32 +426,27 @@
             }
         }
         IoResult::Replay(None) => {
-            response.add(Warning("Could't load the replay".to_string()).send_self())
+            response.warn(REPLAY_LOAD_FAILED);
         }
         IoResult::SaveRoom(_, true) => {
-            response.add(server_chat("Room configs saved successfully.".to_string()).send_self());
+            response.add(server_chat(ROOM_CONFIG_SAVED.to_string()).send_self());
         }
         IoResult::SaveRoom(_, false) => {
-            response.add(Warning("Unable to save the room configs.".to_string()).send_self());
+            response.warn(ROOM_CONFIG_SAVE_FAILED);
         }
         IoResult::LoadRoom(room_id, Some(contents)) => {
             if let Some(ref mut room) = server.rooms.get_mut(room_id) {
                 match room.set_saves(&contents) {
-                    Ok(_) => response.add(
-                        server_chat("Room configs loaded successfully.".to_string()).send_self(),
-                    ),
+                    Ok(_) => response.add(server_chat(ROOM_CONFIG_LOADED.to_string()).send_self()),
                     Err(e) => {
                         warn!("Error while deserializing the room configs: {}", e);
-                        response.add(
-                            Warning("Unable to deserialize the room configs.".to_string())
-                                .send_self(),
-                        );
+                        response.warn(ROOM_CONFIG_DESERIALIZE_FAILED);
                     }
                 }
             }
         }
         IoResult::LoadRoom(_, None) => {
-            response.add(Warning("Unable to load the room configs.".to_string()).send_self());
+            response.warn(ROOM_CONFIG_LOAD_FAILED);
         }
     }
 }
--- a/rust/hedgewars-server/src/handlers/common.rs	Sat Sep 28 16:39:02 2019 +0300
+++ b/rust/hedgewars-server/src/handlers/common.rs	Tue Oct 15 13:42:50 2019 -0400
@@ -2,7 +2,7 @@
     core::{
         client::HwClient,
         room::HwRoom,
-        server::HwServer,
+        server::{HwServer, JoinRoomError},
         types::{ClientId, GameCfg, RoomId, TeamInfo, Vote, VoteType},
     },
     protocol::messages::{
@@ -35,10 +35,10 @@
     }
 }
 
-pub fn join_lobby(server: &mut HwServer, response: &mut Response) {
+pub fn get_lobby_join_data(server: &HwServer, response: &mut Response) {
     let client_id = response.client_id();
 
-    let client = &server.clients[client_id];
+    let client = server.client(client_id);
     let nick = vec![client.nick.clone()];
     let mut flags = vec![];
     if client.is_registered() {
@@ -69,7 +69,7 @@
         ),
     ];
 
-    let server_msg = ServerMessage(server.get_greetings(client_id).to_string());
+    let server_msg = ServerMessage(server.get_greetings(client).to_string());
 
     let rooms_msg = Rooms(
         server
@@ -227,32 +227,43 @@
     );
 }
 
-pub fn enter_room(
-    server: &mut HwServer,
-    client_id: ClientId,
-    room_id: RoomId,
+pub fn get_room_join_data<'a, I: Iterator<Item = &'a HwClient> + Clone>(
+    client: &HwClient,
+    room: &HwRoom,
+    room_clients: I,
     response: &mut Response,
 ) {
-    let nick = server.clients[client_id].nick.clone();
-    server.move_to_room(client_id, room_id);
+    #[inline]
+    fn collect_nicks<'a, I, F>(clients: I, f: F) -> Vec<String>
+    where
+        I: Iterator<Item = &'a HwClient>,
+        F: Fn(&&'a HwClient) -> bool,
+    {
+        clients.filter(f).map(|c| &c.nick).cloned().collect()
+    }
 
-    response.add(RoomJoined(vec![nick.clone()]).send_all().in_room(room_id));
+    let nick = client.nick.clone();
+    response.add(RoomJoined(vec![nick.clone()]).send_all().in_room(room.id));
     response.add(ClientFlags(add_flags(&[Flags::InRoom]), vec![nick]).send_all());
-    let nicks = server.collect_nicks(|(_, c)| c.room_id == Some(room_id));
+    let nicks = collect_nicks(room_clients.clone(), |c| c.room_id == Some(room.id));
     response.add(RoomJoined(nicks).send_self());
 
-    get_room_teams(server, room_id, client_id, response);
-
-    let room = &server.rooms[room_id];
-    get_room_config(room, client_id, response);
+    get_room_teams(room, client.id, response);
+    get_room_config(room, client.id, response);
 
     let mut flag_selectors = [
         (
             Flags::RoomMaster,
-            server.collect_nicks(|(_, c)| c.is_master()),
+            collect_nicks(room_clients.clone(), |c| c.is_master()),
         ),
-        (Flags::Ready, server.collect_nicks(|(_, c)| c.is_ready())),
-        (Flags::InGame, server.collect_nicks(|(_, c)| c.is_in_game())),
+        (
+            Flags::Ready,
+            collect_nicks(room_clients.clone(), |c| c.is_ready()),
+        ),
+        (
+            Flags::InGame,
+            collect_nicks(room_clients.clone(), |c| c.is_in_game()),
+        ),
     ];
 
     for (flag, nicks) in &mut flag_selectors {
@@ -270,6 +281,16 @@
     }
 }
 
+pub fn get_room_join_error(error: JoinRoomError, response: &mut Response) {
+    use super::strings::*;
+    match error {
+        JoinRoomError::DoesntExist => response.warn(NO_ROOM),
+        JoinRoomError::WrongProtocol => response.warn(WRONG_PROTOCOL),
+        JoinRoomError::Full => response.warn(ROOM_FULL),
+        JoinRoomError::Restricted => response.warn(ROOM_JOIN_RESTRICTED),
+    }
+}
+
 pub fn exit_room(server: &mut HwServer, client_id: ClientId, response: &mut Response, msg: &str) {
     let client = &mut server.clients[client_id];
 
@@ -317,8 +338,8 @@
 
     server.remove_client(client_id);
 
-    response.add(LobbyLeft(nick, msg.to_string()).send_all());
-    response.add(Bye("User quit: ".to_string() + &msg).send_self());
+    response.add(LobbyLeft(nick, msg.clone()).send_all());
+    response.add(Bye(msg).send_self());
     response.remove_client(client_id);
 }
 
@@ -354,13 +375,7 @@
     }
 }
 
-pub fn get_room_teams(
-    server: &HwServer,
-    room_id: RoomId,
-    to_client: ClientId,
-    response: &mut Response,
-) {
-    let room = &server.rooms[room_id];
+pub fn get_room_teams(room: &HwRoom, to_client: ClientId, response: &mut Response) {
     let current_teams = match room.game_info {
         Some(ref info) => &info.teams_at_start,
         None => &room.teams,
--- a/rust/hedgewars-server/src/handlers/inlobby.rs	Sat Sep 28 16:39:02 2019 +0300
+++ b/rust/hedgewars-server/src/handlers/inlobby.rs	Tue Oct 15 13:42:50 2019 -0400
@@ -1,10 +1,10 @@
 use mio;
 
-use super::common::rnd_reply;
+use super::{common::rnd_reply, strings::*};
 use crate::{
     core::{
         client::HwClient,
-        server::HwServer,
+        server::{AccessError, CreateRoomError, HwServer, JoinRoomError},
         types::{ClientId, ServerVar},
     },
     protocol::messages::{
@@ -23,41 +23,34 @@
     message: HwProtocolMessage,
 ) {
     use crate::protocol::messages::HwProtocolMessage::*;
+
     match message {
-        CreateRoom(name, password) => {
-            if is_name_illegal(&name) {
-                response.add(Warning("Illegal room name! A room name must be between 1-40 characters long, must not have a trailing or leading space and must not have any of these characters: $()*+?[]^{|}".to_string()).send_self());
-            } else if server.has_room(&name) {
-                response.add(
-                    Warning("A room with the same name already exists.".to_string()).send_self(),
-                );
-            } else {
-                let flags_msg = ClientFlags(
-                    add_flags(&[Flags::RoomMaster, Flags::Ready]),
-                    vec![server.clients[client_id].nick.clone()],
-                );
-
-                let room_id = server.create_room(client_id, name, password);
-                let room = &server.rooms[room_id];
-                let client = &server.clients[client_id];
-
+        CreateRoom(name, password) => match server.create_room(client_id, name, password) {
+            Err(CreateRoomError::InvalidName) => response.warn(ILLEGAL_ROOM_NAME),
+            Err(CreateRoomError::AlreadyExists) => response.warn(ROOM_EXISTS),
+            Ok((client, room)) => {
                 response.add(
                     RoomAdd(room.info(Some(&client)))
                         .send_all()
                         .with_protocol(room.protocol_number),
                 );
                 response.add(RoomJoined(vec![client.nick.clone()]).send_self());
-                response.add(flags_msg.send_self());
-
+                response.add(
+                    ClientFlags(
+                        add_flags(&[Flags::RoomMaster, Flags::Ready]),
+                        vec![client.nick.clone()],
+                    )
+                    .send_self(),
+                );
                 response.add(
                     ClientFlags(add_flags(&[Flags::InRoom]), vec![client.nick.clone()]).send_self(),
                 );
-            };
-        }
+            }
+        },
         Chat(msg) => {
             response.add(
                 ChatMsg {
-                    nick: server.clients[client_id].nick.clone(),
+                    nick: server.get_client_nick(client_id).to_string(),
                     msg,
                 }
                 .send_all()
@@ -65,99 +58,62 @@
                 .but_self(),
             );
         }
-        JoinRoom(name, _password) => {
-            let room = server.rooms.iter().find(|(_, r)| r.name == name);
-            let room_id = room.map(|(_, r)| r.id);
-
-            let client = &mut server.clients[client_id];
-
-            if let Some((_, room)) = room {
-                if client.protocol_number != room.protocol_number {
-                    response.add(
-                        Warning("Room version incompatible to your Hedgewars version!".to_string())
-                            .send_self(),
-                    );
-                } else if room.is_join_restricted() {
-                    response.add(
-                        Warning(
-                            "Access denied. This room currently doesn't allow joining.".to_string(),
-                        )
-                        .send_self(),
-                    );
-                } else if room.players_number == u8::max_value() {
-                    response.add(Warning("This room is already full".to_string()).send_self());
-                } else if let Some(room_id) = room_id {
-                    super::common::enter_room(server, client_id, room_id, response);
+        JoinRoom(name, _password) => match server.join_room_by_name(client_id, &name) {
+            Err(error) => super::common::get_room_join_error(error, response),
+            Ok((client, room, room_clients)) => {
+                super::common::get_room_join_data(client, room, room_clients, response)
+            }
+        },
+        Follow(nick) => {
+            if let Some(client) = server.find_client(&nick) {
+                if let Some(room_id) = client.room_id {
+                    match server.join_room(client_id, room_id) {
+                        Err(error) => super::common::get_room_join_error(error, response),
+                        Ok((client, room, room_clients)) => {
+                            super::common::get_room_join_data(client, room, room_clients, response)
+                        }
+                    }
+                } else {
+                    response.warn(NO_ROOM);
                 }
             } else {
-                response.add(Warning("No such room.".to_string()).send_self());
+                response.warn(NO_USER);
             }
         }
-        Follow(nick) => {
-            if let Some(HwClient {
-                room_id: Some(room_id),
-                ..
-            }) = server.find_client(&nick)
-            {
-                let room = &server.rooms[*room_id];
-                response.add(Joining(room.name.clone()).send_self());
-                super::common::enter_room(server, client_id, *room_id, response);
+        SetServerVar(var) => match server.set_var(client_id, var) {
+            Err(AccessError()) => response.warn(ACCESS_DENIED),
+            Ok(()) => response.add(server_chat(VARIABLE_UPDATED.to_string()).send_self()),
+        },
+        GetServerVar => match server.get_vars(client_id) {
+            Err(AccessError()) => response.warn(ACCESS_DENIED),
+            Ok(vars) => {
+                response.add(
+                    ServerVars(vars.iter().flat_map(|v| v.to_protocol()).collect()).send_self(),
+                );
             }
-        }
-        SetServerVar(var) => {
-            if !server.clients[client_id].is_admin() {
-                response.add(Warning("Access denied.".to_string()).send_self());
-            } else {
-                match var {
-                    ServerVar::MOTDNew(msg) => server.greetings.for_latest_protocol = msg,
-                    ServerVar::MOTDOld(msg) => server.greetings.for_old_protocols = msg,
-                    ServerVar::LatestProto(n) => server.latest_protocol = n,
-                }
-            }
-        }
-        GetServerVar => {
-            if !server.clients[client_id].is_admin() {
-                response.add(Warning("Access denied.".to_string()).send_self());
-            } else {
-                let vars: Vec<_> = [
-                    ServerVar::MOTDNew(server.greetings.for_latest_protocol.clone()),
-                    ServerVar::MOTDOld(server.greetings.for_old_protocols.clone()),
-                    ServerVar::LatestProto(server.latest_protocol),
-                ]
-                .iter()
-                .flat_map(|v| v.to_protocol())
-                .collect();
-                response.add(ServerVars(vars).send_self());
-            }
-        }
+        },
         Rnd(v) => {
             response.add(rnd_reply(&v).send_self());
         }
-        Stats => {
-            let mut protocols: HashSet<_> = server
-                .clients
-                .iter()
-                .map(|(_, c)| c.protocol_number)
-                .chain(server.rooms.iter().map(|(_, r)| r.protocol_number))
-                .collect();
-            let mut protocols: Vec<_> = protocols.drain().collect();
-            protocols.sort();
-
-            let mut html = Vec::with_capacity(protocols.len() + 2);
+        Stats => match server.get_used_protocols(client_id) {
+            Err(AccessError()) => response.warn(ACCESS_DENIED),
+            Ok(protocols) => {
+                let mut html = Vec::with_capacity(protocols.len() + 2);
 
-            html.push("<table>".to_string());
-            for protocol in protocols {
-                html.push(format!(
-                    "<tr><td>{}</td><td>{}</td><td>{}</td></tr>",
-                    super::utils::protocol_version_string(protocol),
-                    server.protocol_clients(protocol).count(),
-                    server.protocol_rooms(protocol).count()
-                ));
+                html.push("<table>".to_string());
+                for protocol in protocols {
+                    html.push(format!(
+                        "<tr><td>{}</td><td>{}</td><td>{}</td></tr>",
+                        super::utils::protocol_version_string(protocol),
+                        server.protocol_clients(protocol).count(),
+                        server.protocol_rooms(protocol).count()
+                    ));
+                }
+                html.push("</table>".to_string());
+
+                response.add(Warning(html.join("")).send_self());
             }
-            html.push("</table>".to_string());
-
-            response.add(Warning(html.join("")).send_self());
-        }
+        },
         List => warn!("Deprecated LIST message received"),
         _ => warn!("Incorrect command in lobby state"),
     }
--- a/rust/hedgewars-server/src/handlers/inroom.rs	Sat Sep 28 16:39:02 2019 +0300
+++ b/rust/hedgewars-server/src/handlers/inroom.rs	Tue Oct 15 13:42:50 2019 -0400
@@ -52,7 +52,7 @@
         [size, typ, body..MAX] => {
             VALID_MESSAGES.contains(typ)
                 && match body {
-                    [1...MAX_HEDGEHOGS_PER_TEAM, team, ..] if *typ == b'h' => {
+                    [1..=MAX_HEDGEHOGS_PER_TEAM, team, ..] if *typ == b'h' => {
                         team_indices.contains(team)
                     }
                     _ => *typ != b'h',
@@ -272,12 +272,8 @@
             Some((_, name)) => {
                 client.teams_in_game -= 1;
                 client.clan = room.find_team_color(client.id);
-                super::common::remove_teams(
-                    room,
-                    vec![name.to_string()],
-                    client.is_in_game(),
-                    response,
-                );
+                let names = vec![name.to_string()];
+                super::common::remove_teams(room, names, client.is_in_game(), response);
 
                 match room.game_info {
                     Some(ref info) if info.teams_in_game == 0 => {
@@ -438,7 +434,7 @@
                 }
                 VoteType::NewSeed => None,
                 VoteType::HedgehogsPerTeam(number) => match number {
-                    1...MAX_HEDGEHOGS_PER_TEAM => None,
+                    1..=MAX_HEDGEHOGS_PER_TEAM => None,
                     _ => Some("/callvote hedgehogs: Specify number from 1 to 8.".to_string()),
                 },
             };
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-server/src/handlers/strings.rs	Tue Oct 15 13:42:50 2019 -0400
@@ -0,0 +1,23 @@
+pub const ACCESS_DENIED: &str = "Access denied.";
+pub const AUTHENTICATION_FAILED: &str = "Authentication failed.";
+pub const ILLEGAL_ROOM_NAME: &str = "Illegal room name! A room name must be between 1-40 characters long, must not have a trailing or leading space and must not have any of these characters: $()*+?[]^{|}";
+pub const NO_ROOM: &str = "No such room.";
+pub const NO_USER: &str = "No such user.";
+pub const REPLAY_LOAD_FAILED: &str = "Could't load the replay";
+pub const REPLAY_NOT_SUPPORTED: &str = "This server does not support replays!";
+pub const REGISTRATION_REQUIRED: &str = "This server only allows registered users to join.";
+pub const REGISTERED_ONLY_ENABLED: &str =
+    "This server no longer allows unregistered players to join.";
+pub const REGISTERED_ONLY_DISABLED: &str = "This server now allows unregistered players to join.";
+pub const ROOM_CONFIG_SAVE_FAILED: &str = "Unable to save the room configs.";
+pub const ROOM_CONFIG_LOAD_FAILED: &str = "Unable to load the room configs.";
+pub const ROOM_CONFIG_DESERIALIZE_FAILED: &str = "Unable to deserialize the room configs.";
+pub const ROOM_CONFIG_LOADED: &str = "Room configs loaded successfully.";
+pub const ROOM_CONFIG_SAVED: &str = "Room configs saved successfully.";
+pub const ROOM_EXISTS: &str = "A room with the same name already exists.";
+pub const ROOM_FULL: &str = "This room is already full.";
+pub const ROOM_JOIN_RESTRICTED: &str = "Access denied. This room currently doesn't allow joining.";
+pub const SUPER_POWER: &str = "Super power activated.";
+pub const USER_OFFLINE: &str = "Player is not online.";
+pub const VARIABLE_UPDATED: &str = "Server variable has been updated.";
+pub const WRONG_PROTOCOL: &str = "Room version incompatible to your Hedgewars version!";
--- a/rust/hedgewars-server/src/protocol.rs	Sat Sep 28 16:39:02 2019 +0300
+++ b/rust/hedgewars-server/src/protocol.rs	Tue Oct 15 13:42:50 2019 -0400
@@ -24,7 +24,8 @@
     fn recover(&mut self) -> bool {
         self.is_recovering = match parser::malformed_message(&self.buf[..]) {
             Ok((tail, ())) => {
-                self.buf.consume(self.buf.len() - tail.len());
+                let length = tail.len();
+                self.buf.consume(self.buf.len() - length);
                 false
             }
             _ => {
@@ -50,7 +51,8 @@
                 match parser::message(&self.buf[..]) {
                     Ok((tail, message)) => {
                         messages.push(message);
-                        self.buf.consume(self.buf.len() - tail.len());
+                        let length = tail.len();
+                        self.buf.consume(self.buf.len() - length);
                     }
                     Err(nom::Err::Incomplete(_)) => break,
                     Err(nom::Err::Failure(e)) | Err(nom::Err::Error(e)) => {
--- a/rust/hedgewars-server/src/utils.rs	Sat Sep 28 16:39:02 2019 +0300
+++ b/rust/hedgewars-server/src/utils.rs	Tue Oct 15 13:42:50 2019 -0400
@@ -69,6 +69,8 @@
         56 => "0.9.25-dev",
         57 => "0.9.25",
         58 => "1.0.0-dev",
+        59 => "1.0.0",
+        60 => "1.0.1-dev",
         _ => "Unknown",
     }
 }
Binary file share/hedgewars/Data/Graphics/AmmoMenu/TurnsLeft.png has changed
Binary file share/hedgewars/Data/Graphics/dynamiteDefused.png has changed
--- a/share/hedgewars/Data/Locale/de.txt	Sat Sep 28 16:39:02 2019 +0300
+++ b/share/hedgewars/Data/Locale/de.txt	Tue Oct 15 13:42:50 2019 -0400
@@ -111,6 +111,7 @@
 01:46=[Klan] %1: %2
 01:47=[%1]: %2
 01:48=?
+01:49=Videos können nicht aufgenommen werden, nachdem der /lua-Befehl benutzt wurde.
 
 ; Event messages
 ; Hog (%1) died
--- a/share/hedgewars/Data/Locale/en.txt	Sat Sep 28 16:39:02 2019 +0300
+++ b/share/hedgewars/Data/Locale/en.txt	Tue Oct 15 13:42:50 2019 -0400
@@ -121,6 +121,7 @@
 01:47=[%1]: %2
 ; Symbol for unknown mine timer
 01:48=?
+01:49=Videos can't be recorded after the /lua command was used.
 
 ; Event messages
 ; Normal hog (%1) died (0 health)
--- a/share/hedgewars/Data/Locale/hedgewars_it.ts	Sat Sep 28 16:39:02 2019 +0300
+++ b/share/hedgewars/Data/Locale/hedgewars_it.ts	Tue Oct 15 13:42:50 2019 -0400
@@ -32,51 +32,51 @@
     <message>
         <source>Dependency versions:</source>
         <extracomment>For the version numbers of Hedgewars&apos; software dependencies</extracomment>
-        <translation type="unfinished"></translation>
+        <translation>Versioni di riferimento:</translation>
     </message>
     <message>
         <source>&lt;a href=&quot;https://gcc.gnu.org&quot;&gt;GCC&lt;/a&gt;: %1</source>
-        <translation type="unfinished"></translation>
+        <translation>&lt;a href=&quot;https://gcc.gnu.org&quot;&gt;GCC&lt;/a&gt;: %1</translation>
     </message>
     <message>
         <source>&lt;a href=&quot;https://www.libsdl.org/&quot;&gt;SDL2&lt;/a&gt;: %1.%2.%3</source>
-        <translation type="unfinished"></translation>
+        <translation>&lt;a href=&quot;https://www.libsdl.org/&quot;&gt;SDL2&lt;/a&gt;: %1.%2.%3</translation>
     </message>
     <message>
         <source>&lt;a href=&quot;https://www.libsdl.org/&quot;&gt;SDL2_mixer&lt;/a&gt;: %1.%2.%3</source>
-        <translation type="unfinished"></translation>
+        <translation>&lt;a href=&quot;https://www.libsdl.org/&quot;&gt;SDL2_mixer&lt;/a&gt;: %1.%2.%3</translation>
     </message>
     <message>
         <source>&lt;a href=&quot;https://www.libsdl.org/&quot;&gt;SDL2_net&lt;/a&gt;: %1.%2.%3</source>
-        <translation type="unfinished"></translation>
+        <translation>&lt;a href=&quot;https://www.libsdl.org/&quot;&gt;SDL2_net&lt;/a&gt;: %1.%2.%3</translation>
     </message>
     <message>
         <source>&lt;a href=&quot;https://www.libsdl.org/&quot;&gt;SDL2_image&lt;/a&gt;: %1.%2.%3</source>
-        <translation type="unfinished"></translation>
+        <translation>&lt;a href=&quot;https://www.libsdl.org/&quot;&gt;SDL2_image&lt;/a&gt;: %1.%2.%3</translation>
     </message>
     <message>
         <source>&lt;a href=&quot;https://www.libsdl.org/&quot;&gt;SDL2_ttf&lt;/a&gt;: %1.%2.%3</source>
-        <translation type="unfinished"></translation>
+        <translation>&lt;a href=&quot;https://www.libsdl.org/&quot;&gt;SDL2_ttf&lt;/a&gt;: %1.%2.%3</translation>
     </message>
     <message>
         <source>&lt;a href=&quot;https://www.qt.io/developers/&quot;&gt;Qt&lt;/a&gt;: %1</source>
-        <translation type="unfinished"></translation>
+        <translation>&lt;a href=&quot;https://www.qt.io/developers/&quot;&gt;Qt&lt;/a&gt;: %1</translation>
     </message>
     <message>
         <source>&lt;a href=&quot;https://libav.org&quot;&gt;libavcodec&lt;/a&gt;: %1.%2.%3</source>
-        <translation type="unfinished"></translation>
+        <translation>&lt;a href=&quot;https://libav.org&quot;&gt;libavcodec&lt;/a&gt;: %1.%2.%3</translation>
     </message>
     <message>
         <source>&lt;a href=&quot;https://libav.org&quot;&gt;libavformat&lt;/a&gt;: %1.%2.%3</source>
-        <translation type="unfinished"></translation>
+        <translation>&lt;a href=&quot;https://libav.org&quot;&gt;libavformat&lt;/a&gt;: %1.%2.%3</translation>
     </message>
     <message>
         <source>&lt;a href=&quot;https://libav.org&quot;&gt;libavutil&lt;/a&gt;: %1.%2.%3</source>
-        <translation type="unfinished"></translation>
+        <translation>&lt;a href=&quot;https://libav.org&quot;&gt;libavutil&lt;/a&gt;: %1.%2.%3</translation>
     </message>
     <message>
         <source>&lt;a href=&quot;https://icculus.org/physfs/&quot;&gt;PhysFS&lt;/a&gt;: %1.%2.%3</source>
-        <translation type="unfinished"></translation>
+        <translation>&lt;a href=&quot;https://icculus.org/physfs/&quot;&gt;PhysFS&lt;/a&gt;: %1.%2.%3</translation>
     </message>
     <message>
         <source>Credits</source>
@@ -88,22 +88,22 @@
     </message>
     <message>
         <source>%1 (alias %2)</source>
-        <translation type="unfinished"></translation>
+        <translation>%1 (alias %2)</translation>
     </message>
     <message>
         <source>%1 &amp;lt;%2&amp;gt;</source>
         <extracomment>Part of credits. %1: Contributor name. %2: E-mail address</extracomment>
-        <translation type="unfinished"></translation>
+        <translation>%1 &amp;lt;%2&amp;gt;</translation>
     </message>
     <message>
         <source>%1: %2</source>
         <extracomment>Part of credits. %1: Description of contribution. %2: Contributor name</extracomment>
-        <translation type="unfinished"></translation>
+        <translation>%1: %2</translation>
     </message>
     <message>
         <source>%1: %2 &amp;lt;%3&amp;gt;</source>
         <extracomment>Part of credits. %1: Description of contribution. %2: Contributor name. %3: E-mail address</extracomment>
-        <translation type="unfinished"></translation>
+        <translation>%1: %2 &amp;lt;%3&amp;gt;</translation>
     </message>
     <message>
         <source>Extended Credits</source>
@@ -115,7 +115,7 @@
     </message>
     <message>
         <source>&lt;a href=&quot;https://visualstudio.microsoft.com&quot;&gt;VC++&lt;/a&gt;: %1</source>
-        <translation type="unfinished"></translation>
+        <translation>&lt;a href=&quot;https://visualstudio.microsoft.com&quot;&gt;VC++&lt;/a&gt;: %1</translation>
     </message>
     <message>
         <source>Unknown Compiler: %1</source>
@@ -1449,15 +1449,15 @@
     <message numerus="yes">
         <source>The best killer is &lt;b&gt;%1&lt;/b&gt; with &lt;b&gt;%2&lt;/b&gt; kills in a turn.</source>
         <translation>
-            <numerusform>&lt;p&gt;Il miglior killer è &lt;b&gt;%1&lt;/b&gt; con &lt;b&gt;%2&lt;/b&gt; uccisione in un turno.&lt;/p&gt;</numerusform>
-            <numerusform>&lt;p&gt;Il miglior killer è &lt;b&gt;%1&lt;/b&gt; con &lt;b&gt;%2&lt;/b&gt; uccisioni in un turno.&lt;/p&gt;</numerusform>
+            <numerusform>Il miglior killer è &lt;b&gt;%1&lt;/b&gt; con &lt;b&gt;%2&lt;/b&gt; uccisione in un turno.</numerusform>
+            <numerusform>Il miglior killer è &lt;b&gt;%1&lt;/b&gt; con &lt;b&gt;%2&lt;/b&gt; uccisioni in un turno.</numerusform>
         </translation>
     </message>
     <message numerus="yes">
         <source>A total of &lt;b&gt;%1&lt;/b&gt; hedgehog(s) were killed during this round.</source>
         <translation>
-            <numerusform>&lt;p&gt;Durante questo round è stato ucciso &lt;b&gt;%1&lt;/b&gt; riccio in totale.&lt;/p&gt;</numerusform>
-            <numerusform>&lt;p&gt;Durante questo round sono stati uccisi &lt;b&gt;%1&lt;/b&gt; ricci in totale.&lt;/p&gt;</numerusform>
+            <numerusform>Durante questo round è stato ucciso &lt;b&gt;%1&lt;/b&gt; riccio in totale.</numerusform>
+            <numerusform>Durante questo round sono stati uccisi &lt;b&gt;%1&lt;/b&gt; ricci in totale.</numerusform>
         </translation>
     </message>
     <message numerus="yes">
@@ -3846,7 +3846,7 @@
     </message>
     <message>
         <source>switch backwards</source>
-        <translation type="unfinished"></translation>
+        <translation>Girati indietro</translation>
     </message>
     <message>
         <source>change bounciness</source>
@@ -4027,7 +4027,7 @@
     </message>
     <message>
         <source>Heads-up display:</source>
-        <translation type="unfinished"></translation>
+        <translation>HUD:</translation>
     </message>
     <message>
         <source>Talk to your clan or all participants:</source>
@@ -4516,7 +4516,7 @@
     <name>credits</name>
     <message>
         <source>Programming</source>
-        <translation type="unfinished"></translation>
+        <translation>Programmatori</translation>
     </message>
     <message>
         <source>Game engine</source>
@@ -4536,7 +4536,7 @@
     </message>
     <message>
         <source>Campaign support</source>
-        <translation type="unfinished"></translation>
+        <translation>Curatori della campagna</translation>
     </message>
     <message>
         <source>Theme customization improvements</source>
@@ -4560,11 +4560,11 @@
     </message>
     <message>
         <source>Core map generators</source>
-        <translation type="unfinished"></translation>
+        <translation>Curatori del generatore centrale delle mappe</translation>
     </message>
     <message>
         <source>Perlin maps and other improvements</source>
-        <translation type="unfinished"></translation>
+        <translation>Curatori delle mappe perlin e altri miglioramenti</translation>
     </message>
     <message>
         <source>Maze maps</source>
@@ -4576,7 +4576,7 @@
     </message>
     <message>
         <source>Most core weapons</source>
-        <translation type="unfinished"></translation>
+        <translation>Curatori della maggior parte delle armi principali</translation>
     </message>
     <message>
         <source>Air mine, rubber, others</source>
@@ -4672,7 +4672,7 @@
     </message>
     <message>
         <source>Android netplay, portability abstraction</source>
-        <translation type="unfinished"></translation>
+        <translation>Curatori del gioco su network Android, Portabilità</translation>
     </message>
     <message>
         <source>WebGL port</source>
@@ -5203,7 +5203,7 @@
     </message>
     <message>
         <source>Empty config entry.</source>
-        <translation type="unfinished"></translation>
+        <translation>Ingresso di configurazione vuoto.</translation>
     </message>
     <message>
         <source>Access denied.</source>
@@ -5247,7 +5247,7 @@
     </message>
     <message>
         <source>Illegal room name! The room name must be between 1-40 characters long, must not have a trailing or leading space and must not have any of these characters: $()*+?[]^{|}</source>
-        <translation type="unfinished"></translation>
+        <translation>Nome stanza invalido! Il nome della stanza deve contenere tra gli 1 e i 40 caratteri,non deve iniziare con uno spazio e non può contenere spazi prolungati,inoltre non può contenere questi caratteri: $()*+?[]^{|}</translation>
     </message>
     <message>
         <source>A room with the same name already exists.</source>
@@ -5275,7 +5275,7 @@
     </message>
     <message>
         <source>Illegal room name! A room name must be between 1-40 characters long, must not have a trailing or leading space and must not have any of these characters: $()*+?[]^{|}</source>
-        <translation type="unfinished"></translation>
+        <translation>Nome stanza invalido! Il nome della stanza deve contenere tra gli 1 e i 40 caratteri,non deve iniziare con uno spazio e non può contenere spazi prolungati,inoltre non può contenere questi caratteri: $()*+?[]^{|}</translation>
     </message>
     <message>
         <source>No such room.</source>
@@ -5299,11 +5299,11 @@
     </message>
     <message>
         <source>Nickname already provided.</source>
-        <translation type="unfinished"></translation>
+        <translation>Nickname già fornito.</translation>
     </message>
     <message>
         <source>Illegal nickname! Nicknames must be between 1-40 characters long, must not have a trailing or leading space and must not have any of these characters: $()*+?[]^{|}</source>
-        <translation type="unfinished"></translation>
+        <translation>Nickname invalido! Il nickname deve contenere tra gli 1 e i 40 caratteri,non deve iniziare con uno spazio e non può contenere spazi prolungati,inoltre non può contenere questi caratteri: $()*+?[]^{|}</translation>
     </message>
     <message>
         <source>Protocol already known.</source>
@@ -5351,27 +5351,27 @@
     </message>
     <message>
         <source>/rnd: Flip a virtual coin and reply with &apos;heads&apos; or &apos;tails&apos;</source>
-        <translation type="unfinished"></translation>
+        <translation>/rnd: Tira una moneta virtuale e rispone con testa o croce</translation>
     </message>
     <message>
         <source>/rnd [A] [B] [C] [...]: Reply with a random word from the given list</source>
-        <translation type="unfinished"></translation>
+        <translation>/rnd [A] [B] [C] [...]: Risponde con una delle parole nella lista in modo casuale</translation>
     </message>
     <message>
         <source>/watch &lt;id&gt;: Watch a demo stored on the server with the given ID</source>
-        <translation type="unfinished"></translation>
+        <translation>/watch &lt;id&gt;: Guarda un filmato presente nel server con questo ID</translation>
     </message>
     <message>
         <source>/help: Show chat command help</source>
-        <translation type="unfinished"></translation>
+        <translation>/help: Mostra un aiuto per i comandi di chat</translation>
     </message>
     <message>
         <source>/callvote [arguments]: Start a vote</source>
-        <translation type="unfinished"></translation>
+        <translation>/callvote [arguments]: Inizia una votazione</translation>
     </message>
     <message>
         <source>/vote &lt;yes/no&gt;: Vote &apos;yes&apos; or &apos;no&apos; for active vote</source>
-        <translation type="unfinished"></translation>
+        <translation>/vote &lt;yes/no&gt;: Vota &apos;si&apos; o &apos;no&apos; per la votazione corrente</translation>
     </message>
     <message>
         <source>/delegate &lt;player&gt;: Surrender room control to player</source>
@@ -5379,7 +5379,7 @@
     </message>
     <message>
         <source>/maxteams &lt;N&gt;: Limit maximum number of teams to N</source>
-        <translation type="unfinished"></translation>
+        <translation>/maxteams &lt;N&gt;: Imposta il limite massimo di squadre a N</translation>
     </message>
     <message>
         <source>/global &lt;message&gt;: Send global chat message which can be seen by everyone on the server</source>
@@ -5387,39 +5387,39 @@
     </message>
     <message>
         <source>/registered_only: Toggle &apos;registered only&apos; state. If enabled, only registered players can join server</source>
-        <translation type="unfinished"></translation>
+        <translation>/registered_only: Abilita l&apos;opzione solo iscritti. Se attivato, solo i giocatori registrati potranno partcipare al server</translation>
     </message>
     <message>
         <source>/super_power: Activate your super power. With it you can enter any room and are protected from kicking. Expires when you leave server</source>
-        <translation type="unfinished"></translation>
+        <translation>/super_power: Attiva il tuo superpoter. Con esso puoi entrare in ogni stanza e sei protetto dall&apos;essere cacciato. L&apos;effetto termina quando ti disconnetti</translation>
     </message>
     <message>
         <source>/stats: Query server stats</source>
-        <translation type="unfinished"></translation>
+        <translation>/stats: Richiedi le statistiche del server</translation>
     </message>
     <message>
         <source>/force &lt;yes/no&gt;: Force vote result for active vote</source>
-        <translation type="unfinished"></translation>
+        <translation>/force &lt;yes/no&gt;: Forza il risultato della votazione in corso</translation>
     </message>
     <message>
         <source>/fix: Force this room to stay open when it is empty</source>
-        <translation type="unfinished"></translation>
+        <translation>/fix: Imponi a questa stanza di rimanere aperta anche quando è vuota</translation>
     </message>
     <message>
         <source>/unfix: Undo the /fix command</source>
-        <translation type="unfinished"></translation>
+        <translation>/unfix: Annulla il comando /fix</translation>
     </message>
     <message>
         <source>List of lobby chat commands:</source>
-        <translation type="unfinished"></translation>
+        <translation>Lista dei comandi di chat per la lobby:</translation>
     </message>
     <message>
         <source>List of room chat commands:</source>
-        <translation type="unfinished"></translation>
+        <translation>Lista dei comandi di chat per la stanza:</translation>
     </message>
     <message>
         <source>Commands for server admins only:</source>
-        <translation type="unfinished"></translation>
+        <translation>Comandi per gli admins:</translation>
     </message>
     <message>
         <source>room</source>
@@ -5443,11 +5443,11 @@
     </message>
     <message>
         <source>/force: Please use &apos;yes&apos; or &apos;no&apos;.</source>
-        <translation type="unfinished"></translation>
+        <translation>/force: Perfavore usa &apos;yes&apos; o &apos;no&apos;.</translation>
     </message>
     <message>
         <source>/vote: Please use &apos;yes&apos; or &apos;no&apos;.</source>
-        <translation type="unfinished"></translation>
+        <translation>/vote: Perfavore usa &apos;yes&apos; or &apos;no&apos;.</translation>
     </message>
     <message>
         <source>Kicked</source>
@@ -5471,23 +5471,23 @@
     </message>
     <message>
         <source>/greeting [message]: Set or clear greeting message to be shown to players who join the room</source>
-        <translation type="unfinished"></translation>
+        <translation>/greeting [message]: Imposta il messaggio di benvenuto che verrà mostrato ai giocatori che entrano in stanza</translation>
     </message>
     <message>
         <source>/save &lt;config ID&gt; &lt;config name&gt;: Add current room configuration as votable choice for /callvote map</source>
-        <translation type="unfinished"></translation>
+        <translation>/save &lt;config ID&gt; &lt;config name&gt;: Aggiunge a questa stanza l&apos;opzione votabile di configurazione con /callvote map</translation>
     </message>
     <message>
         <source>/delete &lt;config ID&gt;: Delete a votable room configuration</source>
-        <translation type="unfinished"></translation>
+        <translation>/delete &lt;config ID&gt;: Elimina l&apos;opzione votabile di configurazione</translation>
     </message>
     <message>
         <source>/saveroom &lt;file name&gt;: Save all votable room configurations (and the greeting) of this room into a file</source>
-        <translation type="unfinished"></translation>
+        <translation>/saveroom &lt;file name&gt;: Salva tutte le opzioni di configurazione della mappa e i messaggi di benvenuto per questa stanza in un file</translation>
     </message>
     <message>
         <source>/loadroom &lt;file name&gt;: Load votable room configurations (and greeting) from a file</source>
-        <translation type="unfinished"></translation>
+        <translation>/loadroom &lt;file name&gt;: Carica l&apos;opzione di configurazione della mappa e il messaggio di benvenuto dal file</translation>
     </message>
     <message>
         <source>Super power activated.</source>
@@ -5495,7 +5495,7 @@
     </message>
     <message>
         <source>Unknown command or invalid parameters. Say &apos;/help&apos; in chat for a list of commands.</source>
-        <translation type="unfinished"></translation>
+        <translation>Comando sconosciuto o parametri illegali. Scrivi &apos;/help&apos; nella chat per la lista dei comandi di chat.</translation>
     </message>
     <message>
         <source>You can&apos;t kick yourself!</source>
@@ -5531,11 +5531,11 @@
     </message>
     <message>
         <source>/callvote kick: This is only allowed in rooms without a room master.</source>
-        <translation type="unfinished"></translation>
+        <translation>/callvote kick: Questa opzione è valida solo nelle stanze senza un capostanza.</translation>
     </message>
     <message>
         <source>/callvote map: No maps available.</source>
-        <translation type="unfinished"></translation>
+        <translation>/callvote map: Nessuna mappa disponibile.</translation>
     </message>
     <message>
         <source>You&apos;re the new room master!</source>
@@ -5543,7 +5543,7 @@
     </message>
     <message>
         <source>/quit: Quit the server</source>
-        <translation type="unfinished"></translation>
+        <translation>/quit: Esci dal server</translation>
     </message>
     <message>
         <source>This command is only available in the lobby.</source>
@@ -5563,15 +5563,15 @@
     </message>
     <message>
         <source>Available callvote commands: hedgehogs &lt;number&gt;, pause, newseed, map &lt;name&gt;, kick &lt;player&gt;</source>
-        <translation type="unfinished"></translation>
+        <translation>Comandi di votazione disponibili:: hedgehogs &lt;number&gt;, pause, newseed, map &lt;name&gt;, kick &lt;player&gt;</translation>
     </message>
     <message>
         <source>Please confirm server restart with &apos;/restart_server yes&apos;.</source>
-        <translation type="unfinished"></translation>
+        <translation>Perfavore conferma il riavvio del server con &apos;/restart_server yes&apos;.</translation>
     </message>
     <message>
         <source>Warning! Room name change flood protection activated</source>
-        <translation type="unfinished"></translation>
+        <translation>Attenzione! Attivata la protezione per l&apos;inondazione di messagi</translation>
     </message>
 </context>
 </TS>
--- a/share/hedgewars/Data/Scripts/Multiplayer/HedgeEditor.lua	Sat Sep 28 16:39:02 2019 +0300
+++ b/share/hedgewars/Data/Scripts/Multiplayer/HedgeEditor.lua	Tue Oct 15 13:42:50 2019 -0400
@@ -3342,7 +3342,7 @@
 
 	reducedSpriteIDArrayFrames = {
 		1, 8, 4, 1, 1,
-		AmmoTypeMax, AmmoTypeMax, 3, 4, 8, 1,
+		AmmoTypeMax, AmmoTypeMax, 3, 4, 9, 1,
 		1, 1, 1, 1, 1, 1,
 		1, 1, 1, 1, 1,
 	}
--- a/share/hedgewars/Data/Scripts/Multiplayer/Mutant.lua	Sat Sep 28 16:39:02 2019 +0300
+++ b/share/hedgewars/Data/Scripts/Multiplayer/Mutant.lua	Tue Oct 15 13:42:50 2019 -0400
@@ -59,6 +59,7 @@
 local teamsDead = {}
 local teamsDeleted = {}
 local hogLimitHit = false
+local teamLimitHit = false
 local cnthhs
 
 local circles = {}
@@ -157,7 +158,7 @@
 end
 
 function limitHogsClan(gear)
-    hogLimitHit = true
+    teamLimitHit = true
     SetEffect(gear, heResurrectable, 0)
     setGearValue(gear, "excess", true)
     DeleteGear(gear)
@@ -197,8 +198,10 @@
         cnthhs = 0
         runOnHogsInTeam(limitHogsTeam, GetTeamName(i))
     end
+    if teamLimitHit then
+        WriteLnToChat(loc("Only one team per clan allowed! Excess teams will be removed."))
+    end
     if hogLimitHit then
-        -- TODO: Update warning message to include excess teams as well
         WriteLnToChat(loc("Only one hog per team allowed! Excess hogs will be removed."))
     end
     trackTeams()
Binary file share/hedgewars/Data/Sounds/dynamitefuse.ogg has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/replay2hwd.hs	Tue Oct 15 13:42:50 2019 -0400
@@ -0,0 +1,237 @@
+{-# LANGUAGE ScopedTypeVariables, OverloadedStrings #-}
+
+import qualified Data.ByteString.Char8 as B
+import Control.Exception as E
+import System.Environment
+import Control.Monad
+import qualified Data.Map as Map
+import Data.Word
+import Data.Int
+import qualified Codec.Binary.Base64 as Base64
+import qualified Data.ByteString.Lazy as BL
+import qualified Data.ByteString as BW
+import qualified Codec.Compression.Zlib.Internal as ZI
+import qualified Codec.Compression.Zlib as Z
+import qualified Data.List as L
+import qualified Data.Set as Set
+import Data.Binary
+import Data.Binary.Put
+import Data.Bits
+import Control.Arrow
+import Data.Maybe
+import qualified Data.Either as Ei
+
+
+decompressWithoutExceptions :: BL.ByteString -> BL.ByteString
+decompressWithoutExceptions = BL.fromChunks . ZI.foldDecompressStreamWithInput chunk end err decomp
+    where
+        decomp = ZI.decompressST ZI.zlibFormat ZI.defaultDecompressParams
+        chunk = (:)
+        end _ = []
+        err = const $ [BW.empty]
+
+data HedgehogInfo =
+    HedgehogInfo B.ByteString B.ByteString
+    deriving (Show, Read)
+    
+data TeamInfo =
+    TeamInfo
+    {
+        teamowner :: !B.ByteString,
+        teamname :: !B.ByteString,
+        teamcolor :: !B.ByteString,
+        teamgrave :: !B.ByteString,
+        teamfort :: !B.ByteString,
+        teamvoicepack :: !B.ByteString,
+        teamflag :: !B.ByteString,
+        isOwnerRegistered :: !Bool,
+        difficulty :: !Int,
+        hhnum :: !Int,
+        hedgehogs :: ![HedgehogInfo]
+    }
+    deriving (Show, Read)
+    
+readInt_ :: (Num a) => B.ByteString -> a
+readInt_ str =
+  case B.readInt str of
+       Just (i, t) | B.null t -> fromIntegral i
+       _                      -> 0
+
+toEngineMsg :: B.ByteString -> B.ByteString
+toEngineMsg msg = fromIntegral (BW.length msg) `BW.cons` msg
+
+em :: B.ByteString -> B.ByteString
+em = toEngineMsg
+
+eml :: [B.ByteString] -> B.ByteString
+eml = em . B.concat       
+    
+showB :: (Show a) => a -> B.ByteString
+showB = B.pack . show
+    
+replayToDemo :: [TeamInfo]
+        -> Map.Map B.ByteString B.ByteString
+        -> Map.Map B.ByteString [B.ByteString]
+        -> [B.ByteString]
+        -> B.ByteString
+replayToDemo ti mParams prms msgs = if not sane then "" else (B.concat $ concat [
+        [em "TD"]
+        , maybeScript
+        , maybeMap
+        , [eml ["etheme ", head $ prms Map.! "THEME"]]
+        , [eml ["eseed ", mParams Map.! "SEED"]]
+        , [eml ["e$gmflags ", showB gameFlags]]
+        , schemeFlags
+        , schemeAdditional
+        , [eml ["e$template_filter ", mParams Map.! "TEMPLATE"]]
+        , [eml ["e$feature_size ", mParams Map.! "FEATURE_SIZE"]]
+        , [eml ["e$mapgen ", mapgen]]
+        , mapgenSpecific
+        , concatMap teamSetup ti
+        , map (Ei.fromRight "" . Base64.decode) $ reverse msgs
+        , [em "!"]
+        ])
+    where
+        keys1, keys2 :: Set.Set B.ByteString
+        keys1 = Set.fromList ["FEATURE_SIZE", "MAP", "MAPGEN", "MAZE_SIZE", "SEED", "TEMPLATE"]
+        keys2 = Set.fromList ["AMMO", "SCHEME", "SCRIPT", "THEME"]
+        sane = Set.null (keys1 Set.\\ Map.keysSet mParams)
+            && Set.null (keys2 Set.\\ Map.keysSet prms)
+            && (not . null . drop 41 $ scheme)
+            && (not . null . tail $ prms Map.! "AMMO")
+            && ((B.length . head . tail $ prms Map.! "AMMO") > 200)
+        mapGenTypes = ["+rnd+", "+maze+", "+drawn+", "+perlin+"]
+        scriptName = head . fromMaybe ["Normal"] $ Map.lookup "SCRIPT" prms
+        maybeScript = let s = scriptName in if s == "Normal" then [] else [eml ["escript Scripts/Multiplayer/", spaces2Underlining s, ".lua"]]
+        maybeMap = let m = mParams Map.! "MAP" in if m `elem` mapGenTypes then [] else [eml ["emap ", m]]
+        scheme = tail $ prms Map.! "SCHEME"
+        mapgen = mParams Map.! "MAPGEN"
+        mazeSizeMsg = eml ["e$maze_size ", mParams Map.! "MAZE_SIZE"]
+        mapgenSpecific = case mapgen of
+            "1" -> [mazeSizeMsg]
+            "2" -> [mazeSizeMsg]
+            "3" -> let d = head . fromMaybe [""] $ Map.lookup "DRAWNMAP" prms in if BW.length d <= 4 then [] else drawnMapData d
+            _ -> []
+        gameFlags :: Word32
+        gameFlags = foldl (\r (b, f) -> if b == "false" then r else r .|. f) 0 $ zip scheme gameFlagConsts
+        schemeFlags = map (\(v, (n, m)) -> eml [n, " ", showB $ (readInt_ v) * m])
+            $ filter (\(_, (n, _)) -> not $ B.null n)
+            $ zip (drop (length gameFlagConsts) scheme) schemeParams
+        schemeAdditional = let scriptParam = B.tail $ scheme !! 42 in [eml ["e$scriptparam ", scriptParam] | not $ B.null scriptParam]
+        ammoStr :: B.ByteString
+        ammoStr = head . tail $ prms Map.! "AMMO"
+        ammo = let l = B.length ammoStr `div` 4; ((a, b), (c, d)) = (B.splitAt l . fst &&& B.splitAt l . snd) . B.splitAt (l * 2) $ ammoStr in
+                   (map (\(x, y) -> eml [x, " ", y]) $ zip ["eammloadt", "eammprob", "eammdelay", "eammreinf"] [a, b, c, d])
+                   ++ [em "eammstore" | scheme !! 14 == "true" || scheme !! 20 == "false"]
+        initHealth = scheme !! 27
+        teamSetup :: TeamInfo -> [B.ByteString]
+        teamSetup t = (++) ammo $
+                eml ["eaddteam <hash> ", showB $ (1 + (readInt_ $ teamcolor t) :: Int) * 2113696, " ", teamname t]
+                : em "erdriven"
+                : eml ["efort ", teamfort t]
+                : take (2 * hhnum t) (
+                    concatMap (\(HedgehogInfo hname hhat) -> [
+                            eml ["eaddhh ", showB $ difficulty t, " ", initHealth, " ", hname]
+                            , eml ["ehat ", hhat]
+                            ])
+                        $ hedgehogs t
+                        )
+        infRopes = ammoStr `B.index` 7  == '9'
+        vamp = gameFlags .&. 0x00000200 /= 0
+        infattacks = gameFlags .&. 0x00100000 /= 0
+        spaces2Underlining = B.map (\c -> if c == ' ' then '_' else c)
+
+drawnMapData :: B.ByteString -> [B.ByteString]
+drawnMapData =
+          L.map (\m -> eml ["edraw ", BW.pack m])
+        . L.unfoldr by200
+        . BL.unpack
+        . unpackDrawnMap
+    where
+        by200 :: [a] -> Maybe ([a], [a])
+        by200 [] = Nothing
+        by200 m = Just $ L.splitAt 200 m
+
+unpackDrawnMap :: B.ByteString -> BL.ByteString
+unpackDrawnMap = either
+        (const BL.empty) 
+        (decompressWithoutExceptions . BL.pack . drop 4 . BW.unpack)
+        . Base64.decode
+
+compressWithLength :: BL.ByteString -> BL.ByteString
+compressWithLength b = BL.drop 8 . encode . runPut $ do
+    put $ ((fromIntegral $ BL.length b)::Word32)
+    mapM_ putWord8 $ BW.unpack $ BL.toStrict $ Z.compress b
+
+packDrawnMap :: BL.ByteString -> B.ByteString
+packDrawnMap =
+      Base64.encode
+    . BL.toStrict
+    . compressWithLength
+
+prependGhostPoints :: [(Int16, Int16)] -> B.ByteString -> B.ByteString
+prependGhostPoints pts dm = packDrawnMap $ (runPut $ forM_ pts $ \(x, y) -> put x >> put y >> putWord8 99) `BL.append` unpackDrawnMap dm
+
+schemeParams :: [(B.ByteString, Int)]
+schemeParams = [
+      ("e$damagepct", 1)
+    , ("e$turntime", 1000)
+    , ("", 0)
+    , ("e$sd_turns", 1)
+    , ("e$casefreq", 1)
+    , ("e$minestime", 1000)
+    , ("e$minesnum", 1)
+    , ("e$minedudpct", 1)
+    , ("e$explosives", 1)
+    , ("e$airmines", 1)
+    , ("e$healthprob", 1)
+    , ("e$hcaseamount", 1)
+    , ("e$waterrise", 1)
+    , ("e$healthdec", 1)
+    , ("e$ropepct", 1)
+    , ("e$getawaytime", 1)
+    , ("e$worldedge", 1)
+    ]
+
+
+gameFlagConsts :: [Word32]
+gameFlagConsts = [
+          0x00001000
+        , 0x00000010
+        , 0x00000004
+        , 0x00000008
+        , 0x00000020
+        , 0x00000040
+        , 0x00000080
+        , 0x00000100
+        , 0x00000200
+        , 0x00000400
+        , 0x00000800
+        , 0x00002000
+        , 0x00004000
+        , 0x00008000
+        , 0x00010000
+        , 0x00020000
+        , 0x00040000
+        , 0x00080000
+        , 0x00100000
+        , 0x00200000
+        , 0x00400000
+        , 0x00800000
+        , 0x01000000
+        , 0x02000000
+        , 0x04000000
+        ]    
+
+loadReplay :: String -> IO (Maybe ([TeamInfo], [(B.ByteString, B.ByteString)], [(B.ByteString, [B.ByteString])], [B.ByteString]))
+loadReplay fileName = E.handle (\(e :: SomeException) -> return Nothing) $ do
+            liftM (Just . read) $ readFile fileName
+
+convert :: String -> IO ()
+convert fileName = do
+    Just (t, c1, c2, m) <- loadReplay fileName
+    B.writeFile (fileName ++ ".hwd") $ replayToDemo t (Map.fromList c1) (Map.fromList c2) m
+
+main = do
+    args <- getArgs
+    when (length args == 1) $ (convert (head args))