# HG changeset patch # User nemo # Date 1571161370 14400 # Node ID 923a6a8ae7c16b2fac943d1c1bb35a39e15dced6 # Parent e0ab70a90718d5b628027b8af0548b9db8d35413# Parent c81b6aaeccede9435dfb7941f6e3f771a3e4177e merge russian translation fixes diff -r c81b6aaecced -r 923a6a8ae7c1 .hgtags --- 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 diff -r c81b6aaecced -r 923a6a8ae7c1 .travis.yml --- 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" diff -r c81b6aaecced -r 923a6a8ae7c1 CMakeLists.txt --- 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" diff -r c81b6aaecced -r 923a6a8ae7c1 CREDITS --- 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) diff -r c81b6aaecced -r 923a6a8ae7c1 ChangeLog.txt --- 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 diff -r c81b6aaecced -r 923a6a8ae7c1 QTfrontend/CMakeLists.txt --- 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} diff -r c81b6aaecced -r 923a6a8ae7c1 QTfrontend/binds.cpp --- 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 diff -r c81b6aaecced -r 923a6a8ae7c1 QTfrontend/hedgewars.ico Binary file QTfrontend/hedgewars.ico has changed diff -r c81b6aaecced -r 923a6a8ae7c1 QTfrontend/hwform.cpp --- 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()) { diff -r c81b6aaecced -r 923a6a8ae7c1 QTfrontend/res/credits.csv --- 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", diff -r c81b6aaecced -r 923a6a8ae7c1 QTfrontend/ui/page/pagegamestats.cpp --- 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() diff -r c81b6aaecced -r 923a6a8ae7c1 QTfrontend/ui/page/pageoptions.cpp --- 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); diff -r c81b6aaecced -r 923a6a8ae7c1 QTfrontend/ui/widget/keybinder.cpp --- 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); diff -r c81b6aaecced -r 923a6a8ae7c1 QTfrontend/util/DataManager.cpp --- 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 #include -#include +#include #include "hwconsts.h" #include "HWApplication.h" diff -r c81b6aaecced -r 923a6a8ae7c1 cmake_modules/compilerchecks.cmake --- 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) diff -r c81b6aaecced -r 923a6a8ae7c1 cmake_modules/cpackvars.cmake --- 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) diff -r c81b6aaecced -r 923a6a8ae7c1 cmake_modules/revinfo.cmake --- 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) diff -r c81b6aaecced -r 923a6a8ae7c1 gameServer/CoreTypes.hs --- 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 "

https://www.hedgewars.org/

" - "

Hedgewars 0.9.25 is out! Please update.

Download page here" - 57 -- latestReleaseVersion + "

Hedgewars 1.0.0 is out! Please update.

Download page here" + 59 -- latestReleaseVersion 41 -- earliestCompatibleVersion 46631 "" diff -r c81b6aaecced -r 923a6a8ae7c1 gameServer/Utils.hs --- 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 diff -r c81b6aaecced -r 923a6a8ae7c1 hedgewars/uAI.pas --- 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) diff -r c81b6aaecced -r 923a6a8ae7c1 hedgewars/uAmmos.pas --- 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); diff -r c81b6aaecced -r 923a6a8ae7c1 hedgewars/uConsts.pas --- 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 diff -r c81b6aaecced -r 923a6a8ae7c1 hedgewars/uGears.pas --- 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 diff -r c81b6aaecced -r 923a6a8ae7c1 hedgewars/uGearsHandlersMess.pas --- 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 diff -r c81b6aaecced -r 923a6a8ae7c1 hedgewars/uGearsRender.pas --- 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); diff -r c81b6aaecced -r 923a6a8ae7c1 hedgewars/uSound.pas --- 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); diff -r c81b6aaecced -r 923a6a8ae7c1 hedgewars/uStats.pas --- 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) diff -r c81b6aaecced -r 923a6a8ae7c1 hedgewars/uTeams.pas --- 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 diff -r c81b6aaecced -r 923a6a8ae7c1 hedgewars/uTypes.pas --- 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 ( 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; diff -r c81b6aaecced -r 923a6a8ae7c1 rust/hedgewars-server/src/core/indexslab.rs --- 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 { + pub fn iter(&self) -> impl Iterator + Clone { self.data .iter() .enumerate() diff -r c81b6aaecced -r 923a6a8ae7c1 rust/hedgewars-server/src/core/server.rs --- 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 = slab::Slab; +#[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, pub protocol_number: Option, 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 { - 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, - ) -> 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 + 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 + 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, 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, +fn create_room<'a, 'b>( + client: &'a mut HwClient, + rooms: &'b mut Slab, name: String, password: Option, -) -> 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) { diff -r c81b6aaecced -r 923a6a8ae7c1 rust/hedgewars-server/src/handlers.rs --- 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); } } } diff -r c81b6aaecced -r 923a6a8ae7c1 rust/hedgewars-server/src/handlers/common.rs --- 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 + 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 + where + I: Iterator, + 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, diff -r c81b6aaecced -r 923a6a8ae7c1 rust/hedgewars-server/src/handlers/inlobby.rs --- 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("".to_string()); - for protocol in protocols { - html.push(format!( - "", - super::utils::protocol_version_string(protocol), - server.protocol_clients(protocol).count(), - server.protocol_rooms(protocol).count() - )); + html.push("
{}{}{}
".to_string()); + for protocol in protocols { + html.push(format!( + "", + super::utils::protocol_version_string(protocol), + server.protocol_clients(protocol).count(), + server.protocol_rooms(protocol).count() + )); + } + html.push("
{}{}{}
".to_string()); + + response.add(Warning(html.join("")).send_self()); } - html.push("".to_string()); - - response.add(Warning(html.join("")).send_self()); - } + }, List => warn!("Deprecated LIST message received"), _ => warn!("Incorrect command in lobby state"), } diff -r c81b6aaecced -r 923a6a8ae7c1 rust/hedgewars-server/src/handlers/inroom.rs --- 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()), }, }; diff -r c81b6aaecced -r 923a6a8ae7c1 rust/hedgewars-server/src/handlers/strings.rs --- /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!"; diff -r c81b6aaecced -r 923a6a8ae7c1 rust/hedgewars-server/src/protocol.rs --- 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)) => { diff -r c81b6aaecced -r 923a6a8ae7c1 rust/hedgewars-server/src/utils.rs --- 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", } } diff -r c81b6aaecced -r 923a6a8ae7c1 share/hedgewars/Data/Graphics/AmmoMenu/TurnsLeft.png Binary file share/hedgewars/Data/Graphics/AmmoMenu/TurnsLeft.png has changed diff -r c81b6aaecced -r 923a6a8ae7c1 share/hedgewars/Data/Graphics/dynamiteDefused.png Binary file share/hedgewars/Data/Graphics/dynamiteDefused.png has changed diff -r c81b6aaecced -r 923a6a8ae7c1 share/hedgewars/Data/Locale/de.txt --- 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 diff -r c81b6aaecced -r 923a6a8ae7c1 share/hedgewars/Data/Locale/en.txt --- 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) diff -r c81b6aaecced -r 923a6a8ae7c1 share/hedgewars/Data/Locale/hedgewars_it.ts --- 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 @@ Dependency versions: For the version numbers of Hedgewars' software dependencies - + Versioni di riferimento: <a href="https://gcc.gnu.org">GCC</a>: %1 - + <a href="https://gcc.gnu.org">GCC</a>: %1 <a href="https://www.libsdl.org/">SDL2</a>: %1.%2.%3 - + <a href="https://www.libsdl.org/">SDL2</a>: %1.%2.%3 <a href="https://www.libsdl.org/">SDL2_mixer</a>: %1.%2.%3 - + <a href="https://www.libsdl.org/">SDL2_mixer</a>: %1.%2.%3 <a href="https://www.libsdl.org/">SDL2_net</a>: %1.%2.%3 - + <a href="https://www.libsdl.org/">SDL2_net</a>: %1.%2.%3 <a href="https://www.libsdl.org/">SDL2_image</a>: %1.%2.%3 - + <a href="https://www.libsdl.org/">SDL2_image</a>: %1.%2.%3 <a href="https://www.libsdl.org/">SDL2_ttf</a>: %1.%2.%3 - + <a href="https://www.libsdl.org/">SDL2_ttf</a>: %1.%2.%3 <a href="https://www.qt.io/developers/">Qt</a>: %1 - + <a href="https://www.qt.io/developers/">Qt</a>: %1 <a href="https://libav.org">libavcodec</a>: %1.%2.%3 - + <a href="https://libav.org">libavcodec</a>: %1.%2.%3 <a href="https://libav.org">libavformat</a>: %1.%2.%3 - + <a href="https://libav.org">libavformat</a>: %1.%2.%3 <a href="https://libav.org">libavutil</a>: %1.%2.%3 - + <a href="https://libav.org">libavutil</a>: %1.%2.%3 <a href="https://icculus.org/physfs/">PhysFS</a>: %1.%2.%3 - + <a href="https://icculus.org/physfs/">PhysFS</a>: %1.%2.%3 Credits @@ -88,22 +88,22 @@ %1 (alias %2) - + %1 (alias %2) %1 &lt;%2&gt; Part of credits. %1: Contributor name. %2: E-mail address - + %1 &lt;%2&gt; %1: %2 Part of credits. %1: Description of contribution. %2: Contributor name - + %1: %2 %1: %2 &lt;%3&gt; Part of credits. %1: Description of contribution. %2: Contributor name. %3: E-mail address - + %1: %2 &lt;%3&gt; Extended Credits @@ -115,7 +115,7 @@ <a href="https://visualstudio.microsoft.com">VC++</a>: %1 - + <a href="https://visualstudio.microsoft.com">VC++</a>: %1 Unknown Compiler: %1 @@ -1449,15 +1449,15 @@ The best killer is <b>%1</b> with <b>%2</b> kills in a turn. - <p>Il miglior killer è <b>%1</b> con <b>%2</b> uccisione in un turno.</p> - <p>Il miglior killer è <b>%1</b> con <b>%2</b> uccisioni in un turno.</p> + Il miglior killer è <b>%1</b> con <b>%2</b> uccisione in un turno. + Il miglior killer è <b>%1</b> con <b>%2</b> uccisioni in un turno. A total of <b>%1</b> hedgehog(s) were killed during this round. - <p>Durante questo round è stato ucciso <b>%1</b> riccio in totale.</p> - <p>Durante questo round sono stati uccisi <b>%1</b> ricci in totale.</p> + Durante questo round è stato ucciso <b>%1</b> riccio in totale. + Durante questo round sono stati uccisi <b>%1</b> ricci in totale. @@ -3846,7 +3846,7 @@ switch backwards - + Girati indietro change bounciness @@ -4027,7 +4027,7 @@ Heads-up display: - + HUD: Talk to your clan or all participants: @@ -4516,7 +4516,7 @@ credits Programming - + Programmatori Game engine @@ -4536,7 +4536,7 @@ Campaign support - + Curatori della campagna Theme customization improvements @@ -4560,11 +4560,11 @@ Core map generators - + Curatori del generatore centrale delle mappe Perlin maps and other improvements - + Curatori delle mappe perlin e altri miglioramenti Maze maps @@ -4576,7 +4576,7 @@ Most core weapons - + Curatori della maggior parte delle armi principali Air mine, rubber, others @@ -4672,7 +4672,7 @@ Android netplay, portability abstraction - + Curatori del gioco su network Android, Portabilità WebGL port @@ -5203,7 +5203,7 @@ Empty config entry. - + Ingresso di configurazione vuoto. Access denied. @@ -5247,7 +5247,7 @@ 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: $()*+?[]^{|} - + 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: $()*+?[]^{|} A room with the same name already exists. @@ -5275,7 +5275,7 @@ 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: $()*+?[]^{|} - + 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: $()*+?[]^{|} No such room. @@ -5299,11 +5299,11 @@ Nickname already provided. - + Nickname già fornito. 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: $()*+?[]^{|} - + 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: $()*+?[]^{|} Protocol already known. @@ -5351,27 +5351,27 @@ /rnd: Flip a virtual coin and reply with 'heads' or 'tails' - + /rnd: Tira una moneta virtuale e rispone con testa o croce /rnd [A] [B] [C] [...]: Reply with a random word from the given list - + /rnd [A] [B] [C] [...]: Risponde con una delle parole nella lista in modo casuale /watch <id>: Watch a demo stored on the server with the given ID - + /watch <id>: Guarda un filmato presente nel server con questo ID /help: Show chat command help - + /help: Mostra un aiuto per i comandi di chat /callvote [arguments]: Start a vote - + /callvote [arguments]: Inizia una votazione /vote <yes/no>: Vote 'yes' or 'no' for active vote - + /vote <yes/no>: Vota 'si' o 'no' per la votazione corrente /delegate <player>: Surrender room control to player @@ -5379,7 +5379,7 @@ /maxteams <N>: Limit maximum number of teams to N - + /maxteams <N>: Imposta il limite massimo di squadre a N /global <message>: Send global chat message which can be seen by everyone on the server @@ -5387,39 +5387,39 @@ /registered_only: Toggle 'registered only' state. If enabled, only registered players can join server - + /registered_only: Abilita l'opzione solo iscritti. Se attivato, solo i giocatori registrati potranno partcipare al server /super_power: Activate your super power. With it you can enter any room and are protected from kicking. Expires when you leave server - + /super_power: Attiva il tuo superpoter. Con esso puoi entrare in ogni stanza e sei protetto dall'essere cacciato. L'effetto termina quando ti disconnetti /stats: Query server stats - + /stats: Richiedi le statistiche del server /force <yes/no>: Force vote result for active vote - + /force <yes/no>: Forza il risultato della votazione in corso /fix: Force this room to stay open when it is empty - + /fix: Imponi a questa stanza di rimanere aperta anche quando è vuota /unfix: Undo the /fix command - + /unfix: Annulla il comando /fix List of lobby chat commands: - + Lista dei comandi di chat per la lobby: List of room chat commands: - + Lista dei comandi di chat per la stanza: Commands for server admins only: - + Comandi per gli admins: room @@ -5443,11 +5443,11 @@ /force: Please use 'yes' or 'no'. - + /force: Perfavore usa 'yes' o 'no'. /vote: Please use 'yes' or 'no'. - + /vote: Perfavore usa 'yes' or 'no'. Kicked @@ -5471,23 +5471,23 @@ /greeting [message]: Set or clear greeting message to be shown to players who join the room - + /greeting [message]: Imposta il messaggio di benvenuto che verrà mostrato ai giocatori che entrano in stanza /save <config ID> <config name>: Add current room configuration as votable choice for /callvote map - + /save <config ID> <config name>: Aggiunge a questa stanza l'opzione votabile di configurazione con /callvote map /delete <config ID>: Delete a votable room configuration - + /delete <config ID>: Elimina l'opzione votabile di configurazione /saveroom <file name>: Save all votable room configurations (and the greeting) of this room into a file - + /saveroom <file name>: Salva tutte le opzioni di configurazione della mappa e i messaggi di benvenuto per questa stanza in un file /loadroom <file name>: Load votable room configurations (and greeting) from a file - + /loadroom <file name>: Carica l'opzione di configurazione della mappa e il messaggio di benvenuto dal file Super power activated. @@ -5495,7 +5495,7 @@ Unknown command or invalid parameters. Say '/help' in chat for a list of commands. - + Comando sconosciuto o parametri illegali. Scrivi '/help' nella chat per la lista dei comandi di chat. You can't kick yourself! @@ -5531,11 +5531,11 @@ /callvote kick: This is only allowed in rooms without a room master. - + /callvote kick: Questa opzione è valida solo nelle stanze senza un capostanza. /callvote map: No maps available. - + /callvote map: Nessuna mappa disponibile. You're the new room master! @@ -5543,7 +5543,7 @@ /quit: Quit the server - + /quit: Esci dal server This command is only available in the lobby. @@ -5563,15 +5563,15 @@ Available callvote commands: hedgehogs <number>, pause, newseed, map <name>, kick <player> - + Comandi di votazione disponibili:: hedgehogs <number>, pause, newseed, map <name>, kick <player> Please confirm server restart with '/restart_server yes'. - + Perfavore conferma il riavvio del server con '/restart_server yes'. Warning! Room name change flood protection activated - + Attenzione! Attivata la protezione per l'inondazione di messagi diff -r c81b6aaecced -r 923a6a8ae7c1 share/hedgewars/Data/Scripts/Multiplayer/HedgeEditor.lua --- 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, } diff -r c81b6aaecced -r 923a6a8ae7c1 share/hedgewars/Data/Scripts/Multiplayer/Mutant.lua --- 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() diff -r c81b6aaecced -r 923a6a8ae7c1 share/hedgewars/Data/Sounds/dynamitefuse.ogg Binary file share/hedgewars/Data/Sounds/dynamitefuse.ogg has changed diff -r c81b6aaecced -r 923a6a8ae7c1 tools/replay2hwd.hs --- /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 ", 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))