# HG changeset patch # User sheepluva # Date 1593953624 -7200 # Node ID 74ede02bc8822558ba34dfb547d626792a9a634a # Parent dc965b82ed1341c8dae25e1662adc67193b43600# Parent b3c9f5463ceecdd8bef211540104db1472cdb3aa [NOOP] Merge branch ui-scaling (with backports) into default to avoid future merging issues diff -r b3c9f5463cee -r 74ede02bc882 .hgtags --- a/.hgtags Sun Jul 05 02:03:08 2020 +0200 +++ b/.hgtags Sun Jul 05 14:53:44 2020 +0200 @@ -89,4 +89,4 @@ afc089c39556bdd895892fa36ed47aaec83c3825 0.9.24.1-release 195208deff1dd3e22d303d4a92c2ba14be3b6623 Hedgewars-iOS-2.1 5e28098fb59379357a145b73380a1cd3839f643f 0.9.25-release -88770c206c31dfdebf67271c0c980312a752eb84 1.0.0-release +3102d95a870e61385ee6951e30dc3be739210093 1.0.0-release diff -r b3c9f5463cee -r 74ede02bc882 .travis.yml --- a/.travis.yml Sun Jul 05 02:03:08 2020 +0200 +++ b/.travis.yml Sun Jul 05 14:53:44 2020 +0200 @@ -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 b3c9f5463cee -r 74ede02bc882 CMakeLists.txt --- a/CMakeLists.txt Sun Jul 05 02:03:08 2020 +0200 +++ b/CMakeLists.txt Sun Jul 05 14:53:44 2020 +0200 @@ -91,8 +91,8 @@ #versioning set(CPACK_PACKAGE_VERSION_MAJOR 1) set(CPACK_PACKAGE_VERSION_MINOR 0) -set(CPACK_PACKAGE_VERSION_PATCH 0) -set(HEDGEWARS_PROTO_VER 59) +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() diff -r b3c9f5463cee -r 74ede02bc882 CREDITS --- a/CREDITS Sun Jul 05 02:03:08 2020 +0200 +++ b/CREDITS Sun Jul 05 14:53:44 2020 +0200 @@ -16,24 +16,42 @@ ========== = HATS ========== -- Robinator -> Terminator_Glasses (2010) +- Tiyuri -> Samurai (2008), WhySoSerious (2008) +- Palewolf -> spartan (2008), RobinHood (2008), clown* (2008), fr_orange (2008), fr_lemon (2008), fr_apple (2008), fr_banana (2008), pirate_jack (2008), pirate_jack_bandana (2008), kiss_* (2008), ushanka (2008), royalguard (2008), scif_swStormtrooper (2008), IndianChief (2008), beefeater (2008) +- joshua -> Bandit (2008) +- DrDickens -> poke_slowpoke (2008), mv_Venom (2008), thug (2009), Eva_00b (2009), Eva_00y (2009) +- alzen -> poke_pikachu (2008) +- Zippy -> Jason (2008), ntd_Falcon (2009) +- Howdy -> scif_Geordi (2009) +- Zept -> Ninja* (2009) +- acegikmo -> anzac (2009) +- Fwirt -> quotecap (2009) +- PhilPhil -> angel (2009) +- em3 -> 4gsuif (2009) +- Armagon -> ntd_Link (2010) +- Robinator -> Terminator_Glasses (2010), chuckl (2010) - shingo666 -> ntd_Samus (2010) -- MeinCookie95 -> InfernalHorns (2010), Mummy (2010), war_* (2010-2011) +- MeinCookie95 -> InfernalHorns (2010), Mummy (2010), vampirichog (2010), hogpharaoh (2010), bobby (2010), bobby2v (2011), war_* (2010-2011) - thuban -> Elvis (2010) -- Miphica -> Disguise (2010) +- Miphica -> Disguise (2010), zoo_Bat (2011) +- maqui -> zoo_Beaver (2010) - Blayde -> zoo_Deer (2010), zoo_Moose (2010) - hillis -> AkuAku (2010) -- Lortinak -> OldMan (2010), ShortHair_* (2010) -- chujoii -> scif_BrainSlug (2010), scif_BrainSlug2 (2010), Dragon (2010), dish_Ladle (2010), Laminaria (2010), Pantsu (2010), zoo_Pig (2010), Plunger (2010), dish_SauceBoatSilver (2010), ShaggyYeti (2010), Sleepwalker (2010), SunWukong (2010), dish_Teapot (2010), dish_Teacup (2010), Zombi (2010) -- Randy Broda -> cyclops (2011), TeamSoldier (2011) +- assassin_killer -> spcartman (2011), spkenny (2011), spkyle (2011), spstan (2011) +- Lortinak -> OldMan (2010), ShortHair_* (2010), sth_Shadow (2010), sth_Super (2010), sth_Metal (2010) +- Grunzer -> zoo_Porkey (2010) +- chujoii -> bubble (2012), car (2012), DayAndNight (2012), dish_Ladle (2010), dish_SauceBoatSilver (2010), dish_Teacup (2010), dish_Teapot (2010), Dauber (2012), Dragon (2010), lamp (2012), Laminaria (2010), Pantsu (2010), Plunger (2010), mechanicaltoy (2012), noface (2012), Sleepwalker (2010), ShaggyYeti (2010), scif_BrainSlug (2010), scif_BrainSlug2 (2010), scif_cosmonaut (2010), ShaggyYeti (2010), Sleepwalker (2010), SunWukong (2012), Zombi (2010), zoo_chicken (2012), zoo_elephant (2012), zoo_fish (2012), zoo_frog (2012), zoo_Pig (2010), zoo_snail (2012), zoo_turtle (2012) +- Randy Broda -> cyclops (2011), TeamSoldier (2011), Joker (2012), Evil (2012) - Zav -> zoo_octopus (2009) -- Star and Moon -> bishop (2011) +- Star and Moon -> bishop (2011), metalband (2011), Meteorhelmet (2013), tf_scout (2013), tf_demoman (2013) +- YoukaiCountry -> touhou_* (2011) - Gimo -> leprechaun [based on tophats] (2011) -- Terrington_Snyde -> pirate_eyepatch (2013), jester (2013) +- Terrington_Snyde -> pirate_eyepatch (2013), jester (2013), snorkel (2013), nurse (2013), doctor (2013), constructor (2013), punkman (2013) - Wohlstand -> policegirl [based on policecap and sm_daisy] (2014) - TheMadCharles -> barrelhider (CC BY 3.0) (2015) -- Trey Perry -> Other hats -- alfadur -> zoo_crocodile (2019) +- alfadur -> zoo_crocodile (2019), zoo_panda (2020) +- Trey Perry -> Some other hats + ========== = GRAVES @@ -83,6 +101,10 @@ 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/ +- Dynamite bounce: Based off sound by kev_durr (CC BY 3.0) + https://freesound.org/people/kev_durr/sounds/426455/ - 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 b3c9f5463cee -r 74ede02bc882 ChangeLog.txt --- a/ChangeLog.txt Sun Jul 05 02:03:08 2020 +0200 +++ b/ChangeLog.txt Sun Jul 05 14:53:44 2020 +0200 @@ -1,9 +1,54 @@ + features * bugfixes -======================= ui-scaling backport to 1.0.0 ======================== +==================== 1.0.1-dev ===================== +Gameplay: + + Minigun push is now much stronger + + Easier to push hogs around with blowtorch + + Backjumps are now a bit easier + + Teach computer players how to ... + + - use drill strike, piano strike, air mine, cleaver, seduction, laser sight + + - use mine strike (0 seconds only) + + - use RC plane (very basic) + + - drop mines from a cliff + + Low level computer players are more inaccurate with guns + + Computer player now takes Strong Wind game modifier into account + + Various small computer player improvements + + New taunt chat commands: /bubble, /happy + * Fix many projectiles not being affected by Heavy Wind after turn end + * Fix hog getting stuck when opening parachute right after a shoryuken digging through land + * Fix game hanging if computer hog has nothing to attack + +Campaigns: + + A Space Adventure: Spacetrip: Meteorite appears blown-up after victory + * A Classic Fairytale: Mission 1: Fix possibility of getting stuck in “Leap of Faith” section + * A Space Adventure: The First Stop: Fix broken victory condition when eliminating minions + * A Space Adventure: Killing the Specialists: Don't award player health if enemy hurts itself + +Styles: + + Racer: Allow to set turn time in game scheme + + Racer: Reset mines, air mines and sticky mines every turn + * Racer: Resize waypoints in custom-sized drawn maps + * Mutant: Fix impossible to become mutant after mutant is gone + +Content: + + New flags: serbia, montenegro + + New hat: zoo_panda + Graphics / user interface: - + In-Game chat size can now be adjusted. Hold Ctrl and press -, + or = while in chat input. Hold shift for finer control. - + The intial in-game chat size can be configured in the Frontend's "advanced" settings tab. + + Add dynamite fuse and impact sounds + + Themes: Add fade-in and fade-out effects for background flakes + + Themes: Make Sudden Death jellyfish in Underwater theme rise + + In-Game chat size can now be adjusted. Hold Ctrl and press -, + or = while in chat input. Hold Shift for finer control + + The intial in-game chat size can be configured in the Frontend's “Video” settings tab + + Various small HUD tweaks + +Frontend: + + Sort ammos in weapon scheme editor + * Fix weapon schemes sometimes not being saved properly + * Fix world edge not being changable under macOS + +Lua: + + Add RopeKnocking library ====================== 1.0.0 ======================= Highlights: diff -r b3c9f5463cee -r 74ede02bc882 QTfrontend/CMakeLists.txt --- a/QTfrontend/CMakeLists.txt Sun Jul 05 02:03:08 2020 +0200 +++ b/QTfrontend/CMakeLists.txt Sun Jul 05 14:53:44 2020 +0200 @@ -13,9 +13,9 @@ include(CheckLibraryExists) -find_package(SDL2 REQUIRED) +find_package(SDL2 REQUIRED CONFIG) find_package(SDL2_mixer 2 REQUIRED) #audio in SDLInteraction -include_directories(${SDL2_INCLUDE_DIR}) +include_directories(${SDL2_INCLUDE_DIRS}) include_directories(${SDL2_MIXER_INCLUDE_DIRS}) if(LIBAV_FOUND) @@ -230,12 +230,12 @@ ) list(APPEND HW_LINK_LIBS - ${SDL2_LIBRARY} + ${SDL2_LIBRARIES} ${SDL2_MIXER_LIBRARIES} ) if(WIN32 AND NOT UNIX) - if(NOT SDL2_LIBRARY) + if(NOT SDL2_LIBRARIES) list(APPEND HW_LINK_LIBS SDL2) endif() diff -r b3c9f5463cee -r 74ede02bc882 QTfrontend/binds.cpp --- a/QTfrontend/binds.cpp Sun Jul 05 02:03:08 2020 +0200 +++ b/QTfrontend/binds.cpp Sun Jul 05 14:53:44 2020 +0200 @@ -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 b3c9f5463cee -r 74ede02bc882 QTfrontend/campaign.cpp --- a/QTfrontend/campaign.cpp Sun Jul 05 02:03:08 2020 +0200 +++ b/QTfrontend/campaign.cpp Sun Jul 05 14:53:44 2020 +0200 @@ -57,25 +57,29 @@ QSettings* teamfile = getCampTeamFile(campaignName, teamName); int progress = teamfile->value("Campaign " + campaignName + "/Progress", 0).toInt(); int unlockedMissions = teamfile->value("Campaign " + campaignName + "/UnlockedMissions", 0).toInt(); - // The CowardMode cheat unlocks all campaign missions, - // but as "punishment", none of them will be marked as completed. + QSettings campfile("physfs://Missions/Campaign/" + campaignName + "/campaign.ini", QSettings::IniFormat, 0); + campfile.setIniCodec("UTF-8"); + int totalMissions = campfile.value("MissionNum", 1).toInt(); + // The CowardMode cheat unlocks all campaign missions. // Added to make it easier to test campaigns. bool cheat = teamfile->value("Team/CowardMode", false).toBool(); - if(cheat) + if(progress>0 && unlockedMissions==0) { - return false; - } - else if(progress>0 && unlockedMissions==0) - { - QSettings campfile("physfs://Missions/Campaign/" + campaignName + "/campaign.ini", QSettings::IniFormat, 0); - campfile.setIniCodec("UTF-8"); - int totalMissions = campfile.value("MissionNum", 1).toInt(); - return (progress > (progress - missionInList)) || (progress >= totalMissions); + int maxMission; + if(cheat) + maxMission = totalMissions - (missionInList + 1); + else + maxMission = progress - missionInList; + return (progress > maxMission) || (progress >= totalMissions); } else if(unlockedMissions>0) { int fileMissionId = missionInList + 1; - int actualMissionId = teamfile->value(QString("Campaign %1/Mission%2").arg(campaignName, QString::number(fileMissionId)), false).toInt(); + int actualMissionId; + if(cheat) + actualMissionId = totalMissions - missionInList; + else + actualMissionId = teamfile->value(QString("Campaign %1/Mission%2").arg(campaignName, QString::number(fileMissionId)), false).toInt(); return teamfile->value(QString("Campaign %1/Mission%2Won").arg(campaignName, QString::number(actualMissionId)), false).toBool(); } else @@ -87,8 +91,7 @@ { QSettings* teamfile = getCampTeamFile(campaignName, teamName); bool won = teamfile->value("Campaign " + campaignName + "/Won", false).toBool(); - bool cheat = teamfile->value("Team/CowardMode", false).toBool(); - return won && !cheat; + return won; } QSettings* getCampMetaInfo() diff -r b3c9f5463cee -r 74ede02bc882 QTfrontend/hedgewars.ico Binary file QTfrontend/hedgewars.ico has changed diff -r b3c9f5463cee -r 74ede02bc882 QTfrontend/hwconsts.cpp.in --- a/QTfrontend/hwconsts.cpp.in Sun Jul 05 02:03:08 2020 +0200 +++ b/QTfrontend/hwconsts.cpp.in Sun Jul 05 14:53:44 2020 +0200 @@ -54,6 +54,8 @@ QString * cEmptyAmmoStore = new QString( AMMOLINE_EMPTY_QT AMMOLINE_EMPTY_PROB AMMOLINE_EMPTY_DELAY AMMOLINE_EMPTY_CRATE ); int cAmmoNumber = cDefaultAmmoStore->size() / 4; +unsigned int ammoMenuAmmos[] = HW_AMMOMENU_ARRAY; +int cAmmoMenuRows = 6; QList< QPair > cDefaultAmmos = QList< QPair >() diff -r b3c9f5463cee -r 74ede02bc882 QTfrontend/hwconsts.h --- a/QTfrontend/hwconsts.h Sun Jul 05 02:03:08 2020 +0200 +++ b/QTfrontend/hwconsts.h Sun Jul 05 14:53:44 2020 +0200 @@ -51,6 +51,8 @@ extern QStringList cQuickGameMaps; extern unsigned int colors[]; +extern unsigned int ammoMenuAmmos[]; +extern int cAmmoMenuRows; extern QString * netHost; extern quint16 netPort; @@ -119,3 +121,18 @@ 0xffffff01, /* yellow */ \ /* add new colors here */ \ 0 } + +/* The ammo types, sorted in the same way as in the ammo menu */ +#define HW_AMMOMENU_ARRAY {\ + 3, 4, 22, 29, 51, 55,\ + 1, 2, 26, 27, 40, 44,\ + 5, 10, 38, 45, 54, 59,\ + 12, 13, 14, 23, 25, 48,\ + 9, 11, 24, 30, 31, 47,\ + 16, 17, 28, 43, 50, 57,\ + 6, 18, 19, 46, 53, 56,\ + 8, 15, 20, 39, 41, 42,\ + 34, 36, 37, 49, 52, 58,\ + 7, 21, 32, 33, 35, 60\ +} + diff -r b3c9f5463cee -r 74ede02bc882 QTfrontend/hwform.cpp --- a/QTfrontend/hwform.cpp Sun Jul 05 02:03:08 2020 +0200 +++ b/QTfrontend/hwform.cpp Sun Jul 05 14:53:44 2020 +0200 @@ -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 b3c9f5463cee -r 74ede02bc882 QTfrontend/model/gameSchemeModel.cpp --- a/QTfrontend/model/gameSchemeModel.cpp Sun Jul 05 02:03:08 2020 +0200 +++ b/QTfrontend/model/gameSchemeModel.cpp Sun Jul 05 14:53:44 2020 +0200 @@ -63,14 +63,15 @@ << QVariant(0) // mine dud pct 33 << QVariant(2) // explosives 34 << QVariant(0) // air mines 35 - << QVariant(35) // health case pct 36 - << QVariant(25) // health case amt 37 - << QVariant(47) // water rise amt 38 - << QVariant(5) // health dec amt 39 - << QVariant(100) // rope modfier 40 - << QVariant(100) // get away time 41 - << QVariant(0) // world edge 42 - << QVariant() // scriptparam 43 + << QVariant(0) // sentries 36 + << QVariant(35) // health case pct 37 + << QVariant(25) // health case amt 38 + << QVariant(47) // water rise amt 39 + << QVariant(5) // health dec amt 40 + << QVariant(100) // rope modfier 41 + << QVariant(100) // get away time 42 + << QVariant(0) // world edge 43 + << QVariant() // scriptparam 44 ; GameSchemeModel::GameSchemeModel(QObject* parent, const QString & directory) : @@ -134,14 +135,15 @@ << "minedudpct" // 33 << "explosives" // 34 << "airmines" // 35 - << "healthprobability" // 36 - << "healthcaseamount" // 37 - << "waterrise" // 38 - << "healthdecrease" // 39 - << "ropepct" // 40 - << "getawaytime" // 41 - << "worldedge" // 42 - << "scriptparam" // scriptparam 43 + << "sentries" // 36 + << "healthprobability" // 37 + << "healthcaseamount" // 38 + << "waterrise" // 39 + << "healthdecrease" // 40 + << "ropepct" // 41 + << "getawaytime" // 42 + << "worldedge" // 43 + << "scriptparam" // scriptparam 44 ; QList proMode; @@ -182,14 +184,15 @@ << QVariant(0) // mine dud pct 33 << QVariant(2) // explosives 34 << QVariant(0) // air mines 35 - << QVariant(35) // health case pct 36 - << QVariant(25) // health case amt 37 - << QVariant(47) // water rise amt 38 - << QVariant(5) // health dec amt 39 - << QVariant(100) // rope modfier 40 - << QVariant(100) // get away time 41 - << QVariant(0) // world edge 42 - << QVariant() // scriptparam 43 + << QVariant(0) // sentries 36 + << QVariant(35) // health case pct 37 + << QVariant(25) // health case amt 38 + << QVariant(47) // water rise amt 39 + << QVariant(5) // health dec amt 40 + << QVariant(100) // rope modfier 41 + << QVariant(100) // get away time 42 + << QVariant(0) // world edge 43 + << QVariant() // scriptparam 44 ; QList shoppa; @@ -230,14 +233,15 @@ << QVariant(0) // mine dud pct 33 << QVariant(0) // explosives 34 << QVariant(0) // air mines 35 - << QVariant(0) // health case pct 36 - << QVariant(25) // health case amt 37 - << QVariant(0) // water rise amt 38 - << QVariant(0) // health dec amt 39 - << QVariant(100) // rope modfier 40 - << QVariant(100) // get away time 41 - << QVariant(0) // world edge 42 - << QVariant() // scriptparam 43 + << QVariant(0) // sentries 36 + << QVariant(0) // health case pct 37 + << QVariant(25) // health case amt 38 + << QVariant(0) // water rise amt 39 + << QVariant(0) // health dec amt 40 + << QVariant(100) // rope modfier 41 + << QVariant(100) // get away time 42 + << QVariant(0) // world edge 43 + << QVariant() // scriptparam 44 ; QList cleanslate; @@ -278,14 +282,15 @@ << QVariant(0) // mine dud pct 33 << QVariant(2) // explosives 34 << QVariant(0) // air mines 35 - << QVariant(35) // health case pct 36 - << QVariant(25) // health case amt 37 - << QVariant(47) // water rise amt 38 - << QVariant(5) // health dec amt 39 - << QVariant(100) // rope modfier 40 - << QVariant(100) // get away time 41 - << QVariant(0) // world edge 42 - << QVariant() // scriptparam 43 + << QVariant(0) // sentries 36 + << QVariant(35) // health case pct 37 + << QVariant(25) // health case amt 38 + << QVariant(47) // water rise amt 39 + << QVariant(5) // health dec amt 40 + << QVariant(100) // rope modfier 41 + << QVariant(100) // get away time 42 + << QVariant(0) // world edge 43 + << QVariant() // scriptparam 44 ; QList minefield; @@ -326,14 +331,15 @@ << QVariant(0) // mine dud pct 33 << QVariant(0) // explosives 34 << QVariant(0) // air mines 35 - << QVariant(35) // health case pct 36 - << QVariant(25) // health case amt 37 - << QVariant(47) // water rise amt 38 - << QVariant(5) // health dec amt 39 - << QVariant(100) // rope modfier 40 - << QVariant(100) // get away time 41 - << QVariant(0) // world edge 42 - << QVariant() // scriptparam 43 + << QVariant(0) // sentries 36 + << QVariant(35) // health case pct 37 + << QVariant(25) // health case amt 38 + << QVariant(47) // water rise amt 39 + << QVariant(5) // health dec amt 40 + << QVariant(100) // rope modfier 41 + << QVariant(100) // get away time 42 + << QVariant(0) // world edge 43 + << QVariant() // scriptparam 44 ; QList barrelmayhem; @@ -374,14 +380,15 @@ << QVariant(0) // mine dud pct 33 << QVariant(200) // explosives 34 << QVariant(0) // air mines 35 - << QVariant(35) // health case pct 36 - << QVariant(25) // health case amt 37 - << QVariant(47) // water rise amt 38 - << QVariant(5) // health dec amt 39 - << QVariant(100) // rope modfier 40 - << QVariant(100) // get away time 41 - << QVariant(0) // world edge 42 - << QVariant() // scriptparam 43 + << QVariant(0) // sentries 36 + << QVariant(35) // health case pct 37 + << QVariant(25) // health case amt 38 + << QVariant(47) // water rise amt 39 + << QVariant(5) // health dec amt 40 + << QVariant(100) // rope modfier 41 + << QVariant(100) // get away time 42 + << QVariant(0) // world edge 43 + << QVariant() // scriptparam 44 ; QList tunnelhogs; @@ -422,14 +429,15 @@ << QVariant(10) // mine dud pct 33 << QVariant(10) // explosives 34 << QVariant(4) // air mines 35 - << QVariant(35) // health case pct 36 - << QVariant(25) // health case amt 37 - << QVariant(47) // water rise amt 38 - << QVariant(5) // health dec amt 39 - << QVariant(100) // rope modfier 40 - << QVariant(100) // get away time 41 - << QVariant(0) // world edge 42 - << QVariant() // scriptparam 43 + << QVariant(0) // sentries 36 + << QVariant(35) // health case pct 37 + << QVariant(25) // health case amt 38 + << QVariant(47) // water rise amt 39 + << QVariant(5) // health dec amt 40 + << QVariant(100) // rope modfier 41 + << QVariant(100) // get away time 42 + << QVariant(0) // world edge 43 + << QVariant() // scriptparam 44 ; QList timeless; @@ -470,14 +478,15 @@ << QVariant(10) // mine dud pct 33 << QVariant(2) // explosives 34 << QVariant(0) // air mines 35 - << QVariant(35) // health case pct 36 - << QVariant(30) // health case amt 37 - << QVariant(0) // water rise amt 38 - << QVariant(0) // health dec amt 39 - << QVariant(100) // rope modfier 40 - << QVariant(100) // get away time 41 - << QVariant(0) // world edge 42 - << QVariant() // scriptparam 43 + << QVariant(0) // sentries 36 + << QVariant(35) // health case pct 37 + << QVariant(30) // health case amt 38 + << QVariant(0) // water rise amt 39 + << QVariant(0) // health dec amt 40 + << QVariant(100) // rope modfier 41 + << QVariant(100) // get away time 42 + << QVariant(0) // world edge 43 + << QVariant() // scriptparam 44 ; QList thinkingportals; @@ -518,14 +527,15 @@ << QVariant(0) // mine dud pct 33 << QVariant(5) // explosives 34 << QVariant(4) // air mines 35 - << QVariant(25) // health case pct 36 - << QVariant(25) // health case amt 37 - << QVariant(47) // water rise amt 38 - << QVariant(5) // health dec amt 39 - << QVariant(100) // rope modfier 40 - << QVariant(100) // get away time 41 - << QVariant(0) // world edge 42 - << QVariant() // scriptparam 43 + << QVariant(0) // sentries 36 + << QVariant(25) // health case pct 37 + << QVariant(25) // health case amt 38 + << QVariant(47) // water rise amt 39 + << QVariant(5) // health dec amt 40 + << QVariant(100) // rope modfier 41 + << QVariant(100) // get away time 42 + << QVariant(0) // world edge 43 + << QVariant() // scriptparam 44 ; QList kingmode; @@ -566,14 +576,15 @@ << QVariant(0) // mine dud pct 33 << QVariant(2) // explosives 34 << QVariant(0) // air mines 35 - << QVariant(35) // health case pct 36 - << QVariant(25) // health case amt 37 - << QVariant(47) // water rise amt 38 - << QVariant(5) // health dec amt 39 - << QVariant(100) // rope modfier 40 - << QVariant(100) // get away time 41 - << QVariant(0) // world edge 42 - << QVariant() // scriptparam 43 + << QVariant(0) // sentries 36 + << QVariant(35) // health case pct 37 + << QVariant(25) // health case amt 38 + << QVariant(47) // water rise amt 39 + << QVariant(5) // health dec amt 40 + << QVariant(100) // rope modfier 41 + << QVariant(100) // get away time 42 + << QVariant(0) // world edge 43 + << QVariant() // scriptparam 44 ; QList mutant; @@ -614,14 +625,15 @@ << QVariant(0) // mine dud pct 33 << QVariant(2) // explosives 34 << QVariant(0) // air mines 35 - << QVariant(0) // health case pct 36 - << QVariant(25) // health case amt 37 - << QVariant(0) // water rise amt 38 - << QVariant(0) // health dec amt 39 - << QVariant(100) // rope modfier 40 - << QVariant(100) // get away time 41 - << QVariant(0) // world edge 42 - << QVariant() // scriptparam 43 + << QVariant(0) // sentries 36 + << QVariant(0) // health case pct 37 + << QVariant(25) // health case amt 38 + << QVariant(0) // water rise amt 39 + << QVariant(0) // health dec amt 40 + << QVariant(100) // rope modfier 41 + << QVariant(100) // get away time 42 + << QVariant(0) // world edge 43 + << QVariant() // scriptparam 44 ; QList construction; @@ -662,15 +674,16 @@ << QVariant(0) // mine dud pct 33 << QVariant(0) // explosives 34 << QVariant(0) // air mines 35 - << QVariant(35) // health case pct 36 - << QVariant(25) // health case amt 37 - << QVariant(47) // water rise amt 38 - << QVariant(5) // health dec amt 39 - << QVariant(100) // rope modfier 40 - << QVariant(100) // get away time 41 - << QVariant(0) // world edge 42 + << QVariant(0) // sentries 36 + << QVariant(35) // health case pct 37 + << QVariant(25) // health case amt 38 + << QVariant(47) // water rise amt 39 + << QVariant(5) // health dec amt 40 + << QVariant(100) // rope modfier 41 + << QVariant(100) // get away time 42 + << QVariant(0) // world edge 43 // NOTE: If you change this, also change the defaults in the Construction Mode script - << QVariant("initialenergy=550, energyperround=50, maxenergy=1000, cratesperround=5") // scriptparam 43 + << QVariant("initialenergy=550, energyperround=50, maxenergy=1000, cratesperround=5") // scriptparam 44 ; QList specialists; @@ -711,15 +724,16 @@ << QVariant(0) // mine dud pct 33 << QVariant(0) // explosives 34 << QVariant(0) // air mines 35 - << QVariant(100) // health case pct 36 - << QVariant(25) // health case amt 37 - << QVariant(47) // water rise amt 38 - << QVariant(5) // health dec amt 39 - << QVariant(100) // rope modfier 40 - << QVariant(100) // get away time 41 - << QVariant(0) // world edge 42 + << QVariant(0) // sentries 36 + << QVariant(100) // health case pct 37 + << QVariant(25) // health case amt 38 + << QVariant(47) // water rise amt 39 + << QVariant(5) // health dec amt 40 + << QVariant(100) // rope modfier 41 + << QVariant(100) // get away time 42 + << QVariant(0) // world edge 43 // NOTE: If you change this, also change the defaults in the The Specialists script - << QVariant("t=SENDXHPL") // scriptparam 43 + << QVariant("t=SENDXHPL") // scriptparam 44 ; QList spaceinvasion; @@ -760,15 +774,16 @@ << QVariant(0) // mine dud pct 33 << QVariant(0) // explosives 34 << QVariant(0) // air mines 35 - << QVariant(0) // health case pct 36 - << QVariant(25) // health case amt 37 - << QVariant(0) // water rise amt 38 - << QVariant(0) // health dec amt 39 - << QVariant(100) // rope modfier 40 - << QVariant(100) // get away time 41 - << QVariant(0) // world edge 42 + << QVariant(0) // sentries 36 + << QVariant(0) // health case pct 37 + << QVariant(25) // health case amt 38 + << QVariant(0) // water rise amt 39 + << QVariant(0) // health dec amt 40 + << QVariant(100) // rope modfier 41 + << QVariant(100) // get away time 42 + << QVariant(0) // world edge 43 // NOTE: If you change this, also change the defaults in the Space Invasion script - << QVariant("rounds=3, shield=30, barrels=5, pings=2, barrelbonus=3, shieldbonus=30, timebonus=4") // scriptparam 43 + << QVariant("rounds=3, shield=30, barrels=5, pings=2, barrelbonus=3, shieldbonus=30, timebonus=4") // scriptparam 44 ; QList hedgeeditor; @@ -809,14 +824,15 @@ << QVariant(0) // mine dud pct 33 << QVariant(0) // explosives 34 << QVariant(0) // air mines 35 - << QVariant(35) // health case pct 36 - << QVariant(25) // health case amt 37 - << QVariant(0) // water rise amt 38 - << QVariant(0) // health dec amt 39 - << QVariant(100) // rope modfier 40 - << QVariant(100) // get away time 41 - << QVariant(0) // world edge 42 - << QVariant() // scriptparam 43 + << QVariant(0) // sentries 36 + << QVariant(35) // health case pct 37 + << QVariant(25) // health case amt 38 + << QVariant(0) // water rise amt 39 + << QVariant(0) // health dec amt 40 + << QVariant(100) // rope modfier 41 + << QVariant(100) // get away time 42 + << QVariant(0) // world edge 43 + << QVariant() // scriptparam 44 ; QList racer; @@ -857,14 +873,15 @@ << QVariant(0) // mine dud pct 33 << QVariant(0) // explosives 34 << QVariant(0) // air mines 35 - << QVariant(0) // health case pct 36 - << QVariant(25) // health case amt 37 - << QVariant(0) // water rise amt 38 - << QVariant(0) // health dec amt 39 - << QVariant(100) // rope modfier 40 - << QVariant(100) // get away time 41 - << QVariant(0) // world edge 42 - << QVariant() // scriptparam 43 + << QVariant(0) // sentries 36 + << QVariant(0) // health case pct 37 + << QVariant(25) // health case amt 38 + << QVariant(0) // water rise amt 39 + << QVariant(0) // health dec amt 40 + << QVariant(100) // rope modfier 41 + << QVariant(100) // get away time 42 + << QVariant(0) // world edge 43 + << QVariant() // scriptparam 44 ; diff -r b3c9f5463cee -r 74ede02bc882 QTfrontend/net/newnetclient.cpp diff -r b3c9f5463cee -r 74ede02bc882 QTfrontend/res/css/april1.css --- a/QTfrontend/res/css/april1.css Sun Jul 05 02:03:08 2020 +0200 +++ b/QTfrontend/res/css/april1.css Sun Jul 05 14:53:44 2020 +0200 @@ -406,6 +406,12 @@ height: 6px; border-radius: 3px; } +QSlider::handle::horizontal:hover { +background-color: yellow; +} +QSlider::handle::horizontal:pressed { +background-color: white; +} QSlider::handle::horizontal:disabled { background-color: #a0a0a0; } diff -r b3c9f5463cee -r 74ede02bc882 QTfrontend/res/css/birthday.css --- a/QTfrontend/res/css/birthday.css Sun Jul 05 02:03:08 2020 +0200 +++ b/QTfrontend/res/css/birthday.css Sun Jul 05 14:53:44 2020 +0200 @@ -410,6 +410,12 @@ height: 6px; border-radius: 3px; } +QSlider::handle::horizontal:hover { +background-color: yellow; +} +QSlider::handle::horizontal:pressed { +background-color: white; +} QSlider::handle::horizontal:disabled { background-color: #a0a0a0; } diff -r b3c9f5463cee -r 74ede02bc882 QTfrontend/res/css/christmas.css --- a/QTfrontend/res/css/christmas.css Sun Jul 05 02:03:08 2020 +0200 +++ b/QTfrontend/res/css/christmas.css Sun Jul 05 14:53:44 2020 +0200 @@ -393,6 +393,12 @@ margin: 2px 0px; background-color: #ffcc00; } +QSlider::handle::horizontal:hover { +background-color: yellow; +} +QSlider::handle::horizontal:pressed { +background-color: white; +} QSlider::groove::horizontal:disabled { background-color: #a0a0a0; } diff -r b3c9f5463cee -r 74ede02bc882 QTfrontend/res/css/easter.css --- a/QTfrontend/res/css/easter.css Sun Jul 05 02:03:08 2020 +0200 +++ b/QTfrontend/res/css/easter.css Sun Jul 05 14:53:44 2020 +0200 @@ -390,6 +390,12 @@ margin: 2px 0px; background-color: #ffcc00; } +QSlider::handle::horizontal:hover { +background-color: yellow; +} +QSlider::handle::horizontal:pressed { +background-color: white; +} QSlider::groove::horizontal:disabled { background-color: #a0a0a0; } diff -r b3c9f5463cee -r 74ede02bc882 QTfrontend/res/css/qt.css --- a/QTfrontend/res/css/qt.css Sun Jul 05 02:03:08 2020 +0200 +++ b/QTfrontend/res/css/qt.css Sun Jul 05 14:53:44 2020 +0200 @@ -397,6 +397,12 @@ height: 6px; border-radius: 3px; } +QSlider::handle::horizontal:hover { +background-color: yellow; +} +QSlider::handle::horizontal:pressed { +background-color: white; +} QSlider::handle::horizontal:disabled { background-color: #a0a0a0; } diff -r b3c9f5463cee -r 74ede02bc882 QTfrontend/ui/page/pagegamestats.cpp --- a/QTfrontend/ui/page/pagegamestats.cpp Sun Jul 05 02:03:08 2020 +0200 +++ b/QTfrontend/ui/page/pagegamestats.cpp Sun Jul 05 14:53:44 2020 +0200 @@ -22,6 +22,7 @@ #include #include #include +#include #include "pagegamestats.h" #include "team.h" @@ -121,7 +122,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 +175,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 b3c9f5463cee -r 74ede02bc882 QTfrontend/ui/page/pagescheme.cpp --- a/QTfrontend/ui/page/pagescheme.cpp Sun Jul 05 02:03:08 2020 +0200 +++ b/QTfrontend/ui/page/pagescheme.cpp Sun Jul 05 14:53:44 2020 +0200 @@ -191,6 +191,7 @@ QString wtMineDuds = tr("Likelihood of a mine being a dud. Does not affect mines placed by hedgehogs."); QString wtExplosives = tr("Average number of barrels to be placed a medium-sized island map. This number will be scaled for other maps."); QString wtAirMines = tr("Average number of air mines to be placed a medium-sized island map. This number will be scaled for other maps."); + QString wtSentries = tr("Average number of sentry bots to be placed on a medium-sized island map. This number will be scaled for other maps."); QString wtWorldEdge = tr("Affects the left and right boundaries of the map"); QString wtGetAwayTime = tr("Time you get after an attack"); QString wtScriptParam = tr("Additional parameter to configure game styles. The meaning depends on the used style, refer to the documentation. When in doubt, leave it empty."); @@ -462,33 +463,50 @@ glBSLayout->addWidget(SB_AirMines,14,2,1,1); l = new QLabel(gbBasicSettings); + l->setText(QLabel::tr("Sentry Bots")); + l->setWhatsThis(wtSentries); + l->setWordWrap(true); + glBSLayout->addWidget(l,15,0,1,1); + l = new QLabel(gbBasicSettings); + l->setWhatsThis(wtSentries); + l->setFixedSize(32,32); + l->setPixmap(QPixmap(":/res/iconAirMine.png")); + glBSLayout->addWidget(l,15,1,1,1); + SB_Sentries = new QSpinBox(gbBasicSettings); + SB_Sentries->setWhatsThis(wtSentries); + SB_Sentries->setRange(0, 200); + SB_Sentries->setValue(0); + SB_Sentries->setSingleStep(5); + glBSLayout->addWidget(SB_Sentries,15,2,1,1); + + l = new QLabel(gbBasicSettings); //: Label of game scheme setting for the time you get after an attack l->setText(QLabel::tr("% Retreat Time")); l->setWhatsThis(wtGetAwayTime); l->setWordWrap(true); - glBSLayout->addWidget(l,15,0,1,1); + glBSLayout->addWidget(l,16,0,1,1); l = new QLabel(gbBasicSettings); l->setWhatsThis(wtGetAwayTime); l->setFixedSize(32,32); l->setPixmap(QPixmap(":/res/iconTime.png")); - glBSLayout->addWidget(l,15,1,1,1); + glBSLayout->addWidget(l,16,1,1,1); SB_GetAwayTime = new QSpinBox(gbBasicSettings); SB_GetAwayTime->setWhatsThis(wtGetAwayTime); SB_GetAwayTime->setRange(0, 999); SB_GetAwayTime->setValue(100); SB_GetAwayTime->setSingleStep(25); - glBSLayout->addWidget(SB_GetAwayTime,15,2,1,1); + glBSLayout->addWidget(SB_GetAwayTime,16,2,1,1); l = new QLabel(gbBasicSettings); l->setText(QLabel::tr("World Edge")); l->setWhatsThis(wtWorldEdge); l->setWordWrap(true); - glBSLayout->addWidget(l,16,0,1,1); + glBSLayout->addWidget(l,17,0,1,1); l = new QLabel(gbBasicSettings); l->setWhatsThis(wtWorldEdge); l->setFixedSize(32,32); l->setPixmap(QPixmap(":/res/iconEarth.png")); - glBSLayout->addWidget(l,16,1,1,1); + glBSLayout->addWidget(l,17,1,1,1); CB_WorldEdge = new QComboBox(gbBasicSettings); CB_WorldEdge->setWhatsThis(wtWorldEdge); @@ -497,24 +515,24 @@ CB_WorldEdge->insertItem(2, tr("Bounce (Edges reflect)")); CB_WorldEdge->insertItem(3, tr("Sea (Edges connect to sea)")); /* CB_WorldEdge->insertItem(4, tr("Skybox")); */ - glBSLayout->addWidget(CB_WorldEdge,16,2,1,1); + glBSLayout->addWidget(CB_WorldEdge,17,2,1,1); l = new QLabel(gbBasicSettings); l->setText(QLabel::tr("Script parameter")); l->setWhatsThis(wtScriptParam); l->setWordWrap(true); - glBSLayout->addWidget(l,17,0,1,1); + glBSLayout->addWidget(l,18,0,1,1); l = new QLabel(gbBasicSettings); l->setWhatsThis(wtScriptParam); l->setFixedSize(32,32); l->setPixmap(QPixmap(":/res/iconScript.png")); - glBSLayout->addWidget(l,17,1,1,1); + glBSLayout->addWidget(l,18,1,1,1); LE_ScriptParam = new QLineEdit(gbBasicSettings); LE_ScriptParam->setWhatsThis(wtScriptParam); LE_ScriptParam->setMaxLength(240); - glBSLayout->addWidget(LE_ScriptParam,17,2,1,1); + glBSLayout->addWidget(LE_ScriptParam,18,2,1,1); L_name = new QLabel(gbBasicSettings); L_name->setText(QLabel::tr("Scheme Name:")); @@ -557,6 +575,7 @@ connect(BtnCopy, SIGNAL(clicked()), this, SLOT(copyRow())); connect(BtnNew, SIGNAL(clicked()), this, SLOT(newRow())); connect(BtnDelete, SIGNAL(clicked()), this, SLOT(deleteRow())); + connect(CB_WorldEdge, SIGNAL(currentIndexChanged(int)), this, SLOT(worldEdgeChanged(int))); mapper = new QDataWidgetMapper(this); connect(selectScheme, SIGNAL(currentIndexChanged(int)), mapper, SLOT(setCurrentIndex(int))); connect(selectScheme, SIGNAL(currentIndexChanged(int)), this, SLOT(schemeSelected(int))); @@ -609,14 +628,15 @@ mapper->addMapping(SB_MineDuds, 33); mapper->addMapping(SB_Explosives, 34); mapper->addMapping(SB_AirMines, 35); - mapper->addMapping(SB_HealthCrates, 36); - mapper->addMapping(SB_CrateHealth, 37); - mapper->addMapping(SB_WaterRise, 38); - mapper->addMapping(SB_HealthDecrease, 39); - mapper->addMapping(SB_RopeModifier, 40); - mapper->addMapping(SB_GetAwayTime, 41); - mapper->addMapping(CB_WorldEdge, 42, "currentIndex"); - mapper->addMapping(LE_ScriptParam, 43); + mapper->addMapping(SB_Sentries, 36); + mapper->addMapping(SB_HealthCrates, 37); + mapper->addMapping(SB_CrateHealth, 38); + mapper->addMapping(SB_WaterRise, 39); + mapper->addMapping(SB_HealthDecrease, 40); + mapper->addMapping(SB_RopeModifier, 41); + mapper->addMapping(SB_GetAwayTime, 42); + mapper->addMapping(CB_WorldEdge, 43, "currentIndex"); + mapper->addMapping(LE_ScriptParam, 44); mapper->toFirst(); @@ -684,6 +704,14 @@ }; } +void PageScheme::worldEdgeChanged(int n) +{ + if (mapper->itemDelegate()) + { + mapper->itemDelegate()->commitData(CB_WorldEdge); + } +} + void PageScheme::schemeSelected(int n) { int c = ((GameSchemeModel*)mapper->model())->numberOfDefaultSchemes; diff -r b3c9f5463cee -r 74ede02bc882 QTfrontend/ui/page/pagescheme.h --- a/QTfrontend/ui/page/pagescheme.h Sun Jul 05 02:03:08 2020 +0200 +++ b/QTfrontend/ui/page/pagescheme.h Sun Jul 05 14:53:44 2020 +0200 @@ -90,6 +90,7 @@ MinesTimeSpinBox * SB_MinesTime; QSpinBox * SB_Mines; QSpinBox * SB_AirMines; + QSpinBox * SB_Sentries; QSpinBox * SB_MineDuds; QSpinBox * SB_Explosives; QSpinBox * SB_RopeModifier; @@ -107,6 +108,7 @@ void checkDupe(); private slots: + void worldEdgeChanged(int); void schemeSelected(int); void dataChanged(QModelIndex topLeft, QModelIndex bottomRight); }; diff -r b3c9f5463cee -r 74ede02bc882 QTfrontend/ui/widget/chatwidget.cpp diff -r b3c9f5463cee -r 74ede02bc882 QTfrontend/ui/widget/gamecfgwidget.cpp --- a/QTfrontend/ui/widget/gamecfgwidget.cpp Sun Jul 05 02:03:08 2020 +0200 +++ b/QTfrontend/ui/widget/gamecfgwidget.cpp Sun Jul 05 14:53:44 2020 +0200 @@ -346,18 +346,19 @@ bcfg << QString("e$minedudpct %1").arg(schemeData(33).toInt()).toUtf8(); bcfg << QString("e$explosives %1").arg(schemeData(34).toInt()).toUtf8(); bcfg << QString("e$airmines %1").arg(schemeData(35).toInt()).toUtf8(); - bcfg << QString("e$healthprob %1").arg(schemeData(36).toInt()).toUtf8(); - bcfg << QString("e$hcaseamount %1").arg(schemeData(37).toInt()).toUtf8(); - bcfg << QString("e$waterrise %1").arg(schemeData(38).toInt()).toUtf8(); - bcfg << QString("e$healthdec %1").arg(schemeData(39).toInt()).toUtf8(); - bcfg << QString("e$ropepct %1").arg(schemeData(40).toInt()).toUtf8(); - bcfg << QString("e$getawaytime %1").arg(schemeData(41).toInt()).toUtf8(); - bcfg << QString("e$worldedge %1").arg(schemeData(42).toInt()).toUtf8(); + bcfg << QString("e$sentries %1").arg(schemeData(36).toInt()).toUtf8(); + bcfg << QString("e$healthprob %1").arg(schemeData(37).toInt()).toUtf8(); + bcfg << QString("e$hcaseamount %1").arg(schemeData(38).toInt()).toUtf8(); + bcfg << QString("e$waterrise %1").arg(schemeData(39).toInt()).toUtf8(); + bcfg << QString("e$healthdec %1").arg(schemeData(40).toInt()).toUtf8(); + bcfg << QString("e$ropepct %1").arg(schemeData(41).toInt()).toUtf8(); + bcfg << QString("e$getawaytime %1").arg(schemeData(42).toInt()).toUtf8(); + bcfg << QString("e$worldedge %1").arg(schemeData(43).toInt()).toUtf8(); bcfg << QString("e$template_filter %1").arg(pMapContainer->getTemplateFilter()).toUtf8(); bcfg << QString("e$feature_size %1").arg(pMapContainer->getFeatureSize()).toUtf8(); bcfg << QString("e$mapgen %1").arg(mapgen).toUtf8(); - if(!schemeData(43).isNull()) - bcfg << QString("e$scriptparam %1").arg(schemeData(43).toString()).toUtf8(); + if(!schemeData(44).isNull()) + bcfg << QString("e$scriptparam %1").arg(schemeData(44).toString()).toUtf8(); else bcfg << QString("e$scriptparam ").toUtf8(); diff -r b3c9f5463cee -r 74ede02bc882 QTfrontend/ui/widget/keybinder.cpp --- a/QTfrontend/ui/widget/keybinder.cpp Sun Jul 05 02:03:08 2020 +0200 +++ b/QTfrontend/ui/widget/keybinder.cpp Sun Jul 05 14:53:44 2020 +0200 @@ -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 b3c9f5463cee -r 74ede02bc882 QTfrontend/ui/widget/selectWeapon.cpp --- a/QTfrontend/ui/widget/selectWeapon.cpp Sun Jul 05 02:03:08 2020 +0200 +++ b/QTfrontend/ui/widget/selectWeapon.cpp Sun Jul 05 14:53:44 2020 +0200 @@ -84,6 +84,16 @@ item->setEnabled(value); } +int SelWeaponWidget::readWeaponValue(const QChar chr, int max) +{ + int value = chr.digitValue(); + if (value == -1) + value = 0; + else if (value > max) + value = max; + return value; +} + SelWeaponWidget::SelWeaponWidget(int numItems, QWidget* parent) : QFrame(parent), m_numItems(numItems) @@ -183,25 +193,34 @@ int i = 0, k = 0; for(; i < m_numItems; ++i) { - // Hide amSkip (6) and amCreeper (57) - // TODO: Unhide amCreeper when this weapon is done - if (i == 6 || i == 57) continue; - if (k % 4 == 0) ++j; - SelWeaponItem * swi = new SelWeaponItem(true, i, currentState[i].digitValue(), QImage(":/res/ammopic.png"), QImage(":/res/ammopicgrey.png"), this); - weaponItems[i].append(swi); - p1Layout->addWidget(swi, j, k % 4); + if (k % cAmmoMenuRows == 0) + ++j; + unsigned int ammo = ammoMenuAmmos[i]; + // Hide amSkip (7) + if (ammo == 7) + continue; + // Hide unused amCreeper (58) + else if (ammo == 58) + { + ++k; + continue; + } + int a = ammo-1; // ammo ID for SelWeaponItem + SelWeaponItem * swi = new SelWeaponItem(true, a, readWeaponValue(currentState[a], 9), QImage(":/res/ammopic.png"), QImage(":/res/ammopicgrey.png"), this); + weaponItems[a].append(swi); + p1Layout->addWidget(swi, j, k % cAmmoMenuRows); - SelWeaponItem * pwi = new SelWeaponItem(false, i, currentState[numItems + i].digitValue(), QImage(":/res/ammopicbox.png"), QImage(":/res/ammopicboxgrey.png"), this); - weaponItems[i].append(pwi); - p2Layout->addWidget(pwi, j, k % 4); + SelWeaponItem * pwi = new SelWeaponItem(false, a, readWeaponValue(currentState[numItems + a], 8), QImage(":/res/ammopicbox.png"), QImage(":/res/ammopicboxgrey.png"), this); + weaponItems[a].append(pwi); + p2Layout->addWidget(pwi, j, k % cAmmoMenuRows); - SelWeaponItem * dwi = new SelWeaponItem(false, i, currentState[numItems*2 + i].digitValue(), QImage(":/res/ammopicdelay.png"), QImage(":/res/ammopicdelaygrey.png"), this); - weaponItems[i].append(dwi); - p3Layout->addWidget(dwi, j, k % 4); + SelWeaponItem * dwi = new SelWeaponItem(false, a, readWeaponValue(currentState[numItems*2 + a], 8), QImage(":/res/ammopicdelay.png"), QImage(":/res/ammopicdelaygrey.png"), this); + weaponItems[a].append(dwi); + p3Layout->addWidget(dwi, j, k % cAmmoMenuRows); - SelWeaponItem * awi = new SelWeaponItem(false, i, currentState[numItems*3 + i].digitValue(), QImage(":/res/ammopic.png"), QImage(":/res/ammopicgrey.png"), this); - weaponItems[i].append(awi); - p4Layout->addWidget(awi, j, k % 4); + SelWeaponItem * awi = new SelWeaponItem(false, a, readWeaponValue(currentState[numItems*3 + a], 8), QImage(":/res/ammopic.png"), QImage(":/res/ammopicgrey.png"), this); + weaponItems[a].append(awi); + p4Layout->addWidget(awi, j, k % cAmmoMenuRows); ++k; } @@ -229,10 +248,10 @@ { twi::iterator it = weaponItems.find(i); if (it == weaponItems.end()) continue; - it.value()[0]->setItemsNum(ammo[i].digitValue()); - it.value()[1]->setItemsNum(ammo[m_numItems + i].digitValue()); - it.value()[2]->setItemsNum(ammo[m_numItems*2 + i].digitValue()); - it.value()[3]->setItemsNum(ammo[m_numItems*3 + i].digitValue()); + it.value()[0]->setItemsNum(readWeaponValue(ammo[i], 9)); + it.value()[1]->setItemsNum(readWeaponValue(ammo[m_numItems + i], 8)); + it.value()[2]->setItemsNum(readWeaponValue(ammo[m_numItems*2 + i], 8)); + it.value()[3]->setItemsNum(readWeaponValue(ammo[m_numItems*3 + i], 8)); it.value()[0]->setEnabled(enable); it.value()[1]->setEnabled(enable); it.value()[2]->setEnabled(enable); @@ -331,6 +350,7 @@ file.close(); } emit weaponsEdited(curWeaponsName, m_name->text(), stateFull); + curWeaponsName = m_name->text(); } int SelWeaponWidget::operator [] (unsigned int weaponIndex) const diff -r b3c9f5463cee -r 74ede02bc882 QTfrontend/ui/widget/selectWeapon.h --- a/QTfrontend/ui/widget/selectWeapon.h Sun Jul 05 02:03:08 2020 +0200 +++ b/QTfrontend/ui/widget/selectWeapon.h Sun Jul 05 14:53:44 2020 +0200 @@ -95,6 +95,7 @@ QGridLayout* p4Layout; QString fixWeaponSet(const QString & s); + int readWeaponValue(const QChar chr, int max); }; #endif // _SELECT_WEAPON_INCLUDED diff -r b3c9f5463cee -r 74ede02bc882 QTfrontend/weapons.h --- a/QTfrontend/weapons.h Sun Jul 05 02:03:08 2020 +0200 +++ b/QTfrontend/weapons.h Sun Jul 05 14:53:44 2020 +0200 @@ -16,10 +16,10 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#define AMMOLINE_EMPTY_QT "00000090000000000000000000000000000000000000000000000000000" -#define AMMOLINE_EMPTY_PROB "00000000000000000000000000000000000000000000000000000000000" -#define AMMOLINE_EMPTY_DELAY "00000000000000000000000000000000000000000000000000000000000" -#define AMMOLINE_EMPTY_CRATE "13111103121111111231141111111111111112111111111111111111111" +#define AMMOLINE_EMPTY_QT "000000900000000000000000000000000000000000000000000000000000" +#define AMMOLINE_EMPTY_PROB "000000000000000000000000000000000000000000000000000000000000" +#define AMMOLINE_EMPTY_DELAY "000000000000000000000000000000000000000000000000000000000000" +#define AMMOLINE_EMPTY_CRATE "131111031211111112311411111111111111121111111111111111111111" /* AmmoType lookup table (use monospace font / cursor movements) @@ -83,68 +83,69 @@ amAirMine-------------------------------------------------------------------------------| amCreeper--------------------------------------------------------------------------------| amMinigun---------------------------------------------------------------------------------| + amSentry-----------------------------------------------------------------------------------| */ -#define AMMOLINE_DEFAULT_QT "93919294221991210322351110012000000002111001010111110001000" -#define AMMOLINE_DEFAULT_PROB "04050405416006555465544647765766666661555101011154111111107" -#define AMMOLINE_DEFAULT_DELAY "00000000000002055000000400070040000000002200000006000200000" -#define AMMOLINE_DEFAULT_CRATE "13111103121111111231141111111111111112111111111111111111111" +#define AMMOLINE_DEFAULT_QT "939192942219912103223511100120000000021110010101111100010001" +#define AMMOLINE_DEFAULT_PROB "040504054160065554655446477657666666615551010111541111111073" +#define AMMOLINE_DEFAULT_DELAY "000000000000020550000004000700400000000022000000060002000000" +#define AMMOLINE_DEFAULT_CRATE "131111031211111112311411111111111111121111111111111111111111" -#define AMMOLINE_CRAZY_QT "99999999999999999929999999999999992999999999999999929991909" -#define AMMOLINE_CRAZY_PROB "11111101111111111111111111111111111111111111111111111111101" -#define AMMOLINE_CRAZY_DELAY "00000000000000000000000000000000000000000000000000000000000" -#define AMMOLINE_CRAZY_CRATE "13111103121111111231141111111111111112111111111111111111111" +#define AMMOLINE_CRAZY_QT "999999999999999999299999999999999929999999999999999299919099" +#define AMMOLINE_CRAZY_PROB "111111011111111111111111111111111111111111111111111111111011" +#define AMMOLINE_CRAZY_DELAY "000000000000000000000000000000000000000000000000000000000000" +#define AMMOLINE_CRAZY_CRATE "131111031211111112311411111111111111121111111111111111111111" -#define AMMOLINE_PROMODE_QT "90900090000000000000090000000000000000000000000000000000000" -#define AMMOLINE_PROMODE_PROB "00000000000000000000000000000000000000000000000000000000000" -#define AMMOLINE_PROMODE_DELAY "00000000000002055000000400070040000000002000000000000200000" -#define AMMOLINE_PROMODE_CRATE "11111101111111111111111111111111111111111111111111111111111" +#define AMMOLINE_PROMODE_QT "909000900000000000000900000000000000000000000000000000000000" +#define AMMOLINE_PROMODE_PROB "000000000000000000000000000000000000000000000000000000000000" +#define AMMOLINE_PROMODE_DELAY "000000000000020550000004000700400000000020000000000002000000" +#define AMMOLINE_PROMODE_CRATE "111111011111111111111111111111111111111111111111111111111111" -#define AMMOLINE_SHOPPA_QT "00000099000000000000000000000000000000000000000000000000000" -#define AMMOLINE_SHOPPA_PROB "44444100442444022101121212224220000000020004000100110010101" -#define AMMOLINE_SHOPPA_DELAY "00000000000000000000000000000000000000000000000000000000000" -#define AMMOLINE_SHOPPA_CRATE "11111101111111111111111111111111111111111111111111111111111" +#define AMMOLINE_SHOPPA_QT "000000990000000000000000000000000000000000000000000000000000" +#define AMMOLINE_SHOPPA_PROB "444441004424440221011212122242200000000200040001001100101010" +#define AMMOLINE_SHOPPA_DELAY "000000000000000000000000000000000000000000000000000000000000" +#define AMMOLINE_SHOPPA_CRATE "111111011111111111111111111111111111111111111111111111111111" -#define AMMOLINE_CLEAN_QT "10100090000100000110000000000000000000000000000010000000000" -#define AMMOLINE_CLEAN_PROB "04050405416006555465544647765766666661555101011154111211104" -#define AMMOLINE_CLEAN_DELAY "00000000000000000000000000000000000000000000000000000200000" -#define AMMOLINE_CLEAN_CRATE "13111103121111111231141111111111111112111111111111111111111" +#define AMMOLINE_CLEAN_QT "101000900001000001100000000000000000000000000000100000000000" +#define AMMOLINE_CLEAN_PROB "040504054160065554655446477657666666615551010111541112111040" +#define AMMOLINE_CLEAN_DELAY "000000000000000000000000000000000000000000000000000002000000" +#define AMMOLINE_CLEAN_CRATE "131111031211111112311411111111111111121111111111111111111111" -#define AMMOLINE_MINES_QT "00000099000900000003000000000000000000000000000000000000000" -#define AMMOLINE_MINES_PROB "00000000000000000000000000000000000000000000000000000000000" -#define AMMOLINE_MINES_DELAY "00000000000002055000000400070040000000002000000006000200000" -#define AMMOLINE_MINES_CRATE "11111101111111111111111111111111111111111111111111111111111" +#define AMMOLINE_MINES_QT "000000990009000000030000000000000000000000000000000000000000" +#define AMMOLINE_MINES_PROB "000000000000000000000000000000000000000000000000000000000000" +#define AMMOLINE_MINES_DELAY "000000000000020550000004000700400000000020000000060002000000" +#define AMMOLINE_MINES_CRATE "111111011111111111111111111111111111111111111111111111111111" -#define AMMOLINE_PORTALS_QT "90000090020000000021000000000000001100000900000000000000000" -#define AMMOLINE_PORTALS_PROB "04050405416006555465544647765766666661555101011154111211102" -#define AMMOLINE_PORTALS_DELAY "00000000000002055000000400070040000000002000000006000200000" -#define AMMOLINE_PORTALS_CRATE "13111103121111111231141111111111111112111111111111111111111" +#define AMMOLINE_PORTALS_QT "900000900200000000210000000000000011000009000000000000000000" +#define AMMOLINE_PORTALS_PROB "040504054160065554655446477657666666615551010111541112111020" +#define AMMOLINE_PORTALS_DELAY "000000000000020550000004000700400000000020000000060002000000" +#define AMMOLINE_PORTALS_CRATE "131111031211111112311411111111111111121111111111111111111111" -#define AMMOLINE_ONEEVERY_QT "11111191111111111111111111111111111111111111111111111111101" -#define AMMOLINE_ONEEVERY_PROB "11111101111111111111111111111111111111111111111111111111101" -#define AMMOLINE_ONEEVERY_DELAY "00000000000000000000000000000000000000000000000000000000000" -#define AMMOLINE_ONEEVERY_CRATE "11111101111111111111111111111111111111111111111111111111111" +#define AMMOLINE_ONEEVERY_QT "111111911111111111111111111111111111111111111111111111111011" +#define AMMOLINE_ONEEVERY_PROB "111111011111111111111111111111111111111111111111111111111011" +#define AMMOLINE_ONEEVERY_DELAY "000000000000000000000000000000000000000000000000000000000000" +#define AMMOLINE_ONEEVERY_CRATE "111111011111111111111111111111111111111111111111111111111111" -#define AMMOLINE_BRW_QT "33323392332332322323233131122113000003232203022022200020301" -#define AMMOLINE_BRW_PROB "00000000000000000000000000000000111110000000000000000000000" -#define AMMOLINE_BRW_DELAY "00000000000000000000000000000000000000000000000000000000000" -#define AMMOLINE_BRW_CRATE "11111101111111111111111111111111111111111111111111111111111" +#define AMMOLINE_BRW_QT "333233923323323223232331311221130000032322030220222000203010" +#define AMMOLINE_BRW_PROB "000000000000000000000000000000001111100000000000000000000000" +#define AMMOLINE_BRW_DELAY "000000000000000000000000000000000000000000000000000000000000" +#define AMMOLINE_BRW_CRATE "111111011111111111111111111111111111111111111111111111111111" -#define AMMOLINE_HIGHLANDER_QT "11111191111111111111019111111111100101111101111001001011101" -#define AMMOLINE_HIGHLANDER_PROB "00000000000000000000000000000000000000000000000000000000000" -#define AMMOLINE_HIGHLANDER_DELAY "00000000000000000000000000000000000000000000000000000000000" -#define AMMOLINE_HIGHLANDER_CRATE "00000000000000000000000000000000000000000000000000000000000" +#define AMMOLINE_HIGHLANDER_QT "111111911111111111110191111111111001011111011110010010111010" +#define AMMOLINE_HIGHLANDER_PROB "000000000000000000000000000000000000000000000000000000000000" +#define AMMOLINE_HIGHLANDER_DELAY "000000000000000000000000000000000000000000000000000000000000" +#define AMMOLINE_HIGHLANDER_CRATE "000000000000000000000000000000000000000000000000000000000001" -#define AMMOLINE_CONSTRUCTION_QT "11000190000000100100900000000000000000000000000000000000000" -#define AMMOLINE_CONSTRUCTION_PROB "11111101111111100100011111101111111111111101111100101110101" -#define AMMOLINE_CONSTRUCTION_DELAY "00000000000000000000000000000000000000000000000000000000000" -#define AMMOLINE_CONSTRUCTION_CRATE "11111101111111111111111111111111111111111111111111111111111" +#define AMMOLINE_CONSTRUCTION_QT "110001900000001001009000000000000000000000000000000000000000" +#define AMMOLINE_CONSTRUCTION_PROB "111111011111111001000111111011111111111111011111001011101010" +#define AMMOLINE_CONSTRUCTION_DELAY "000000000000000000000000000000000000000000000000000000000000" +#define AMMOLINE_CONSTRUCTION_CRATE "111111011111111111111111111111111111111111111111111111111111" -#define AMMOLINE_SHOPPAPRO_QT "00000099000000000000000000000000000000000000000000000000000" -#define AMMOLINE_SHOPPAPRO_PROB "44444000440444000000000000004000000000000000000000000000000" -#define AMMOLINE_SHOPPAPRO_DELAY "00000000000000000000000000000000000000000000000000000000000" -#define AMMOLINE_SHOPPAPRO_CRATE "11111101111111111111111111111111111111111111111111111211111" +#define AMMOLINE_SHOPPAPRO_QT "000000990000000000000000000000000000000000000000000000000000" +#define AMMOLINE_SHOPPAPRO_PROB "444440004404440000000000000040000000000000000000000000000000" +#define AMMOLINE_SHOPPAPRO_DELAY "000000000000000000000000000000000000000000000000000000000000" +#define AMMOLINE_SHOPPAPRO_CRATE "111111011111111111111111111111111111111111111111111112111111" -#define AMMOLINE_HEDGEEDITOR_QT "00000090000000000000000000000000000000000000000000000000000" -#define AMMOLINE_HEDGEEDITOR_PROB "00000000000000000000000000000000000000000000000000000000000" -#define AMMOLINE_HEDGEEDITOR_DELAY "00000000000000000000000000000000000000000000000000000000000" -#define AMMOLINE_HEDGEEDITOR_CRATE "11111101111111111111111111111111111111111111111111111111111" +#define AMMOLINE_HEDGEEDITOR_QT "000000900000000000000000000000000000000000000000000000000000" +#define AMMOLINE_HEDGEEDITOR_PROB "000000000000000000000000000000000000000000000000000000000000" +#define AMMOLINE_HEDGEEDITOR_DELAY "000000000000000000000000000000000000000000000000000000000000" +#define AMMOLINE_HEDGEEDITOR_CRATE "111111011111111111111111111111111111111111111111111111111111" diff -r b3c9f5463cee -r 74ede02bc882 cmake_modules/FindSDL2.cmake --- a/cmake_modules/FindSDL2.cmake Sun Jul 05 02:03:08 2020 +0200 +++ b/cmake_modules/FindSDL2.cmake Sun Jul 05 14:53:44 2020 +0200 @@ -1,6 +1,6 @@ # Locate SDL2 library # This module defines -# SDL2_LIBRARY, the name of the library to link against +# SDL2_LIBRARIES, the name of the library to link against # SDL2_FOUND, if false, do not try to link to SDL2 # SDL2_INCLUDE_DIR, where to find SDL.h # @@ -10,7 +10,7 @@ # only applications need main(). # Otherwise, it is assumed you are building an application and this # module will attempt to locate and set the the proper link flags -# as part of the returned SDL2_LIBRARY variable. +# as part of the returned SDL2_LIBRARIES variable. # # Don't forget to include SDLmain.h and SDLmain.m your project for the # OS X framework based version. (Other versions link to -lSDL2main which @@ -19,12 +19,12 @@ # # # Additional Note: If you see an empty SDL2_LIBRARY_TEMP in your configuration -# and no SDL2_LIBRARY, it means CMake did not find your SDL2 library +# and no SDL2_LIBRARIES, it means CMake did not find your SDL2 library # (SDL2.dll, libsdl2.so, SDL2.framework, etc). # Set SDL2_LIBRARY_TEMP to point to your SDL2 library, and configure again. # Similarly, if you see an empty SDL2MAIN_LIBRARY, you should set this value -# as appropriate. These values are used to generate the final SDL2_LIBRARY -# variable, but when these values are unset, SDL2_LIBRARY does not get created. +# as appropriate. These values are used to generate the final SDL2_LIBRARIES +# variable, but when these values are unset, SDL2_LIBRARIES does not get created. # # # $SDL2DIR is an environment variable that would @@ -44,7 +44,7 @@ # # On OSX, this will prefer the Framework version (if found) over others. # People will have to manually change the cache values of -# SDL2_LIBRARY to override this selection or set the CMake environment +# SDL2_LIBRARIES to override this selection or set the CMake environment # CMAKE_INCLUDE_PATH to modify the search paths. # # Note that the header path has changed from SDL2/SDL.h to just SDL.h @@ -76,7 +76,7 @@ /opt ) -FIND_PATH(SDL2_INCLUDE_DIR SDL.h +FIND_PATH(SDL2_INCLUDE_DIRS SDL.h HINTS $ENV{SDL2DIR} PATH_SUFFIXES include/SDL2 include @@ -92,7 +92,7 @@ ) IF(NOT SDL2_BUILDING_LIBRARY) - IF(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework") + IF(NOT ${SDL2_INCLUDE_DIRS} MATCHES ".framework") # Non-OS X framework versions expect you to also dynamically link to # SDL2main. This is mainly for Windows and OS X. Other (Unix) platforms # seem to provide SDL2main for compatibility even though they don't @@ -104,7 +104,7 @@ PATH_SUFFIXES lib64 lib PATHS ${SDL2_SEARCH_PATHS} ) - ENDIF(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework") + ENDIF(NOT ${SDL2_INCLUDE_DIRS} MATCHES ".framework") ENDIF(NOT SDL2_BUILDING_LIBRARY) # SDL2 may require threads on your system. @@ -153,17 +153,17 @@ ENDIF(MINGW) # Set the final string here so the GUI reflects the final state. - SET(SDL2_LIBRARY ${SDL2_LIBRARY_TEMP} CACHE STRING "Where the SDL2 Library can be found") + SET(SDL2_LIBRARIES ${SDL2_LIBRARY_TEMP} CACHE STRING "Where the SDL2 Library can be found") # Set the temp variable to INTERNAL so it is not seen in the CMake GUI SET(SDL2_LIBRARY_TEMP "${SDL2_LIBRARY_TEMP}" CACHE INTERNAL "") ENDIF(SDL2_LIBRARY_TEMP) if(BUILD_ENGINE_JS) - set(SDL2_LIBRARY "sdl2_emscripten_internal" CACHE STRING "emscripten override" FORCE) - set(SDL2_INCLUDE_DIR "${CMAKE_SYSTEM_INCLUDE_PATH}/SDL" CACHE STRING "emscripten override" FORCE) + set(SDL2_LIBRARIES "sdl2_emscripten_internal" CACHE STRING "emscripten override" FORCE) + set(SDL2_INCLUDE_DIRS "${CMAKE_SYSTEM_INCLUDE_PATH}/SDL" CACHE STRING "emscripten override" FORCE) endif() INCLUDE(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2 REQUIRED_VARS SDL2_LIBRARY SDL2_INCLUDE_DIR) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2 REQUIRED_VARS SDL2_LIBRARIES SDL2_INCLUDE_DIRS) diff -r b3c9f5463cee -r 74ede02bc882 cmake_modules/cpackvars.cmake --- a/cmake_modules/cpackvars.cmake Sun Jul 05 02:03:08 2020 +0200 +++ b/cmake_modules/cpackvars.cmake Sun Jul 05 14:53:44 2020 +0200 @@ -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 b3c9f5463cee -r 74ede02bc882 cmake_modules/paths.cmake --- a/cmake_modules/paths.cmake Sun Jul 05 02:03:08 2020 +0200 +++ b/cmake_modules/paths.cmake Sun Jul 05 14:53:44 2020 +0200 @@ -61,7 +61,11 @@ #install_name_tool for libraries set(CMAKE_BUILD_WITH_INSTALL_NAME_DIR TRUE) set(CMAKE_INSTALL_NAME_DIR "@executable_path/../Frameworks") -else(APPLE AND NOT (${CMAKE_INSTALL_PREFIX} MATCHES "/usr")) +# should this be a separate if block like so +#if(NOT APPLE AND NOT (${CMAKE_INSTALL_PREFIX} MATCHES "/usr")) +# there were some conditions here that implied not setting the RPATH if installed to /usr +# but it was not being applied due to else not actually taking parameters (HT wuzzy) +else() #paths where to find libraries (final slash not optional): # - the first is relative to the executable # - the second is the same directory of the executable (so it runs in bin/) diff -r b3c9f5463cee -r 74ede02bc882 hedgewars/CMakeLists.txt --- a/hedgewars/CMakeLists.txt Sun Jul 05 02:03:08 2020 +0200 +++ b/hedgewars/CMakeLists.txt Sun Jul 05 14:53:44 2020 +0200 @@ -1,6 +1,6 @@ enable_language(Pascal) -find_package(SDL2 REQUIRED) +find_package(SDL2 REQUIRED CONFIG) find_package(SDL2_image 2 REQUIRED) find_package(SDL2_net 2 REQUIRED) find_package(SDL2_ttf 2 REQUIRED) @@ -130,7 +130,7 @@ add_flag_append(CMAKE_Pascal_FLAGS "-k-framework -kOpenGL") #set the correct library or framework style depending on the main SDL - string(FIND "${SDL2_LIBRARY}" "dylib" sdl_framework) + string(FIND "${SDL2_LIBRARIES}" "dylib" sdl_framework) if(${sdl_framework} GREATER -1) add_flag_append(CMAKE_Pascal_FLAGS "-k-lsdl2 -k-lsdl2_image -k-lsdl2_mixer -k-lsdl2_ttf -k-lsdl2_net") else() diff -r b3c9f5463cee -r 74ede02bc882 hedgewars/uAI.pas --- a/hedgewars/uAI.pas Sun Jul 05 02:03:08 2020 +0200 +++ b/hedgewars/uAI.pas Sun Jul 05 14:53:44 2020 +0200 @@ -106,6 +106,7 @@ begin BotLevel:= Me^.Hedgehog^.BotLevel; windSpeed:= hwFloat2Float(cWindSpeed); +aiLaserSighting:= (cLaserSighting) or (HHHasAmmo(Me^.Hedgehog^, amLaserSight) > 0); useThisActions:= false; Me^.AIHints:= Me^.AIHints and (not aihAmmosChanged); @@ -123,7 +124,7 @@ then begin {$HINTS OFF} - Score:= AmmoTests[a].proc(Me, Targets.ar[i], BotLevel, ap); + Score:= AmmoTests[a].proc(Me, Targets.ar[i], BotLevel, ap, AmmoTests[a].flags); {$HINTS ON} if (Score > BadTurn) and (Actions.Score + Score > BestActions.Score) then if (BestActions.Score < 0) or (Actions.Score + Score > BestActions.Score + Byte(BotLevel - 1) * 2048) then @@ -141,23 +142,30 @@ BestActions.Score:= Actions.Score + Score; - // if not between shots, activate invulnerability/vampirism if available + // if not between shots, activate invulnerability/vampirism/etc. if available if CurrentHedgehog^.MultiShootAttacks = 0 then begin - if (HHHasAmmo(Me^.Hedgehog^, amInvulnerable) > 0) and (Me^.Hedgehog^.Effects[heInvulnerable] = 0) then + if (not cLaserSighting) and (HHHasAmmo(Me^.Hedgehog^, amLaserSight) > 0) and ((AmmoTests[a].flags and amtest_LaserSight) <> 0) then + begin + AddAction(BestActions, aia_Weapon, Longword(amLaserSight), 80, 0, 0); + AddAction(BestActions, aia_attack, aim_push, 10, 0, 0); + AddAction(BestActions, aia_attack, aim_release, 10, 0, 0); + end; + if ((AmmoTests[a].flags and amtest_NoInvulnerable) = 0) and + (HHHasAmmo(Me^.Hedgehog^, amInvulnerable) > 0) and (Me^.Hedgehog^.Effects[heInvulnerable] = 0) then begin AddAction(BestActions, aia_Weapon, Longword(amInvulnerable), 80, 0, 0); AddAction(BestActions, aia_attack, aim_push, 10, 0, 0); AddAction(BestActions, aia_attack, aim_release, 10, 0, 0); end; - if (HHHasAmmo(Me^.Hedgehog^, amExtraDamage) > 0) and (cDamageModifier <> _1_5) then begin AddAction(BestActions, aia_Weapon, Longword(amExtraDamage), 80, 0, 0); AddAction(BestActions, aia_attack, aim_push, 10, 0, 0); AddAction(BestActions, aia_attack, aim_release, 10, 0, 0); end; - if (HHHasAmmo(Me^.Hedgehog^, amVampiric) > 0) and (not cVampiric) then + if (not cVampiric) and ((AmmoTests[a].flags and amtest_NoVampiric) = 0) and + (HHHasAmmo(Me^.Hedgehog^, amVampiric) > 0) then begin AddAction(BestActions, aia_Weapon, Longword(amVampiric), 80, 0, 0); AddAction(BestActions, aia_attack, aim_push, 10, 0, 0); @@ -167,11 +175,6 @@ AddAction(BestActions, aia_Weapon, Longword(a), 300 + random(400), 0, 0); - if (Ammoz[a].Ammo.Propz and ammoprop_NeedTarget) <> 0 then - begin - AddAction(BestActions, aia_Put, 0, 8, ap.AttackPutX, ap.AttackPutY) - end; - if (ap.Angle > 0) then AddAction(BestActions, aia_LookRight, 0, 200, 0, 0) else if (ap.Angle < 0) then @@ -180,6 +183,18 @@ if (Ammoz[a].Ammo.Propz and ammoprop_Timerable) <> 0 then AddAction(BestActions, aia_Timer, ap.Time div 1000, 400, 0, 0); + if ((Ammoz[a].Ammo.Propz and ammoprop_SetBounce) > 0) and (ap.Bounce > 0) then + begin + AddAction(BestActions, aia_Precise, aim_push, 10, 0, 0); + AddAction(BestActions, aia_Timer, ap.Bounce, 200, 0, 0); + AddAction(BestActions, aia_Precise, aim_release, 10, 0, 0); + end; + + if (Ammoz[a].Ammo.Propz and ammoprop_NeedTarget) <> 0 then + begin + AddAction(BestActions, aia_Put, 0, 8, ap.AttackPutX, ap.AttackPutY) + end; + if (Ammoz[a].Ammo.Propz and ammoprop_NoCrosshair) = 0 then begin dAngle:= LongInt(Me^.Angle) - Abs(ap.Angle); @@ -247,7 +262,7 @@ const FallPixForBranching = cHHRadius; var maxticks, oldticks, steps, tmp: Longword; - BaseRate, BestRate, Rate: LongInt; + BaseRate, BestRate, Rate, i: LongInt; GoInfo: TGoInfo; CanGo: boolean; AltMe: TGear; @@ -321,6 +336,13 @@ AddAction(BestActions, aia_Weapon, Longword(amExtraTime), 80, 0, 0); AddAction(BestActions, aia_attack, aim_push, 10, 0, 0); AddAction(BestActions, aia_attack, aim_release, 10, 0, 0); + // Better bot levels know they can spam extra time if infinite + if (BotLevel < 3) and (HHHasAmmo(Me^.Hedgehog^, amExtraTime) = AMMO_INFINITE) then + for i:= 1 to 3 do + begin + AddAction(BestActions, aia_attack, aim_push, 100, 0, 0); + AddAction(BestActions, aia_attack, aim_release, 100, 0, 0); + end; end; break; @@ -492,7 +514,14 @@ Me^.AIHints := ME^.AIHints and (not aihAmmosChanged); end; - end else SDL_Delay(100) + end + else + begin + // No target found, skip turn + BestActions.Count:= 0; + AddAction(BestActions, aia_Skip, 0, 250, 0, 0); + Me^.AIHints := ME^.AIHints and (not aihAmmosChanged); + end else begin BackMe:= Me^; @@ -545,11 +574,6 @@ ThinkingHH:= Me; FillTargets; -if Targets.Count = 0 then - begin - OutError('AI: no targets!?', false); - exit - end; FillBonuses(((Me^.State and gstAttacked) <> 0) and (not isInMultiShoot) and ((GameFlags and gfInfAttack) = 0)); diff -r b3c9f5463cee -r 74ede02bc882 hedgewars/uAIActions.pas --- a/hedgewars/uAIActions.pas Sun Jul 05 02:03:08 2020 +0200 +++ b/hedgewars/uAIActions.pas Sun Jul 05 14:53:44 2020 +0200 @@ -31,6 +31,7 @@ aia_Up = 5; aia_Down = 6; aia_Switch = 7; + aia_Precise = 8; aia_Weapon = $8000; aia_WaitXL = $8001; @@ -73,7 +74,7 @@ var PrevX: LongInt = 0; timedelta: Longword = 0; -const ActionIdToStr: array[0..7] of string[16] = ( +const ActionIdToStr: array[0..8] of string[16] = ( {aia_none} '', {aia_Left} 'left', {aia_Right} 'right', @@ -81,7 +82,8 @@ {aia_attack} 'attack', {aia_Up} 'up', {aia_Down} 'down', -{aia_Switch} 'switch' +{aia_Switch} 'switch', +{aia_Precise} 'precise' ); {$IFDEF TRACEAIACTIONS} diff -r b3c9f5463cee -r 74ede02bc882 hedgewars/uAIAmmoTests.pas --- a/hedgewars/uAIAmmoTests.pas Sun Jul 05 02:03:08 2020 +0200 +++ b/hedgewars/uAIAmmoTests.pas Sun Jul 05 14:53:44 2020 +0200 @@ -25,39 +25,54 @@ amtest_Rare = $00000001; // check only several positions amtest_NoTarget = $00000002; // each pos, but no targetting amtest_MultipleAttacks = $00000004; // test could result in multiple attacks, set AttacksNum + amtest_NoTrackFall = $00000008; // skip fall tracing. + amtest_LaserSight = $00000010; // supports laser sighting + amtest_NoVampiric = $00000020; // don't use vampirism with this ammo + amtest_NoInvulnerable = $00000040; // don't use invulnerable with this with ammo var windSpeed: real; + aiLaserSighting: boolean; type TAttackParams = record - Time, AttacksNum: Longword; + Time, Bounce, AttacksNum: Longword; Angle, Power: LongInt; ExplX, ExplY, ExplR: LongInt; AttackPutX, AttackPutY: LongInt; end; -function TestBazooka(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; -function TestBee(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; -function TestSnowball(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; -function TestGrenade(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; -function TestMolotov(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; -function TestClusterBomb(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; -function TestWatermelon(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; -function TestDrillRocket(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; -function TestMortar(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; -function TestShotgun(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; -function TestDesertEagle(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; -function TestSniperRifle(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; -function TestBaseballBat(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; -function TestFirePunch(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; -function TestWhip(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; -function TestKamikaze(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; -function TestAirAttack(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; -function TestTeleport(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; -function TestHammer(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; -function TestCake(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; -function TestDynamite(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; +function TestBazooka(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestBee(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestSnowball(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestGrenade(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestMolotov(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestClusterBomb(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestWatermelon(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestDrillRocket(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestRCPlane(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestMortar(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestShotgun(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestDesertEagle(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestSniperRifle(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestBaseballBat(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestFirePunch(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestWhip(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestKamikaze(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestAirAttack(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestDrillStrike(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestMineStrike(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestSMine(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestPiano(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestTeleport(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestHammer(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestCake(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestSeduction(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestDynamite(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestMine(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestKnife(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestAirMine(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestMinigun(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; -type TAmmoTestProc = function (Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; +type TAmmoTestProc = function (Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; TAmmoTest = record proc: TAmmoTestProc; flags: Longword; @@ -70,34 +85,34 @@ (proc: @TestClusterBomb; flags: 0), // amClusterBomb (proc: @TestBazooka; flags: 0), // amBazooka (proc: @TestBee; flags: amtest_Rare), // amBee - (proc: @TestShotgun; flags: 0), // amShotgun + (proc: @TestShotgun; flags: amtest_LaserSight), // amShotgun (proc: nil; flags: 0), // amPickHammer (proc: nil; flags: 0), // amSkip (proc: nil; flags: 0), // amRope - (proc: nil; flags: 0), // amMine - (proc: @TestDesertEagle; flags: amtest_MultipleAttacks), // amDEagle + (proc: @TestMine; flags: amtest_NoTarget), // amMine + (proc: @TestDesertEagle; flags: amtest_MultipleAttacks or amtest_LaserSight), // amDEagle (proc: @TestDynamite; flags: amtest_NoTarget), // amDynamite (proc: @TestFirePunch; flags: amtest_NoTarget), // amFirePunch - (proc: @TestWhip; flags: amtest_NoTarget), // amWhip - (proc: @TestBaseballBat; flags: amtest_NoTarget), // amBaseballBat + (proc: @TestWhip; flags: amtest_NoTarget or amtest_NoInvulnerable), // amWhip + (proc: @TestBaseballBat; flags: amtest_NoTarget or amtest_NoInvulnerable), // amBaseballBat (proc: nil; flags: 0), // amParachute (proc: @TestAirAttack; flags: amtest_Rare), // amAirAttack - (proc: nil; flags: 0), // amMineStrike + (proc: @TestMineStrike; flags: amtest_Rare), // amMineStrike (proc: nil; flags: 0), // amBlowTorch (proc: nil; flags: 0), // amGirder (proc: nil; flags: 0), // amTeleport //(proc: @TestTeleport; flags: amtest_OnTurn), // amTeleport (proc: nil; flags: 0), // amSwitch (proc: @TestMortar; flags: 0), // amMortar - (proc: @TestKamikaze; flags: 0), // amKamikaze + (proc: @TestKamikaze; flags: amtest_LaserSight or amtest_NoInvulnerable or amtest_NoVampiric), // amKamikaze (proc: @TestCake; flags: amtest_Rare or amtest_NoTarget), // amCake - (proc: nil; flags: 0), // amSeduction + (proc: @TestSeduction; flags: amtest_NoTarget), // amSeduction (proc: @TestWatermelon; flags: 0), // amWatermelon (proc: nil; flags: 0), // amHellishBomb (proc: nil; flags: 0), // amNapalm (proc: @TestDrillRocket; flags: 0), // amDrill (proc: nil; flags: 0), // amBallgun - (proc: nil; flags: 0), // amRCPlane + (proc: @TestRCPlane; flags: amtest_LaserSight), // amRCPlane (proc: nil; flags: 0), // amLowGravity (proc: nil; flags: 0), // amExtraDamage (proc: nil; flags: 0), // amInvulnerable @@ -109,23 +124,24 @@ (proc: @TestMolotov; flags: 0), // amMolotov (proc: nil; flags: 0), // amBirdy (proc: nil; flags: 0), // amPortalGun - (proc: nil; flags: 0), // amPiano - (proc: @TestGrenade; flags: 0), // amGasBomb + (proc: @TestPiano; flags: amtest_Rare or amtest_NoInvulnerable or amtest_NoVampiric), // amPiano + (proc: @TestGrenade; flags: amtest_NoTrackFall), // amGasBomb (proc: @TestShotgun; flags: 0), // amSineGun (proc: nil; flags: 0), // amFlamethrower - (proc: @TestGrenade; flags: 0), // amSMine - (proc: @TestHammer; flags: amtest_NoTarget), // amHammer + (proc: @TestSMine; flags: 0), // amSMine + (proc: @TestHammer; flags: amtest_NoTarget or amtest_NoInvulnerable), // amHammer (proc: nil; flags: 0), // amResurrector - (proc: nil; flags: 0), // amDrillStrike + (proc: @TestDrillStrike; flags: amtest_Rare), // amDrillStrike (proc: nil; flags: 0), // amSnowball (proc: nil; flags: 0), // amTardis (proc: nil; flags: 0), // amLandGun (proc: nil; flags: 0), // amIceGun - (proc: nil; flags: 0), // amKnife + (proc: @TestKnife; flags: 0), // amKnife (proc: nil; flags: 0), // amRubber - (proc: nil; flags: 0), // amAirMine + (proc: @TestAirMine; flags: amtest_LaserSight), // amAirMine (proc: nil; flags: 0), // amCreeper - (proc: @TestShotgun; flags: 0) // amMinigun + (proc: @TestMinigun; flags: amtest_LaserSight), // amMinigun + (proc: nil; flags: 0) // amSentry ); implementation @@ -136,7 +152,7 @@ Metric:= abs(x1 - x2) + abs(y1 - y2) end; -function TestBazooka(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; +function TestBazooka(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; const cExtraTime = 300; var Vx, Vy, r, mX, mY: real; rTime: LongInt; @@ -146,6 +162,7 @@ t: LongInt; value: LongInt; begin +Flags:= Flags; // avoid compiler hint mX:= hwFloat2Float(Me^.X); mY:= hwFloat2Float(Me^.Y); ap.Time:= 0; @@ -267,12 +284,13 @@ calcBeeFlight:= BadTurn end; -function TestBee(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; +function TestBee(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; var i, j: LongInt; valueResult, v, a, p: LongInt; mX, mY: real; eX, eY: LongInt; begin +Flags:= Flags; // avoid compiler hint if Level > 1 then exit(BadTurn); @@ -320,7 +338,7 @@ TestBee:= BadTurn // no digging end; -function TestDrillRocket(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; +function TestDrillRocket(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; var Vx, Vy, r, mX, mY: real; rTime: LongInt; EX, EY: LongInt; @@ -331,6 +349,7 @@ t2: real; timer: Longint; begin +Flags:= Flags; // avoid compiler hint if (Level > 3) then exit(BadTurn); mX:= hwFloat2Float(Me^.X); @@ -407,8 +426,89 @@ TestDrillRocket:= valueResult end; +function TestRCPlane(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +const + MIN_RANGE = 200; +var Vx, Vy, meX, meY, x, y: real; + rx, ry, valueResult: LongInt; + range, maxRange: integer; +begin +// This is a very simple test to let a RC plane fly in a straight line, without dropping any bombs +// TODO: Teach AI how to steer +// TODO: Teach AI how to drop bombs +// TODO: Teach AI how to predict fire behavior +Flags:= Flags; // avoid compiler hint +if Level = 5 then + exit(BadTurn) +else if Level = 4 then + maxRange:= 2200 +else if Level = 3 then + maxRange:= 2900 +else if Level = 2 then + maxRange:= 3500 +else + maxRange:= 3900; +TestRCPlane:= BadTurn; +ap.ExplR:= 0; +ap.Time:= 0; +ap.Power:= 1; +meX:= hwFloat2Float(Me^.X); +meY:= hwFloat2Float(Me^.Y); +x:= meX; +y:= meY; +range:= Metric(trunc(x), trunc(y), Targ.Point.X, Targ.Point.Y); +if ( range < MIN_RANGE ) or ( range > maxRange) then + exit(BadTurn); -function TestSnowball(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; +Vx:= (Targ.Point.X - x) * 1 / 1024; +Vy:= (Targ.Point.Y - y) * 1 / 1024; +ap.Angle:= DxDy2AttackAnglef(Vx, -Vy); +repeat + x:= x + vX; + y:= y + vY; + rx:= trunc(x); + ry:= trunc(y); + if ((Me = CurrentHedgehog^.Gear) and TestColl(rx, ry, 8)) or + ((Me <> CurrentHedgehog^.Gear) and TestCollExcludingMe(Me^.Hedgehog^.Gear, rx, ry, 8)) then + begin + x:= x + vX * 8; + y:= y + vY * 8; + + // Intentionally low rating to discourage use + if Level = 1 then + valueResult:= RateExplosion(Me, rx, ry, 26, afTrackFall or afErasesLand) + else + valueResult:= RateExplosion(Me, rx, ry, 26); + + // Check range again in case the plane collided before target + range:= Metric(trunc(meX), trunc(meY), rx, ry); + if ( range < MIN_RANGE ) or ( range > maxRange) then + exit(BadTurn); + + // If impact location is close, above us and wind blows in our direction, + // there's a risk of fire flying towards us, so fail in this case. + if (Level < 3) and (range <= 600) and (meY >= ry) and + (((ap.Angle < 0) and (windSpeed > 0)) or ((ap.Angle > 0) and (windSpeed < 0))) then + exit(BadTurn); + + // Apply inaccuracy + if (not aiLaserSighting) then + inc(ap.Angle, AIrndSign(random((Level - 1) * 9))); + + if (valueResult <= 0) then + valueResult:= BadTurn; + exit(valueResult) + end +until (Abs(Targ.Point.X - trunc(x)) + Abs(Targ.Point.Y - trunc(y)) < 4) + or (x < 0) + or (y < 0) + or (trunc(x) > LAND_WIDTH) + or (trunc(y) > LAND_HEIGHT); + +TestRCPlane:= BadTurn +end; + +function TestSnowball(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; var Vx, Vy, r: real; rTime: LongInt; EX, EY: LongInt; @@ -418,6 +518,7 @@ value: LongInt; begin +Flags:= Flags; // avoid compiler hint meX:= hwFloat2Float(Me^.X); meY:= hwFloat2Float(Me^.Y); ap.Time:= 0; @@ -473,74 +574,106 @@ TestSnowball:= valueResult end; -function TestMolotov(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; -var Vx, Vy, r: real; - Score, EX, EY, valueResult: LongInt; - TestTime: LongInt; - targXWrap, x, y, dY, meX, meY: real; +function TestMolotov(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +const timeLimit = 50; + Density : real = 2.0; +var Vx, Vy, r, meX, meY: real; + rTime: LongInt; + EX, EY: LongInt; + valueResult: LongInt; + targXWrap, x, y, dX, dY: real; t: LongInt; + value, range: LongInt; begin +Flags:= Flags; // avoid compiler hint meX:= hwFloat2Float(Me^.X); meY:= hwFloat2Float(Me^.Y); -valueResult:= BadTurn; -TestTime:= 0; +ap.Time:= 0; +rTime:= 350; ap.ExplR:= 0; if (WorldEdge = weWrap) then if (Targ.Point.X < meX) then targXWrap:= Targ.Point.X + (RightX-LeftX) else targXWrap:= Targ.Point.X - (RightX-LeftX); +valueResult:= BadTurn; repeat - inc(TestTime, 300); + rTime:= rTime + 300 + Level * 50 + random(300); if (WorldEdge = weWrap) and (random(2)=0) then - Vx:= (targXWrap - meX) / TestTime - else Vx:= (Targ.Point.X - meX) / TestTime; - Vy:= cGravityf * (TestTime div 2) - Targ.Point.Y - meY / TestTime; + Vx:= (targXWrap + AIrndSign(2) + AIrndOffset(Targ, Level) - meX) / rTime + else + Vx:= (Targ.Point.X + AIrndSign(2) + AIrndOffset(Targ, Level) - meX) / rTime; + if (GameFlags and gfMoreWind) <> 0 then + Vx:= -(windSpeed / Density) * rTime * 0.5 + Vx; + Vy:= cGravityf * rTime * 0.5 - (Targ.Point.Y + 1 - meY) / rTime; r:= sqr(Vx) + sqr(Vy); + if not (r > 1) then begin x:= meX; y:= meY; + dX:= Vx; dY:= -Vy; - t:= TestTime; + t:= rTime; repeat x:= CheckWrap(x); - x:= x + Vx; + x:= x + dX; + if (GameFlags and gfMoreWind) <> 0 then + dX:= dX + windSpeed / Density; + y:= y + dY; dY:= dY + cGravityf; dec(t) - until (((Me = CurrentHedgehog^.Gear) and TestColl(trunc(x), trunc(y), 6)) or - ((Me <> CurrentHedgehog^.Gear) and TestCollExcludingMe(Me^.Hedgehog^.Gear, trunc(x), trunc(y), 6))) or (t = 0); + until (((Me = CurrentHedgehog^.Gear) and TestColl(trunc(x), trunc(y), 5)) or + ((Me <> CurrentHedgehog^.Gear) and TestCollExcludingMe(Me^.Hedgehog^.Gear, trunc(x), trunc(y), 5))) or (t < -timeLimit); + EX:= trunc(x); EY:= trunc(y); - if t < 50 then - Score:= RateExplosion(Me, EX, EY, 97) // average of 17 attempts, most good, but some failing spectacularly - else - Score:= BadTurn; + + // Sanity check 1: Make sure we're not too close to impact location + range:= Metric(trunc(meX), trunc(meY), EX, EY); + if (range < 150) and (Level < 5) then + exit(BadTurn); + + // Sanity check 2: If impact location is close, above us and wind blows + // towards us, there's a risk of fire flying towards us, so fail in this case. + if (Level < 3) and (range <= 600) and (trunc(meY) >= EX) and + (((ap.Angle < 0) and (windSpeed > 0)) or ((ap.Angle > 0) and (windSpeed < 0))) then + exit(BadTurn); - if valueResult < Score then + if t >= -timeLimit then + value:= RateExplosion(Me, EX, EY, 97) // average of 17 attempts, most good, but some failing spectacularly + else + value:= BadTurn; + + if (value = 0) and (Targ.Kind = gtHedgehog) and (Targ.Score > 0) then + value := BadTurn; + + if (valueResult < value) or ((valueResult = value) and (Level < 3)) then begin - ap.Angle:= DxDy2AttackAnglef(Vx, Vy) + AIrndSign(random(Level)); - ap.Power:= trunc(sqrt(r) * cMaxPower) + AIrndSign(random(Level) * 15); + ap.Angle:= DxDy2AttackAnglef(Vx, Vy) + AIrndSign(random((Level - 1) * 9)); + ap.Power:= trunc(sqrt(r) * cMaxPower) - random((Level - 1) * 17 + 1); ap.ExplR:= 100; ap.ExplX:= EX; ap.ExplY:= EY; - valueResult:= Score + valueResult:= value end; end -until (TestTime > 5050 - Level * 800); +until rTime > 5050 - Level * 800; TestMolotov:= valueResult end; -function TestGrenade(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; +function TestGrenade(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; const tDelta = 24; + Density : real = 1.5; var Vx, Vy, r: real; Score, EX, EY, valueResult: LongInt; TestTime: LongInt; - targXWrap, x, y, meX, meY, dY: real; + targXWrap, x, y, meX, meY, dX, dY: real; t: LongInt; begin valueResult:= BadTurn; TestTime:= 0; +ap.Bounce:= 0; ap.ExplR:= 0; meX:= hwFloat2Float(Me^.X); meY:= hwFloat2Float(Me^.Y); @@ -552,18 +685,24 @@ inc(TestTime, 1000); if (WorldEdge = weWrap) and (random(2)=0) then Vx:= (targXWrap + AIrndOffset(Targ, Level) - meX) / (TestTime + tDelta) - else Vx:= (Targ.Point.X + AIrndOffset(Targ, Level) - meX) / (TestTime + tDelta); + else + Vx:= (Targ.Point.X + AIrndOffset(Targ, Level) - meX) / (TestTime + tDelta); + if (GameFlags and gfMoreWind) <> 0 then + Vx:= -(windSpeed / Density) * (TestTime + tDelta) * 0.5 + Vx; Vy:= cGravityf * ((TestTime + tDelta) div 2) - (Targ.Point.Y - meY) / (TestTime + tDelta); r:= sqr(Vx) + sqr(Vy); if not (r > 1) then begin x:= meX; y:= meY; + dX:= Vx; dY:= -Vy; t:= TestTime; repeat x:= CheckWrap(x); - x:= x + Vx; + x:= x + dX; + if (GameFlags and gfMoreWind) <> 0 then + dX:= dX + windSpeed / Density; y:= y + dY; dY:= dY + cGravityf; dec(t) @@ -572,7 +711,7 @@ EX:= trunc(x); EY:= trunc(y); if t < 50 then - if Level = 1 then + if (Level = 1) and (Flags and amtest_NoTrackFall = 0) then Score:= RateExplosion(Me, EX, EY, 101, afTrackFall or afErasesLand) else Score:= RateExplosion(Me, EX, EY, 101) else @@ -594,23 +733,28 @@ TestGrenade:= valueResult end; -function TestClusterBomb(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; +function TestClusterBomb(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; const tDelta = 24; + Density : real = 1.5; var Vx, Vy, r: real; Score, EX, EY, valueResult: LongInt; TestTime: Longword; - x, y, dY, meX, meY: real; + x, y, dX, dY, meX, meY: real; t: LongInt; begin +Flags:= Flags; // avoid compiler hint valueResult:= BadTurn; TestTime:= 500; +ap.Bounce:= 0; ap.ExplR:= 0; meX:= hwFloat2Float(Me^.X); meY:= hwFloat2Float(Me^.Y); repeat inc(TestTime, 900); + if (GameFlags and gfMoreWind) <> 0 then + Vx:= (-(windSpeed / Density) * (TestTime + tDelta) * 0.5) + ((Targ.Point.X - meX) / (TestTime + tDelta)) // Try to overshoot slightly, seems to pay slightly better dividends in terms of hitting cluster - if meX 1) then begin x:= meX; + dX:= Vx; y:= meY; dY:= -Vy; t:= TestTime; repeat - x:= x + Vx; + x:= x + dX; + if (GameFlags and gfMoreWind) <> 0 then + dX:= dX + windSpeed / Density; y:= y + dY; dY:= dY + cGravityf; dec(t) @@ -651,14 +798,16 @@ TestClusterBomb:= valueResult end; -function TestWatermelon(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; +function TestWatermelon(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; const tDelta = 24; + Density : real = 2.0; var Vx, Vy, r: real; Score, EX, EY, valueResult: LongInt; TestTime: Longword; - targXWrap, x, y, dY, meX, meY: real; + targXWrap, x, y, dX, dY, meX, meY: real; t: LongInt; begin +Flags:= Flags; // avoid compiler hint valueResult:= BadTurn; TestTime:= 500; ap.ExplR:= 0; @@ -671,19 +820,26 @@ repeat inc(TestTime, 900); if (WorldEdge = weWrap) and (random(2)=0) then - Vx:= (targXWrap - meX) / (TestTime + tDelta) - else Vx:= (Targ.Point.X - meX) / (TestTime + tDelta); + Vx:= (targXWrap - meX) / (TestTime + tDelta) + else + Vx:= (Targ.Point.X - meX) / (TestTime + tDelta); + if (GameFlags and gfMoreWind) <> 0 then + Vx:= -(windSpeed / Density) * (TestTime + tDelta) * 0.5 + Vx; + Vy:= cGravityf * ((TestTime + tDelta) div 2) - ((Targ.Point.Y-50) - meY) / (TestTime + tDelta); r:= sqr(Vx)+sqr(Vy); if not (r > 1) then begin x:= meX; + dX:= Vx; y:= meY; dY:= -Vy; t:= TestTime; repeat x:= CheckWrap(x); - x:= x + Vx; + x:= x + dX; + if (GameFlags and gfMoreWind) <> 0 then + dX:= dX + windSpeed / Density; y:= y + dY; dY:= dY + cGravityf; dec(t) @@ -734,13 +890,14 @@ Solve:= 0 end; -function TestMortar(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; -//const tDelta = 24; +function TestMortar(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +const Density : real = 1.0; var Vx, Vy: real; Score, EX, EY: LongInt; TestTime: Longword; - x, y, dY, meX, meY: real; + x, y, dX, dY, meX, meY: real; begin +Flags:= Flags; // avoid compiler hint TestMortar:= BadTurn; ap.ExplR:= 0; @@ -756,14 +913,19 @@ exit(BadTurn); Vx:= (Targ.Point.X - meX) / TestTime; + if (GameFlags and gfMoreWind) <> 0 then + Vx:= -(windSpeed / Density) * TestTime * 0.5 + Vx; Vy:= cGravityf * (TestTime div 2) - (Targ.Point.Y - meY) / TestTime; x:= meX; + dX:= Vx; y:= meY; dY:= -Vy; repeat - x:= x + Vx; + x:= x + dX; + if (GameFlags and gfMoreWind) <> 0 then + dX:= dX + windSpeed / Density; y:= y + dY; dY:= dY + cGravityf; EX:= trunc(x); @@ -796,7 +958,7 @@ end; end; -function TestShotgun(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; +function TestShotgun(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; const MIN_RANGE = 80; MAX_RANGE = 400; @@ -804,6 +966,7 @@ rx, ry, valueResult: LongInt; range: integer; begin +Flags:= Flags; // avoid compiler hint TestShotgun:= BadTurn; ap.ExplR:= 0; ap.Time:= 0; @@ -817,6 +980,9 @@ Vx:= (Targ.Point.X - x) * 1 / 1024; Vy:= (Targ.Point.Y - y) * 1 / 1024; ap.Angle:= DxDy2AttackAnglef(Vx, -Vy); +// Apply inaccuracy +if (not aiLaserSighting) then + inc(ap.Angle, AIrndSign(random((Level - 1) * 10))); repeat x:= x + vX; y:= y + vY; @@ -849,11 +1015,12 @@ TestShotgun:= BadTurn end; -function TestDesertEagle(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; +function TestDesertEagle(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; var Vx, Vy, x, y, t: real; d: Longword; ix, iy, valueResult: LongInt; begin +Flags:= Flags; // avoid compiler hint if (Level > 4) or (Targ.Score < 0) or (Targ.Kind <> gtHedgehog) then exit(BadTurn); Level:= Level; // avoid compiler hint ap.ExplR:= 1; @@ -870,6 +1037,9 @@ Vx:= (Targ.Point.X - x) * t; Vy:= (Targ.Point.Y - y) * t; ap.Angle:= DxDy2AttackAnglef(Vx, -Vy); +// Apply inaccuracy +if (not aiLaserSighting) then + inc(ap.Angle, AIrndSign(random((Level - 1) * 10))); d:= 0; ix:= trunc(x); @@ -902,11 +1072,12 @@ end; -function TestSniperRifle(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; +function TestSniperRifle(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; var Vx, Vy, x, y, t, dmg: real; d: Longword; //fallDmg: LongInt; begin +Flags:= Flags; // avoid compiler hint if (Level > 3) or (Targ.Score < 0) or (Targ.Kind <> gtHedgehog) then exit(BadTurn); Level:= Level; // avoid compiler hint ap.ExplR:= 0; @@ -923,6 +1094,9 @@ Vx:= (Targ.Point.X - x) * t; Vy:= (Targ.Point.Y - y) * t; ap.Angle:= DxDy2AttackAnglef(Vx, -Vy); +// Apply inaccuracy +inc(ap.Angle, AIrndSign(random((Level - 1) * 5))); + d:= 0; repeat @@ -944,11 +1118,12 @@ end; -function TestBaseballBat(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; +function TestBaseballBat(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; var valueResult, a, v1, v2: LongInt; x, y, trackFall: LongInt; dx, dy: real; begin +Flags:= Flags; // avoid compiler hint Targ:= Targ; // avoid compiler hint if Level < 3 then trackFall:= afTrackFall @@ -996,10 +1171,11 @@ TestBaseballBat:= valueResult; end; -function TestFirePunch(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; +function TestFirePunch(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; var valueResult, v1, v2, i: LongInt; x, y, trackFall: LongInt; begin +Flags:= Flags; // avoid compiler hint Targ:= Targ; // avoid compiler hint if Level = 1 then trackFall:= afTrackFall @@ -1054,10 +1230,11 @@ end; -function TestWhip(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; +function TestWhip(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; var valueResult, v1, v2: LongInt; x, y, trackFall: LongInt; begin +Flags:= Flags; // avoid compiler hint Targ:= Targ; // avoid compiler hint if Level = 1 then trackFall:= afTrackFall @@ -1109,12 +1286,13 @@ TestWhip:= valueResult; end; -function TestKamikaze(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; +function TestKamikaze(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; const step = 8; var valueResult, i, v, tx: LongInt; trackFall: LongInt; t, d, x, y, dx, dy, cx: real; begin +Flags:= Flags; // avoid compiler hint ap.ExplR:= 0; ap.Time:= 0; ap.Power:= 1; @@ -1144,7 +1322,10 @@ dx:= (Targ.Point.X - x) * t; dy:= (Targ.Point.Y - y) * t; - ap.Angle:= DxDy2AttackAnglef(dx, -dy) + ap.Angle:= DxDy2AttackAnglef(dx, -dy); + // Apply inaccuracy + if (not aiLaserSighting) then + inc(ap.Angle, AIrndSign(random((Level - 1) * 10))); end; if dx >= 0 then cx:= 0.45 else cx:= -0.45; @@ -1157,7 +1338,7 @@ valueResult:= valueResult + RateShove(Me, trunc(x), trunc(y) , 30, 30, 25 - , cx, -0.9, trackFall or afSetSkip); + , cx, -0.9, trackFall or afSetSkip or afIgnoreMe); end; if (d < 10) and (dx = 0) then @@ -1167,14 +1348,14 @@ tx:= trunc(x); v:= RateShove(Me, tx, trunc(y) , 30, 30, 25 - , -cx, -0.9, trackFall); + , -cx, -0.9, trackFall or afIgnoreMe); for i:= 1 to 512 div step - 2 do begin y:= y + dy; v:= v + RateShove(Me, tx, trunc(y) , 30, 30, 25 - , -cx, -0.9, trackFall or afSetSkip); + , -cx, -0.9, trackFall or afSetSkip or afIgnoreMe); end end; @@ -1187,18 +1368,19 @@ v:= RateShove(Me, trunc(x), trunc(y) , 30, 30, 25 - , cx, -0.9, trackFall); + , cx, -0.9, trackFall or afIgnoreMe); valueResult:= valueResult + v - KillScore * friendlyfactor div 100 * 1024; if v < 65536 then - inc(valueResult, RateExplosion(Me, trunc(x), trunc(y), 30)); + inc(valueResult, RateExplosion(Me, trunc(x), trunc(y), 30, afIgnoreMe)); TestKamikaze:= valueResult; end; -function TestHammer(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; +function TestHammer(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; var rate: LongInt; begin +Flags:= Flags; // avoid compiler hint Level:= Level; // avoid compiler hint Targ:= Targ; @@ -1213,14 +1395,16 @@ TestHammer:= rate; end; -function TestAirAttack(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; +function TestAirAttack(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; const cShift = 4; -var bombsSpeed, X, Y, dY: real; + Density : real = 2.0; +var bombsSpeed, X, Y, dX, dY: real; b: array[0..9] of boolean; dmg: array[0..9] of LongInt; fexit: boolean; i, t, valueResult: LongInt; begin +Flags:= Flags; // avoid compiler hint ap.ExplR:= 0; ap.Time:= 0; if (Level > 3) or (cGravityf = 0) then @@ -1234,6 +1418,7 @@ X:= Targ.Point.X - 135 - cShift; // hh center - cShift X:= X - bombsSpeed * sqrt(((Targ.Point.Y + 128) * 2) / cGravityf); Y:= -128; +dX:= bombsSpeed; dY:= 0; for i:= 0 to 9 do @@ -1244,7 +1429,9 @@ valueResult:= 0; repeat - X:= X + bombsSpeed; + X:= X + dX; + if (GameFlags and gfMoreWind) <> 0 then + dX:= dX + windSpeed / Density; Y:= Y + dY; dY:= dY + cGravityf; fexit:= true; @@ -1285,12 +1472,378 @@ TestAirAttack:= valueResult; end; +function TestDrillStrike(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +const cShift = 4; +var bombsSpeed, X, Y, dX, dY, drillX, drillY: real; + t2: real; + b: array[0..9] of boolean; + dmg: array[0..9] of LongInt; + fexit, collided: boolean; + i, t, value, valueResult, attackTime, drillTimer, targetX: LongInt; +begin +Flags:= Flags; // avoid compiler hint +ap.ExplR:= 0; +// TODO: Add support for More Wind +if (Level > 3) or (cGravityf = 0) or ((GameFlags and gfMoreWind) <> 0) then + exit(BadTurn); -function TestTeleport(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; +ap.Angle:= 0; +targetX:= Targ.Point.X; +ap.AttackPutY:= Targ.Point.Y; + +bombsSpeed:= hwFloat2Float(cBombsSpeed); +X:= Targ.Point.X - 135 - cShift; // hh center - cShift +X:= X - bombsSpeed * sqrt(((Targ.Point.Y + 128) * 2) / cGravityf); +Y:= -128; +dX:= bombsSpeed; +dY:= 0; + +valueResult:= 0; + +attackTime:= 0; +while attackTime <= 4000 do + begin + inc(attackTime, 1000); + value:= 0; + for i:= 0 to 9 do + begin + b[i]:= true; + dmg[i]:= 0 + end; + + repeat + X:= X + dX; + Y:= Y + dY; + dY:= dY + cGravityf; + fexit:= true; + + for i:= 0 to 9 do + if b[i] then + begin + fexit:= false; + collided:= false; + drillX:= trunc(X) + LongWord(i * 30); + drillY:= trunc(Y); + // Collided with land ... simulate drilling + if TestCollExcludingObjects(trunc(drillX), trunc(drillY), 4) and + (Abs(Targ.Point.X - trunc(X)) + Abs(Targ.Point.Y - trunc(Y)) > 21) then + begin + drillTimer := attackTime; + t2 := 0.5 / sqrt(sqr(dX) + sqr(dY)); + dX := dX * t2; + dY := dY * t2; + repeat + drillX:= drillX + dX; + drillY:= drillY + dY; + dec(drillTimer, 10); + until (Abs(Targ.Point.X - drillX) + Abs(Targ.Point.Y - drillY) < 22) + or (drillX < 0) + or (drillY < 0) + or (trunc(drillX) > LAND_WIDTH) + or (trunc(drillY) > LAND_HEIGHT) + // TODO: Simulate falling again when rocket has left terrain again + or (drillTimer <= 0); + collided:= true; + end + // Collided with something else ... record collision + else if TestColl(trunc(drillX), trunc(drillY), 4) then + collided:= true; + + // Simulate explosion + if collided then + begin + b[i]:= false; + dmg[i]:= RateExplosion(Me, trunc(drillX), trunc(drillY), 58); + // 58 (instead of 60) for better prediction (hh moves after explosion of one of the rockets) + end; + end; + until fexit or (Y > cWaterLine); + + for i:= 0 to 5 do + if dmg[i] <> BadTurn then + inc(value, dmg[i]); + t:= value; + targetX:= Targ.Point.X - 60; + + for i:= 0 to 3 do + if dmg[i] <> BadTurn then + begin + dec(t, dmg[i]); + inc(t, dmg[i + 6]); + if t > value then + begin + value:= t; + targetX:= Targ.Point.X - 30 - cShift + i * 30 + end + end; + + if value > valueResult then + begin + valueResult:= value; + ap.AttackPutX:= targetX; + ap.Time:= attackTime; + end; +end; + +if valueResult <= 0 then + valueResult:= BadTurn +else + begin + // Weaker AI has chance to get the time wrong by 1-3 seconds + if Level = 5 then + // +/- 3 seconds + ap.Time:= ap.Time + (3 - random(7)) * 1000 + else if Level = 4 then + // +/- 2 seconds + ap.Time:= ap.Time + (2 - random(5)) * 1000 + else if Level = 3 then + // +/- 1 second + if (random(2) = 0) then + ap.Time:= ap.Time + (1 - random(3)) * 1000 + else if Level = 2 then + // 50% chance for +/- 1 second + if (random(2) = 0) then + ap.Time:= ap.Time + (1 - random(3)) * 1000; + ap.Time:= Min(5000, Max(1000, ap.Time)); + end; + +TestDrillStrike:= valueResult; +end; + +function TestMineStrike(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +const cShift = 4; + Density : real = 1.0; +var minesSpeed, X, Y, dX, dY: real; + b: array[0..9] of boolean; + dmg: array[0..9] of LongInt; + fexit: boolean; + i, t, valueResult: LongInt; +begin +Flags:= Flags; // avoid compiler hint +ap.ExplR:= 0; +ap.Time:= 0; + +// AI currently only supports cMinesTime = 0 because it's the most +// predictable. +// Other cMinesTime values are risky because of bouncy mines; +// so they are unsupported. +// TODO: Implement mine strike for other values of MineTime +// TODO: Teach AI to avoid hitting their own with mines +if (Level > 3) or (cGravityf = 0) or (cMinesTime <> 0) then + exit(BadTurn); + +ap.Angle:= 0; +ap.AttackPutX:= Targ.Point.X; +ap.AttackPutY:= Targ.Point.Y; + +minesSpeed:= hwFloat2Float(cBombsSpeed); +X:= Targ.Point.X - 135 - cShift; // hh center - cShift +X:= X - minesSpeed * sqrt(((Targ.Point.Y + 128) * 2) / cGravityf); +Y:= -128; +dX:= minesSpeed; +dY:= 0; + +for i:= 0 to 9 do + begin + b[i]:= true; + dmg[i]:= 0 + end; +valueResult:= 0; + +repeat + X:= X + dX; + if (GameFlags and (gfMoreWind or gfInfAttack)) <> 0 then + dX:= dX + windSpeed / Density; + Y:= Y + dY; + dY:= dY + cGravityf; + fexit:= true; + + for i:= 0 to 9 do + if b[i] then + begin + fexit:= false; + if TestColl(trunc(X) + LongWord(i * 30), trunc(Y), 4) then + begin + b[i]:= false; + dmg[i]:= RateExplosion(Me, trunc(X) + LongWord(i * 30), trunc(Y), 96) + end + end; +until fexit or (Y > cWaterLine); + +for i:= 0 to 5 do + if dmg[i] <> BadTurn then + inc(valueResult, dmg[i]); +t:= valueResult; +ap.AttackPutX:= Targ.Point.X - 60; + +for i:= 0 to 3 do + if dmg[i] <> BadTurn then + begin + dec(t, dmg[i]); + inc(t, dmg[i + 6]); + if t > valueResult then + begin + valueResult:= t; + ap.AttackPutX:= Targ.Point.X - 30 - cShift + i * 30 + end + end; + +if valueResult <= 0 then + valueResult:= BadTurn; +TestMineStrike:= valueResult; +end; + +function TestSMine(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +const timeLimit = 50; + Density : real = 1.6; +var Vx, Vy, r, meX, meY: real; + rTime: LongInt; + EX, EY: LongInt; + valueResult: LongInt; + targXWrap, x, y, dX, dY: real; + t: LongInt; + value: LongInt; +begin +Flags:= Flags; // avoid compiler hint +meX:= hwFloat2Float(Me^.X); +meY:= hwFloat2Float(Me^.Y); +ap.Time:= 0; +rTime:= 350; +ap.ExplR:= 0; +if (WorldEdge = weWrap) then + if (Targ.Point.X < meX) then + targXWrap:= Targ.Point.X + (RightX-LeftX) + else targXWrap:= Targ.Point.X - (RightX-LeftX); +valueResult:= BadTurn; +repeat + rTime:= rTime + 300 + Level * 50 + random(300); + if (WorldEdge = weWrap) and (random(2)=0) then + Vx:= (targXWrap + AIrndSign(2) + AIrndOffset(Targ, Level) - meX) / rTime + else + Vx:= (Targ.Point.X + AIrndSign(2) + AIrndOffset(Targ, Level) - meX) / rTime; + if (GameFlags and gfMoreWind) <> 0 then + Vx:= -(windSpeed / Density) * rTime * 0.5 + Vx; + Vy:= cGravityf * rTime * 0.5 - (Targ.Point.Y + 1 - meY) / rTime; + r:= sqr(Vx) + sqr(Vy); + + if not (r > 1) then + begin + x:= meX; + y:= meY; + dX:= Vx; + dY:= -Vy; + t:= rTime; + repeat + x:= CheckWrap(x); + x:= x + dX; + if (GameFlags and gfMoreWind) <> 0 then + dX:= dX + windSpeed / Density; + + y:= y + dY; + dY:= dY + cGravityf; + dec(t) + until (((Me = CurrentHedgehog^.Gear) and TestColl(trunc(x), trunc(y), 2)) or + ((Me <> CurrentHedgehog^.Gear) and TestCollExcludingMe(Me^.Hedgehog^.Gear, trunc(x), trunc(y), 2))) or (t < -timeLimit); + + EX:= trunc(x); + EY:= trunc(y); + + if t >= -timeLimit then + if (Level = 1) and (Flags and amtest_NoTrackFall = 0) then + value:= RateExplosion(Me, EX, EY, 61, afTrackFall) + else + value:= RateExplosion(Me, EX, EY, 61); + + if (value = 0) and (Targ.Kind = gtHedgehog) and (Targ.Score > 0) then + value := BadTurn; + + if (valueResult < value) or ((valueResult = value) and (Level < 3)) then + begin + ap.Angle:= DxDy2AttackAnglef(Vx, Vy) + AIrndSign(random((Level - 1) * 9)); + ap.Power:= trunc(sqrt(r) * cMaxPower) - random((Level - 1) * 17 + 1); + ap.ExplR:= 60; + ap.ExplX:= EX; + ap.ExplY:= EY; + valueResult:= value + end; + end +until rTime > 5050 - Level * 800; +TestSMine:= valueResult +end; + +function TestPiano(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +const BOUNCES = 5; +var X, Y: real; + dmg: array[0..BOUNCES-1] of LongInt; + i, e, rate, valueResult: LongInt; +begin +Flags:= Flags; // avoid compiler hint +ap.ExplR:= 0; +ap.Time:= 0; +if (cGravityf <= 0) then + exit(BadTurn); + +if (Level > 2) then + exit(BadTurn); + +ap.Angle:= 0; +ap.AttackPutX:= Targ.Point.X; +ap.AttackPutY:= Targ.Point.Y; + +X:= Targ.Point.X; +Y:= -128; + +for i:= 0 to BOUNCES-1 do + dmg[i]:= 0; + +i:= 1; +repeat + // Piano goes down + Y:= Y + 11; + if TestCollExcludingMe(Me^.Hedgehog^.Gear, trunc(X), trunc(Y), 32) then + begin + for e:= -1 to 1 do + begin + rate:= RateExplosion(Me, trunc(X) + 30*e, trunc(Y)+40, 161, afIgnoreMe); + if rate <> BadTurn then + dmg[i]:= dmg[i] + rate; + end; + + if (i > 1) and (dmg[i] > 0) then + dmg[i]:= dmg[i] div 2; + inc(i); + // Skip past the blast hole + Y:= Y + 41 + end; +until (i > BOUNCES) or (Y > cWaterLine); + +if (i = 0) and (Y > cWaterLine) then + exit(BadTurn); + +valueResult:= 0; +for i:= 0 to BOUNCES do + if dmg[i] <= BadTurn then + begin + valueResult:= BadTurn; + break; + end + else + inc(valueResult, dmg[i]); +ap.AttackPutX:= Targ.Point.X; + +valueResult:= valueResult - KillScore * friendlyfactor div 100 * 1024; + +if valueResult <= 0 then + valueResult:= BadTurn; +TestPiano:= valueResult; +end; + +function TestTeleport(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; var i, failNum: longword; maxTop: longword; begin +Flags:= Flags; // avoid compiler hint TestTeleport := BadTurn; exit(BadTurn); Level:= Level; // avoid compiler hint @@ -1350,10 +1903,11 @@ end; end; -function TestCake(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; +function TestCake(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; var valueResult, v1, v2: LongInt; cake: TGear; begin +Flags:= Flags; // avoid compiler hint Targ:= Targ; // avoid compiler hint if (Level > 2) then @@ -1406,20 +1960,47 @@ TestCake:= valueResult; end; -function TestDynamite(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams): LongInt; +function TestSeduction(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +var rate: LongInt; +begin +Flags:= Flags; // avoid compiler hint +Level:= Level; // avoid compiler hint +Targ:= Targ; + +if (Level = 5) then + exit(BadTurn); + +ap.ExplR:= 0; +ap.Time:= 0; +ap.Power:= 1; +ap.Angle:= 0; + +rate:= RateSeduction(Me); +if rate <= 0 then + rate:= BadTurn; +TestSeduction:= rate; +end; + +function TestDynamite(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +const Density : real = 2.0; var valueResult: LongInt; x, y, dx, dy: real; EX, EY, t: LongInt; begin +Flags:= Flags; // avoid compiler hint Targ:= Targ; // avoid compiler hint x:= hwFloat2Float(Me^.X) + hwSign(Me^.dX) * 7; y:= hwFloat2Float(Me^.Y); dx:= hwSign(Me^.dX) * 0.03; +if (GameFlags and gfMoreWind) <> 0 then + dx:= -(windSpeed / Density) + dx; dy:= 0; t:= 5000; repeat dec(t); + if (GameFlags and gfMoreWind) <> 0 then + dx:= dx + windSpeed / Density; x:= x + dx; dy:= dy + cGravityf; y:= y + dy; @@ -1450,4 +2031,273 @@ TestDynamite:= valueResult end; +function TestMine(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +const Density : real = 1.0; +var valueResult: LongInt; + x, y, dx, dy: real; + EX, EY, t: LongInt; +begin +Flags:= Flags; // avoid compiler hint +Targ:= Targ; // avoid compiler hint + +x:= hwFloat2Float(Me^.X) + hwSign(Me^.dX) * 7; +y:= hwFloat2Float(Me^.Y); +dx:= hwSign(Me^.dX) * 0.02; +if (GameFlags and gfMoreWind) <> 0 then + dx:= -(windSpeed / Density) + dx; +dy:= 0; +t:= 10000; +repeat + dec(t); + if (GameFlags and gfMoreWind) <> 0 then + dx:= dx + windSpeed / Density; + x:= x + dx; + dy:= dy + cGravityf; + y:= y + dy; + if ((Me = CurrentHedgehog^.Gear) and TestColl(trunc(x), trunc(y), 2)) or + ((Me <> CurrentHedgehog^.Gear) and TestCollExcludingMe(Me^.Hedgehog^.Gear, trunc(x), trunc(y), 2)) then + t:= 0; +until t = 0; + +EX:= trunc(x); +EY:= trunc(y); + +if Level = 1 then + valueResult:= RateExplosion(Me, EX, EY, 51, afTrackFall or afErasesLand) +else + valueResult:= RateExplosion(Me, EX, EY, 51); + +if (valueResult > 0) then + begin + ap.Angle:= 0; + ap.Power:= 1; + ap.Time:= 0; + if (Level < 5) then + // Set minimum mine bounciness for improved aim + ap.Bounce:= 1 + else + ap.Bounce:= 0; + ap.ExplR:= 100; + ap.ExplX:= EX; + ap.ExplY:= EY + end else + valueResult:= BadTurn; + +TestMine:= valueResult +end; + +function TestKnife(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +const timeLimit = 300; + Density : real = 4.0; +var Vx, Vy, r, meX, meY: real; + rTime: LongInt; + EX, EY: LongInt; + valueResult: LongInt; + targXWrap, x, y, dX, dY: real; + t: LongInt; + value, range: LongInt; +begin +Flags:= Flags; // avoid compiler hint +meX:= hwFloat2Float(Me^.X); +meY:= hwFloat2Float(Me^.Y); +ap.Time:= 0; +rTime:= 350; +ap.ExplR:= 0; +if (WorldEdge = weWrap) then + if (Targ.Point.X < meX) then + targXWrap:= Targ.Point.X + (RightX-LeftX) + else + targXWrap:= Targ.Point.X - (RightX-LeftX); +valueResult:= BadTurn; +repeat + rTime:= rTime + 300 + Level * 50 + random(300); + if (WorldEdge = weWrap) and (random(2)=0) then + Vx:= (targXWrap - meX) / rTime + else + Vx:= (Targ.Point.X - meX) / rTime; + if (GameFlags and gfMoreWind) <> 0 then + Vx:= -(windSpeed / Density) * rTime * 0.5 + Vx; + Vy:= cGravityf * rTime * 0.5 - (Targ.Point.Y + 1 - meY) / rTime; + r:= sqr(Vx) + sqr(Vy); + + if not (r > 1) then + begin + x:= meX; + y:= meY; + dX:= Vx; + dY:= -Vy; + t:= rTime; + repeat + x:= CheckWrap(x); + x:= x + dX; + if (GameFlags and gfMoreWind) <> 0 then + dX:= dX + windSpeed / Density; + + y:= y + dY; + dY:= dY + cGravityf; + dec(t) + until (((Me = CurrentHedgehog^.Gear) and TestColl(trunc(x), trunc(y), 7)) or + ((Me <> CurrentHedgehog^.Gear) and TestCollExcludingMe(Me^.Hedgehog^.Gear, trunc(x), trunc(y), 7))) or (t < -timeLimit); + + EX:= trunc(x); + EY:= trunc(y); + + // Sanity check: Make sure we're not too close to impact location + range:= Metric(trunc(meX), trunc(meY), EX, EY); + if (range <= 40) then + exit(BadTurn); + + if t >= -timeLimit then + value:= RateShove(Me, EX, EY, 16, trunc(sqr((abs(dY)+abs(dX))*40000/10000)), 0, dX, dY, 0) + else + value:= BadTurn; + + if (value = 0) and (Targ.Kind = gtHedgehog) and (Targ.Score > 0) then + value := BadTurn; + + if (valueResult < value) or ((valueResult = value) and (Level = 1)) then + begin + ap.Angle:= DxDy2AttackAnglef(Vx, Vy) + AIrndSign(random((Level - 1) * 12)); + ap.Power:= trunc(sqrt(r) * cMaxPower) - random((Level - 1) * 22 + 1); + valueResult:= value + end; + end +until rTime > 5050 - Level * 800; +TestKnife:= valueResult +end; + +function TestAirMine(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +const + MIN_RANGE = 160; + MAX_RANGE = 2612; +var Vx, Vy, meX, meY, x, y, r: real; + rx, ry, valueResult: LongInt; + range, maxRange: integer; +begin +Flags:= Flags; // avoid compiler hint +maxRange:= MAX_RANGE - ((Level - 1) * 300); +TestAirMine:= BadTurn; +ap.ExplR:= 60; +ap.Time:= 0; +meX:= hwFloat2Float(Me^.X); +meY:= hwFloat2Float(Me^.Y); +x:= meX; +y:= meY; + +// Rough first range check +range:= Metric(trunc(x), trunc(y), Targ.Point.X, Targ.Point.Y); +if ( range < MIN_RANGE ) or ( range > maxRange ) then + exit(BadTurn); + +Vx:= (Targ.Point.X - x) * 1 / 1024; +Vy:= (Targ.Point.Y - y) * 1 / 1024; +ap.Angle:= DxDy2AttackAnglef(Vx, -Vy); +repeat + x:= x + vX; + y:= y + vY; + rx:= trunc(x); + ry:= trunc(y); + if ((Me = CurrentHedgehog^.Gear) and TestColl(rx, ry, 8)) or + ((Me <> CurrentHedgehog^.Gear) and TestCollExcludingMe(Me^.Hedgehog^.Gear, rx, ry, 8)) then + begin + x:= x + vX * 8; + y:= y + vY * 8; + + if Level = 1 then + valueResult:= RateExplosion(Me, rx, ry, 61, afTrackFall) + else + valueResult:= RateExplosion(Me, rx, ry, 61); + + // Precise range calculation required to calculate power; + // The air mine is very sensitive to small changes in power. + r:= sqr(meX - rx) + sqr(meY - ry); + range:= trunc(sqrt(r)); + + if ( range < MIN_RANGE ) or ( range > maxRange ) then + exit(BadTurn); + ap.Power:= ((range + cHHRadius*2) * cMaxPower) div MAX_RANGE; + + // Apply inaccuracy + inc(ap.Power, (random(93*(Level-1)) - 31*(Level-1))); // Level 1 spread: -124 .. 248 + if (not aiLaserSighting) then + inc(ap.Angle, AIrndSign(random((Level - 1) * 10))); + + if (valueResult <= 0) then + valueResult:= BadTurn; + exit(valueResult) + end +until (abs(Targ.Point.X - trunc(x)) + abs(Targ.Point.Y - trunc(y)) < 4) + or (x < 0) + or (y < 0) + or (trunc(x) > LAND_WIDTH) + or (trunc(y) > LAND_HEIGHT); + +TestAirMine := BadTurn +end; + +function TestMinigun(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +const + MAX_RANGE = 400; +var Vx, Vy, x, y: real; + rx, ry, valueResult: LongInt; + range: integer; +begin +// This code is still very similar to TestShotgun, +// but it's a good simple estimate. +// TODO: Simulate random bullets +// TODO: Replace RateShotgun with something else +// TODO: Teach AI to move aim during shooting +Flags:= Flags; // avoid compiler hint +TestMinigun:= BadTurn; +ap.ExplR:= 0; +ap.Time:= 0; +ap.Power:= 1; +x:= hwFloat2Float(Me^.X); +y:= hwFloat2Float(Me^.Y); +range:= Metric(trunc(x), trunc(y), Targ.Point.X, Targ.Point.Y); +if ( range > MAX_RANGE ) then + exit(BadTurn); + +Vx:= (Targ.Point.X - x) * 1 / 1024; +Vy:= (Targ.Point.Y - y) * 1 / 1024; +ap.Angle:= DxDy2AttackAnglef(Vx, -Vy); +// Minigun angle is limited +if (ap.Angle < Ammoz[amMinigun].minAngle) or (ap.Angle > Ammoz[amMinigun].maxAngle) then + exit(BadTurn); + +// Apply inaccuracy +if (not aiLaserSighting) then + inc(ap.Angle, AIrndSign(random((Level - 1) * 10))); +repeat + x:= x + vX; + y:= y + vY; + rx:= trunc(x); + ry:= trunc(y); + if ((Me = CurrentHedgehog^.Gear) and TestColl(rx, ry, 1)) or + ((Me <> CurrentHedgehog^.Gear) and TestCollExcludingMe(Me^.Hedgehog^.Gear, rx, ry, 1)) then + begin + x:= x + vX * 8; + y:= y + vY * 8; + // TODO: Use different rating function + valueResult:= RateShotgun(Me, vX, vY, rx, ry); + + if (valueResult = 0) and (Targ.Kind = gtHedgehog) and (Targ.Score > 0) then + begin + if GameFlags and gfSolidLand = 0 then + valueResult:= 1024 - Metric(Targ.Point.X, Targ.Point.Y, rx, ry) div 64 + else valueResult := BadTurn + end + else + dec(valueResult, Level * 4000); + exit(valueResult) + end +until (Abs(Targ.Point.X - trunc(x)) + Abs(Targ.Point.Y - trunc(y)) < 4) + or (x < 0) + or (y < 0) + or (trunc(x) > LAND_WIDTH) + or (trunc(y) > LAND_HEIGHT); + +TestMinigun:= BadTurn +end; + end. diff -r b3c9f5463cee -r 74ede02bc882 hedgewars/uAIMisc.pas --- a/hedgewars/uAIMisc.pas Sun Jul 05 02:03:08 2020 +0200 +++ b/hedgewars/uAIMisc.pas Sun Jul 05 14:53:44 2020 +0200 @@ -27,6 +27,7 @@ afTrackFall = $00000001; afErasesLand = $00000002; afSetSkip = $00000004; + afIgnoreMe = $00000008; BadTurn = Low(LongInt) div 4; @@ -86,6 +87,7 @@ function RealRateExplosion(Me: PGear; x, y, r: LongInt; Flags: LongWord): LongInt; function RateShove(Me: PGear; x, y, r, power, kick: LongInt; gdX, gdY: real; Flags: LongWord): LongInt; function RateShotgun(Me: PGear; gdX, gdY: real; x, y: LongInt): LongInt; +function RateSeduction(Me: PGear): LongInt; function RateHammer(Me: PGear): LongInt; function HHGo(Gear, AltGear: PGear; var GoInfo: TGoInfo): boolean; @@ -539,20 +541,22 @@ x:= round(CheckWrap(real(x))); fallDmg:= 0; rate:= 0; -// add our virtual position -with Targets.ar[Targets.Count] do - begin - Point.x:= hwRound(Me^.X); - Point.y:= hwRound(Me^.Y); - skip:= false; - matters:= true; - Kind:= gtHedgehog; - Density:= 1; - Radius:= cHHRadius; - Score:= - ThinkingHH^.Health - end; + +if (Flags and afIgnoreMe) = 0 then + // add our virtual position + with Targets.ar[Targets.Count] do + begin + Point.x:= hwRound(Me^.X); + Point.y:= hwRound(Me^.Y); + skip:= false; + matters:= true; + Kind:= gtHedgehog; + Density:= 1; + Radius:= cHHRadius; + Score:= - ThinkingHH^.Health + end; + // rate explosion - if (Flags and afErasesLand <> 0) and (GameFlags and gfSolidLand = 0) then erasure:= r else erasure:= 0; @@ -561,8 +565,9 @@ for i:= 0 to Targets.Count do if not Targets.ar[i].dead then with Targets.ar[i] do - if not matters then hadSkips:= true - else + if not matters then + hadSkips:= true + else begin dmg:= 0; dmgBase:= r + Radius div 2; @@ -832,6 +837,92 @@ ResetTargets; end; +function RateSeduction(Me: PGear): LongInt; +var pX, pY, i, r, rate, subrate, fallDmg: LongInt; + diffX, diffY: LongInt; + meX, meY, dX, dY: hwFloat; + pXr, pYr: real; + hadSkips: boolean; +begin +meX:= Me^.X; +meY:= Me^.Y; +rate:= 0; +for i:= 0 to Targets.Count do + if not Targets.ar[i].dead then + with Targets.ar[i] do + begin + pX:= Point.X; + pY:= Point.Y; + diffX:= pX - hwRound(meX); + diffY:= pY - hwRound(meY); + if (Me^.Hedgehog^.BotLevel < 4) and (abs(diffX) <= cHHRadius*2) and (diffY >= 0) and (diffY <= cHHRadius*2) then + // Don't use seduction if too close to other hog. We could be + // standing on it, so using seduction would remove the ground on + // which we stand on, which is dangerous + exit(BadTurn); + + if (not matters) then + hadSkips:= true + else if matters and (Kind = gtHedgehog) and (abs(pX - hwRound(meX)) + abs(pY - hwRound(meY)) < cSeductionDist) then + begin + r:= trunc(sqrt(sqr(abs(pX - hwRound(meX)))+sqr(abs(pY - hwRound(meY))))); + if r < cSeductionDist then + begin + + if (WorldEdge <> weWrap) or (not (hwAbs(meX - int2hwFloat(pX)) > int2hwFloat(cSeductionDist))) then + dX:= _50 * cGravity * (meX - int2hwFloat(pX)) / _25 + else if (not (hwAbs(meX + int2hwFloat((RightX-LeftX) - pX)) > int2hwFloat(cSeductionDist))) then + dX:= _50 * cGravity * (meX + (int2hwFloat((RightX-LeftX) - pX))) / _25 + else + dX:= _50 * cGravity * (meX - (int2hwFloat((RightX-LeftX) - pX))) / _25; + dY:= -_450 * cMaxWindSpeed * 2; + + + pXr:= pX; + pYr:= pY; + fallDmg:= trunc(TraceShoveFall(pXr, pYr, hwFloat2Float(dX), hwFloat2Float(dY), Targets.ar[i]) * dmgMod); + + // rate damage + if fallDmg < 0 then // drowning + begin + if Score > 0 then + inc(rate, (KillScore + Score div 10) * 1024) // Add a bit of a bonus for bigger hog drownings + else + dec(rate, (KillScore * friendlyfactor div 100 - Score div 10) * 1024) // and more of a punishment for drowning bigger friendly hogs + end + else if (fallDmg) >= abs(Score) then // deadly fall damage + begin + dead:= true; + Targets.reset:= true; + if (hwFloat2Float(dX) < 0.035) then + begin + subrate:= RealRateExplosion(Me, round(pX), round(pY), 61, afErasesLand or afTrackFall); // hog explodes + if abs(subrate) > 2000 then + inc(rate, subrate) + end; + if Score > 0 then + inc(rate, KillScore * 1024 + (fallDmg)) // tiny bonus for dealing more damage than needed to kill + else + dec(rate, KillScore * friendlyfactor div 100 * 1024) + end + else if (fallDmg <> 0) then // non-deadly fall damage + if Score > 0 then + inc(rate, fallDmg * 1024) + else + dec(rate, fallDmg * friendlyfactor div 100 * 1024) + else // no damage, just shoved + if (Score < 0) then + dec(rate, 100); // small penalty for shoving friendly hogs as it might be dangerous + end; + end; + end; + +if hadSkips and (rate <= 0) then + RateSeduction:= BadTurn +else + RateSeduction:= rate * 1024; +end; + function RateHammer(Me: PGear): LongInt; var x, y, i, r, rate: LongInt; hadSkips: boolean; diff -r b3c9f5463cee -r 74ede02bc882 hedgewars/uChat.pas --- a/hedgewars/uChat.pas Sun Jul 05 02:03:08 2020 +0200 +++ b/hedgewars/uChat.pas Sun Jul 05 14:53:44 2020 +0200 @@ -678,12 +678,15 @@ AddChatString(#3 + shortstring(trcmd[sidCmdHsa])); AddChatString(#3 + shortstring(trcmd[sidCmdHta])); AddChatString(#3 + shortstring(trcmd[sidCmdHya])); + AddChatString(#3 + shortstring(trcmd[sidCmdHappy])); + AddChatString(#3 + shortstring(trcmd[sidCmdWave])); AddChatString(#3 + shortstring(trcmd[sidCmdHurrah])); + AddChatString(#3 + shortstring(trcmd[sidCmdShrug])); + AddChatString(#3 + shortstring(trcmd[sidCmdSad])); AddChatString(#3 + shortstring(trcmd[sidCmdIlovelotsoflemonade])); AddChatString(#3 + shortstring(trcmd[sidCmdJuggle])); AddChatString(#3 + shortstring(trcmd[sidCmdRollup])); - AddChatString(#3 + shortstring(trcmd[sidCmdShrug])); - AddChatString(#3 + shortstring(trcmd[sidCmdWave])); + AddChatString(#3 + shortstring(trcmd[sidCmdBubble])); exit end; @@ -721,15 +724,14 @@ end; // hedghog animations/taunts and engine commands - if (not CurrentTeam^.ExtDriven) and (CurrentTeam^.Hedgehogs[0].BotLevel = 0) then - begin - for i:= Low(TWave) to High(TWave) do - if (s = Wavez[i].cmd) then - begin + for i:= Low(TWave) to High(TWave) do + if (s = Wavez[i].cmd) then + begin + // only works for local non-bot teams + if (not CurrentTeam^.ExtDriven) and (CurrentTeam^.Hedgehogs[0].BotLevel = 0) then ParseCommand('/taunt ' + char(i), true); - exit - end; - end; + exit; + end; for j:= Low(TChatCmd) to High(TChatCmd) do if (s = ChatCommandz[j].ChatCmd) then diff -r b3c9f5463cee -r 74ede02bc882 hedgewars/uCollisions.pas --- a/hedgewars/uCollisions.pas Sun Jul 05 02:03:08 2020 +0200 +++ b/hedgewars/uCollisions.pas Sun Jul 05 14:53:44 2020 +0200 @@ -52,6 +52,11 @@ cX, cY: LongInt; //for visual effects only end; +type TKickTest = record + kick: Boolean; + collisionMask: Word; + end; + procedure initModule; procedure freeModule; @@ -65,28 +70,37 @@ function CheckGearsLineCollision(Gear: PGear; oX, oY, tX, tY: hwFloat): PGearArray; function CheckAllGearsLineCollision(SourceGear: PGear; oX, oY, tX, tY: hwFloat): PGearArray; -function UpdateHitOrder(Gear: PGear; Order: LongInt): boolean; -procedure ClearHitOrderLeq(MinOrder: LongInt); +function UpdateHitOrder(Gear: PGear; Order: LongInt): boolean; inline; +function UpdateHitOrder(Gear: PGear; Order: LongInt; Global: boolean): boolean; inline; +function UpdateGlobalHitOrder(Gear: PGear; Order: LongInt): boolean; inline; +procedure ClearHitOrderLeq(MinOrder: LongInt); inline; +procedure ClearGlobalHitOrderLeq(MinOrder: LongInt); inline; procedure ClearHitOrder(); procedure RefillProximityCache(SourceGear: PGear; radius: LongInt); procedure RemoveFromProximityCache(Gear: PGear); procedure ClearProximityCache(); -function TestCollisionXwithGear(Gear: PGear; Dir: LongInt): Word; -function TestCollisionYwithGear(Gear: PGear; Dir: LongInt): Word; +function TestCollisionXImpl(centerX, centerY, radius, direction: LongInt; collisionMask: Word): Word; +function TestCollisionYImpl(centerX, centerY, radius, direction: LongInt; collisionMask: Word): Word; + +function TestCollisionXwithGear(Gear: PGear; Dir: LongInt): Word; inline; +function TestCollisionYwithGear(Gear: PGear; Dir: LongInt): Word; inline; + +function TestCollisionX(Gear: PGear; Dir: LongInt): Word; inline; +function TestCollisionY(Gear: PGear; Dir: LongInt): Word; inline; + +function TestCollisionXwithXYShift(Gear: PGear; ShiftX: hwFloat; ShiftY: LongInt; Dir: LongInt): Word; inline; +function TestCollisionXwithXYShift(Gear: PGear; ShiftX: hwFloat; ShiftY: LongInt; Dir: LongInt; withGear: boolean): Word; inline; +function TestCollisionYwithXYShift(Gear: PGear; ShiftX, ShiftY: LongInt; Dir: LongInt): Word; inline; +function TestCollisionYwithXYShift(Gear: PGear; ShiftX, ShiftY: LongInt; Dir: LongInt; withGear: boolean): Word; inline; + +function TestCollisionXKickImpl(centerX, centerY, radius, direction: LongInt; collisionMask, kickMask: Word): TKickTest; +function TestCollisionYKickImpl(centerX, centerY, radius, direction: LongInt; collisionMask, kickMask: Word): TKickTest; function TestCollisionXKick(Gear: PGear; Dir: LongInt): Word; function TestCollisionYKick(Gear: PGear; Dir: LongInt): Word; -function TestCollisionX(Gear: PGear; Dir: LongInt): Word; -function TestCollisionY(Gear: PGear; Dir: LongInt): Word; - -function TestCollisionXwithXYShift(Gear: PGear; ShiftX: hwFloat; ShiftY: LongInt; Dir: LongInt): Word; inline; -function TestCollisionXwithXYShift(Gear: PGear; ShiftX: hwFloat; ShiftY: LongInt; Dir: LongInt; withGear: boolean): Word; -function TestCollisionYwithXYShift(Gear: PGear; ShiftX, ShiftY: LongInt; Dir: LongInt): Word; inline; -function TestCollisionYwithXYShift(Gear: PGear; ShiftX, ShiftY: LongInt; Dir: LongInt; withGear: boolean): Word; - function TestRectangleForObstacle(x1, y1, x2, y2: LongInt; landOnly: boolean): boolean; function CheckCoordInWater(X, Y: LongInt): boolean; inline; @@ -111,6 +125,7 @@ cinfos: array[0..MAXRECTSINDEX] of TCollisionEntry; ga: TGearArray; ordera: TGearHitOrder; + globalordera: TGearHitOrder; proximitya: TGearProximityCache; procedure AddCI(Gear: PGear); @@ -329,51 +344,80 @@ end; end; -function UpdateHitOrder(Gear: PGear; Order: LongInt): boolean; +function UpdateHitOrderImpl(HitOrder: PGearHitOrder; Gear: PGear; Order: LongInt): boolean; var i: LongInt; begin -UpdateHitOrder:= true; -for i:= 0 to ordera.Count - 1 do - if ordera.ar[i] = Gear then + UpdateHitOrderImpl:= true; + for i := 0 to HitOrder^.Count - 1 do + if HitOrder^.ar[i] = Gear then begin - if Order <= ordera.order[i] then UpdateHitOrder:= false; - ordera.order[i]:= Max(ordera.order[i], order); - exit; + if Order <= HitOrder^.order[i] then + UpdateHitOrderImpl := false; + HitOrder^.order[i] := Max(HitOrder^.order[i], Order); + exit; end; -if ordera.Count > cMaxGearHitOrderInd then - UpdateHitOrder:= false -else + if HitOrder^.Count > cMaxGearHitOrderInd then + UpdateHitOrderImpl := false + else begin - ordera.ar[ordera.Count]:= Gear; - ordera.order[ordera.Count]:= Order; - Inc(ordera.Count); + HitOrder^.ar[HitOrder^.Count] := Gear; + HitOrder^.order[HitOrder^.Count] := Order; + Inc(HitOrder^.Count); end end; -procedure ClearHitOrderLeq(MinOrder: LongInt); +function UpdateHitOrder(Gear: PGear; Order: LongInt): boolean; inline; +begin + UpdateHitOrder := UpdateHitOrderImpl(@ordera, Gear, Order); +end; + +function UpdateHitOrder(Gear: PGear; Order: LongInt; Global: boolean): boolean; inline; +begin + if Global then + UpdateHitOrder := UpdateHitOrderImpl(@ordera, Gear, Order) + else + UpdateHitOrder := UpdateHitOrderImpl(@globalordera, Gear, Order) +end; + +function UpdateGlobalHitOrder(Gear: PGear; Order: LongInt): boolean; inline; +begin + UpdateGlobalHitOrder := UpdateHitOrderImpl(@globalordera, Gear, Order); +end; + +procedure ClearHitOrderLeqImpl(HitOrder: PGearHitOrder; MinOrder: LongInt); var i, freeIndex: LongInt; begin; -freeIndex:= 0; -i:= 0; + freeIndex:= 0; + i:= 0; -while i < ordera.Count do + while i < HitOrder^.Count do begin - if ordera.order[i] <= MinOrder then - Dec(ordera.Count) + if HitOrder^.order[i] <= MinOrder then + Dec(HitOrder^.Count) else + begin + if freeIndex < i then begin - if freeIndex < i then - begin - ordera.ar[freeIndex]:= ordera.ar[i]; - ordera.order[freeIndex]:= ordera.order[i]; - end; + HitOrder^.ar[freeIndex]:= HitOrder^.ar[i]; + HitOrder^.order[freeIndex]:= HitOrder^.order[i]; + end; Inc(freeIndex); - end; + end; Inc(i) end end; +procedure ClearHitOrderLeq(MinOrder: LongInt); inline; +begin + ClearHitOrderLeqImpl(@ordera, MinOrder); +end; + +procedure ClearGlobalHitOrderLeq(MinOrder: LongInt); inline; +begin + ClearHitOrderLeqImpl(@globalordera, MinOrder); +end; + procedure ClearHitOrder(); begin ordera.Count:= 0; @@ -423,194 +467,110 @@ proximitya.Count:= 0; end; -function TestCollisionXwithGear(Gear: PGear; Dir: LongInt): Word; -var x, y, i: LongInt; +function TestCollisionXImpl(centerX, centerY, radius, direction: LongInt; collisionMask: Word): Word; +var x, y, minY, maxY: LongInt; begin -// Special case to emulate the old intersect gear clearing, but with a bit of slop for pixel overlap -if (Gear^.CollisionMask = lfNotCurHogCrate) and (Gear^.Kind <> gtHedgehog) and (Gear^.Hedgehog <> nil) and (Gear^.Hedgehog^.Gear <> nil) and - ((hwRound(Gear^.Hedgehog^.Gear^.X) + Gear^.Hedgehog^.Gear^.Radius + 16 < hwRound(Gear^.X) - Gear^.Radius) or - (hwRound(Gear^.Hedgehog^.Gear^.X) - Gear^.Hedgehog^.Gear^.Radius - 16 > hwRound(Gear^.X) + Gear^.Radius)) then - Gear^.CollisionMask:= lfAll; + if direction < 0 then + x := centerX - radius + else + x := centerX + radius; -x:= hwRound(Gear^.X); -if Dir < 0 then - x:= x - Gear^.Radius -else - x:= x + Gear^.Radius; - -if (x and LAND_WIDTH_MASK) = 0 then + if (x and LAND_WIDTH_MASK) = 0 then begin - y:= hwRound(Gear^.Y) - Gear^.Radius + 1; - i:= y + Gear^.Radius * 2 - 2; - repeat - if (y and LAND_HEIGHT_MASK) = 0 then - if Land[y, x] and Gear^.CollisionMask <> 0 then - exit(Land[y, x] and Gear^.CollisionMask); - inc(y) - until (y > i); + minY := max(centerY - radius + 1, 0); + maxY := min(centerY + radius - 1, LAND_HEIGHT - 1); + for y := minY to maxY do + if Land[y, x] and collisionMask <> 0 then + exit(Land[y, x] and collisionMask); end; -TestCollisionXwithGear:= 0 + TestCollisionXImpl := 0; end; -function TestCollisionYwithGear(Gear: PGear; Dir: LongInt): Word; -var x, y, i: LongInt; +function TestCollisionYImpl(centerX, centerY, radius, direction: LongInt; collisionMask: Word): Word; +var x, y, minX, maxX: LongInt; begin -// Special case to emulate the old intersect gear clearing, but with a bit of slop for pixel overlap -if (Gear^.CollisionMask = lfNotCurHogCrate) and (Gear^.Kind <> gtHedgehog) and (Gear^.Hedgehog <> nil) and (Gear^.Hedgehog^.Gear <> nil) and - ((hwRound(Gear^.Hedgehog^.Gear^.Y) + Gear^.Hedgehog^.Gear^.Radius + 16 < hwRound(Gear^.Y) - Gear^.Radius) or - (hwRound(Gear^.Hedgehog^.Gear^.Y) - Gear^.Hedgehog^.Gear^.Radius - 16 > hwRound(Gear^.Y) + Gear^.Radius)) then - Gear^.CollisionMask:= lfAll; - -y:= hwRound(Gear^.Y); -if Dir < 0 then - y:= y - Gear^.Radius -else - y:= y + Gear^.Radius; + if direction < 0 then + y := centerY - radius + else + y := centerY + radius; -if (y and LAND_HEIGHT_MASK) = 0 then + if (y and LAND_HEIGHT_MASK) = 0 then begin - x:= hwRound(Gear^.X) - Gear^.Radius + 1; - i:= x + Gear^.Radius * 2 - 2; - repeat - if (x and LAND_WIDTH_MASK) = 0 then - if Land[y, x] and Gear^.CollisionMask <> 0 then - begin - exit(Land[y, x] and Gear^.CollisionMask) - end; - inc(x) - until (x > i); + minX := max(centerX - radius + 1, 0); + maxX := min(centerX + radius - 1, LAND_WIDTH - 1); + for x := minX to maxX do + if Land[y, x] and collisionMask <> 0 then + exit(Land[y, x] and collisionMask); end; -TestCollisionYwithGear:= 0 + TestCollisionYImpl := 0; +end; + +function TestCollisionX(Gear: PGear; Dir: LongInt): Word; inline; +begin + TestCollisionX := TestCollisionXImpl(hwRound(Gear^.X), hwRound(Gear^.Y), Gear^.Radius, Dir, Gear^.CollisionMask and lfLandMask); +end; + +function TestCollisionY(Gear: PGear; Dir: LongInt): Word; inline; +begin + TestCollisionY := TestCollisionYImpl(hwRound(Gear^.X), hwRound(Gear^.Y), Gear^.Radius, Dir, Gear^.CollisionMask and lfLandMask); end; -function TestCollisionXKick(Gear: PGear; Dir: LongInt): Word; -var x, y, mx, my, i: LongInt; - pixel: Word; +procedure LegacyFixupX(Gear: PGear); begin -pixel:= 0; -x:= hwRound(Gear^.X); -if Dir < 0 then - x:= x - Gear^.Radius -else - x:= x + Gear^.Radius; - -if (x and LAND_WIDTH_MASK) = 0 then - begin - y:= hwRound(Gear^.Y) - Gear^.Radius + 1; - i:= y + Gear^.Radius * 2 - 2; - repeat - if (y and LAND_HEIGHT_MASK) = 0 then - begin - if Land[y, x] and Gear^.CollisionMask <> 0 then - begin - if ((Land[y, x] and Gear^.CollisionMask) and lfLandMask) <> 0 then - exit(Land[y, x] and Gear^.CollisionMask) - else - pixel:= Land[y, x] and Gear^.CollisionMask; - end; - end; - inc(y) - until (y > i); - end; -TestCollisionXKick:= pixel; +// Special case to emulate the old intersect gear clearing, but with a bit of slop for pixel overlap + if (Gear^.CollisionMask = lfNotCurHogCrate) and (Gear^.Kind <> gtHedgehog) and (Gear^.Hedgehog <> nil) and (Gear^.Hedgehog^.Gear <> nil) and + ((hwRound(Gear^.Hedgehog^.Gear^.X) + Gear^.Hedgehog^.Gear^.Radius + 16 < hwRound(Gear^.X) - Gear^.Radius) or + (hwRound(Gear^.Hedgehog^.Gear^.X) - Gear^.Hedgehog^.Gear^.Radius - 16 > hwRound(Gear^.X) + Gear^.Radius)) then + Gear^.CollisionMask:= lfAll; +end; -if pixel <> 0 then - begin - if hwAbs(Gear^.dX) < cHHKick then - exit; - if (Gear^.State and gstHHJumping <> 0) - and (hwAbs(Gear^.dX) < _0_4) then - exit; - - mx:= hwRound(Gear^.X); - my:= hwRound(Gear^.Y); +procedure LegacyFixupY(Gear: PGear); +begin +// Special case to emulate the old intersect gear clearing, but with a bit of slop for pixel overlap + if (Gear^.CollisionMask = lfNotCurHogCrate) and (Gear^.Kind <> gtHedgehog) and (Gear^.Hedgehog <> nil) and (Gear^.Hedgehog^.Gear <> nil) and + ((hwRound(Gear^.Hedgehog^.Gear^.Y) + Gear^.Hedgehog^.Gear^.Radius + 16 < hwRound(Gear^.Y) - Gear^.Radius) or + (hwRound(Gear^.Hedgehog^.Gear^.Y) - Gear^.Hedgehog^.Gear^.Radius - 16 > hwRound(Gear^.Y) + Gear^.Radius)) then + Gear^.CollisionMask:= lfAll; +end; - for i:= 0 to Pred(Count) do - with cinfos[i] do - if (Gear <> cGear) and - ((mx > x) xor (Dir > 0)) and - ( - ((cGear^.Kind in [gtHedgehog, gtMine, gtKnife]) and ((Gear^.State and gstNotKickable) = 0)) or - // only apply X kick if the barrel is knocked over - ((cGear^.Kind = gtExplosives) and ((cGear^.State and gsttmpflag) <> 0)) - ) and - (sqr(mx - x) + sqr(my - y) <= sqr(Radius + Gear^.Radius + 2)) then - begin - with cGear^ do - begin - dX:= Gear^.dX; - dY:= Gear^.dY * _0_5; - State:= State or gstMoving; - if Kind = gtKnife then State:= State and (not gstCollision); - Active:= true - end; - DeleteCI(cGear); - exit(0); - end - end +function TestCollisionXwithGear(Gear: PGear; Dir: LongInt): Word; inline; +begin + LegacyFixupX(Gear); + TestCollisionXwithGear:= TestCollisionXImpl(hwRound(Gear^.X), hwRound(Gear^.Y), Gear^.Radius, Dir, Gear^.CollisionMask); end; -function TestCollisionYKick(Gear: PGear; Dir: LongInt): Word; -var x, y, mx, my, myr, i: LongInt; - pixel: Word; +function TestCollisionYwithGear(Gear: PGear; Dir: LongInt): Word; inline; begin -pixel:= 0; -y:= hwRound(Gear^.Y); -if Dir < 0 then - y:= y - Gear^.Radius -else - y:= y + Gear^.Radius; + LegacyFixupY(Gear); + TestCollisionYwithGear:= TestCollisionYImpl(hwRound(Gear^.X), hwRound(Gear^.Y), Gear^.Radius, Dir, Gear^.CollisionMask); +end; -if (y and LAND_HEIGHT_MASK) = 0 then - begin - x:= hwRound(Gear^.X) - Gear^.Radius + 1; - i:= x + Gear^.Radius * 2 - 2; - repeat - if (x and LAND_WIDTH_MASK) = 0 then - if Land[y, x] > 0 then - begin - if ((Land[y, x] and Gear^.CollisionMask) and lfLandMask) <> 0 then - exit(Land[y, x] and Gear^.CollisionMask) - else // if Land[y, x] <> 0 then - pixel:= Land[y, x] and Gear^.CollisionMask; - end; - inc(x) - until (x > i); - end; -TestCollisionYKick:= pixel; - -if pixel <> 0 then +function TestCollisionXwithXYShift(Gear: PGear; ShiftX: hwFloat; ShiftY: LongInt; Dir: LongInt; withGear: boolean): Word; inline; +var collisionMask: Word; +begin + if withGear then begin - if hwAbs(Gear^.dY) < cHHKick then - exit; - if (Gear^.State and gstHHJumping <> 0) and (not Gear^.dY.isNegative) and (Gear^.dY < _0_4) then - exit; + LegacyFixupX(Gear); + collisionMask:= Gear^.CollisionMask; + end + else + collisionMask:= Gear^.CollisionMask and lfLandMask; - mx:= hwRound(Gear^.X); - my:= hwRound(Gear^.Y); - myr:= my+Gear^.Radius; + TestCollisionXwithXYShift := TestCollisionXImpl(hwRound(Gear^.X + ShiftX), hwRound(Gear^.Y) + ShiftY, Gear^.Radius, Dir, collisionMask) +end; - for i:= 0 to Pred(Count) do - with cinfos[i] do - if (Gear <> cGear) and - ((myr > y) xor (Dir > 0)) and - (Gear^.State and gstNotKickable = 0) and - (cGear^.Kind in [gtHedgehog, gtMine, gtKnife, gtExplosives]) and - (sqr(mx - x) + sqr(my - y) <= sqr(Radius + Gear^.Radius + 2)) then - begin - with cGear^ do - begin - if (Kind <> gtExplosives) or ((State and gsttmpflag) <> 0) then - dX:= Gear^.dX * _0_5; - dY:= Gear^.dY; - State:= State or gstMoving; - if Kind = gtKnife then State:= State and (not gstCollision); - Active:= true - end; - DeleteCI(cGear); - exit(0) - end +function TestCollisionYwithXYShift(Gear: PGear; ShiftX, ShiftY: LongInt; Dir: LongInt; withGear: boolean): Word; inline; +var collisionMask: Word; +begin + if withGear then + begin + LegacyFixupY(Gear); + collisionMask:= Gear^.CollisionMask; end + else + collisionMask:= Gear^.CollisionMask and lfLandMask; + + TestCollisionYwithXYShift := TestCollisionYImpl(hwRound(Gear^.X) + ShiftX, hwRound(Gear^.Y) + ShiftY, Gear^.Radius, Dir, collisionMask) end; function TestCollisionXwithXYShift(Gear: PGear; ShiftX: hwFloat; ShiftY: LongInt; Dir: LongInt): Word; inline; @@ -618,80 +578,163 @@ TestCollisionXwithXYShift:= TestCollisionXwithXYShift(Gear, ShiftX, ShiftY, Dir, true); end; -function TestCollisionXwithXYShift(Gear: PGear; ShiftX: hwFloat; ShiftY: LongInt; Dir: LongInt; withGear: boolean): Word; -begin -Gear^.X:= Gear^.X + ShiftX; -Gear^.Y:= Gear^.Y + int2hwFloat(ShiftY); -if withGear then - TestCollisionXwithXYShift:= TestCollisionXwithGear(Gear, Dir) -else TestCollisionXwithXYShift:= TestCollisionX(Gear, Dir); -Gear^.X:= Gear^.X - ShiftX; -Gear^.Y:= Gear^.Y - int2hwFloat(ShiftY) -end; - -function TestCollisionX(Gear: PGear; Dir: LongInt): Word; -var x, y, i: LongInt; -begin -x:= hwRound(Gear^.X); -if Dir < 0 then - x:= x - Gear^.Radius -else - x:= x + Gear^.Radius; - -if (x and LAND_WIDTH_MASK) = 0 then - begin - y:= hwRound(Gear^.Y) - Gear^.Radius + 1; - i:= y + Gear^.Radius * 2 - 2; - repeat - if (y and LAND_HEIGHT_MASK) = 0 then - if ((Land[y, x] and Gear^.CollisionMask) and lfLandMask) <> 0 then - exit(Land[y, x] and Gear^.CollisionMask); - inc(y) - until (y > i); - end; -TestCollisionX:= 0 -end; - -function TestCollisionY(Gear: PGear; Dir: LongInt): Word; -var x, y, i: LongInt; -begin -y:= hwRound(Gear^.Y); -if Dir < 0 then - y:= y - Gear^.Radius -else - y:= y + Gear^.Radius; - -if (y and LAND_HEIGHT_MASK) = 0 then - begin - x:= hwRound(Gear^.X) - Gear^.Radius + 1; - i:= x + Gear^.Radius * 2 - 2; - repeat - if (x and LAND_WIDTH_MASK) = 0 then - if ((Land[y, x] and Gear^.CollisionMask) and lfLandMask) <> 0 then - exit(Land[y, x] and Gear^.CollisionMask); - inc(x) - until (x > i); - end; -TestCollisionY:= 0 -end; - function TestCollisionYwithXYShift(Gear: PGear; ShiftX, ShiftY: LongInt; Dir: LongInt): Word; inline; begin TestCollisionYwithXYShift:= TestCollisionYwithXYShift(Gear, ShiftX, ShiftY, Dir, true); end; -function TestCollisionYwithXYShift(Gear: PGear; ShiftX, ShiftY: LongInt; Dir: LongInt; withGear: boolean): Word; +function TestCollisionXKickImpl(centerX, centerY, radius, direction: LongInt; collisionMask, kickMask: Word): TKickTest; +var x, y, minY, maxY: LongInt; +begin + TestCollisionXKickImpl.kick := false; + TestCollisionXKickImpl.collisionMask := 0; + + if direction < 0 then + x := centerX - radius + else + x := centerX + radius; + + if (x and LAND_WIDTH_MASK) = 0 then + begin + minY := max(centerY - radius + 1, 0); + maxY := min(centerY + radius - 1, LAND_HEIGHT - 1); + for y := minY to maxY do + if Land[y, x] and collisionMask <> 0 then + begin + TestCollisionXKickImpl.kick := false; + TestCollisionXKickImpl.collisionMask := Land[y, x] and collisionMask; + exit + end + else if Land[y, x] and kickMask <> 0 then + begin + TestCollisionXKickImpl.kick := true; + TestCollisionXKickImpl.collisionMask := Land[y, x] and kickMask; + end; + end; +end; + +function TestCollisionYKickImpl(centerX, centerY, radius, direction: LongInt; collisionMask, kickMask: Word): TKickTest; +var x, y, minX, maxX: LongInt; begin -Gear^.X:= Gear^.X + int2hwFloat(ShiftX); -Gear^.Y:= Gear^.Y + int2hwFloat(ShiftY); + TestCollisionYKickImpl.kick := false; + TestCollisionYKickImpl.collisionMask := 0; + + if direction < 0 then + y := centerY - radius + else + y := centerY + radius; + + if (y and LAND_HEIGHT_MASK) = 0 then + begin + minX := max(centerX - radius + 1, 0); + maxX := min(centerX + radius - 1, LAND_WIDTH - 1); + for x := minX to maxX do + if Land[y, x] and collisionMask <> 0 then + begin + TestCollisionYKickImpl.kick := false; + TestCollisionYKickImpl.collisionMask := Land[y, x] and collisionMask; + exit + end + else if Land[y, x] and kickMask <> 0 then + begin + TestCollisionYKickImpl.kick := true; + TestCollisionYKickImpl.collisionMask := Land[y, x] and kickMask; + end; + end; +end; + +function TestCollisionXKick(Gear: PGear; Dir: LongInt): Word; +var centerX, centerY, i: LongInt; + test: TKickTest; + info: TCollisionEntry; +begin + test := TestCollisionXKickImpl( + hwRound(Gear^.X), hwRound(Gear^.Y), + Gear^.Radius, Dir, + Gear^.CollisionMask and lfLandMask, Gear^.CollisionMask); + + TestCollisionXKick := test.collisionMask; -if withGear then - TestCollisionYwithXYShift:= TestCollisionYwithGear(Gear, Dir) -else - TestCollisionYwithXYShift:= TestCollisionY(Gear, Dir); + if test.kick then + begin + if hwAbs(Gear^.dX) < cHHKick then + exit; + if ((Gear^.State and gstHHJumping) <> 0) and (hwAbs(Gear^.dX) < _0_4) then + exit; + + centerX := hwRound(Gear^.X); + centerY := hwRound(Gear^.Y); + + for i:= 0 to Pred(Count) do + begin + info:= cinfos[i]; + if (Gear <> info.cGear) + and ((centerX > info.X) xor (Dir > 0)) + and ((info.cGear^.State and gstNotKickable) = 0) + and ((info.cGear^.Kind in [gtHedgehog, gtMine, gtKnife]) + or (info.cGear^.Kind = gtExplosives) and ((info.cGear^.State and gsttmpflag) <> 0)) // only apply X kick if the barrel is knocked over + and (sqr(centerX - info.X) + sqr(centerY - info.Y) <= sqr(info.Radius + Gear^.Radius + 2)) then + begin + with info.cGear^ do + begin + dX := Gear^.dX; + dY := Gear^.dY * _0_5; + State := State or gstMoving; + if Kind = gtKnife then State := State and (not gstCollision); + Active:= true + end; + DeleteCI(info.cGear); + exit(0) + end + end + end +end; -Gear^.X:= Gear^.X - int2hwFloat(ShiftX); -Gear^.Y:= Gear^.Y - int2hwFloat(ShiftY) +function TestCollisionYKick(Gear: PGear; Dir: LongInt): Word; +var centerX, centerY, i: LongInt; + test: TKickTest; + info: TCollisionEntry; +begin + test := TestCollisionYKickImpl( + hwRound(Gear^.X), hwRound(Gear^.Y), + Gear^.Radius, Dir, + Gear^.CollisionMask and lfLandMask, Gear^.CollisionMask); + + TestCollisionYKick := test.collisionMask; + + if test.kick then + begin + if hwAbs(Gear^.dY) < cHHKick then + exit; + if ((Gear^.State and gstHHJumping) <> 0) and (not Gear^.dY.isNegative) and (Gear^.dY < _0_4) then + exit; + + centerX := hwRound(Gear^.X); + centerY := hwRound(Gear^.Y); + + for i := 0 to Pred(Count) do + begin + info := cinfos[i]; + if (Gear <> info.cGear) + and ((centerY + Gear^.Radius > info.Y) xor (Dir > 0)) + and (info.cGear^.State and gstNotKickable = 0) + and (info.cGear^.Kind in [gtHedgehog, gtMine, gtKnife, gtExplosives]) + and (sqr(centerX - info.X) + sqr(centerY - info.Y) <= sqr(info.Radius + Gear^.Radius + 2)) then + begin + with info.cGear^ do + begin + if (Kind <> gtExplosives) or ((State and gsttmpflag) <> 0) then + dX := Gear^.dX * _0_5; + dY := Gear^.dY; + State := State or gstMoving; + if Kind = gtKnife then State:= State and (not gstCollision); + Active := true + end; + DeleteCI(info.cGear); + exit(0) + end + end + end end; function TestRectangleForObstacle(x1, y1, x2, y2: LongInt; landOnly: boolean): boolean; diff -r b3c9f5463cee -r 74ede02bc882 hedgewars/uCommandHandlers.pas --- a/hedgewars/uCommandHandlers.pas Sun Jul 05 02:03:08 2020 +0200 +++ b/hedgewars/uCommandHandlers.pas Sun Jul 05 14:53:44 2020 +0200 @@ -856,6 +856,11 @@ cAirMines:= StrToInt(s) end; +procedure chSentries(var s: shortstring); +begin +cSentries:= StrToInt(s) +end; + procedure chExplosives(var s: shortstring); begin cExplosives:= StrToInt(s) @@ -980,6 +985,7 @@ RegisterVariable('minedudpct',@chMineDudPercent, false); RegisterVariable('minesnum', @chLandMines , false); RegisterVariable('airmines', @chAirMines , false); + RegisterVariable('sentries', @chSentries , false); RegisterVariable('explosives',@chExplosives , false); RegisterVariable('gmflags' , @chGameFlags , false); RegisterVariable('turntime', @chHedgehogTurnTime, false); diff -r b3c9f5463cee -r 74ede02bc882 hedgewars/uConsts.pas --- a/hedgewars/uConsts.pas Sun Jul 05 02:03:08 2020 +0200 +++ b/hedgewars/uConsts.pas Sun Jul 05 14:53:44 2020 +0200 @@ -171,6 +171,7 @@ cVisibleWater : LongInt = 128; cTeamHealthWidth : LongInt = 128; + cTeamHealthHeight : LongInt = 19 * HDPIScaleFactor; cGearContourThreshold : LongInt = 179; // if water opacity is higher than this, draw contour for some gears when in water cifRandomize = $00000001; @@ -194,6 +195,7 @@ cBorderWidth = 6; // width of indestructible border // width of 3 allowed hogs to be knocked through with grenade + cCloudOffset = 1184; // Y offset for clouds (cloud height = LAND_HEIGHT-cCloudOffset) cHHRadius = 9; // hedgehog radius cHHStepTicks = 29; @@ -226,10 +228,18 @@ cSeductionDist = 250; // effect distance of seduction ExtraTime = 30000; // amount of time (ms) given for using Extra Time + MaxMoreWindTime = 5000; // amount of time (ms) for land objects like gfMine to be affected after end of turn // do not change this value cDefaultZoomLevel = 2.0; // 100% zoom + // Maximum camera positions, values are "pixels outside the mainland" + cCamLimitX = 1920; // X (left/right) camera limit, no border. Note: Influences sndFlyAway + // Note: Also make sure it's far enough from airplane spawn + cCamLimitY = 2048; // Top Y camera limit, no border + cCamLimitBorderX = 1920; // X (left/right) camera limit, with border + cCamLimitBorderY = 2048; // Top Y camera limit, with border + cFontPxToPtRatio = 1.3281472327365; cBaseChatFontHeight = 12; cChatScaleRelDelta = 0.1; diff -r b3c9f5463cee -r 74ede02bc882 hedgewars/uFloat.pas --- a/hedgewars/uFloat.pas Sun Jul 05 02:03:08 2020 +0200 +++ b/hedgewars/uFloat.pas Sun Jul 05 14:53:44 2020 +0200 @@ -118,6 +118,7 @@ _0_0128: hwFloat = (isNegative: false; QWordValue: 54975581); _0_02: hwFloat = (isNegative: false; QWordValue: 85899345); _0_03: hwFloat = (isNegative: false; QWordValue: 128849018); + _0_05: hwFloat = (isNegative: false; QWordValue: 214748365); _0_07: hwFloat = (isNegative: false; QWordValue: 300647710); _0_08: hwFloat = (isNegative: false; QWordValue: 343597383); _0_1: hwFloat = (isNegative: false; QWordValue: 429496730); diff -r b3c9f5463cee -r 74ede02bc882 hedgewars/uGears.pas --- a/hedgewars/uGears.pas Sun Jul 05 02:03:08 2020 +0200 +++ b/hedgewars/uGears.pas Sun Jul 05 14:53:44 2020 +0200 @@ -595,6 +595,11 @@ dec(TurnTimeLeft) end; +if (TurnTimeLeft = 0) and (ReadyTimeLeft = 0) then + inc(TimeNotInTurn) +else + TimeNotInTurn:= 0; + if skipFlag then begin if TagTurnTimeLeft = 0 then @@ -858,6 +863,21 @@ end; if p <> 0 then DeleteGear(Gear); +i:= 0; +unplaced:= 0; +while (i < cSentries) and (unplaced < 4) do + begin + Gear:= AddGear(0, 0, gtSentry, 0, _0, _0, 0); + FindPlace(Gear, false, 0, LAND_WIDTH); + + if Gear = nil then + inc(unplaced) + else + unplaced:= 0; + + inc(i) + end; + if (GameFlags and gfLowGravity) <> 0 then begin cGravity:= cMaxWindSpeed; @@ -1456,7 +1476,8 @@ @doStepKnife, @doStepCreeper, @doStepMinigun, - @doStepMinigunBullet); + @doStepMinigunBullet, + @doStepSentry); begin doStepHandlers:= handlers; diff -r b3c9f5463cee -r 74ede02bc882 hedgewars/uGearsHandlersMess.pas --- a/hedgewars/uGearsHandlersMess.pas Sun Jul 05 02:03:08 2020 +0200 +++ b/hedgewars/uGearsHandlersMess.pas Sun Jul 05 14:53:44 2020 +0200 @@ -141,6 +141,7 @@ procedure doStepMinigunWork(Gear: PGear); procedure doStepMinigun(Gear: PGear); procedure doStepMinigunBullet(Gear: PGear); +procedure doStepSentry(Gear: PGear); var upd: Longword; @@ -535,10 +536,15 @@ if isFalling and (Gear^.State and gstNoGravity = 0) then begin + // Apply gravity and wind Gear^.dY := Gear^.dY + cGravity; - if (GameFlags and gfMoreWind <> 0) and (TurnTimeLeft > 0) and - ((xland or land) = 0) and - ((Gear^.dX.QWordValue + Gear^.dY.QWordValue) > _0_02.QWordValue) then + if ((GameFlags and gfMoreWind) <> 0) and + // Disable gfMoreWind for land objects on turn end to prevent bouncing them forever + // This solution is rather ugly, in that it will occassionally suddenly wind physics + // while a gear is moving, this can be rather confusing. + // TODO: Find a way to make gfMoreWind-affected land objects settle more reliably + // and quickler without touching wind itselvs + ((not (Gear^.Kind in [gtMine, gtAirMine, gtSMine, gtKnife, gtExplosives, gtSentry])) or (TimeNotInTurn < MaxMoreWindTime)) then Gear^.dX := Gear^.dX + cWindSpeed / Gear^.Density end; @@ -564,7 +570,7 @@ (((Gear^.Radius < 3) and (Gear^.dY < -_0_1)) or ((Gear^.Radius >= 3) and ((Gear^.dX.QWordValue > _0_1.QWordValue) or (Gear^.dY.QWordValue > _0_1.QWordValue)))) then - PlaySound(TSound(ord(Gear^.ImpactSound) + LongInt(GetRandom(Gear^.nImpactSounds))), true); + PlaySound(TSound(ord(Gear^.ImpactSound) + LongInt(GetRandom(Gear^.nImpactSounds))), Gear^.Kind <> gtDynamite); end; //////////////////////////////////////////////////////////////////////////////// @@ -1558,7 +1564,7 @@ begin if Gear^.Kind = gtMinigunBullet then begin - doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 5, + doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), Gear^.Karma, Gear^.Hedgehog, (EXPLNoDamage or EXPLDoNotTouchHH){ or EXPLDontDraw or EXPLNoGfx}); VGear := AddVisualGear(hwRound(Gear^.X + Gear^.dX * 5), hwRound(Gear^.Y + Gear^.dY * 5), vgtBulletHit); end @@ -1601,10 +1607,10 @@ procedure doStepDEagleShot(Gear: PGear); begin - Gear^.Data:= nil; - // remember who fired this - if (Gear^.Hedgehog <> nil) and (Gear^.Hedgehog^.Gear <> nil) then - Gear^.Data:= Pointer(Gear^.Hedgehog^.Gear); + if Gear^.Data = nil then + // remember who fired this + if (Gear^.Hedgehog <> nil) and (Gear^.Hedgehog^.Gear <> nil) then + Gear^.Data:= Pointer(Gear^.Hedgehog^.Gear); PlaySound(sndGun); ClearHitOrder(); @@ -1842,8 +1848,7 @@ procedure doStepBlowTorchWork(Gear: PGear); var HHGear: PGear; - b: boolean; - prevX: LongInt; + dig, hit: boolean; begin AllInactive := false; WorldWrap(Gear); @@ -1851,6 +1856,7 @@ if Gear^.Hedgehog^.Gear = nil then begin + ClearProximityCache(); StopSoundChan(Gear^.SoundChannel); DeleteGear(Gear); AfterAttack; @@ -1861,14 +1867,16 @@ HedgehogChAngle(HHGear); - b := false; + dig := false; + hit := false; if abs(LongInt(HHGear^.Angle) - BTPrevAngle) > 7 then begin Gear^.dX := SignAs(AngleSin(HHGear^.Angle) * _0_5, Gear^.dX); Gear^.dY := AngleCos(HHGear^.Angle) * ( - _0_5); + BTPrevAngle := HHGear^.Angle; - b := true + dig := true end; if ((HHGear^.State and gstMoving) <> 0) then @@ -1878,9 +1886,12 @@ Gear^.Timer := 0 end; + if Gear^.Timer mod 1500 = 0 then + RefillProximityCache(Gear, 200); + if Gear^.Timer mod cHHStepTicks = 0 then begin - b := true; + dig := true; if Gear^.dX.isNegative then HHGear^.Message := (HHGear^.Message and (gmAttack or gmUp or gmDown)) or gmLeft else @@ -1889,21 +1900,15 @@ if ((HHGear^.State and gstMoving) = 0) then begin HHGear^.State := HHGear^.State and (not gstAttacking); - prevX := hwRound(HHGear^.X); - - // why the call to HedgehogStep then a further increment of X? - if (prevX = hwRound(HHGear^.X)) and - CheckLandValue(hwRound(HHGear^.X + SignAs(_6, HHGear^.dX)), hwRound(HHGear^.Y), - lfIndestructible) then HedgehogStep(HHGear); - - if (prevX = hwRound(HHGear^.X)) and - CheckLandValue(hwRound(HHGear^.X + SignAs(_6, HHGear^.dX)), hwRound(HHGear^.Y), - lfIndestructible) then HHGear^.X := HHGear^.X + SignAs(_1, HHGear^.dX); + + if CheckLandValue(hwRound(HHGear^.X + SignAs(_6, HHGear^.dX)), hwRound(HHGear^.Y),lfIndestructible) then + HedgehogStep(HHGear); + HHGear^.State := HHGear^.State or gstAttacking end; inc(BTSteps); - if BTSteps = 7 then + if BTSteps = 11 then begin BTSteps := 0; if CheckLandValue(hwRound(HHGear^.X + Gear^.dX * (cHHRadius + cBlowTorchC) + SignAs(_6,Gear^.dX)), hwRound(HHGear^.Y + Gear^.dY * (cHHRadius + cBlowTorchC)),lfIndestructible) then @@ -1911,24 +1916,30 @@ Gear^.X := HHGear^.X + Gear^.dX * (cHHRadius + cBlowTorchC); Gear^.Y := HHGear^.Y + Gear^.dY * (cHHRadius + cBlowTorchC); end; - HHGear^.State := HHGear^.State or gstNoDamage; - AmmoShove(Gear, Gear^.Boom, 15); - HHGear^.State := HHGear^.State and (not gstNoDamage) + hit := true end; end; - if b then + if dig then begin DrawTunnel(HHGear^.X + Gear^.dX * cHHRadius, - HHGear^.Y + Gear^.dY * cHHRadius - _1 - - ((hwAbs(Gear^.dX) / (hwAbs(Gear^.dX) + hwAbs(Gear^.dY))) * _0_5 * 7), - Gear^.dX, Gear^.dY, - cHHStepTicks, cHHRadius * 2 + 7); + HHGear^.Y + Gear^.dY * cHHRadius - _1 - + ((hwAbs(Gear^.dX) / (hwAbs(Gear^.dX) + hwAbs(Gear^.dY))) * _0_5 * 7), + Gear^.dX, Gear^.dY, + cHHStepTicks, cHHRadius * 2 + 7); + + HHGear^.State := HHGear^.State or gstNoDamage; + if hit then + AmmoShoveCache(Gear, Gear^.Boom, 15) + else + AmmoShoveCache(Gear, 0, 15); + HHGear^.State := HHGear^.State and (not gstNoDamage); end; if (TurnTimeLeft = 0) or (Gear^.Timer = 0) or ((HHGear^.Message and gmAttack) <> 0) then begin + ClearProximityCache(); StopSoundChan(Gear^.SoundChannel); HHGear^.Message := 0; HHGear^.State := HHGear^.State and (not gstNotKickable); @@ -1954,6 +1965,8 @@ cHHStepTicks, cHHRadius * 2 + 7); HHGear^.Message := 0; HHGear^.State := HHGear^.State or gstNotKickable; + RefillProximityCache(Gear, 200); + Gear^.SoundChannel := LoopSound(sndBlowTorch); Gear^.doStep := @doStepBlowTorchWork end; @@ -2348,12 +2361,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 @@ -3081,6 +3104,16 @@ AfterAttack; + // Delete parachute early if hog already collides + if (TestCollisionXwithGear(HHGear, -1) <> 0) and (TestCollisionXwithGear(HHGear, 1) <> 0) then + begin + HHGear^.dY := cGravity * 100; + isCursorVisible:= false; + ApplyAmmoChanges(HHGear^.Hedgehog^); + DeleteGear(Gear); + exit; + end; + // make sure hog doesn't end up facing in wrong direction due to high jump if (HHGear^.State and gstHHHJump) <> 0 then HHGear^.dX.isNegative := (not HHGear^.dX.isNegative); @@ -3154,7 +3187,7 @@ // Get rid of gear and cleanup if ((WorldEdge = weWrap) and (Gear^.FlightTime >= 4000)) or - ((WorldEdge <> weWrap) and (((hwRound(Gear^.X) - Gear^.Radius > (max(LAND_WIDTH,4096)+2048)) or (hwRound(Gear^.X) + Gear^.Radius < -2048) or ((Gear^.Message and gmDestroy) > 0)))) then + ((WorldEdge <> weWrap) and (((hwRound(Gear^.X) - Gear^.Radius > (LAND_WIDTH+2048)) or (hwRound(Gear^.X) + Gear^.Radius < -2048) or ((Gear^.Message and gmDestroy) > 0)))) then begin // fail-safe: instanly stop sound if it wasn't disabled before if (Gear^.SoundChannel <> -1) then @@ -3222,7 +3255,7 @@ if (WorldEdge = weWrap) then Gear^.X := int2hwFloat(CalcWorldWrap(Gear^.Target.X - max(384, LAND_WIDTH shr 2), 0)) else - Gear^.X := int2hwFloat(max(LAND_WIDTH,4096) + 2048); + Gear^.X := int2hwFloat(LAND_WIDTH + 2048); end; Gear^.Y := int2hwFloat(topY - 300); @@ -7127,10 +7160,10 @@ procedure doStepMinigunBullet(Gear: PGear); begin - Gear^.Data:= nil; - // remember who fired this - if (Gear^.Hedgehog <> nil) and (Gear^.Hedgehog^.Gear <> nil) then - Gear^.Data:= Pointer(Gear^.Hedgehog^.Gear); + if Gear^.Data = nil then + // remember who fired this + if (Gear^.Hedgehog <> nil) and (Gear^.Hedgehog^.Gear <> nil) then + Gear^.Data:= Pointer(Gear^.Hedgehog^.Gear); Gear^.X := Gear^.X + Gear^.dX * 2; Gear^.Y := Gear^.Y + Gear^.dY * 2; @@ -7138,4 +7171,277 @@ Gear^.doStep := @doStepBulletWork end; +//////////////////////////////////////////////////////////////////////////////// + +function MakeSentryStep(Sentry: PGear; maxYStep: LongInt; TestOnly: Boolean): Boolean; +var x, y, offset, direction: LongInt; +begin + MakeSentryStep := false; + x := hwRound(Sentry^.X); + y := hwRound(Sentry^.Y); + direction := hwSign(Sentry^.dX); + + for offset := -maxYStep - 1 to maxYStep + 1 do + begin + if TestCollisionYImpl(x + direction, y + offset, Sentry^.Radius, 1, Sentry^.CollisionMask) <> 0 then + break; + end; + + if (offset >= -maxYStep) and (offset <= maxYStep) then + begin + if not TestOnly then + begin + Sentry^.X := Sentry^.X + signAs(_1, Sentry^.dX); + Sentry^.Y := Sentry^.Y + int2hwFloat(offset); + end; + MakeSentryStep := true + end +end; + +function MakeSentryJump(Sentry: PGear; maxXStep, maxYStep: LongInt): Boolean; +var x, y, offsetX, offsetY: LongInt; + jumpTime: hwFloat; +begin + MakeSentryJump := false; + x := hwRound(Sentry^.X); + y := hwRound(Sentry^.Y); + offsetX := (maxXStep - Sentry^.Radius) * hwSign(Sentry^.dX); + repeat + for offsetY := -maxYStep - 1 to maxYStep + 1 do + begin + if TestCollisionYImpl(x + offsetX, y + offsetY, Sentry^.Radius, 1, Sentry^.CollisionMask) <> 0 then + break; + end; + if (offsetY >= -maxYStep) and (offsetY <= maxYStep) then + break; + Dec(offsetX, Sentry^.Radius * hwSign(Sentry^.dX)); + until offsetX <= 0; + + if (offsetX > 0) and (not cGravity.isNegative) then + begin + Sentry^.dY := -_0_25; + jumpTime := _2 * Sentry^.dY / cGravity; + Sentry^.dX := SignAs(int2hwFloat(abs(offsetX) - Sentry^.Radius) / jumpTime, Sentry^.dX); + MakeSentryJump := true; + end; +end; + +function TraceAttackPath(fromX, fromY, toX, toY, step: hwFloat; mask: Word): LongWord; +var distX, distY, dist, invDistance: HwFloat; + i, count: LongInt; +begin + TraceAttackPath := 0; + if (step < _1) + or ((hwRound(fromX) and LAND_WIDTH_MASK) <> 0) + or ((hwRound(toX) and LAND_WIDTH_MASK) <> 0) + or ((hwRound(fromY) and LAND_HEIGHT_MASK) <> 0) + or ((hwRound(toY) and LAND_HEIGHT_MASK) <> 0) then + exit; + + distX := toX - fromX; + distY := toY - fromY; + dist := Distance(distX, distY); + count := hwRound(dist / step); + + invDistance := step / dist; + distX := distX * invDistance; + distY := distY * invDistance; + + for i := 0 to count - 1 do + begin + if (Land[hwRound(fromY), hwRound(fromX)] and mask) <> 0 then + Inc(TraceAttackPath); + fromX := fromX + distX; + fromY := fromY + distY; + end +end; + +function CheckSentryAttackRange(Sentry: PGear; targetX, targetY: HwFloat): Boolean; +var distX, distY: hwFloat; +begin + distX := targetX - Sentry^.X; + distY := targetY - Sentry^.Y; + CheckSentryAttackRange := + (distX.isNegative = Sentry^.dX.isNegative) + and (distX.Round > 24) + and (distX.Round < 500) + and (hwAbs(distY) < hwAbs(distX * _1_5)) + and (TraceAttackPath(Sentry^.X, Sentry^.Y, targetX, targetY, _4, lfLandMask) <= 18); +end; + +procedure doStepSentry(Gear: PGear); +var HHGear, bullet: PGear; + distX, distY, invDistance: HwFloat; +const sentry_Idle = 0; + sentry_Walking = 1; + sentry_Aiming = 2; + sentry_Attacking = 3; + sentry_Reloading = 4; +begin + HHGear:= nil; + + if CheckGearDrowning(Gear) then + exit; + + if Gear^.dY.isNegative or (TestCollisionYwithGear(Gear, 1) = 0) then + begin + doStepFallingGear(Gear); + if Gear^.Tag <> sentry_Idle then + begin + Gear^.Timer := 0; + Gear^.Tag := sentry_Idle; + Gear^.Target.X := 0; + Gear^.Target.Y := 0; + if Gear^.Karma <> 0 then + begin + ClearGlobalHitOrderLeq(Gear^.Karma); + Gear^.Karma := 0; + end; + end; + exit; + end; + + if Gear^.Timer > 0 then dec(Gear^.Timer); + + if Gear^.Timer = 0 then + begin + if Gear^.Tag = sentry_Idle then + begin + Gear^.Tag := sentry_Walking; + Gear^.Timer := 1000 + GetRandom(3000); + if GetRandom(4) = 0 then + begin + if MakeSentryJump(Gear, 80, 60) then + Gear^.Timer := 4000 + else + Gear^.Timer := 1000; + Gear^.Tag := sentry_Idle; + end + else + begin + Gear^.dX.isNegative := GetRandom(2) = 1; + + if MakeSentryStep(Gear, 6, true) then + begin + if GetRandom(4) = 0 then + begin + Gear^.Timer := 2000; + Gear^.Tag := sentry_Idle; + end; + end + else + begin + Gear^.dX.isNegative := not Gear^.dX.isNegative; + if not MakeSentryStep(Gear, 6, true) then + begin + if GetRandom(2) = 0 then + begin + Gear^.dY := - _0_25; + Gear^.Timer := 3000; + end + else + Gear^.Timer := 5000; + Gear^.Tag := sentry_Idle; + end; + end + end + end + else if Gear^.Tag in [sentry_Walking, sentry_Reloading] then + begin + Gear^.Tag := sentry_Idle; + Gear^.Timer := 1000 + GetRandom(1000); + end + else if Gear^.Tag = sentry_Aiming then + begin + if CheckSentryAttackRange(Gear, int2hwFloat(Gear^.Target.X), int2hwFloat(Gear^.Target.Y)) then + begin + Gear^.WDTimer := 5 + GetRandom(3); + Gear^.Tag := sentry_Attacking; + Gear^.Timer := 100; + end + else + begin + Gear^.Target.X := 0; + Gear^.Target.Y := 0; + Gear^.Tag := sentry_Idle; + Gear^.Timer := 5000; + end + end + else if Gear^.Tag = sentry_Attacking then + begin + distX := int2hwFloat(Gear^.Target.X) - Gear^.X; + distY := int2hwFloat(Gear^.Target.Y) - Gear^.Y; + invDistance := _1 / Distance(distX, distY); + distX := distX * invDistance; + distY := distY * invDistance; + + bullet := AddGear( + hwRound(Gear^.X), hwRound(Gear^.Y), + gtMinigunBullet, 0, + distX * _0_9 + rndSign(getRandomf * _0_1), + distY * _0_9 + rndSign(getRandomf * _0_1), + 0); + + bullet^.Karma := 12; + bullet^.Pos := 1; + bullet^.WDTimer := GameTicks; + bullet^.PortalCounter := 1; + bullet^.Elasticity := Gear^.X; + bullet^.Friction := Gear^.Y; + bullet^.Data := Pointer(Gear); + + CreateShellForGear(Gear, Gear^.WDTimer and 1); + PlaySound(sndGun); + + if Gear^.WDTimer = 0 then + begin + Gear^.Target.X := 0; + Gear^.Target.Y := 0; + ClearGlobalHitOrderLeq(Gear^.Karma); + Gear^.Karma := 0; + Gear^.Tag := sentry_Reloading; + Gear^.Timer := 6000 + GetRandom(2000); + end + else + begin + dec(Gear^.WDTimer); + Gear^.Timer := 100; + end + end; + end; + + if (Gear^.Tag = sentry_Walking) and ((GameTicks and $1F) = 0) then + begin + if not MakeSentryStep(Gear, 6, false) then + Gear^.Timer := 0 + end; + + if ((GameTicks and $1F) = 0) + and (Gear^.Tag = sentry_Aiming) + and (CurrentHedgehog <> nil) + and (CurrentHedgehog^.Gear <> nil) then + begin + HHGear := CurrentHedgehog^.Gear; + Gear^.Target.X := Gear^.Target.X + hwSign(HHGear^.X - int2hwFloat(Gear^.Target.X)); + Gear^.Target.Y := Gear^.Target.Y + hwSign(HHGear^.Y - int2hwFloat(Gear^.Target.Y)); + end; + + if ((GameTicks and $FF) = 0) + and (Gear^.Tag in [sentry_Idle, sentry_Walking]) + and (CurrentHedgehog <> nil) + and (CurrentHedgehog^.Gear <> nil) + and ((CurrentHedgehog^.Gear^.State and (gstMoving or gstHHDriven)) = (gstMoving or gstHHDriven)) then + begin + HHGear := CurrentHedgehog^.Gear; + if CheckSentryAttackRange(Gear, HHGear^.X, HHGear^.Y) then + begin + Gear^.Target.X := hwRound(HHGear^.X); + Gear^.Target.Y := hwRound(HHGear^.Y); + Gear^.Karma := GameTicks; + Gear^.Tag := sentry_Aiming; + Gear^.Timer := 1800 + GetRandom(400); + end + end +end; + end. diff -r b3c9f5463cee -r 74ede02bc882 hedgewars/uGearsHedgehog.pas --- a/hedgewars/uGearsHedgehog.pas Sun Jul 05 02:03:08 2020 +0200 +++ b/hedgewars/uGearsHedgehog.pas Sun Jul 05 14:53:44 2020 +0200 @@ -400,6 +400,7 @@ else newGear^.Tag:= 1; end; + amSentry: newGear:= AddGear(hwRound(lx) + hwSign(dX) * 7, hwRound(ly), gtSentry, 0, SignAs(_0_03, dX), _0, 0); amFirePunch: newGear:= AddGear(hwRound(lx) + hwSign(dX) * 10, hwRound(ly), gtFirePunch, 0, xx, _0, 0); amWhip: begin newGear:= AddGear(hwRound(lx) + hwSign(dX) * 10, hwRound(ly), gtWhip, 0, SignAs(_1, dX), - _0_8, 0); @@ -529,7 +530,8 @@ amMineStrike, amDrillStrike, amRubber, amMinigun: CurAmmoGear:= newGear; end; - if CurAmmoType = amCake then FollowGear:= newGear; + if (CurAmmoType = amCake) or (CurAmmoType = amPiano) then + FollowGear:= newGear; if ((CurAmmoType = amMine) or (CurAmmoType = amSMine) or (CurAmmoType = amAirMine)) and (GameFlags and gfInfAttack <> 0) then newGear^.FlightTime:= GameTicks + min(TurnTimeLeft,1000) @@ -1279,7 +1281,7 @@ uStats.hedgehogFlight(Gear, Gear^.FlightTime); Gear^.FlightTime:= 0; end; -if (WorldEdge = weNone) and (not Gear^.Hedgehog^.FlownOffMap) and (not isZero(Gear^.dX)) and (not isUnderwater) and ((Gear^.State and gstHHDriven) = 0) and (hwRound(Gear^.Y) < cWaterLine-300) and ((hwRound(Gear^.X) < leftX-2048) or (hwRound(Gear^.X) > rightX+2048)) then +if (WorldEdge = weNone) and (not hasBorder) and (not Gear^.Hedgehog^.FlownOffMap) and (not isZero(Gear^.dX)) and (not isUnderwater) and ((Gear^.State and gstHHDriven) = 0) and (hwRound(Gear^.Y) < cWaterLine-300) and ((hwRound(Gear^.X) < -cCamLimitX) or (hwRound(Gear^.X) > LAND_WIDTH+cCamLimitX)) then begin PlaySoundV(sndFlyAway, Gear^.Hedgehog^.Team^.voicepack); Gear^.Hedgehog^.FlownOffMap:= true; @@ -1409,7 +1411,7 @@ wasJumping:= ((HHGear^.State and gstHHJumping) <> 0); if ((HHGear^.Message and gmHJump) <> 0) and wasJumping and ((HHGear^.State and gstHHHJump) = 0) then - if (not (hwAbs(HHGear^.dX) > cLittle)) and (HHGear^.dY < -_0_02) then + if (not (hwAbs(HHGear^.dX) > cLittle)) and (HHGear^.dY < _0_05) then begin HHGear^.State:= HHGear^.State or gstHHHJump; HHGear^.dY:= -_0_25; diff -r b3c9f5463cee -r 74ede02bc882 hedgewars/uGearsList.pas --- a/hedgewars/uGearsList.pas Sun Jul 05 02:03:08 2020 +0200 +++ b/hedgewars/uGearsList.pas Sun Jul 05 14:53:44 2020 +0200 @@ -107,6 +107,7 @@ (* gtCreeper *) , amCreeper (* gtMinigun *) , amMinigun (* gtMinigunBullet *) , amMinigun +(* gtSentry *) , amSentry ); @@ -259,6 +260,7 @@ gtSnowball, gtKnife, gtCreeper, + gtSentry, gtMolotov, gtFlake, gtGrave, @@ -315,7 +317,8 @@ gtPoisonCloud: Gear^.Boom := 20; gtKnife: Gear^.Boom := 40000; // arbitrary scaling factor since impact-based gtCreeper: Gear^.Boom := 100; - gtMinigunBullet: Gear^.Boom := 2; + gtMinigunBullet: Gear^.Boom := 2; + gtSentry: Gear^.Boom := 25; end; case Kind of @@ -560,13 +563,16 @@ end; gtDEagleShot: begin gear^.Radius:= 1; - gear^.Health:= 50 + gear^.Health:= 50; + gear^.Data:= nil; end; gtSniperRifleShot: begin gear^.Radius:= 1; gear^.Health:= 50 end; gtDynamite: begin + gear^.ImpactSound:= sndDynamiteImpact; + gear^.nImpactSounds:= 1; gear^.Radius:= 3; gear^.Elasticity:= _0_55; gear^.Friction:= _0_03; @@ -772,6 +778,7 @@ end; gtPoisonCloud: begin if gear^.Timer = 0 then gear^.Timer:= 5000; + gear^.WDTimer:= gear^.Timer; // initial timer gear^.dY:= int2hwfloat(-4 + longint(getRandom(8))) / 1000; gear^.Tint:= $C0C000C0 end; @@ -825,6 +832,18 @@ gtMinigunBullet: begin gear^.Radius:= 1; gear^.Health:= 2; + gear^.Karma:= 5; //impact radius + gear^.Pos:= 0; //uses non-global hit order + gear^.Data:= nil; + end; + gtSentry: begin + gear^.Radius:= cHHRadius; + gear^.Health:= 100; + gear^.Friction:= _0_93; + gear^.Elasticity:= _0_5; + gear^.Tag:= 0; + gear^.Timer:= 500; + gear^.WDTimer:= 0; end; gtGenericFaller:begin gear^.AdvBounce:= 1; diff -r b3c9f5463cee -r 74ede02bc882 hedgewars/uGearsRender.pas --- a/hedgewars/uGearsRender.pas Sun Jul 05 02:03:08 2020 +0200 +++ b/hedgewars/uGearsRender.pas Sun Jul 05 14:53:44 2020 +0200 @@ -662,8 +662,8 @@ end; gtBlowTorch: begin - DrawSpriteRotated(sprBlowTorch, hx, hy, sign, aangle); - DrawHedgehog(sx, sy, + DrawSpriteRotated(sprBlowTorch, ox + 8 * sign, oy - 2, sign, aangle); + DrawHedgehog(ox + 1, oy - 3, sign, 3, HH^.visStepPos div 2, @@ -673,8 +673,8 @@ begin DrawTextureF(curhat, 1, - sx, - sy - 5, + ox + 1, + oy - 8, 0, sign, 32, @@ -688,8 +688,8 @@ Tint(HH^.Team^.Clan^.Color shl 8 or $FF); DrawTextureF(curhat, 1, - sx, - sy - 5, + ox + 1, + oy - 8, tx, sign, 32, @@ -697,7 +697,8 @@ untint end end; - defaultPos:= false + defaultPos:= false; + sign:= hwSign(Gear^.dX); end; gtFirePunch: begin @@ -894,6 +895,7 @@ amClusterBomb: DrawSpriteRotated(sprHandCluster, hx, hy, sign, aangle); amDynamite: DrawSpriteRotated(sprHandDynamite, hx, hy, sign, aangle); amCreeper: DrawSpriteRotatedF(sprHandCreeper, hx, hy, 0, sign, aangle); + amSentry: DrawSpriteRotated(sprHandSentry, hx, hy, sign, aangle); amHellishBomb: DrawSpriteRotated(sprHandHellish, hx, hy, sign, aangle); amGasBomb: DrawSpriteRotated(sprHandCheese, hx, hy, sign, aangle); amMine: DrawSpriteRotated(sprHandMine, hx, hy, sign, aangle); @@ -1510,7 +1512,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); @@ -1653,8 +1658,8 @@ gtPoisonCloud: begin if Gear^.Timer < 1020 then Tint(Gear^.Tint and $FFFFFF00 or Gear^.Timer div 8) - else if Gear^.Timer > 3980 then - Tint(Gear^.Tint and $FFFFFF00 or (5000 - Gear^.Timer) div 8) + else if (Gear^.Timer > Gear^.WDTimer - 1020) and (Gear^.WDTimer > 2040) then + Tint(Gear^.Tint and $FFFFFF00 or (Gear^.WDTimer - Gear^.Timer) div 8) else Tint(Gear^.Tint); DrawTextureRotatedF(SpritesData[sprSmokeWhite].texture, 3, 0, 0, x, y, 0, 1, 22, 22, (RealTicks shr 4 + Gear^.UID * 100) mod 360); @@ -1738,7 +1743,10 @@ gtCreeper: if (Gear^.Hedgehog <> nil) and (Gear^.Hedgehog^.Gear <> nil) then DrawSpriteRotatedF(sprCreeper, x, y, 1, hwRound(SignAs(_1,Gear^.Hedgehog^.Gear^.X-Gear^.X)), 0) else DrawSpriteRotatedF(sprCreeper, x, y, 1, hwRound(SignAs(_1,Gear^.dX)), 0); - + gtSentry: begin + DrawSpriteRotated(sprSentry, x, y, hwSign(Gear^.dX), 0); + DrawCircle(x, y, Gear^.Radius, 1, 255, 0, 0, 255); + end; gtGenericFaller: begin // DEBUG: draw gtGenericFaller if Gear^.Tag <> 0 then diff -r b3c9f5463cee -r 74ede02bc882 hedgewars/uGearsUtils.pas --- a/hedgewars/uGearsUtils.pas Sun Jul 05 02:03:08 2020 +0200 +++ b/hedgewars/uGearsUtils.pas Sun Jul 05 14:53:44 2020 +0200 @@ -1377,7 +1377,10 @@ if (Ammo^.Kind in [gtDEagleShot, gtSniperRifleShot, gtMinigunBullet, gtFirePunch, gtKamikaze, gtWhip, gtShover]) and (((Ammo^.Data <> nil) and (PGear(Ammo^.Data) = Gear)) - or (not UpdateHitOrder(Gear, Ammo^.WDTimer))) then + or (not UpdateHitOrder( + Gear, + Ammo^.WDTimer, + (Ammo^.Kind = gtMinigunBullet) and (Gear^.Pos <> 0)))) then continue; if ((Ammo^.Kind = gtFlame) or (Ammo^.Kind = gtBlowTorch)) and @@ -1462,8 +1465,16 @@ end else if ((Ammo^.Kind <> gtFlame) or (Gear^.Kind = gtHedgehog)) and (Power <> 0) then begin - Gear^.dX:= Ammo^.dX * Power * _0_01; - Gear^.dY:= Ammo^.dY * Power * _0_01 + if (Ammo^.Kind in [gtMinigunBullet]) then + begin + Gear^.dX:= Gear^.dX + Ammo^.dX * Power * _0_01; + Gear^.dY:= Gear^.dY + Ammo^.dY * Power * _0_01 + end + else + begin + Gear^.dX:= Ammo^.dX * Power * _0_01; + Gear^.dY:= Ammo^.dY * Power * _0_01 + end end; if (not isZero(Gear^.dX)) or (not isZero(Gear^.dY)) then diff -r b3c9f5463cee -r 74ede02bc882 hedgewars/uScript.pas --- a/hedgewars/uScript.pas Sun Jul 05 02:03:08 2020 +0200 +++ b/hedgewars/uScript.pas Sun Jul 05 14:53:44 2020 +0200 @@ -1403,7 +1403,7 @@ end; FreeAndNilTexture(clan^.HealthTex); - clan^.HealthTex:= makeHealthBarTexture(cTeamHealthWidth + 5, clan^.Teams[0]^.NameTagTex^.h, clan^.Color); + clan^.HealthTex:= makeHealthBarTexture(cTeamHealthWidth + 5, cTeamHealthHeight, clan^.Color); end; lc_setclancolor:= 0 diff -r b3c9f5463cee -r 74ede02bc882 hedgewars/uSound.pas --- a/hedgewars/uSound.pas Sun Jul 05 02:03:08 2020 +0200 +++ b/hedgewars/uSound.pas Sun Jul 05 14:53:44 2020 +0200 @@ -332,7 +332,9 @@ (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 + (FileName: 'dynamiteimpact.ogg'; Path: ptSounds; AltPath: ptNone) // sndDynamiteImpact ); diff -r b3c9f5463cee -r 74ede02bc882 hedgewars/uStore.pas --- a/hedgewars/uStore.pas Sun Jul 05 02:03:08 2020 +0200 +++ b/hedgewars/uStore.pas Sun Jul 05 14:53:44 2020 +0200 @@ -328,9 +328,9 @@ for t:= 0 to Pred(ClansCount) do with ClansArray[t]^ do - HealthTex:= makeHealthBarTexture(cTeamHealthWidth + 5, 19 * HDPIScaleFactor, Color); + HealthTex:= makeHealthBarTexture(cTeamHealthWidth + 5, cTeamHealthHeight, Color); -GenericHealthTexture:= makeHealthBarTexture(cTeamHealthWidth + 5, 19 * HDPIScaleFactor, cWhiteColor) +GenericHealthTexture:= makeHealthBarTexture(cTeamHealthWidth + 5, cTeamHealthHeight, cWhiteColor) end; diff -r b3c9f5463cee -r 74ede02bc882 hedgewars/uTypes.pas --- a/hedgewars/uTypes.pas Sun Jul 05 02:03:08 2020 +0200 +++ b/hedgewars/uTypes.pas Sun Jul 05 14:53:44 2020 +0200 @@ -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, sprHogBubble, sprHappy, sprSentry, sprHandSentry); // Gears that interact with other Gears and/or Land // first row of gears ( 0 then @@ -180,17 +183,60 @@ // flake fell far below map? outside:= (not rising) and (round(Y) - spawnMargin + randMargin > LAND_HEIGHT); // if not, did it rise far above map? - outside:= outside or (rising and (round(Y) < LAND_HEIGHT - 1024 - spawnMargin - randMargin)); + outside:= outside or (rising and (round(Y) < LAND_HEIGHT - (cCloudOffset - 110))); // if flake left acceptable vertical area, respawn it opposite side if outside then begin - X:= cLeftScreenBorder + random(cScreenSpace); if rising then - Y:= Y + (1024 + spawnMargin + random(50)) + // rising flake + begin + if State = 0 then + begin + // fade out rising flake + diff:= (LAND_HEIGHT - (cCloudOffset - 110)) - round(Y); + diff:= Min(diff*2, $FF); + if diff >= $FF then + begin + // end of fade-out + diff:= $FF; + State:= 1; + end; + Tint:= (Tint and $FFFFFF00) or ($FF - diff); + end + else + begin + // reset and move back to bottom + Y:= LAND_HEIGHT + spawnMargin + random(50); + moved:= true; + State:= 0; + Tint:= Tint or $FF; + end; + end else + // falling flake + begin + // move back to top Y:= Y - (1024 + spawnMargin + random(50)); - moved:= true; + moved:= true; + // activate fade-in if not falling too fast + if fallingFadeIn then + begin + State:= $FF; + Tint:= (Tint and $FFFFFF00) or ($FF - State); + end; + end; + if moved then + X:= cLeftScreenBorder + random(cScreenSpace); + end + else if (not rising) and (State > 0) then + begin + // quickly fade in falling flake after appearing at top + if State > 16 then + Dec(State, 16) + else + State:= 0; + Tint:= (Tint and $FFFFFF00) or ($FF - State); end; if moved then @@ -229,7 +275,7 @@ t := 8 * Gear^.Scale * hwFloat2Float(AngleSin(s mod 2048)); if (s < 2048) then t := -t; -Gear^.Y := LAND_HEIGHT - 1184 + LongInt(Gear^.Timer mod 8) + t; +Gear^.Y := LAND_HEIGHT - cCloudOffset + LongInt(Gear^.Timer mod 8) + t; if round(Gear^.X) < cLeftScreenBorder then Gear^.X:= Gear^.X + cScreenSpace diff -r b3c9f5463cee -r 74ede02bc882 hedgewars/uVisualGearsList.pas --- a/hedgewars/uVisualGearsList.pas Sun Jul 05 02:03:08 2020 +0200 +++ b/hedgewars/uVisualGearsList.pas Sun Jul 05 14:53:44 2020 +0200 @@ -100,6 +100,7 @@ case Kind of vgtFlake: begin + State:= 0; Timer:= 0; tdX:= 0; tdY:= 0; diff -r b3c9f5463cee -r 74ede02bc882 hedgewars/uWorld.pas --- a/hedgewars/uWorld.pas Sun Jul 05 02:03:08 2020 +0200 +++ b/hedgewars/uWorld.pas Sun Jul 05 14:53:44 2020 +0200 @@ -877,6 +877,43 @@ end end; +// Force camera to stay within a certain area +procedure CameraBounds; +var lowBound: LongInt; +begin +if (not hasBorder) then + begin + if WorldDy > (-(cScreenHeight / cScaleFactor) + cScreenHeight div 2 - TopY + cCamLimitY) then + WorldDy:= (-trunc(cScreenHeight / cScaleFactor) + cScreenHeight div 2 - TopY + cCamLimitY); + if (RightX - LeftX + cCamLimitX * 2) div 2 < cScreenWidth / cScaleFactor then + WorldDx:= -((LeftX + RightX) div 2) + else + begin + if WorldDx < -LAND_WIDTH - cCamLimitX + (cScreenWidth / cScaleFactor) then + WorldDx:= -LAND_WIDTH - cCamLimitX + trunc(cScreenWidth / cScaleFactor); + if WorldDx > cCamLimitX - (cScreenWidth / cScaleFactor) then + WorldDx:= cCamLimitX - trunc(cScreenWidth / cScaleFactor); + end; + end +else + begin + if WorldDy > (-(cScreenHeight / cScaleFactor) + cScreenHeight div 2 - TopY + cCamLimitBorderY) then + WorldDy:= (-trunc(cScreenHeight / cScaleFactor) + cScreenHeight div 2 - TopY + cCamLimitBorderY); + if (RightX - LeftX + cCamLimitBorderX * 2) div 2 < cScreenWidth / cScaleFactor then + WorldDx:= -((LeftX + RightX) div 2) + else + begin + if WorldDx > -LeftX + cCamLimitBorderX - (cScreenWidth / cScaleFactor) then + WorldDx:= -LeftX + cCamLimitBorderX - trunc(cScreenWidth / cScaleFactor); + if WorldDx < -RightX - cCamLimitBorderX + (cScreenWidth / cScaleFactor) then + WorldDx:= -RightX - cCamLimitBorderX + trunc(cScreenWidth / cScaleFactor); + end; + end; + +lowBound:= trunc(cScreenHeight / cScaleFactor) + cScreenHeight div 2 - cWaterLine - (cVisibleWater + trunc(CinematicBarH / (cScaleFactor / 2.0))); +if WorldDy < lowBound then + WorldDy:= lowBound; +end; procedure DrawWorld(Lag: LongInt); begin @@ -897,7 +934,9 @@ ZoomValue:= zoom; if (not isPaused) and (not isAFK) and (GameType <> gmtRecord) then - MoveCamera; + MoveCamera + else if (isPaused) then + CameraBounds; if cStereoMode = smNone then begin @@ -1280,7 +1319,7 @@ // line at airplane height for certain airstrike types (when spawning height is important) with CurrentHedgehog^ do if (isCursorVisible) and ((CurAmmoType = amNapalm) or (CurAmmoType = amMineStrike) or (((GameFlags and gfMoreWind) <> 0) and ((CurAmmoType = amDrillStrike) or (CurAmmoType = amAirAttack)))) then - DrawLine(-3000, topY-300, 7000, topY-300, 3.0, (Team^.Clan^.Color shr 16), (Team^.Clan^.Color shr 8) and $FF, Team^.Clan^.Color and $FF, $FF); + DrawLine(-cCamLimitX, topY-300, LAND_WIDTH + cCamLimitX, topY-300, 3.0, (Team^.Clan^.Color shr 16), (Team^.Clan^.Color shr 8) and $FF, Team^.Clan^.Color and $FF, $FF); // gear HUD extras (fuel indicator, secondary ammo, etc.) if replicateToLeft then @@ -1550,9 +1589,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; @@ -1889,7 +1932,7 @@ var PrevSentPointTime: LongWord = 0; procedure MoveCamera; -var EdgesDist, wdy, shs,z, dstX: LongInt; +var EdgesDist, shs,z, dstX: LongInt; inbtwnTrgtAttks: Boolean; begin {$IFNDEF MOBILE} @@ -1937,12 +1980,11 @@ WorldDx:= WorldDx + rightX - leftX; end; -wdy:= trunc(cScreenHeight / cScaleFactor) + cScreenHeight div 2 - cWaterLine - (cVisibleWater + trunc(CinematicBarH / (cScaleFactor / 2.0))); -if WorldDy < wdy then - WorldDy:= wdy; - if ((CursorPoint.X = prevPoint.X) and (CursorPoint.Y = prevpoint.Y)) then + begin + CameraBounds; exit; + end; if (AMState = AMShowingUp) or (AMState = AMShowing) then begin @@ -1955,6 +1997,7 @@ if CursorPoint.Y < cScreenHeight - (AmmoRect.y + AmmoRect.h - AMSlotSize - 5) then//check bottom CursorPoint.Y:= cScreenHeight - (AmmoRect.y + AmmoRect.h - AMSlotSize - 5); prevPoint:= CursorPoint; + CameraBounds; exit end; @@ -1974,16 +2017,16 @@ if (CurrentTeam^.ExtDriven and isCursorVisible and autoCameraOn) or (not CurrentTeam^.ExtDriven and isCursorVisible) or ((FollowGear <> nil) and autoCameraOn) then begin - if CursorPoint.X < - cScreenWidth div 2 + EdgesDist then + if CursorPoint.X < - trunc(cScreenWidth / cScaleFactor) + EdgesDist then begin - WorldDx:= WorldDx - CursorPoint.X - cScreenWidth div 2 + EdgesDist; - CursorPoint.X:= - cScreenWidth div 2 + EdgesDist + WorldDx:= WorldDx - CursorPoint.X - trunc(cScreenWidth / cScaleFactor) + EdgesDist; + CursorPoint.X:= - trunc(cScreenWidth / cScaleFactor) + EdgesDist end else - if CursorPoint.X > cScreenWidth div 2 - EdgesDist then + if CursorPoint.X > trunc(cScreenWidth / cScaleFactor) - EdgesDist then begin - WorldDx:= WorldDx - CursorPoint.X + cScreenWidth div 2 - EdgesDist; - CursorPoint.X:= cScreenWidth div 2 - EdgesDist + WorldDx:= WorldDx - CursorPoint.X + trunc(cScreenWidth / cScaleFactor) - EdgesDist; + CursorPoint.X:= trunc(cScreenWidth / cScaleFactor) - EdgesDist end; shs:= min(cScreenHeight div 2 - trunc(cScreenHeight / cScaleFactor) + EdgesDist, cScreenHeight - EdgesDist); @@ -2010,14 +2053,10 @@ // this moves the camera according to CursorPoint X and Y prevPoint:= CursorPoint; -if WorldDy > LAND_HEIGHT + 1024 then - WorldDy:= LAND_HEIGHT + 1024; -if WorldDy < wdy then - WorldDy:= wdy; -if WorldDx < - LAND_WIDTH - 1024 then - WorldDx:= - LAND_WIDTH - 1024; -if WorldDx > 1024 then - WorldDx:= 1024; + +// enforce camera bounds +CameraBounds(); + end; procedure ShowMission(caption, subcaption, text: ansistring; icon, time : LongInt); diff -r b3c9f5463cee -r 74ede02bc882 misc/flags_js.xhtml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/misc/flags_js.xhtml Sun Jul 05 14:53:44 2020 +0200 @@ -0,0 +1,228 @@ + + + + + Hedgewars Flags + + + + + +

List of Hedgewars flags

+ + + diff -r b3c9f5463cee -r 74ede02bc882 misc/graves_js_anim.xhtml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/misc/graves_js_anim.xhtml Sun Jul 05 14:53:44 2020 +0200 @@ -0,0 +1,319 @@ + + + + + Hedgewars Graves + + + + + +

List of Hedgewars graves

+ + + diff -r b3c9f5463cee -r 74ede02bc882 misc/hats_js_anim.xhtml --- a/misc/hats_js_anim.xhtml Sun Jul 05 02:03:08 2020 +0200 +++ b/misc/hats_js_anim.xhtml Sun Jul 05 14:53:44 2020 +0200 @@ -6,7 +6,7 @@ @@ -187,8 +383,8 @@

List of Hedgewars hats

diff -r b3c9f5463cee -r 74ede02bc882 misc/libphyslayer/CMakeLists.txt --- a/misc/libphyslayer/CMakeLists.txt Sun Jul 05 02:03:08 2020 +0200 +++ b/misc/libphyslayer/CMakeLists.txt Sun Jul 05 14:53:44 2020 +0200 @@ -1,6 +1,6 @@ -find_package(SDL2 REQUIRED) +find_package(SDL2 REQUIRED CONFIG) -include_directories(${SDL2_INCLUDE_DIR}) +include_directories(${SDL2_INCLUDE_DIRS}) include_directories(${PHYSFS_INCLUDE_DIR}) include_directories(${LUA_INCLUDE_DIR}) @@ -17,7 +17,7 @@ set_target_properties(physlayer PROPERTIES VERSION 1.0 SOVERSION 1.0) -target_link_libraries(physlayer ${SDL2_LIBRARY} lua physfs) +target_link_libraries(physlayer ${SDL2_LIBRARIES} lua physfs) install(TARGETS physlayer RUNTIME DESTINATION ${target_binary_install_dir} LIBRARY DESTINATION ${target_library_install_dir} ARCHIVE DESTINATION ${target_library_install_dir}) diff -r b3c9f5463cee -r 74ede02bc882 misc/racer.yaml --- a/misc/racer.yaml Sun Jul 05 02:03:08 2020 +0200 +++ b/misc/racer.yaml Sun Jul 05 14:53:44 2020 +0200 @@ -43,7 +43,7 @@ - 'false' - 'false' - '100' - - '30' + - '90' - '100' - '50' - '0' @@ -105,7 +105,7 @@ - 'false' - 'false' - '100' - - '30' + - '90' - '100' - '50' - '1' @@ -167,7 +167,7 @@ - 'false' - 'false' - '100' - - '30' + - '90' - '100' - '50' - '0' @@ -229,7 +229,7 @@ - 'false' - 'false' - '100' - - '30' + - '90' - '100' - '50' - '0' @@ -291,7 +291,7 @@ - 'false' - 'false' - '100' - - '30' + - '90' - '100' - '50' - '0' @@ -353,7 +353,7 @@ - 'false' - 'false' - '100' - - '30' + - '90' - '100' - '50' - '0' @@ -415,7 +415,7 @@ - 'false' - 'false' - '100' - - '30' + - '90' - '100' - '50' - '1' @@ -477,7 +477,7 @@ - 'false' - 'false' - '100' - - '30' + - '90' - '100' - '50' - '0' @@ -539,7 +539,7 @@ - 'false' - 'false' - '100' - - '30' + - '90' - '100' - '50' - '0' @@ -601,7 +601,7 @@ - 'false' - 'false' - '100' - - '30' + - '90' - '100' - '50' - '0' @@ -663,7 +663,7 @@ - 'false' - 'false' - '100' - - '30' + - '90' - '100' - '50' - '0' @@ -725,7 +725,7 @@ - 'false' - 'false' - '100' - - '30' + - '90' - '100' - '50' - '0' @@ -787,7 +787,7 @@ - 'false' - 'false' - '100' - - '30' + - '90' - '100' - '50' - '1' @@ -849,7 +849,7 @@ - 'false' - 'false' - '100' - - '30' + - '90' - '100' - '50' - '0' @@ -911,7 +911,7 @@ - 'false' - 'false' - '100' - - '30' + - '90' - '100' - '50' - '0' @@ -973,7 +973,7 @@ - 'false' - 'false' - '100' - - '30' + - '90' - '100' - '50' - '0' @@ -1035,7 +1035,7 @@ - 'false' - 'false' - '100' - - '30' + - '90' - '100' - '50' - '0' @@ -1097,7 +1097,7 @@ - 'false' - 'false' - '100' - - '30' + - '90' - '100' - '50' - '1' @@ -1159,7 +1159,7 @@ - 'false' - 'false' - '100' - - '30' + - '90' - '100' - '50' - '0' @@ -1221,7 +1221,7 @@ - 'false' - 'false' - '100' - - '30' + - '90' - '100' - '50' - '0' @@ -1283,7 +1283,7 @@ - 'false' - 'false' - '100' - - '30' + - '90' - '100' - '50' - '1' @@ -1345,7 +1345,7 @@ - 'false' - 'false' - '100' - - '30' + - '90' - '100' - '50' - '0' @@ -1407,7 +1407,7 @@ - 'false' - 'false' - '100' - - '30' + - '90' - '100' - '50' - '0' @@ -1469,7 +1469,7 @@ - 'false' - 'false' - '100' - - '30' + - '90' - '100' - '50' - '1' @@ -1531,7 +1531,7 @@ - 'false' - 'false' - '100' - - '30' + - '90' - '100' - '50' - '0' @@ -1593,7 +1593,7 @@ - 'false' - 'false' - '100' - - '30' + - '90' - '100' - '50' - '1' @@ -1655,7 +1655,7 @@ - 'false' - 'false' - '100' - - '30' + - '90' - '100' - '50' - '0' diff -r b3c9f5463cee -r 74ede02bc882 project_files/hwc/CMakeLists.txt --- a/project_files/hwc/CMakeLists.txt Sun Jul 05 02:03:08 2020 +0200 +++ b/project_files/hwc/CMakeLists.txt Sun Jul 05 14:53:44 2020 +0200 @@ -1,6 +1,6 @@ #the usual set of dependencies find_package(OpenGL REQUIRED) -find_package(SDL2 REQUIRED) +find_package(SDL2 REQUIRED CONFIG) find_package(SDL2_mixer 2 REQUIRED) find_package(SDL2_net 2 REQUIRED) find_package(SDL2_image 2 REQUIRED) @@ -111,7 +111,7 @@ target_link_libraries(hwengine fpcrtl ${LUA_LIBRARY} ${OPENGL_LIBRARY} - ${SDL2_LIBRARY} + ${SDL2_LIBRARIES} ${SDL2_MIXER_LIBRARIES} ${SDL2_NET_LIBRARIES} ${SDL2_IMAGE_LIBRARIES} diff -r b3c9f5463cee -r 74ede02bc882 rust/hedgewars-server/Cargo.toml --- a/rust/hedgewars-server/Cargo.toml Sun Jul 05 02:03:08 2020 +0200 +++ b/rust/hedgewars-server/Cargo.toml Sun Jul 05 14:53:44 2020 +0200 @@ -12,11 +12,12 @@ [dependencies] getopts = "0.2.18" rand = "0.6" +chrono = "0.4" mio = "0.6" mio-extras = "2.0.5" slab = "0.4" netbuf = "0.4" -nom = "5.0" +nom = "5.1" env_logger = "0.6" log = "0.4" base64 = "0.10" diff -r b3c9f5463cee -r 74ede02bc882 rust/hedgewars-server/src/core.rs --- a/rust/hedgewars-server/src/core.rs Sun Jul 05 02:03:08 2020 +0200 +++ b/rust/hedgewars-server/src/core.rs Sun Jul 05 14:53:44 2020 +0200 @@ -1,3 +1,4 @@ +pub mod anteroom; pub mod client; pub mod indexslab; pub mod room; diff -r b3c9f5463cee -r 74ede02bc882 rust/hedgewars-server/src/core/anteroom.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/core/anteroom.rs Sun Jul 05 14:53:44 2020 +0200 @@ -0,0 +1,88 @@ +use super::{indexslab::IndexSlab, types::ClientId}; +use chrono::{offset, DateTime}; +use std::{iter::Iterator, num::NonZeroU16}; + +pub struct HwAnteroomClient { + 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, +} + +struct Ipv4AddrRange { + min: [u8; 4], + max: [u8; 4], +} + +impl Ipv4AddrRange { + fn contains(&self, addr: [u8; 4]) -> bool { + (0..4).all(|i| self.min[i] <= addr[i] && addr[i] <= self.max[i]) + } +} + +struct BanCollection { + ban_ips: Vec, + ban_timeouts: Vec>, + ban_reasons: Vec, +} + +impl BanCollection { + fn new() -> Self { + Self { + ban_ips: vec![], + ban_timeouts: vec![], + ban_reasons: vec![], + } + } + + fn find(&self, addr: [u8; 4]) -> Option { + let time = offset::Utc::now(); + self.ban_ips + .iter() + .enumerate() + .find(|(i, r)| r.contains(addr) && time < self.ban_timeouts[*i]) + .map(|(i, _)| self.ban_reasons[i].clone()) + } +} + +pub struct HwAnteroom { + pub clients: IndexSlab, + bans: BanCollection, +} + +impl HwAnteroom { + pub fn new(clients_limit: usize) -> Self { + let clients = IndexSlab::with_capacity(clients_limit); + HwAnteroom { + clients, + bans: BanCollection::new(), + } + } + + pub fn find_ip_ban(&self, addr: [u8; 4]) -> Option { + self.bans.find(addr) + } + + pub fn add_client(&mut self, client_id: ClientId, salt: String, is_local_admin: bool) { + let client = HwAnteroomClient { + nick: None, + protocol_number: None, + 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 client = self.clients.remove(client_id); + client + } +} diff -r b3c9f5463cee -r 74ede02bc882 rust/hedgewars-server/src/core/client.rs --- a/rust/hedgewars-server/src/core/client.rs Sun Jul 05 02:03:08 2020 +0200 +++ b/rust/hedgewars-server/src/core/client.rs Sun Jul 05 14:53:44 2020 +0200 @@ -2,16 +2,14 @@ use bitflags::*; bitflags! { - pub struct ClientFlags: u16 { + pub struct ClientFlags: u8 { const IS_ADMIN = 0b0000_0001; const IS_MASTER = 0b0000_0010; const IS_READY = 0b0000_0100; const IS_IN_GAME = 0b0000_1000; - const IS_JOINED_MID_GAME = 0b0001_0000; - const IS_CHECKER = 0b0010_0000; - const IS_CONTRIBUTOR = 0b0100_0000; - const HAS_SUPER_POWER = 0b1000_0000; - const IS_REGISTERED = 0b0001_0000_0000; + const IS_CONTRIBUTOR = 0b0001_0000; + const HAS_SUPER_POWER = 0b0010_0000; + const IS_REGISTERED = 0b0100_0000; const NONE = 0b0000_0000; const DEFAULT = Self::NONE.bits; @@ -63,12 +61,6 @@ pub fn is_in_game(&self) -> bool { self.contains(ClientFlags::IS_IN_GAME) } - pub fn is_joined_mid_game(&self) -> bool { - self.contains(ClientFlags::IS_JOINED_MID_GAME) - } - pub fn is_checker(&self) -> bool { - self.contains(ClientFlags::IS_CHECKER) - } pub fn is_contributor(&self) -> bool { self.contains(ClientFlags::IS_CONTRIBUTOR) } @@ -91,12 +83,6 @@ pub fn set_is_in_game(&mut self, value: bool) { self.set(ClientFlags::IS_IN_GAME, value) } - pub fn set_is_joined_mid_game(&mut self, value: bool) { - self.set(ClientFlags::IS_JOINED_MID_GAME, value) - } - pub fn set_is_checker(&mut self, value: bool) { - self.set(ClientFlags::IS_CHECKER, value) - } pub fn set_is_contributor(&mut self, value: bool) { self.set(ClientFlags::IS_CONTRIBUTOR, value) } diff -r b3c9f5463cee -r 74ede02bc882 rust/hedgewars-server/src/core/room.rs --- a/rust/hedgewars-server/src/core/room.rs Sun Jul 05 02:03:08 2020 +0200 +++ b/rust/hedgewars-server/src/core/room.rs Sun Jul 05 14:53:44 2020 +0200 @@ -11,7 +11,7 @@ use std::{collections::HashMap, iter}; pub const MAX_TEAMS_IN_ROOM: u8 = 8; -pub const MAX_HEDGEHOGS_IN_ROOM: u8 = MAX_HEDGEHOGS_PER_TEAM * MAX_HEDGEHOGS_PER_TEAM; +pub const MAX_HEDGEHOGS_IN_ROOM: u8 = MAX_TEAMS_IN_ROOM * MAX_HEDGEHOGS_PER_TEAM; fn client_teams_impl( teams: &[(ClientId, TeamInfo)], @@ -24,13 +24,12 @@ } pub struct GameInfo { - pub teams_in_game: u8, - pub teams_at_start: Vec<(ClientId, TeamInfo)>, + pub original_teams: Vec<(ClientId, TeamInfo)>, pub left_teams: Vec, pub msg_log: Vec, pub sync_msg: Option, pub is_paused: bool, - config: RoomConfig, + original_config: RoomConfig, } impl GameInfo { @@ -40,14 +39,30 @@ msg_log: Vec::new(), sync_msg: None, is_paused: false, - teams_in_game: teams.len() as u8, - teams_at_start: teams, - config, + original_teams: teams, + original_config: config, } } pub fn client_teams(&self, client_id: ClientId) -> impl Iterator + Clone { - client_teams_impl(&self.teams_at_start, client_id) + client_teams_impl(&self.original_teams, client_id) + } + + pub fn mark_left_teams<'a, I>(&mut self, team_names: I) + where + I: Iterator, + { + if let Some(m) = &self.sync_msg { + self.msg_log.push(m.clone()); + self.sync_msg = None + } + + for team_name in team_names { + self.left_teams.push(team_name.clone()); + + let remove_msg = crate::utils::to_engine_msg(iter::once(b'F').chain(team_name.bytes())); + self.msg_log.push(remove_msg); + } } } @@ -62,7 +77,7 @@ const FIXED = 0b0000_0001; const RESTRICTED_JOIN = 0b0000_0010; const RESTRICTED_TEAM_ADD = 0b0000_0100; - const RESTRICTED_UNREGISTERED_PLAYERS = 0b0000_1000; + const REGISTRATION_REQUIRED = 0b0000_1000; } } @@ -142,18 +157,15 @@ &self.teams.last().unwrap().1 } - pub fn remove_team(&mut self, name: &str) { - if let Some(index) = self.teams.iter().position(|(_, t)| t.name == name) { + pub fn remove_team(&mut self, team_name: &str) { + if let Some(index) = self.teams.iter().position(|(_, t)| t.name == team_name) { self.teams.remove(index); } } pub fn set_hedgehogs_number(&mut self, n: u8) -> Vec { let mut names = Vec::new(); - let teams = match self.game_info { - Some(ref mut info) => &mut info.teams_at_start, - None => &mut self.teams, - }; + let teams = &mut self.teams; if teams.len() as u8 * n <= MAX_HEDGEHOGS_IN_ROOM { for (_, team) in teams.iter_mut() { @@ -165,6 +177,12 @@ names } + pub fn teams_in_game(&self) -> Option { + self.game_info + .as_ref() + .map(|info| (info.original_teams.len() - info.left_teams.len()) as u8) + } + pub fn find_team_and_owner_mut(&mut self, f: F) -> Option<(ClientId, &mut TeamInfo)> where F: Fn(&TeamInfo) -> bool, @@ -239,9 +257,8 @@ pub fn is_team_add_restricted(&self) -> bool { self.flags.contains(RoomFlags::RESTRICTED_TEAM_ADD) } - pub fn are_unregistered_players_restricted(&self) -> bool { - self.flags - .contains(RoomFlags::RESTRICTED_UNREGISTERED_PLAYERS) + pub fn is_registration_required(&self) -> bool { + self.flags.contains(RoomFlags::REGISTRATION_REQUIRED) } pub fn set_is_fixed(&mut self, value: bool) { @@ -254,8 +271,7 @@ self.flags.set(RoomFlags::RESTRICTED_TEAM_ADD, value) } pub fn set_unregistered_players_restriction(&mut self, value: bool) { - self.flags - .set(RoomFlags::RESTRICTED_UNREGISTERED_PLAYERS, value) + self.flags.set(RoomFlags::REGISTRATION_REQUIRED, value) } fn flags_string(&self) -> String { @@ -269,7 +285,7 @@ if self.is_join_restricted() { result += "j" } - if self.are_unregistered_players_restricted() { + if self.is_registration_required() { result += "r" } result @@ -290,23 +306,27 @@ ] } + pub fn config(&self) -> &RoomConfig { + &self.config + } + pub fn active_config(&self) -> &RoomConfig { match self.game_info { - Some(ref info) => &info.config, + Some(ref info) => &info.original_config, None => &self.config, } } pub fn map_config(&self) -> Vec { match self.game_info { - Some(ref info) => info.config.to_map_config(), + Some(ref info) => info.original_config.to_map_config(), None => self.config.to_map_config(), } } pub fn game_config(&self) -> Vec { match self.game_info { - Some(ref info) => info.config.to_game_config(), + Some(ref info) => info.original_config.to_game_config(), None => self.config.to_game_config(), } } diff -r b3c9f5463cee -r 74ede02bc882 rust/hedgewars-server/src/core/server.rs --- a/rust/hedgewars-server/src/core/server.rs Sun Jul 05 02:03:08 2020 +0200 +++ b/rust/hedgewars-server/src/core/server.rs Sun Jul 05 14:53:44 2020 +0200 @@ -1,18 +1,16 @@ use super::{ + anteroom::HwAnteroomClient, client::HwClient, indexslab::IndexSlab, room::HwRoom, - types::{ClientId, RoomId, ServerVar}, + types::{ClientId, GameCfg, RoomId, ServerVar, TeamInfo, Vote, VoteType, Voting}, }; -use crate::{protocol::messages::HwProtocolMessage::Greeting, utils}; +use crate::utils; -use crate::core::server::JoinRoomError::WrongProtocol; use bitflags::*; use log::*; -use slab; -use std::{borrow::BorrowMut, collections::HashSet, iter, num::NonZeroU16}; - -type Slab = slab::Slab; +use slab::Slab; +use std::{borrow::BorrowMut, cmp::min, collections::HashSet, iter, mem::replace}; #[derive(Debug)] pub enum CreateRoomError { @@ -24,8 +22,114 @@ pub enum JoinRoomError { DoesntExist, WrongProtocol, + WrongPassword, Full, Restricted, + RegistrationRequired, +} + +#[derive(Debug)] +pub enum LeaveRoomResult { + RoomRemoved, + RoomRemains { + is_empty: bool, + was_master: bool, + was_in_game: bool, + new_master: Option, + removed_teams: Vec, + }, +} + +#[derive(Debug)] +pub struct ChangeMasterResult { + pub old_master_id: Option, + pub new_master_id: ClientId, +} + +#[derive(Debug)] +pub enum ChangeMasterError { + NoAccess, + AlreadyMaster, + NoClient, + ClientNotInRoom, +} + +#[derive(Debug)] +pub enum AddTeamError { + TooManyTeams, + TooManyHedgehogs, + TeamAlreadyExists, + GameInProgress, + Restricted, +} + +#[derive(Debug)] +pub enum RemoveTeamError { + NoTeam, + TeamNotOwned, +} + +#[derive(Debug)] +pub enum ModifyTeamError { + NoTeam, + NotMaster, +} + +#[derive(Debug)] +pub enum SetTeamCountError { + InvalidNumber, + NotMaster, +} + +#[derive(Debug)] +pub enum SetHedgehogsError { + NoTeam, + InvalidNumber(u8), + NotMaster, +} + +#[derive(Debug)] +pub enum SetConfigError { + NotMaster, + RoomFixed, +} + +#[derive(Debug)] +pub enum ModifyRoomNameError { + AccessDenied, + InvalidName, + DuplicateName, +} + +#[derive(Debug)] +pub enum StartVoteError { + VotingInProgress, +} + +#[derive(Debug)] +pub enum VoteResult { + Submitted, + Succeeded(VoteType), + Failed, +} + +#[derive(Debug)] +pub enum VoteError { + NoVoting, + AlreadyVoted, +} + +#[derive(Debug)] +pub enum StartGameError { + NotEnoughClans, + NotReady, + AlreadyInGame, +} + +#[derive(Debug)] +pub struct EndGameResult { + pub left_teams: Vec, + pub unreadied_nicks: Vec, } #[derive(Debug)] @@ -33,47 +137,6 @@ #[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 { - pub clients: IndexSlab, -} - -impl HwAnteroom { - pub fn new(clients_limit: usize) -> Self { - let clients = IndexSlab::with_capacity(clients_limit); - HwAnteroom { clients } - } - - pub fn add_client(&mut self, client_id: ClientId, salt: String, is_local_admin: bool) { - let client = HwAnteClient { - nick: None, - protocol_number: None, - 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 client = self.clients.remove(client_id); - client - } -} - pub struct ServerGreetings { pub for_latest_protocol: String, pub for_old_protocols: String, @@ -94,23 +157,38 @@ } } +struct HwChecker { + pub id: ClientId, + pub is_ready: bool, +} + +impl HwChecker { + pub fn new(id: ClientId) -> Self { + Self { + id, + is_ready: false, + } + } +} + pub struct HwServer { - pub clients: IndexSlab, - pub rooms: Slab, - pub anteroom: HwAnteroom, - pub latest_protocol: u16, - pub flags: ServerFlags, - pub greetings: ServerGreetings, + clients: IndexSlab, + rooms: Slab, + checkers: IndexSlab, + latest_protocol: u16, + flags: ServerFlags, + greetings: ServerGreetings, } impl HwServer { pub fn new(clients_limit: usize, rooms_limit: usize) -> Self { let rooms = Slab::with_capacity(rooms_limit); let clients = IndexSlab::with_capacity(clients_limit); + let checkers = IndexSlab::new(); Self { clients, rooms, - anteroom: HwAnteroom::new(clients_limit), + checkers, greetings: ServerGreetings::new(), latest_protocol: 58, flags: ServerFlags::empty(), @@ -123,8 +201,13 @@ } #[inline] - pub fn client_mut(&mut self, client_id: ClientId) -> &mut HwClient { - &mut self.clients[client_id] + pub fn has_client(&self, client_id: ClientId) -> bool { + self.clients.contains(client_id) + } + + #[inline] + pub fn iter_clients(&self) -> impl Iterator + Clone { + self.clients.iter().map(|(_, c)| c) } #[inline] @@ -133,8 +216,38 @@ } #[inline] - pub fn room_mut(&mut self, room_id: RoomId) -> &mut HwRoom { - &mut self.rooms[room_id] + pub fn get_room(&self, room_id: RoomId) -> Option<&HwRoom> { + self.rooms.get(room_id) + } + + #[inline] + fn get_room_mut(&mut self, room_id: RoomId) -> Option<&mut HwRoom> { + self.rooms.get_mut(room_id) + } + + #[inline] + pub fn iter_rooms(&self) -> impl Iterator { + self.rooms.iter().map(|(_, r)| r) + } + + #[inline] + pub fn client_and_room(&self, client_id: ClientId, room_id: RoomId) -> (&HwClient, &HwRoom) { + (&self.clients[client_id], &self.rooms[room_id]) + } + + #[inline] + fn client_and_room_mut(&mut self, client_id: ClientId) -> Option<(&mut HwClient, &mut HwRoom)> { + let client = &mut self.clients[client_id]; + if let Some(room_id) = client.room_id { + Some((client, &mut self.rooms[room_id])) + } else { + None + } + } + + #[inline] + pub fn get_room_control(&mut self, client_id: ClientId) -> Option { + HwRoomControl::new(self, client_id) } #[inline] @@ -145,18 +258,19 @@ .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) { + pub fn add_client(&mut self, client_id: ClientId, data: HwAnteroomClient) { + if data.is_checker { + self.checkers.insert(client_id, HwChecker::new(client_id)); + } else if let (Some(protocol), Some(nick)) = (data.protocol_number, data.nick) { let mut client = HwClient::new(client_id, protocol.get(), nick); - client.set_is_checker(data.is_checker); #[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); + client.set_is_registered(data.is_registered); + client.set_is_admin(data.is_admin); + client.set_is_contributor(data.is_contributor); } self.clients.insert(client_id, client); @@ -176,11 +290,6 @@ } #[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, @@ -206,6 +315,7 @@ &mut self, client_id: ClientId, room_id: RoomId, + room_password: Option<&str>, ) -> Result<(&HwClient, &HwRoom, impl Iterator + Clone), JoinRoomError> { use JoinRoomError::*; let room = &mut self.rooms[room_id]; @@ -213,8 +323,15 @@ if client.protocol_number != room.protocol_number { Err(WrongProtocol) + } else if room.password.is_some() + && room_password != room.password.as_deref() + && !client.has_super_power() + { + Err(WrongPassword) } else if room.is_join_restricted() { Err(Restricted) + } else if room.is_registration_required() { + Err(RegistrationRequired) } else if room.players_number == u8::max_value() { Err(Full) } else { @@ -223,7 +340,8 @@ Ok(( &self.clients[client_id], &self.rooms[room_id], - self.clients.iter().map(|(_, c)| c), + self.iter_clients() + .filter(move |c| c.room_id == Some(room_id)), )) } } @@ -233,17 +351,26 @@ &mut self, client_id: ClientId, room_name: &str, + room_password: Option<&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) + self.join_room(client_id, room_id, room_password) } else { Err(DoesntExist) } } + pub fn enable_super_power(&mut self, client_id: ClientId) -> bool { + let client = &mut self.clients[client_id]; + if client.is_admin() { + client.set_has_super_power(true); + } + client.is_admin() + } + #[inline] pub fn set_var(&mut self, client_id: ClientId, var: ServerVar) -> Result<(), AccessError> { if self.clients[client_id].is_admin() { @@ -299,7 +426,7 @@ .find_map(|(_, r)| Some(r).filter(|r| r.name == name)) } - pub fn find_room_mut(&mut self, name: &str) -> Option<&mut HwRoom> { + fn find_room_mut(&mut self, name: &str) -> Option<&mut HwRoom> { self.rooms .iter_mut() .find_map(|(_, r)| Some(r).filter(|r| r.name == name)) @@ -311,13 +438,13 @@ .find_map(|(_, c)| Some(c).filter(|c| c.nick == nick)) } - pub fn find_client_mut(&mut self, nick: &str) -> Option<&mut HwClient> { + fn find_client_mut(&mut self, nick: &str) -> Option<&mut HwClient> { self.clients .iter_mut() .find_map(|(_, c)| Some(c).filter(|c| c.nick == nick)) } - pub fn all_clients(&self) -> impl Iterator + '_ { + pub fn iter_client_ids(&self) -> impl Iterator + '_ { self.clients.iter().map(|(id, _)| id) } @@ -335,7 +462,7 @@ self.rooms.iter().filter(f).map(|(_, c)| c.id) } - pub fn collect_clients(&self, f: F) -> Vec + pub fn collect_client_ids(&self, f: F) -> Vec where F: Fn(&(usize, &HwClient)) -> bool, { @@ -353,25 +480,25 @@ .collect() } - pub fn lobby_clients(&self) -> impl Iterator + '_ { + pub fn lobby_client_ids(&self) -> impl Iterator + '_ { self.filter_clients(|(_, c)| c.room_id == None) } - pub fn room_clients(&self, room_id: RoomId) -> impl Iterator + '_ { + pub fn room_client_ids(&self, room_id: RoomId) -> impl Iterator + '_ { self.filter_clients(move |(_, c)| c.room_id == Some(room_id)) } - pub fn protocol_clients(&self, protocol: u16) -> impl Iterator + '_ { + pub fn protocol_client_ids(&self, protocol: u16) -> impl Iterator + '_ { self.filter_clients(move |(_, c)| c.protocol_number == protocol) } - pub fn protocol_rooms(&self, protocol: u16) -> impl Iterator + '_ { + pub fn protocol_room_ids(&self, protocol: u16) -> impl Iterator + '_ { self.filter_rooms(move |(_, r)| r.protocol_number == protocol) } - pub fn other_clients_in_room(&self, self_id: ClientId) -> Vec { + pub fn other_client_ids_in_room(&self, self_id: ClientId) -> Vec { let room_id = self.clients[self_id].room_id; - self.collect_clients(|(id, c)| *id != self_id && c.room_id == room_id) + self.collect_client_ids(|(id, c)| *id != self_id && c.room_id == room_id) } pub fn is_registered_only(&self) -> bool { @@ -381,6 +508,557 @@ pub fn set_is_registered_only(&mut self, value: bool) { self.flags.set(ServerFlags::REGISTERED_ONLY, value) } + + pub fn set_room_saves(&mut self, room_id: RoomId, text: &str) -> Result<(), serde_yaml::Error> { + if let Some(room) = self.rooms.get_mut(room_id) { + room.set_saves(text) + } else { + Ok(()) + } + } +} + +pub struct HwRoomControl<'a> { + server: &'a mut HwServer, + client_id: ClientId, + room_id: RoomId, + is_room_removed: bool, +} + +impl<'a> HwRoomControl<'a> { + #[inline] + pub fn new(server: &'a mut HwServer, client_id: ClientId) -> Option { + if let Some(room_id) = server.clients[client_id].room_id { + Some(Self { + server, + client_id, + room_id, + is_room_removed: false, + }) + } else { + None + } + } + + #[inline] + pub fn cleanup_room(self) { + if self.is_room_removed { + self.server.rooms.remove(self.room_id); + } + } + + #[inline] + pub fn server(&self) -> &HwServer { + self.server + } + + #[inline] + pub fn client(&self) -> &HwClient { + &self.server.clients[self.client_id] + } + + #[inline] + fn client_mut(&mut self) -> &mut HwClient { + &mut self.server.clients[self.client_id] + } + + #[inline] + pub fn room(&self) -> &HwRoom { + &self.server.rooms[self.room_id] + } + + #[inline] + fn room_mut(&mut self) -> &mut HwRoom { + &mut self.server.rooms[self.room_id] + } + + #[inline] + pub fn get(&self) -> (&HwClient, &HwRoom) { + (self.client(), self.room()) + } + + #[inline] + fn get_mut(&mut self) -> (&mut HwClient, &mut HwRoom) { + ( + &mut self.server.clients[self.client_id], + &mut self.server.rooms[self.room_id], + ) + } + + pub fn change_client<'b: 'a>(self, client_id: ClientId) -> Option> { + let room_id = self.room_id; + HwRoomControl::new(self.server, client_id).filter(|c| c.room_id == room_id) + } + + pub fn leave_room(&mut self) -> LeaveRoomResult { + let (client, room) = self.get_mut(); + room.players_number -= 1; + client.room_id = None; + + let is_empty = room.players_number == 0; + let is_fixed = room.is_fixed(); + let was_master = room.master_id == Some(client.id); + let was_in_game = client.is_in_game(); + let mut removed_teams = vec![]; + + if is_empty && !is_fixed { + if client.is_ready() && room.ready_players_number > 0 { + room.ready_players_number -= 1; + } + + if let Some(ref mut info) = room.game_info { + removed_teams = info + .client_teams(client.id) + .map(|t| t.name.clone()) + .collect(); + info.mark_left_teams(removed_teams.iter()); + } else { + removed_teams = room + .client_teams(client.id) + .map(|t| t.name.clone()) + .collect(); + for team_name in &removed_teams { + room.remove_team(team_name); + } + } + + if client.is_master() && !is_fixed { + client.set_is_master(false); + room.master_id = None; + } + } + + client.set_is_ready(false); + client.set_is_in_game(false); + + if !is_fixed { + if room.players_number == 0 { + self.is_room_removed = true + } else if room.master_id == None { + let protocol_number = room.protocol_number; + let new_master_id = self.server.room_client_ids(self.room_id).next(); + + if let Some(new_master_id) = new_master_id { + let room = self.room_mut(); + room.master_id = Some(new_master_id); + let new_master = &mut self.server.clients[new_master_id]; + new_master.set_is_master(true); + + if protocol_number < 42 { + let nick = new_master.nick.clone(); + self.room_mut().name = nick; + } + + let room = self.room_mut(); + room.set_join_restriction(false); + room.set_team_add_restriction(false); + room.set_unregistered_players_restriction(true); + } + } + } + + if is_empty && !is_fixed { + LeaveRoomResult::RoomRemoved + } else { + LeaveRoomResult::RoomRemains { + is_empty, + was_master, + was_in_game, + new_master: self.room().master_id, + removed_teams, + } + } + } + + pub fn change_master( + &mut self, + new_master_nick: String, + ) -> Result { + use ChangeMasterError::*; + let (client, room) = self.get_mut(); + + if client.is_admin() || room.master_id == Some(client.id) { + let new_master_id = self + .server + .clients + .iter() + .find(|(_, c)| c.nick == new_master_nick) + .map(|(id, _)| id); + + match new_master_id { + Some(new_master_id) if new_master_id == self.client_id => Err(AlreadyMaster), + Some(new_master_id) => { + let new_master = &mut self.server.clients[new_master_id]; + if new_master.room_id == Some(self.room_id) { + self.server.clients[new_master_id].set_is_master(true); + let old_master_id = self.room().master_id; + + if let Some(master_id) = old_master_id { + self.server.clients[master_id].set_is_master(false); + } + self.room_mut().master_id = Some(new_master_id); + Ok(ChangeMasterResult { + old_master_id, + new_master_id, + }) + } else { + Err(ClientNotInRoom) + } + } + None => Err(NoClient), + } + } else { + Err(NoAccess) + } + } + + pub fn start_vote(&mut self, kind: VoteType) -> Result<(), StartVoteError> { + use StartVoteError::*; + match self.room().voting { + Some(_) => Err(VotingInProgress), + None => { + let voting = Voting::new(kind, self.server.room_client_ids(self.room_id).collect()); + self.room_mut().voting = Some(voting); + Ok(()) + } + } + } + + pub fn vote(&mut self, vote: Vote) -> Result { + use self::{VoteError::*, VoteResult::*}; + let client_id = self.client_id; + if let Some(ref mut voting) = self.room_mut().voting { + if vote.is_forced || voting.votes.iter().all(|(id, _)| client_id != *id) { + voting.votes.push((client_id, vote.is_pro)); + let i = voting.votes.iter(); + let pro = i.clone().filter(|(_, v)| *v).count(); + let contra = i.filter(|(_, v)| !*v).count(); + let success_quota = voting.voters.len() / 2 + 1; + if vote.is_forced && vote.is_pro || pro >= success_quota { + let voting = self.room_mut().voting.take().unwrap(); + Ok(Succeeded(voting.kind)) + } else if vote.is_forced && !vote.is_pro + || contra > voting.voters.len() - success_quota + { + Ok(Failed) + } else { + Ok(Submitted) + } + } else { + Err(AlreadyVoted) + } + } else { + Err(NoVoting) + } + } + + pub fn toggle_flag(&mut self, flags: super::room::RoomFlags) -> bool { + let (client, room) = self.get_mut(); + if client.is_master() { + room.flags.toggle(flags); + } + client.is_master() + } + + pub fn fix_room(&mut self) -> Result<(), AccessError> { + let (client, room) = self.get_mut(); + if client.is_admin() { + room.set_is_fixed(true); + room.set_join_restriction(false); + room.set_team_add_restriction(false); + room.set_unregistered_players_restriction(true); + Ok(()) + } else { + Err(AccessError()) + } + } + + pub fn unfix_room(&mut self) -> Result<(), AccessError> { + let (client, room) = self.get_mut(); + if client.is_admin() { + room.set_is_fixed(false); + Ok(()) + } else { + Err(AccessError()) + } + } + + pub fn set_room_name(&mut self, mut name: String) -> Result { + use ModifyRoomNameError::*; + let room_exists = self.server.has_room(&name); + let (client, room) = self.get_mut(); + if room.is_fixed() || room.master_id != Some(client.id) { + Err(AccessDenied) + } else if utils::is_name_illegal(&name) { + Err(InvalidName) + } else if room_exists { + Err(DuplicateName) + } else { + std::mem::swap(&mut room.name, &mut name); + Ok(name) + } + } + + pub fn set_room_greeting(&mut self, greeting: Option) -> Result<(), AccessError> { + let (client, room) = self.get_mut(); + if client.is_admin() { + room.greeting = greeting.unwrap_or(String::new()); + Ok(()) + } else { + Err(AccessError()) + } + } + + pub fn set_room_max_teams(&mut self, count: u8) -> Result<(), SetTeamCountError> { + use SetTeamCountError::*; + let (client, room) = self.get_mut(); + if !client.is_master() { + Err(NotMaster) + } else if !(2..=super::room::MAX_TEAMS_IN_ROOM).contains(&count) { + Err(InvalidNumber) + } else { + room.max_teams = count; + Ok(()) + } + } + + pub fn set_team_hedgehogs_number( + &mut self, + team_name: &str, + number: u8, + ) -> Result<(), SetHedgehogsError> { + use SetHedgehogsError::*; + let (client, room) = self.get_mut(); + let addable_hedgehogs = room.addable_hedgehogs(); + if let Some((_, team)) = room.find_team_and_owner_mut(|t| t.name == team_name) { + let max_hedgehogs = min( + super::room::MAX_HEDGEHOGS_IN_ROOM, + addable_hedgehogs + team.hedgehogs_number, + ); + if !client.is_master() { + Err(NotMaster) + } else if !(1..=max_hedgehogs).contains(&number) { + Err(InvalidNumber(team.hedgehogs_number)) + } else { + team.hedgehogs_number = number; + Ok(()) + } + } else { + Err(NoTeam) + } + } + + pub fn set_hedgehogs_number(&mut self, number: u8) -> Vec { + self.room_mut().set_hedgehogs_number(number) + } + + pub fn add_team(&mut self, mut info: Box) -> Result<&TeamInfo, AddTeamError> { + use AddTeamError::*; + let (client, room) = self.get_mut(); + if room.teams.len() >= room.max_teams as usize { + Err(TooManyTeams) + } else if room.addable_hedgehogs() == 0 { + Err(TooManyHedgehogs) + } else if room.find_team(|t| t.name == info.name) != None { + Err(TeamAlreadyExists) + } else if room.game_info.is_some() { + Err(GameInProgress) + } else if room.is_team_add_restricted() { + Err(Restricted) + } else { + info.owner = client.nick.clone(); + let team = room.add_team(client.id, *info, client.protocol_number < 42); + client.teams_in_game += 1; + client.clan = Some(team.color); + Ok(team) + } + } + + pub fn remove_team(&mut self, team_name: &str) -> Result<(), RemoveTeamError> { + use RemoveTeamError::*; + let (client, room) = self.get_mut(); + match room.find_team_owner(team_name) { + None => Err(NoTeam), + Some((id, _)) if id != client.id => Err(RemoveTeamError::TeamNotOwned), + Some(_) => { + client.teams_in_game -= 1; + client.clan = room.find_team_color(client.id); + room.remove_team(team_name); + Ok(()) + } + } + } + + pub fn set_team_color(&mut self, team_name: &str, color: u8) -> Result<(), ModifyTeamError> { + use ModifyTeamError::*; + let (client, room) = self.get_mut(); + if let Some((owner, team)) = room.find_team_and_owner_mut(|t| t.name == team_name) { + if !client.is_master() { + Err(NotMaster) + } else { + team.color = color; + self.server.clients[owner].clan = Some(color); + Ok(()) + } + } else { + Err(NoTeam) + } + } + + pub fn set_config(&mut self, cfg: GameCfg) -> Result<(), SetConfigError> { + use SetConfigError::*; + let (client, room) = self.get_mut(); + if room.is_fixed() { + Err(RoomFixed) + } else if !client.is_master() { + Err(NotMaster) + } else { + let cfg = match cfg { + GameCfg::Scheme(name, mut values) => { + if client.protocol_number == 49 && values.len() >= 2 { + let mut s = "X".repeat(50); + s.push_str(&values.pop().unwrap()); + values.push(s); + } + GameCfg::Scheme(name, values) + } + cfg => cfg, + }; + + room.set_config(cfg); + Ok(()) + } + } + + pub fn save_config(&mut self, name: String, location: String) { + self.room_mut().save_config(name, location); + } + + pub fn load_config(&mut self, name: &str) -> Option<&str> { + self.room_mut().load_config(name) + } + + pub fn delete_config(&mut self, name: &str) -> bool { + self.room_mut().delete_config(name) + } + + pub fn toggle_ready(&mut self) -> bool { + let (client, room) = self.get_mut(); + client.set_is_ready(!client.is_ready()); + if client.is_ready() { + room.ready_players_number += 1; + } else { + room.ready_players_number -= 1; + } + client.is_ready() + } + + pub fn start_game(&mut self) -> Result, StartGameError> { + use StartGameError::*; + let (room_clients, room_nicks): (Vec<_>, Vec<_>) = self + .server + .clients + .iter() + .map(|(id, c)| (id, c.nick.clone())) + .unzip(); + + let room = self.room_mut(); + + if !room.has_multiple_clans() { + Err(NotEnoughClans) + } else if room.protocol_number <= 43 && room.players_number != room.ready_players_number { + Err(NotReady) + } else if room.game_info.is_some() { + Err(AlreadyInGame) + } else { + room.start_round(); + for id in room_clients { + let team_indices = self.room().client_team_indices(id); + let c = &mut self.server.clients[id]; + c.set_is_in_game(true); + c.team_indices = team_indices; + } + Ok(room_nicks) + } + } + + pub fn toggle_pause(&mut self) -> bool { + if let Some(ref mut info) = self.room_mut().game_info { + info.is_paused = !info.is_paused; + } + self.room_mut().game_info.is_some() + } + + pub fn leave_game(&mut self) -> Option> { + let (client, room) = self.get_mut(); + let client_left = client.is_in_game(); + if client_left { + client.set_is_in_game(false); + + if let Some(ref mut info) = room.game_info { + let team_names: Vec<_> = info + .client_teams(client.id) + .map(|t| t.name.clone()) + .collect(); + + info.mark_left_teams(team_names.iter()); + + Some(team_names) + } else { + None + } + } else { + None + } + } + + pub fn end_game(&mut self) -> Option { + let room = self.room_mut(); + room.ready_players_number = room.master_id.is_some() as u8; + + if let Some(mut info) = replace(&mut room.game_info, None) { + let room_id = room.id; + for team_name in &info.left_teams { + room.remove_team(team_name); + } + + let unreadied_nicks: Vec<_> = self + .server + .clients + .iter_mut() + .filter(|(_, c)| c.room_id == Some(room_id)) + .map(|(_, c)| { + c.set_is_ready(c.is_master()); + c + }) + .filter_map(|c| { + if !c.is_master() { + Some(c.nick.clone()) + } else { + None + } + }) + .collect(); + + Some(EndGameResult { + left_teams: replace(&mut info.left_teams, vec![]), + unreadied_nicks, + }) + } else { + None + } + } + + pub fn log_engine_msg(&mut self, log_msg: String, sync_msg: Option>) { + if let Some(ref mut info) = self.room_mut().game_info { + if !log_msg.is_empty() { + info.msg_log.push(log_msg); + } + if let Some(msg) = sync_msg { + info.sync_msg = msg; + } + } + } } fn allocate_room(rooms: &mut Slab) -> &mut HwRoom { @@ -408,7 +1086,10 @@ client.room_id = Some(room.id); client.set_is_master(true); client.set_is_ready(true); - client.set_is_joined_mid_game(false); + client.set_is_in_game(false); + client.clan = None; + client.teams_in_game = 0; + client.team_indices = vec![]; (client, room) } @@ -419,7 +1100,6 @@ room.players_number += 1; client.room_id = Some(room.id); - client.set_is_joined_mid_game(room.game_info.is_some()); client.set_is_in_game(room.game_info.is_some()); if let Some(ref mut info) = room.game_info { @@ -430,13 +1110,6 @@ if !team_names.is_empty() { info.left_teams.retain(|name| !team_names.contains(&name)); - info.teams_in_game += team_names.len() as u8; - room.teams = info - .teams_at_start - .iter() - .filter(|(_, t)| !team_names.contains(&t.name)) - .cloned() - .collect(); } } } diff -r b3c9f5463cee -r 74ede02bc882 rust/hedgewars-server/src/core/types.rs --- a/rust/hedgewars-server/src/core/types.rs Sun Jul 05 02:03:08 2020 +0200 +++ b/rust/hedgewars-server/src/core/types.rs Sun Jul 05 14:53:44 2020 +0200 @@ -28,7 +28,7 @@ DrawnMap(String), } -#[derive(PartialEq, Eq, Clone, Debug)] +#[derive(PartialEq, Eq, Clone, Debug, Default)] pub struct TeamInfo { pub owner: String, pub name: String, @@ -42,7 +42,7 @@ pub hedgehogs: [HedgehogInfo; MAX_HEDGEHOGS_PER_TEAM as usize], } -#[derive(PartialEq, Eq, Clone, Debug)] +#[derive(PartialEq, Eq, Clone, Debug, Default)] pub struct HedgehogInfo { pub name: String, pub hat: String, diff -r b3c9f5463cee -r 74ede02bc882 rust/hedgewars-server/src/handlers.rs --- a/rust/hedgewars-server/src/handlers.rs Sun Jul 05 02:03:08 2020 +0200 +++ b/rust/hedgewars-server/src/handlers.rs Sun Jul 05 14:53:44 2020 +0200 @@ -1,4 +1,3 @@ -use mio; use std::{ cmp::PartialEq, collections::HashMap, @@ -13,6 +12,7 @@ }; use crate::{ core::{ + anteroom::HwAnteroom, room::RoomSave, server::HwServer, types::{ClientId, GameCfg, Replay, RoomId, TeamInfo}, @@ -82,6 +82,20 @@ } } +pub struct ServerState { + pub server: HwServer, + pub anteroom: HwAnteroom, +} + +impl ServerState { + pub fn new(clients_limit: usize, rooms_limit: usize) -> Self { + Self { + server: HwServer::new(clients_limit, rooms_limit), + anteroom: HwAnteroom::new(clients_limit), + } + } +} + #[derive(Debug)] pub struct AccountInfo { pub is_registered: bool, @@ -101,6 +115,10 @@ client_salt: String, server_salt: String, }, + GetCheckerAccount { + nick: String, + password: String, + }, GetReplay { id: u32, }, @@ -119,6 +137,7 @@ pub enum IoResult { AccountRegistered(bool), Account(Option), + CheckerAccount { is_registered: bool }, Replay(Option), SaveRoom(RoomId, bool), LoadRoom(RoomId, Option), @@ -219,10 +238,10 @@ Destination::ToIds(ids) => ids, Destination::ToAll { group, skip_self } => { let mut ids: Vec<_> = match group { - DestinationGroup::All => server.all_clients().collect(), - DestinationGroup::Lobby => server.lobby_clients().collect(), - DestinationGroup::Protocol(proto) => server.protocol_clients(proto).collect(), - DestinationGroup::Room(id) => server.room_clients(id).collect(), + DestinationGroup::All => server.iter_client_ids().collect(), + DestinationGroup::Lobby => server.lobby_client_ids().collect(), + DestinationGroup::Protocol(proto) => server.protocol_client_ids(proto).collect(), + DestinationGroup::Room(id) => server.room_client_ids(id).collect(), }; if skip_self { @@ -237,7 +256,7 @@ } pub fn handle( - server: &mut HwServer, + state: &mut ServerState, client_id: ClientId, response: &mut Response, message: HwProtocolMessage, @@ -246,35 +265,42 @@ HwProtocolMessage::Ping => response.add(Pong.send_self()), HwProtocolMessage::Pong => (), _ => { - if server.anteroom.clients.contains(client_id) { - match inanteroom::handle(server, client_id, response, message) { + if state.anteroom.clients.contains(client_id) { + match inanteroom::handle(state, client_id, response, message) { LoginResult::Unchanged => (), LoginResult::Complete => { - if let Some(client) = server.anteroom.remove_client(client_id) { - server.add_client(client_id, client); - common::get_lobby_join_data(server, response); + if let Some(client) = state.anteroom.remove_client(client_id) { + let is_checker = client.is_checker; + state.server.add_client(client_id, client); + if !is_checker { + common::get_lobby_join_data(&state.server, response); + } } } LoginResult::Exit => { - server.anteroom.remove_client(client_id); + state.anteroom.remove_client(client_id); response.remove_client(client_id); } } - } else if server.clients.contains(client_id) { + } else if state.server.has_client(client_id) { match message { HwProtocolMessage::Quit(Some(msg)) => { - common::remove_client(server, response, "User quit: ".to_string() + &msg); + common::remove_client( + &mut state.server, + response, + "User quit: ".to_string() + &msg, + ); } HwProtocolMessage::Quit(None) => { - common::remove_client(server, response, "User quit".to_string()); + common::remove_client(&mut state.server, response, "User quit".to_string()); } HwProtocolMessage::Info(nick) => { - if let Some(client) = server.find_client(&nick) { + if let Some(client) = state.server.find_client(&nick) { let admin_sign = if client.is_admin() { "@" } else { "" }; let master_sign = if client.is_master() { "+" } else { "" }; let room_info = match client.room_id { Some(room_id) => { - let room = server.room(room_id); + let room = state.server.room(room_id); let status = match room.game_info { Some(_) if client.teams_in_game == 0 => "(spectating)", Some(_) => "(playing)", @@ -300,11 +326,13 @@ } } HwProtocolMessage::ToggleServerRegisteredOnly => { - if !server.is_admin(client_id) { + if !state.server.is_admin(client_id) { response.warn(ACCESS_DENIED); } else { - server.set_is_registered_only(!server.is_registered_only()); - let msg = if server.is_registered_only() { + state + .server + .set_is_registered_only(!state.server.is_registered_only()); + let msg = if state.server.is_registered_only() { REGISTERED_ONLY_ENABLED } else { REGISTERED_ONLY_DISABLED @@ -313,19 +341,17 @@ } } HwProtocolMessage::Global(msg) => { - if !server.is_admin(client_id) { + if !state.server.is_admin(client_id) { response.warn(ACCESS_DENIED); } else { response.add(global_chat(msg).send_all()) } } HwProtocolMessage::SuperPower => { - let client = server.client_mut(client_id); - if !client.is_admin() { + if state.server.enable_super_power(client_id) { + response.add(server_chat(SUPER_POWER.to_string()).send_self()) + } else { response.warn(ACCESS_DENIED); - } else { - client.set_has_super_power(true); - response.add(server_chat(SUPER_POWER.to_string()).send_self()) } } HwProtocolMessage::Watch(id) => { @@ -339,11 +365,9 @@ response.warn(REPLAY_NOT_SUPPORTED); } } - _ => 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) - } + _ => match state.server.get_room_control(client_id) { + None => inlobby::handle(&mut state.server, client_id, response, message), + Some(control) => inroom::handle(control, response, message), }, } } @@ -352,62 +376,82 @@ } pub fn handle_client_accept( - server: &mut HwServer, + state: &mut ServerState, client_id: ClientId, response: &mut Response, + addr: [u8; 4], is_local: bool, ) { - let mut salt = [0u8; 18]; - thread_rng().fill_bytes(&mut salt); + let ban_reason = Some(addr) + .filter(|_| !is_local) + .and_then(|a| state.anteroom.find_ip_ban(a)); + if let Some(reason) = ban_reason { + response.add(HwServerMessage::Bye(reason).send_self()); + response.remove_client(client_id); + } else { + let mut salt = [0u8; 18]; + thread_rng().fill_bytes(&mut salt); - server - .anteroom - .add_client(client_id, encode(&salt), is_local); + state + .anteroom + .add_client(client_id, encode(&salt), is_local); - response.add(HwServerMessage::Connected(utils::SERVER_VERSION).send_self()); + response.add(HwServerMessage::Connected(utils::SERVER_VERSION).send_self()); + } } -pub fn handle_client_loss(server: &mut HwServer, client_id: ClientId, response: &mut Response) { - if server.anteroom.remove_client(client_id).is_none() { - common::remove_client(server, response, "Connection reset".to_string()); +pub fn handle_client_loss(state: &mut ServerState, client_id: ClientId, response: &mut Response) { + if state.anteroom.remove_client(client_id).is_none() { + common::remove_client(&mut state.server, response, "Connection reset".to_string()); } } pub fn handle_io_result( - server: &mut HwServer, + state: &mut ServerState, client_id: ClientId, response: &mut Response, io_result: IoResult, ) { match io_result { IoResult::AccountRegistered(is_registered) => { - if !is_registered && server.is_registered_only() { + if !is_registered && state.server.is_registered_only() { 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::get_lobby_join_data(server, response); + let client = &state.anteroom.clients[client_id]; + response.add(AskPassword(client.server_salt.clone()).send_self()); + } else if let Some(client) = state.anteroom.remove_client(client_id) { + state.server.add_client(client_id, client); + common::get_lobby_join_data(&state.server, response); } } + IoResult::Account(None) => { + response.add(Bye(AUTHENTICATION_FAILED.to_string()).send_self()); + response.remove_client(client_id); + } IoResult::Account(Some(info)) => { response.add(ServerAuth(format!("{:x}", info.server_hash)).send_self()); - if let Some(mut client) = server.anteroom.remove_client(client_id) { + if let Some(mut client) = state.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); - common::get_lobby_join_data(server, response); + state.server.add_client(client_id, client); + common::get_lobby_join_data(&state.server, response); } } - IoResult::Account(None) => { - response.error(AUTHENTICATION_FAILED); - response.remove_client(client_id); + IoResult::CheckerAccount { is_registered } => { + if is_registered { + if let Some(client) = state.anteroom.remove_client(client_id) { + state.server.add_client(client_id, client); + response.add(LogonPassed.send_self()); + } + } else { + response.add(Bye(NO_CHECKER_RIGHTS.to_string()).send_self()); + response.remove_client(client_id); + } } IoResult::Replay(Some(replay)) => { - let client = server.client(client_id); + let client = state.server.client(client_id); let protocol = client.protocol_number; let start_msg = if protocol < 58 { RoomJoined(vec![client.nick.clone()]) @@ -416,8 +460,8 @@ }; response.add(start_msg.send_self()); - common::get_room_config_impl(&replay.config, client_id, response); - common::get_teams(replay.teams.iter(), client_id, response); + common::get_room_config_impl(&replay.config, Destination::ToSelf, response); + common::get_teams(replay.teams.iter(), Destination::ToSelf, response); response.add(RunGame.send_self()); response.add(ForwardEngineMessage(replay.message_log).send_self()); @@ -435,13 +479,11 @@ 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_CONFIG_LOADED.to_string()).send_self()), - Err(e) => { - warn!("Error while deserializing the room configs: {}", e); - response.warn(ROOM_CONFIG_DESERIALIZE_FAILED); - } + match state.server.set_room_saves(room_id, &contents) { + Ok(_) => response.add(server_chat(ROOM_CONFIG_LOADED.to_string()).send_self()), + Err(e) => { + warn!("Error while deserializing the room configs: {}", e); + response.warn(ROOM_CONFIG_DESERIALIZE_FAILED); } } } diff -r b3c9f5463cee -r 74ede02bc882 rust/hedgewars-server/src/handlers/actions.rs --- a/rust/hedgewars-server/src/handlers/actions.rs Sun Jul 05 02:03:08 2020 +0200 +++ b/rust/hedgewars-server/src/handlers/actions.rs Sun Jul 05 14:53:44 2020 +0200 @@ -12,6 +12,7 @@ use rand::{distributions::Uniform, thread_rng, Rng}; use std::{io, io::Write, iter::once, mem::replace}; +#[derive(Clone)] pub enum DestinationGroup { All, Lobby, @@ -19,6 +20,7 @@ Protocol(u16), } +#[derive(Clone)] pub enum Destination { ToId(ClientId), ToIds(Vec), @@ -112,4 +114,10 @@ pub fn send_all(self) -> PendingMessage { PendingMessage::send_all(self) } + pub fn send_to_destination(self, destination: Destination) -> PendingMessage { + PendingMessage { + destination, + message: self, + } + } } diff -r b3c9f5463cee -r 74ede02bc882 rust/hedgewars-server/src/handlers/common.rs --- a/rust/hedgewars-server/src/handlers/common.rs Sun Jul 05 02:03:08 2020 +0200 +++ b/rust/hedgewars-server/src/handlers/common.rs Sun Jul 05 14:53:44 2020 +0200 @@ -2,8 +2,11 @@ core::{ client::HwClient, room::HwRoom, - server::{HwServer, JoinRoomError}, - types::{ClientId, GameCfg, RoomId, TeamInfo, Vote, VoteType}, + server::{ + EndGameResult, HwRoomControl, HwServer, JoinRoomError, LeaveRoomResult, StartGameError, + VoteError, VoteResult, + }, + types::{ClientId, GameCfg, RoomId, TeamInfo, Vote, VoteType, MAX_HEDGEHOGS_PER_TEAM}, }, protocol::messages::{ add_flags, remove_flags, server_chat, @@ -14,7 +17,10 @@ utils::to_engine_msg, }; -use super::Response; +use super::{ + actions::{Destination, DestinationGroup}, + Response, +}; use crate::core::types::RoomConfig; use rand::{self, seq::SliceRandom, thread_rng, Rng}; @@ -73,10 +79,9 @@ let rooms_msg = Rooms( server - .rooms - .iter() - .filter(|(_, r)| r.protocol_number == client.protocol_number) - .flat_map(|(_, r)| r.info(r.master_id.map(|id| &server.clients[id]))) + .iter_rooms() + .filter(|r| r.protocol_number == client.protocol_number) + .flat_map(|r| r.info(r.master_id.map(|id| server.client(id)))) .collect(), ); @@ -98,135 +103,6 @@ response.add(rooms_msg.send_self()); } -pub fn remove_teams( - room: &mut HwRoom, - team_names: Vec, - is_in_game: bool, - response: &mut Response, -) { - if let Some(ref mut info) = room.game_info { - for team_name in &team_names { - info.left_teams.push(team_name.clone()); - - if is_in_game { - let msg = once(b'F').chain(team_name.bytes()); - response.add( - ForwardEngineMessage(vec![to_engine_msg(msg)]) - .send_all() - .in_room(room.id) - .but_self(), - ); - - info.teams_in_game -= 1; - - let remove_msg = to_engine_msg(once(b'F').chain(team_name.bytes())); - if let Some(m) = &info.sync_msg { - info.msg_log.push(m.clone()); - info.sync_msg = None - } - info.msg_log.push(remove_msg.clone()); - - response.add( - ForwardEngineMessage(vec![remove_msg]) - .send_all() - .in_room(room.id) - .but_self(), - ); - } - } - } - - for team_name in team_names { - room.remove_team(&team_name); - response.add(TeamRemove(team_name).send_all().in_room(room.id)); - } -} - -fn remove_client_from_room( - client: &mut HwClient, - room: &mut HwRoom, - response: &mut Response, - msg: &str, -) { - room.players_number -= 1; - if room.players_number > 0 || room.is_fixed() { - if client.is_ready() && room.ready_players_number > 0 { - room.ready_players_number -= 1; - } - - let team_names: Vec<_> = room - .client_teams(client.id) - .map(|t| t.name.clone()) - .collect(); - remove_teams(room, team_names, client.is_in_game(), response); - - if room.players_number > 0 { - response.add( - RoomLeft(client.nick.clone(), msg.to_string()) - .send_all() - .in_room(room.id) - .but_self(), - ); - } - - if client.is_master() && !room.is_fixed() { - client.set_is_master(false); - response.add( - ClientFlags( - remove_flags(&[Flags::RoomMaster]), - vec![client.nick.clone()], - ) - .send_all() - .in_room(room.id), - ); - room.master_id = None; - } - } - - client.room_id = None; - - let update_msg = if room.players_number == 0 && !room.is_fixed() { - RoomRemove(room.name.clone()) - } else { - RoomUpdated(room.name.clone(), room.info(Some(&client))) - }; - response.add(update_msg.send_all().with_protocol(room.protocol_number)); - - response.add(ClientFlags(remove_flags(&[Flags::InRoom]), vec![client.nick.clone()]).send_all()); -} - -pub fn change_master( - server: &mut HwServer, - room_id: RoomId, - new_master_id: ClientId, - response: &mut Response, -) { - let room = &mut server.rooms[room_id]; - if let Some(master_id) = room.master_id { - server.clients[master_id].set_is_master(false); - response.add( - ClientFlags( - remove_flags(&[Flags::RoomMaster]), - vec![server.clients[master_id].nick.clone()], - ) - .send_all() - .in_room(room_id), - ) - } - - room.master_id = Some(new_master_id); - server.clients[new_master_id].set_is_master(true); - - response.add( - ClientFlags( - add_flags(&[Flags::RoomMaster]), - vec![server.clients[new_master_id].nick.clone()], - ) - .send_all() - .in_room(room_id), - ); -} - pub fn get_room_join_data<'a, I: Iterator + Clone>( client: &HwClient, room: &HwRoom, @@ -234,42 +110,67 @@ response: &mut Response, ) { #[inline] - fn collect_nicks<'a, I, F>(clients: I, f: F) -> Vec + fn partition_nicks<'a, I, F>(clients: I, f: F) -> (Vec, Vec) where - I: Iterator, + I: Iterator + Clone, F: Fn(&&'a HwClient) -> bool, { - clients.filter(f).map(|c| &c.nick).cloned().collect() + ( + clients + .clone() + .filter(|c| f(c)) + .map(|c| &c.nick) + .cloned() + .collect(), + clients + .filter(|c| !f(c)) + .map(|c| &c.nick) + .cloned() + .collect(), + ) } 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 = collect_nicks(room_clients.clone(), |c| c.room_id == Some(room.id)); + response.add( + RoomJoined(vec![nick.clone()]) + .send_all() + .in_room(room.id) + .but_self(), + ); + response.add(ClientFlags(add_flags(&[Flags::InRoom]), vec![nick.clone()]).send_all()); + let nicks = room_clients.clone().map(|c| c.nick.clone()).collect(); response.add(RoomJoined(nicks).send_self()); - get_room_teams(room, client.id, response); - get_room_config(room, client.id, response); - let mut flag_selectors = [ ( Flags::RoomMaster, - collect_nicks(room_clients.clone(), |c| c.is_master()), + partition_nicks(room_clients.clone(), |c| c.is_master()), ), ( Flags::Ready, - collect_nicks(room_clients.clone(), |c| c.is_ready()), + partition_nicks(room_clients.clone(), |c| c.is_ready()), ), ( Flags::InGame, - collect_nicks(room_clients.clone(), |c| c.is_in_game()), + partition_nicks(room_clients.clone(), |c| c.is_in_game()), ), ]; - for (flag, nicks) in &mut flag_selectors { - response.add(ClientFlags(add_flags(&[*flag]), replace(nicks, vec![])).send_self()); + for (flag, (set_nicks, cleared_nicks)) in &mut flag_selectors { + if !set_nicks.is_empty() { + response.add(ClientFlags(add_flags(&[*flag]), replace(set_nicks, vec![])).send_self()); + } + + if !cleared_nicks.is_empty() { + response.add( + ClientFlags(remove_flags(&[*flag]), replace(cleared_nicks, vec![])).send_self(), + ); + } } + get_active_room_teams(room, Destination::ToSelf, response); + get_active_room_config(room, Destination::ToSelf, response); + if !room.greeting.is_empty() { response.add( ChatMsg { @@ -279,45 +180,139 @@ .send_self(), ); } + + if let Some(info) = &room.game_info { + response.add( + ClientFlags(add_flags(&[Flags::Ready, Flags::InGame]), vec![nick]) + .send_all() + .in_room(room.id), + ); + response.add(RunGame.send_self()); + + response.add( + ForwardEngineMessage( + once(to_engine_msg("e$spectate 1".bytes())) + .chain(info.msg_log.iter().cloned()) + .collect(), + ) + .send_self(), + ); + + for team in info.client_teams(client.id) { + response.add( + ForwardEngineMessage(vec![to_engine_msg(once(b'G').chain(team.name.bytes()))]) + .send_all() + .in_room(room.id), + ); + } + + if info.is_paused { + response.add(ForwardEngineMessage(vec![to_engine_msg(once(b'I'))]).send_self()); + } + + for (_, original_team) in &info.original_teams { + if let Some(team) = room.find_team(|team| team.name == original_team.name) { + if team.color != original_team.color { + response.add(TeamColor(team.name.clone(), team.color).send_self()); + } + if team.hedgehogs_number != original_team.hedgehogs_number { + response + .add(HedgehogsNumber(team.name.clone(), team.hedgehogs_number).send_self()); + } + } else { + response.add(TeamRemove(original_team.name.clone()).send_self()); + } + } + + get_room_config_impl(room.config(), Destination::ToSelf, response); + } } 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::WrongProtocol => response.warn(INCOMPATIBLE_ROOM_PROTOCOL), + JoinRoomError::WrongPassword => { + response.add(Notice("WrongPassword".to_string()).send_self()) + } JoinRoomError::Full => response.warn(ROOM_FULL), JoinRoomError::Restricted => response.warn(ROOM_JOIN_RESTRICTED), + JoinRoomError::RegistrationRequired => response.warn(ROOM_REGISTRATION_REQUIRED), + } +} + +pub fn get_remove_teams_data( + room_id: RoomId, + was_in_game: bool, + removed_teams: Vec, + response: &mut Response, +) { + if was_in_game { + for team_name in &removed_teams { + let remove_msg = to_engine_msg(once(b'F').chain(team_name.bytes())); + + response.add( + ForwardEngineMessage(vec![remove_msg]) + .send_all() + .in_room(room_id) + .but_self(), + ); + } + } else { + for team_name in removed_teams { + response.add(TeamRemove(team_name).send_all().in_room(room_id)); + } } } -pub fn exit_room(server: &mut HwServer, client_id: ClientId, response: &mut Response, msg: &str) { - let client = &mut server.clients[client_id]; +pub fn get_room_leave_result( + server: &HwServer, + room: &HwRoom, + leave_message: &str, + result: LeaveRoomResult, + response: &mut Response, +) { + let client = server.client(response.client_id); + response.add(ClientFlags(remove_flags(&[Flags::InRoom]), vec![client.nick.clone()]).send_all()); - if let Some(room_id) = client.room_id { - let room = &mut server.rooms[room_id]; - - remove_client_from_room(client, room, response, msg); + match result { + LeaveRoomResult::RoomRemoved => { + response.add( + RoomRemove(room.name.clone()) + .send_all() + .with_protocol(room.protocol_number), + ); + } - if !room.is_fixed() { - if room.players_number == 0 { - server.rooms.remove(room_id); - } else if room.master_id == None { - let new_master_id = server.room_clients(room_id).next(); - if let Some(new_master_id) = new_master_id { - let new_master_nick = server.clients[new_master_id].nick.clone(); - let room = &mut server.rooms[room_id]; - room.master_id = Some(new_master_id); - server.clients[new_master_id].set_is_master(true); + LeaveRoomResult::RoomRemains { + is_empty, + was_master, + new_master, + was_in_game, + removed_teams, + } => { + if !is_empty { + response.add( + RoomLeft(client.nick.clone(), leave_message.to_string()) + .send_all() + .in_room(room.id) + .but_self(), + ); + } - if room.protocol_number < 42 { - room.name = new_master_nick.clone(); - } + if was_master { + response.add( + ClientFlags( + remove_flags(&[Flags::RoomMaster]), + vec![client.nick.clone()], + ) + .send_all() + .in_room(room.id), + ); - room.set_join_restriction(false); - room.set_team_add_restriction(false); - room.set_unregistered_players_restriction(true); - + if let Some(new_master_id) = new_master { + let new_master_nick = server.client(new_master_id).nick.clone(); response.add( ClientFlags(add_flags(&[Flags::RoomMaster]), vec![new_master_nick]) .send_all() @@ -325,16 +320,28 @@ ); } } + + get_remove_teams_data(room.id, was_in_game, removed_teams, response); + + response.add( + RoomUpdated(room.name.clone(), room.info(Some(&client))) + .send_all() + .with_protocol(room.protocol_number), + ); } } } pub fn remove_client(server: &mut HwServer, response: &mut Response, msg: String) { let client_id = response.client_id(); - let client = &mut server.clients[client_id]; + let client = server.client(client_id); let nick = client.nick.clone(); - exit_room(server, client_id, response, &msg); + if let Some(mut room_control) = server.get_room_control(client_id) { + let room_id = room_control.room().id; + let result = room_control.leave_room(); + get_room_leave_result(server, server.room(room_id), &msg, result, response); + } server.remove_client(client_id); @@ -353,274 +360,280 @@ response.add(update_msg.send_all().with_protocol(room.protocol_number)); } -pub fn get_room_config_impl(config: &RoomConfig, to_client: ClientId, response: &mut Response) { - response.add(ConfigEntry("FULLMAPCONFIG".to_string(), config.to_map_config()).send(to_client)); +pub fn get_room_config_impl( + config: &RoomConfig, + destination: Destination, + response: &mut Response, +) { + response.add( + ConfigEntry("FULLMAPCONFIG".to_string(), config.to_map_config()) + .send_to_destination(destination.clone()), + ); for cfg in config.to_game_config() { - response.add(cfg.to_server_msg().send(to_client)); + response.add(cfg.to_server_msg().send_to_destination(destination.clone())); } } -pub fn get_room_config(room: &HwRoom, to_client: ClientId, response: &mut Response) { - get_room_config_impl(room.active_config(), to_client, response); +pub fn get_active_room_config(room: &HwRoom, destination: Destination, response: &mut Response) { + get_room_config_impl(room.active_config(), destination, response); } -pub fn get_teams<'a, I>(teams: I, to_client: ClientId, response: &mut Response) +pub fn get_teams<'a, I>(teams: I, destination: Destination, response: &mut Response) where I: Iterator, { for team in teams { - response.add(TeamAdd(team.to_protocol()).send(to_client)); - response.add(TeamColor(team.name.clone(), team.color).send(to_client)); - response.add(HedgehogsNumber(team.name.clone(), team.hedgehogs_number).send(to_client)); + response.add(TeamAdd(team.to_protocol()).send_to_destination(destination.clone())); + response + .add(TeamColor(team.name.clone(), team.color).send_to_destination(destination.clone())); + response.add( + HedgehogsNumber(team.name.clone(), team.hedgehogs_number) + .send_to_destination(destination.clone()), + ); } } -pub fn get_room_teams(room: &HwRoom, to_client: ClientId, response: &mut Response) { +pub fn get_active_room_teams(room: &HwRoom, destination: Destination, response: &mut Response) { let current_teams = match room.game_info { - Some(ref info) => &info.teams_at_start, + Some(ref info) => &info.original_teams, None => &room.teams, }; - get_teams(current_teams.iter().map(|(_, t)| t), to_client, response); + get_teams(current_teams.iter().map(|(_, t)| t), destination, response); } pub fn get_room_flags( server: &HwServer, room_id: RoomId, - to_client: ClientId, + destination: Destination, response: &mut Response, ) { - let room = &server.rooms[room_id]; + let room = server.room(room_id); if let Some(id) = room.master_id { response.add( ClientFlags( add_flags(&[Flags::RoomMaster]), - vec![server.clients[id].nick.clone()], + vec![server.client(id).nick.clone()], ) - .send(to_client), + .send_to_destination(destination.clone()), ); } - let nicks: Vec<_> = server - .clients - .iter() - .filter(|(_, c)| c.room_id == Some(room_id) && c.is_ready()) - .map(|(_, c)| c.nick.clone()) - .collect(); + let nicks = server.collect_nicks(|(_, c)| c.room_id == Some(room_id) && c.is_ready()); + if !nicks.is_empty() { - response.add(ClientFlags(add_flags(&[Flags::Ready]), nicks).send(to_client)); + response + .add(ClientFlags(add_flags(&[Flags::Ready]), nicks).send_to_destination(destination)); } } -pub fn apply_voting_result( - server: &mut HwServer, - room_id: RoomId, +pub fn check_vote( + server: &HwServer, + room: &HwRoom, + kind: &VoteType, response: &mut Response, - kind: VoteType, -) { - match kind { +) -> bool { + let error = match &kind { VoteType::Kick(nick) => { - if let Some(client) = server.find_client(&nick) { - if client.room_id == Some(room_id) { - let id = client.id; - response.add(Kicked.send(id)); - exit_room(server, id, response, "kicked"); - } + if server + .find_client(&nick) + .filter(|c| c.room_id == Some(room.id)) + .is_some() + { + None + } else { + Some("/callvote kick: No such user!".to_string()) } } - VoteType::Map(None) => (), + VoteType::Map(None) => { + let names: Vec<_> = room.saves.keys().cloned().collect(); + if names.is_empty() { + Some("/callvote map: No maps saved in this room!".to_string()) + } else { + Some(format!("Available maps: {}", names.join(", "))) + } + } VoteType::Map(Some(name)) => { - if let Some(location) = server.rooms[room_id].load_config(&name) { - response.add( - server_chat(location.to_string()) - .send_all() - .in_room(room_id), - ); - let room = &server.rooms[room_id]; - let room_master = if let Some(id) = room.master_id { - Some(&server.clients[id]) - } else { - None - }; - get_room_update(None, room, room_master, response); - - for (_, client) in server.clients.iter() { - if client.room_id == Some(room_id) { - super::common::get_room_config(&server.rooms[room_id], client.id, response); - } - } + if room.saves.get(&name[..]).is_some() { + None + } else { + Some("/callvote map: No such map!".to_string()) } } VoteType::Pause => { - if let Some(ref mut info) = server.rooms[room_id].game_info { - info.is_paused = !info.is_paused; - response.add( - server_chat("Pause toggled.".to_string()) - .send_all() - .in_room(room_id), - ); - response.add( - ForwardEngineMessage(vec![to_engine_msg(once(b'I'))]) - .send_all() - .in_room(room_id), - ); + if room.game_info.is_some() { + None + } else { + Some("/callvote pause: No game in progress!".to_string()) } } - VoteType::NewSeed => { - let seed = thread_rng().gen_range(0, 1_000_000_000).to_string(); - let cfg = GameCfg::Seed(seed); - response.add(cfg.to_server_msg().send_all().in_room(room_id)); - server.rooms[room_id].set_config(cfg); - } - VoteType::HedgehogsPerTeam(number) => { - let r = &mut server.rooms[room_id]; - let nicks = r.set_hedgehogs_number(number); + VoteType::NewSeed => None, + VoteType::HedgehogsPerTeam(number) => match number { + 1..=MAX_HEDGEHOGS_PER_TEAM => None, + _ => Some("/callvote hedgehogs: Specify number from 1 to 8.".to_string()), + }, + }; - response.extend( - nicks - .into_iter() - .map(|n| HedgehogsNumber(n, number).send_all().in_room(room_id)), - ); + match error { + None => true, + Some(msg) => { + response.add(server_chat(msg).send_self()); + false } } } -fn add_vote(room: &mut HwRoom, response: &mut Response, vote: Vote) -> Option { - let client_id = response.client_id; - let mut result = None; - - if let Some(ref mut voting) = room.voting { - if vote.is_forced || voting.votes.iter().all(|(id, _)| client_id != *id) { - response.add(server_chat("Your vote has been counted.".to_string()).send_self()); - voting.votes.push((client_id, vote.is_pro)); - let i = voting.votes.iter(); - let pro = i.clone().filter(|(_, v)| *v).count(); - let contra = i.filter(|(_, v)| !*v).count(); - let success_quota = voting.voters.len() / 2 + 1; - if vote.is_forced && vote.is_pro || pro >= success_quota { - result = Some(true); - } else if vote.is_forced && !vote.is_pro || contra > voting.voters.len() - success_quota - { - result = Some(false); - } - } else { - response.add(server_chat("You already have voted.".to_string()).send_self()); +pub fn get_vote_data( + room_id: RoomId, + result: &Result, + response: &mut Response, +) { + match result { + Ok(VoteResult::Submitted) => { + response.add(server_chat("Your vote has been counted.".to_string()).send_self()) } - } else { - response.add(server_chat("There's no voting going on.".to_string()).send_self()); + Ok(VoteResult::Succeeded(_)) | Ok(VoteResult::Failed) => response.add( + server_chat("Voting closed.".to_string()) + .send_all() + .in_room(room_id), + ), + Err(VoteError::NoVoting) => { + response.add(server_chat("There's no voting going on.".to_string()).send_self()) + } + Err(VoteError::AlreadyVoted) => { + response.add(server_chat("You already have voted.".to_string()).send_self()) + } } - - result } -pub fn submit_vote(server: &mut HwServer, vote: Vote, response: &mut Response) { - let client_id = response.client_id; - let client = &server.clients[client_id]; +pub fn handle_vote( + mut room_control: HwRoomControl, + result: Result, + response: &mut super::Response, +) { + let room_id = room_control.room().id; + super::common::get_vote_data(room_control.room().id, &result, response); - if let Some(room_id) = client.room_id { - let room = &mut server.rooms[room_id]; + if let Ok(VoteResult::Succeeded(kind)) = result { + match kind { + VoteType::Kick(nick) => { + if let Some(kicked_client) = room_control.server().find_client(&nick) { + let kicked_id = kicked_client.id; + if let Some(mut room_control) = room_control.change_client(kicked_id) { + response.add(Kicked.send(kicked_id)); + let result = room_control.leave_room(); + super::common::get_room_leave_result( + room_control.server(), + room_control.room(), + "kicked", + result, + response, + ); + } + } + } + VoteType::Map(None) => (), + VoteType::Map(Some(name)) => { + if let Some(location) = room_control.load_config(&name) { + let msg = server_chat(location.to_string()); + let room = room_control.room(); + response.add(msg.send_all().in_room(room.id)); + + let room_master = room.master_id.map(|id| room_control.server().client(id)); - if let Some(res) = add_vote(room, response, vote) { - response.add( - server_chat("Voting closed.".to_string()) - .send_all() - .in_room(room.id), - ); - let voting = replace(&mut room.voting, None).unwrap(); - if res { - apply_voting_result(server, room_id, response, voting.kind); + super::common::get_room_update(None, room, room_master, response); + + let room_destination = Destination::ToAll { + group: DestinationGroup::Room(room.id), + skip_self: false, + }; + super::common::get_active_room_config(room, room_destination, response); + } + } + VoteType::Pause => { + if room_control.toggle_pause() { + response.add( + server_chat("Pause toggled.".to_string()) + .send_all() + .in_room(room_id), + ); + response.add( + ForwardEngineMessage(vec![to_engine_msg(once(b'I'))]) + .send_all() + .in_room(room_id), + ); + } + } + VoteType::NewSeed => { + let seed = thread_rng().gen_range(0, 1_000_000_000).to_string(); + let cfg = GameCfg::Seed(seed); + response.add(cfg.to_server_msg().send_all().in_room(room_id)); + room_control.set_config(cfg); + } + VoteType::HedgehogsPerTeam(number) => { + let nicks = room_control.set_hedgehogs_number(number); + response.extend( + nicks + .into_iter() + .map(|n| HedgehogsNumber(n, number).send_all().in_room(room_id)), + ); } } } } -pub fn start_game(server: &mut HwServer, room_id: RoomId, response: &mut Response) { - let (room_clients, room_nicks): (Vec<_>, Vec<_>) = server - .clients - .iter() - .map(|(id, c)| (id, c.nick.clone())) - .unzip(); - let room = &mut server.rooms[room_id]; +pub fn get_start_game_data( + server: &HwServer, + room_id: RoomId, + result: Result, StartGameError>, + response: &mut Response, +) { + match result { + Ok(room_nicks) => { + let room = server.room(room_id); + response.add(RunGame.send_all().in_room(room.id)); + response.add( + ClientFlags(add_flags(&[Flags::InGame]), room_nicks) + .send_all() + .in_room(room.id), + ); - if !room.has_multiple_clans() { - response.add( - Warning("The game can't be started with less than two clans!".to_string()).send_self(), - ); - } else if room.protocol_number <= 43 && room.players_number != room.ready_players_number { - response.add(Warning("Not all players are ready".to_string()).send_self()); - } else if room.game_info.is_some() { - response.add(Warning("The game is already in progress".to_string()).send_self()); - } else { - room.start_round(); - for id in room_clients { - let c = &mut server.clients[id]; - c.set_is_in_game(true); - c.team_indices = room.client_team_indices(c.id); + let room_master = room.master_id.map(|id| server.client(id)); + get_room_update(None, room, room_master, response); } - response.add(RunGame.send_all().in_room(room.id)); - response.add( - ClientFlags(add_flags(&[Flags::InGame]), room_nicks) - .send_all() - .in_room(room.id), - ); - - let room_master = if let Some(id) = room.master_id { - Some(&server.clients[id]) - } else { - None - }; - get_room_update(None, room, room_master, response); + Err(StartGameError::NotEnoughClans) => { + response.warn("The game can't be started with less than two clans!") + } + Err(StartGameError::NotReady) => response.warn("Not all players are ready"), + Err(StartGameError::AlreadyInGame) => response.warn("The game is already in progress"), } } -pub fn end_game(server: &mut HwServer, room_id: RoomId, response: &mut Response) { - let room = &mut server.rooms[room_id]; - room.ready_players_number = 1; - let room_master = if let Some(id) = room.master_id { - Some(&server.clients[id]) - } else { - None - }; +pub fn get_end_game_result( + server: &HwServer, + room_id: RoomId, + result: EndGameResult, + response: &mut Response, +) { + let room = server.room(room_id); + let room_master = room.master_id.map(|id| server.client(id)); + get_room_update(None, room, room_master, response); response.add(RoundFinished.send_all().in_room(room_id)); - if let Some(info) = replace(&mut room.game_info, None) { - for (_, client) in server.clients.iter() { - if client.room_id == Some(room_id) && client.is_joined_mid_game() { - super::common::get_room_config(room, client.id, response); - response.extend( - info.left_teams - .iter() - .map(|name| TeamRemove(name.clone()).send(client.id)), - ); - } - } - } + response.extend( + result + .left_teams + .iter() + .filter(|name| room.find_team(|t| t.name == **name).is_some()) + .map(|name| TeamRemove(name.clone()).send_all().in_room(room.id)), + ); - let nicks: Vec<_> = server - .clients - .iter_mut() - .filter(|(_, c)| c.room_id == Some(room_id)) - .map(|(_, c)| { - c.set_is_ready(c.is_master()); - c.set_is_joined_mid_game(false); - c - }) - .filter_map(|c| { - if !c.is_master() { - Some(c.nick.clone()) - } else { - None - } - }) - .collect(); - - if !nicks.is_empty() { - let msg = if room.protocol_number < 38 { - LegacyReady(false, nicks) - } else { - ClientFlags(remove_flags(&[Flags::Ready]), nicks) - }; - response.add(msg.send_all().in_room(room_id)); + if !result.unreadied_nicks.is_empty() { + response.add( + ClientFlags(remove_flags(&[Flags::Ready]), result.unreadied_nicks) + .send_all() + .in_room(room_id), + ); } } @@ -655,21 +668,4 @@ fn test_handle_rnd_nonempty() { run_handle_test(vec!["A".to_owned(), "B".to_owned(), "C".to_owned()]) } - - /// This test terminates almost surely (strong law of large numbers) - #[test] - fn test_distribution() { - let eps = 0.000001; - let lim = 0.5; - let opts = vec![0.to_string(), 1.to_string()]; - let mut ones = 0; - let mut tries = 0; - - while tries < 1000 || ((ones as f64 / tries as f64) - lim).abs() >= eps { - tries += 1; - if reply2string(rnd_reply(&opts)) == 1.to_string() { - ones += 1; - } - } - } } diff -r b3c9f5463cee -r 74ede02bc882 rust/hedgewars-server/src/handlers/inanteroom.rs --- a/rust/hedgewars-server/src/handlers/inanteroom.rs Sun Jul 05 02:03:08 2020 +0200 +++ b/rust/hedgewars-server/src/handlers/inanteroom.rs Sun Jul 05 14:53:44 2020 +0200 @@ -1,9 +1,11 @@ use mio; +use super::strings::*; use crate::{ core::{ + anteroom::{HwAnteroom, HwAnteroomClient}, client::HwClient, - server::{HwAnteClient, HwAnteroom, HwServer}, + server::HwServer, types::ClientId, }, protocol::messages::{HwProtocolMessage, HwProtocolMessage::LoadRoom, HwServerMessage::*}, @@ -26,24 +28,18 @@ fn completion_result<'a, I>( mut other_clients: I, - client: &mut HwAnteClient, + client: &mut HwAnteroomClient, response: &mut super::Response, ) -> LoginResult where - I: Iterator, + I: Iterator, { - let has_nick_clash = - other_clients.any(|(_, c)| !c.is_checker() && c.nick == *client.nick.as_ref().unwrap()); + let has_nick_clash = other_clients.any(|c| c.nick == *client.nick.as_ref().unwrap()); if has_nick_clash { - if client.protocol_number.unwrap().get() < 38 { - response.add(Bye("User quit: Nickname is already in use".to_string()).send_self()); - LoginResult::Exit - } else { - client.nick = None; - response.add(Notice("NickAlreadyInUse".to_string()).send_self()); - LoginResult::Unchanged - } + client.nick = None; + response.add(Notice("NickAlreadyInUse".to_string()).send_self()); + LoginResult::Unchanged } else { #[cfg(feature = "official-server")] { @@ -61,7 +57,7 @@ } pub fn handle( - server: &mut HwServer, + server_state: &mut super::ServerState, client_id: ClientId, response: &mut super::Response, message: HwProtocolMessage, @@ -72,39 +68,39 @@ LoginResult::Exit } HwProtocolMessage::Nick(nick) => { - let client = &mut server.anteroom.clients[client_id]; + let client = &mut server_state.anteroom.clients[client_id]; if client.nick.is_some() { - response.add(Error("Nickname already provided.".to_string()).send_self()); + response.error(NICKNAME_PROVIDED); LoginResult::Unchanged } else if is_name_illegal(&nick) { - response.add(Bye("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: $()*+?[]^{|}".to_string()).send_self()); + response.add(Bye(ILLEGAL_CLIENT_NAME.to_string()).send_self()); LoginResult::Exit } else { client.nick = Some(nick.clone()); response.add(Nick(nick).send_self()); if client.protocol_number.is_some() { - completion_result(server.clients.iter(), client, response) + completion_result(server_state.server.iter_clients(), client, response) } else { LoginResult::Unchanged } } } HwProtocolMessage::Proto(proto) => { - let client = &mut server.anteroom.clients[client_id]; + let client = &mut server_state.anteroom.clients[client_id]; if client.protocol_number.is_some() { - response.add(Error("Protocol already known.".to_string()).send_self()); + response.error(PROTOCOL_PROVIDED); LoginResult::Unchanged - } else if proto == 0 { - response.add(Error("Bad number.".to_string()).send_self()); - LoginResult::Unchanged + } else if proto < 48 { + response.add(Bye(PROTOCOL_TOO_OLD.to_string()).send_self()); + LoginResult::Exit } else { client.protocol_number = NonZeroU16::new(proto); response.add(Proto(proto).send_self()); if client.nick.is_some() { - completion_result(server.clients.iter(), client, response) + completion_result(server_state.server.iter_clients(), client, response) } else { LoginResult::Unchanged } @@ -112,7 +108,7 @@ } #[cfg(feature = "official-server")] HwProtocolMessage::Password(hash, salt) => { - let client = &server.anteroom.clients[client_id]; + let client = &server_state.anteroom.clients[client_id]; if let (Some(nick), Some(protocol)) = (client.nick.as_ref(), client.protocol_number) { response.request_io(super::IoTask::GetAccount { @@ -128,19 +124,31 @@ } #[cfg(feature = "official-server")] HwProtocolMessage::Checker(protocol, nick, password) => { - let client = &mut server.anteroom.clients[client_id]; + let client = &mut server_state.anteroom.clients[client_id]; if protocol == 0 { - response.add(Error("Bad number.".to_string()).send_self()); + response.error("Bad number."); LoginResult::Unchanged } else { client.protocol_number = NonZeroU16::new(protocol); - client.nick = Some(nick); client.is_checker = true; - LoginResult::Complete + #[cfg(not(feature = "official-server"))] + { + response.request_io(super::IoTask::GetCheckerAccount { + nick: nick, + password: password, + }); + LoginResult::Unchanged + } + + #[cfg(feature = "official-server")] + { + response.add(LogonPassed.send_self()); + LoginResult::Complete + } } } _ => { - warn!("Incorrect command in logging-in state"); + warn!("Incorrect command in anteroom"); LoginResult::Unchanged } } diff -r b3c9f5463cee -r 74ede02bc882 rust/hedgewars-server/src/handlers/inlobby.rs --- a/rust/hedgewars-server/src/handlers/inlobby.rs Sun Jul 05 02:03:08 2020 +0200 +++ b/rust/hedgewars-server/src/handlers/inlobby.rs Sun Jul 05 14:53:44 2020 +0200 @@ -1,5 +1,3 @@ -use mio; - use super::{common::rnd_reply, strings::*}; use crate::{ core::{ @@ -37,20 +35,17 @@ response.add(RoomJoined(vec![client.nick.clone()]).send_self()); response.add( ClientFlags( - add_flags(&[Flags::RoomMaster, Flags::Ready]), + add_flags(&[Flags::RoomMaster, Flags::Ready, Flags::InRoom]), vec![client.nick.clone()], ) - .send_self(), - ); - response.add( - ClientFlags(add_flags(&[Flags::InRoom]), vec![client.nick.clone()]).send_self(), + .send_all(), ); } }, Chat(msg) => { response.add( ChatMsg { - nick: server.get_client_nick(client_id).to_string(), + nick: server.client(client_id).nick.clone(), msg, } .send_all() @@ -58,16 +53,18 @@ .but_self(), ); } - 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) + JoinRoom(name, password) => { + match server.join_room_by_name(client_id, &name, password.as_deref()) { + 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) { + match server.join_room(client_id, room_id, None) { 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) @@ -105,8 +102,8 @@ html.push(format!( "{}{}{}", super::utils::protocol_version_string(protocol), - server.protocol_clients(protocol).count(), - server.protocol_rooms(protocol).count() + server.protocol_client_ids(protocol).count(), + server.protocol_room_ids(protocol).count() )); } html.push("".to_string()); diff -r b3c9f5463cee -r 74ede02bc882 rust/hedgewars-server/src/handlers/inroom.rs --- a/rust/hedgewars-server/src/handlers/inroom.rs Sun Jul 05 02:03:08 2020 +0200 +++ b/rust/hedgewars-server/src/handlers/inroom.rs Sun Jul 05 14:53:44 2020 +0200 @@ -1,11 +1,13 @@ -use mio; - -use super::common::rnd_reply; -use crate::utils::to_engine_msg; +use super::{common::rnd_reply, strings::*}; +use crate::core::room::GameInfo; +use crate::core::server::{AddTeamError, SetTeamCountError}; use crate::{ core::{ room::{HwRoom, RoomFlags, MAX_TEAMS_IN_ROOM}, - server::HwServer, + server::{ + ChangeMasterError, ChangeMasterResult, HwRoomControl, HwServer, LeaveRoomResult, + ModifyTeamError, StartGameError, + }, types, types::{ClientId, GameCfg, RoomId, VoteType, Voting, MAX_HEDGEHOGS_PER_TEAM}, }, @@ -13,7 +15,7 @@ add_flags, remove_flags, server_chat, HwProtocolMessage, HwServerMessage::*, ProtocolFlags as Flags, }, - utils::is_name_illegal, + utils::{is_name_illegal, to_engine_msg}, }; use base64::{decode, encode}; use log::*; @@ -46,10 +48,9 @@ b"M#+LlRrUuDdZzAaSjJ,NpPwtgfhbc12345\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A"; const NON_TIMED_MESSAGES: &[u8] = b"M#hb"; -#[cfg(canhazslicepatterns)] fn is_msg_valid(msg: &[u8], team_indices: &[u8]) -> bool { match msg { - [size, typ, body..MAX] => { + [size, typ, body @ ..] => { VALID_MESSAGES.contains(typ) && match body { [1..=MAX_HEDGEHOGS_PER_TEAM, team, ..] if *typ == b'h' => { @@ -62,14 +63,6 @@ } } -fn is_msg_valid(msg: &[u8], _team_indices: &[u8]) -> bool { - if let Some(typ) = msg.get(1) { - VALID_MESSAGES.contains(typ) - } else { - false - } -} - fn is_msg_empty(msg: &[u8]) -> bool { msg.get(1).filter(|t| **t == b'+').is_some() } @@ -98,20 +91,18 @@ match msg { ToggleRestrictJoin => RoomFlags::RESTRICTED_JOIN, ToggleRestrictTeams => RoomFlags::RESTRICTED_TEAM_ADD, - ToggleRegisteredOnly => RoomFlags::RESTRICTED_UNREGISTERED_PLAYERS, + ToggleRegisteredOnly => RoomFlags::REGISTRATION_REQUIRED, _ => RoomFlags::empty(), } } pub fn handle( - server: &mut HwServer, - client_id: ClientId, + mut room_control: HwRoomControl, response: &mut super::Response, - room_id: RoomId, message: HwProtocolMessage, ) { - let client = &mut server.clients[client_id]; - let room = &mut server.rooms[room_id]; + let (client, room) = room_control.get(); + let (client_id, room_id) = (client.id, room.id); use crate::protocol::messages::HwProtocolMessage::*; match message { @@ -120,7 +111,16 @@ Some(s) => format!("part: {}", s), None => "part".to_string(), }; - super::common::exit_room(server, client_id, response, &msg); + + let result = room_control.leave_room(); + super::common::get_room_leave_result( + room_control.server(), + room_control.room(), + &msg, + result, + response, + ); + room_control.cleanup_room(); } Chat(msg) => { response.add( @@ -133,10 +133,8 @@ ); } TeamChat(msg) => { - let room = &server.rooms[room_id]; - if let Some(ref info) = room.game_info { + if room.game_info.is_some() { if let Some(clan_color) = room.find_team_color(client_id) { - let client = &server.clients[client_id]; let engine_msg = to_engine_msg(format!("b{}]{}\x20\x20", client.nick, msg).bytes()); let team = room.clan_team_owners(clan_color).collect(); @@ -145,207 +143,150 @@ } } Fix => { - if client.is_admin() { - room.set_is_fixed(true); - room.set_join_restriction(false); - room.set_team_add_restriction(false); - room.set_unregistered_players_restriction(true); + if let Err(_) = room_control.fix_room() { + response.warn(ACCESS_DENIED) } } Unfix => { - if client.is_admin() { - room.set_is_fixed(false); + if let Err(_) = room_control.unfix_room() { + response.warn(ACCESS_DENIED) } } Greeting(text) => { - if client.is_admin() || client.is_master() && !room.is_fixed() { - room.greeting = text.unwrap_or(String::new()); + if let Err(_) = room_control.set_room_greeting(text) { + response.warn(ACCESS_DENIED) } } MaxTeams(count) => { - if !client.is_master() { - response.add(Warning("You're not the room master!".to_string()).send_self()); - } else if !(2..=MAX_TEAMS_IN_ROOM).contains(&count) { - response - .add(Warning("/maxteams: specify number from 2 to 8".to_string()).send_self()); - } else { - server.rooms[room_id].max_teams = count; - } + use crate::core::server::SetTeamCountError; + match room_control.set_room_max_teams(count) { + Ok(()) => {} + Err(SetTeamCountError::NotMaster) => response.warn(NOT_MASTER), + Err(SetTeamCountError::InvalidNumber) => { + response.warn("/maxteams: specify number from 2 to 8") + } + }; } RoomName(new_name) => { - if is_name_illegal(&new_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(&new_name) { - response.add( - Warning("A room with the same name already exists.".to_string()).send_self(), - ); - } else { - let room = &mut server.rooms[room_id]; - if room.is_fixed() || room.master_id != Some(client_id) { - response.add(Warning("Access denied.".to_string()).send_self()); - } else { - let mut old_name = new_name.clone(); - let client = &server.clients[client_id]; - swap(&mut room.name, &mut old_name); - super::common::get_room_update(Some(old_name), room, Some(&client), response); + use crate::core::server::ModifyRoomNameError; + match room_control.set_room_name(new_name) { + Ok(old_name) => { + let (client, room) = room_control.get(); + super::common::get_room_update(Some(old_name), room, Some(client), response) } + Err(ModifyRoomNameError::AccessDenied) => response.warn(ACCESS_DENIED), + Err(ModifyRoomNameError::InvalidName) => response.warn(ILLEGAL_ROOM_NAME), + Err(ModifyRoomNameError::DuplicateName) => response.warn(ROOM_EXISTS), } } ToggleReady => { - let flags = if client.is_ready() { - room.ready_players_number -= 1; - remove_flags(&[Flags::Ready]) + let flags = if room_control.toggle_ready() { + add_flags(&[Flags::Ready]) } else { - room.ready_players_number += 1; - add_flags(&[Flags::Ready]) + remove_flags(&[Flags::Ready]) }; + let (client, room) = room_control.get(); let msg = if client.protocol_number < 38 { LegacyReady(client.is_ready(), vec![client.nick.clone()]) } else { ClientFlags(flags, vec![client.nick.clone()]) }; - response.add(msg.send_all().in_room(room.id)); - client.set_is_ready(!client.is_ready()); + response.add(msg.send_all().in_room(room_id)); if room.is_fixed() && room.ready_players_number == room.players_number { - super::common::start_game(server, room_id, response); + let result = room_control.start_game(); + super::common::get_start_game_data( + room_control.server(), + room_id, + result, + response, + ); } } - AddTeam(mut info) => { - if room.teams.len() >= room.max_teams as usize { - response.add(Warning("Too many teams!".to_string()).send_self()); - } else if room.addable_hedgehogs() == 0 { - response.add(Warning("Too many hedgehogs!".to_string()).send_self()); - } else if room.find_team(|t| t.name == info.name) != None { - response.add( - Warning("There's already a team with same name in the list.".to_string()) - .send_self(), - ); - } else if room.game_info.is_some() { - response.add( - Warning("Joining not possible: Round is in progress.".to_string()).send_self(), - ); - } else if room.is_team_add_restricted() { - response.add( - Warning("This room currently does not allow adding new teams.".to_string()) - .send_self(), - ); - } else { - info.owner = client.nick.clone(); - let team = room.add_team(client.id, *info, client.protocol_number < 42); - client.teams_in_game += 1; - client.clan = Some(team.color); - response.add(TeamAccepted(team.name.clone()).send_self()); - response.add( - TeamAdd(team.to_protocol()) - .send_all() - .in_room(room_id) - .but_self(), - ); - response.add( - TeamColor(team.name.clone(), team.color) - .send_all() - .in_room(room_id), - ); - response.add( - HedgehogsNumber(team.name.clone(), team.hedgehogs_number) - .send_all() - .in_room(room_id), - ); + AddTeam(info) => { + use crate::core::server::AddTeamError; + match room_control.add_team(info) { + Ok(team) => { + response.add(TeamAccepted(team.name.clone()).send_self()); + response.add( + TeamAdd(team.to_protocol()) + .send_all() + .in_room(room_id) + .but_self(), + ); + response.add( + TeamColor(team.name.clone(), team.color) + .send_all() + .in_room(room_id), + ); + response.add( + HedgehogsNumber(team.name.clone(), team.hedgehogs_number) + .send_all() + .in_room(room_id), + ); - let room_master = if let Some(id) = room.master_id { - Some(&server.clients[id]) - } else { - None - }; - super::common::get_room_update(None, room, room_master, response); + let room = room_control.room(); + let room_master = room.master_id.map(|id| room_control.server().client(id)); + super::common::get_room_update(None, room, room_master, response); + } + Err(AddTeamError::TooManyTeams) => response.warn(TOO_MANY_TEAMS), + Err(AddTeamError::TooManyHedgehogs) => response.warn(TOO_MANY_HEDGEHOGS), + Err(AddTeamError::TeamAlreadyExists) => response.warn(TEAM_EXISTS), + Err(AddTeamError::GameInProgress) => response.warn(ROUND_IN_PROGRESS), + Err(AddTeamError::Restricted) => response.warn(TEAM_ADD_RESTRICTED), } } - RemoveTeam(name) => match room.find_team_owner(&name) { - None => response.add( - Warning("Error: The team you tried to remove does not exist.".to_string()) - .send_self(), - ), - Some((id, _)) if id != client_id => response - .add(Warning("You can't remove a team you don't own.".to_string()).send_self()), - Some((_, name)) => { - client.teams_in_game -= 1; - client.clan = room.find_team_color(client.id); - let names = vec![name.to_string()]; - super::common::remove_teams(room, names, client.is_in_game(), response); + RemoveTeam(name) => { + use crate::core::server::RemoveTeamError; + match room_control.remove_team(&name) { + Ok(()) => { + let (client, room) = room_control.get(); - match room.game_info { - Some(ref info) if info.teams_in_game == 0 => { - super::common::end_game(server, room_id, response) - } - _ => (), + let removed_teams = vec![name]; + super::common::get_remove_teams_data(room_id, false, removed_teams, response); } + Err(RemoveTeamError::NoTeam) => response.warn(NO_TEAM_TO_REMOVE), + Err(RemoveTeamError::TeamNotOwned) => response.warn(TEAM_NOT_OWNED), } - }, + } SetHedgehogsNumber(team_name, number) => { - let addable_hedgehogs = room.addable_hedgehogs(); - if let Some((_, team)) = room.find_team_and_owner_mut(|t| t.name == team_name) { - let max_hedgehogs = min( - MAX_HEDGEHOGS_PER_TEAM, - addable_hedgehogs + team.hedgehogs_number, - ); - if !client.is_master() { - response.add(Error("You're not the room master!".to_string()).send_self()); - } else if !(1..=max_hedgehogs).contains(&number) { - response - .add(HedgehogsNumber(team.name.clone(), team.hedgehogs_number).send_self()); - } else { - team.hedgehogs_number = number; + use crate::core::server::SetHedgehogsError; + match room_control.set_team_hedgehogs_number(&team_name, number) { + Ok(()) => { response.add( - HedgehogsNumber(team.name.clone(), number) + HedgehogsNumber(team_name.clone(), number) .send_all() .in_room(room_id) .but_self(), ); } - } else { - response.add(Warning("No such team.".to_string()).send_self()); + Err(SetHedgehogsError::NotMaster) => response.error(NOT_MASTER), + Err(SetHedgehogsError::NoTeam) => response.warn(NO_TEAM), + Err(SetHedgehogsError::InvalidNumber(previous_number)) => { + response.add(HedgehogsNumber(team_name.clone(), previous_number).send_self()) + } } } - SetTeamColor(team_name, color) => { - if let Some((owner, team)) = room.find_team_and_owner_mut(|t| t.name == team_name) { - if !client.is_master() { - response.add(Error("You're not the room master!".to_string()).send_self()); - } else { - team.color = color; - response.add( - TeamColor(team.name.clone(), color) - .send_all() - .in_room(room_id) - .but_self(), - ); - server.clients[owner].clan = Some(color); - } - } else { - response.add(Warning("No such team.".to_string()).send_self()); - } - } + SetTeamColor(team_name, color) => match room_control.set_team_color(&team_name, color) { + Ok(()) => response.add( + TeamColor(team_name, color) + .send_all() + .in_room(room_id) + .but_self(), + ), + Err(ModifyTeamError::NoTeam) => response.warn(NO_TEAM), + Err(ModifyTeamError::NotMaster) => response.error(NOT_MASTER), + }, Cfg(cfg) => { - if room.is_fixed() { - response.add(Warning("Access denied.".to_string()).send_self()); - } else if !client.is_master() { - response.add(Error("You're not the room master!".to_string()).send_self()); - } else { - let cfg = match cfg { - GameCfg::Scheme(name, mut values) => { - if client.protocol_number == 49 && values.len() >= 2 { - let mut s = "X".repeat(50); - s.push_str(&values.pop().unwrap()); - values.push(s); - } - GameCfg::Scheme(name, values) - } - cfg => cfg, - }; - - response.add(cfg.to_server_msg().send_all().in_room(room.id).but_self()); - room.set_config(cfg); + use crate::core::server::SetConfigError; + let msg = cfg.to_server_msg(); + match room_control.set_config(cfg) { + Ok(()) => { + response.add(msg.send_all().in_room(room_control.room().id).but_self()); + } + Err(SetConfigError::NotMaster) => response.error(NOT_MASTER), + Err(SetConfigError::RoomFixed) => response.warn(ACCESS_DENIED), } } Save(name, location) => { @@ -354,7 +295,7 @@ .send_all() .in_room(room_id), ); - room.save_config(name, location); + room_control.save_config(name, location); } #[cfg(feature = "official-server")] SaveRoom(filename) => { @@ -367,10 +308,7 @@ }), Err(e) => { warn!("Error while serializing the room configs: {}", e); - response.add( - Warning("Unable to serialize the room configs.".to_string()) - .send_self(), - ) + response.warn("Unable to serialize the room configs.") } } } @@ -382,7 +320,7 @@ } } Delete(name) => { - if !room.delete_config(&name) { + if !room_control.delete_config(&name) { response.add(Warning(format!("Save doesn't exist: {}", name)).send_self()); } else { response.add( @@ -397,98 +335,57 @@ .send_self()); } CallVote(Some(kind)) => { - let is_in_game = room.game_info.is_some(); - let error = match &kind { - VoteType::Kick(nick) => { - if server - .find_client(&nick) - .filter(|c| c.room_id == Some(room_id)) - .is_some() - { - None - } else { - Some("/callvote kick: No such user!".to_string()) - } - } - VoteType::Map(None) => { - let names: Vec<_> = server.rooms[room_id].saves.keys().cloned().collect(); - if names.is_empty() { - Some("/callvote map: No maps saved in this room!".to_string()) - } else { - Some(format!("Available maps: {}", names.join(", "))) - } - } - VoteType::Map(Some(name)) => { - if room.saves.get(&name[..]).is_some() { - None - } else { - Some("/callvote map: No such map!".to_string()) - } - } - VoteType::Pause => { - if is_in_game { - None - } else { - Some("/callvote pause: No game in progress!".to_string()) - } - } - VoteType::NewSeed => None, - VoteType::HedgehogsPerTeam(number) => match number { - 1..=MAX_HEDGEHOGS_PER_TEAM => None, - _ => Some("/callvote hedgehogs: Specify number from 1 to 8.".to_string()), - }, - }; - - match error { - None => { - let msg = voting_description(&kind); - let voting = Voting::new(kind, server.room_clients(client_id).collect()); - let room = &mut server.rooms[room_id]; - room.voting = Some(voting); - response.add(server_chat(msg).send_all().in_room(room_id)); - super::common::submit_vote( - server, - types::Vote { + use crate::core::server::StartVoteError; + let room_id = room_control.room().id; + if super::common::check_vote( + room_control.server(), + room_control.room(), + &kind, + response, + ) { + match room_control.start_vote(kind.clone()) { + Ok(()) => { + let msg = voting_description(&kind); + response.add(server_chat(msg).send_all().in_room(room_id)); + let vote_result = room_control.vote(types::Vote { is_pro: true, is_forced: false, - }, - response, - ); - } - Some(msg) => { - response.add(server_chat(msg).send_self()); + }); + super::common::handle_vote(room_control, vote_result, response); + } + Err(StartVoteError::VotingInProgress) => { + response.add( + server_chat("There is already voting in progress".to_string()) + .send_self(), + ); + } } } } Vote(vote) => { - super::common::submit_vote( - server, - types::Vote { - is_pro: vote, - is_forced: false, - }, - response, - ); + let vote_result = room_control.vote(types::Vote { + is_pro: vote, + is_forced: false, + }); + super::common::handle_vote(room_control, vote_result, response); } ForceVote(vote) => { let is_forced = client.is_admin(); - super::common::submit_vote( - server, - types::Vote { - is_pro: vote, - is_forced, - }, - response, - ); + let vote_result = room_control.vote(types::Vote { + is_pro: vote, + is_forced, + }); + super::common::handle_vote(room_control, vote_result, response); } ToggleRestrictJoin | ToggleRestrictTeams | ToggleRegisteredOnly => { - if client.is_master() { - room.flags.toggle(room_message_flag(&message)); + if room_control.toggle_flag(room_message_flag(&message)) { + let (client, room) = room_control.get(); super::common::get_room_update(None, room, Some(&client), response); } } StartGame => { - super::common::start_game(server, room_id, response); + let result = room_control.start_game(); + super::common::get_start_game_data(room_control.server(), room_id, result, response); } EngineMessage(em) => { if client.teams_in_game > 0 { @@ -514,98 +411,87 @@ ); } let em_log = encode(&non_empty.flat_map(|msg| msg).cloned().collect::>()); - if let Some(ref mut info) = room.game_info { - if !em_log.is_empty() { - info.msg_log.push(em_log); - } - if let Some(msg) = sync_msg { - info.sync_msg = msg; - } - } + + room_control.log_engine_msg(em_log, sync_msg); } } RoundFinished => { - let mut game_ended = false; - if client.is_in_game() { - client.set_is_in_game(false); + if let Some(team_names) = room_control.leave_game() { + let (client, room) = room_control.get(); response.add( ClientFlags(remove_flags(&[Flags::InGame]), vec![client.nick.clone()]) .send_all() .in_room(room.id), ); - let team_names: Vec<_> = room - .client_teams(client_id) - .map(|t| t.name.clone()) - .collect(); - - if let Some(ref mut info) = room.game_info { - info.teams_in_game -= team_names.len() as u8; - if info.teams_in_game == 0 { - game_ended = true; - } - for team_name in team_names { - let msg = once(b'F').chain(team_name.bytes()); - response.add( - ForwardEngineMessage(vec![to_engine_msg(msg)]) - .send_all() - .in_room(room_id) - .but_self(), - ); + for team_name in team_names { + let msg = once(b'F').chain(team_name.bytes()); + response.add( + ForwardEngineMessage(vec![to_engine_msg(msg)]) + .send_all() + .in_room(room_id) + .but_self(), + ); + } - let remove_msg = to_engine_msg(once(b'F').chain(team_name.bytes())); - if let Some(m) = &info.sync_msg { - info.msg_log.push(m.clone()); - } - if info.sync_msg.is_some() { - info.sync_msg = None - } - info.msg_log.push(remove_msg.clone()); - response.add( - ForwardEngineMessage(vec![remove_msg]) - .send_all() - .in_room(room_id) - .but_self(), + if let Some(0) = room.teams_in_game() { + if let Some(result) = room_control.end_game() { + super::common::get_end_game_result( + room_control.server(), + room_id, + result, + response, ); } } } - if game_ended { - super::common::end_game(server, room_id, response) - } } Rnd(v) => { let result = rnd_reply(&v); let mut echo = vec!["/rnd".to_string()]; echo.extend(v.into_iter()); let chat_msg = ChatMsg { - nick: server.clients[client_id].nick.clone(), + nick: client.nick.clone(), msg: echo.join(" "), }; response.add(chat_msg.send_all().in_room(room_id)); response.add(result.send_all().in_room(room_id)); } - Delegate(nick) => { - let delegate_id = server.find_client(&nick).map(|c| (c.id, c.room_id)); - let client = &server.clients[client_id]; - if !(client.is_admin() || client.is_master()) { + Delegate(nick) => match room_control.change_master(nick) { + Ok(ChangeMasterResult { + old_master_id, + new_master_id, + }) => { + if let Some(master_id) = old_master_id { + response.add( + ClientFlags( + remove_flags(&[Flags::RoomMaster]), + vec![room_control.server().client(master_id).nick.clone()], + ) + .send_all() + .in_room(room_id), + ); + } response.add( - Warning("You're not the room master or a server admin!".to_string()) - .send_self(), - ) - } else { - match delegate_id { - None => response.add(Warning("Player is not online.".to_string()).send_self()), - Some((id, _)) if id == client_id => response - .add(Warning("You're already the room master.".to_string()).send_self()), - Some((_, id)) if id != Some(room_id) => response - .add(Warning("The player is not in your room.".to_string()).send_self()), - Some((id, _)) => { - super::common::change_master(server, room_id, id, response); - } - } + ClientFlags( + add_flags(&[Flags::RoomMaster]), + vec![room_control.server().client(new_master_id).nick.clone()], + ) + .send_all() + .in_room(room_id), + ); } - } + Err(ChangeMasterError::NoAccess) => { + response.warn("You're not the room master or a server admin!") + } + Err(ChangeMasterError::AlreadyMaster) => { + response.warn("You're already the room master.") + } + Err(ChangeMasterError::NoClient) => response.warn("Player is not online."), + Err(ChangeMasterError::ClientNotInRoom) => { + response.warn("The player is not in your room.") + } + }, _ => warn!("Unimplemented!"), } } diff -r b3c9f5463cee -r 74ede02bc882 rust/hedgewars-server/src/handlers/strings.rs --- a/rust/hedgewars-server/src/handlers/strings.rs Sun Jul 05 02:03:08 2020 +0200 +++ b/rust/hedgewars-server/src/handlers/strings.rs Sun Jul 05 14:53:44 2020 +0200 @@ -1,23 +1,40 @@ -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!"; +pub const ACCESS_DENIED: &str = "Access denied."; +pub const AUTHENTICATION_FAILED: &str = "Authentication failed"; +pub const BAD_NUMBER: &str = "Bad number."; +pub const ILLEGAL_CLIENT_NAME: &str = "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: $()*+?[]^{|}"; +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 NICKNAME_PROVIDED: &str = "Nickname already provided."; +pub const NO_CHECKER_RIGHTS: &str = "No checker rights"; +pub const NO_ROOM: &str = "No such room."; +pub const NO_TEAM: &str = "No such team."; +pub const NO_TEAM_TO_REMOVE: &str = "Error: The team you tried to remove does not exist."; +pub const NO_USER: &str = "No such user."; +pub const NOT_MASTER: &str = "You're not the room master!"; +pub const PROTOCOL_PROVIDED: &str = "Protocol already known."; +pub const PROTOCOL_TOO_OLD: &str = "Protocol version is too old"; +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 ROUND_IN_PROGRESS: &str = "Joining not possible: Round is in progress."; +pub const ROOM_REGISTRATION_REQUIRED: &str = + "Access denied. This room is for registered users only."; +pub const SUPER_POWER: &str = "Super power activated."; +pub const TEAM_EXISTS: &str = "There's already a team with same name in the list."; +pub const TEAM_NOT_OWNED: &str = "You can't remove a team you don't own."; +pub const TEAM_ADD_RESTRICTED: &str = "This room currently does not allow adding new teams."; +pub const TOO_MANY_HEDGEHOGS: &str = "Too many hedgehogs!"; +pub const TOO_MANY_TEAMS: &str = "Too many teams!"; +pub const USER_OFFLINE: &str = "Player is not online."; +pub const VARIABLE_UPDATED: &str = "Server variable has been updated."; +pub const INCOMPATIBLE_ROOM_PROTOCOL: &str = "Room version incompatible to your Hedgewars version!"; diff -r b3c9f5463cee -r 74ede02bc882 rust/hedgewars-server/src/protocol/messages.rs --- a/rust/hedgewars-server/src/protocol/messages.rs Sun Jul 05 02:03:08 2020 +0200 +++ b/rust/hedgewars-server/src/protocol/messages.rs Sun Jul 05 14:53:44 2020 +0200 @@ -120,6 +120,7 @@ Proto(u16), AskPassword(String), ServerAuth(String), + LogonPassed, LobbyLeft(String, String), LobbyJoined(Vec), @@ -390,6 +391,7 @@ Proto(proto) => msg!["PROTO", proto], AskPassword(salt) => msg!["ASKPASSWORD", salt], ServerAuth(hash) => msg!["SERVER_AUTH", hash], + LogonPassed => msg!["LOGONPASSED"], LobbyLeft(nick, msg) => msg!["LOBBY:LEFT", nick, msg], LobbyJoined(nicks) => construct_message(&["LOBBY:JOINED"], &nicks), ClientFlags(flags, nicks) => construct_message(&["CLIENT_FLAGS", flags], &nicks), diff -r b3c9f5463cee -r 74ede02bc882 rust/hedgewars-server/src/server.rs --- a/rust/hedgewars-server/src/server.rs Sun Jul 05 02:03:08 2020 +0200 +++ b/rust/hedgewars-server/src/server.rs Sun Jul 05 14:53:44 2020 +0200 @@ -1,5 +1,7 @@ #[cfg(feature = "official-server")] mod database; +pub mod demo; +mod haskell; #[cfg(feature = "official-server")] pub mod io; pub mod network; diff -r b3c9f5463cee -r 74ede02bc882 rust/hedgewars-server/src/server/database.rs --- a/rust/hedgewars-server/src/server/database.rs Sun Jul 05 02:03:08 2020 +0200 +++ b/rust/hedgewars-server/src/server/database.rs Sun Jul 05 14:53:44 2020 +0200 @@ -7,8 +7,7 @@ const CHECK_ACCOUNT_EXISTS_QUERY: &str = r"SELECT 1 FROM users WHERE users.name = :username LIMIT 1"; -const GET_ACCOUNT_QUERY: &str = - r"SELECT CASE WHEN users.status = 1 THEN users.pass ELSE '' END, +const GET_ACCOUNT_QUERY: &str = r"SELECT CASE WHEN users.status = 1 THEN users.pass ELSE '' END, (SELECT COUNT(users_roles.rid) FROM users_roles WHERE users.uid = users_roles.uid AND users_roles.rid = 3), (SELECT COUNT(users_roles.rid) FROM users_roles WHERE users.uid = users_roles.uid AND users_roles.rid = 13) FROM users WHERE users.name = :username"; @@ -87,6 +86,23 @@ } } + pub fn get_checker_account( + &mut self, + nick: &str, + checker_password: &str, + ) -> Result { + if let Some(pool) = &self.pool { + if let Some(row) = pool.first_exec(GET_ACCOUNT_QUERY, params! { "username" => nick })? { + let (mut password, _, _) = from_row_opt::<(String, i32, i32)>(row)?; + Ok(checker_password == password) + } else { + Ok(false) + } + } else { + Err(DriverError::SetupError.into()) + } + } + pub fn store_stats(&mut self, stats: &ServerStatistics) -> Result<(), Error> { if let Some(pool) = &self.pool { for mut stmt in pool.prepare(STORE_STATS_QUERY).into_iter() { @@ -110,7 +126,7 @@ if let Some(row) = pool.first_exec(GET_REPLAY_NAME_QUERY, params! { "id" => replay_id })? { - let filename = from_row_opt::<(String)>(row)?; + let filename = from_row_opt::(row)?; Ok(Some(filename)) } else { Ok(None) diff -r b3c9f5463cee -r 74ede02bc882 rust/hedgewars-server/src/server/demo.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/server/demo.rs Sun Jul 05 14:53:44 2020 +0200 @@ -0,0 +1,467 @@ +use crate::{ + core::types::{GameCfg, HedgehogInfo, TeamInfo}, + server::haskell::HaskellValue, +}; +use std::{ + collections::HashMap, + fs, + io::{self, BufReader, Read, Write}, + str::FromStr, +}; + +#[derive(PartialEq, Debug)] +pub struct Demo { + teams: Vec, + config: Vec, + messages: Vec, +} + +impl Demo { + fn save(self, filename: String) -> io::Result<()> { + let text = format!("{}", demo_to_haskell(self)); + let mut file = fs::File::open(filename)?; + file.write(text.as_bytes())?; + Ok(()) + } + + fn load(filename: String) -> io::Result { + let mut file = fs::File::open(filename)?; + let mut bytes = vec![]; + file.read_to_end(&mut bytes)?; + match super::haskell::parse(&bytes[..]) { + Ok((_, value)) => haskell_to_demo(value).ok_or(io::Error::new( + io::ErrorKind::InvalidData, + "Invalid demo structure", + )), + Err(_) => Err(io::Error::new( + io::ErrorKind::InvalidData, + "Unable to parse file", + )), + } + } + + fn load_hwd(filename: String) -> io::Result { + let file = fs::File::open(filename)?; + let mut reader = io::BufReader::new(file); + + #[inline] + fn error(cause: &str) -> io::Result { + Err(io::Error::new(io::ErrorKind::InvalidData, cause)) + } + + fn read_command<'a>( + reader: &mut BufReader, + buffer: &'a mut [u8], + ) -> io::Result> { + use io::BufRead; + + let mut size = [0u8; 1]; + if reader.read(&mut size)? == 0 { + Ok(None) + } else { + let text = &mut buffer[0..size[0] as _]; + + if reader.read(text)? < text.len() { + Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + "Incomplete command", + )) + } else { + std::str::from_utf8(text).map(Some).map_err(|e| { + io::Error::new(io::ErrorKind::InvalidInput, "The string is not UTF8") + }) + } + } + } + + fn get_script_name(arg: &str) -> io::Result { + const PREFIX: &str = "Scripts/Multiplayer/"; + const SUFFIX: &str = ".lua"; + if arg.starts_with(PREFIX) && arg.ends_with(SUFFIX) { + let script = arg[PREFIX.len()..arg.len() - SUFFIX.len()].to_string(); + Ok(script.replace('_', " ")) + } else { + error("Script is not multiplayer") + } + } + + fn get_game_flags(arg: &str) -> io::Result> { + const FLAGS: &[u32] = &[ + 0x0000_1000, + 0x0000_0010, + 0x0000_0004, + 0x0000_0008, + 0x0000_0020, + 0x0000_0040, + 0x0000_0080, + 0x0000_0100, + 0x0000_0200, + 0x0000_0400, + 0x0000_0800, + 0x0000_2000, + 0x0000_4000, + 0x0000_8000, + 0x0001_0000, + 0x0002_0000, + 0x0004_0000, + 0x0008_0000, + 0x0010_0000, + 0x0020_0000, + 0x0040_0000, + 0x0080_0000, + 0x0100_0000, + 0x0200_0000, + 0x0400_0000, + ]; + + let flags = u32::from_str(arg).unwrap_or_default(); + let game_flags = FLAGS + .iter() + .map(|flag| (flag & flags != 0).to_string()) + .collect(); + + Ok(game_flags) + } + + let mut config = Vec::new(); + let mut buffer = [0u8; u8::max_value() as _]; + + let mut game_flags = vec![]; + let mut scheme_properties: Vec<_> = [ + "1", "1000", "100", "1", "1", "1000", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", + "1", "", + ] + .iter() + .map(|p| p.to_string()) + .collect(); + const SCHEME_PROPERTY_NAMES: &[&str] = &[ + "$damagepct", + "$turntime", + "", + "$sd_turns", + "$casefreq", + "$minestime", + "$minesnum", + "$minedudpct", + "$explosives", + "$airmines", + "$healthprob", + "$hcaseamount", + "$waterrise", + "$healthdec", + "$ropepct", + "$getawaytime", + "$worldedge", + ]; + const AMMO_PROPERTY_NAMES: &[&str] = &["eammloadt", "eammprob", "eammdelay", "eammreinf"]; + let mut ammo_settings = vec![String::new(); AMMO_PROPERTY_NAMES.len()]; + let mut teams = vec![]; + let mut hog_index = 7usize; + + let mut messages = vec![]; + + while let Some(cmd) = read_command(&mut reader, &mut buffer)? { + if let Some(index) = cmd.find(' ') { + match cmd.chars().next().unwrap_or_default() { + 'T' => { + if cmd != "TD" { + let () = error("Not a demo file")?; + } + } + 'e' => { + if let Some(index) = cmd.find(' ') { + let (name, arg) = cmd.split_at(index); + match name { + "script" => config.push(GameCfg::Script(get_script_name(arg)?)), + "map" => config.push(GameCfg::MapType(arg.to_string())), + "theme" => config.push(GameCfg::Theme(arg.to_string())), + "seed" => config.push(GameCfg::Seed(arg.to_string())), + "$gmflags" => game_flags = get_game_flags(arg)?, + "$scriptparam" => { + *scheme_properties.last_mut().unwrap() = arg.to_string() + } + "$template_filter" => config.push(GameCfg::Template( + u32::from_str(arg).unwrap_or_default(), + )), + "$feature_size" => config.push(GameCfg::FeatureSize( + u32::from_str(arg).unwrap_or_default(), + )), + "$map_gen" => config.push(GameCfg::MapGenerator( + u32::from_str(arg).unwrap_or_default(), + )), + "$maze_size" => config.push(GameCfg::MazeSize( + u32::from_str(arg).unwrap_or_default(), + )), + "addteam" => { + if let parts = arg.splitn(3, ' ').collect::>() { + let color = parts.get(1).unwrap_or(&"1"); + let name = parts.get(2).unwrap_or(&"Unnamed"); + teams.push(TeamInfo { + color: (u32::from_str(color).unwrap_or(2113696) + / 2113696 + - 1) + as u8, + name: name.to_string(), + ..TeamInfo::default() + }) + }; + } + "fort" => teams + .last_mut() + .iter_mut() + .for_each(|t| t.fort = arg.to_string()), + "grave" => teams + .last_mut() + .iter_mut() + .for_each(|t| t.grave = arg.to_string()), + "addhh" => { + hog_index = (hog_index + 1) % 8; + if let parts = arg.splitn(3, ' ').collect::>() { + let health = parts.get(1).unwrap_or(&"100"); + teams.last_mut().iter_mut().for_each(|t| { + if let Some(difficulty) = parts.get(0) { + t.difficulty = + u8::from_str(difficulty).unwrap_or(0); + } + if let Some(init_health) = parts.get(1) { + scheme_properties[2] = init_health.to_string(); + } + t.hedgehogs_number = (hog_index + 1) as u8; + t.hedgehogs[hog_index].name = + parts.get(2).unwrap_or(&"Unnamed").to_string() + }); + } + } + "hat" => { + teams + .last_mut() + .iter_mut() + .for_each(|t| t.hedgehogs[hog_index].hat = arg.to_string()); + } + name => { + if let Some(index) = + SCHEME_PROPERTY_NAMES.iter().position(|n| *n == name) + { + scheme_properties[index] = arg.to_string(); + } else if let Some(index) = + AMMO_PROPERTY_NAMES.iter().position(|n| *n == name) + { + ammo_settings[index] = arg.to_string(); + } + } + } + } + } + '+' => {} + _ => (), + } + } + } + + game_flags.append(&mut scheme_properties); + config.push(GameCfg::Scheme("ADHOG_SCHEME".to_string(), game_flags)); + config.push(GameCfg::Ammo( + "ADHOG_AMMO".to_string(), + Some(ammo_settings.concat()), + )); + + Ok(Demo { + teams, + config, + messages, + }) + } +} + +fn demo_to_haskell(mut demo: Demo) -> HaskellValue { + use HaskellValue as Hs; + + let mut teams = Vec::with_capacity(demo.teams.len()); + for team in demo.teams { + let mut fields = HashMap::::new(); + + fields.insert("teamowner".to_string(), Hs::String(team.owner)); + fields.insert("teamname".to_string(), Hs::String(team.name)); + fields.insert("teamcolor".to_string(), Hs::Number(team.color)); + fields.insert("teamgrave".to_string(), Hs::String(team.grave)); + fields.insert("teamvoicepack".to_string(), Hs::String(team.voice_pack)); + fields.insert("teamflag".to_string(), Hs::String(team.flag)); + fields.insert("difficulty".to_string(), Hs::Number(team.difficulty)); + fields.insert("hhnum".to_string(), Hs::Number(team.hedgehogs_number)); + + let hogs = team + .hedgehogs + .iter() + .map(|hog| Hs::AnonStruct { + name: "HedgehogInfo".to_string(), + fields: vec![Hs::String(hog.name.clone()), Hs::String(hog.hat.clone())], + }) + .collect(); + + fields.insert("hedgehogs".to_string(), Hs::List(hogs)); + + teams.push(Hs::Struct { + name: "TeamInfo".to_string(), + fields, + }) + } + + let mut map_config = vec![]; + let mut game_config = vec![]; + + let mut save_map_config = |name: &str, value: String| { + map_config.push(Hs::Tuple(vec![ + Hs::String(name.to_string()), + Hs::String(value), + ])); + }; + + for config_item in &demo.config { + match config_item { + GameCfg::FeatureSize(size) => save_map_config("FEATURE_SIZE", size.to_string()), + GameCfg::MapType(map_type) => save_map_config("MAP", map_type.clone()), + GameCfg::MapGenerator(generator) => save_map_config("MAPGEN", generator.to_string()), + GameCfg::MazeSize(size) => save_map_config("MAZE_SIZE", size.to_string()), + GameCfg::Seed(seed) => save_map_config("SEED", seed.clone()), + GameCfg::Template(template) => save_map_config("TEMPLATE", template.to_string()), + GameCfg::DrawnMap(map) => save_map_config("DRAWNMAP", map.clone()), + _ => (), + } + } + + let mut save_game_config = |name: &str, mut value: Vec| { + map_config.push(Hs::Tuple(vec![ + Hs::String(name.to_string()), + Hs::List(value.drain(..).map(Hs::String).collect()), + ])); + }; + + for config_item in &demo.config { + match config_item { + GameCfg::Ammo(name, Some(ammo)) => { + save_game_config("AMMO", vec![name.clone(), ammo.clone()]) + } + GameCfg::Ammo(name, None) => save_game_config("AMMO", vec![name.clone()]), + GameCfg::Scheme(name, scheme) => { + let mut values = vec![name.clone()]; + values.extend_from_slice(&scheme); + save_game_config("SCHEME", values); + } + GameCfg::Script(script) => save_game_config("SCRIPT", vec![script.clone()]), + GameCfg::Theme(theme) => save_game_config("THEME", vec![theme.clone()]), + _ => (), + } + } + + Hs::Tuple(vec![ + Hs::List(teams), + Hs::List(map_config), + Hs::List(game_config), + Hs::List(demo.messages.drain(..).map(Hs::String).collect()), + ]) +} + +fn haskell_to_demo(value: HaskellValue) -> Option { + use HaskellValue::*; + let mut lists = value.into_tuple()?; + let mut lists_iter = lists.drain(..); + + let teams_list = lists_iter.next()?.into_list()?; + let map_config = lists_iter.next()?.into_list()?; + let game_config = lists_iter.next()?.into_list()?; + let engine_messages = lists_iter.next()?.into_list()?; + + let mut teams = Vec::with_capacity(teams_list.len()); + + for team in teams_list { + let (_, mut fields) = team.into_struct()?; + + let mut team_info = TeamInfo::default(); + for (name, value) in fields.drain() { + match &name[..] { + "teamowner" => team_info.owner = value.into_string()?, + "teamname" => team_info.name = value.into_string()?, + "teamcolor" => team_info.color = u8::from_str(&value.into_string()?).ok()?, + "teamgrave" => team_info.grave = value.into_string()?, + "teamfort" => team_info.fort = value.into_string()?, + "teamvoicepack" => team_info.voice_pack = value.into_string()?, + "teamflag" => team_info.flag = value.into_string()?, + "difficulty" => team_info.difficulty = value.into_number()?, + "hhnum" => team_info.hedgehogs_number = value.into_number()?, + "hedgehogs" => { + for (index, hog) in value + .into_list()? + .drain(..) + .enumerate() + .take(team_info.hedgehogs.len()) + { + let (_, mut fields) = hog.into_anon_struct()?; + let mut fields_iter = fields.drain(..); + team_info.hedgehogs[index] = HedgehogInfo { + name: fields_iter.next()?.into_string()?, + hat: fields_iter.next()?.into_string()?, + } + } + } + _ => (), + } + } + teams.push(team_info) + } + + let mut config = Vec::with_capacity(map_config.len() + game_config.len()); + + for item in map_config { + let mut tuple = item.into_tuple()?; + let mut tuple_iter = tuple.drain(..); + let name = tuple_iter.next()?.into_string()?; + let value = tuple_iter.next()?.into_string()?; + + let config_item = match &name[..] { + "FEATURE_SIZE" => GameCfg::FeatureSize(u32::from_str(&value).ok()?), + "MAP" => GameCfg::MapType(value), + "MAPGEN" => GameCfg::MapGenerator(u32::from_str(&value).ok()?), + "MAZE_SIZE" => GameCfg::MazeSize(u32::from_str(&value).ok()?), + "SEED" => GameCfg::Seed(value), + "TEMPLATE" => GameCfg::Template(u32::from_str(&value).ok()?), + "DRAWNMAP" => GameCfg::DrawnMap(value), + _ => None?, + }; + config.push(config_item); + } + + for item in game_config { + let mut tuple = item.into_tuple()?; + let mut tuple_iter = tuple.drain(..); + let name = tuple_iter.next()?.into_string()?; + let mut value = tuple_iter.next()?.into_list()?; + let mut value_iter = value.drain(..); + + let config_item = match &name[..] { + "AMMO" => GameCfg::Ammo( + value_iter.next()?.into_string()?, + value_iter.next().and_then(|v| v.into_string()), + ), + "SCHEME" => GameCfg::Scheme( + value_iter.next()?.into_string()?, + value_iter.filter_map(|v| v.into_string()).collect(), + ), + "SCRIPT" => GameCfg::Script(value_iter.next()?.into_string()?), + "THEME" => GameCfg::Theme(value_iter.next()?.into_string()?), + _ => None?, + }; + config.push(config_item); + } + + let mut messages = Vec::with_capacity(engine_messages.len()); + + for message in engine_messages { + messages.push(message.into_string()?); + } + + Some(Demo { + teams, + config, + messages, + }) +} diff -r b3c9f5463cee -r 74ede02bc882 rust/hedgewars-server/src/server/haskell.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-server/src/server/haskell.rs Sun Jul 05 14:53:44 2020 +0200 @@ -0,0 +1,412 @@ +use nom::{ + branch::alt, + bytes::complete::{escaped_transform, is_not, tag, take_while, take_while1}, + character::{is_alphanumeric, is_digit, is_space}, + combinator::{map, map_res}, + multi::{many0, separated_list}, + sequence::{delimited, pair, preceded, separated_pair, terminated}, + ExtendInto, IResult, +}; +use std::{ + collections::HashMap, + fmt::{Display, Error, Formatter}, +}; + +type HaskellResult<'a, T> = IResult<&'a [u8], T>; + +#[derive(Debug, PartialEq)] +pub enum HaskellValue { + Boolean(bool), + Number(u8), + String(String), + Tuple(Vec), + List(Vec), + AnonStruct { + name: String, + fields: Vec, + }, + Struct { + name: String, + fields: HashMap, + }, +} + +impl HaskellValue { + pub fn to_number(&self) -> Option { + match self { + HaskellValue::Number(value) => Some(*value), + _ => None, + } + } + + pub fn into_number(self) -> Option { + match self { + HaskellValue::Number(value) => Some(value), + _ => None, + } + } + + pub fn to_string(&self) -> Option<&str> { + match self { + HaskellValue::String(value) => Some(value), + _ => None, + } + } + + pub fn into_string(self) -> Option { + match self { + HaskellValue::String(value) => Some(value), + _ => None, + } + } + + pub fn into_list(self) -> Option> { + match self { + HaskellValue::List(items) => Some(items), + _ => None, + } + } + + pub fn into_tuple(self) -> Option> { + match self { + HaskellValue::Tuple(items) => Some(items), + _ => None, + } + } + + pub fn into_anon_struct(self) -> Option<(String, Vec)> { + match self { + HaskellValue::AnonStruct { name, fields } => Some((name, fields)), + _ => None, + } + } + + pub fn into_struct(self) -> Option<(String, HashMap)> { + match self { + HaskellValue::Struct { name, fields } => Some((name, fields)), + _ => None, + } + } +} + +fn write_sequence( + f: &mut Formatter<'_>, + brackets: &[u8; 2], + mut items: std::slice::Iter, +) -> Result<(), Error> { + write!(f, "{}", brackets[0] as char)?; + while let Some(value) = items.next() { + write!(f, "{}", value)?; + if !items.as_slice().is_empty() { + write!(f, ", ")?; + } + } + if brackets[1] != b'\0' { + write!(f, "{}", brackets[1] as char) + } else { + Ok(()) + } +} + +fn write_text(f: &mut Formatter<'_>, text: &str) -> Result<(), Error> { + write!(f, "\"")?; + for c in text.chars() { + if c.is_ascii() && !(c as u8).is_ascii_control() { + write!(f, "{}", c)?; + } else { + let mut bytes = [0u8; 4]; + let size = c.encode_utf8(&mut bytes).len(); + for byte in &bytes[0..size] { + write!(f, "\\{:03}", byte)?; + } + } + } + write!(f, "\"") +} + +impl Display for HaskellValue { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + match self { + HaskellValue::Boolean(value) => write!(f, "{}", if *value { "True" } else { "False" }), + HaskellValue::Number(value) => write!(f, "{}", value), + HaskellValue::String(value) => write_text(f, value), + HaskellValue::Tuple(items) => write_sequence(f, b"()", items.iter()), + HaskellValue::List(items) => write_sequence(f, b"[]", items.iter()), + HaskellValue::AnonStruct { name, fields } => { + write!(f, "{} ", name)?; + write_sequence(f, b" \0", fields.iter()) + } + HaskellValue::Struct { name, fields } => { + write!(f, "{} {{", name)?; + let fields = fields.iter().collect::>(); + let mut items = fields.iter(); + while let Some((field_name, value)) = items.next() { + write!(f, "{} = {}", field_name, value)?; + if !items.as_slice().is_empty() { + write!(f, ", ")?; + } + } + write!(f, "}}") + } + } + } +} + +fn comma(input: &[u8]) -> HaskellResult<&[u8]> { + delimited(take_while(is_space), tag(","), take_while(is_space))(input) +} + +fn surrounded<'a, P, O>( + prefix: &'static str, + suffix: &'static str, + parser: P, +) -> impl Fn(&'a [u8]) -> HaskellResult<'a, O> +where + P: Fn(&'a [u8]) -> HaskellResult<'a, O>, +{ + move |input| { + delimited( + delimited(take_while(is_space), tag(prefix), take_while(is_space)), + |i| parser(i), + delimited(take_while(is_space), tag(suffix), take_while(is_space)), + )(input) + } +} + +fn boolean(input: &[u8]) -> HaskellResult { + map( + alt((map(tag("True"), |_| true), map(tag("False"), |_| false))), + HaskellValue::Boolean, + )(input) +} + +fn number_raw(input: &[u8]) -> HaskellResult { + use std::str::FromStr; + map_res(take_while(is_digit), |s| { + std::str::from_utf8(s) + .map_err(|_| ()) + .and_then(|s| u8::from_str(s).map_err(|_| ())) + })(input) +} + +fn number(input: &[u8]) -> HaskellResult { + map(number_raw, HaskellValue::Number)(input) +} + +enum Escape { + Empty, + Byte(u8), +} + +impl ExtendInto for Escape { + type Item = u8; + type Extender = Vec; + + fn new_builder(&self) -> Self::Extender { + Vec::new() + } + + fn extend_into(&self, acc: &mut Self::Extender) { + if let Escape::Byte(b) = self { + acc.push(*b); + } + } +} + +impl Extend for Vec { + fn extend>(&mut self, iter: T) { + for item in iter { + item.extend_into(self); + } + } +} + +fn string_escape(input: &[u8]) -> HaskellResult { + use Escape::*; + alt(( + map(number_raw, |n| Byte(n)), + alt(( + map(tag("\\"), |_| Byte(b'\\')), + map(tag("\""), |_| Byte(b'\"')), + map(tag("'"), |_| Byte(b'\'')), + map(tag("n"), |_| Byte(b'\n')), + map(tag("r"), |_| Byte(b'\r')), + map(tag("t"), |_| Byte(b'\t')), + map(tag("a"), |_| Byte(b'\x07')), + map(tag("b"), |_| Byte(b'\x08')), + map(tag("v"), |_| Byte(b'\x0B')), + map(tag("f"), |_| Byte(b'\x0C')), + map(tag("&"), |_| Empty), + map(tag("NUL"), |_| Byte(b'\x00')), + map(tag("SOH"), |_| Byte(b'\x01')), + map(tag("STX"), |_| Byte(b'\x02')), + map(tag("ETX"), |_| Byte(b'\x03')), + map(tag("EOT"), |_| Byte(b'\x04')), + map(tag("ENQ"), |_| Byte(b'\x05')), + map(tag("ACK"), |_| Byte(b'\x06')), + )), + alt(( + map(tag("SO"), |_| Byte(b'\x0E')), + map(tag("SI"), |_| Byte(b'\x0F')), + map(tag("DLE"), |_| Byte(b'\x10')), + map(tag("DC1"), |_| Byte(b'\x11')), + map(tag("DC2"), |_| Byte(b'\x12')), + map(tag("DC3"), |_| Byte(b'\x13')), + map(tag("DC4"), |_| Byte(b'\x14')), + map(tag("NAK"), |_| Byte(b'\x15')), + map(tag("SYN"), |_| Byte(b'\x16')), + map(tag("ETB"), |_| Byte(b'\x17')), + map(tag("CAN"), |_| Byte(b'\x18')), + map(tag("EM"), |_| Byte(b'\x19')), + map(tag("SUB"), |_| Byte(b'\x1A')), + map(tag("ESC"), |_| Byte(b'\x1B')), + map(tag("FS"), |_| Byte(b'\x1C')), + map(tag("GS"), |_| Byte(b'\x1D')), + map(tag("RS"), |_| Byte(b'\x1E')), + map(tag("US"), |_| Byte(b'\x1F')), + map(tag("DEL"), |_| Byte(b'\x7F')), + )), + ))(input) +} + +fn string_content(input: &[u8]) -> HaskellResult { + map_res( + escaped_transform(is_not("\"\\"), '\\', string_escape), + |bytes| String::from_utf8(bytes).map_err(|_| ()), + )(input) +} + +fn string(input: &[u8]) -> HaskellResult { + map( + delimited(tag("\""), string_content, tag("\"")), + HaskellValue::String, + )(input) +} + +fn tuple(input: &[u8]) -> HaskellResult { + map( + surrounded("(", ")", separated_list(comma, value)), + HaskellValue::Tuple, + )(input) +} + +fn list(input: &[u8]) -> HaskellResult { + map( + surrounded("[", "]", separated_list(comma, value)), + HaskellValue::List, + )(input) +} + +fn identifier(input: &[u8]) -> HaskellResult { + map_res(take_while1(is_alphanumeric), |s| { + std::str::from_utf8(s).map_err(|_| ()).map(String::from) + })(input) +} + +fn named_field(input: &[u8]) -> HaskellResult<(String, HaskellValue)> { + separated_pair( + identifier, + delimited(take_while(is_space), tag("="), take_while(is_space)), + value, + )(input) +} + +fn structure(input: &[u8]) -> HaskellResult { + alt(( + map( + pair( + identifier, + surrounded("{", "}", separated_list(comma, named_field)), + ), + |(name, mut fields)| HaskellValue::Struct { + name, + fields: fields.drain(..).collect(), + }, + ), + map( + pair( + identifier, + preceded( + take_while(is_space), + many0(terminated(value, take_while(is_space))), + ), + ), + |(name, fields)| HaskellValue::AnonStruct { + name: name.clone(), + fields, + }, + ), + ))(input) +} + +fn value(input: &[u8]) -> HaskellResult { + alt((boolean, number, string, tuple, list, structure))(input) +} + +#[inline] +pub fn parse(input: &[u8]) -> HaskellResult { + delimited(take_while(is_space), value, take_while(is_space))(input) +} + +mod test { + use super::*; + + #[test] + fn terminals() { + use HaskellValue::*; + + matches!(number(b"127"), Ok((_, Number(127)))); + matches!(number(b"adas"), Err(nom::Err::Error(_))); + + assert_eq!( + string(b"\"Hail \\240\\159\\166\\148!\""), + Ok((&b""[..], String("Hail \u{1f994}!".to_string()))) + ); + } + + #[test] + fn sequences() { + use HaskellValue::*; + + let value = Tuple(vec![ + Number(64), + String("text\t1".to_string()), + List(vec![Number(1), Number(2), Number(3)]), + ]); + + assert_eq!( + tuple(b"(64, \"text\\t1\", [1 , 2, 3])"), + Ok((&b""[..], value)) + ); + } + + #[test] + fn structures() { + use HaskellValue::*; + + let value = Struct { + name: "Hog".to_string(), + fields: vec![ + ("name".to_string(), String("\u{1f994}".to_string())), + ("health".to_string(), Number(100)), + ] + .drain(..) + .collect(), + }; + + assert_eq!( + structure(b"Hog {name = \"\\240\\159\\166\\148\", health = 100}"), + Ok((&b""[..], value)) + ); + + let value = AnonStruct { + name: "Hog".to_string(), + fields: vec![Boolean(true), Number(100), String("\u{1f994}".to_string())], + }; + + assert_eq!( + structure(b"Hog True 100 \"\\240\\159\\166\\148\""), + Ok((&b""[..], value)) + ); + } +} diff -r b3c9f5463cee -r 74ede02bc882 rust/hedgewars-server/src/server/io.rs --- a/rust/hedgewars-server/src/server/io.rs Sun Jul 05 02:03:08 2020 +0200 +++ b/rust/hedgewars-server/src/server/io.rs Sun Jul 05 14:53:44 2020 +0200 @@ -61,6 +61,18 @@ } } + IoTask::GetCheckerAccount { nick, password } => { + match db.get_checker_account(&nick, &password) { + Ok(is_registered) => IoResult::CheckerAccount { is_registered }, + Err(e) => { + warn!("Unable to get checker account data: {}", e); + IoResult::CheckerAccount { + is_registered: false, + } + } + } + } + IoTask::GetReplay { id } => { let result = match db.get_replay_name(id) { Ok(Some(filename)) => { diff -r b3c9f5463cee -r 74ede02bc882 rust/hedgewars-server/src/server/network.rs --- a/rust/hedgewars-server/src/server/network.rs Sun Jul 05 02:03:08 2020 +0200 +++ b/rust/hedgewars-server/src/server/network.rs Sun Jul 05 14:53:44 2020 +0200 @@ -18,9 +18,9 @@ use slab::Slab; use crate::{ - core::{server::HwServer, types::ClientId}, + core::types::ClientId, handlers, - handlers::{IoResult, IoTask}, + handlers::{IoResult, IoTask, ServerState}, protocol::{messages::HwServerMessage::Redirect, messages::*, ProtocolDecoder}, utils, }; @@ -317,7 +317,7 @@ pub struct NetworkLayer { listener: TcpListener, - server: HwServer, + server_state: ServerState, clients: Slab, pending: HashSet<(ClientId, NetworkClientState)>, pending_cache: Vec<(ClientId, NetworkClientState)>, @@ -425,7 +425,7 @@ } debug!("{} pending server messages", response.len()); - let output = response.extract_messages(&mut self.server); + let output = response.extract_messages(&mut self.server_state.server); for (clients, message) in output { debug!("Message {:?} to {:?}", message, clients); let msg_string = message.to_raw_protocol(); @@ -471,7 +471,7 @@ client.send_string( &HwServerMessage::Bye("Ping timeout".to_string()).to_raw_protocol(), ); - client.write(); + let _res = client.write(); } self.operation_failed( poll, @@ -490,7 +490,7 @@ while let Some((client_id, result)) = self.io.try_recv() { debug!("Handling io result {:?} for client {}", result, client_id); let mut response = handlers::Response::new(client_id); - handlers::handle_io_result(&mut self.server, client_id, &mut response, result); + handlers::handle_io_result(&mut self.server_state, client_id, &mut response, result); self.handle_response(response, poll); } Ok(()) @@ -523,13 +523,18 @@ response.add(Redirect(self.ssl.listener.local_addr().unwrap().port()).send_self()) } - handlers::handle_client_accept( - &mut self.server, - client_id, - &mut response, - self.clients[client_id].peer_addr.ip().is_loopback(), - ); - self.handle_response(response, poll); + if let IpAddr::V4(addr) = self.clients[client_id].peer_addr.ip() { + handlers::handle_client_accept( + &mut self.server_state, + client_id, + &mut response, + addr.octets(), + addr.is_loopback(), + ); + self.handle_response(response, poll); + } else { + todo!("implement something") + } } pub fn accept_client(&mut self, poll: &Poll, server_token: mio::Token) -> io::Result<()> { @@ -589,7 +594,7 @@ Ok((messages, state)) => { for message in messages { debug!("Handling message {:?} for client {}", message, client_id); - handlers::handle(&mut self.server, client_id, &mut response, message); + handlers::handle(&mut self.server_state, client_id, &mut response, message); } match state { NetworkClientState::NeedsRead => { @@ -644,7 +649,7 @@ if !pending_close { let mut response = handlers::Response::new(client_id); - handlers::handle_client_loss(&mut self.server, client_id, &mut response); + handlers::handle_client_loss(&mut self.server_state, client_id, &mut response); self.handle_response(response, poll); } @@ -717,7 +722,9 @@ .set_private_key_file("ssl/key.pem", SslFiletype::PEM) .expect("Cannot find private key file"); builder.set_options(SslOptions::NO_COMPRESSION); - builder.set_cipher_list("DEFAULT:!LOW:!RC4:!EXP").unwrap(); + builder.set_options(SslOptions::NO_TLSV1); + builder.set_options(SslOptions::NO_TLSV1_1); + builder.set_cipher_list("ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384").unwrap(); ServerSsl { listener, context: builder.build(), @@ -725,7 +732,8 @@ } pub fn build(self) -> NetworkLayer { - let server = HwServer::new(self.clients_capacity, self.rooms_capacity); + let server_state = ServerState::new(self.clients_capacity, self.rooms_capacity); + let clients = Slab::with_capacity(self.clients_capacity); let pending = HashSet::with_capacity(2 * self.clients_capacity); let pending_cache = Vec::with_capacity(2 * self.clients_capacity); @@ -733,7 +741,7 @@ NetworkLayer { listener: self.listener.expect("No listener provided"), - server, + server_state, clients, pending, pending_cache, diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/AmmoMenu/TurnsLeft.png Binary file share/hedgewars/Data/Graphics/AmmoMenu/TurnsLeft.png has changed diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/Flags/montenegro.png Binary file share/hedgewars/Data/Graphics/Flags/montenegro.png has changed diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/Flags/serbia.png Binary file share/hedgewars/Data/Graphics/Flags/serbia.png has changed diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/Hats/Dauber.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/hedgewars/Data/Graphics/Hats/Dauber.svg Sun Jul 05 14:53:44 2020 +0200 @@ -0,0 +1,1634 @@ + + + + + dauber + + + + + + + + + + image/svg+xml + + dauber + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + hedgewars hat dauber + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/Hats/DayAndNight.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/hedgewars/Data/Graphics/Hats/DayAndNight.svg Sun Jul 05 14:53:44 2020 +0200 @@ -0,0 +1,1218 @@ + + + + + star and moon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + star and moon + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + hedgewars hat star and moon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 4 + 3 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/Hats/Dragon.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/hedgewars/Data/Graphics/Hats/Dragon.svg Sun Jul 05 14:53:44 2020 +0200 @@ -0,0 +1,8195 @@ + + + + + dragon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + dragon + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + hedgewars hat dragon and laminaria hat + + + + A moustache without a dragon +is the same as a moustache with a dragon, +only without a dragon. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + +  2 + + + +   3 + + + +    4  + + + +     5  + + + +      6  + + + +       7  + + + +        8  + + + +         9  + + + +          10  + + + +           11  + + + +            12  + + + +             13  + + + +              14  + + + +               15  + + + +                16  + + + +                 17   + + + +                  18  + + + +                   19 + + + + + diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/Hats/Pantsu.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/hedgewars/Data/Graphics/Hats/Pantsu.svg Sun Jul 05 14:53:44 2020 +0200 @@ -0,0 +1,367 @@ + + + + + pantsu hat + + + + + + + + + + + + + image/svg+xml + + pantsu hat + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + hedgewars hat pantsu hat + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/Hats/Plunger.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/hedgewars/Data/Graphics/Hats/Plunger.svg Sun Jul 05 14:53:44 2020 +0200 @@ -0,0 +1,400 @@ + + + + + plunger + + + + + + image/svg+xml + + plunger + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + hedgewars hat plunger + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/Hats/ShaggyYeti.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/hedgewars/Data/Graphics/Hats/ShaggyYeti.svg Sun Jul 05 14:53:44 2020 +0200 @@ -0,0 +1,13216 @@ + + + + + shaggy yeti + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + shaggy yeti + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + hedgewars hat shaggy yeti and chicken + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/Hats/Sleepwalker.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/hedgewars/Data/Graphics/Hats/Sleepwalker.svg Sun Jul 05 14:53:44 2020 +0200 @@ -0,0 +1,895 @@ + + + + + sleep walker + + + + + + + + + + + + image/svg+xml + + sleep walker + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + hedgewars hat sleep walker + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/Hats/SunWukong.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/hedgewars/Data/Graphics/Hats/SunWukong.svg Sun Jul 05 14:53:44 2020 +0200 @@ -0,0 +1,1167 @@ + + + + + Sun Wukong monkey king + + + + + + + + + + + + + + + + + image/svg+xml + + Sun Wukong monkey king + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + hedgewars hat Sun Wukong monkey king + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/Hats/Zombi.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/hedgewars/Data/Graphics/Hats/Zombi.svg Sun Jul 05 14:53:44 2020 +0200 @@ -0,0 +1,2560 @@ + + + + + zombie + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + zombie + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + hedgewars hat zombie + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + repeat + + + + + + + + + + repeat + + + + + + + + + + repeat + + + + + + + + + + repeat + + + + + + + + + + repeat + + + + + + + + + + repeat + + + + + + + + + + repeat + + + + + + + + + + repeat + + + + + + + + + + repeat + + + + + + + + + + repeat + + + + + + + + + + repeat + + + + + + + + + + repeat + + + + + + + + + + repeat + + + + + + + + + + repeat + + + + + + + + + + repeat + + + + + + + + + + repeat + + + + + + + + + + repeat + + + + + + + + + + repeat + + + + + + + + + + repeat + + + + + + + + + + + repeat + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/Hats/bubble.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/hedgewars/Data/Graphics/Hats/bubble.svg Sun Jul 05 14:53:44 2020 +0200 @@ -0,0 +1,2450 @@ + + + + + bubble + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + bubble + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + hedgewars hat bubble + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/Hats/car.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/hedgewars/Data/Graphics/Hats/car.svg Sun Jul 05 14:53:44 2020 +0200 @@ -0,0 +1,4792 @@ + + + + + car + + + + + + + + + + image/svg+xml + + car + + + hedgewars hat car + + + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 01 + + + + + +  02 + + + + + +   03 + + + + + +    04 + + + + + +     05 + + + + + +      06 + + + + + +       07 + + + + + +        08 + + + + + +         09 + + + + + +          10 + + + + + +           11 + + + + + +            12 + + + + + +             13 + + + + + +              14 + + + + + +               15 + + + + + +                16 + + + + + +                 17 + + + + + +                  18 + + + + + +                   19 + + + + + + + + + + + + + + + + + + + + + + + + + current + + + + + + + + + + + + + + + + + + + + + + previous + + + + + + + + + + + + + + + + + + + + + + next + + + + + + + + + + diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/Hats/dish_Ladle.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/hedgewars/Data/Graphics/Hats/dish_Ladle.svg Sun Jul 05 14:53:44 2020 +0200 @@ -0,0 +1,1011 @@ + + + + + ladle + + + + + + + + + + image/svg+xml + + ladle + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + hedgewars hat ladle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/Hats/dish_SauceBoatTemplate.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/hedgewars/Data/Graphics/Hats/dish_SauceBoatTemplate.svg Sun Jul 05 14:53:44 2020 +0200 @@ -0,0 +1,791 @@ + + + + + sauce boat + + + + + + image/svg+xml + + sauce boat + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + hedgewars hat sauce boat + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/Hats/dish_Teacup.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/hedgewars/Data/Graphics/Hats/dish_Teacup.svg Sun Jul 05 14:53:44 2020 +0200 @@ -0,0 +1,416 @@ + + + + + teacup + + + + + + image/svg+xml + + teacup + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + hedgewars hat teacup + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/Hats/dish_Teapot.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/hedgewars/Data/Graphics/Hats/dish_Teapot.svg Sun Jul 05 14:53:44 2020 +0200 @@ -0,0 +1,2144 @@ + + + + + teapot + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + teapot + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + hedgewars hat teapot + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/Hats/lamp.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/hedgewars/Data/Graphics/Hats/lamp.svg Sun Jul 05 14:53:44 2020 +0200 @@ -0,0 +1,2010 @@ + + + + + incandescent light bulb (lamp) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + incandescent light bulb (lamp) + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + hedgewars hat incandescent light bulb (lamp) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 11 + + + + + + + diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/Hats/mechanicaltoy.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/hedgewars/Data/Graphics/Hats/mechanicaltoy.svg Sun Jul 05 14:53:44 2020 +0200 @@ -0,0 +1,5451 @@ + + + + + mechanical toy with key + + + + + + + + + + + + + + + + + + + image/svg+xml + + mechanical toy with key + + + hedgewars hat mechanical toy with key + + + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + + + + + + + + + + + + + + + + + + + 01 + + + + + + + + + + + + +  02 + + + + + + + + + + + + +   03 + + + + + + + + + + + + +    04 + + + + + + + + + + + + +     05 + + + + + + + + + + + + +      06 + + + + + + + + + + + + +       07 + + + + + + + + + + + + +        08 + + + + + + + + + + + + +         09 + + + + + + + + + + + + +          10 + + + + + + + + + + + + +           11 + + + + + + + + + + + + +            12 + + + + + + + + + + + + +             13 + + + + + + + + + + + + +              14 + + + + + + + + + + + + +               15 + + + + + + + + + + + + +                16 + + + + + + + + + + + + +                 17 + + + + + + + + + + + + +                  18 + + + + + + + + + + + + +                   19 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + current + + + + + + + + + + + + + + + + + + + + + + previous + + + + + + + + + + + + + + + + + + + + + + next + + + + + + + + + (let ((pi 3.1415926) (start-width 13.019)) (do ((i 1 (+ i 1))) ((> i 19)) (format #t "~d\t~1,2f\n" i (* start-width (cos (* i (/ pi 19))))))) + + + + + + + + diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/Hats/noface.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/hedgewars/Data/Graphics/Hats/noface.svg Sun Jul 05 14:53:44 2020 +0200 @@ -0,0 +1,1627 @@ + + + + + No Face + + + + + + + + + + + + + + + image/svg+xml + + No Face + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + hedgewars hat No Face + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/Hats/scif_BrainSlugTemplate.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/hedgewars/Data/Graphics/Hats/scif_BrainSlugTemplate.svg Sun Jul 05 14:53:44 2020 +0200 @@ -0,0 +1,1782 @@ + + + + + brain slug + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + brain slug + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + hedgewars hat brain slug + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/Hats/scif_cosmonaut.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/hedgewars/Data/Graphics/Hats/scif_cosmonaut.svg Sun Jul 05 14:53:44 2020 +0200 @@ -0,0 +1,2037 @@ + + + + + cosmonaut + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + cosmonaut + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + hedgewars hat cosmonaut + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + clones disconnected from parent object :( for illuminator + + diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/Hats/zoo_Pig.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/hedgewars/Data/Graphics/Hats/zoo_Pig.svg Sun Jul 05 14:53:44 2020 +0200 @@ -0,0 +1,878 @@ + + + + + pig + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + pig + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + hedgewars hat pig + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/Hats/zoo_elephant.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/hedgewars/Data/Graphics/Hats/zoo_elephant.svg Sun Jul 05 14:53:44 2020 +0200 @@ -0,0 +1,2022 @@ + + + + + Ganesha elephant + + + + + + + + + + + + + + + image/svg+xml + + Ganesha elephant + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + hedgewars hat Ganesha elephant + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/Hats/zoo_fish.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/hedgewars/Data/Graphics/Hats/zoo_fish.svg Sun Jul 05 14:53:44 2020 +0200 @@ -0,0 +1,3995 @@ + + + + + fish + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + fish + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + hedgewars hat fish + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/Hats/zoo_frog.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/hedgewars/Data/Graphics/Hats/zoo_frog.svg Sun Jul 05 14:53:44 2020 +0200 @@ -0,0 +1,1863 @@ + + + + + frog + + + + + + + + + + image/svg+xml + + frog + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + hedgewars hat frog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/Hats/zoo_panda.png Binary file share/hedgewars/Data/Graphics/Hats/zoo_panda.png has changed diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/Hats/zoo_snail.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/hedgewars/Data/Graphics/Hats/zoo_snail.svg Sun Jul 05 14:53:44 2020 +0200 @@ -0,0 +1,2321 @@ + + + + + snail + + + + + + + + + + + + + image/svg+xml + + snail + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + hedgewars hat snail + + + this is definitely not "Leucochloridium paradoxum" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/Hats/zoo_turtle.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/hedgewars/Data/Graphics/Hats/zoo_turtle.svg Sun Jul 05 14:53:44 2020 +0200 @@ -0,0 +1,1790 @@ + + + + + turtle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + turtle + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + Roman V. Prikhodchenko (chujoii@gmail.com) + + + + + hedgewars hat turtle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/Hedgehog/Bubble.png Binary file share/hedgewars/Data/Graphics/Hedgehog/Bubble.png has changed diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/Hedgehog/Happy.png Binary file share/hedgewars/Data/Graphics/Hedgehog/Happy.png has changed diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/Missions/Scenario/Big_Armory@2x.png Binary file share/hedgewars/Data/Graphics/Missions/Scenario/Big_Armory@2x.png has changed diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Graphics/dynamiteDefused.png Binary file share/hedgewars/Data/Graphics/dynamiteDefused.png has changed diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/campaigns_de.txt --- a/share/hedgewars/Data/Locale/campaigns_de.txt Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/campaigns_de.txt Sun Jul 05 14:53:44 2020 +0200 @@ -54,7 +54,7 @@ A_Space_Adventure-desert02.desc="Unser Held suchte nach dem Teil in diesem Tunnel, als er unerwarteterweise anfing, geflutet zu werden! Komm so schnell wie möglich zur Oberfläche und pass auf, keine Mine auszulösen." A_Space_Adventure-desert03.name="Nebenmission: Präzisionsfliegen" -A_Space_Adventure-desert03.desc="Unser Held hat etwas Zeit, um mit Funkflugzeugen zu spielen und etwas Spaß zu haben. Flieg das Funkflugzeug und triff alle Ziele!" +A_Space_Adventure-desert03.desc="Unser Held hat etwas Zeit, um mit Funkflugzeugen zu spielen und etwas Spaß zu haben. Flieg das Funkflugzeug und triff alle Ziele! Wenn du es schaffst, erhältst du zusätzliche Munition für die Hauptmission." A_Space_Adventure-fruit01.name="Hauptmission: Schlechtes Timing" A_Space_Adventure-fruit01.desc="Auf dem Obstplaneten laufen die Dinge nicht so gut. Igel sammeln kein Obst, sondern sie bereiten sich auf den Kampf vor. Du musst dich entscheiden, ob du kämpfen oder fliehen wirst." @@ -69,7 +69,7 @@ A_Space_Adventure-death01.desc="Auf dem Todesplaneten, dem sterilsten Planeten in der Gegend, ist unser Held ganz kurz davor, das letzte Teil des Geräts zu holen! Allerdings erwartet ihn eine unangenehme Überraschung." A_Space_Adventure-death02.name="Nebenmission: Die Spezialisten töten" -A_Space_Adventure-death02.desc="Unser Held ist wieder in eine schwierige Situation geraten. Besiege die »5 tödlichen Igel« in ihrem eigenem Spiel!" +A_Space_Adventure-death02.desc="Unser Held ist wieder in eine schwierige Situation geraten. Besiege die »5 tödlichen Igel« in ihrem eigenem Spiel! Wenn du gewinnst, erhältst du ein paar Extras für die Hauptmission." A_Space_Adventure-final.name="Hauptmission: Der große Knall" A_Space_Adventure-final.desc="Unser Held muss ein paar Sprengkörper, die auf dem Meteoriten platziert wurden, detonieren. Beende diese Mission, ohne verletzt zu werden!" diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/campaigns_en.txt --- a/share/hedgewars/Data/Locale/campaigns_en.txt Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/campaigns_en.txt Sun Jul 05 14:53:44 2020 +0200 @@ -46,7 +46,7 @@ A_Space_Adventure-desert02.name="Side Mission: Running for survival" A_Space_Adventure-desert02.desc="Our hero was searching for the part in this tunnel when it unexpectedly start getting flooded! Get to the surface as soon as possible and be careful not to trigger a mine." A_Space_Adventure-desert03.name="Side Mission: Precise flying" -A_Space_Adventure-desert03.desc="Our hero has some time to play with RC planes and have some fun. Fly the RC plane and hit all the targets!" +A_Space_Adventure-desert03.desc="Our hero has some time to play with RC planes and have some fun. Fly the RC plane and hit all the targets! If you win, you will gain some bonus ammo for the main mission." A_Space_Adventure-fruit01.name="Main Mission: Bad timing" A_Space_Adventure-fruit01.desc="On the fruit planet things aren't going so well. Hogs aren't collecting fruits but they are preparing for battle. You'll have to choose if you'll fight or if you'll flee." A_Space_Adventure-fruit02.name="Main Mission: Getting to the device" @@ -56,6 +56,6 @@ A_Space_Adventure-death01.name="Main Mission: The last encounter" A_Space_Adventure-death01.desc="On the Death Planet, the most infertile planet around, our hero is very close to get the last part of the device! However, an unpleasant surprise awaits ..." A_Space_Adventure-death02.name="Side Mission: Killing the specialists" -A_Space_Adventure-death02.desc="Again our hero has gotten in a difficult situation. Defeat the “5 Deadly Hogs“ in their own game!" +A_Space_Adventure-death02.desc="Again our hero has gotten in a difficult situation. Defeat the “5 Deadly Hogs“ in their own game! If you win, you will gain bonuses for the main mission." A_Space_Adventure-final.name="Main Mission: The big bang" A_Space_Adventure-final.desc="Our hero has to detonate some explosives that have been placed on the meteorite. Complete this mission without getting hurt!" diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/cs.lua --- a/share/hedgewars/Data/Locale/cs.lua Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/cs.lua Sun Jul 05 14:53:44 2020 +0200 @@ -677,11 +677,13 @@ -- ["Flawless victory!"] = "", -- User_Mission_-_RCPlane_Challenge -- ["Flee: Press [Jump]"] = "", -- A_Space_Adventure:fruit01 -- ["Flesh for Brainz"] = "", -- A_Classic_Fairytale:journey +-- ["Flower Power"] = "", -- Basic_Training_-_Rope -- ["Fly around and hurl explosives to your enemies."] = "", -- Tumbler -- ["Flying Saucer Training"] = "", -- Basic_Training_-_Flying_Saucer -- ["Fly into space to fight off the invaders with barrels!"] = "", -- Space_Invasion -- ["Fly to the meteorite and detonate the explosives"] = "", -- A_Space_Adventure:cosmos -- ["Follow the path and destroy the next target."] = "", -- Basic_Training_-_Rope +-- ["For each kill you win %d seconds."] = "", -- RopeKnocking -- ["Forgetfulness: You will lose all your weapons each turn."] = "", -- Continental_supplies -- ["For the next crate, you have to do back jumps."] = "", -- Basic_Training_-_Movement -- ["Four Eyes"] = "", -- @@ -1540,6 +1542,7 @@ -- ["Oneye"] = "", -- portal -- ["Only one hog per team allowed! Excess hogs will be removed"] = "", -- Mutant -- ["Only one hog per team allowed! Excess hogs will be removed."] = "", -- Mutant +-- ["Only one team per clan allowed! Excess teams will be removed."] = "", -- Mutant -- ["Only %s can be trusted with the crate."] = "", -- A_Space_Adventure:fruit02 -- ["Only the best pilots can master the following stunts."] = "", -- Basic_Training_-_Flying_Saucer -- ["Only two clans allowed! Excess hedgehogs will be removed."] = "", -- CTF_Blizzard @@ -2849,6 +2852,7 @@ -- ["You have killed all enemies."] = "", -- Big_Armory -- ["You have killed an innocent hedgehog!"] = "", -- A_Classic_Fairytale:backstab -- ["You have killed %d of 16 hedgehogs (+%d points)."] = "", -- User_Mission_-_Rope_Knock_Challenge +-- ["You have killed %d of %d hedgehogs (+%d points)."] = "", -- RopeKnocking -- ["You have launched %d bazookas."] = "", -- Target_Practice_-_Bazooka_easy, Target_Practice_-_Bazooka_hard, Basic_Training_-_Bazooka -- ["You have launched %d homing bees."] = "", -- Target_Practice_-_Homing_Bee -- ["You have made %d shots."] = "", -- Basic_Training_-_Sniper_Rifle diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/da.lua --- a/share/hedgewars/Data/Locale/da.lua Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/da.lua Sun Jul 05 14:53:44 2020 +0200 @@ -677,11 +677,13 @@ -- ["Flawless victory!"] = "", -- User_Mission_-_RCPlane_Challenge -- ["Flee: Press [Jump]"] = "", -- A_Space_Adventure:fruit01 -- ["Flesh for Brainz"] = "", -- A_Classic_Fairytale:journey +-- ["Flower Power"] = "", -- Basic_Training_-_Rope -- ["Fly around and hurl explosives to your enemies."] = "", -- Tumbler -- ["Flying Saucer Training"] = "", -- Basic_Training_-_Flying_Saucer -- ["Fly into space to fight off the invaders with barrels!"] = "", -- Space_Invasion -- ["Fly to the meteorite and detonate the explosives"] = "", -- A_Space_Adventure:cosmos -- ["Follow the path and destroy the next target."] = "", -- Basic_Training_-_Rope +-- ["For each kill you win %d seconds."] = "", -- RopeKnocking -- ["Forgetfulness: You will lose all your weapons each turn."] = "", -- Continental_supplies -- ["For the next crate, you have to do back jumps."] = "", -- Basic_Training_-_Movement -- ["Four Eyes"] = "", -- @@ -1540,6 +1542,7 @@ -- ["Oneye"] = "", -- portal -- ["Only one hog per team allowed! Excess hogs will be removed"] = "", -- Mutant -- ["Only one hog per team allowed! Excess hogs will be removed."] = "", -- Mutant +-- ["Only one team per clan allowed! Excess teams will be removed."] = "", -- Mutant -- ["Only %s can be trusted with the crate."] = "", -- A_Space_Adventure:fruit02 -- ["Only the best pilots can master the following stunts."] = "", -- Basic_Training_-_Flying_Saucer -- ["Only two clans allowed! Excess hedgehogs will be removed."] = "", -- CTF_Blizzard @@ -2849,6 +2852,7 @@ -- ["You have killed all enemies."] = "", -- Big_Armory -- ["You have killed an innocent hedgehog!"] = "", -- A_Classic_Fairytale:backstab -- ["You have killed %d of 16 hedgehogs (+%d points)."] = "", -- User_Mission_-_Rope_Knock_Challenge +-- ["You have killed %d of %d hedgehogs (+%d points)."] = "", -- RopeKnocking -- ["You have launched %d bazookas."] = "", -- Target_Practice_-_Bazooka_easy, Target_Practice_-_Bazooka_hard, Basic_Training_-_Bazooka -- ["You have launched %d homing bees."] = "", -- Target_Practice_-_Homing_Bee -- ["You have made %d shots."] = "", -- Basic_Training_-_Sniper_Rifle diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/de.lua --- a/share/hedgewars/Data/Locale/de.lua Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/de.lua Sun Jul 05 14:53:44 2020 +0200 @@ -499,8 +499,8 @@ ["Destroy him, Leaks A Lot! He is responsible for the deaths of many of us!"]="Zerstöre ihn, Undichte Stelle! Er ist verantwortlich für viele Tote auf unserer Seite!", ["Destroy invaders and collect bonuses to score points."] = "Zerstöre Invasoren und sammle Boni auf, um zu punkten.", -- Space_Invasion ["- Destroy the enemy"] = "- Vernichte den Feind", -- HedgeEditor +["- Destroy the red targets"] = "- Zerstöre die roten Ziele", -- HedgeEditor ["- Destroy the red target"] = "- Zerstöre das rote Ziel", -- HedgeEditor -["- Destroy the red targets"] = "- Zerstöre die roten Ziele", -- HedgeEditor ["Destroy the targets!"] = "Zerstöre die Zielscheiben!", -- Basic_Training_-_Bazooka, Basic_Training_-_Grenade ["+%d flamer fuel!"] = "+%d Flammenwerfertreibstoff", -- Tumbler ["%d-Hit Combo! +%d points!"] = "%d-Treffer-Kombi! +%d Punkte!", -- Space_Invasion @@ -692,6 +692,7 @@ ["Flawless victory!"]="Perfekter Sieg!", ["Flee: Press [Jump]"] = "Fliehen: Drücke [Springen]", -- A_Space_Adventure:fruit01 ["Flesh for Brainz"]="Fleisch gegen Hirn", +["Flower Power"] = "Flower-Power", -- Basic_Training_-_Rope ["Fly around and hurl explosives to your enemies."] = "Flieg herum und wirf Sprengkörper auf deine Gegner.", -- Tumbler ["Flying Saucer Training"] = "Grundausbildung: Fliegende Untertasse", -- Basic_Training_-_Flying_Saucer ["Fly into space to fight off the invaders with barrels!"] = "Flieg in den Weltraum, um die Invasoren mit Fässern abzuwehren!", -- Space_Invasion @@ -699,6 +700,7 @@ ["Fly to the moon"]="Flieg zum Mond.", ["Fly to the moon."] = "Flieg zum Mond.", -- A_Space_Adventure:cosmos ["Follow the path and destroy the next target."] = "Folge dem Pfad und zerstöre die nächste Zielscheibe.", -- Basic_Training_-_Rope +["For each kill you win %d seconds."] = "Für jeden toten Igel erhältst du %d Sekunden.", -- RopeKnocking ["Forgetfulness: You will lose all your weapons each turn."] = "Vergesslichkeit: Du wirst jeden Zug alle Waffen verlieren.", -- Continental_supplies ["For the next crate, you have to do back jumps."] = "Für die nächste Kiste brauchst du Rückwärtssprünge.", -- Basic_Training_-_Movement ["Four Eyes"] = "Vier Augen", -- @@ -1545,6 +1547,7 @@ ["One tribe was peaceful, spending their time hunting and training, enjoying the small pleasures of life..."]="Ein Stamm war friedlich und verbrachte die Zeit mit der Jagd, Übungen und den kleinen Freuden des Lebens.", ["Oneye"] = "Einauge", -- portal ["Only one hog per team allowed! Excess hogs will be removed."] = "Nur ein Igel pro Team erlaubt! Überschüssige Igel werden entfernt.", -- Mutant +["Only one team per clan allowed! Excess teams will be removed."] = "Nur ein Team pro Klan erlaubt! Überschüssige Teams werden entfernt.", -- Mutant ["Only %s can be trusted with the crate."] = "Die Kiste kann nur %s anvertraut werden.", -- A_Space_Adventure:fruit02 ["Only the best pilots can master the following stunts."] = "Nur die besten Piloten können die folgenden Stunts meistern.", -- Basic_Training_-_Flying_Saucer ["Only two clans allowed! Excess hedgehogs will be removed."] = "Nur zwei Klans erlaubt! Überschüssige Igel werden entfernt.", -- CTF_Blizzard @@ -2485,8 +2488,8 @@ ["Use it wisely!"]="Benutze sie weise!", ["Use it with precaution!"]="Benutze sie weise.", ["User Challenge"]="Benutzerherausforderung", +["User Mission"] = "Benutzermission", -- HedgeEditor ["!"] = "!", -- User_Mission_-_Dangerous_Ducklings -["User Mission"] = "Benutzermission", -- HedgeEditor ["Use space button twice to change flying saucer while being in air."]="Drücke die Angriffstaste 2 mal, um die fliegende Untertasse im Flug zu wechseln", ["Use space button twice to change flying saucer while floating in mid-air."]="Drücke die Angriffstaste 2 mal, um die fliegende Untertasse im Flug zu wechseln.", ["Use the attack key twice to change the flying saucer while being in air."] = "Benutze die Angriffstaste 2 mal, um die fliegende Untertasse in der Luft zu wechseln.", -- A_Space_Adventure:ice02 @@ -2838,6 +2841,7 @@ ["You have kidnapped our whole tribe!"]="Ihr habt unseren ganzen Stamm entführt!", ["You have killed an innocent hedgehog!"]="Du hast einen unschuldigen Igel getötet!", ["You have killed %d of 16 hedgehogs (+%d points)."] = "Du hast %d von 16 Igeln getötet (+%d Punkte).", -- User_Mission_-_Rope_Knock_Challenge +["You have killed %d of %d hedgehogs (+%d points)."] = "Du hast %d von %d Igeln getötet (+%d Punkte).", -- RopeKnocking ["You have launched %d bazookas."]="Du hast %d Bazookas abgefeuert.", ["You have launched %d homing bees."]="Du hast %d zielsuchende Bienen abgefeuert.", ["You have made %d shots."]="Du hast %d Schüsse abgegeben.", diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/de.txt --- a/share/hedgewars/Data/Locale/de.txt Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/de.txt Sun Jul 05 14:53:44 2020 +0200 @@ -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 @@ -1421,3 +1422,6 @@ 06:26=Unbekannter Befehl oder ungültige Parameter. Sag »/help« im Chat für eine Liste an Befehlen. 06:27=/help room: Raum-Chatbefehle auflisten 06:28=Du bist nicht online! +06:29=/bubble: Igel die Luft anhalten lassen +06:30=/happy: Igel glücklich aussehen lassen +06:31=/sad: Igel unglücklich aussehen lassen diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/en.txt --- a/share/hedgewars/Data/Locale/en.txt Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/en.txt Sun Jul 05 14:53:44 2020 +0200 @@ -63,6 +63,7 @@ 00:58=Air Mine 00:59=Creeper 00:60=Minigun +00:61=Sentry Bot ; Game messages and HUD texts 01:00=Loading … @@ -121,6 +122,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) @@ -1277,6 +1279,7 @@ 04:58=This proximity bomb will float freely in the air and follow|hedgehogs careless enough to come too close to it.|Its explosion is weaker than that of the land mine, however.|Attack: Hold to shoot with more power 04:59=This weapon is unfinished and experimental.|Use at your own risk! 04:60=Unleash a rain of bullets upon your foes!|And they thought they were safe|behind a triple layer of girders.|Attack: Shoot at full power|Up/Down: Continue aiming +04:61=Beep Boop Whirrrrr|Attack: Deploy the bot ; Game goal strings 05:00=Game Modes @@ -1333,3 +1336,6 @@ 06:26=Unknown command or invalid parameters. Say “/help” in chat for a list of commands. 06:27=/help room: List room chat commands 06:28=You're not online! +06:29=/bubble: Make hedgehog hold its breath +06:30=/happy: Make hedgehog look happy +06:31=/sad: Make hedgehog look sad diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/es.lua --- a/share/hedgewars/Data/Locale/es.lua Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/es.lua Sun Jul 05 14:53:44 2020 +0200 @@ -677,11 +677,13 @@ -- ["Flawless victory!"] = "", -- User_Mission_-_RCPlane_Challenge -- ["Flee: Press [Jump]"] = "", -- A_Space_Adventure:fruit01 -- ["Flesh for Brainz"] = "", -- A_Classic_Fairytale:journey +-- ["Flower Power"] = "", -- Basic_Training_-_Rope -- ["Fly around and hurl explosives to your enemies."] = "", -- Tumbler -- ["Flying Saucer Training"] = "", -- Basic_Training_-_Flying_Saucer -- ["Fly into space to fight off the invaders with barrels!"] = "", -- Space_Invasion -- ["Fly to the meteorite and detonate the explosives"] = "", -- A_Space_Adventure:cosmos -- ["Follow the path and destroy the next target."] = "", -- Basic_Training_-_Rope +-- ["For each kill you win %d seconds."] = "", -- RopeKnocking -- ["Forgetfulness: You will lose all your weapons each turn."] = "", -- Continental_supplies -- ["For the next crate, you have to do back jumps."] = "", -- Basic_Training_-_Movement -- ["Four Eyes"] = "", -- @@ -1540,6 +1542,7 @@ -- ["Oneye"] = "", -- portal -- ["Only one hog per team allowed! Excess hogs will be removed"] = "", -- Mutant -- ["Only one hog per team allowed! Excess hogs will be removed."] = "", -- Mutant +-- ["Only one team per clan allowed! Excess teams will be removed."] = "", -- Mutant -- ["Only %s can be trusted with the crate."] = "", -- A_Space_Adventure:fruit02 -- ["Only the best pilots can master the following stunts."] = "", -- Basic_Training_-_Flying_Saucer -- ["Only two clans allowed! Excess hedgehogs will be removed."] = "", -- CTF_Blizzard @@ -2849,6 +2852,7 @@ -- ["You have killed all enemies."] = "", -- Big_Armory -- ["You have killed an innocent hedgehog!"] = "", -- A_Classic_Fairytale:backstab -- ["You have killed %d of 16 hedgehogs (+%d points)."] = "", -- User_Mission_-_Rope_Knock_Challenge +-- ["You have killed %d of %d hedgehogs (+%d points)."] = "", -- RopeKnocking -- ["You have launched %d bazookas."] = "", -- Target_Practice_-_Bazooka_easy, Target_Practice_-_Bazooka_hard, Basic_Training_-_Bazooka -- ["You have launched %d homing bees."] = "", -- Target_Practice_-_Homing_Bee -- ["You have made %d shots."] = "", -- Basic_Training_-_Sniper_Rifle diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/fr.lua --- a/share/hedgewars/Data/Locale/fr.lua Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/fr.lua Sun Jul 05 14:53:44 2020 +0200 @@ -685,11 +685,13 @@ -- ["Flawless victory!"] = "", -- User_Mission_-_RCPlane_Challenge -- ["Flee: Press [Jump]"] = "", -- A_Space_Adventure:fruit01 ["Flesh for Brainz"] = "Flesh for Brainz", +-- ["Flower Power"] = "", -- Basic_Training_-_Rope -- ["Fly around and hurl explosives to your enemies."] = "", -- Tumbler -- ["Flying Saucer Training"] = "", -- Basic_Training_-_Flying_Saucer -- ["Fly into space to fight off the invaders with barrels!"] = "", -- Space_Invasion -- ["Fly to the meteorite and detonate the explosives"] = "", -- A_Space_Adventure:cosmos -- ["Follow the path and destroy the next target."] = "", -- Basic_Training_-_Rope +-- ["For each kill you win %d seconds."] = "", -- RopeKnocking -- ["Forgetfulness: You will lose all your weapons each turn."] = "", -- Continental_supplies -- ["For the next crate, you have to do back jumps."] = "", -- Basic_Training_-_Movement -- ["Four Eyes"] = "", -- @@ -1553,6 +1555,7 @@ -- ["Oneye"] = "", -- portal -- ["Only one hog per team allowed! Excess hogs will be removed"] = "", -- Mutant -- ["Only one hog per team allowed! Excess hogs will be removed."] = "", -- Mutant +-- ["Only one team per clan allowed! Excess teams will be removed."] = "", -- Mutant -- ["Only %s can be trusted with the crate."] = "", -- A_Space_Adventure:fruit02 -- ["Only the best pilots can master the following stunts."] = "", -- Basic_Training_-_Flying_Saucer -- ["Only two clans allowed! Excess hedgehogs will be removed."] = "", -- CTF_Blizzard @@ -2864,6 +2867,7 @@ -- ["You have killed all enemies."] = "", -- Big_Armory ["You have killed an innocent hedgehog!"] = "Tu as tué un innocent !", -- ["You have killed %d of 16 hedgehogs (+%d points)."] = "", -- User_Mission_-_Rope_Knock_Challenge +-- ["You have killed %d of %d hedgehogs (+%d points)."] = "", -- RopeKnocking -- ["You have launched %d bazookas."] = "", -- Target_Practice_-_Bazooka_easy, Target_Practice_-_Bazooka_hard, Basic_Training_-_Bazooka -- ["You have launched %d homing bees."] = "", -- Target_Practice_-_Homing_Bee -- ["You have made %d shots."] = "", -- Basic_Training_-_Sniper_Rifle diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/hedgewars_bg.ts --- a/share/hedgewars/Data/Locale/hedgewars_bg.ts Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/hedgewars_bg.ts Sun Jul 05 14:53:44 2020 +0200 @@ -340,6 +340,7 @@ Scheme '%1' not supported + Here, “scheme” refers to the scheme of a Uniform Resource Identifier” @@ -544,12 +545,6 @@ - Your nickname is not registered. -To prevent someone else from using it, -please register it at www.hedgewars.org - - - Your password wasn't saved either. @@ -615,6 +610,13 @@ Internal error: Reply object is invalid. + + Your nickname is not registered. +To be able to rejoin games in progress and +prevent someone else from using your nickname, +please register it at www.hedgewars.org. + + HWGame @@ -1391,7 +1393,7 @@ Save - Запазване + Запазване (%1 %2) @@ -1442,6 +1444,14 @@ + + Save demo + + + + Save demo (unavailable because the /lua command was used) + + PageInGame @@ -3753,10 +3763,6 @@ - precise + switch + toggle hedgehog tags - - - high jump (twice) @@ -3764,6 +3770,10 @@ precise + screenshot + + precise + switch + toggle team bars + + binds (descriptions) @@ -4648,6 +4658,10 @@ Project founder + + Hungarian + + server diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/hedgewars_cs.ts --- a/share/hedgewars/Data/Locale/hedgewars_cs.ts Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/hedgewars_cs.ts Sun Jul 05 14:53:44 2020 +0200 @@ -346,6 +346,7 @@ Scheme '%1' not supported + Here, “scheme” refers to the scheme of a Uniform Resource Identifier” @@ -550,12 +551,6 @@ - Your nickname is not registered. -To prevent someone else from using it, -please register it at www.hedgewars.org - - - Your password wasn't saved either. @@ -621,6 +616,13 @@ Internal error: Reply object is invalid. + + Your nickname is not registered. +To be able to rejoin games in progress and +prevent someone else from using your nickname, +please register it at www.hedgewars.org. + + HWGame @@ -1419,7 +1421,7 @@ Save - Uložit + Uložit (%1 %2) @@ -1476,6 +1478,14 @@ + + Save demo + + + + Save demo (unavailable because the /lua command was used) + + PageInGame @@ -3810,10 +3820,6 @@ - precise + switch + toggle hedgehog tags - - - high jump (twice) @@ -3821,6 +3827,10 @@ precise + screenshot + + precise + switch + toggle team bars + + binds (descriptions) @@ -4845,6 +4855,10 @@ Project founder + + Hungarian + + server diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/hedgewars_da.ts --- a/share/hedgewars/Data/Locale/hedgewars_da.ts Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/hedgewars_da.ts Sun Jul 05 14:53:44 2020 +0200 @@ -344,6 +344,7 @@ Scheme '%1' not supported + Here, “scheme” refers to the scheme of a Uniform Resource Identifier” @@ -548,12 +549,6 @@ - Your nickname is not registered. -To prevent someone else from using it, -please register it at www.hedgewars.org - - - Your password wasn't saved either. @@ -619,6 +614,13 @@ Internal error: Reply object is invalid. + + Your nickname is not registered. +To be able to rejoin games in progress and +prevent someone else from using your nickname, +please register it at www.hedgewars.org. + + HWGame @@ -1413,7 +1415,7 @@ Save - Gem + Gem (%1 %2) @@ -1464,6 +1466,14 @@ + + Save demo + + + + Save demo (unavailable because the /lua command was used) + + PageInGame @@ -3803,10 +3813,6 @@ - precise + switch + toggle hedgehog tags - - - high jump (twice) @@ -3814,6 +3820,10 @@ precise + screenshot + + precise + switch + toggle team bars + + binds (descriptions) @@ -4842,6 +4852,10 @@ Project founder + + Hungarian + + server diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/hedgewars_de.ts --- a/share/hedgewars/Data/Locale/hedgewars_de.ts Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/hedgewars_de.ts Sun Jul 05 14:53:44 2020 +0200 @@ -380,6 +380,7 @@ Scheme '%1' not supported + Here, “scheme” refers to the scheme of a Uniform Resource Identifier” Das Schema »%1« wird nicht unterstützt @@ -598,7 +599,7 @@ Your nickname is not registered. To prevent someone else from using it, please register it at www.hedgewars.org - Dein Spitzname ist nicht registriert. + Dein Spitzname ist nicht registriert. Um Andere von der Benutzung abzuhalten, registrier ihn bitte auf www.hedgewars.org @@ -680,6 +681,16 @@ Internal error: Reply object is invalid. Interner Fehler: Reply-Objekt ist ungültig. + + Your nickname is not registered. +To be able to rejoin games in progress and +prevent someone else from using your nickname, +please register it at www.hedgewars.org. + Dein Spitzname ist nicht registriert. +Um laufenden Spielen wieder beitreten zu können +und Andere von der Benutzung deines Spitznamens +abzuhalten, registriere ihn bitte auf www.hedgewars.org. + HWGame @@ -1604,7 +1615,7 @@ Save - Speichern + Speichern (%1 %2) @@ -1655,6 +1666,14 @@ (%1 Kisten) + + Save demo + Wiederholung speichern + + + Save demo (unavailable because the /lua command was used) + Wiederholung speichern (nicht möglich, da der /lua-Befehl benutzt wurde) + PageInGame @@ -4204,7 +4223,7 @@ precise + switch + toggle hedgehog tags - Genaues Zielen + wechseln + Igelschilder umschalten + Genaues Zielen + wechseln + Igelschilder umschalten high jump (twice) @@ -4214,6 +4233,10 @@ precise + screenshot Genaues Zielen + Bildschirmfoto + + precise + switch + toggle team bars + Genaues Zielen + Wechseln + Teamleisten umschalten + binds (descriptions) diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/hedgewars_el.ts --- a/share/hedgewars/Data/Locale/hedgewars_el.ts Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/hedgewars_el.ts Sun Jul 05 14:53:44 2020 +0200 @@ -336,6 +336,7 @@ Scheme '%1' not supported + Here, “scheme” refers to the scheme of a Uniform Resource Identifier” @@ -542,12 +543,6 @@ - Your nickname is not registered. -To prevent someone else from using it, -please register it at www.hedgewars.org - - - Your password wasn't saved either. @@ -613,6 +608,13 @@ Internal error: Reply object is invalid. + + Your nickname is not registered. +To be able to rejoin games in progress and +prevent someone else from using your nickname, +please register it at www.hedgewars.org. + + HWGame @@ -1401,10 +1403,6 @@ Play again - - Save - - (%1 %2) For custom number of points in the stats screen, written after the team name. %1 is the number, %2 is the word. Example: “4 points” @@ -1454,6 +1452,14 @@ + + Save demo + + + + Save demo (unavailable because the /lua command was used) + + PageInGame @@ -3777,10 +3783,6 @@ - precise + switch + toggle hedgehog tags - - - high jump (twice) @@ -3788,6 +3790,10 @@ precise + screenshot + + precise + switch + toggle team bars + + binds (descriptions) @@ -4816,6 +4822,10 @@ Project founder + + Hungarian + + server diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/hedgewars_en.ts --- a/share/hedgewars/Data/Locale/hedgewars_en.ts Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/hedgewars_en.ts Sun Jul 05 14:53:44 2020 +0200 @@ -344,6 +344,7 @@ Scheme '%1' not supported + Here, “scheme” refers to the scheme of a Uniform Resource Identifier” Scheme ‘%1’ not supported @@ -563,7 +564,7 @@ Your nickname is not registered. To prevent someone else from using it, please register it at www.hedgewars.org - Your nickname is not registered. + Your nickname is not registered. To prevent someone else from using it, please register it at www.hedgewars.org @@ -657,6 +658,13 @@ Internal error: Reply object is invalid. Internal error: Reply object is invalid. + + Your nickname is not registered. +To be able to rejoin games in progress and +prevent someone else from using your nickname, +please register it at www.hedgewars.org. + + HWGame @@ -1515,7 +1523,7 @@ Save - Save + Save (%1 %2) @@ -1566,6 +1574,14 @@ (%1 crates) + + Save demo + + + + Save demo (unavailable because the /lua command was used) + + PageInGame @@ -3985,10 +4001,6 @@ - precise + switch + toggle hedgehog tags - - - high jump (twice) @@ -3996,6 +4008,10 @@ precise + screenshot + + precise + switch + toggle team bars + + binds (descriptions) @@ -5024,6 +5040,10 @@ Project founder + + Hungarian + + server diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/hedgewars_es.ts --- a/share/hedgewars/Data/Locale/hedgewars_es.ts Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/hedgewars_es.ts Sun Jul 05 14:53:44 2020 +0200 @@ -344,6 +344,7 @@ Scheme '%1' not supported + Here, “scheme” refers to the scheme of a Uniform Resource Identifier” @@ -548,12 +549,6 @@ - Your nickname is not registered. -To prevent someone else from using it, -please register it at www.hedgewars.org - - - Your password wasn't saved either. @@ -619,6 +614,13 @@ Internal error: Reply object is invalid. + + Your nickname is not registered. +To be able to rejoin games in progress and +prevent someone else from using your nickname, +please register it at www.hedgewars.org. + + HWGame @@ -1409,7 +1411,7 @@ Save - Guardar + Guardar (%1 %2) @@ -1460,6 +1462,14 @@ + + Save demo + + + + Save demo (unavailable because the /lua command was used) + + PageInGame @@ -3795,10 +3805,6 @@ - precise + switch + toggle hedgehog tags - - - high jump (twice) @@ -3806,6 +3812,10 @@ precise + screenshot + + precise + switch + toggle team bars + + binds (descriptions) @@ -4834,6 +4844,10 @@ Project founder + + Hungarian + + server diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/hedgewars_fi.ts --- a/share/hedgewars/Data/Locale/hedgewars_fi.ts Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/hedgewars_fi.ts Sun Jul 05 14:53:44 2020 +0200 @@ -340,6 +340,7 @@ Scheme '%1' not supported + Here, “scheme” refers to the scheme of a Uniform Resource Identifier” Säännöt '%1' ei ole tuettu @@ -555,7 +556,7 @@ Your nickname is not registered. To prevent someone else from using it, please register it at www.hedgewars.org - Nimimerkkiäsi ei ole rekisteröity. + Nimimerkkiäsi ei ole rekisteröity. Estääksesi muita käyttämästä sitä, voit rekisteröidä sen osoitteessa hedgewars.org @@ -637,6 +638,13 @@ Internal error: Reply object is invalid. + + Your nickname is not registered. +To be able to rejoin games in progress and +prevent someone else from using your nickname, +please register it at www.hedgewars.org. + + HWGame @@ -1468,7 +1476,7 @@ Save - Tallenna + Tallenna (%1 %2) @@ -1519,6 +1527,14 @@ + + Save demo + + + + Save demo (unavailable because the /lua command was used) + + PageInGame @@ -3867,10 +3883,6 @@ - precise + switch + toggle hedgehog tags - - - high jump (twice) @@ -3878,6 +3890,10 @@ precise + screenshot + + precise + switch + toggle team bars + + binds (descriptions) @@ -4902,6 +4918,10 @@ Project founder + + Hungarian + + server diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/hedgewars_fr.ts --- a/share/hedgewars/Data/Locale/hedgewars_fr.ts Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/hedgewars_fr.ts Sun Jul 05 14:53:44 2020 +0200 @@ -360,6 +360,7 @@ Scheme '%1' not supported + Here, “scheme” refers to the scheme of a Uniform Resource Identifier” Règle %1 incomprise @@ -575,7 +576,7 @@ Your nickname is not registered. To prevent someone else from using it, please register it at www.hedgewars.org - Votre pseudo n'est pas enregistré. + Votre pseudo n'est pas enregistré. Pour éviter que d'autre joueurs l'utilisent, veuillez l'enregistrer sur www.hedgewars.org @@ -657,6 +658,13 @@ Internal error: Reply object is invalid. Erreur interne : L'objet de réponse est invalide. + + Your nickname is not registered. +To be able to rejoin games in progress and +prevent someone else from using your nickname, +please register it at www.hedgewars.org. + + HWGame @@ -1493,7 +1501,7 @@ Save - Enregistrer + Enregistrer (%1 %2) @@ -1544,6 +1552,14 @@ + + Save demo + + + + Save demo (unavailable because the /lua command was used) + + PageInGame @@ -4045,10 +4061,6 @@ - precise + switch + toggle hedgehog tags - - - high jump (twice) @@ -4056,6 +4068,10 @@ precise + screenshot + + precise + switch + toggle team bars + + binds (descriptions) @@ -5084,6 +5100,10 @@ Project founder + + Hungarian + + server diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/hedgewars_gd.ts --- a/share/hedgewars/Data/Locale/hedgewars_gd.ts Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/hedgewars_gd.ts Sun Jul 05 14:53:44 2020 +0200 @@ -352,6 +352,7 @@ Scheme '%1' not supported + Here, “scheme” refers to the scheme of a Uniform Resource Identifier” Cha chuir sinn taic ris ann sgeama “%1” @@ -571,7 +572,7 @@ Your nickname is not registered. To prevent someone else from using it, please register it at www.hedgewars.org - Chan eil d’ fhar-ainm clàraichte. + Chan eil d’ fhar-ainm clàraichte. ’S urrainn dhut a chlàradh air www.hedgewars.org ach nach cleachd duine eile e. @@ -665,6 +666,13 @@ Internal error: Reply object is invalid. Mearachd taobh a-staigh: Chan eil oibseact na freagairt dligheach. + + Your nickname is not registered. +To be able to rejoin games in progress and +prevent someone else from using your nickname, +please register it at www.hedgewars.org. + + HWGame @@ -1535,7 +1543,7 @@ Save - Sàbhail + Sàbhail (%1 %2) @@ -1598,6 +1606,14 @@ (%1 creat) + + Save demo + + + + Save demo (unavailable because the /lua command was used) + + PageInGame @@ -3961,7 +3977,7 @@ precise + switch + toggle hedgehog tags - amas pongail + dèan suidse + toglaich thagaichean gràineige + amas pongail + dèan suidse + toglaich thagaichean gràineige high jump (twice) @@ -3971,6 +3987,10 @@ precise + screenshot amas pongail + glacadh-sgrìn + + precise + switch + toggle team bars + + binds (descriptions) @@ -4995,6 +5015,10 @@ Project founder Stèidheadair a’ phròiseict + + Hungarian + + server diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/hedgewars_gl.ts --- a/share/hedgewars/Data/Locale/hedgewars_gl.ts Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/hedgewars_gl.ts Sun Jul 05 14:53:44 2020 +0200 @@ -336,6 +336,7 @@ Scheme '%1' not supported + Here, “scheme” refers to the scheme of a Uniform Resource Identifier” @@ -536,12 +537,6 @@ - Your nickname is not registered. -To prevent someone else from using it, -please register it at www.hedgewars.org - - - Your password wasn't saved either. @@ -607,6 +602,13 @@ Internal error: Reply object is invalid. + + Your nickname is not registered. +To be able to rejoin games in progress and +prevent someone else from using your nickname, +please register it at www.hedgewars.org. + + HWGame @@ -1381,10 +1383,6 @@ Play again - - Save - - (%1 %2) For custom number of points in the stats screen, written after the team name. %1 is the number, %2 is the word. Example: “4 points” @@ -1434,6 +1432,14 @@ + + Save demo + + + + Save demo (unavailable because the /lua command was used) + + PageInGame @@ -3720,10 +3726,6 @@ - precise + switch + toggle hedgehog tags - - - high jump (twice) @@ -3731,6 +3733,10 @@ precise + screenshot + + precise + switch + toggle team bars + + binds (descriptions) @@ -4759,6 +4765,10 @@ Project founder + + Hungarian + + server diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/hedgewars_hu.ts --- a/share/hedgewars/Data/Locale/hedgewars_hu.ts Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/hedgewars_hu.ts Sun Jul 05 14:53:44 2020 +0200 @@ -1,6 +1,6 @@ - + About @@ -330,6 +330,7 @@ Scheme '%1' not supported + Here, “scheme” refers to the scheme of a Uniform Resource Identifier” A(z) %1 séma nem támogatott @@ -541,7 +542,7 @@ Your nickname is not registered. To prevent someone else from using it, please register it at www.hedgewars.org - A beceneved nincs regisztrálva. + A beceneved nincs regisztrálva. Nehogy más is használja, kérjük, regisztráld a www.hedgewars.org címen @@ -615,6 +616,13 @@ Internal error: Reply object is invalid. Belső hiba: Érvénytelen válaszobjektum. + + Your nickname is not registered. +To be able to rejoin games in progress and +prevent someone else from using your nickname, +please register it at www.hedgewars.org. + + HWGame @@ -874,7 +882,7 @@ Connection refused - Kapcsolat visszautasítva + Kapcsolat visszautasítva Room destroyed @@ -882,7 +890,7 @@ Quit reason: - Kilépés oka: + Kilépés oka: You got kicked @@ -1407,7 +1415,7 @@ Save - Mentés + Mentés (%1 %2) @@ -1452,6 +1460,14 @@ (%1 csomag) + + Save demo + + + + Save demo (unavailable because the /lua command was used) + + PageInGame @@ -1891,7 +1907,7 @@ Land can not be destroyed! - A talajt nem lehet elpusztítani! + A talajt nem lehet elpusztítani! Lower gravity @@ -2886,7 +2902,7 @@ QMessageBox Connection to server is lost - A kapcsolat a szerverrel megszakadt + A kapcsolat a szerverrel megszakadt Error @@ -3160,7 +3176,7 @@ Specify - Beállítás + Beállítás Start @@ -3560,7 +3576,7 @@ pause - szünet + szünet confirmation @@ -3580,7 +3596,7 @@ capture - elfogás + elfogás quit @@ -3596,7 +3612,7 @@ reset zoom - nagyítás visszaállítása + nagyítás visszaállítása long jump @@ -3759,7 +3775,7 @@ precise + switch + toggle hedgehog tags - pontos célzás + váltás + süncímkék kapcsolása + pontos célzás + váltás + süncímkék kapcsolása high jump (twice) @@ -3769,6 +3785,10 @@ precise + screenshot pontos célzás + képernyőkép készítése + + precise + switch + toggle team bars + + binds (descriptions) @@ -3810,7 +3830,7 @@ Talk to your team or all participants: - Beszélgetés a csapattal vagy minden résztvevővel: + Beszélgetés a csapattal vagy minden résztvevővel: Pause, continue or leave your game: @@ -3830,7 +3850,7 @@ Toggle labels above hedgehogs: - Sünik feletti címkék beállítása: + Sünik feletti címkék beállítása: Record video: @@ -3861,31 +3881,31 @@ binds (keys) Axis - Tengely + Tengely (Up) - (Fel) + (Fel) (Down) - (Le) + (Le) Hat - Fejfedő + Fejfedő (Left) - (Balra) + (Balra) (Right) - (Jobbra) + (Jobbra) Button - Gomb + Gomb Keyboard @@ -3945,71 +3965,71 @@ Numpad 0 - Numerikus 0 + Numerikus 0 Numpad 1 - Numerikus 1 + Numerikus 1 Numpad 2 - Numerikus 2 + Numerikus 2 Numpad 3 - Numerikus 3 + Numerikus 3 Numpad 4 - Numerikus 4 + Numerikus 4 Numpad 5 - Numerikus 5 + Numerikus 5 Numpad 6 - Numerikus 6 + Numerikus 6 Numpad 7 - Numerikus 7 + Numerikus 7 Numpad 8 - Numerikus 8 + Numerikus 8 Numpad 9 - Numerikus 9 + Numerikus 9 Numpad . - Numerikus . + Numerikus . Numpad / - Numerikus / + Numerikus / Numpad * - Numerikus * + Numerikus * Numpad - - Numerikus - + Numerikus - Numpad + - Numerikus + + Numerikus + Enter - Enter + Enter Equals - Egyenlő + Egyenlő Up @@ -4041,55 +4061,55 @@ Page up - Page Up + Page Up Page down - Page Down + Page Down Num lock - Num Lock + Num Lock Caps lock - Caps Lock + Caps Lock Scroll lock - Scroll Lock + Scroll Lock Right shift - Jobb oldali Shift + Jobb oldali Shift Left shift - Bal oldali Shift + Bal oldali Shift Right ctrl - Jobb oldali Ctrl + Jobb oldali Ctrl Left ctrl - Bal oldali Ctrl + Bal oldali Ctrl Right alt - Jobb oldali Alt + Jobb oldali Alt Left alt - Bal oldali Alt + Bal oldali Alt Right meta - Jobb oldali Meta + Jobb oldali Meta Left meta - Bal oldali Meta + Bal oldali Meta A button @@ -4173,7 +4193,7 @@ DPad - DPad + DPad D-pad @@ -4797,6 +4817,10 @@ Project founder Projektalapító + + Hungarian + + server diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/hedgewars_it.ts --- a/share/hedgewars/Data/Locale/hedgewars_it.ts Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/hedgewars_it.ts Sun Jul 05 14:53:44 2020 +0200 @@ -348,6 +348,7 @@ Scheme '%1' not supported + Here, “scheme” refers to the scheme of a Uniform Resource Identifier” Schema '%1' non supportato @@ -563,7 +564,7 @@ Your nickname is not registered. To prevent someone else from using it, please register it at www.hedgewars.org - Il tuo nome non è registrato. + Il tuo nome non è registrato. Per evitare che qualcun altro lo usi, per favore registralo su www.hedgewars.org @@ -645,6 +646,13 @@ Internal error: Reply object is invalid. Errore interno: Oggetto di risposta non valido. + + Your nickname is not registered. +To be able to rejoin games in progress and +prevent someone else from using your nickname, +please register it at www.hedgewars.org. + + HWGame @@ -1495,7 +1503,7 @@ Save - Salva + Salva (%1 %2) @@ -1546,6 +1554,14 @@ + + Save demo + + + + Save demo (unavailable because the /lua command was used) + + PageInGame @@ -3940,7 +3956,7 @@ precise + switch + toggle hedgehog tags - Shift + cambia riccio + attiva tags del riccio + Shift + cambia riccio + attiva tags del riccio high jump (twice) @@ -3950,6 +3966,10 @@ precise + screenshot Shift + screenshot + + precise + switch + toggle team bars + + binds (descriptions) @@ -4974,6 +4994,10 @@ Project founder Fondatore del progetto + + Hungarian + + server diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/hedgewars_ja.ts --- a/share/hedgewars/Data/Locale/hedgewars_ja.ts Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/hedgewars_ja.ts Sun Jul 05 14:53:44 2020 +0200 @@ -326,6 +326,7 @@ Scheme '%1' not supported + Here, “scheme” refers to the scheme of a Uniform Resource Identifier” スキーム「%1」はサポートされていません @@ -525,7 +526,7 @@ Your nickname is not registered. To prevent someone else from using it, please register it at www.hedgewars.org - 指定されたニックネームは登録されていません。 + 指定されたニックネームは登録されていません。 他のプレーヤーからの使用を防ぐためには, 「www.hedgewars.org」をアクセスして登録してください。 @@ -615,6 +616,13 @@ Internal error: Reply object is invalid. 内部エラー:返事オブジェクトは無効です。 + + Your nickname is not registered. +To be able to rejoin games in progress and +prevent someone else from using your nickname, +please register it at www.hedgewars.org. + + HWGame @@ -1394,7 +1402,7 @@ Save - セーブ + セーブ (%1 %2) @@ -1439,6 +1447,14 @@ + + Save demo + + + + Save demo (unavailable because the /lua command was used) + + PageInGame @@ -3674,10 +3690,6 @@ - precise + switch + toggle hedgehog tags - - - high jump (twice) @@ -3685,6 +3697,10 @@ precise + screenshot + + precise + switch + toggle team bars + + binds (descriptions) @@ -4705,6 +4721,10 @@ Project founder + + Hungarian + + server diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/hedgewars_ko.ts --- a/share/hedgewars/Data/Locale/hedgewars_ko.ts Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/hedgewars_ko.ts Sun Jul 05 14:53:44 2020 +0200 @@ -326,6 +326,7 @@ Scheme '%1' not supported + Here, “scheme” refers to the scheme of a Uniform Resource Identifier” @@ -526,12 +527,6 @@ - Your nickname is not registered. -To prevent someone else from using it, -please register it at www.hedgewars.org - - - Your password wasn't saved either. @@ -597,6 +592,13 @@ Internal error: Reply object is invalid. + + Your nickname is not registered. +To be able to rejoin games in progress and +prevent someone else from using your nickname, +please register it at www.hedgewars.org. + + HWGame @@ -1346,10 +1348,6 @@ Play again - - Save - - (%1 %2) For custom number of points in the stats screen, written after the team name. %1 is the number, %2 is the word. Example: “4 points” @@ -1393,6 +1391,14 @@ + + Save demo + + + + Save demo (unavailable because the /lua command was used) + + PageInGame @@ -3580,10 +3586,6 @@ - precise + switch + toggle hedgehog tags - - - high jump (twice) @@ -3591,6 +3593,10 @@ precise + screenshot + + precise + switch + toggle team bars + + binds (descriptions) @@ -4455,6 +4461,10 @@ Project founder + + Hungarian + + server diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/hedgewars_lt.ts --- a/share/hedgewars/Data/Locale/hedgewars_lt.ts Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/hedgewars_lt.ts Sun Jul 05 14:53:44 2020 +0200 @@ -330,22 +330,22 @@ GameSchemeModel - + New - + New (%1) - + Copy of %1 - + Copy of %1 (%2) @@ -353,7 +353,7 @@ GameUIConfig - + Guest @@ -411,8 +411,9 @@ - + Scheme '%1' not supported + Here, “scheme” refers to the scheme of a Uniform Resource Identifier” @@ -505,82 +506,82 @@ HWChatWidget - + Chat log - + Enter chat messages here and send them with [Enter] - + List of players - + %1 has joined - + %1 has left - + %1 has left (%2) - + %1 has been removed from your ignore list - + %1 has been added to your ignore list - + %1 has been removed from your friends list - + %1 has been added to your friends list + + Stylesheet imported from %1 + + + - Stylesheet imported from %1 - - - - Enter %1 if you want to use the current StyleSheet in future, enter %2 to reset! - + Couldn't read %1 - + StyleSheet discarded - + StyleSheet saved to %1 - + Failed to save StyleSheet to %1 @@ -588,56 +589,64 @@ HWForm - + Game aborted - + + Your nickname is not registered. +To be able to rejoin games in progress and +prevent someone else from using your nickname, +please register it at www.hedgewars.org. + + + + Nickname - - + + No nickname supplied. - + Someone already uses your nickname %1 on the server. Please pick another nickname: - + Team 1 - + %1's Team - + Team %1 Default team name - + Computer %1 Default computer team name - + Hedgewars - Nick registered - + This nick is registered, and you haven't specified a password. If this nick isn't yours, please register your own nick at www.hedgewars.org @@ -646,102 +655,95 @@ - - Your nickname is not registered. -To prevent someone else from using it, -please register it at www.hedgewars.org - - - - + Your password wasn't saved either. - - + + Hedgewars - Empty nickname - + Hedgewars - Wrong password - + You entered a wrong password. - + Room password - + The room is protected with password. Please, enter the password: - + Try Again - + Hedgewars - Connection error - + You reconnected too fast. Please wait a few seconds and try again. - - + + Cannot save record to file %1 - + Hedgewars Demo File File Types - + Hedgewars Save File File Types - + Demo name - + Demo name: - + Unknown network error (possibly missing SSL library). - + This feature requires an Internet connection, but you don't appear to be online (error code: %1). - + Internal error: Reply object is invalid. @@ -749,7 +751,7 @@ HWGame - + A fatal ERROR occured! The game engine had to stop. We are very sorry for the inconvenience. :-( @@ -761,14 +763,14 @@ - + en.txt IMPORTANT: This text has a special meaning, do not translate it directly. This is the file name of translation files for the game engine, found in Data/Locale/. Usually, you replace “en” with the ISO-639-1 language code of your language. lt.txt - + Cannot open demofile %1 @@ -1110,7 +1112,7 @@ - + Reason: @@ -1346,17 +1348,17 @@ - + Refresh - + Add - + Remove @@ -1564,43 +1566,43 @@ - + Randomize the team name - + Randomize the grave - + Randomize the flag - + Play a random example of this voice - + Randomize the voice - + Randomize the fort - + CPU %1 Name of a flag for computer-controlled enemies. %1 is replaced with the computer level - + %1 (%2) @@ -1614,7 +1616,7 @@ - + Health graph @@ -1629,12 +1631,17 @@ - - Save + + Save demo + + + + + Save demo (unavailable because the /lua command was used) - + The best shot award was won by <b>%1</b> with <b>%2</b> pts. @@ -1643,7 +1650,7 @@ - + The best killer is <b>%1</b> with <b>%2</b> kills in a turn. @@ -1652,7 +1659,7 @@ - + A total of <b>%1</b> hedgehog(s) were killed during this round. @@ -1661,7 +1668,7 @@ - + (%1 kill) Number of kills in stats screen, written after the team name @@ -1671,7 +1678,7 @@ - + (%1 point(s)) Number of points in stats screen, written after the team name @@ -1681,8 +1688,8 @@ - - + + (%L1 second(s)) Time in seconds @@ -1692,7 +1699,7 @@ - + (%1 crate(s)) @@ -1701,7 +1708,7 @@ - + (%1 %2) For custom number of points in the stats screen, written after the team name. %1 is the number, %2 is the word. Example: “4 points” @@ -1711,7 +1718,7 @@ - + <b>%1</b> thought it's good to shoot their own hedgehogs for <b>%2</b> pts. @@ -1720,7 +1727,7 @@ - + <b>%1</b> killed <b>%2</b> of their own hedgehogs. @@ -1729,7 +1736,7 @@ - + <b>%1</b> was scared and skipped turn <b>%2</b> times. @@ -1738,7 +1745,7 @@ - + With everyone having the same clan color, there was no reason to fight. And so the hedgehogs happily lived in peace ever after. @@ -1798,6 +1805,7 @@ + Feedback @@ -2054,125 +2062,125 @@ - - + + x Multiplication sign, to be used between two numbers. Note the “x” is only a dummy character, we recommend to use “×” if your language permits it - + Frontend - + Custom colors - + Reset to default colors - + Game audio - + Frontend audio - + Account - + Proxy settings + + Proxy host + + + - Proxy host + Proxy port - Proxy port + Proxy login - Proxy login - - - - Proxy password + + No proxy + + + - No proxy + System proxy settings - System proxy settings + Socks5 proxy - Socks5 proxy - - - - HTTP proxy - + Miscellaneous - + MISSING LANGUAGE NAME [%1] In the case of an error, this is shown in the language selection for a language with unknown name. %1 = language code - + Updates + + Check for updates + + + - Check for updates - - - - Check now - + Video recording options - + Can't delete last team - + You can't delete the last team! @@ -2570,12 +2578,12 @@ PageTraining - + Pick the training to play - + Pick the challenge to play @@ -2585,66 +2593,66 @@ - + Trainings - - Challenges - - - + Challenges + + + + Scenarios - + Team - - + + Start fighting - + No description available - + Team highscore: %1 Highest score of a team - + Team lowscore: %1 Lowest score of a team - + Team's top accuracy: %1% Best accuracy of a team (in a challenge) - + Team's best time: %L1 s - + Team's longest time: %L1 s - + Select a mission! @@ -2662,7 +2670,7 @@ - + %1 bytes @@ -2671,34 +2679,34 @@ - + %1% Video encoding progress. %1 = number - + (in progress...) - + Date: %1 - + Size: %1 - + %1 (%2%) - %3 Video encoding list entry. %1 = file name, %2 = percent complete, %3 = video operation type (e.g. “encoding”) - + encoding @@ -2706,49 +2714,49 @@ QAction - + Info - + Kick - + Ban - + Delegate room control - + Follow - - + + Ignore - - + + Add friend - + Unignore - + Remove friend @@ -2791,146 +2799,146 @@ QCheckBox - + Show ammo menu tooltips - + Alternative damage show + + Team + + + - Team - - - - Enable team tags by default + + Hog + + + - Hog - - - - Enable hedgehog tags by default + + Health + + + - Health - - - - Enable health tags by default + + Translucent + + + - Translucent - - - - Enable translucent tags by default + + Visual effects + + + - Visual effects - - - - Enable visual effects such as animated menu transitions and falling stars + + + Sound + + + - - Sound - - - - In-game sound effects + + + Music + + + - - Music - - - - In-game music + + Dampen when losing focus + Checkbox text. If checked, the in-game audio volume is reduced (=dampened) when the game window loses its focus + + + - Dampen when losing focus - Checkbox text. If checked, the in-game audio volume is reduced (=dampened) when the game window loses its focus - - - - Reduce the game audio volume if the game window has lost its focus - + Frontend sound effects - + Frontend music + + Append date and time to record file name + + + - Append date and time to record file name - - - - If enabled, Hedgewars adds the date and time in the form "YYYY-MM-DD_hh-mm" for automatically created demos. - + Check for updates at startup - + Fullscreen - + Show FPS - + Save password - + Record audio - + Use game resolution @@ -2948,117 +2956,117 @@ - + Community - + (System default) + + Disabled + + + + + Stereoscopy creates an illusion of depth when you wear 3D glasses. + + + - Disabled - - - - - Stereoscopy creates an illusion of depth when you wear 3D glasses. + Red/Cyan - Red/Cyan + Cyan/Red - Cyan/Red + Red/Blue - Red/Blue + Blue/Red - Blue/Red + Red/Green - Red/Green - - - - Green/Red + + Side-by-side + + + - Side-by-side - - - - Top-Bottom + + 24 FPS + + + - 24 FPS + 25 FPS - 25 FPS + 30 FPS - 30 FPS + 50 FPS - 50 FPS - - - - 60 FPS + + Red/Cyan grayscale + + + - Red/Cyan grayscale + Cyan/Red grayscale - Cyan/Red grayscale + Red/Blue grayscale - Red/Blue grayscale + Blue/Red grayscale - Blue/Red grayscale + Red/Green grayscale - Red/Green grayscale - - - - Green/Red grayscale @@ -3076,7 +3084,7 @@ - + Fort @@ -3106,7 +3114,7 @@ - + Description @@ -3185,38 +3193,38 @@ - + Locale - + Nickname - + Stereoscopy - + This setting will be effective at next restart. - + Resolution - + Bitrate (Kibit/s) “Kibit/s” is the symbol for 1024 bits per second - + Quality @@ -3236,22 +3244,22 @@ - + Zoom (%) - + Displayed tags above hogs and translucent tags - + Initial sound volume - + FPS limit @@ -3389,22 +3397,22 @@ - + Format - + Audio codec - + Video codec - + Framerate @@ -3412,22 +3420,22 @@ QLineEdit - + unnamed - + unnamed (%1) - + hedgehog %1 - + anonymous @@ -3448,69 +3456,69 @@ QMessageBox - + Teams - Are you sure? - + Do you really want to delete the team '%1'? - - Teams - Name already taken - - - + Teams - Name already taken + + + + The team name '%1' is already taken, so your team has been renamed to '%2'. - + Cannot delete default scheme '%1'! - + Please select a record from the list - + Hedgewars - Nick not registered - + Unable to start server - + The connection to the server is lost. - + Server redirection - + This server supports secure connections on port %1. Would you like to reconnect securely? - + Not all players are ready - + Are you sure you want to start this game? Not all players are ready. @@ -3543,18 +3551,18 @@ - + Hedgewars - Success - + All file associations have been set - + File association failed. @@ -3646,18 +3654,18 @@ - - + + Videos - Are you sure? - + Do you really want to delete the video '%1'? - + Do you really want to remove %1 file(s)? @@ -3760,7 +3768,7 @@ - + Cancel @@ -3807,7 +3815,7 @@ - + Start @@ -3817,7 +3825,7 @@ - + Associate file extensions @@ -3833,8 +3841,8 @@ - - + + Delete @@ -3849,37 +3857,37 @@ - + Set default options - + Restore default coding parameters - + Open videos directory - + Open the video directory in your system - - Play - - - + Play + + + + Play this video - + Delete this video @@ -3887,7 +3895,7 @@ QSpinBox - + Specify the bitrate of recorded videos as a multiple of 1024 bits per second @@ -4493,7 +4501,7 @@ - precise + switch + toggle hedgehog tags + precise + switch + toggle team bars @@ -5508,71 +5516,76 @@ - Italian + Hungarian - Japanese + Italian - Korean + Japanese - Lithuanian + Korean - Polish + Lithuanian - Portuguese + Polish - Russian + Portuguese - Scottish Gaelic + Russian - Slovak + Scottish Gaelic - Spanish + Slovak - Swedish + Spanish - Ukrainian + Swedish - Special thanks + Ukrainian + Special thanks + + + + Project founder @@ -5616,7 +5629,7 @@ - + Unknown command or invalid parameters. Say '/help' in chat for a list of commands. diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/hedgewars_ms.ts --- a/share/hedgewars/Data/Locale/hedgewars_ms.ts Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/hedgewars_ms.ts Sun Jul 05 14:53:44 2020 +0200 @@ -328,22 +328,22 @@ GameSchemeModel - + New - + New (%1) - + Copy of %1 - + Copy of %1 (%2) @@ -351,7 +351,7 @@ GameUIConfig - + Guest @@ -399,8 +399,9 @@ - + Scheme '%1' not supported + Here, “scheme” refers to the scheme of a Uniform Resource Identifier” @@ -493,82 +494,82 @@ HWChatWidget - + Chat log - + Enter chat messages here and send them with [Enter] - + List of players - + %1 has joined - + %1 has left - + %1 has left (%2) - + %1 has been removed from your ignore list - + %1 has been added to your ignore list - + %1 has been removed from your friends list - + %1 has been added to your friends list + + Stylesheet imported from %1 + + + - Stylesheet imported from %1 - - - - Enter %1 if you want to use the current StyleSheet in future, enter %2 to reset! - + Couldn't read %1 - + StyleSheet discarded - + StyleSheet saved to %1 - + Failed to save StyleSheet to %1 @@ -576,39 +577,39 @@ HWForm - + Team 1 - + %1's Team - + Team %1 Default team name - + Computer %1 Default computer team name - + Game aborted - + Hedgewars - Nick registered - + This nick is registered, and you haven't specified a password. If this nick isn't yours, please register your own nick at www.hedgewars.org @@ -617,119 +618,120 @@ - + Your nickname is not registered. -To prevent someone else from using it, -please register it at www.hedgewars.org - - - - +To be able to rejoin games in progress and +prevent someone else from using your nickname, +please register it at www.hedgewars.org. + + + + Your password wasn't saved either. - + Nickname - + Someone already uses your nickname %1 on the server. Please pick another nickname: - - + + No nickname supplied. - - + + Hedgewars - Empty nickname - + Hedgewars - Wrong password - + You entered a wrong password. - + Room password - + The room is protected with password. Please, enter the password: - + Try Again - + Hedgewars - Connection error - + You reconnected too fast. Please wait a few seconds and try again. - - + + Cannot save record to file %1 - + Hedgewars Demo File File Types - + Hedgewars Save File File Types - + Demo name - + Demo name: - + Unknown network error (possibly missing SSL library). - + This feature requires an Internet connection, but you don't appear to be online (error code: %1). - + Internal error: Reply object is invalid. @@ -737,7 +739,7 @@ HWGame - + A fatal ERROR occured! The game engine had to stop. We are very sorry for the inconvenience. :-( @@ -749,14 +751,14 @@ - + en.txt IMPORTANT: This text has a special meaning, do not translate it directly. This is the file name of translation files for the game engine, found in Data/Locale/. Usually, you replace “en” with the ISO-639-1 language code of your language. ms.txt - + Cannot open demofile %1 @@ -1098,7 +1100,7 @@ - + Reason: @@ -1332,17 +1334,17 @@ - + Refresh - + Add - + Remove @@ -1550,43 +1552,43 @@ - + Randomize the team name - + Randomize the grave - + Randomize the flag - + Play a random example of this voice - + Randomize the voice - + Randomize the fort - + CPU %1 Name of a flag for computer-controlled enemies. %1 is replaced with the computer level - + %1 (%2) @@ -1600,7 +1602,7 @@ - + Health graph @@ -1615,33 +1617,38 @@ - - Save + + Save demo + + + + + Save demo (unavailable because the /lua command was used) - + The best shot award was won by <b>%1</b> with <b>%2</b> pts. - + The best killer is <b>%1</b> with <b>%2</b> kills in a turn. - + A total of <b>%1</b> hedgehog(s) were killed during this round. - + (%1 kill) Number of kills in stats screen, written after the team name @@ -1649,7 +1656,7 @@ - + (%1 point(s)) Number of points in stats screen, written after the team name @@ -1657,8 +1664,8 @@ - - + + (%L1 second(s)) Time in seconds @@ -1666,14 +1673,14 @@ - + (%1 crate(s)) - + (%1 %2) For custom number of points in the stats screen, written after the team name. %1 is the number, %2 is the word. Example: “4 points” @@ -1681,28 +1688,28 @@ - + <b>%1</b> thought it's good to shoot their own hedgehogs for <b>%2</b> pts. - + <b>%1</b> killed <b>%2</b> of their own hedgehogs. - + <b>%1</b> was scared and skipped turn <b>%2</b> times. - + With everyone having the same clan color, there was no reason to fight. And so the hedgehogs happily lived in peace ever after. @@ -1762,6 +1769,7 @@ + Feedback @@ -2018,125 +2026,125 @@ - - + + x Multiplication sign, to be used between two numbers. Note the “x” is only a dummy character, we recommend to use “×” if your language permits it - + Frontend - + Custom colors - + Reset to default colors - + Game audio - + Frontend audio - + Account - + Proxy settings + + Proxy host + + + - Proxy host + Proxy port - Proxy port + Proxy login - Proxy login - - - - Proxy password + + No proxy + + + - No proxy + System proxy settings - System proxy settings + Socks5 proxy - Socks5 proxy - - - - HTTP proxy - + Miscellaneous - + MISSING LANGUAGE NAME [%1] In the case of an error, this is shown in the language selection for a language with unknown name. %1 = language code - + Updates + + Check for updates + + + - Check for updates - - - - Check now - + Video recording options - + Can't delete last team - + You can't delete the last team! @@ -2532,12 +2540,12 @@ PageTraining - + Pick the training to play - + Pick the challenge to play @@ -2547,66 +2555,66 @@ - + Trainings - - Challenges - - - + Challenges + + + + Scenarios - + Team - - + + Start fighting - + No description available - + Team highscore: %1 Highest score of a team - + Team lowscore: %1 Lowest score of a team - + Team's top accuracy: %1% Best accuracy of a team (in a challenge) - + Team's best time: %L1 s - + Team's longest time: %L1 s - + Select a mission! @@ -2624,41 +2632,41 @@ - + %1 bytes - + %1% Video encoding progress. %1 = number - + (in progress...) - + Date: %1 - + Size: %1 - + %1 (%2%) - %3 Video encoding list entry. %1 = file name, %2 = percent complete, %3 = video operation type (e.g. “encoding”) - + encoding @@ -2681,49 +2689,49 @@ - + Info - + Kick - + Ban - + Delegate room control - + Follow - - + + Ignore - - + + Add friend - + Unignore - + Remove friend @@ -2752,145 +2760,145 @@ QCheckBox - + Save password - + Check for updates at startup - + Fullscreen - + Alternative damage show - + Show FPS - + Show ammo menu tooltips + + Team + + + - Team - - - - Enable team tags by default + + Hog + + + - Hog - - - - Enable hedgehog tags by default + + Health + + + - Health - - - - Enable health tags by default + + Translucent + + + - Translucent - - - - Enable translucent tags by default + + Visual effects + + + - Visual effects - - - - Enable visual effects such as animated menu transitions and falling stars + + + Sound + + + - - Sound - - - - In-game sound effects + + + Music + + + - - Music - - - - In-game music + + Dampen when losing focus + Checkbox text. If checked, the in-game audio volume is reduced (=dampened) when the game window loses its focus + + + - Dampen when losing focus - Checkbox text. If checked, the in-game audio volume is reduced (=dampened) when the game window loses its focus - - - - Reduce the game audio volume if the game window has lost its focus - + Frontend sound effects - + Frontend music + + Append date and time to record file name + + + - Append date and time to record file name - - - - If enabled, Hedgewars adds the date and time in the form "YYYY-MM-DD_hh-mm" for automatically created demos. - + Record audio - + Use game resolution @@ -2908,117 +2916,117 @@ - + Community - + (System default) + + Disabled + + + + + Stereoscopy creates an illusion of depth when you wear 3D glasses. + + + - Disabled - - - - - Stereoscopy creates an illusion of depth when you wear 3D glasses. + Red/Cyan - Red/Cyan + Cyan/Red - Cyan/Red + Red/Blue - Red/Blue + Blue/Red - Blue/Red + Red/Green - Red/Green - - - - Green/Red + + Side-by-side + + + - Side-by-side - - - - Top-Bottom + + 24 FPS + + + - 24 FPS + 25 FPS - 25 FPS + 30 FPS - 30 FPS + 50 FPS - 50 FPS - - - - 60 FPS + + Red/Cyan grayscale + + + - Red/Cyan grayscale + Cyan/Red grayscale - Cyan/Red grayscale + Red/Blue grayscale - Red/Blue grayscale + Blue/Red grayscale - Blue/Red grayscale + Red/Green grayscale - Red/Green grayscale - - - - Green/Red grayscale @@ -3036,7 +3044,7 @@ - + Fort @@ -3061,7 +3069,7 @@ - + Description @@ -3172,38 +3180,38 @@ - + Locale - + Nickname - + Stereoscopy - + This setting will be effective at next restart. - + Resolution - + Bitrate (Kibit/s) “Kibit/s” is the symbol for 1024 bits per second - + Quality @@ -3223,22 +3231,22 @@ - + Zoom (%) - + Displayed tags above hogs and translucent tags - + Initial sound volume - + FPS limit @@ -3339,22 +3347,22 @@ - + Format - + Audio codec - + Video codec - + Framerate @@ -3372,22 +3380,22 @@ QLineEdit - + unnamed - + unnamed (%1) - + hedgehog %1 - + anonymous @@ -3408,69 +3416,69 @@ QMessageBox - + Teams - Are you sure? - + Do you really want to delete the team '%1'? - - Teams - Name already taken - - - + Teams - Name already taken + + + + The team name '%1' is already taken, so your team has been renamed to '%2'. - + Cannot delete default scheme '%1'! - + Please select a record from the list - + Hedgewars - Nick not registered - + Unable to start server - + The connection to the server is lost. - + Server redirection - + This server supports secure connections on port %1. Would you like to reconnect securely? - + Not all players are ready - + Are you sure you want to start this game? Not all players are ready. @@ -3503,18 +3511,18 @@ - + Hedgewars - Success - + All file associations have been set - + File association failed. @@ -3581,18 +3589,18 @@ - - + + Videos - Are you sure? - + Do you really want to delete the video '%1'? - + Do you really want to remove %1 file(s)? @@ -3723,7 +3731,7 @@ - + Cancel @@ -3770,7 +3778,7 @@ - + Start @@ -3780,7 +3788,7 @@ - + Associate file extensions @@ -3796,8 +3804,8 @@ - - + + Delete @@ -3807,37 +3815,37 @@ - + Set default options - + Restore default coding parameters - + Open videos directory - + Open the video directory in your system - - Play - - - + Play + + + + Play this video - + Delete this video @@ -3845,7 +3853,7 @@ QSpinBox - + Specify the bitrate of recorded videos as a multiple of 1024 bits per second @@ -4451,7 +4459,7 @@ - precise + switch + toggle hedgehog tags + precise + switch + toggle team bars @@ -5466,71 +5474,76 @@ - Italian + Hungarian - Japanese + Italian - Korean + Japanese - Lithuanian + Korean - Polish + Lithuanian - Portuguese + Polish - Russian + Portuguese - Scottish Gaelic + Russian - Slovak + Scottish Gaelic - Spanish + Slovak - Swedish + Spanish - Ukrainian + Swedish - Special thanks + Ukrainian + Special thanks + + + + Project founder @@ -5574,7 +5587,7 @@ - + Unknown command or invalid parameters. Say '/help' in chat for a list of commands. diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/hedgewars_nl.ts --- a/share/hedgewars/Data/Locale/hedgewars_nl.ts Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/hedgewars_nl.ts Sun Jul 05 14:53:44 2020 +0200 @@ -332,6 +332,7 @@ Scheme '%1' not supported + Here, “scheme” refers to the scheme of a Uniform Resource Identifier” @@ -532,12 +533,6 @@ - Your nickname is not registered. -To prevent someone else from using it, -please register it at www.hedgewars.org - - - Your password wasn't saved either. @@ -603,6 +598,13 @@ Internal error: Reply object is invalid. + + Your nickname is not registered. +To be able to rejoin games in progress and +prevent someone else from using your nickname, +please register it at www.hedgewars.org. + + HWGame @@ -1358,10 +1360,6 @@ Play again - - Save - - (%1 %2) For custom number of points in the stats screen, written after the team name. %1 is the number, %2 is the word. Example: “4 points” @@ -1411,6 +1409,14 @@ + + Save demo + + + + Save demo (unavailable because the /lua command was used) + + PageInGame @@ -3601,10 +3607,6 @@ - precise + switch + toggle hedgehog tags - - - high jump (twice) @@ -3612,6 +3614,10 @@ precise + screenshot + + precise + switch + toggle team bars + + binds (descriptions) @@ -4476,6 +4482,10 @@ Project founder + + Hungarian + + server diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/hedgewars_pl.ts --- a/share/hedgewars/Data/Locale/hedgewars_pl.ts Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/hedgewars_pl.ts Sun Jul 05 14:53:44 2020 +0200 @@ -362,6 +362,7 @@ Scheme '%1' not supported + Here, “scheme” refers to the scheme of a Uniform Resource Identifier” Shemat '%1' nie jest wspierany @@ -581,7 +582,7 @@ Your nickname is not registered. To prevent someone else from using it, please register it at www.hedgewars.org - Twój nick nie jest zarejestrowany. + Twój nick nie jest zarejestrowany. By zapobiec używania go przez kogoś innego zarejestruj go na www.hedgewars.org @@ -681,6 +682,13 @@ Twoje hasło nie zostało zapisane. + + Your nickname is not registered. +To be able to rejoin games in progress and +prevent someone else from using your nickname, +please register it at www.hedgewars.org. + + HWGame @@ -1576,7 +1584,7 @@ Save - Zapisz + Zapisz (%1 %2) @@ -1633,6 +1641,14 @@ + + Save demo + + + + Save demo (unavailable because the /lua command was used) + + PageInGame @@ -4187,7 +4203,7 @@ precise + switch + toggle hedgehog tags - precyzja + zmiana + przełącz tagi jeży + precyzja + zmiana + przełącz tagi jeży high jump (twice) @@ -4197,6 +4213,10 @@ precise + screenshot precyzja + zrzut ekranu + + precise + switch + toggle team bars + + binds (descriptions) @@ -5225,6 +5245,10 @@ Project founder Fundator projektu + + Hungarian + + server diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/hedgewars_pt_BR.ts --- a/share/hedgewars/Data/Locale/hedgewars_pt_BR.ts Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/hedgewars_pt_BR.ts Sun Jul 05 14:53:44 2020 +0200 @@ -348,6 +348,7 @@ Scheme '%1' not supported + Here, “scheme” refers to the scheme of a Uniform Resource Identifier” O esquema "%1" não é suportado @@ -563,7 +564,7 @@ Your nickname is not registered. To prevent someone else from using it, please register it at www.hedgewars.org - Seu apelido não está registrado. + Seu apelido não está registrado. Para evitar de outra pessoa usá-lo, registre-o em www.hedgewars.org @@ -640,6 +641,13 @@ Internal error: Reply object is invalid. + + Your nickname is not registered. +To be able to rejoin games in progress and +prevent someone else from using your nickname, +please register it at www.hedgewars.org. + + HWGame @@ -1466,7 +1474,7 @@ Save - Salvar + Salvar (%1 %2) @@ -1517,6 +1525,14 @@ + + Save demo + + + + Save demo (unavailable because the /lua command was used) + + PageInGame @@ -3995,10 +4011,6 @@ - precise + switch + toggle hedgehog tags - - - high jump (twice) @@ -4006,6 +4018,10 @@ precise + screenshot + + precise + switch + toggle team bars + + binds (descriptions) @@ -5036,6 +5052,10 @@ Project founder + + Hungarian + + server diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/hedgewars_pt_PT.ts --- a/share/hedgewars/Data/Locale/hedgewars_pt_PT.ts Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/hedgewars_pt_PT.ts Sun Jul 05 14:53:44 2020 +0200 @@ -352,6 +352,7 @@ Scheme '%1' not supported + Here, “scheme” refers to the scheme of a Uniform Resource Identifier” Esquema '%1' não suportado @@ -567,7 +568,7 @@ Your nickname is not registered. To prevent someone else from using it, please register it at www.hedgewars.org - O teu nome de utilizador não está registado. + O teu nome de utilizador não está registado. De forma a prevenir que alguém o utilize, por favor regista-o em www.hedgewars.org @@ -644,6 +645,13 @@ Internal error: Reply object is invalid. + + Your nickname is not registered. +To be able to rejoin games in progress and +prevent someone else from using your nickname, +please register it at www.hedgewars.org. + + HWGame @@ -1474,7 +1482,7 @@ Save - Gravar + Gravar (%1 %2) @@ -1525,6 +1533,14 @@ + + Save demo + + + + Save demo (unavailable because the /lua command was used) + + PageInGame @@ -3999,10 +4015,6 @@ - precise + switch + toggle hedgehog tags - - - high jump (twice) @@ -4010,6 +4022,10 @@ precise + screenshot + + precise + switch + toggle team bars + + binds (descriptions) @@ -5038,6 +5054,10 @@ Project founder + + Hungarian + + server diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/hedgewars_ro.ts --- a/share/hedgewars/Data/Locale/hedgewars_ro.ts Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/hedgewars_ro.ts Sun Jul 05 14:53:44 2020 +0200 @@ -342,6 +342,7 @@ Scheme '%1' not supported + Here, “scheme” refers to the scheme of a Uniform Resource Identifier” @@ -546,12 +547,6 @@ - Your nickname is not registered. -To prevent someone else from using it, -please register it at www.hedgewars.org - - - Your password wasn't saved either. @@ -617,6 +612,13 @@ Internal error: Reply object is invalid. + + Your nickname is not registered. +To be able to rejoin games in progress and +prevent someone else from using your nickname, +please register it at www.hedgewars.org. + + HWGame @@ -1397,10 +1399,6 @@ Play again - - Save - - (%1 %2) For custom number of points in the stats screen, written after the team name. %1 is the number, %2 is the word. Example: “4 points” @@ -1456,6 +1454,14 @@ + + Save demo + + + + Save demo (unavailable because the /lua command was used) + + PageInGame @@ -3750,10 +3756,6 @@ - precise + switch + toggle hedgehog tags - - - high jump (twice) @@ -3761,6 +3763,10 @@ precise + screenshot + + precise + switch + toggle team bars + + binds (descriptions) @@ -4637,6 +4643,10 @@ Project founder + + Hungarian + + server diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/hedgewars_ru.ts --- a/share/hedgewars/Data/Locale/hedgewars_ru.ts Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/hedgewars_ru.ts Sun Jul 05 14:53:44 2020 +0200 @@ -354,6 +354,7 @@ Scheme '%1' not supported + Here, “scheme” refers to the scheme of a Uniform Resource Identifier” Схема "%1" не поддерживается @@ -565,7 +566,7 @@ Your nickname is not registered. To prevent someone else from using it, please register it at www.hedgewars.org - Ваше имя пользователя не зарегистрировано. + Ваше имя пользователя не зарегистрировано. Чтобы никто другой не воспользовался им, зарегистрируйте его на www.hedgewars.org @@ -637,6 +638,13 @@ Internal error: Reply object is invalid. Внутренняя ошибка: невалидный ответ + + Your nickname is not registered. +To be able to rejoin games in progress and +prevent someone else from using your nickname, +please register it at www.hedgewars.org. + + HWGame @@ -1446,7 +1454,7 @@ Save - Сохранить + Сохранить (%1 %2) @@ -1475,7 +1483,7 @@ With everyone having the same clan color, there was no reason to fight. And so the hedgehogs happily lived in peace ever after. - Когда все имеют один и тот же цвет клана, нет причины воевать. И так ёжики жили долго и и счастливо в мире и согласии. + Когда все имеют один и тот же цвет союза, нет причины воевать. И так ёжики жили долго и и счастливо в мире и согласии. (%1 point(s)) @@ -1503,6 +1511,14 @@ (%1 ящиков) + + Save demo + + + + Save demo (unavailable because the /lua command was used) + + PageInGame @@ -2056,7 +2072,7 @@ Teams in each clan take successive turns sharing their turn time. - Команды в каждом клане будут последовательно получать право хода, имея общее время на ход. + Команды в каждом союзе будут последовательно получать право хода, имея общее время на ход. Add an indestructible border around the terrain @@ -2084,7 +2100,7 @@ Each clan starts in its own part of the terrain. - Каждый клан стартует в своей части карты. + Каждый союз стартует в своей части карты. Overall damage and knockback in percent @@ -3745,7 +3761,7 @@ clan chat - чат клана + чат союза unselect weapon @@ -3855,7 +3871,7 @@ precise + switch + toggle hedgehog tags - точность + переключить + вкл ярлыки над ёжиками + точность + переключить + вкл ярлыки над ёжиками high jump (twice) @@ -3865,6 +3881,10 @@ precise + screenshot точность + снимок экрана + + precise + switch + toggle team bars + + binds (descriptions) @@ -3942,7 +3962,7 @@ Talk to your clan or all participants: - Общение с кланом или другими игроками: + Общение с союзом или другими игроками: @@ -4733,6 +4753,10 @@ Project founder Основатель проекта + + Hungarian + + server diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/hedgewars_sk.ts --- a/share/hedgewars/Data/Locale/hedgewars_sk.ts Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/hedgewars_sk.ts Sun Jul 05 14:53:44 2020 +0200 @@ -350,6 +350,7 @@ Scheme '%1' not supported + Here, “scheme” refers to the scheme of a Uniform Resource Identifier” Schéma '%1' nie je podporovaná @@ -561,12 +562,6 @@ Heslo: - Your nickname is not registered. -To prevent someone else from using it, -please register it at www.hedgewars.org - - - Your password wasn't saved either. @@ -637,6 +632,13 @@ Internal error: Reply object is invalid. Interná chyba: Objekt odpovede nie je platný. + + Your nickname is not registered. +To be able to rejoin games in progress and +prevent someone else from using your nickname, +please register it at www.hedgewars.org. + + HWGame @@ -1463,7 +1465,7 @@ Save - Uložiť + Uložiť (%1 %2) @@ -1520,6 +1522,14 @@ + + Save demo + + + + Save demo (unavailable because the /lua command was used) + + PageInGame @@ -3990,10 +4000,6 @@ - precise + switch + toggle hedgehog tags - - - high jump (twice) @@ -4001,6 +4007,10 @@ precise + screenshot + + precise + switch + toggle team bars + + binds (descriptions) @@ -5029,6 +5039,10 @@ Project founder + + Hungarian + + server diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/hedgewars_sv.ts --- a/share/hedgewars/Data/Locale/hedgewars_sv.ts Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/hedgewars_sv.ts Sun Jul 05 14:53:44 2020 +0200 @@ -344,6 +344,7 @@ Scheme '%1' not supported + Here, “scheme” refers to the scheme of a Uniform Resource Identifier” @@ -548,12 +549,6 @@ - Your nickname is not registered. -To prevent someone else from using it, -please register it at www.hedgewars.org - - - Your password wasn't saved either. @@ -619,6 +614,13 @@ Internal error: Reply object is invalid. + + Your nickname is not registered. +To be able to rejoin games in progress and +prevent someone else from using your nickname, +please register it at www.hedgewars.org. + + HWGame @@ -1409,7 +1411,7 @@ Save - Spara + Spara (%1 %2) @@ -1460,6 +1462,14 @@ + + Save demo + + + + Save demo (unavailable because the /lua command was used) + + PageInGame @@ -3795,10 +3805,6 @@ - precise + switch + toggle hedgehog tags - - - high jump (twice) @@ -3806,6 +3812,10 @@ precise + screenshot + + precise + switch + toggle team bars + + binds (descriptions) @@ -4834,6 +4844,10 @@ Project founder + + Hungarian + + server diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/hedgewars_tr_TR.ts --- a/share/hedgewars/Data/Locale/hedgewars_tr_TR.ts Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/hedgewars_tr_TR.ts Sun Jul 05 14:53:44 2020 +0200 @@ -354,6 +354,7 @@ Scheme '%1' not supported + Here, “scheme” refers to the scheme of a Uniform Resource Identifier” '%1' planı desteklenmiyor @@ -572,7 +573,7 @@ Your nickname is not registered. To prevent someone else from using it, please register it at www.hedgewars.org - Takma adın kayıtlı değil. + Takma adın kayıtlı değil. Başkasının kullanmaması için lütfen, www.hedgewars.org sitesinden kaydet. @@ -645,6 +646,13 @@ Internal error: Reply object is invalid. + + Your nickname is not registered. +To be able to rejoin games in progress and +prevent someone else from using your nickname, +please register it at www.hedgewars.org. + + HWGame @@ -1463,7 +1471,7 @@ Save - Kaydet + Kaydet (%1 %2) @@ -1508,6 +1516,14 @@ + + Save demo + + + + Save demo (unavailable because the /lua command was used) + + PageInGame @@ -3997,10 +4013,6 @@ - precise + switch + toggle hedgehog tags - - - high jump (twice) @@ -4008,6 +4020,10 @@ precise + screenshot + + precise + switch + toggle team bars + + binds (descriptions) @@ -5036,6 +5052,10 @@ Project founder + + Hungarian + + server diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/hedgewars_uk.ts --- a/share/hedgewars/Data/Locale/hedgewars_uk.ts Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/hedgewars_uk.ts Sun Jul 05 14:53:44 2020 +0200 @@ -358,6 +358,7 @@ Scheme '%1' not supported + Here, “scheme” refers to the scheme of a Uniform Resource Identifier” Схема '%1' не підтримується @@ -573,7 +574,7 @@ Your nickname is not registered. To prevent someone else from using it, please register it at www.hedgewars.org - Ваш нікнейм не зареєстрований. + Ваш нікнейм не зареєстрований. Щоб ніхто інший ним не користувався, зареєструйте його на www.hedgewars.org @@ -655,6 +656,13 @@ Internal error: Reply object is invalid. Внутрішня помилка: об'єкт відповіді недійсний. + + Your nickname is not registered. +To be able to rejoin games in progress and +prevent someone else from using your nickname, +please register it at www.hedgewars.org. + + HWGame @@ -1506,7 +1514,7 @@ Save - Зберегти + Зберегти (%1 %2) @@ -1563,6 +1571,14 @@ (%1 ящиків) + + Save demo + + + + Save demo (unavailable because the /lua command was used) + + PageInGame @@ -4030,7 +4046,7 @@ precise + switch + toggle hedgehog tags - приціл + переключення + перемкнути теги їжаків + приціл + переключення + перемкнути теги їжаків high jump (twice) @@ -4040,6 +4056,10 @@ precise + screenshot приціл + знімок + + precise + switch + toggle team bars + + binds (descriptions) @@ -5060,6 +5080,10 @@ Project founder Засновник проекту + + Hungarian + + server diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/hedgewars_zh_CN.ts --- a/share/hedgewars/Data/Locale/hedgewars_zh_CN.ts Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/hedgewars_zh_CN.ts Sun Jul 05 14:53:44 2020 +0200 @@ -332,22 +332,22 @@ - + New 新游戏 - + New (%1) - + Copy of %1 - + Copy of %1 (%2) @@ -355,7 +355,7 @@ GameUIConfig - + Guest @@ -403,8 +403,9 @@ - + Scheme '%1' not supported + Here, “scheme” refers to the scheme of a Uniform Resource Identifier” @@ -497,82 +498,82 @@ HWChatWidget - + Chat log - + Enter chat messages here and send them with [Enter] - + List of players - + %1 has joined - + %1 has left - + %1 has left (%2) - + %1 has been removed from your ignore list - + %1 has been added to your ignore list - + %1 has been removed from your friends list - + %1 has been added to your friends list + + Stylesheet imported from %1 + + + - Stylesheet imported from %1 - - - - Enter %1 if you want to use the current StyleSheet in future, enter %2 to reset! - + Couldn't read %1 - + StyleSheet discarded - + StyleSheet saved to %1 - + Failed to save StyleSheet to %1 @@ -580,39 +581,39 @@ HWForm - + Team 1 - + %1's Team - + Team %1 Default team name - + Computer %1 Default computer team name - + Game aborted - + Hedgewars - Nick registered - + This nick is registered, and you haven't specified a password. If this nick isn't yours, please register your own nick at www.hedgewars.org @@ -621,119 +622,120 @@ - + Your nickname is not registered. -To prevent someone else from using it, -please register it at www.hedgewars.org - - - - +To be able to rejoin games in progress and +prevent someone else from using your nickname, +please register it at www.hedgewars.org. + + + + Your password wasn't saved either. - + Nickname - + Someone already uses your nickname %1 on the server. Please pick another nickname: - - + + No nickname supplied. - - + + Hedgewars - Empty nickname - + Hedgewars - Wrong password - + You entered a wrong password. - + Room password - + The room is protected with password. Please, enter the password: - + Try Again - + Hedgewars - Connection error - + You reconnected too fast. Please wait a few seconds and try again. - + Hedgewars Demo File File Types - + Hedgewars Save File File Types - + Demo name - + Demo name: - + Unknown network error (possibly missing SSL library). - + This feature requires an Internet connection, but you don't appear to be online (error code: %1). - + Internal error: Reply object is invalid. - - + + Cannot save record to file %1 无法录入文件 %1 @@ -741,7 +743,7 @@ HWGame - + A fatal ERROR occured! The game engine had to stop. We are very sorry for the inconvenience. :-( @@ -753,14 +755,14 @@ - + en.txt IMPORTANT: This text has a special meaning, do not translate it directly. This is the file name of translation files for the game engine, found in Data/Locale/. Usually, you replace “en” with the ISO-639-1 language code of your language. zh_CN.txt - + Cannot open demofile %1 DEMO %1 打不开 @@ -1110,7 +1112,7 @@ 被踢出 - + Reason: @@ -1351,17 +1353,17 @@ - + Refresh - + Add - + Remove @@ -1569,43 +1571,43 @@ - + Randomize the team name - + Randomize the grave - + Randomize the flag - + Play a random example of this voice - + Randomize the voice - + Randomize the fort - + CPU %1 Name of a flag for computer-controlled enemies. %1 is replaced with the computer level - + %1 (%2) @@ -1619,7 +1621,7 @@ - + Health graph @@ -1634,33 +1636,42 @@ - Save - 保存 + 保存 + + + + Save demo + + + + + Save demo (unavailable because the /lua command was used) + - + The best shot award was won by <b>%1</b> with <b>%2</b> pts. - + The best killer is <b>%1</b> with <b>%2</b> kills in a turn. - + A total of <b>%1</b> hedgehog(s) were killed during this round. - + (%1 kill) Number of kills in stats screen, written after the team name @@ -1668,7 +1679,7 @@ - + (%1 point(s)) Number of points in stats screen, written after the team name @@ -1676,8 +1687,8 @@ - - + + (%L1 second(s)) Time in seconds @@ -1685,14 +1696,14 @@ - + (%1 crate(s)) - + (%1 %2) For custom number of points in the stats screen, written after the team name. %1 is the number, %2 is the word. Example: “4 points” @@ -1700,28 +1711,28 @@ - + <b>%1</b> thought it's good to shoot their own hedgehogs for <b>%2</b> pts. - + <b>%1</b> killed <b>%2</b> of their own hedgehogs. - + <b>%1</b> was scared and skipped turn <b>%2</b> times. - + With everyone having the same clan color, there was no reason to fight. And so the hedgehogs happily lived in peace ever after. @@ -1781,6 +1792,7 @@ + Feedback @@ -2041,125 +2053,125 @@ - - + + x Multiplication sign, to be used between two numbers. Note the “x” is only a dummy character, we recommend to use “×” if your language permits it - + Frontend - + Custom colors - + Reset to default colors - + Game audio - + Frontend audio - + Account - + Proxy settings + + Proxy host + + + - Proxy host + Proxy port - Proxy port + Proxy login - Proxy login - - - - Proxy password + + No proxy + + + - No proxy + System proxy settings - System proxy settings + Socks5 proxy - Socks5 proxy - - - - HTTP proxy - + Miscellaneous - + MISSING LANGUAGE NAME [%1] In the case of an error, this is shown in the language selection for a language with unknown name. %1 = language code - + Updates + + Check for updates + + + - Check for updates - - - - Check now - + Video recording options - + Can't delete last team - + You can't delete the last team! @@ -2563,12 +2575,12 @@ PageTraining - + Pick the training to play - + Pick the challenge to play @@ -2578,66 +2590,66 @@ - + Trainings - - Challenges - - - + Challenges + + + + Scenarios - + Team - - + + Start fighting - + No description available - + Team highscore: %1 Highest score of a team - + Team lowscore: %1 Lowest score of a team - + Team's top accuracy: %1% Best accuracy of a team (in a challenge) - + Team's best time: %L1 s - + Team's longest time: %L1 s - + Select a mission! @@ -2655,41 +2667,41 @@ - + %1 bytes - + %1% Video encoding progress. %1 = number - + (in progress...) - + Date: %1 - + Size: %1 - + %1 (%2%) - %3 Video encoding list entry. %1 = file name, %2 = percent complete, %3 = video operation type (e.g. “encoding”) - + encoding @@ -2697,7 +2709,7 @@ QAction - + Kick @@ -2721,44 +2733,44 @@ - + Info 信息 - + Ban 屏蔽 - + Delegate room control - + Follow - - + + Ignore - - + + Add friend - + Unignore - + Remove friend @@ -2786,146 +2798,146 @@ QCheckBox - + Fullscreen 游戏全屏幕 - + Show FPS 显示帧率 (FPS) - + Alternative damage show 另一种伤害显示方式 + + Team + + + - Team - - - - Enable team tags by default + + Hog + + + - Hog - - - - Enable hedgehog tags by default + + Health + + + - Health - - - - Enable health tags by default + + Translucent + + + - Translucent - - - - Enable translucent tags by default + + Visual effects + + + - Visual effects - - - - Enable visual effects such as animated menu transitions and falling stars + + + Sound + + + - - Sound - - - - In-game sound effects + + + Music + + + - - Music - - - - In-game music + + Dampen when losing focus + Checkbox text. If checked, the in-game audio volume is reduced (=dampened) when the game window loses its focus + + + - Dampen when losing focus - Checkbox text. If checked, the in-game audio volume is reduced (=dampened) when the game window loses its focus - - - - Reduce the game audio volume if the game window has lost its focus - + Frontend sound effects - + Frontend music - - If enabled, Hedgewars adds the date and time in the form "YYYY-MM-DD_hh-mm" for automatically created demos. - - - - - Check for updates at startup - - - - - Show ammo menu tooltips - - - + If enabled, Hedgewars adds the date and time in the form "YYYY-MM-DD_hh-mm" for automatically created demos. + + + + + Check for updates at startup + + + + + Show ammo menu tooltips + + + + Append date and time to record file name 记录名称中包含具体时间日期 - + Save password - + Record audio - + Use game resolution @@ -2943,7 +2955,7 @@ - + Community @@ -2952,112 +2964,112 @@ Lv 级别 - + (System default) + + Disabled + + + + + Stereoscopy creates an illusion of depth when you wear 3D glasses. + + + - Disabled - - - - - Stereoscopy creates an illusion of depth when you wear 3D glasses. + Red/Cyan - Red/Cyan + Cyan/Red - Cyan/Red + Red/Blue - Red/Blue + Blue/Red - Blue/Red + Red/Green - Red/Green - - - - Green/Red + + Side-by-side + + + - Side-by-side - - - - Top-Bottom + + 24 FPS + + + - 24 FPS + 25 FPS - 25 FPS + 30 FPS - 30 FPS + 50 FPS - 50 FPS - - - - 60 FPS + + Red/Cyan grayscale + + + - Red/Cyan grayscale + Cyan/Red grayscale - Cyan/Red grayscale + Red/Blue grayscale - Red/Blue grayscale + Blue/Red grayscale - Blue/Red grayscale + Red/Green grayscale - Red/Green grayscale - - - - Green/Red grayscale @@ -3075,7 +3087,7 @@ - + Fort 城堡模式 @@ -3105,7 +3117,7 @@ - + Description @@ -3113,48 +3125,48 @@ QLabel - + Locale - + Nickname - + Zoom (%) - + Stereoscopy - + Displayed tags above hogs and translucent tags - + This setting will be effective at next restart. - + Resolution 分辨率 - + Bitrate (Kibit/s) “Kibit/s” is the symbol for 1024 bits per second - + Quality @@ -3174,7 +3186,7 @@ - + FPS limit FPS 上限 @@ -3208,7 +3220,7 @@ 版本 - + Initial sound volume 初始音量 @@ -3382,22 +3394,22 @@ - + Format - + Audio codec - + Video codec - + Framerate @@ -3415,22 +3427,22 @@ QLineEdit - + unnamed 无名 - + unnamed (%1) - + hedgehog %1 - + anonymous @@ -3476,58 +3488,58 @@ - + Teams - Are you sure? - + Do you really want to delete the team '%1'? - - Teams - Name already taken - - - + Teams - Name already taken + + + + The team name '%1' is already taken, so your team has been renamed to '%2'. - + Cannot delete default scheme '%1'! - + Please select a record from the list - + Hedgewars - Nick not registered - + Unable to start server - + The connection to the server is lost. - + Server redirection - + This server supports secure connections on port %1. Would you like to reconnect securely? @@ -3537,12 +3549,12 @@ 服务器连接丢失 - + Not all players are ready - + Are you sure you want to start this game? Not all players are ready. @@ -3575,18 +3587,18 @@ - + Hedgewars - Success - + All file associations have been set - + File association failed. @@ -3653,18 +3665,18 @@ - - + + Videos - Are you sure? - + Do you really want to delete the video '%1'? - + Do you really want to remove %1 file(s)? @@ -3795,7 +3807,7 @@ - + Start 开始 @@ -3840,14 +3852,14 @@ - + Cancel 取消 - - + + Delete 删除 @@ -3857,42 +3869,42 @@ - + Associate file extensions + + Set default options + + + - Set default options - - - - Restore default coding parameters - + Open videos directory - + Open the video directory in your system - - Play - - - + Play + + + + Play this video - + Delete this video @@ -3900,7 +3912,7 @@ QSpinBox - + Specify the bitrate of recorded videos as a multiple of 1024 bits per second @@ -4522,7 +4534,7 @@ - precise + switch + toggle hedgehog tags + precise + switch + toggle team bars @@ -5537,71 +5549,76 @@ - Italian + Hungarian - Japanese + Italian - Korean + Japanese - Lithuanian + Korean - Polish + Lithuanian - Portuguese + Polish - Russian + Portuguese - Scottish Gaelic + Russian - Slovak + Scottish Gaelic - Spanish + Slovak - Swedish + Spanish - Ukrainian + Swedish - Special thanks + Ukrainian + Special thanks + + + + Project founder @@ -5645,7 +5662,7 @@ - + Unknown command or invalid parameters. Say '/help' in chat for a list of commands. diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/hedgewars_zh_TW.ts --- a/share/hedgewars/Data/Locale/hedgewars_zh_TW.ts Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/hedgewars_zh_TW.ts Sun Jul 05 14:53:44 2020 +0200 @@ -342,6 +342,7 @@ Scheme '%1' not supported + Here, “scheme” refers to the scheme of a Uniform Resource Identifier” 不支持遊戲方案“%1” @@ -553,7 +554,7 @@ Your nickname is not registered. To prevent someone else from using it, please register it at www.hedgewars.org - 你的暱稱未註冊。 + 你的暱稱未註冊。 要防止其他人使用它, 請上www.hedgewars.org進行註冊 @@ -633,6 +634,13 @@ Internal error: Reply object is invalid. 內部錯誤: 回覆的對象是無效的 + + Your nickname is not registered. +To be able to rejoin games in progress and +prevent someone else from using your nickname, +please register it at www.hedgewars.org. + + HWGame @@ -1483,7 +1491,7 @@ Save - 存檔 + 存檔 (%1 %2) @@ -1528,6 +1536,14 @@ (%1 箱子) + + Save demo + + + + Save demo (unavailable because the /lua command was used) + + PageInGame @@ -3960,7 +3976,7 @@ precise + switch + toggle hedgehog tags - 精細瞄準 + 切換 + 隊伍資訊欄開關 + 精細瞄準 + 切換 + 隊伍資訊欄開關 high jump (twice) @@ -3970,6 +3986,10 @@ precise + screenshot 精細瞄準 + 擷圖 + + precise + switch + toggle team bars + + binds (descriptions) @@ -4982,6 +5002,10 @@ Project founder 項目創始人 + + Hungarian + + server diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/it.lua --- a/share/hedgewars/Data/Locale/it.lua Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/it.lua Sun Jul 05 14:53:44 2020 +0200 @@ -677,11 +677,13 @@ -- ["Flawless victory!"] = "", -- User_Mission_-_RCPlane_Challenge -- ["Flee: Press [Jump]"] = "", -- A_Space_Adventure:fruit01 ["Flesh for Brainz"] = "Carne per Brainz", -- A_Classic_Fairytale:journey +-- ["Flower Power"] = "", -- Basic_Training_-_Rope -- ["Fly around and hurl explosives to your enemies."] = "", -- Tumbler -- ["Flying Saucer Training"] = "", -- Basic_Training_-_Flying_Saucer -- ["Fly into space to fight off the invaders with barrels!"] = "", -- Space_Invasion -- ["Fly to the meteorite and detonate the explosives"] = "", -- A_Space_Adventure:cosmos -- ["Follow the path and destroy the next target."] = "", -- Basic_Training_-_Rope +-- ["For each kill you win %d seconds."] = "", -- RopeKnocking -- ["Forgetfulness: You will lose all your weapons each turn."] = "", -- Continental_supplies -- ["For the next crate, you have to do back jumps."] = "", -- Basic_Training_-_Movement -- ["Four Eyes"] = "", -- @@ -1540,6 +1542,7 @@ -- ["Oneye"] = "", -- portal -- ["Only one hog per team allowed! Excess hogs will be removed"] = "", -- Mutant -- ["Only one hog per team allowed! Excess hogs will be removed."] = "", -- Mutant +-- ["Only one team per clan allowed! Excess teams will be removed."] = "", -- Mutant -- ["Only %s can be trusted with the crate."] = "", -- A_Space_Adventure:fruit02 -- ["Only the best pilots can master the following stunts."] = "", -- Basic_Training_-_Flying_Saucer -- ["Only two clans allowed! Excess hedgehogs will be removed."] = "", -- CTF_Blizzard @@ -2849,6 +2852,7 @@ -- ["You have killed all enemies."] = "", -- Big_Armory ["You have killed an innocent hedgehog!"] = "Hai ucciso un riccio innocente!", -- A_Classic_Fairytale:backstab -- ["You have killed %d of 16 hedgehogs (+%d points)."] = "", -- User_Mission_-_Rope_Knock_Challenge +-- ["You have killed %d of %d hedgehogs (+%d points)."] = "", -- RopeKnocking -- ["You have launched %d bazookas."] = "", -- Target_Practice_-_Bazooka_easy, Target_Practice_-_Bazooka_hard, Basic_Training_-_Bazooka -- ["You have launched %d homing bees."] = "", -- Target_Practice_-_Homing_Bee -- ["You have made %d shots."] = "", -- Basic_Training_-_Sniper_Rifle diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/ko.lua --- a/share/hedgewars/Data/Locale/ko.lua Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/ko.lua Sun Jul 05 14:53:44 2020 +0200 @@ -677,11 +677,13 @@ -- ["Flawless victory!"] = "", -- User_Mission_-_RCPlane_Challenge -- ["Flee: Press [Jump]"] = "", -- A_Space_Adventure:fruit01 -- ["Flesh for Brainz"] = "", -- A_Classic_Fairytale:journey +-- ["Flower Power"] = "", -- Basic_Training_-_Rope -- ["Fly around and hurl explosives to your enemies."] = "", -- Tumbler -- ["Flying Saucer Training"] = "", -- Basic_Training_-_Flying_Saucer -- ["Fly into space to fight off the invaders with barrels!"] = "", -- Space_Invasion -- ["Fly to the meteorite and detonate the explosives"] = "", -- A_Space_Adventure:cosmos -- ["Follow the path and destroy the next target."] = "", -- Basic_Training_-_Rope +-- ["For each kill you win %d seconds."] = "", -- RopeKnocking -- ["Forgetfulness: You will lose all your weapons each turn."] = "", -- Continental_supplies -- ["For the next crate, you have to do back jumps."] = "", -- Basic_Training_-_Movement -- ["Four Eyes"] = "", -- @@ -1540,6 +1542,7 @@ -- ["Oneye"] = "", -- portal -- ["Only one hog per team allowed! Excess hogs will be removed"] = "", -- Mutant -- ["Only one hog per team allowed! Excess hogs will be removed."] = "", -- Mutant +-- ["Only one team per clan allowed! Excess teams will be removed."] = "", -- Mutant -- ["Only %s can be trusted with the crate."] = "", -- A_Space_Adventure:fruit02 -- ["Only the best pilots can master the following stunts."] = "", -- Basic_Training_-_Flying_Saucer -- ["Only two clans allowed! Excess hedgehogs will be removed."] = "", -- CTF_Blizzard @@ -2849,6 +2852,7 @@ -- ["You have killed all enemies."] = "", -- Big_Armory -- ["You have killed an innocent hedgehog!"] = "", -- A_Classic_Fairytale:backstab -- ["You have killed %d of 16 hedgehogs (+%d points)."] = "", -- User_Mission_-_Rope_Knock_Challenge +-- ["You have killed %d of %d hedgehogs (+%d points)."] = "", -- RopeKnocking -- ["You have launched %d bazookas."] = "", -- Target_Practice_-_Bazooka_easy, Target_Practice_-_Bazooka_hard, Basic_Training_-_Bazooka -- ["You have launched %d homing bees."] = "", -- Target_Practice_-_Homing_Bee -- ["You have made %d shots."] = "", -- Basic_Training_-_Sniper_Rifle diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/lt.lua --- a/share/hedgewars/Data/Locale/lt.lua Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/lt.lua Sun Jul 05 14:53:44 2020 +0200 @@ -677,11 +677,13 @@ -- ["Flawless victory!"] = "", -- User_Mission_-_RCPlane_Challenge -- ["Flee: Press [Jump]"] = "", -- A_Space_Adventure:fruit01 -- ["Flesh for Brainz"] = "", -- A_Classic_Fairytale:journey +-- ["Flower Power"] = "", -- Basic_Training_-_Rope -- ["Fly around and hurl explosives to your enemies."] = "", -- Tumbler -- ["Flying Saucer Training"] = "", -- Basic_Training_-_Flying_Saucer -- ["Fly into space to fight off the invaders with barrels!"] = "", -- Space_Invasion -- ["Fly to the meteorite and detonate the explosives"] = "", -- A_Space_Adventure:cosmos -- ["Follow the path and destroy the next target."] = "", -- Basic_Training_-_Rope +-- ["For each kill you win %d seconds."] = "", -- RopeKnocking -- ["Forgetfulness: You will lose all your weapons each turn."] = "", -- Continental_supplies -- ["For the next crate, you have to do back jumps."] = "", -- Basic_Training_-_Movement -- ["Four Eyes"] = "", -- @@ -1540,6 +1542,7 @@ -- ["Oneye"] = "", -- portal -- ["Only one hog per team allowed! Excess hogs will be removed"] = "", -- Mutant -- ["Only one hog per team allowed! Excess hogs will be removed."] = "", -- Mutant +-- ["Only one team per clan allowed! Excess teams will be removed."] = "", -- Mutant -- ["Only %s can be trusted with the crate."] = "", -- A_Space_Adventure:fruit02 -- ["Only the best pilots can master the following stunts."] = "", -- Basic_Training_-_Flying_Saucer -- ["Only two clans allowed! Excess hedgehogs will be removed."] = "", -- CTF_Blizzard @@ -2849,6 +2852,7 @@ -- ["You have killed all enemies."] = "", -- Big_Armory -- ["You have killed an innocent hedgehog!"] = "", -- A_Classic_Fairytale:backstab -- ["You have killed %d of 16 hedgehogs (+%d points)."] = "", -- User_Mission_-_Rope_Knock_Challenge +-- ["You have killed %d of %d hedgehogs (+%d points)."] = "", -- RopeKnocking -- ["You have launched %d bazookas."] = "", -- Target_Practice_-_Bazooka_easy, Target_Practice_-_Bazooka_hard, Basic_Training_-_Bazooka -- ["You have launched %d homing bees."] = "", -- Target_Practice_-_Homing_Bee -- ["You have made %d shots."] = "", -- Basic_Training_-_Sniper_Rifle diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/missions_pt.txt diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/pl.lua --- a/share/hedgewars/Data/Locale/pl.lua Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/pl.lua Sun Jul 05 14:53:44 2020 +0200 @@ -462,8 +462,8 @@ ["Defeat all enemies!"] = "Pokonaj wszystkich wrogów!", -- portal ["Defeat!"] = "Porażka!", -- HedgeEditor ["Defeat Professor Hogevil!"] = "Pokonaj Profesora Jeżozło!", -- A_Space_Adventure:death01 + ["Defeat the cannibals!|Grenade hint: set the timer with [1-5], aim with [Up]/[Down] and hold [Space] to set power"] = "Pokonaj kanibali!|Porada do granatów: ustaw zapalnik używając [1-5], celuj [Góra]/[Dół] i przytrzymaj [Spację], by ustawić moc", -- A_Classic_Fairytale:shadow ["Defeat the cannibals!"] = "Pokonaj kanibali!", -- A_Classic_Fairytale:shadow - ["Defeat the cannibals!|Grenade hint: set the timer with [1-5], aim with [Up]/[Down] and hold [Space] to set power"] = "Pokonaj kanibali!|Porada do granatów: ustaw zapalnik używając [1-5], celuj [Góra]/[Dół] i przytrzymaj [Spację], by ustawić moc", -- A_Classic_Fairytale:shadow ["Defeat the cyborgs!"] = "Pokonaj cyborgów!", -- A_Classic_Fairytale:enemy ["Defeat the enemy!"] = "Pokonaj wroga!", -- A_Classic_Fairytale:queen ["Delete Waypoint"] = "Usuń punkt kontrolny", -- HedgeEditor @@ -485,8 +485,8 @@ ["Destroy him, Leaks A Lot! He is responsible for the deaths of many of us!"] = "Zniszcz go, Spory Przecieku! On jest odpowiedzalny za śmierć wielu z nas!", -- A_Classic_Fairytale:first_blood ["Destroy invaders and collect bonuses to score points."] = "Niszcz najeźdźców i zbieraj bonusy, by zaliczać punkty.", -- Space_Invasion ["- Destroy the enemy"] = "- Zniszcz wroga", -- HedgeEditor + ["- Destroy the red targets"] = "- Zniszcz czerwone cele", -- HedgeEditor ["- Destroy the red target"] = "- Zniszcz czerwony cel", -- HedgeEditor - ["- Destroy the red targets"] = "- Zniszcz czerwone cele", -- HedgeEditor ["Destroy the targets!"] = "Zniszcz cele!", -- Basic_Training_-_Bazooka, Basic_Training_-_Grenade ["+%d flamer fuel!"] = "+%d paliwa miotacza ognia!", -- Tumbler ["+%d health"] = "+%d zdrowia", -- Mutant @@ -677,11 +677,13 @@ ["Flawless victory!"] = "Bezbłędne zwycięstwo!", -- User_Mission_-_RCPlane_Challenge ["Flee: Press [Jump]"] = "Ucieknij: Wciśnij [Skok]", -- A_Space_Adventure:fruit01 ["Flesh for Brainz"] = "Mięso dla Mózgów", -- A_Classic_Fairytale:journey +-- ["Flower Power"] = "", -- Basic_Training_-_Rope ["Fly around and hurl explosives to your enemies."] = "Lataj dookoła i ciskaj ładunki wybuchowe na swoich wrogów.", -- Tumbler ["Flying Saucer Training"] = "Trening latającego talerza", -- Basic_Training_-_Flying_Saucer ["Fly into space to fight off the invaders with barrels!"] = "Leć w kosmos, by odeprzeć najeźdźców beczkami!", -- Space_Invasion ["Fly to the meteorite and detonate the explosives"] = "Poleć do meteorytu i zdetonuj ładunki", -- A_Space_Adventure:cosmos ["Follow the path and destroy the next target."] = "Podążaj ściezką i zniszcz następny cel.", -- Basic_Training_-_Rope +-- ["For each kill you win %d seconds."] = "", -- RopeKnocking ["Forgetfulness: You will lose all your weapons each turn."] = "Zapominalstwo: W każdej turze stracisz wszystkie swoje bronie.", -- Continental_supplies ["For the next crate, you have to do back jumps."] = "Do następnej skrzyni musisz zrobić skoki w tył.", -- Basic_Training_-_Movement ["Four Eyes"] = "Czterooki", @@ -1378,8 +1380,8 @@ ["Minions"] = "Sługusy", -- A_Space_Adventure:moon01 ["Mission failed!"] = "Misja zakończona niepowodzeniem!", -- Big_Armory ["Mission failure in %d s"] = "Porażka misji w %d s", -- Big_Armory + ["Mission lost!"] = "Misja stracona!", -- Basic_Training_-_Grenade ["Mission"] = "Misja", -- HedgeEditor - ["Mission lost!"] = "Misja stracona!", -- Basic_Training_-_Grenade ["Mission panel: [M]"] = "Panel misji: [M]", -- Basic_Training_-_Movement ["Mission Panel"] = "Panel misji", -- Basic_Training_-_Movement ["Mission succeeded!"] = "Misja ukończona!", -- portal, User_Mission_-_Bamboo_Thicket, User_Mission_-_Dangerous_Ducklings, User_Mission_-_Diver, User_Mission_-_Spooky_Tree, User_Mission_-_Teamwork_2, User_Mission_-_Teamwork, SimpleMission, HedgeEditor @@ -1541,6 +1543,7 @@ ["Oneye"] = "Jednooki", -- portal ["Only one hog per team allowed! Excess hogs will be removed"] = "Dozwolony tylko jeden jeż na drużynę! Nadmiarowe jeże będą usunięte", -- Mutant ["Only one hog per team allowed! Excess hogs will be removed."] = "Dozwolony tylko jeden jeż na drużynę! Nadmiarowe jeże będą usunięte.", -- Mutant +-- ["Only one team per clan allowed! Excess teams will be removed."] = "", -- Mutant ["Only %s can be trusted with the crate."] = "Tylko jeżowi %s można ufać ze skrzynią.", -- A_Space_Adventure:fruit02 ["Only the best pilots can master the following stunts."] = "Tylko najlepsi piloci mogą opanować następujące wyczyny.", -- Basic_Training_-_Flying_Saucer ["Only two clans allowed! Excess hedgehogs will be removed."] = "Tylko dwa klany dozwolone! Nadmiarowe jeże będą usunięte.", -- CTF_Blizzard @@ -2851,6 +2854,7 @@ ["You have killed all enemies."] = "Zabiłeś wszystkich wrogów.", -- Big_Armory ["You have killed an innocent hedgehog!"] = "Zabiłeś niewinnego jeża!", -- A_Classic_Fairytale:backstab ["You have killed %d of 16 hedgehogs (+%d points)."] = "Zabiłeś %d z 16 jeży (+%d punktów)", -- User_Mission_-_Rope_Knock_Challenge +-- ["You have killed %d of %d hedgehogs (+%d points)."] = "", -- RopeKnocking ["You have launched %d bazookas."] = "Wystrzeliłeś %d bazook.", -- Target_Practice_-_Bazooka_easy, Target_Practice_-_Bazooka_hard, Basic_Training_-_Bazooka ["You have launched %d homing bees."] = "Wystrzeliłeś %d pszczół.", -- Target_Practice_-_Homing_Bee ["You have made %d shots."] = "Wykonałeś %d strzałów.", -- Basic_Training_-_Sniper_Rifle diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/pt_BR.lua --- a/share/hedgewars/Data/Locale/pt_BR.lua Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/pt_BR.lua Sun Jul 05 14:53:44 2020 +0200 @@ -677,11 +677,13 @@ -- ["Flawless victory!"] = "", -- User_Mission_-_RCPlane_Challenge -- ["Flee: Press [Jump]"] = "", -- A_Space_Adventure:fruit01 -- ["Flesh for Brainz"] = "", -- A_Classic_Fairytale:journey +-- ["Flower Power"] = "", -- Basic_Training_-_Rope -- ["Fly around and hurl explosives to your enemies."] = "", -- Tumbler -- ["Flying Saucer Training"] = "", -- Basic_Training_-_Flying_Saucer -- ["Fly into space to fight off the invaders with barrels!"] = "", -- Space_Invasion -- ["Fly to the meteorite and detonate the explosives"] = "", -- A_Space_Adventure:cosmos -- ["Follow the path and destroy the next target."] = "", -- Basic_Training_-_Rope +-- ["For each kill you win %d seconds."] = "", -- RopeKnocking -- ["Forgetfulness: You will lose all your weapons each turn."] = "", -- Continental_supplies -- ["For the next crate, you have to do back jumps."] = "", -- Basic_Training_-_Movement -- ["Four Eyes"] = "", -- @@ -1540,6 +1542,7 @@ -- ["Oneye"] = "", -- portal -- ["Only one hog per team allowed! Excess hogs will be removed"] = "", -- Mutant -- ["Only one hog per team allowed! Excess hogs will be removed."] = "", -- Mutant +-- ["Only one team per clan allowed! Excess teams will be removed."] = "", -- Mutant -- ["Only %s can be trusted with the crate."] = "", -- A_Space_Adventure:fruit02 -- ["Only the best pilots can master the following stunts."] = "", -- Basic_Training_-_Flying_Saucer -- ["Only two clans allowed! Excess hedgehogs will be removed."] = "", -- CTF_Blizzard @@ -2849,6 +2852,7 @@ -- ["You have killed all enemies."] = "", -- Big_Armory -- ["You have killed an innocent hedgehog!"] = "", -- A_Classic_Fairytale:backstab -- ["You have killed %d of 16 hedgehogs (+%d points)."] = "", -- User_Mission_-_Rope_Knock_Challenge +-- ["You have killed %d of %d hedgehogs (+%d points)."] = "", -- RopeKnocking -- ["You have launched %d bazookas."] = "", -- Target_Practice_-_Bazooka_easy, Target_Practice_-_Bazooka_hard, Basic_Training_-_Bazooka -- ["You have launched %d homing bees."] = "", -- Target_Practice_-_Homing_Bee -- ["You have made %d shots."] = "", -- Basic_Training_-_Sniper_Rifle diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/pt_PT.lua --- a/share/hedgewars/Data/Locale/pt_PT.lua Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/pt_PT.lua Sun Jul 05 14:53:44 2020 +0200 @@ -677,11 +677,13 @@ -- ["Flawless victory!"] = "", -- User_Mission_-_RCPlane_Challenge -- ["Flee: Press [Jump]"] = "", -- A_Space_Adventure:fruit01 -- ["Flesh for Brainz"] = "", -- A_Classic_Fairytale:journey +-- ["Flower Power"] = "", -- Basic_Training_-_Rope -- ["Fly around and hurl explosives to your enemies."] = "", -- Tumbler -- ["Flying Saucer Training"] = "", -- Basic_Training_-_Flying_Saucer -- ["Fly into space to fight off the invaders with barrels!"] = "", -- Space_Invasion -- ["Fly to the meteorite and detonate the explosives"] = "", -- A_Space_Adventure:cosmos -- ["Follow the path and destroy the next target."] = "", -- Basic_Training_-_Rope +-- ["For each kill you win %d seconds."] = "", -- RopeKnocking -- ["Forgetfulness: You will lose all your weapons each turn."] = "", -- Continental_supplies -- ["For the next crate, you have to do back jumps."] = "", -- Basic_Training_-_Movement -- ["Four Eyes"] = "", -- @@ -1540,6 +1542,7 @@ -- ["Oneye"] = "", -- portal -- ["Only one hog per team allowed! Excess hogs will be removed"] = "", -- Mutant -- ["Only one hog per team allowed! Excess hogs will be removed."] = "", -- Mutant +-- ["Only one team per clan allowed! Excess teams will be removed."] = "", -- Mutant -- ["Only %s can be trusted with the crate."] = "", -- A_Space_Adventure:fruit02 -- ["Only the best pilots can master the following stunts."] = "", -- Basic_Training_-_Flying_Saucer -- ["Only two clans allowed! Excess hedgehogs will be removed."] = "", -- CTF_Blizzard @@ -2850,6 +2853,7 @@ -- ["You have killed all enemies."] = "", -- Big_Armory -- ["You have killed an innocent hedgehog!"] = "", -- A_Classic_Fairytale:backstab -- ["You have killed %d of 16 hedgehogs (+%d points)."] = "", -- User_Mission_-_Rope_Knock_Challenge +-- ["You have killed %d of %d hedgehogs (+%d points)."] = "", -- RopeKnocking -- ["You have launched %d bazookas."] = "", -- Target_Practice_-_Bazooka_easy, Target_Practice_-_Bazooka_hard, Basic_Training_-_Bazooka -- ["You have launched %d homing bees."] = "", -- Target_Practice_-_Homing_Bee -- ["You have made %d shots."] = "", -- Basic_Training_-_Sniper_Rifle diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/ru.lua --- a/share/hedgewars/Data/Locale/ru.lua Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/ru.lua Sun Jul 05 14:53:44 2020 +0200 @@ -676,11 +676,13 @@ ["Flawless victory!"] = "Безупречная победа!", -- User_Mission_-_RCPlane_Challenge -- ["Flee: Press [Jump]"] = "", -- A_Space_Adventure:fruit01 -- ["Flesh for Brainz"] = "", -- A_Classic_Fairytale:journey +-- ["Flower Power"] = "", -- Basic_Training_-_Rope -- ["Fly around and hurl explosives to your enemies."] = "", -- Tumbler -- ["Flying Saucer Training"] = "", -- Basic_Training_-_Flying_Saucer -- ["Fly into space to fight off the invaders with barrels!"] = "", -- Space_Invasion -- ["Fly to the meteorite and detonate the explosives"] = "", -- A_Space_Adventure:cosmos -- ["Follow the path and destroy the next target."] = "", -- Basic_Training_-_Rope +-- ["For each kill you win %d seconds."] = "", -- RopeKnocking -- ["Forgetfulness: You will lose all your weapons each turn."] = "", -- Continental_supplies -- ["For the next crate, you have to do back jumps."] = "", -- Basic_Training_-_Movement -- ["Four Eyes"] = "", -- @@ -1538,6 +1540,7 @@ -- ["Oneye"] = "", -- portal -- ["Only one hog per team allowed! Excess hogs will be removed"] = "", -- Mutant -- ["Only one hog per team allowed! Excess hogs will be removed."] = "", -- Mutant +-- ["Only one team per clan allowed! Excess teams will be removed."] = "", -- Mutant -- ["Only %s can be trusted with the crate."] = "", -- A_Space_Adventure:fruit02 -- ["Only the best pilots can master the following stunts."] = "", -- Basic_Training_-_Flying_Saucer -- ["Only two clans allowed! Excess hedgehogs will be removed."] = "", -- CTF_Blizzard @@ -2844,6 +2847,7 @@ -- ["You have killed all enemies."] = "", -- Big_Armory -- ["You have killed an innocent hedgehog!"] = "", -- A_Classic_Fairytale:backstab -- ["You have killed %d of 16 hedgehogs (+%d points)."] = "", -- User_Mission_-_Rope_Knock_Challenge +-- ["You have killed %d of %d hedgehogs (+%d points)."] = "", -- RopeKnocking ["You have launched %d bazookas."] = "Вы запустили %d базук.", -- Basic_Training_-_Bazooka ["You have launched %d homing bees."] = "Вы запустили %d пчёлок.", -- Target_Practice_-_Homing_Bee ["You have made %d shots."] = "Вы сделали %d выстрелов.", -- Basic_Training_-_Sniper_Rifle diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/ru.txt --- a/share/hedgewars/Data/Locale/ru.txt Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/ru.txt Sun Jul 05 14:53:44 2020 +0200 @@ -677,7 +677,7 @@ 02:17=%1 больше не с нами 02:17=Наш любимый король %1 покинул нас -; Weapon Categories +; Weapon categories/subcaptions 03:00=Граната с таймером 03:01=Бомба с таймером 03:02=Баллистическое оружие @@ -741,7 +741,7 @@ 03:59=Незавершённое оружие 03:60=Наимощнейшая пушка -; Weapon Descriptions (use | as line breaks) +; Weapon descriptions (use | as line breaks) 04:00=Атакуй своих врагов обычной гранатой.|Она взорвется сразу, как только таймер достигнет нуля.|1-5: Установить таймер гранаты|Точность + 1-5: Установить силу отскока|Атака: Удерживай для более дальнего броска 04:01=Атакуй своих врагов касетной бомбой.|Она разорвётся на несколько меньших бомб,|когда таймер достигнет нуля.|1-5: Установить таймер бомбы|Точность + 1-5: Установить силу отскока|Атака: Удерживай для более дальнего броска 04:02=Атакуй своих врагов баллистическим снарядом,|на который может повлиять направление ветра.|Атака: Удерживай для выстрела с большей силой @@ -826,13 +826,13 @@ 05:18=Неограниченные атаки: Ход не заканчивается после атаки 05:19=Постоянное вооружение: Оружие поменяется в конце хода 05:20=Личное оружие: Ежи не имеют общего оружия -05:21=Признак команды: Команды в клане ходят последовательно|Общее время: Команды в клане имеют общее время хода +05:21=Признак команды: Команды в союзе ходят последовательно|Общее время: Команды в союзе имеют общее время хода 05:22=Сильный ветер: Ветер влияет почти на всё ; Chat command help 06:00=Список основных команд чата: 06:01=/togglechat: Отобразить чат -06:02=/clan <сообщение>: Отправить сообщение только участникам клана +06:02=/clan <сообщение>: Отправить сообщение только участникам союза 06:03=/me <сообщение>: Действие в чате, например "/me ест пиццу" становится "* Игрок ест пиццу" 06:04=/pause: Включить паузу 06:05=/pause: Включить автопропуск хода diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/sk.lua --- a/share/hedgewars/Data/Locale/sk.lua Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/sk.lua Sun Jul 05 14:53:44 2020 +0200 @@ -677,11 +677,13 @@ -- ["Flawless victory!"] = "", -- User_Mission_-_RCPlane_Challenge -- ["Flee: Press [Jump]"] = "", -- A_Space_Adventure:fruit01 -- ["Flesh for Brainz"] = "", -- A_Classic_Fairytale:journey +-- ["Flower Power"] = "", -- Basic_Training_-_Rope -- ["Fly around and hurl explosives to your enemies."] = "", -- Tumbler -- ["Flying Saucer Training"] = "", -- Basic_Training_-_Flying_Saucer -- ["Fly into space to fight off the invaders with barrels!"] = "", -- Space_Invasion -- ["Fly to the meteorite and detonate the explosives"] = "", -- A_Space_Adventure:cosmos -- ["Follow the path and destroy the next target."] = "", -- Basic_Training_-_Rope +-- ["For each kill you win %d seconds."] = "", -- RopeKnocking -- ["Forgetfulness: You will lose all your weapons each turn."] = "", -- Continental_supplies -- ["For the next crate, you have to do back jumps."] = "", -- Basic_Training_-_Movement -- ["Four Eyes"] = "", -- @@ -1540,6 +1542,7 @@ -- ["Oneye"] = "", -- portal -- ["Only one hog per team allowed! Excess hogs will be removed"] = "", -- Mutant -- ["Only one hog per team allowed! Excess hogs will be removed."] = "", -- Mutant +-- ["Only one team per clan allowed! Excess teams will be removed."] = "", -- Mutant -- ["Only %s can be trusted with the crate."] = "", -- A_Space_Adventure:fruit02 -- ["Only the best pilots can master the following stunts."] = "", -- Basic_Training_-_Flying_Saucer -- ["Only two clans allowed! Excess hedgehogs will be removed."] = "", -- CTF_Blizzard @@ -2853,6 +2856,7 @@ -- ["You have killed all enemies."] = "", -- Big_Armory -- ["You have killed an innocent hedgehog!"] = "", -- A_Classic_Fairytale:backstab -- ["You have killed %d of 16 hedgehogs (+%d points)."] = "", -- User_Mission_-_Rope_Knock_Challenge +-- ["You have killed %d of %d hedgehogs (+%d points)."] = "", -- RopeKnocking -- ["You have launched %d bazookas."] = "", -- Target_Practice_-_Bazooka_easy, Target_Practice_-_Bazooka_hard, Basic_Training_-_Bazooka -- ["You have launched %d homing bees."] = "", -- Target_Practice_-_Homing_Bee -- ["You have made %d shots."] = "", -- Basic_Training_-_Sniper_Rifle diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/stub.lua --- a/share/hedgewars/Data/Locale/stub.lua Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/stub.lua Sun Jul 05 14:53:44 2020 +0200 @@ -636,11 +636,13 @@ -- ["Flawless victory!"] = "", -- User_Mission_-_RCPlane_Challenge -- ["Flee: Press [Jump]"] = "", -- A_Space_Adventure:fruit01 -- ["Flesh for Brainz"] = "", -- A_Classic_Fairytale:journey +-- ["Flower Power"] = "", -- Basic_Training_-_Rope -- ["Fly around and hurl explosives to your enemies."] = "", -- Tumbler -- ["Flying Saucer Training"] = "", -- Basic_Training_-_Flying_Saucer -- ["Fly into space to fight off the invaders with barrels!"] = "", -- Space_Invasion -- ["Fly to the meteorite and detonate the explosives"] = "", -- A_Space_Adventure:cosmos -- ["Follow the path and destroy the next target."] = "", -- Basic_Training_-_Rope +-- ["For each kill you win %d seconds."] = "", -- RopeKnocking -- ["Forgetfulness: You will lose all your weapons each turn."] = "", -- Continental_supplies -- ["For the next crate, you have to do back jumps."] = "", -- Basic_Training_-_Movement -- ["Four Eyes"] = "", -- @@ -1446,6 +1448,7 @@ -- ["One tribe was peaceful, spending their time hunting and training, enjoying the small pleasures of life..."] = "", -- A_Classic_Fairytale:first_blood -- ["Oneye"] = "", -- portal -- ["Only one hog per team allowed! Excess hogs will be removed."] = "", -- Mutant +-- ["Only one team per clan allowed! Excess teams will be removed."] = "", -- Mutant -- ["Only %s can be trusted with the crate."] = "", -- A_Space_Adventure:fruit02 -- ["Only the best pilots can master the following stunts."] = "", -- Basic_Training_-_Flying_Saucer -- ["Only two clans allowed! Excess hedgehogs will be removed."] = "", -- CTF_Blizzard @@ -2661,6 +2664,7 @@ -- ["You have kidnapped our whole tribe!"] = "", -- A_Classic_Fairytale:enemy -- ["You have killed an innocent hedgehog!"] = "", -- A_Classic_Fairytale:backstab -- ["You have killed %d of 16 hedgehogs (+%d points)."] = "", -- User_Mission_-_Rope_Knock_Challenge +-- ["You have killed %d of %d hedgehogs (+%d points)."] = "", -- RopeKnocking -- ["You have launched %d bazookas."] = "", -- Basic_Training_-_Bazooka -- ["You have launched %d homing bees."] = "", -- Target_Practice_-_Homing_Bee -- ["You have made %d shots."] = "", -- Basic_Training_-_Sniper_Rifle diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/sv.lua --- a/share/hedgewars/Data/Locale/sv.lua Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/sv.lua Sun Jul 05 14:53:44 2020 +0200 @@ -677,11 +677,13 @@ -- ["Flawless victory!"] = "", -- User_Mission_-_RCPlane_Challenge -- ["Flee: Press [Jump]"] = "", -- A_Space_Adventure:fruit01 -- ["Flesh for Brainz"] = "", -- A_Classic_Fairytale:journey +-- ["Flower Power"] = "", -- Basic_Training_-_Rope -- ["Fly around and hurl explosives to your enemies."] = "", -- Tumbler -- ["Flying Saucer Training"] = "", -- Basic_Training_-_Flying_Saucer -- ["Fly into space to fight off the invaders with barrels!"] = "", -- Space_Invasion -- ["Fly to the meteorite and detonate the explosives"] = "", -- A_Space_Adventure:cosmos -- ["Follow the path and destroy the next target."] = "", -- Basic_Training_-_Rope +-- ["For each kill you win %d seconds."] = "", -- RopeKnocking -- ["Forgetfulness: You will lose all your weapons each turn."] = "", -- Continental_supplies -- ["For the next crate, you have to do back jumps."] = "", -- Basic_Training_-_Movement -- ["Four Eyes"] = "", -- @@ -1540,6 +1542,7 @@ -- ["Oneye"] = "", -- portal -- ["Only one hog per team allowed! Excess hogs will be removed"] = "", -- Mutant -- ["Only one hog per team allowed! Excess hogs will be removed."] = "", -- Mutant +-- ["Only one team per clan allowed! Excess teams will be removed."] = "", -- Mutant -- ["Only %s can be trusted with the crate."] = "", -- A_Space_Adventure:fruit02 -- ["Only the best pilots can master the following stunts."] = "", -- Basic_Training_-_Flying_Saucer -- ["Only two clans allowed! Excess hedgehogs will be removed."] = "", -- CTF_Blizzard @@ -2849,6 +2852,7 @@ -- ["You have killed all enemies."] = "", -- Big_Armory -- ["You have killed an innocent hedgehog!"] = "", -- A_Classic_Fairytale:backstab -- ["You have killed %d of 16 hedgehogs (+%d points)."] = "", -- User_Mission_-_Rope_Knock_Challenge +-- ["You have killed %d of %d hedgehogs (+%d points)."] = "", -- RopeKnocking -- ["You have launched %d bazookas."] = "", -- Target_Practice_-_Bazooka_easy, Target_Practice_-_Bazooka_hard, Basic_Training_-_Bazooka -- ["You have launched %d homing bees."] = "", -- Target_Practice_-_Homing_Bee -- ["You have made %d shots."] = "", -- Basic_Training_-_Sniper_Rifle diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/tr.lua --- a/share/hedgewars/Data/Locale/tr.lua Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/tr.lua Sun Jul 05 14:53:44 2020 +0200 @@ -677,11 +677,13 @@ -- ["Flawless victory!"] = "", -- User_Mission_-_RCPlane_Challenge -- ["Flee: Press [Jump]"] = "", -- A_Space_Adventure:fruit01 -- ["Flesh for Brainz"] = "", -- A_Classic_Fairytale:journey +-- ["Flower Power"] = "", -- Basic_Training_-_Rope -- ["Fly around and hurl explosives to your enemies."] = "", -- Tumbler -- ["Flying Saucer Training"] = "", -- Basic_Training_-_Flying_Saucer -- ["Fly into space to fight off the invaders with barrels!"] = "", -- Space_Invasion -- ["Fly to the meteorite and detonate the explosives"] = "", -- A_Space_Adventure:cosmos -- ["Follow the path and destroy the next target."] = "", -- Basic_Training_-_Rope +-- ["For each kill you win %d seconds."] = "", -- RopeKnocking -- ["Forgetfulness: You will lose all your weapons each turn."] = "", -- Continental_supplies -- ["For the next crate, you have to do back jumps."] = "", -- Basic_Training_-_Movement -- ["Four Eyes"] = "", -- @@ -1541,6 +1543,7 @@ -- ["Oneye"] = "", -- portal -- ["Only one hog per team allowed! Excess hogs will be removed"] = "", -- Mutant -- ["Only one hog per team allowed! Excess hogs will be removed."] = "", -- Mutant +-- ["Only one team per clan allowed! Excess teams will be removed."] = "", -- Mutant -- ["Only %s can be trusted with the crate."] = "", -- A_Space_Adventure:fruit02 -- ["Only the best pilots can master the following stunts."] = "", -- Basic_Training_-_Flying_Saucer -- ["Only two clans allowed! Excess hedgehogs will be removed."] = "", -- CTF_Blizzard @@ -2851,6 +2854,7 @@ -- ["You have killed all enemies."] = "", -- Big_Armory -- ["You have killed an innocent hedgehog!"] = "", -- A_Classic_Fairytale:backstab -- ["You have killed %d of 16 hedgehogs (+%d points)."] = "", -- User_Mission_-_Rope_Knock_Challenge +-- ["You have killed %d of %d hedgehogs (+%d points)."] = "", -- RopeKnocking -- ["You have launched %d bazookas."] = "", -- Target_Practice_-_Bazooka_easy, Target_Practice_-_Bazooka_hard, Basic_Training_-_Bazooka -- ["You have launched %d homing bees."] = "", -- Target_Practice_-_Homing_Bee -- ["You have made %d shots."] = "", -- Basic_Training_-_Sniper_Rifle diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/uk.lua --- a/share/hedgewars/Data/Locale/uk.lua Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/uk.lua Sun Jul 05 14:53:44 2020 +0200 @@ -676,11 +676,13 @@ -- ["Flawless victory!"] = "", -- User_Mission_-_RCPlane_Challenge -- ["Flee: Press [Jump]"] = "", -- A_Space_Adventure:fruit01 -- ["Flesh for Brainz"] = "", -- A_Classic_Fairytale:journey +-- ["Flower Power"] = "", -- Basic_Training_-_Rope -- ["Fly around and hurl explosives to your enemies."] = "", -- Tumbler -- ["Flying Saucer Training"] = "", -- Basic_Training_-_Flying_Saucer -- ["Fly into space to fight off the invaders with barrels!"] = "", -- Space_Invasion -- ["Fly to the meteorite and detonate the explosives"] = "", -- A_Space_Adventure:cosmos -- ["Follow the path and destroy the next target."] = "", -- Basic_Training_-_Rope +-- ["For each kill you win %d seconds."] = "", -- RopeKnocking -- ["Forgetfulness: You will lose all your weapons each turn."] = "", -- Continental_supplies -- ["For the next crate, you have to do back jumps."] = "", -- Basic_Training_-_Movement -- ["Four Eyes"] = "", -- @@ -1539,6 +1541,7 @@ -- ["Oneye"] = "", -- portal -- ["Only one hog per team allowed! Excess hogs will be removed"] = "", -- Mutant -- ["Only one hog per team allowed! Excess hogs will be removed."] = "", -- Mutant +-- ["Only one team per clan allowed! Excess teams will be removed."] = "", -- Mutant -- ["Only %s can be trusted with the crate."] = "", -- A_Space_Adventure:fruit02 -- ["Only the best pilots can master the following stunts."] = "", -- Basic_Training_-_Flying_Saucer -- ["Only two clans allowed! Excess hedgehogs will be removed."] = "", -- CTF_Blizzard @@ -2848,6 +2851,7 @@ -- ["You have killed all enemies."] = "", -- Big_Armory -- ["You have killed an innocent hedgehog!"] = "", -- A_Classic_Fairytale:backstab -- ["You have killed %d of 16 hedgehogs (+%d points)."] = "", -- User_Mission_-_Rope_Knock_Challenge +-- ["You have killed %d of %d hedgehogs (+%d points)."] = "", -- RopeKnocking -- ["You have launched %d bazookas."] = "", -- Target_Practice_-_Bazooka_easy, Target_Practice_-_Bazooka_hard, Basic_Training_-_Bazooka -- ["You have launched %d homing bees."] = "", -- Target_Practice_-_Homing_Bee -- ["You have made %d shots."] = "", -- Basic_Training_-_Sniper_Rifle diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Locale/zh_CN.lua --- a/share/hedgewars/Data/Locale/zh_CN.lua Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Locale/zh_CN.lua Sun Jul 05 14:53:44 2020 +0200 @@ -677,11 +677,13 @@ -- ["Flawless victory!"] = "", -- User_Mission_-_RCPlane_Challenge -- ["Flee: Press [Jump]"] = "", -- A_Space_Adventure:fruit01 -- ["Flesh for Brainz"] = "", -- A_Classic_Fairytale:journey +-- ["Flower Power"] = "", -- Basic_Training_-_Rope -- ["Fly around and hurl explosives to your enemies."] = "", -- Tumbler -- ["Flying Saucer Training"] = "", -- Basic_Training_-_Flying_Saucer -- ["Fly into space to fight off the invaders with barrels!"] = "", -- Space_Invasion -- ["Fly to the meteorite and detonate the explosives"] = "", -- A_Space_Adventure:cosmos -- ["Follow the path and destroy the next target."] = "", -- Basic_Training_-_Rope +-- ["For each kill you win %d seconds."] = "", -- RopeKnocking -- ["Forgetfulness: You will lose all your weapons each turn."] = "", -- Continental_supplies -- ["For the next crate, you have to do back jumps."] = "", -- Basic_Training_-_Movement -- ["Four Eyes"] = "", -- @@ -1540,6 +1542,7 @@ -- ["Oneye"] = "", -- portal -- ["Only one hog per team allowed! Excess hogs will be removed"] = "", -- Mutant -- ["Only one hog per team allowed! Excess hogs will be removed."] = "", -- Mutant +-- ["Only one team per clan allowed! Excess teams will be removed."] = "", -- Mutant -- ["Only %s can be trusted with the crate."] = "", -- A_Space_Adventure:fruit02 -- ["Only the best pilots can master the following stunts."] = "", -- Basic_Training_-_Flying_Saucer -- ["Only two clans allowed! Excess hedgehogs will be removed."] = "", -- CTF_Blizzard @@ -2851,6 +2854,7 @@ -- ["You have killed all enemies."] = "", -- Big_Armory -- ["You have killed an innocent hedgehog!"] = "", -- A_Classic_Fairytale:backstab -- ["You have killed %d of 16 hedgehogs (+%d points)."] = "", -- User_Mission_-_Rope_Knock_Challenge +-- ["You have killed %d of %d hedgehogs (+%d points)."] = "", -- RopeKnocking -- ["You have launched %d bazookas."] = "", -- Target_Practice_-_Bazooka_easy, Target_Practice_-_Bazooka_hard, Basic_Training_-_Bazooka -- ["You have launched %d homing bees."] = "", -- Target_Practice_-_Homing_Bee -- ["You have made %d shots."] = "", -- Basic_Training_-_Sniper_Rifle diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Maps/ClimbHome/map.lua --- a/share/hedgewars/Data/Maps/ClimbHome/map.lua Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Maps/ClimbHome/map.lua Sun Jul 05 14:53:44 2020 +0200 @@ -11,7 +11,6 @@ local RecordHeight = 33000 local RecordHeightHogName = nil local Fire = {} ---local BoomFire = nil local HH = {} local totalHedgehogs = 0 local deadHedgehogs = 0 @@ -91,10 +90,9 @@ MineDudPercent = 0 EnableGameFlags(gfOneClanMode) DisableGameFlags(gfBottomBorder+gfBorder) - --This reduced startup time by only about 15% and looked ugly - --EnableGameFlags(gfDisableLandObjects) - -- force seed instead. Some themes will still be easier, but at least you won't luck out on the same theme - Seed = ClimbHome + -- gfDisableLandObjects is not used. This reduced startup time by only about 15% and looked ugly + -- Force seed so the land objects are the same. Some themes will still be easier, but at least you won't luck out on the same theme + Seed = "" -- Disable Sudden Death WaterRise = 0 HealthDecrease = 0 @@ -125,7 +123,6 @@ end function onGameStart() - --SetClanColor(ClansCount-1, 0x0000ffff) appears to be broken SendHealthStatsOff() local recordInfo = "" if isSinglePlayer then @@ -139,7 +136,6 @@ local x = 1818 for h,i in pairs(HH) do if h ~= nil then - -- SetGearPosition(h,x,32549) SetGearPosition(h,x,108) SetHealth(h,1) if x < 1978 then x = x+32 else x = 1818 end @@ -151,7 +147,7 @@ SetState(h,bor(GetState(h),gstInvisible)) end end --- 1925,263 - Mr. Mine position + -- 1925,263 - Mr. Mine position MrMine = AddGear(1925,263,gtMine,0,0,0,0) for i=0, TeamsCount-1 do SetTeamLabel(GetTeamName(i), "0") @@ -225,17 +221,10 @@ CakeTries = 0 end ---function onGearDelete(gear) --- if gear == WaterRise and MaxHeight > 500 and CurrentHedgehog ~= nil and band(GetState(CurrentHedgehog),gstHHDriven) ~= 0 then --- WaterRise = AddGear(0,0,gtWaterUp, 0, 0, 0, 0) --- end ---end - function FireBoom(x,y,d) -- going to add for rockets too PlaySound(sndExplosion) AddVisualGear(x,y,vgtExplosion,0,false) -- should approximate circle by removing corners - --if BoomFire == nil then BoomFire = {} end for i = 0,50 do fx = GetRandom(d)-div(d,2) fy = GetRandom(d)-div(d,2) @@ -253,7 +242,6 @@ SetTag(flame, 999999+i) SetFlightTime(flame, 0) Fire[flame]=1 --- BoomFire[flame]=1 end end @@ -267,13 +255,6 @@ dummySkip = 0 end - --if BoomFire ~= nil then - -- for f,i in pairs(BoomFire) do - -- if band(GetState(f),gstCollision~=0) then DeleteGear(f) end - -- end - -- BoomFire = nil - --end - for s,i in pairs(Stars) do local _, Y = GetVisualGearValues(s) if Y ~= nil and Y > WaterLine + 500 then @@ -333,7 +314,7 @@ AddCaption(loc("Don't touch the flames!")) CakeFireWarning = true end - FireBoom(cx,cy,200) -- todo animate + FireBoom(cx,cy,200) -- TODO: animate DeleteGear(Cake) end end @@ -372,7 +353,6 @@ 999999999, -- frameticks sprStar, -- star 0, c) - --, 0xFFCC00FF) -- could be fun to make colour shift as you rise... Stars[s] = 1 end end @@ -693,7 +673,6 @@ if teamBests[teamName] < actualHeight then teamBests[teamName] = actualHeight end if teamScoreStats[teamName] == nil then teamScoreStats[teamName] = {} end table.insert(teamScoreStats[teamName], actualHeight) - --SendStat(siClanHealth, tostring(teamBests[teamName]), teamName) end function makeMultiPlayerWinnerStat(gear) diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Missions/Campaign/A_Classic_Fairytale/first_blood.lua --- a/share/hedgewars/Data/Missions/Campaign/A_Classic_Fairytale/first_blood.lua Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Missions/Campaign/A_Classic_Fairytale/first_blood.lua Sun Jul 05 14:53:44 2020 +0200 @@ -487,7 +487,7 @@ end local x = GetX(youngh) local y = GetY(youngh) - return x < 3005 and y > 1500 and StoppedGear(youngh) + return x > 2575 and x < 3016 and y > 1538 and StoppedGear(youngh) end function CheckOnOrPastMoleHead() diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/cosmos.hwp Binary file share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/cosmos.hwp has changed diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/cosmos.lua --- a/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/cosmos.lua Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/cosmos.lua Sun Jul 05 14:53:44 2020 +0200 @@ -117,11 +117,7 @@ HealthDecrease = 0 -- completed main missions status = getCompletedStatus() - if status.death01 then - Map = "cosmos2_map" - else - Map = "cosmos_map" -- custom map included in file - end + Map = "cosmos_map" -- custom map included in file Theme = "Nature" -- Hero teamC.name = AddMissionTeam(teamC.color) @@ -187,6 +183,15 @@ end function onGameStart() + -- Place meteorite on map + if status.final then + -- Campaign complete: Blown-up meteorite sprite + PlaceSprite(3171, 909, sprCustom2, 0, nil, false, false, false) + elseif status.death01 then + -- death01 mission complete: Normal meteorite sprite + PlaceSprite(3171, 909, sprCustom1, 0, nil, false, false, false) + end + -- wait for the first turn to start AnimWait(hero.gear, 3000) @@ -626,8 +631,8 @@ end end if status.final then - vgear = AddVisualGear(3070, 810, vgtBeeTrace, 0, false) - vgear = AddVisualGear(3070, 790, vgtBeeTrace, 0, false) + vgear = AddVisualGear(3080, 810, vgtBeeTrace, 0, false) + vgear = AddVisualGear(3080, 790, vgtBeeTrace, 0, false) end end diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/death02.lua --- a/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/death02.lua Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/death02.lua Sun Jul 05 14:53:44 2020 +0200 @@ -123,12 +123,14 @@ function onGearDelete(gear) if isHog(gear) then - -- Set health to 100 (with heal effect, if health was smaller) - local healthDiff = 100 - GetHealth(hero.gear) - if healthDiff > 1 then - HealHog(hero.gear, healthDiff, true, 0x00FF00FF) - else - SetHealth(hero.gear, 100) + if CurrentHedgehog == hero.gear then + -- Set health to 100 (with heal effect, if health was smaller) + local healthDiff = 100 - GetHealth(hero.gear) + if healthDiff > 1 then + HealHog(hero.gear, healthDiff, true, 0x00FF00FF) + else + SetHealth(hero.gear, 100) + end end local deadHog = getHog(gear) if deadHog.weapon == amMortar then @@ -155,7 +157,7 @@ end function onGearDamage(gear, damage) - if isHog(gear) and GetHealth(hero.gear) then + if isHog(gear) and GetHealth(hero.gear) and CurrentHedgehog == hero.gear then local bonusHealth = div(damage, 3) HealHog(hero.gear, bonusHealth, true, 0xFF0000FF) end diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/moon01.lua --- a/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/moon01.lua Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/moon01.lua Sun Jul 05 14:53:44 2020 +0200 @@ -20,6 +20,7 @@ local checkPointReached = 1 -- 1 is start of the game local afterDialog02 = false local gameOver = false +local minionsDead = false -- dialogs local dialog01 = {} local dialog02 = {} @@ -262,6 +263,9 @@ EndTurn(true) end end + if minionsDead and (not (professor.dead or GetHealth(professor.gear) == nil or GetHealth(professor.gear) == 0)) then + FollowGear(professor.gear) + end end function onPrecise() @@ -444,9 +448,11 @@ end function minionsDeath(gear) + minionsDead = true if professor.dead or GetHealth(professor.gear) == nil or GetHealth(professor.gear) == 0 then return end if gameOver then return end if (not IsHogAlive(hero.gear)) or (not StoppedGear(hero.gear)) then return end + SetTeamPassive(teamC.name, false) AddAnim(dialog05) end diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/moon02.lua --- a/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/moon02.lua Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Missions/Campaign/A_Space_Adventure/moon02.lua Sun Jul 05 14:53:44 2020 +0200 @@ -130,6 +130,9 @@ runnerTime = runnerTime + runner.places[currentPosition].turnTime SetTeamLabel(teamB.name, string.format(loc("%.1fs"), runnerTime/1000)) else + if currentPosition > 2 then + AddCaption(loc("Go, get him again!"), capcolDefault, capgrpGameState) + end SetWeapon(amRope) SetTurnTimeLeft(runner.places[currentPosition].turnTime + previousTimeLeft) previousTimeLeft = 0 @@ -268,9 +271,6 @@ function moveRunner() if currentPosition == 4 then currentPosition = currentPosition + 1 - if GetX(hero.gear) > GetX(runner.gear) then - HogTurnLeft(runner.gear, false) - end AddAnim(dialog02) -- Update time record @@ -295,7 +295,6 @@ AddAmmo(hero.gear, amRope, 1) if currentPosition ~= 1 then if currentPosition > 1 and currentPosition < 4 then - AnimCaption(hero.gear, loc("Go, get him again!"), 3000) AnimSay(runner.gear, loc("You got me!"), SAY_SAY, 3000) end runnerCaught = true @@ -306,6 +305,9 @@ SetGearPosition(runner.gear, runner.places[currentPosition].x, runner.places[currentPosition].y) EndTurn(true) end + if runner.gear and hero.gear then + HogTurnLeft(runner.gear, GetX(hero.gear) < GetX(runner.gear)) + end end function lose() @@ -341,6 +343,7 @@ end function win() + AnimSetInputMask(0) SendStat(siGameResult, loc("Congratulations, you are the fastest!")) -- siCustomAchievements were added earlier SendStat(siPointType, "!TIME") diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Missions/Challenge/ClimbHome.lua --- a/share/hedgewars/Data/Missions/Challenge/ClimbHome.lua Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Missions/Challenge/ClimbHome.lua Sun Jul 05 14:53:44 2020 +0200 @@ -4,7 +4,6 @@ -- trying to allow random theme, but fixed theme objects... -- Also skip some ugly themes, or ones where the sky is "meh" ---local themes = { "Art","Cake","City","EarthRise","Halloween","Olympics","Underwater","Bamboo","Castle","Compost","Eyes","Hell","Planes","Bath","Cave","CrazyMission","Freeway","Island","Sheep","Blox","Cheese","Deepspace","Fruit","Jungle","Snow","Brick","Christmas","Desert","Golf","Nature","Stage" } local themes = {"Christmas","Hell","Bamboo","City","Island","Bath","Compost","Jungle","Desert","Nature","Olympics","Brick","EarthRise","Sheep","Cake","Freeway","Snow","Castle","Fruit","Stage","Cave","Golf","Cheese","Halloween"} local totalHedgehogs = 0 local HH = {} @@ -13,9 +12,9 @@ function onGameInit() + Theme = themes[GetRandom(#themes)+1] -- Ensure people get same map for same theme - Theme = themes[GetRandom(#themes)+1] - Seed = ClimbHome + Seed = "" TurnTime = MAX_TURN_TIME EnableGameFlags(gfOneClanMode) DisableGameFlags(gfBottomBorder+gfBorder) diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Missions/Challenge/User_Mission_-_Rope_Knock_Challenge.lua --- a/share/hedgewars/Data/Missions/Challenge/User_Mission_-_Rope_Knock_Challenge.lua Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Missions/Challenge/User_Mission_-_Rope_Knock_Challenge.lua Sun Jul 05 14:53:44 2020 +0200 @@ -1,348 +1,133 @@ -HedgewarsScriptLoad("/Scripts/Utils.lua") HedgewarsScriptLoad("/Scripts/Locale.lua") +HedgewarsScriptLoad("/Scripts/RopeKnocking.lua") -local hhs = {} -local missionWon = nil -local missionEndHandled = false -local endTimer = 1000 -local hogsKilled = 0 -local finishTime -local ouchies = false -local valkyriesTimer = -1 +-- In this mission, the names of the enemy hogs are chosen randomly from this list. +-- As a nod to the community, this list contains names of actual users/players; +-- Mostly developers, contributors, high-ranking players in a shoppa tournament, +-- highly active forum users. -local HogData = { - {"amn", "NinjaFull",false}, - {"alfadur", "NoHat",false}, - {"Anachron", "war_americanww2helmet",false}, - {"Bufon", "ShaggyYeti",false}, - {"burp", "lambda",false}, - {"Blue", "cap_blue",false}, - {"bender", "NoHat",false}, - {"Castell", "NoHat",false}, - {"cekoto", "NoHat",false}, - {"CheezeMonkey", "NoHat",false}, - {"claymore", "NoHat",false}, - {"CIA-144", "cyborg1",false}, - {"cri.the.grinch", "sf_blanka",false}, - {"eldiablo", "Evil",false}, - {"Displacer", "fr_lemon",false}, - {"doomy", "NoHat",false}, - {"Falkenauge", "NoHat",false}, - {"FadeOne", "NoHat",false}, - {"hayaa", "NoHat",false}, - {"Hermes", "laurel",false}, - {"Henek", "WizardHat",false}, - {"HedgeKing", "NoHat",false}, - {"Izack1535", "NoHat",false}, - {"Kiofspa", "NoHat",false}, - {"KoBeWi", "NoHat",false}, - {"Komplex", "NoHat",false}, - {"koda", "poke_mudkip",false}, - {"Lalo", "NoHat",false}, - {"Logan", "NoHat",false}, - {"lollkiller", "NoHat",false}, - {"Luelle", "NoHat",false}, - {"mikade", "Skull",false}, - {"Mushi", "sm_daisy",false}, - {"Naboo", "NoHat",false}, - {"nemo", "bb_bub",false}, - {"practice", "NoHat",false}, - {"Prof. Panic", "NoHat",false}, - {"Randy", "zoo_Sheep",false}, - {"rhino", "NinjaTriangle",false}, - {"Radissthor", "NoHat",false}, - {"Sami", "sm_peach",false}, - {"soreau", "NoHat",false}, - {"Solar", "pinksunhat",false}, - {"sparkle", "NoHat",false}, - {"szczur", "mp3",false}, - {"sdw195", "NoHat",false}, - {"sphrix", "TeamTopHat",false}, - {"sheepluva", "zoo_Sheep",false}, - {"Smaxx", "NoHat",false}, - {"shadowzero", "NoHat",false}, - {"Star and Moon", "SparkleSuperFun",false}, - {"The 24", "NoHat",false}, - {"TLD", "NoHat",false}, - {"Tiyuri", "sf_ryu",false}, - {"unC0Rr", "cyborg1",false}, - {"Waldsau", "cyborg1",false}, - {"wolfmarc", "knight",false}, - {"Wuzzy", "fr_orange",false}, - {"Xeli", "android",false} +-- NOTE: These names are intentionally not translated. +local hogData = { + {"amn", "NinjaFull"}, + {"alfadur", "NoHat"}, + {"Anachron", "war_americanww2helmet"}, + {"Bufon", "ShaggyYeti"}, + {"burp", "lambda"}, + {"Blue", "cap_blue"}, + {"bender", "NoHat"}, + {"Castell", "NoHat"}, + {"cekoto", "NoHat"}, + {"CheezeMonkey", "NoHat"}, + {"claymore", "NoHat"}, + {"CIA-144", "cyborg1"}, + {"cri.the.grinch", "sf_blanka"}, + {"eldiablo", "Evil"}, + {"Displacer", "fr_lemon"}, + {"doomy", "NoHat"}, + {"Falkenauge", "NoHat"}, + {"FadeOne", "NoHat"}, + {"hayaa", "NoHat"}, + {"Hermes", "laurel"}, + {"Henek", "WizardHat"}, + {"HedgeKing", "NoHat"}, + {"Izack1535", "NoHat"}, + {"Kiofspa", "NoHat"}, + {"KoBeWi", "NoHat"}, + {"Komplex", "NoHat"}, + {"koda", "poke_mudkip"}, + {"Lalo", "NoHat"}, + {"Logan", "NoHat"}, + {"lollkiller", "NoHat"}, + {"Luelle", "NoHat"}, + {"mikade", "Skull"}, + {"Mushi", "sm_daisy"}, + {"Naboo", "NoHat"}, + {"nemo", "bb_bub"}, + {"practice", "NoHat"}, + {"Prof. Panic", "NoHat"}, + {"Randy", "zoo_Sheep"}, + {"rhino", "NinjaTriangle"}, + {"Radissthor", "NoHat"}, + {"Sami", "sm_peach"}, + {"soreau", "NoHat"}, + {"Solar", "pinksunhat"}, + {"sparkle", "NoHat"}, + {"szczur", "mp3"}, + {"sdw195", "NoHat"}, + {"sphrix", "TeamTopHat"}, + {"sheepluva", "zoo_Sheep"}, + {"Smaxx", "NoHat"}, + {"shadowzero", "NoHat"}, + {"Star and Moon", "SparkleSuperFun"}, + {"The 24", "NoHat"}, + {"TLD", "NoHat"}, + {"Tiyuri", "sf_ryu"}, + {"unC0Rr", "cyborg1"}, + {"Waldsau", "cyborg1"}, + {"wolfmarc", "knight"}, + {"Wuzzy", "fr_orange"}, + {"Xeli", "android"} +} - } - -local playerTeamName - -function GetKillScore() - return math.ceil((hogsKilled / 16)*6000) -end - -function ProtectEnemies() - for i=1, 16 do - if hhs[i] and GetHealth(hhs[i]) then - SetEffect(hhs[i], heInvulnerable, 1) - end +local function assignNamesAndHats(team) + for t=1, #team do + local d = 1 + GetRandom(#hogData) + team[t].name = hogData[d][1] + team[t].hat = hogData[d][2] + table.remove(hogData, d) end end -function GameOverMan() - StopMusicSound(sndRideOfTheValkyries) - valkyriesTimer = -1 - missionWon = false - ProtectEnemies() - SendStat(siGameResult, loc("Challenge over!")) - local score = GetKillScore() - SendStat(siCustomAchievement, string.format(loc("You have killed %d of 16 hedgehogs (+%d points)."), hogsKilled, score)) - SendStat(siPointType, "!POINTS") - SendStat(siPlayerKills, tostring(score), playerTeamName) - - -- Update highscore - updateChallengeRecord("Highscore", score) - - EndGame() -end - -function GG() - missionWon = true - local completeTime = (TurnTime - finishTime) / 1000 - ShowMission(loc("Rope-knocking Challenge"), loc("Challenge completed!"), loc("Congratulations!") .. "|" .. string.format(loc("Completion time: %.2fs"), completeTime), 0, 0) - PlaySound(sndHomerun) - SendStat(siGameResult, loc("Challenge completed!")) - local hogScore = GetKillScore() - local timeScore = math.ceil((finishTime/TurnTime)*6000) - local score = hogScore + timeScore - - SendStat(siCustomAchievement, string.format(loc("You have killed %d of 16 hedgehogs (+%d points)."), hogsKilled, hogScore)) - SendStat(siCustomAchievement, string.format(loc("You have completed this challenge in %.2f s (+%d points)."), completeTime, timeScore)) - SendStat(siPointType, "!POINTS") - SendStat(siPlayerKills, tostring(score), playerTeamName) - SetTeamLabel(playerTeamName, tostring(score)) - - -- Update highscore - updateChallengeRecord("Highscore", score) - - if hhs[0] and GetHealth(hhs[0]) then - SetEffect(hhs[0], heInvulnerable, 1) - end - SetTurnTimeLeft(MAX_TURN_TIME) -end - -function AssignCharacter(p) - - done = false - sanityCheck = 0 - - while(done == false) do - i = 1+ GetRandom(#HogData) - if HogData[i][3] == false then - HogData[i][3] = true - done = true - SetHogName(hhs[p], HogData[i][1]) - SetHogHat(hhs[p], HogData[i][2]) - elseif HogData[i][3] == true then - sanityCheck = sanityCheck +1 - if sanityCheck == 100 then - done = true - SetHogName(hhs[p], "Newbie") - SetHogHat(hhs[p], "NoHat") - end - end - - end - -end - -function onGameInit() - - --Seed = 1 - GameFlags = gfBorder + gfSolidLand - - TurnTime = 180 * 1000 - Map = "Ropes" - Theme = "Eyes" - - -- Disable Sudden Death - WaterRise = 0 - HealthDecrease = 0 - - CaseFreq = 0 - MinesNum = 0 - Explosives = 0 +local enemyTeam1 = { + { x = 3350, y = 570 }, + { x = 3039, y = 1300 }, + { x = 2909, y = 430 }, + { x = 2150, y = 879 }, + { x = 1735, y = 1136 }, + { x = 1563, y = 553 }, + { x = 679, y = 859 }, + { x = 1034, y = 251 }, +} +local enemyTeam2 = { + { x = 255, y = 91 }, + { x = 2671, y = 7 }, + { x = 2929, y = 244 }, + { x = 1946, y = 221 }, + { x = 3849, y = 1067 }, + { x = 3360, y = 659 }, + { x = 3885, y = 285 }, + { x = 935, y = 1160 }, +} - playerTeamName = AddMissionTeam(-1) - hhs[0] = AddMissionHog(1) - - AddTeam(loc("Unsuspecting Louts"), -2, "Simple", "Island", "Default", "cm_face") - for i = 1, 8 do - -- The name "generic" is a placeholder and will be replaced in AssignCharacter - hhs[i] = AddHog("generic", 0, 1, "NoHat") - end - - AddTeam(loc("Unlucky Sods"), -2, "Simple", "Island", "Default", "cm_balrog") - for i = 9, 16 do - hhs[i] = AddHog("generic", 0, 1, "NoHat") - end - -end - - - -function onGameStart() - SendHealthStatsOff() - - local recordInfo = getReadableChallengeRecord("Highscore") - if recordInfo == nil then - recordInfo = "" - else - recordInfo = "|" .. recordInfo - end - ShowMission ( - loc("Rope-knocking Challenge"), - loc("Challenge"), - loc("Use the rope to knock your enemies to their doom.") .. "|" .. - loc("Finish this challenge as fast as possible to earn bonus points.").. recordInfo, - -amRope, 4000) - SetTeamLabel(playerTeamName, "0") - - PlaceGirder(46,1783, 0) +assignNamesAndHats(enemyTeam1) +assignNamesAndHats(enemyTeam2) - SetGearPosition(hhs[0], 2419, 1769) - SetGearPosition(hhs[1], 3350, 570) - SetGearPosition(hhs[2], 3039, 1300) - SetGearPosition(hhs[3], 2909, 430) - SetGearPosition(hhs[4], 2150, 879) - SetGearPosition(hhs[5], 1735, 1136) - SetGearPosition(hhs[6], 1563, 553) - SetGearPosition(hhs[7], 679, 859) - SetGearPosition(hhs[8], 1034, 251) - SetGearPosition(hhs[9], 255, 67) - SetGearPosition(hhs[10], 2671, 7) - SetGearPosition(hhs[11], 2929, 244) - SetGearPosition(hhs[12], 1946, 221) - SetGearPosition(hhs[13], 3849, 1067) - SetGearPosition(hhs[14], 3360, 659) - SetGearPosition(hhs[15], 3885, 285) - SetGearPosition(hhs[16], 935, 1160) - HogTurnLeft(hhs[0], true) - - for i = 1, 16 do - AssignCharacter(i) - end - -end - -function onGameTick() - - if (TurnTimeLeft == 1) and (missionWon == nil) then - GameOverMan() - end - - if missionWon ~= nil then - - endTimer = endTimer - 1 - if endTimer == 1 then - EndGame() - end - - if not missionEndHandled then - if missionWon == true then - SaveMissionVar("Won", "true") - AddCaption(loc("Victory!"), capcolDefault, capgrpGameState) - end - missionEndHandled = true - end - - end - -end - -function onGameTick20() - if (valkyriesTimer > 0) then - valkyriesTimer = valkyriesTimer - 20 - if valkyriesTimer <= 0 then - StopMusicSound(sndRideOfTheValkyries) - end - end -end - -function onGearDamage(gear, damage) - - if gear == hhs[0] then - ouchies = true - StopMusicSound(sndRideOfTheValkyries) - valkyriesTimer = -1 - ProtectEnemies() - end +RopeKnocking({ + missionName = loc("Rope-knocking Challenge"), + map = "Ropes", + theme = "Eyes", + turnTime = 180000, + valkyries = true, + playerTeam = { + x = 2419, + y = 1769, + faceLeft = true, + }, + enemyTeams = { + { + name = loc("Unsuspecting Louts"), + flag = "cm_face", + hogs = enemyTeam1, + }, + { + name = loc("Unlucky Sods"), + flag = "cm_balrog", + hogs = enemyTeam2, + }, + }, + onGameStart = function() + PlaceGirder(46,1783, 0) + end, +}) - if gear ~= hhs[0] and GetGearType(gear) == gtHedgehog and missionWon == nil and ouchies == false then - - AddVisualGear(GetX(gear), GetY(gear), vgtBigExplosion, 0, false) - DeleteGear(gear) - PlaySound(sndExplosion) - AddCaption(string.format(knockTaunt(), GetHogName(gear)), capcolDefault, capgrpMessage) - - hogsKilled = hogsKilled +1 - SetTeamLabel(playerTeamName, tostring(GetKillScore())) - - if hogsKilled == 15 then - PlayMusicSound(sndRideOfTheValkyries) - -- Time in ms after which to return to normal music - valkyriesTimer = 20000 - elseif hogsKilled == 16 then - finishTime = TurnTimeLeft - GG() - end - - end - -end - -function knockTaunt() - local r = math.random(0,23) - local taunt - if r == 0 then taunt = loc("%s has been knocked out.") - elseif r == 1 then taunt = loc("%s hit the ground.") - elseif r == 2 then taunt = loc("%s splatted.") - elseif r == 3 then taunt = loc("%s was smashed.") - elseif r == 4 then taunt = loc("%s felt unstable.") - elseif r == 5 then taunt = loc("%s exploded.") - elseif r == 6 then taunt = loc("%s fell from a high cliff.") - elseif r == 7 then taunt = loc("%s goes the way of the lemming.") - elseif r == 8 then taunt = loc("%s was knocked away.") - elseif r == 9 then taunt = loc("%s was really unlucky.") - elseif r == 10 then taunt = loc("%s felt victim to rope-knocking.") - elseif r == 11 then taunt = loc("%s had no chance.") - elseif r == 12 then taunt = loc("%s was a good target.") - elseif r == 13 then taunt = loc("%s spawned at a really bad position.") - elseif r == 14 then taunt = loc("%s was doomed from the beginning.") - elseif r == 15 then taunt = loc("%s has fallen victim to gravity.") - elseif r == 16 then taunt = loc("%s hates Newton.") -- Isaac Newton - elseif r == 17 then taunt = loc("%s had it coming.") - elseif r == 18 then taunt = loc("%s is eliminated!") - elseif r == 19 then taunt = loc("%s fell too fast.") - elseif r == 20 then taunt = loc("%s flew like a rock.") - elseif r == 21 then taunt = loc("%s stumbled.") - elseif r == 22 then taunt = loc("%s was shoved away.") - elseif r == 23 then taunt = loc("%s didn't expect that.") - end - return taunt -end - -function onGearDelete(gear) - - if (gear == hhs[0]) and (missionWon == nil) then - GameOverMan() - end - -end - -function onAmmoStoreInit() - SetAmmo(amRope, 9, 0, 0, 0) -end - -function onNewTurn() - SetWeapon(amRope) -end diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Missions/Scenario/Big_Armory.lua --- a/share/hedgewars/Data/Missions/Scenario/Big_Armory.lua Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Missions/Scenario/Big_Armory.lua Sun Jul 05 14:53:44 2020 +0200 @@ -2,7 +2,7 @@ HedgewarsScriptLoad("/Scripts/Locale.lua") local heroAmmo = {} -for a=0, amCreeper do +for a=0, amMinigun do if a == amExtraTime then heroAmmo[a] = 2 elseif a ~= amNothing and a ~= amCreeper then diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Missions/Training/Basic_Training_-_Rope.lua --- a/share/hedgewars/Data/Missions/Training/Basic_Training_-_Rope.lua Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Missions/Training/Basic_Training_-_Rope.lua Sun Jul 05 14:53:44 2020 +0200 @@ -14,6 +14,7 @@ HedgewarsScriptLoad("/Scripts/Locale.lua") HedgewarsScriptLoad("/Scripts/Utils.lua") +HedgewarsScriptLoad("/Scripts/Achievements.lua") -- Map definition automatically converted from HWMAP file by hwmap2lua.sh local map = @@ -42,6 +43,8 @@ local gameOver = false -- game over (only victory possible) local currentTarget = 0 -- current target ID. First target = 1 local flawless = true -- flawless if no damage taken and no mistake made +local flowerPower = false -- random flower visual gears appear all ower the place +local bonusFlowerPlaced = false -- a hidden flower sprite was placed local cpX, cpY = 208, 1384 -- hog checkpoint, initialized with start coords @@ -118,6 +121,7 @@ SetHealth(hog, initHogHealthFinal) AddAmmo(hog, amRope, 1) SetGearVelocity(hog, 0, 0) + flowerPower = false if setPos then PlaySound(sndWarp) @@ -207,6 +211,13 @@ end function onGameTick() + + if flowerPower then + for i=1,2 do + AddVisualGear(math.random(-1024, LAND_WIDTH+1024), math.random(TopY-1024, LAND_HEIGHT), vgtBeeTrace, 0, false) + end + end + if gameOver or (not CurrentHedgehog) then return end @@ -255,7 +266,13 @@ if isInFinalChallenge then local dX, dY = GetGearVelocity(CurrentHedgehog) local x, y = GetGearPosition(CurrentHedgehog) - if band(GetState(CurrentHedgehog), gstHHDriven) ~= 0 and GetAmmoCount(CurrentHedgehog, amRope) == 0 and + local driven = band(GetState(CurrentHedgehog), gstHHDriven) ~= 0 + if driven and y > 1310 and x < 338 and not flowerPower then + -- Player reached the bonus flower. Enable Flower Power mode! + PlaySound(sndKiss) + flowerPower = true + end + if driven and GetAmmoCount(CurrentHedgehog, amRope) == 0 and GetFlightTime(CurrentHedgehog) == 0 and (not ropeGear) and math.abs(dX) < 5 and math.abs(dY) < 5 and (x < 3417 or y > 471) then @@ -268,6 +285,12 @@ end function onGameTick20() + if flowerPower then + if math.random(1,2) == 1 then + local vg = AddVisualGear(GetX(CurrentHedgehog), GetY(CurrentHedgehog), vgtStraightShot, sprTargetBee, false, 1) + SetVisualGearValues(vg, nil, nil, nil, nil, math.random(0, 360), nil, nil, nil, nil, 0xFFFFFFC0) + end + end if not gameOver and not target1Reached and CurrentHedgehog and gearIsInCircle(CurrentHedgehog, targetData[1][1], targetData[1][2], 48, false) then ShowMission(loc("Basic Rope Training"), loc("Target Puncher"), loc("Okay, now destroy the target|using the baseball bat.").."|".. @@ -362,6 +385,12 @@ 2, 25000) eraseGirder(4) eraseGirder(5) + -- Sneakingly place a flower sprite near spawn when player reached the last section + -- When the player reaches it, Flower Power mode is enabled + if not bonusFlowerPlaced then + PlaceSprite(240, 1360, sprTargetBee, 0) + bonusFlowerPlaced = true + end AddAmmo(hog, amRope, 1) SetHealth(hog, initHogHealthFinal) isInFinalChallenge = true @@ -379,6 +408,9 @@ AddAmmo(hog, amRope, 0) SendStat(siCustomAchievement, loc("Oh yeah! You sure know how to rope!")) SendStat(siGameResult, loc("You have finished the Basic Rope Training!")) + if flowerPower then + awardAchievement(loc("Flower Power")) + end EndGame() SetState(hog, gstWinner) gameOver = true diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Music/CMakeLists.txt diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Scripts/Multiplayer/HedgeEditor.lua --- a/share/hedgewars/Data/Scripts/Multiplayer/HedgeEditor.lua Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Scripts/Multiplayer/HedgeEditor.lua Sun Jul 05 14:53:44 2020 +0200 @@ -397,91 +397,91 @@ loc_noop("Clowns"), {"WhySoSerious","clown-copper","clown-crossed","clown","Joker"}, {loc_noop("Baggy"),loc_noop("Bingo"),loc_noop("Bobo"),loc_noop("Bozo"),loc_noop("Buster"),loc_noop("Chester"),loc_noop("Copper"),loc_noop("Heckles"),loc_noop("Giggles"),loc_noop("Jingo"),loc_noop("Molly"),loc_noop("Loopy"),loc_noop("Patches"),loc_noop("Tatters")}, - "R","cm_balls","Mobster","Rubberduck","Castle" + "R","cm_balls","Mobster_qau","Rubberduck","Castle" }, { loc_noop("Street Fighters"), {"sf_balrog","sf_blanka","sf_chunli","sf_guile","sf_honda","sf_ken","sf_ryu","sf_vega"}, {loc_noop("Balrog"),loc_noop("Blanka"),loc_noop("Chunli"),loc_noop("Guile"),loc_noop("Honda"),loc_noop("Ken"),loc_noop("Ryu"),loc_noop("Vega")}, - "F","cm_balrog","Surfer","dragonball","Castle" + "F","cm_balrog","Surfer_qau","dragonball","Castle" }, { loc_noop("Cybernetic Empire"), {"cyborg1","cyborg2"}, {loc_noop("Unit 189"),loc_noop("Unit 234"),loc_noop("Unit 333"),loc_noop("Unit 485"),loc_noop("Unit 527"),loc_noop("Unit 638"),loc_noop("Unit 709"),loc_noop("Unit 883")}, - "R","cm_binary","Robot","Grave","Castle" + "R","cm_binary","Robot_qau","Grave","Castle" }, { loc_noop("Color Squad"), {"hair_blue","hair_green","hair_red","hair_yellow","hair_purple","hair_grey","hair_orange","hair_pink"}, {loc_noop("Blue"),loc_noop("Green"),loc_noop("Red"),loc_noop("Yellow"),loc_noop("Purple"),loc_noop("Grey"),loc_noop("Orange"),loc_noop("Pink")}, - "F","mauritius","Singer","Grave","Castle" + "F","mauritius","Singer_qau","Grave","Castle" }, { loc_noop("Fruit"), {"fr_apple","fr_banana","fr_lemon","fr_orange","fr_pumpkin","fr_tomato"}, {loc_noop("Juicy"),loc_noop("Squishy"),loc_noop("Sweet"),loc_noop("Sour"),loc_noop("Bitter"),loc_noop("Ripe"),loc_noop("Rotten"),loc_noop("Fruity")}, - "R","cm_mog","Default","Cherry","Castle" + "R","cm_mog","Default_qau","Cherry","Castle" }, { loc_noop("The Police"), {"bobby","bobby2v","policecap","policegirl","royalguard"}, {loc_noop("Hightower"),loc_noop("Lassard"),loc_noop("Callahan"),loc_noop("Jones"),loc_noop("Harris"),loc_noop("Thompson"),loc_noop("Mahoney"),loc_noop("Hooks"),loc_noop("Tackleberry")}, - "R","cm_star","British","Statue","Castle" + "R","cm_star","British_qau","Statue","Castle" }, { loc_noop("The Ninja-Samurai Alliance"), {"NinjaFull","NinjaStraight","NinjaTriangle","Samurai","StrawHat","StrawHatEyes","StrawHatFacial","naruto"}, {loc_noop("Bushi"),loc_noop("Tatsujin"),loc_noop("Itami"),loc_noop("Arashi"),loc_noop("Shinobi"),loc_noop("Ukemi"),loc_noop("Godai"),loc_noop("Kenshi"),loc_noop("Ninpo")}, - "R","japan","Default","octopus","Castle" + "R","japan","Default_qau","octopus","Castle" }, { loc_noop("Pokémon"), {"poke_ash","poke_charmander","poke_chikorita","poke_jigglypuff","poke_lugia","poke_mudkip","poke_pikachu","poke_slowpoke","poke_squirtle","poke_voltorb"}, {loc_noop("Ash"),loc_noop("Charmander"),loc_noop("Chikorita"),loc_noop("Jigglypuff"),loc_noop("Lugia"),loc_noop("Mudkip"),loc_noop("Pikachu"),loc_noop("Slowpoke"),loc_noop("Squirtle"),loc_noop("Voltorb")}, - "FR","cm_pokemon","Default","pokeball","Castle" + "FR","cm_pokemon","Default_qau","pokeball","Castle" }, { loc_noop("The Zoo"), {"zoo_Bat","zoo_Beaver","zoo_Bunny","zoo_Deer","zoo_Hedgehog","zoo_Moose","zoo_Pig","zoo_Porkey","zoo_Sheep","zoo_chicken","zoo_elephant","zoo_fish","zoo_frog","zoo_snail","zoo_turtle"}, {loc_noop("Batty"),loc_noop("Tails"),loc_noop("Bunny"),loc_noop("Deer"),loc_noop("Spikes"),loc_noop("Horns"),loc_noop("Bacon"),loc_noop("Porkey"),loc_noop("Sheepy"),loc_noop("Chicken"),loc_noop("Trunks"),loc_noop("Fishy"),loc_noop("Legs"),loc_noop("Slimer"),loc_noop("Roshi")}, - "FR","cm_birdy","Default","Bone","Castle" + "FR","cm_birdy","Default_qau","Bone","Castle" }, { loc_noop("The Devs"), {"ushanka","zoo_Sheep","bb_bob","Skull","poke_mudkip","lambda","WizardHat","sf_ryu","android","fr_lemon","mp3"}, {loc_noop("unC0Rr"), loc_noop("sheepluva"), loc_noop("nemo"), loc_noop("mikade"), loc_noop("koda"), loc_noop("burp"),loc_noop("HeneK"),loc_noop("Tiyuri"),loc_noop("Xeli"),loc_noop("Displacer"),loc_noop("szczur")}, - "FR","cm_hw","Classic","Statue","Castle" + "FR","cm_hw","Classic_qau","Statue","Castle" }, { loc_noop("Mushroom Kingdom"), {"sm_daisy","sm_luigi","sm_mario","sm_peach","sm_toad","sm_wario","NoHat","NoHat"}, {loc_noop("Daisy"),loc_noop("Luigi"),loc_noop("Mario"),loc_noop("Princess Peach"),loc_noop("Toad"),loc_noop("Wario"),loc_noop("Yoshi"),loc_noop("Waluigi")}, - "FR","comoros","Default","Badger","Castle" + "FR","comoros","Default_qau","Badger","Castle" }, { loc_noop("Pirates"), {"pirate_jack","pirate_jack_bandana"}, {loc_noop("Rusted Diego"),loc_noop("Fuzzy Beard"),loc_noop("Al.Kaholic"),loc_noop("Morris"),loc_noop("Yumme Gunpowder"),loc_noop("Cutlass Cain"),loc_noop("Jim Morgan"),loc_noop("Silver"),loc_noop("Dubloon Devil"),loc_noop("Ugly Mug"),loc_noop("Fair Wind"),loc_noop("Scallywag"),loc_noop("Salty Dog"),loc_noop("Bearded Beast"),loc_noop("Timbers"),loc_noop("Both Barrels"),loc_noop("Jolly Roger")}, - "R","cm_pirate","Pirate","chest","Castle" + "R","cm_pirate","Pirate_qau","chest","Castle" }, { loc_noop("Gangsters"), {"Moustache","Cowboy","anzac","Bandit","thug","Jason","NinjaFull","chef"}, {loc_noop("The Boss"),loc_noop("Jimmy"),loc_noop("Frankie"),loc_noop("Morris"),loc_noop("Mooney"),loc_noop("Knives"),loc_noop("Tony"),loc_noop("Meals")}, - "F","cm_anarchy","Mobster","deadhog","Castle" + "F","cm_anarchy","Mobster_qau","deadhog","Castle" }, @@ -489,7 +489,7 @@ loc_noop("Twenty-Twenty"), {"Glasses","lambda","SunGlasses","Sniper","Terminator_Glasses","Moustache_glasses","doctor","punkman","rasta"}, {loc_noop("Specs"),loc_noop("Speckles"),loc_noop("Spectator"),loc_noop("Glasses"),loc_noop("Glassy"),loc_noop("Harry Potter"),loc_noop("Goggles"),loc_noop("Clark Kent"),loc_noop("Goggs"),loc_noop("Lightbender"),loc_noop("Specs Appeal"),loc_noop("Four Eyes")}, - "R","cm_face","Default","eyecross","Castle" + "R","cm_face","Default_qau","eyecross","Castle" }, @@ -497,28 +497,28 @@ loc_noop("Monsters"), {"Skull","Jason","ShaggyYeti","Zombi","cyclops","Mummy","hogpharoah","vampirichog"}, {loc_noop("Bones"),loc_noop("Jason"),loc_noop("Yeti"),loc_noop("Zombie"),loc_noop("Old One Eye"),loc_noop("Ramesses"),loc_noop("Xerxes"),loc_noop("Count Hogula")}, - "FR","cm_vampire","Default","octopus","Castle" + "FR","cm_vampire","Default_qau","octopus","Castle" }, { loc_noop("The Iron Curtain"), {"ushanka","war_sovietcomrade1","war_sovietcomrade1","ushanka"}, {loc_noop("Alex"),loc_noop("Sergey"),loc_noop("Vladimir"),loc_noop("Andrey"),loc_noop("Dimitry"),loc_noop("Ivan"),loc_noop("Oleg"),loc_noop("Kostya"),loc_noop("Anton"),loc_noop("Eugene")}, - "R","cm_soviet","Russian","skull","Castle" + "R","cm_soviet","Russian_qau","skull","Castle" }, { loc_noop("Desert Storm"), {"war_desertofficer","war_desertgrenadier1","war_desertmedic","war_desertsapper1","war_desertgrenadier2","war_desertgrenadier4","war_desertsapper2","war_desertgrenadier5"}, {loc_noop("Brigadier Briggs"),loc_noop("Lt. Luke"),loc_noop("Sgt. Smith"),loc_noop("Corporal Calvin"),loc_noop("Frank"),loc_noop("Joe"),loc_noop("Sam"),loc_noop("Donald")}, - "F","bhutan","Default","Grave","Castle" + "F","bhutan","Default_qau","Grave","Castle" }, { loc_noop("The Hospital"), {"doctor","nurse","war_britmedic","war_desertmedic","war_germanww2medic"}, {loc_noop("Dr. Blackwell"),loc_noop("Dr. Drew"),loc_noop("Dr. Harvey"),loc_noop("Dr. Crushing"),loc_noop("Dr. Jenner"),loc_noop("Dr. Barnard"),loc_noop("Dr. Parkinson"),loc_noop("Dr. Banting"),loc_noop("Dr. Horace"),loc_noop("Dr. Hollows"),loc_noop("Dr. Jung")}, - "R","cm_firstaid","Default","heart","Castle" + "R","cm_firstaid","Default_qau","heart","Castle" } } @@ -1503,7 +1503,7 @@ if not tFort then tFort = "Castle" end if not tGrave then tGrave = "Statue" end if not tFlag then tFlag= "hedgewars" end - if not tVoice then tVoice = "Default" end + if not tVoice then tVoice = "Default_qau" end lastRecordedTeam = GetHogTeamName(gear) @@ -3117,9 +3117,9 @@ elseif (preciseOn == true) and (s == 1) then helpDisabled = not(helpDisabled) if helpDisabled then - AddCaption(loc("Help Disabled"), colorInfoMessage, capgrpVolume) + AddCaption(loc("Help Disabled"), capcolSetting, capgrpVolume) else - AddCaption(loc("Help Enabled"), colorInfoMessage, capgrpVolume) + AddCaption(loc("Help Enabled"), capcolSetting, capgrpVolume) end updateHelp() elseif (cat[cIndex] == loc("Sprite Placement Mode")) or (cat[cIndex] == loc("Girder Placement Mode")) or (cat[cIndex] == loc("Rubber Placement Mode")) or (cat[cIndex] == loc("Sprite Modification Mode")) then @@ -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 b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Scripts/Multiplayer/Mutant.lua --- a/share/hedgewars/Data/Scripts/Multiplayer/Mutant.lua Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Scripts/Multiplayer/Mutant.lua Sun Jul 05 14:53:44 2020 +0200 @@ -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() @@ -813,6 +816,11 @@ if GetGearType(gear) == gtHedgehog then numhhs = numhhs - 1 + if (not gameOver) and (gear == mutant) then + mutant = nil + mt_hurt = false + end + local found for i=0, #hhs do if hhs[i] == gear then diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Scripts/Multiplayer/Racer.lua --- a/share/hedgewars/Data/Scripts/Multiplayer/Racer.lua Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Scripts/Multiplayer/Racer.lua Sun Jul 05 14:53:44 2020 +0200 @@ -55,6 +55,9 @@ local specialPointsY = {} local specialPointsCount = 0 +local landObjectPoints = {} +local landObjects = {} + local TeamRope = false local waypointCursor = false @@ -101,6 +104,7 @@ local wpCol = {} local wpActive = {} local wpRad = 450 +local WAYPOINT_RADIUS_MIN = 40 local wpCount = 0 local wpLimit = 8 @@ -193,7 +197,7 @@ end end if params["waypointradius"] ~= nil then - wpRad = math.max(40, math.floor(tonumber(params["waypointradius"]))) + wpRad = math.max(WAYPOINT_RADIUS_MIN, math.floor(tonumber(params["waypointradius"]))) if type(wpRad) ~= "number" then wpRad = 450 end @@ -609,11 +613,10 @@ ---------------------------------- function onGameInit() - EnableGameFlags(gfInfAttack) + EnableGameFlags(gfInfAttack, gfSolidLand) -- Force-disable various game flags that would break the script DisableGameFlags(gfKing, gfSwitchHog, gfAISurvival, gfPlaceHog, gfTagTeam) CaseFreq = 0 - TurnTime = 90000 WaterRise = 0 HealthDecrease = 0 end @@ -635,6 +638,20 @@ end function onGameStart() + + -- Adjust pre-defined waypoints in scaled drawn maps + if MapGen == mgDrawn and MapFeatureSize ~= 12 and specialPointsCount > 0 then + local landW = RightX - LeftX + 1 + local landH = LAND_HEIGHT - TopY + -- Reposition pre-defined waypoints + for i = 0, (specialPointsCount-1) do + specialPointsX[i] = LeftX + div(specialPointsX[i] * landW, 4096) + specialPointsY[i] = TopY + div(specialPointsY[i] * landH, 2048) + end + -- Scale waypoint size + wpRad = math.max(WAYPOINT_RADIUS_MIN, div(wpRad * landW, 4096)) + end + if ClansCount >= 2 then SendGameResultOff() SendRankingStatsOff() @@ -642,6 +659,11 @@ SendAchievementsStatsOff() end + -- Keep track of land objects that got placed by the scheme (mines, air mines, barrels) + for id, _ in pairs(landObjects) do + table.insert(landObjectPoints, { type = GetGearType(id), x = GetX(id), y = GetY(id) }) + end + SetSoundMask(sndIncoming, true) SetSoundMask(sndMissed, true) @@ -833,8 +855,17 @@ end end - -- Set the waypoints to unactive on new round if gameBegun and not gameOver then + + -- Reset land objects so each player starts with same racing conditions + for id,_ in pairs(landObjects) do + DeleteGear(id) + end + for i=1, #landObjectPoints do + AddGear(landObjectPoints[i].x, landObjectPoints[i].y, landObjectPoints[i].type, 0, 0, 0, 0) + end + + -- Set the waypoints to unactive for i = 0,(wpCount-1) do wpActive[i] = false wpCol[i] = waypointColour @@ -1022,18 +1053,20 @@ end function onGearAdd(gear) - - if GetGearType(gear) == gtHedgehog then + local gt = GetGearType(gear) + if gt == gtHedgehog then hhs[numhhs] = gear numhhs = numhhs + 1 SetEffect(gear, heResurrectable, 1) - elseif GetGearType(gear) == gtAirAttack then + elseif gt == gtAirAttack then cGear = gear local x,y = GetGearPosition(cGear) SetGearPosition(cGear, 10000, y) - elseif (not gameBegun) and GetGearType(gear) == gtAirBomb then + elseif (gt == gtMine or gt == gtAirMine or gt == gtExplosives) then + landObjects[gear] = true + elseif (not gameBegun) and gt == gtAirBomb then DeleteGear(gear) - elseif GetGearType(gear) == gtRope and TeamRope then + elseif gt == gtRope and TeamRope then SetTag(gear,1) SetGearValues(gear,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,GetClanColor(GetHogClan(CurrentHedgehog))) end @@ -1043,6 +1076,8 @@ if GetGearType(gear) == gtAirAttack then cGear = nil + elseif landObjects[gear] == true then + landObjects[gear] = nil elseif gear == cameraGear then cameraGear = nil end diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Scripts/RopeKnocking.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/hedgewars/Data/Scripts/RopeKnocking.lua Sun Jul 05 14:53:44 2020 +0200 @@ -0,0 +1,441 @@ +HedgewarsScriptLoad("/Scripts/Locale.lua") +HedgewarsScriptLoad("/Scripts/Utils.lua") + +local hhs = {} +local deadHogs = {} +local missionWon = nil +local endTimer = 1000 +local hogsKilled = 0 +local totalEnemies = 0 +local finishTime +local playerFail = false +local ropeGear = nil +local endGameCalled = false +local missionEndHandled = false +local valkyriesTimer = -1 + +local valkyriesDuration = 20000 +local timeBonus = 6000 +local killBonus = 6000 +local playValkyries = false + +local extraTime + +local playerTeamName +local missionName = loc("Rope-knocking Challenge") +-- Mission type: +-- 0 = none (no special handling) +-- 1 = challenge (saves mission vars) +local missionType = 1 + +local function getKillScore() + return div(hogsKilled * killBonus, totalEnemies) +end + +local function protectEnemies() + -- Protect enemy hogs + for i=1, totalEnemies do + if hhs[i] and GetHealth(hhs[i]) then + SetEffect(hhs[i], heInvulnerable, 1) + SetEffect(hhs[i], heResurrectable, 1) + end + end +end + +local function killStr(killed, total, score) + if total == 16 then + return string.format(loc("You have killed %d of 16 hedgehogs (+%d points)."), killed, score) + else + return string.format(loc("You have killed %d of %d hedgehogs (+%d points)."), killed, total, score) + end +end + +local function gameOver() + StopMusicSound(sndRideOfTheValkyries) + valkyriesTimer = -1 + missionWon = false + SendStat(siGameResult, loc("Challenge over!")) + local score = getKillScore() + SendStat(siCustomAchievement, killStr(hogsKilled, totalEnemies, score)) + SendStat(siPointType, "!POINTS") + SendStat(siPlayerKills, tostring(score), playerTeamName) + protectEnemies() + if not endGameCalled then + EndGame() + endGameCalled = true + end + if missionType == 1 then + -- Update highscore + updateChallengeRecord("Highscore", score) + end +end + +local function victory(onVictory) + missionWon = true + local e = 0 + if extraTime then + e = extraTime + end + local totalTime = TurnTime + e * totalEnemies + local completeTime = (totalTime - finishTime) / 1000 + ShowMission(missionName, loc("Challenge completed!"), loc("Congratulations!") .. "|" .. string.format(loc("Completion time: %.2fs"), completeTime), 0, 0) + PlaySound(sndHomerun) + -- Protect player hog + if hhs[0] and GetHealth(hhs[0]) then + SetEffect(hhs[0], heInvulnerable, 1) + SetEffect(hhs[0], heResurrectable, 1) + end + SendStat(siGameResult, loc("Challenge completed!")) + local hogScore = getKillScore() + local timeScore = div(finishTime * timeBonus, totalTime) + local score = hogScore + timeScore + SendStat(siCustomAchievement, killStr(hogsKilled, totalEnemies, hogScore)) + SendStat(siCustomAchievement, string.format(loc("You have completed this challenge in %.2f s (+%d points)."), completeTime, timeScore)) + SendStat(siPointType, "!POINTS") + SendStat(siPlayerKills, tostring(score), playerTeamName) + SetTeamLabel(playerTeamName, tostring(score)) + SetTurnTimeLeft(MAX_TURN_TIME) + + if missionType == 1 then + -- Update highscore + updateChallengeRecord("Highscore", score) + end + if onVictory then + onVictory() + end +end + +local function knockTaunt() + local r = math.random(0,23) + local taunt + if r == 0 then taunt = loc("%s has been knocked out.") + elseif r == 1 then taunt = loc("%s hit the ground.") + elseif r == 2 then taunt = loc("%s splatted.") + elseif r == 3 then taunt = loc("%s was smashed.") + elseif r == 4 then taunt = loc("%s felt unstable.") + elseif r == 5 then taunt = loc("%s exploded.") + elseif r == 6 then taunt = loc("%s fell from a high cliff.") + elseif r == 7 then taunt = loc("%s goes the way of the lemming.") + elseif r == 8 then taunt = loc("%s was knocked away.") + elseif r == 9 then taunt = loc("%s was really unlucky.") + elseif r == 10 then taunt = loc("%s felt victim to rope-knocking.") + elseif r == 11 then taunt = loc("%s had no chance.") + elseif r == 12 then taunt = loc("%s was a good target.") + elseif r == 13 then taunt = loc("%s spawned at a really bad position.") + elseif r == 14 then taunt = loc("%s was doomed from the beginning.") + elseif r == 15 then taunt = loc("%s has fallen victim to gravity.") + elseif r == 16 then taunt = loc("%s hates Newton.") -- Isaac Newton + elseif r == 17 then taunt = loc("%s had it coming.") + elseif r == 18 then taunt = loc("%s is eliminated!") + elseif r == 19 then taunt = loc("%s fell too fast.") + elseif r == 20 then taunt = loc("%s flew like a rock.") + elseif r == 21 then taunt = loc("%s stumbled.") + elseif r == 22 then taunt = loc("%s was shoved away.") + elseif r == 23 then taunt = loc("%s didn't expect that.") + end + return taunt +end + +local function declareEnemyKilled(gear, onVictory) + if deadHogs[gear] or playerFail then + return + end + deadHogs[gear] = true + hogsKilled = hogsKilled + 1 + + -- Award extra time, if available + if extraTime and extraTime ~= 0 then + SetTurnTimeLeft(TurnTimeLeft + extraTime) + AddCaption(string.format(loc("+%d seconds!"), div(extraTime, 1000)), 0xFFFFFFFF, capgrpMessage2) + end + + SetTeamLabel(playerTeamName, tostring(getKillScore())) + + if hogsKilled == totalEnemies - 1 then + if playValkyries then + PlayMusicSound(sndRideOfTheValkyries) + valkyriesTimer = valkyriesDuration + end + elseif hogsKilled == totalEnemies then + finishTime = TurnTimeLeft + victory(onVictory) + end +end + +--[[ +RopeKnocking function! + +This creates a rope-knocking challenge. +The player spawns with one hog and a rope and must kill all other hogs +by rope-knocking before the time runs out. +The player wins points for each kill and gets a time bonus for killing +all enemies. + +params is a table with all the required parameters. +Fields of the params table: + + MANDATORY: + - map: Map name + - theme: Theme name + - turnTime: Turn time + - playerTeam: Player team info: + { + x, y: Start position + faceLeft: If true, hog faces left + } + - enemyTeams: Table of enemy team tables. each enemy team table has this format: + { + name: Team name + flag: Flag + hogs: Hogs table: + { + x, y: Position + faceLeft: If true, hog faces left + hat: Hat name + name: Hog name + } + } + + OPTIONAL: + - missionName: Mission name + - missionType: + 0: None/other: No special handling + 1: Challenge: Will save mission variables at end (default) + - killBonus: Score for killing all hogs (one hog scores ca. (killBonus/ 0) then + valkyriesTimer = valkyriesTimer - 20 + if valkyriesTimer <= 0 then + StopMusicSound(sndRideOfTheValkyries) + end + end + local drown = (hhs[0]) and (band(GetState(hhs[0]), gstDrowning) ~= 0) + if drown and missionWon == nil then + -- Player hog drowns + playerFail = true + return + end + for i=1, totalEnemies do + local hog = hhs[i] + drown = (hog) and (not deadHogs[hog]) and (band(GetState(hhs[i]), gstDrowning) ~= 0) + if drown then + declareEnemyKilled(hog, params.onVictory) + end + end + + if ropeGear and not missionWon and band(GetState(ropeGear), gstCollision) ~= 0 then + -- Hide mission on first rope attach + HideMission() + end + end + + _G.onGearDamage = function(gear, damage) + + if gear == hhs[0] then + -- Player hog hurts itself + playerFail = true + StopMusicSound(sndRideOfTheValkyries) + valkyriesTimer = -1 + protectEnemies() + end + + if gear ~= hhs[0] and GetGearType(gear) == gtHedgehog and not deadHogs[gear] and missionWon == nil and playerFail == false then + -- Enemy hog took damage + AddVisualGear(GetX(gear), GetY(gear), vgtBigExplosion, 0, false) + DeleteGear(gear) + PlaySound(sndExplosion) + AddCaption(string.format(knockTaunt(), GetHogName(gear)), 0xFFFFFFFF, capgrpMessage) + + declareEnemyKilled(gear, params.onVictory) + end + + end + + _G.onGearAdd = function(gear) + if GetGearType(gear) == gtRope then + ropeGear = gear + end + end + + _G.onGearDelete = function(gear) + + if (gear == hhs[0]) and (missionWon == nil) then + playerFail = true + gameOver() + end + + if GetGearType(gear) == gtHedgehog and gear ~= hhs[0] and not deadHogs[gear] then + declareEnemyKilled(gear, params.onVictory) + end + + if GetGearType(gear) == gtRope then + ropeGear = nil + end + + end + + if params.onAmmoStoreInit then + _G.onAmmoStoreInit = params.onAmmoStoreInit + else + _G.onAmmoStoreInit = function() + SetAmmo(amRope, 9, 0, 0, 0) + end + + _G.onNewTurn = function() + SetWeapon(amRope) + end + end + +end diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Sounds/dynamitefuse.ogg Binary file share/hedgewars/Data/Sounds/dynamitefuse.ogg has changed diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Sounds/dynamiteimpact.ogg Binary file share/hedgewars/Data/Sounds/dynamiteimpact.ogg has changed diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Themes/Jungle/theme.cfg diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/Themes/Underwater/theme.cfg --- a/share/hedgewars/Data/Themes/Underwater/theme.cfg Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/Themes/Underwater/theme.cfg Sun Jul 05 14:53:44 2020 +0200 @@ -15,8 +15,6 @@ object = coral, 3, 10, 193, 38, 32, 2, 128, 66, 66, 94, 39, 0, 88, 167 object = coral2, 3, 119, 146, 23, 22, 1, 5, 0, 123, 130 flakes = 20, 20, 150, 0, 5 -sd-flakes = 40, 20, 150, 0, 5 -; TODO: Use this when rising flakes don't look so strange: -; sd-flakes = 40, 20, 60, 0, -100 +sd-flakes = 40, 20, 60, 0, -100 rq-sky = 0, 70, 210 sd-tint = $a9, $52, $52, $ff diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/misc/hedgewars.png Binary file share/hedgewars/Data/misc/hedgewars.png has changed diff -r b3c9f5463cee -r 74ede02bc882 share/hedgewars/Data/misc/hedgewars.xpm --- a/share/hedgewars/Data/misc/hedgewars.xpm Sun Jul 05 02:03:08 2020 +0200 +++ b/share/hedgewars/Data/misc/hedgewars.xpm Sun Jul 05 14:53:44 2020 +0200 @@ -1,239 +1,516 @@ /* XPM */ -static char *Icon___x__x__[] = { +static char *hedgehog[] = { /* columns rows colors chars-per-pixel */ -"32 32 201 2", -" c #010101", -". c #0A0709", -"X c #1A1A1A", -"o c #454545", -"O c #94005E", -"+ c #AE005D", -"@ c #970063", -"# c #9B0064", -"$ c #94066D", -"% c #9D036A", -"& c #9E086E", -"* c #880873", -"= c #960E74", -"- c #980D73", -"; c #8C1578", -": c #8C1A7D", -"> c #9D1075", -", c #9D177C", -"< c #91187D", -"1 c #9B1C7D", -"2 c #A20065", -"3 c #AA0067", -"4 c #A3046B", -"5 c #A0096F", -"6 c #A20C73", -"7 c #AD0B72", -"8 c #A41276", -"9 c #AD1074", -"0 c #A61479", -"q c #A81C7B", -"w c #7B2083", -"e c #772E8F", -"r c #7D2C8E", -"t c #732F91", -"y c #6B3596", -"u c #653B9B", -"i c #6D3A9B", -"p c #743294", -"a c #783092", -"s c #713899", -"d c #5641A1", -"f c #504BA9", -"g c #5A4EAD", -"h c #4D5AB6", -"j c #5652B0", -"k c #5058B5", -"l c #6444A3", -"z c #7C4CA8", -"x c #7654B2", -"c c #4363BF", -"v c #3D67C3", -"b c #3A69C5", -"n c #3C73CE", -"m c #2C77D1", -"M c #237CD6", -"N c #2C78D2", -"B c #2F7ED8", -"V c #3E7BD5", -"C c #5F71CC", -"Z c #6F6ECA", -"A c #627FD6", -"S c #6C7ED6", -"D c #617ED9", -"F c #6F7ED9", -"G c #881F82", -"H c #A11F83", -"J c #AC1D82", -"K c #872386", -"L c #8A2387", -"P c #8D2689", -"I c #8A2C8F", -"U c #952388", -"Y c #812F91", -"T c #873595", -"R c #8D3192", -"E c #8A3799", -"W c #84399A", -"Q c #AE2285", -"! c #A32488", -"~ c #A92D8D", -"^ c #B1258A", -"/ c #B32C8C", -"( c #BD298E", -") c #B5308E", -"_ c #B72E91", -"` c #BE2C91", -"' c #BA3495", -"] c #BA3895", -"[ c #BC3C9A", -"{ c #BF3BA0", -"} c #C32E93", -"| c #C23391", -" . c #CF3D9D", -".. c #C13DA2", -"X. c #CA3BA0", -"o. c #BE419C", -"O. c #8148A4", -"+. c #8152B0", -"@. c #C0439E", -"#. c #C345A3", -"$. c #C54EA5", -"%. c #CE4AA3", -"&. c #C645AA", -"*. c #C74EA9", -"=. c #C94AAE", -"-. c #D045AA", -";. c #D04BAF", -":. c #C750A8", -">. c #CC59AB", -",. c #CB4DB1", -"<. c #CC51B4", -"1. c #CD5AB0", -"2. c #D45CBF", -"3. c #D363BA", -"4. c #D65FC3", -"5. c #D769C0", -"6. c #DB6CC3", -"7. c #DE66C9", -"8. c #DE6BCE", -"9. c #DB74C5", -"0. c #DE76CB", -"q. c #DF6DD0", -"w. c #E068CC", -"e. c #EA7FCF", -"r. c #E06FD1", -"t. c #E274D5", -"y. c #E47BD6", -"u. c #E376D8", -"i. c #E479D9", -"p. c #E87FDE", -"a. c #1E83DC", -"s. c #2482DB", -"d. c #2B80DA", -"f. c #3682DC", -"g. c #1B8BE3", -"h. c #3187E0", -"j. c #3D8BE4", -"k. c #2790E8", -"l. c #3594EC", -"z. c #3D94EC", -"x. c #4C90EA", -"c. c #5494EB", -"v. c #469CF4", -"b. c #4C98F1", -"n. c #5A97F1", -"m. c #529AF2", -"M. c #5A9FF8", -"N. c #6B87E1", -"B. c #49A1F9", -"V. c #59A3F6", -"C. c #52A6FE", -"Z. c #5DA7FE", -"A. c #56AFFF", -"S. c #5CABFF", -"D. c #56B4FF", -"F. c #58B5FF", -"G. c #54BBFF", -"H. c #898989", -"J. c #8A918D", -"K. c #949394", -"L. c #9C9B9C", -"P. c #B5B4B5", -"I. c #E483D2", -"U. c #E789D6", -"Y. c #E781DA", -"T. c #E985DC", -"R. c #EA89DC", -"E. c #EC91DE", -"W. c #EE9BDA", -"Q. c #EC87E2", -"!. c #EC8CE2", -"~. c #EE93E1", -"^. c #F195E6", -"/. c #F29BE7", -"(. c #F49EE9", -"). c #F4A6E5", -"_. c #F5A2EB", -"`. c #F9A6EF", -"'. c #F6AAED", -"]. c #F5B2EC", -"[. c #F6A2F0", -"{. c #FAA6F1", -"}. c #FAAAF3", -"|. c #FBB7F5", -" X c #FEB1F8", -".X c #C8C7C8", -"XX c #CBCBCC", -"oX c #CDD1CF", -"OX c #F3D2EB", -"+X c #F7C6F0", -"@X c #F8C1F1", -"#X c #F8CAF2", -"$X c #FFD1FC", -"%X c #FDDBFB", -"&X c #F9E6F7", -"*X c #FDE5FA", -"=X c #FDEDFB", -"-X c #F3FCF5", -";X c #FCF3FA", -":X c #FEFEFE", -">X c None", +"254 256 254 2 ", +" c #002100210021", +". c #0B970B970B97", +"X c #122212221222", +"o c #18FF18FF18FF", +"O c #228422842284", +"+ c #2A582A582A58", +"@ c #33AD33AD33AD", +"# c #3DF33DF33DF3", +"$ c #433943394339", +"% c #4B824B824B82", +"& c #54FB54FB54FB", +"* c #5BDF5BDF5BDF", +"= c #644064406440", +"- c #6A5E6A5E6A5E", +"; c #755B755B755B", +": c #7D447D447D44", +"> c #9F9F080C6E72", +", c #98040F827521", +"< c #9C450C147214", +"1 c #8FBC17977C4B", +"2 c #8DBA197A7E00", +"3 c #964D111E768C", +"4 c #9B13111D76AA", +"5 c #927F14CA79DB", +"6 c #993215387A6B", +"7 c #9718198D7E60", +"8 c #A05909856F8C", +"9 c #A1BF0C3C71C5", +"0 c #A45A124875AF", +"q c #A5D717F877BB", +"w c #A6C114D079B7", +"e c #A83C174C7BF3", +"r c #A72E1B7B79D8", +"t c #A9891B1D7DA0", +"y c #A9DC225F7DE4", +"u c #7FAF27178A56", +"i c #7C112AB08D5F", +"p c #775E2F299164", +"a c #78282E7390B1", +"s c #6FDB366897F0", +"d c #6F543714987A", +"f c #671B3F0A9F87", +"g c #6B4C3B1B9C07", +"h c #72FB33769543", +"j c #5F3546BAA66A", +"k c #5DA64848A7D1", +"l c #56F94EB7AD90", +"z c #5A5D4B93AABE", +"x c #54EC50D0AF6F", +"c c #4ECC56BCB4BB", +"v c #4C5A592AB6E2", +"b c #468F5EB6BBD4", +"n c #49EE5C2FB998", +"m c #52155390B1DF", +"M c #509F5E00BBA6", +"N c #62F44321A32F", +"B c #750A4345A3C7", +"V c #7EB34BA4ABE0", +"C c #7A4454C4B42F", +"Z c #769558E9B7E9", +"A c #76485CEFBB90", +"S c #439761A5BE71", +"D c #746360BCBF22", +"F c #3E5B66B1C2F2", +"G c #3AFC6A0BC5F3", +"H c #368A6E63C9D7", +"J c #38026CF8C88D", +"K c #2F62755BD00F", +"L c #320272C8CDBD", +"P c #2DB176F9D17D", +"I c #2C357880D2D9", +"U c #30D67D58D777", +"Y c #405D64BDC137", +"T c #56A0753ED102", +"R c #6DC26DC5CAE5", +"E c #7237652BC32A", +"W c #707668F7C69E", +"Q c #6C0871E6CEB6", +"! c #6A487553D1CE", +"~ c #67957B00D6F3", +"^ c #684D79A6D5B1", +"/ c #661B7E31D9EE", +"( c #87F01F4F831C", +") c #89AD1D6D8189", +"_ c #952C1D7B8204", +"` c #AB5D1D1A81A2", +"' c #84CE222185C0", +"] c #80DD2606892D", +"[ c #8EDA2A638DAC", +"{ c #9339218D85A7", +"} c #915325608924", +"| c #8CDA2E889166", +" . c #8A83335D95D5", +".. c #8867378599CA", +"X. c #86583BEF9DA2", +"o. c #ADFB227D8466", +"O. c #AD892BD68377", +"+. c #AF6324668957", +"@. c #B09B26548736", +"#. c #B0AD26AE8AB3", +"$. c #B2D52A7D8CEE", +"%. c #AF9D31338698", +"&. c #B069333E87C9", +"*. c #B27F34798AA8", +"=. c #B3C93BE28CDF", +"-. c #B45D2D5A91D5", +";. c #B6FC31FC942E", +":. c #B918357393AE", +">. c #BB7339A19743", +",. c #B7F133FA982E", +"<. c #B93D360C9A28", +"1. c #BC213B289CB9", +"2. c #84B23F49A0CC", +"3. c #BD843DC8A171", +"4. c #B59C40A58FA0", +"5. c #B70E445A91D5", +"6. c #B9C84B5895F4", +"7. c #B9E54BA6961E", +"8. c #BF8640E79CEF", +"9. c #BCD7532D9A88", +"0. c #BFA95A729ED1", +"q. c #830542C9A3D2", +"w. c #809A47B2A845", +"e. c #803E486EA91A", +"r. c #BF3940DCA46F", +"t. c #C11743C49F34", +"y. c #C0615C629FEA", +"u. c #C1A044E6A3FC", +"i. c #C5754B88A59B", +"p. c #C26546A7A9E1", +"a. c #C4F04B2AAD15", +"s. c #C12D5E56A11E", +"d. c #C8265063A992", +"f. c #C9FB53A0AC71", +"g. c #CC6357FBAFB6", +"h. c #C6C54E93B167", +"j. c #C7EC509EB34C", +"k. c #C9D25400B66A", +"l. c #CE8D5BB4B2D6", +"z. c #CB4A5698B909", +"x. c #CD825AA8BCD7", +"c. c #D05D5F0DB58C", +"v. c #C3856467A4AA", +"b. c #C5556936A770", +"n. c #C7216DBAAA21", +"m. c #C9CB7487AE1F", +"M. c #D1346084B6BA", +"N. c #D3A864D2BA52", +"B. c #D68B6A04BE86", +"V. c #CD177D15B327", +"C. c #CFDB5ED9C0D5", +"Z. c #D2B56400C5B8", +"A. c #D7E16C67C08B", +"S. c #D9216E83C23A", +"D. c #D48B674BC8E2", +"F. c #D6D46B68CCC7", +"G. c #D8C96EDBCEB1", +"H. c #DBA77316C5F6", +"J. c #DB51730ACD4B", +"K. c #DF6B79BBCB64", +"L. c #DC2574BFD1ED", +"P. c #DF547A53D4F6", +"I. c #E1C27DEFCED5", +"U. c #E1C27E72D681", +"Y. c #351781DBDBBE", +"T. c #38B1858ADF4D", +"R. c #3A9E87A5E146", +"E. c #3D6B8A80E403", +"W. c #637E8369DEA6", +"Q. c #40DE8E2DE77B", +"!. c #42218F90E8D3", +"~. c #5F2A8C65E6CA", +"^. c #5DF68ED2E908", +"/. c #456392EFEC01", +"(. c #48B6967BEF58", +"). c #532494C7EE2D", +"_. c #5BDB9325ECEB", +"`. c #49E497AEF09D", +"'. c #4D009AEEF39F", +"]. c #59AF978FF0FB", +"[. c #53FC9E6AF712", +"{. c #58729A3BF351", +"}. c #5468A007F88F", +"|. c #61E086A2E196", +" X c #605E89F3E48C", +".X c #5450A297FAF0", +"XX c #841A841A841A", +"oX c #8C3C8C3C8C3C", +"OX c #941294129412", +"+X c #9A049A049A04", +"@X c #A3A8A3A8A3A8", +"#X c #ACA1ACA1ACA1", +"$X c #B62DB62DB62D", +"%X c #BD03BD03BD03", +"&X c #CF28824CB63A", +"*X c #D06D8591B80A", +"=X c #D0E086CBB8D2", +"-X c #D3148C68BC24", +";X c #D5079183BF22", +":X c #D6D29611C1D3", +">X c #D8029900C383", +",X c #D9CA9DB6C64B", +"X>X>X>X>X>X>X>X>X>X>X>X>X>X>X>X>X>X>X>X>X>X>X>X>X>X>X>X>X>X>X>X", -">X>X>X>X>X>X>X>X>X>X>X= & >X>X>X>X>X>X>X>X>X>X>X>X>X>X>X>X>X>X>X", -">X>X>X>X>X>X>X>X>X>X>Xp v : >X>X>X>X>X>X>X>X>X>X>X>X>X>X>X>X>X>X", -">X>X>X>X>X>X>X>X>X>X>Xr k.f.u 4 >X>X>X>X>X>X>X>X>X>X>X>X>X>X>X>X", -">X>X>X>X>X>X>X>X>X>X>Xt z.Z.l.c : 3 >X>X>X>X>X>X>X>X>X>X>X>X>X>X", -">X>X>X>X>X>X>X>X>X>X>Xy l.Z.Z.B.h.l > >X>X>X>X>X>X>X>X>X>X>X>X>X", -">X>X>X>X4 >X>X>X>X>X>Xt j.Z.A.S.n.C f * % >X>X>X>X>X>X>X>X>X>X>X", -">X>X>X4 l g l i p r K : T P L R ~ ] [ ] ^ 8 O >X>X>X>X>X>X>X>X>X", -">X>X>X>Xs g.s.s.s.M M s.d 8 %.I.(._.{.`._.E.3.Q # >X>X>X>X>X>X>X", -">X>X>X>X4 D C.m.m.b.v.p ' E.}._.(././.(./._.`._.1.& >X>X>X>X>X>X", -">X>X>X>X>XT A.Z.D.D.W .}._././.(._.(.(././.^./.}.6.6 >X>X>X>X>X", -">X>X>X>X>X2 A D.N.W ( _._./.(.(.(.(.(./.'.+X@X_.].$X9.# >X>X>X>X", -">X>X>X>X>X>X1 E ; 7 i._.(.(.(.(.(._./.].:X:X:X;X:X:X:X$.>X>X>X>X", -">X>X>X>X4 < i b y -.!.(.(.(._.(.(.(./.*X:X:X:X:X:X:X-XOX6 >X>X>X", -">X>X6 r k m a.m ! 7.!._.(.(.(.(.(./._.:X:X:XL.P.:XH. XX>.>X>X>X", -">X* n a.M B z.C ( t.R._.(.(.(.(.(./.'.;X:X.X X :Xo J.W.# >X>X", -">X# a V v.V.G.z X.t.R._./._.(./.(.(.(.=X:X.X X :XK.. oX).% >X>X", -">X>X>X0 F G.S 2 ,.t.Y._./.(.(._.(._.^.#X:X:XK.P.:X:X-X=X~.8 >X>X", -">X>X>X>X4 x I w ,.u.y.(.(._.(.(._.(.(./.&X:X:X:X=X+X%X'.U.) ] >X", -">X>X>X>X>X+ h f .t.7.~.(./.(.(./.(.(.(._.#X&X*X|.{.3.U.U.] ^.q ", -">X>X>X>X>Xp g.h ` i.t.Q.}._./.(.(.(.(.(./.(.~.6.3.@.$.}.3.#. X) ", -">X>X>X>X: m d.c.! 7.,...3.E.`./.(._.(.(./._./.| @.r._.}.' 3.U.q ", -">X>X>X4 c M x.F.O.7 [ :.o.Q 3.{./.(.(._.(./._.{. X_._.R.^ r.J >X", -">X>X4 j g.z.F.V.U 6.}.}.}.E.Q 9.`./.(./.(.(.(././.).[.] 8 6 >X>X", -">X>X- l g Z N.x } [.(././.}.9._ (.(.(._._.(.(.(.^.^.,.% >X>X>X>X", -">X>X>X>X>X4 4 2 &.Q.(.(./._.T.Q t.y.R.~.~.~.Q.Q.Y.=.- >X>X>X>X>X", -">X>X>X>X>X>X>X>X^ u.R./.(.{.*.[ i.q.8.8.q.r.i.q.' & >X>X>X>X>X>X", -">X>X>X>X>X>X>X>X% _ 4.r.8.#.& ,.q.t.t.t.q.4.| J | 0 >X>X>X>X>X>X", -">X>X>X>X>X>X>X>X>X>X6 0 6 - &.' Q ^ ^ ^ Q / *.U. X[ >X>X>X>X>X>X", -">X>X>X>X>X>X>X>X>X>X>X>X>X6 q.!.~.I.U.*.] }.}._.}.@.>X>X>X>X>X>X", -">X>X>X>X>X>X>X>X>X>X>X>X>X>X' u.Q.^.~.Q &.Q.~.(.r.8 >X>X>X>X>X>X", -">X>X>X>X>X>X>X>X>X>X>X>X>X>X% ^ ,.&.J % 0 ..,...6 >X>X>X>X>X>X>X" +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXzXs.y > q 5.;XFXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXm.< > > > > > > r >XLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXm.> > > > > > > > > > 5.cXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXwX8 > > > > > > > > > > > q ;XLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLX9.> > > > > > > > > > > > > > =.xXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXFXr > > > > > > > > > > > > > > > 0 &XLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXcX> > > > > > > > > > > > > > > > > > =.zXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXwX> > > > > > , s < > > > > > > > > > > q V.KXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXqX> > > > > > 5 L b ( > > > > > > > > > > > &.kXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX8X> > > > > > 2 K P L f < > > > > > > > > > > 0 m.DXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX8X> > > > > > 2 K I I I S ' > > > > > > > > > > > %.qXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX7X> > > > > > 2 P I I I P K N , > > > > > > > > > > 9 n.DXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX,X> > > > > > ( I I I I I I I Y ' > > > > > > > > > > > O.9XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX>X> > > > > > ( I I I I I I I I K k < > > > > > > > > > > 9 v.DXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX:X> > > > > > ' P I I I I I I I I I F u > > > > > > > > > > > y 8XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX;X> > > > > > ' I I I I I I I I I I I K z 3 > > > > > > > > > > 9 0.AXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX*X> > > > > > u I I I I I I I I I I I I I F i 8 > > > > > > > > > > r 8XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX&X> > > > > > i I I I I I I I I I I I I I I P l , > > > > > > > > > > > 9.ZXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXV.> > > > > > i I I I I I I I I I I I I I I I I J a > > > > > > > > > > > q 6XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXm.> > > > > > p I I I I I I I I I I I I I I I I I I x 5 > > > > > > > > > > > 6.bXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXn.> > > > > > p I I I I I I I Y.Q.U I I I I I I I I I H a < > > > > > > > > > > q >XLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXn.> > > > > > s I I I I I I I Q..X.XE.I I I I I I I I I I m 1 > > > > > > > > > > > 6.cXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXv.> > > > > > d I I I I I I I /..X.X.X(.Y.I I I I I I I I I H h > > > > > > > > > > > q ;XLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXs.> > > > > > s I I I I I I I /..X.X.X.X.XE.I I I I I I I I I P c 1 > > > > > > > > > > > 4.cXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX0.> > > > > > g I I I I I I I /..X.X.X.X.X.X'.Y.I I I I I I I I I L s < > > > > > > > > > > 0 -XLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX9.> > > > > > g I I I I I I I /..X.X.X.X.X.X.X.XQ.I I I I I I I I I K n 2 > > > > > > > > > > > =.lXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX6.> > > > > > f I I I I I I I /..X.X.X.X.X.X.X.X.X'.Y.I I I I I I I I I L g < > > > > > > > > > > 0 &XKXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX6.> > > > > > j I I I I I I I (..X.X.X.X.X.X.X.X.X.X.XQ.I I I I I I I I I I b 2 > > > > > > > > > > > *.kXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX5.> > > > > > j I I I I I I I (..X.X.X.X.X.X.X.X.X.X.X.X'.T.I I I I I I I I I L g < > > > > > > > > > > 0 m.KXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX5.> > > > > > k I I I I I I I (..X.X.X.X.X.X.X.X.X.X.X.X.X.X!.I I I I I I I I I I S ' > > > > > > > > > > > %.kXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLX=.> > > > > > z I I I I I I I (..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X[.T.I I I I I I I I I L f , > > > > > > > > > > 9 n.FXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX%.> > > > > > l I I I I I I I '..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X}.!.U I I I I I I I I I S ' > > > > > > > > > > > O.qXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLX%.> > > > > > l I I I I I I I '..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X'.T.I I I I I I I I I K j , > > > > > > > > > > > b.DXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXO.> > > > > > m I I I I I I I .X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X/.U I I I I I I I I I S ] > > > > > > > > > > > y 0XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXO.> > > > > > c I I I I I I I '..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X[.R.I I I I I I I I I I k , > > > > > > > > > > 9 v.AXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXy > > > > > > v I I I I I I U '..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X(.I I I I I I I I I I F u > > > > > > > > > > > y 8XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXr > > > > > > n P I I I I I I .X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X[.R.I I I I I I I I I K z , > > > > > > > > > > > 0.ZXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXKXr > > > > > > b I I I I I I I '..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X(.U I I I I I I I I P H i > > > > > > > > > > > r 7XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXKX0 > > > > > > b I I I I I I I .X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.XE.I I I I I I I I I P l 3 > > > > > > > > > > > 9.ZXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXKX0 > > > > > > F I I I I I I I .X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X(.Y.I I I I I I I I I H p > > > > > > > > > > > q ,XLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXKX> > > > > > > F I I I I I I I .X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X[.E.I I I I I I I I I I m 5 > > > > > > > > > > > 6.bXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXFX> > > > > > > F I I I I I I I .X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X(.Y.I I I I I I I I I H h > > > > > > > > > > > q ;XLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXDX> > > > > > > G I I I I I I I .X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.XE.I I I I I I I I I P m 1 > > > > > > > > > > > 6.cXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXAX> > > > > > < G I I I I I I U .X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X(.Y.P I I I I I I I I H s < > > > > > > > > > > 0 -XLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXbX> > > > > > < H I I I I I I U .X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.XQ.I I I I I I I I I I c 1 > > > > > > > > > > > =.zXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXKX7X0.*.%.%.=.9.v.m.&X:X7XqXzXcXbXAXKXLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXcX> > > > > > , J I I I I I I U .X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X`.T.I I I I I I I I I H g < > > > > > > > > > > q *XLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXqXO.> > > > > > > > > > > > > > > 0 q y O.*.6.0.n.V.;X,X9XkXcXcXZXDXLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXcX> > > > > > , H P I I I I I Y..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.XE.I I I I I I I I I I n 2 > > > > > > > > > > > *.zXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX7X9 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > 9 q r y %.=.6.v.n.=X:X8XqXzXcXbXAXKXPXPXPXPXPXPXPXPXPXxX> > > > > > 3 H I I I I I I U .X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X[._.~./ b m x z z z z z l x l ' > > > > > > > > > > > q V.KXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXbXq > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > < r r O.=.6.0.n.&X-X,X0X:X> > > > > > 5 H I I I I I P Y..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X[.~.! Z V .{ 6 4 < > > > > > > > > > > > > > > > > > > > > > > > > > > &.kXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXn.> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > i h g f k l c Y |.^.].]..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X[. XR w.| 7 > > > > > > > > 8 > > > > > > > > > > > > > > > > > > > > > > > > > > > 0 m.FXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXy 8 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > 4 4 6 _ [ ..q.V V A W Q ~ W.~._.]..X.X.X.X.X.X~.W X._ > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > O.8XFXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXKX8 8 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > 8 > > > > > > > > > > > > 9 4 4 w } | X.q.} < 8 8 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > 8 q 6.:XbXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXKX> > > > > > > s l j f s a u ' 2 5 < > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > 8 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > 8 9 =.-XZXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXy > > > > > > ' K I I I I I P P P K L G S v x z j g h a ' 2 5 , > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > 8 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > 8 8 > 9 5.8XLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX0.> > > > > > > m P I I I I I I I I I I I I I I I I I I I P P P K J F n m l j f s a u ' 2 3 < > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > 8 8 > 8 0 r o.o.@.@.@.@.o.` w 9 8 > 8 8 > > > 8 > > > > > > > > > > > > > > > > > > > r m.bXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXwX> > > > > > > 2 K I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I P H G b v m j f g h i ' 2 5 < < > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > o.:.i.c.S.I.4XyXyXyXyXyXyXyXyXyXyXyXyXuXrX3XI.B.g.t.-.e > > > > > > > > > > > > > > > > > > 8 8 8 8 9.kXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX5.> > > > > > > z P I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I L G S n m l j g s a ] 2 2 , < < < > > > > > > > > > > > > > > > > > > > > > > > > > > > 9 @.t.N..r 9 8 8 8 8 > > > > > > > > > > > > > 8 4.0XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXkX> > > > > > > 2 L I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I K H F S n x k s 3 > > > > > > > > > > > > > > > > > 9 @.i.K.rXyXiXuXtXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX4XB.1.r 8 > > > > > > > > > > > > > > 8 8 4.0XLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLX9.> > > > > > > j I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I K j 5 > > > > > > > > > > > > > > > 0 :.N.4XyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXI.f.@.9 8 > 8 > > > > > > > > > > 8 8 5.kXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXcX> > > > > > > 3 J I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I K n 2 > > > > > > > > > > > > > > 0 :.N.5XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX > > > > > > > > > > 8 8 8 y.bXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXs.> > > > > > > g I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I F a > > > > > > > > > > > > > > t g.1XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXI.t.q 8 > > > > > > > > > > > > 0 =XKXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXbX4 > > > > > > 3 G I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I P I I I I I I I I I I I I I I I I I I I I I I I I I I I I K j , > > > > > > > > > > > > 9 :.H.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX4Xc.o.8 > > > > > > > > > > > > %.wXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXm.> > > > > > > s I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I S ' > > > > > > > > > 8 > > 0 t. > > > > > > > > > 9 n.FXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXZXr > > > > > > < Y I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I K f < > > > > > > > > > > < 0 u. > > > > > > > > > > O.qXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX&X> > > > > > > p I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I F ' > > > > > > > > > > > 9 8.1XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtXH.$.8 > > > > > > > > > > 8 V.KXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXDXy > > > > > > > T R.T.T.Y.Y.U U I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I P m 3 > > > > > > > > > > 8 :.I.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtXB.t > > > > > > > > > > 8 =.bXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX;X> > > > > > > ..}..X.X.X.X.X[.'.'.'.`.(.!.!.E.E.T.T.T.U Y.I I U I I I I I U I I I I I I I I I I I I I I I I I I I I I I I L g > > > > > > > > > > > ` S.tXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXrXf.9 8 8 > > > > > > > > r 9XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXKX%.> > > > > > > ^ .X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X'.[.'.`.(./.!.E.E.T.T.Y.Y.U U I I I I I I I I I I I I I I I I H ] > > > > > > > > > > 9 d.rXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX1X:.8 > > > > > > > > > 9 &XLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX6X> > > > > > > [ .X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X'..X.X[.'.'.`././.!.E.R.R.T.Y.U I I S 2 > > > > > > > > > > @.2XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXM.0 8 8 > > > > > > > > 0.FXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLX*.> > > > > > > Q .X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.XQ 3 > > > > > > > > > 9 h.tXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtXyX1X;.8 8 > > > > > > > > =.ZXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX8X> > > > > > > { .X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.XD < > > > > > > > > > +.L.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtXl.9 > > > > > > > > > O.xXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX5.> > > > > > > W .X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.XZ > > > > > > > > > > >.eXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXI.t 8 > > > > > > > > r lXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXwX> > > > > > > 7 ]..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.Xe.> > > > > > > > > < g.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX5X:.> > > > > > > > > q qXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX9.> > > > > > > A .X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X].V > > > > > > > > > q H.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtXd.8 > > > > > > > > 0 0XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXzX9 > > > > > > 6 ]..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.XV > > > > > > > > > o. > > > > > > 0 0XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXs.> > > > > > > C .X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X}.e.< > > > > > > > > $.5XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXK.q > > > > > > > > 0 qXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXcXq > > > > > > 4 _..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.XZ 8 > > > > > > > > :.rXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX > > > > > > > 0 lXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXn.> > > > > > > V .X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.XD > 8 8 > > > > > > -.5XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX1Xo.> > > > > > > > r cXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXZXq > > > > > 8 < ~..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.XQ > > > > > > > > > -.U.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX4Xo.> > > > > > > > y ZXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXV.> > > > > > 8 q..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.XW.3 > > > > > > > < ,.F.rXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX4Xo.> > > > > 8 8 8 =.KXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXDXr > > > > > > < W..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X[..X^._ > > > > > > > 8 -.D.P.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX1Xo.8 > > > > > > > y.PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX-X> > > > > > > ...X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X[.[.[ > > > > > > > > -.D.G.rXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXtX1X` > > > > > > > > =XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXKXO.> > > > > > < / .X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X2.< > > > > > > > +.D.F.P.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXI.e 8 > > > > > > 8 qXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX>X> > > > > 8 8 | .X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.XW > > > > > > > > ` Z.F.G.eXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXH.9 > > > > > > > y bXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLX%.> > > > > > > Q .X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X[.Q 4 > > > > > > > 0 x.F.F.L.yXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXM.8 8 > > > > > > 5.KXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX8X> > > > > > > } {..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X~.A { 9 > > > > > > > 8 k.F.F.F.3XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXuXyXyXyXuXuXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXtXyXyXyXyXyXyXyXtXf.8 > > > > > > 8 &XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX=.> > > > > > > W .X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X[.! X.4 > > > > > > > > > > p.F.F.F.G.tXiXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXiXsXMXGXHXLXLXLXLXGXNXnXpXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXiXtXyXiXpXpXpXpXiXyXyXyXyXyXyXyXtX:.> > > > > > 8 9 kXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXwX> > > > > > > { ]..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X^.Z _ 8 > > > > > > > > > > > -.F.F.F.F.U.yXtXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXsXBXLXPXPXPXPXPXPXPXPXPXPXPXPXHXMXpXyXuXtXyXyXyXyXyXyXyXyXyXyXiXnXGXLXLXPXPXPXLXGXnXpXyXyXyXyXtX5Xt > > > > > 8 8 O.FXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX5.> > > > > > > A .X.X.X.X.X.X.X.X.X.X.X.X.X[.Q ..9 8 > > > > > > > > > > > > ` Z.F.F.F.G.rXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXuXuXyXaXHXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXGXaXuXyXyXyXyXyXyXtXuXuXMXLXPXPXPXPXPXPXPXPXPXPXLXGXpXyXyXyXyXI.8 8 > > > > > 8 m.PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXxX> > > > > > > 7 ]..X.X.X.X.X.X.X.X.X.X^.C _ > > > 8 > > > > > > > > > > 8 8 C.F.F.F.F.L.tXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtXuXiXSXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXBXiXyXyXyXyXuXuXaXHXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXNXiXyXyXyXf.> > > > > > 8 8 kXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXy.> > > > > > > Z .X.X.X.X.X.X.X[.Q .> > > > > > > > > > > > > > > 8 8 > 3.F.F.F.F.F.U.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXsXLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXmXyXyXyXyXdXLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXaXyXyXyX-.8 > > > > > > =.KXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXvX8 > > > > > > 6 _..X.X.X.X XC 5 > > > > > > > > > > > > > > > > > > 8 +.D.F.F.F.F.G.rXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXnXLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXBXiXyXsXLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXdXyXyX1X9 > > > > > 8 8 ;XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXb.> > > > > > < V .X].R | 8 8 > > > > > > > > > > > > > > > > > > > 9 C.F.F.F.F.F.L.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXmXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXHXsXLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXnXyXyXc.> > > > > > > q ZXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXZX0 > > > > > > 4 e.6 > > 8 8 > > > > > > > > > > > > > > > > > > > 3.F.F.F.F.F.F.U.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXdXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXnXyXyX$.> > > > > > > v.PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXm.> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > ` D.F.F.F.F.F.F.eXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXaXLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXsXuX > > > 0 zXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXDXr > > > > > > > > > > > > > > > > > > > , p l 4 > > > > > > 8 z.F.F.F.F.F.F.F.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXiXLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXaXtXi.8 8 > > > > > 6.LXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXFX6Xy 8 > > > > > > > > > > > > > > > > 2 k J P s > > > > > > > -.F.F.F.F.F.F.F.L.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXBXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXHXiX5Xr > > > > > > 9 kXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXzXn.r > > > > > > > > > > > > > > > > , h b P I I G 3 > > > > > > > C.F.F.F.F.F.F.F.2XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXsXLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXNXyXM.> > > > > > > 6.LXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXDX,X4.8 > > > > > > > > > > > > > > > > ( k H I I I I I s > > > > > > > <.F.F.F.F.F.F.F.F.eXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtXuXHXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXaXrX@.> > > > > > 9 wXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXlXb.r > > > > > > > > > > > > > > > > 3 s b P I I I I I I G , > > > > > > w Z.F.F.F.F.F.F.F.G.tXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXsXLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXHXiXB.> > > > > > > 9.PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXAX:X*.8 > > > > > > > > > > > > > > > < ' z H I I I I I I I I P f > > > > > > > 3.F.F.F.F.F.F.F.F.L.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXHXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXmXyX@.8 8 8 > > > > zXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXkXv.r > > > > > > > > > > > > > > > > 3 s b P I I I I I I I I I I L 3 > > > > > > w Z.F.F.F.F.F.F.F.F.U.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXaXLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXyXS.8 8 8 > > > > v.PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXZX;X&.8 > > > > > > > > > > > > > > > < ' l H I I I I I I I I I I I I I z > > > > > > < 1.F.F.F.F.F.F.F.F.F.3XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXNXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXmXtX@.> 8 > > > > 0 ZXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXwX0.q > > > > > > > > > > > > > > > > 5 d S I I I I I I I I I I I I I I I P ( > > > > > > 9 C.F.F.F.F.F.F.F.F.F.eXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXHXuXN.> 8 > > > > > &XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXZX-X%.> > > > > > > > > > > > > > > > > ] l L I I I I I I I I I I I I I I I I I b > > > > > > > -.F.F.F.F.F.F.F.F.F.G.tXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXaXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXaXrXt 8 > > > > > O.KXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXqX9.0 > > > > > > > > > > > > > > > > 5 g F I I I I I I I I I I I I I I I I I I I I h > > > > > > 8 C.F.F.F.F.F.F.F.F.F.G.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXMXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXSXyXf.> > > > > > > qXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXZX&XO.> > > > > > > > > > > > > > > > > u x L I I I I I I I I I I I I I I I I I I I I I L , > > > > > > +.F.F.F.F.F.F.F.F.F.F.L.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXuXyXHXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXJX#X& + + $ XXVXPXPXPXPXPXPXPXPXLXiX4X0 > > > > > > 0.PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXLX9X9.< > > > > > > > > > > > > > > > > 5 N F K I I I I I I I I I I I I I I I I I I I I I I I l > > > > > > > a.F.F.F.F.F.F.F.F.F.F.L.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtXuXiXLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXfX+ X oXPXPXPXPXPXPXPXPXnXyX>.> > > > > > 0 DXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXZX&Xo.> > > > > > > > > > > > > > > > > i m K I I I I I I I I I I I I I I I I I I I I I I I I I P ] > > > > > > w D.F.F.F.F.F.F.F.F.F.F.U.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXpXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX#X. = PXPXPXPXPXPXPXBXyXH.> > > > > > > ,XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXLX8X6.> > > > > > > > > > > > > > > > > 1 f G I I I I I I I I I I I I I I I I I I I I I I I I I I I I H < > > > > > > ,.F.F.F.F.F.F.F.F.F.F.F.2XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXsXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXgXOX; : @XCXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXgX. ; PXPXPXPXPXPXLXiXrXy > > > > > > 6.PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXbXV.y > > > > > > > > > > > > > > > > < i c I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I l > > > > > > > k.F.F.F.F.F.F.F.F.F.F.F.3XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXMXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXfX@ . = VXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXJX@ %XPXPXPXPXPXPXaXyXi.> > > > > > 0 DXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXwX6.> > > > > > > > > > > > > > > > > 1 f G I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I i > > > > > > ` D.F.F.F.F.F.F.F.F.F.F.F.3XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXNXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXXX o %XPXPXPXPXPXPXPXPXPXPXPXPXPXPX+X @ PXPXPXPXPXPXnXyXI.8 > > > > > > 7XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXDX0.> > > > > > > > > > > > > > > > < i c K I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I P < > > > > > > <.F.F.F.F.F.F.F.F.F.F.F.F.eXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXBXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX: X %XPXPXPXPXPXPXPXPXPXPXPXPXLX@ $XPXPXPXPXPXBXyXrX` > > > > > > 0.PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXAX%.> > > > > > > > > > > > > > > 1 j J I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I P v 8 > > > > > > k.F.F.F.F.F.F.F.F.F.F.F.F.eXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXSXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX#X o VXPXPXPXPXPXPXPXPXPXPXPXhX * PXPXPXPXPXGXyXyXi.> > > > > > r LXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PX6.> > > > > > > > > > > > > , a v K I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I T.B > > > > > > w Z.F.F.F.F.F.F.F.F.F.F.F.F.eXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXGXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXJXO & LXPXPXPXPXPXPXPXPXPXPXOX o PXPXPXPXPXLXiXyXK.8 > > > > > > zXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"qX> > > > > > > > > > > > 2 k J I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I U R.'..X} > > > > > > -.D.F.F.F.F.F.F.F.F.F.F.F.F.tXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXGXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXoX fXPXPXPXPXPXPXPXPXPXPX= jXPXPXPXPXLXiXyX5Xq > > > > > > &XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"v.> > > > > > > > > 5 p n K I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I U T./.[..X.X].> > > > > > > p.F.F.F.F.F.F.F.F.F.F.F.F.F.eXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXGXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX+ * PXPXPXPXPXPXPXPXPXPX% %XPXPXPXPXPXpXyXyX:.> > > > > > 6.PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"&.> > > > > > > 2 k J I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I Y.E.'..X.X.X.X.XQ > > > > > > 9 C.F.F.F.F.F.F.F.F.F.F.F.F.F.tXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXBXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXgX o CXPXPXPXPXPXPXPXPXPX$ #XPXPXPXPXPXaXyXyXl.> > > > > > q LXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"O.> > > > > > 2 F P I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I U T./..X.X.X.X.X.X.X.XC > > > > > > ` Z.F.F.F.F.F.F.F.F.F.F.F.F.F.tXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXNXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXOX $XPXPXPXPXPXPXPXPXPX% #XPXPXPXPXPXaXtXuXI.8 > > > > > > xXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"7.> > > > > > > 2 n I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I I Y.E.'..X.X.X.X.X.X.X.X.X.X| > > > > > > -.F.F.F.F.F.F.F.F.F.F.F.F.F.F.eXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXMXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX= XXPXPXPXPXPXPXPXPXPX= %XPXPXPXPXPXsXyXuX5Xw > > > > > > >XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +";X> > > > > > > > < N L I I I I I I I I I I I I I I I I I I I I I I I I I I I I I U T.(..X.X.X.X.X.X.X.X.X.X.X.X.X6 > > > > > > 3.D.F.F.F.F.F.F.F.F.F.F.F.F.F.eXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXnXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX% = PXPXPXPXPXPXPXPXPXoX jXPXPXPXPXPXsXyXyXyX;.8 8 > > > > b.PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"FXr > > > > > > > > > u G I I I I I I I I I I I I I I I I I I I I I I I I I I Y.Q.'..X.X.X.X.X.X.X.X.X.X.X.X.X.X~.> > > > > > 8 C.D.F.F.F.F.F.F.F.F.F.F.F.F.F.eXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXaXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX@ % PXPXPXPXPXPXPXPXPXfX o LXPXPXPXPXPXaXyXyXyXi.8 8 > > > > *.PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PX8X< 8 > > > > > > > > 5 m I I I I I I I I I I I I I I I I I I I I I I I R.`..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.XQ > > > > > > 9 Z.F.F.F.F.F.F.F.F.F.F.F.F.F.F.eXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXiXLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX# % PXPXPXPXPXPXPXPXPXJXO * PXPXPXPXPXPXaXyXyXyXB.> > > > > > 0 LXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPX;X9 > > > > > > > > > < g L I I I I I I I I I I I I I I I I I I Y.E.[..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.XZ > > > > > > t F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.3XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXuXuXLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX& & PXPXPXPXPXPXPXPXPXPX: %XPXPXPXPXPXLXpXyXyXyX > > > > > bXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPX9Xr 8 > > > > > > > > > ' F I I I I I I I I I I I I I I U R.`..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X..8 > > > > > $.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.2XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXBXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX; ; PXPXPXPXPXPXPXPXPXPXVXo @ PXPXPXPXPXPXLXiXyXyXyXrX0 > > > > > > 0XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXZX5.> > > > > > > > > > 3 x I I I I I I I I I I I Y.!.[..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.XR > 8 > > > > > 1.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.2XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXnXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX@X +XPXPXPXPXPXPXPXPXPXPXPX+X . fXPXPXPXPXPXPXLXyXyXyXyXyXo.8 > > > > > ;XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXLX&X8 > > > > > > > > > > h I I I I I I I U R.`..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X^.5 > > > > > > > p.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.P.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXpXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXVX. jXPXPXPXPXPXPXPXPXPXPXPXPX= oXPXPXPXPXPXPXPXBXyXyXyXyXyX:.> > > > > > n.PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXwXy > > > > > > > > > > 2 Y I I I U !.[..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X[.| > > > > > > > > k.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.L.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXtXLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX$ @ PXPXPXPXPXPXPXPXPXPXPXPXPXLX- oXPXPXPXPXPXPXPXPXMXyXyXyXyXtXi.> > > > > > 9.PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXDX9.> > > > > > > > > > < z E.'..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X}.}.C > > > > > > > > 9 C.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.G.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXNXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX$X +XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX#X+ @ fXPXPXPXPXPXPXPXPXPXaXyXyXyXyXyXl.> > > > > > *.PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXLX-X0 > > > > > > > > > > X.]..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X}.^ < > > > > > > > > 9 D.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.tXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXsXLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLX$ + JXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXgXOXXX+XhXPXPXPXPXPXPXPXPXPXPXLXiXyXyXyXyXyXA.> > > > > > r PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXlXO.> > > > > > > > > > { W..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X}.]._ < > > > > > > > > 9 F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.rXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXHXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXhX. $XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXBXyXyXyXyXyXyXK.8 > > > > 8 9 KXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXDXy.> > > > > > > > > > < A }..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X[.X.> 8 > > > > > > > > ` F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.3XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXMXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX#X. XXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXsXyXyXyXyXyXyX > > > 8 > 9.zXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPX:Xq > > > > > > > > > > ..^..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X}.E 8 > > > > > > > > > > ` F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.U.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXiXLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX#X. XXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXuXyXyXyXyXyXyX4X9 8 8 > > > > 8 8 v.ZXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXxX*.> > > > > > > 8 8 > _ ^ .X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X|.9 8 > > > > > > > > > > #.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.P.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXNXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXhX% . @ $XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXmXtXyXyXyXyXyXyX4X0 > > > > > > > > > r 6XLXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXKXv.< > > > > > > > > > < Z ]..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X].[ < 8 > > > > > > > > > > -.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.tXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXpXLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXhXOX- - XXfXLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXiXyXyXyXyXyXyXyXrX0 > > > > > > > > > > 8 v.FXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX7Xr > > > > > > > > > > ..~..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X}.e.> > > > > > > > > > > > > -.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.rXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXBXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXsXuXyXyXyXyXyXyXyXyX0 > > > > > > > > > > 8 > *.vXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXcX=.> > > > > > > > > > 7 ! .X.X.X.X.X.X.X.X.X.X.X.X.X}.Q < 8 > > > > > > > > > > > > -.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.3XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtXiXLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXBXuXyXyXyXyXyXyXyXyXyXw > > > > > > > > > > > > 8 y zXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXm.> > > > > > > > > > w w.[.[..X.X.X.X.X.X.X.X.X.X~.6 > 8 > > > > > > > > > > > > ,.F.F.F.F.G.F.F.F.F.F.F.F.F.F.F.F.F.F.U.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXuXmXLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXiXyXyXyXyXyXyXyXyXyXyXw > > > > > > > > > > > > > > r kXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX9Xy > > > > > > > > > > | ~..X.X.X.X.X.X.X.X.X}. .8 > > > > > > , ( > > > > > > <.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.L.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXHXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXpXyXyXyXyXyXyXyXyXyXyXyXw > > > > > > > > > > > > > > 8 r cXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXbX6.> > > > > > > > > > 6 R .X.X.X.X.X.X.X.XZ 8 8 > > > > > > j h > > > > > > -.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.rXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXpXLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXpXtXyXyXyXyXyXyXyXyXyXyXyXw > > > > > > :.8 > > > > > > > 8 y AXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXV.9 > > > > > > > > > < q.]..X.X.X.X.X^ , > > > > > > > a K s > > > > > > -.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.2XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXnXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXNXLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXGXiXuXyXyXyXyXyXyXyXyXyXyXyXrXe > > > > > 8 H.H.0 8 > > > > > > > 5.KXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXqXy > > > > > > > > 8 > } X.X}..X]._ > > > > > > > 1 J I g > > > > > > -.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.P.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXBXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXMXtXpXNXLXPXPXPXPXPXPXPXPXPXPXHXdXyXyXyXyXyXyXyXyXyXyXyXyXyXyXrX0 > > > > > 8 K.yX > > > > > > > &XPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXAX9.> > > > > > > > > 8 4 E }..X2.8 > > > > > > < v I I f > > > > > > #.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.tXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXtXyXyXyXyXyXyXyXyXyXyXyXuXGXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXHXuXyXyXyXiXdXBXLXLXPXPXLXHXNXaXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX5X0 > > > > > 8 I.yXyX > > > > > > 0 zXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLX-X9 > > > > > > > > > < q.A > > > > > > > > f I I P j > > > > > > ` F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.2XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXuXyXyXyXyXyXyXuXyXyXyXyXyXyXiXHXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXpXyXyXyXyXyXuXtXuXyXiXiXiXuXtXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX1X9 > > > > > 8 > > > > > > =.LXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXlXO.> > > > > > > > > > > > > > > > > > ' K I I I z > > > > > > t F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.P.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXtXyXyXyXyXyXyXyXyXyXyXyXyXyXyXiXHXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXnXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX > > > 8 8 5XyXyXyXyXB.8 > > > > > > > 7XPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXDX0.8 > > > > > > > > > > > > > > > 5 F P I I I v > > > > > > 0 D.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.G.rXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXuXyXyXyXyXyXyXyXyXyXyXyXyXyXuXiXGXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXNXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXK.> > > > 8 8 8 tXyXyXyXyXyXu.> > > > > > > O.LXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX>X9 8 > > > > > > > > > > > > 8 l I I I I I b > > > > > > 9 Z.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.U.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXiXBXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXBXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXB.> > > > > 8 e yXyXyXyXyXyX5Xt > > > > > > > 7XPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXcX%.> > > > > > > > > > > > h I I I I I I F < > > > > 8 > x.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.P.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXtXuXMXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXNXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXg.> > > > > 8 @.yXyXyXyXyXyXuXN.> > > > > > > =.PXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXKXv.8 > > > > > > > > > 2 L I I I I I I P < > > > > > > k.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.eXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXaXHXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXmXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXt.e > > > > > :.yXyXyXyXyXyXyXyXo.> > > > > > 8 cXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX6X> > > > > > > > < S I I I I I I I I 5 > > > > > > p.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.U.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXiXMXLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXHXpXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXi.> 8 f.yXyXyXyXyXyXyXyXyXyXyXyX:.> > > > > > i.yXyXyXyXyXyXyXyXM.8 > > > > > > &XPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX;X> > > > > > > > j I I I I I I I I I ' > > > > > > <.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.tXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtXuXyXiXNXLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXnXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXi.8 8 8 N.yXyXyXyXyXyXyXyXyXyXyXyXy > > > > > > g.yXyXyXyXyXyXyXyX5Xe > > > > > > *.PX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXxX0 > > > > > > > i P I I I I I I I I I s > > > > > > +.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.U.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXiXnXGXLXPXPXPXPXPXPXPXPXPXPXPXLXGXdXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXrXt.8 > 8 q rXyXyXyXyXyXyXyXyXyXyXyX5X0 > > > > > 8 H.yXyXyXyXyXyXyXyXyX8.> > > > > > 8 DX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXFX%.> > > > > > > 5 J I I I I I I I I I I N > > > > > < w F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.G.tXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXiXsXMXGXHXLXLXLXHXGXNXsXiXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX5X:.> > > > l.yXyXyXyXyXyXyXyXyXyXyXyXI.8 > > > > > 8 1XyXyXyXyXyXyXyXyXyXB.> > > > > > > wX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXb.> > > > > > > < m I I I I I I I I I I I v > > > > > 8 9 D.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.G.U.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXuXyXtXuXuXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXI.y > > > > @.tXyXyXyXyXyXyXyXyXyXyXyXyXN.8 8 > > > > 9 yXyXyXyXyXyXyXyXyXyX1X9 > > > > > > -X", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX7X> > > > > > > > g I I I I I I I I I I I I G > > > > > > > k.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.L.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXc.9 > > > > 0 I.yXyXyXyXyXyXyXyXyXyXyXyXyXi.> > > > > > $.yXyXyXyXyXyXyXyXyXyXrXt 8 > > > > > b.", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXvXr > > > > > > > ' K I I I I I I I I I I I I P 5 > > > > > > r.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.3XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX > > > 9 B.yXyXyXyXyXyXyXyXyXyXyXyXyXyX$.> > > > > > t.yXyXyXyXyXyXyXyXyXyXyX%.> > > > > > 6.", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLX5.> > > > > > > 5 F I I I I I I I I I I I I I I ] 8 > > > > > -.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.G.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX5Xg.0 > > > > > > l.yXyXyXyXyXyXyXyXyXyXyXyXyXyX5Xw > > > > > > M.yXyXyXyXyXyXyXyXyXyXyX:.> > > > > > *.", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXV.> > > > > > > > l I I I I I I I I I I I I I I I N > > > > > > w D.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.U.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX5Xc.t > > > > > > 9 l.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXK.8 > > > > > > > > > > > O.", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXqX< > > > > > > > h K I I I I I I I I I I I I I I I M > > > > > > 8 x.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.rXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX4XS.1.0 > > > > > > > 0 B.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXg.8 > > > > > 0 yXyXyXyXyXyXyXyXyXyXyXyXt.> > > > > > O.", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXAXO.> > > > > > > 1 H I I I I I I I I I I I I I I I U ).> > > > > > 8 r.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.P.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX5X1XI.H.l.t.o.9 8 8 8 > > > > > > o.I.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX-.> > > > > > :.yXyXyXyXyXyXyXyXyXyXyXyX8.> > > > > > &.", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLX0.> > > > > > > > S I I I I I P I I I I I I I I I I (..X[ > > > > > 8 #.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.eXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXU.c.t.:.-.@.t e w 0 9 8 > > > > > > > > > > 8 8 8 t.5XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX5Xw > > > > > > l.yXyXyXyXyXyXyXyXyXyXyXyX-.> > > > > > 5.", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX:X8 > > > > > > > j I I I I I I I I I I I I I I I I T..X.XC > > > > > 8 w Z.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.G.tXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXN.9 > > > > > > > > > > > > > > > > > > > > > 8 @.H.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXS.8 > > > > > 8 I.yXyXyXyXyXyXyXyXyXyXyXyX` > > > > > > y.", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXxXq > > > > > > > u P I I I I I I I I I I I I I I I U [..X.X/ 8 > > > > 8 8 h.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.U.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtXo.> > > > > > > > > > > > > > > > > > > > > e M.tXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXt.> > > > > > e tXyXyXyXyXyXyXyXyXyXyXyX5Xw > > > > > > V.", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXKX*.> > > > > > > 5 G I I I I I I I I I I I I I I I I '..X.X.X].4 > > > > 8 > ,.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.eXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtXt > > > > > > > > > > > > > > > > > > 8 t c.5XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXrXt > > > > > > 8.yXyXyXyXyXyXyXyXyXyXyXyXH.> > > > > > > 8X", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXn.> > > > > > > > c I I I I I I I I I I I I I I I I !..X.X.X.X.Xq.> > > > > > ` D.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.L.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXd.> > > > > > > > > > > > > > > 8 8 :.B.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXK.8 > > > > > > A.yXyXyXyXyXyXyXyXyXyXyXyXd.> > > > > > > bX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX8X8 > > > > > > > d P I I I I I I I I I I I I I I I T..X.X.X.X.X.XQ > > > > > > > j.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.U.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXrX-.> > > > > > > > > > > 8 9 -.c.1XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXt.> > > > > 8 9 eXyXyXyXyXyXyXyXyXyXyXyXrX` > > > > > > y LX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXbXr > > > > > > > ( L I I I I I I I I I I I I I I I U [..X.X.X.X.X.X].4 > > > > > 8 -.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.eXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtXN.+.0 9 > 9 9 9 t :.f.H.rXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX5Xe > > > > > > :.yXyXyXyXyXyXyXyXyXyXrXU.z.8 > > > > 8 8 v.PX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLX6.> > > > > > > , Y I I I I I I I I I I I I I I I I `..X.X.X.X.X.X.X[.X.> > > > > 8 9 Z.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.D.Z.C.x.k.z.z.x.C.Z.F.F.rXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXrX1X1X1X5XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXB.> > > > > > 8 B.yXyXyXyXyXyXyXtX3XP.F.F.$.> > > > > 8 8 0XPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX&X> > > > > > > > z I I I I I I I I I I I I I I I I E..X.X.X.X.X.X.X.X.XQ > > > > > > > p.F.F.F.F.F.F.F.F.F.F.D.x.u.;.` 0 8 > > > > > > > 9 ` ` >.B.1XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX:.> > > > > > w 5XyXyXtXeX3XU.L.G.F.F.F.z.9 > > > > > > y KXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXkX0 > > > > > > > h I I I I I I I I I I I I I I I I T..X.X.X.X.X.X.X.X.X.X].7 > > > > > > ` F.F.F.F.F.F.F.F.p.-.w > > > > 8 > > > > > > > > > > > > > > t t.K.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX > > > > > -.J.G.G.G.F.F.F.F.F.F.F.F.$.> > > > > > 8 V.PXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXDXO.> > > > > > > 2 H I I I I I I I I I I I I I I I U '..X.X.X.X.X.X.X.X.X.X.XV > > > > > > > k.F.F.F.F.x.,.w > > > > > > > > > > > > > > > > > > > > > > > > > t f.1XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXd.> > > > > > > j.F.F.F.F.F.F.F.F.F.F.F.p.8 > > > > > > 0 ZXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLX0.> > > > > > > < n I I I I I I I I I I I I I I I I !..X.X.X.X.X.X.X.X.X.X.X.X|.> > > > > > > -.F.F.z.#.8 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > w t.1XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX5Xe 8 > > > > > t D.F.F.F.F.F.F.F.F.F.F.x.0 > > > > > > > m.PXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX,X> > > > > > > > f I I I I I I I I I I I I I I I I R..X.X.X.X.X.X.X.X.X.X.X.X.X.X| > > > > > > 9 j.-.9 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > 0 i.5XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXc.> > > > > > > 8.F.F.F.F.F.F.F.F.F.F.D.` > > > > > > > r AXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXcXq > > > > > > > ] K I I I I I I I I I I I I I I I Y..X.X.X.X.X.X.X.X.X.X.X.X.X.X.XR > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > t S.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXuXyXyXyXyXrX` > > > > > > 0 Z.F.F.F.F.F.F.F.F.F.D.$.> > > > > > > > :XPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXKX*.> > > > > > > 5 F I I I I I I I I I I I I I I I I `..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X[._ > > > > > > > > > > > > > > > > > > > 9 t $.8.u.d.f.d.u.>.#.` > > > > > > > > > > > > > > 9 t.5XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXN.8 > > > > > 8 ;.F.F.F.F.F.F.F.F.F.D.-.> > > > > > > 8 5.LXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXm.> > > > > > > < x I I I I I I I I I I I I I I I I !..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.XA > > > > > > > > > > > > > > > > t t.B.1XrXyXyXyXyXyXyXyXyXyXeXU.N.>.e > > > > > > > > > > > > @.1XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXrXo.8 > > > > > 8 x.F.F.F.F.F.F.F.F.Z.#.> > > > > > > > r bXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX0X> > > > > > > > s I I I I I I I I I I I I I I I I T..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X}.].w > > > > > > > > > > > > w 8.K.rXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX5XS.:.8 > > > > > > > > > > e I.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXN.> > > > > > > -.F.F.F.F.F.F.F.F.z.t > > > > > > > > 0 8XPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXZXy > > > > > > > 2 L I I I I I I I I I I I I I I I U [..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X}.X.> > > > > > > > > > 0 t.1XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXI.>.9 > > > > > > > > 8 0 K.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXrX` > > > > > > > z.F.F.F.F.F.F.D.1.9 > > > > > > > > 9 ;XPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLX6.> > > > > > > , S I I I I I I I I I I I I I I I I (..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X}.R > 8 > > > > > > > > :.U.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXH.@.> > > > > > > 8 > 0 I.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXg.8 > > > > > > $.F.F.F.F.F.F.a.` > > > > > > > > > > V.LXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX-X> > > > > > > > k I I I I I I I I I I I I I I I I E..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X^ < > > > > > > > > 0 M.tXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX5Xd.9 > > > > > > > 8 e 1XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX3X0 8 > > > > > 9 z.F.F.F.D.p.` > > > > > > > > > > 8 V.PXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXlX0 > > > > > > > i I I I I I I I I I I I I I I I I Y..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X_._ > > > > > > > > t I.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXH.0 > > > > > > > > @.tXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtXtX<.> > > > > > > -.D.Z.a.-.9 8 > > > > > > > > > > < :XPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXFXO.> > > > > > > 5 J I I I I I I I I I I I I I I I I '..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X[.| > > > > > > > > @.4XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXI.e > > > > > > > 8 u.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXrXx.8 > > > > > > > ` ` < > > > > > > > > > > > > > o.qXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXv.> > > > > > > < n I I I I I I I I I I I I I I I I /..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.XA > > > > > > > > ` 4XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX > > > > > > 8 K.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXeXD.e > > > > > > > > > > > > > > > > > > > > > > > 6.ZXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPX6X8 > > > > > > > f I I I I I I I I I I I I I I I I R..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X_.4 > > > > > > > w A.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXI.0 > > > > > > > t tXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtX2XF.<.> > > > > > > > > > > > > > > > > > > > > 8 r >XPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXcXr > > > > > > > u K I I I I I I I I I I I I I I I U [..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.Xq.8 8 > > > > > 9 k.P.tXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXN.9 > > > > > > > l.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXtXuXtXL.F.z.8 > > > > > > > > > > > > > > > > > > > > 0 m.DXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXKX4.> > > > > > > < F I I I I I I I I I I I I I I I I '..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X^.< > 8 > > > > > 3.F.2XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX>.> > > > > > > e rXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXtXeXF.F.D.` > > > > > > > > > > > 8 8 > > > > > > r m.ZXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPX-X> > > > > > > > l I I I I I I I I I I I I I I I I !..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.Xe.> > > > > > > ` D.D.eXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX4X0 > > > > > > 8 M.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtXP.F.F.F.,.8 > > > > > > > > > > > > 8 8 8 > 9 5.6XFXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXDXr > > > > > > > h I I I I I I I I I I I I I I I P T.[..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X[.[.9 > > > > > > 8 h.F.F.eXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXl.> > > > > > > #.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX4XG.F.F.F.a.8 > > > > > > > > > 8 > > > 9 O.s.7XAXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPX7X> > > > > > > 2 L I I I I I I I I I I I I I I I U [..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X[.R 8 > > > > > > +.D.F.F.eXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX5Xw > > > > > > 8 I.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXrXP.F.F.F.F.C.0 > > > > > > > O.v.v.m.-X6XlXZXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXm.> > > > > > < S I I I I I I I I I I I I I I I I (..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.XX.8 8 > > > > > p.F.F.F.eXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXi.> > > > > > > h.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtX > > > > > > q cXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPX0.> > > > > > 5 g N z x v S F G H L I I I I I I Q..X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X[.6 > > > > > > 9 Z.F.F.F.2XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXI.8 8 > > > > > o.P.rXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtX1XG.F.F.F.F.F.F.-.> > > > > > > 8 :XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXv.> > > > > > > > > > > > > > < 3 5 2 2 u p s f Z E R ^ W.~._.].}..X.X.X.X.X.X.X.X.X.X.X.X.X|.< > > > > > > $.F.F.F.F.P.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXt > > > > > > 0 D.G.U.rXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtX3XL.F.F.F.F.F.F.F.1.> > > > > > > > 0.LXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPX>X> > > > > > > > > > > > > > > > > > > > > > > > > > > > > 4 5 _ { [ .X.e.Z E R ! W. X^._.E > > > > > > > 3.F.F.F.F.L.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX8.> > > > > > > x.F.F.G.U.eXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX3XG.G.F.F.F.F.F.F.F.p.8 > > > > > > > O.AXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXAXr > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > 8 > > 8 > > 8 8 > 8 > > < 6 4 > > > > > > 8 j.F.F.F.F.G.tXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXl.> > > > > > > a.F.F.F.F.G.P.eXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtX2XG.F.F.F.F.F.F.F.F.F.j.9 > > > > > > > q lXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPX;X> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > 8 > > > > 8 > 8 C.F.F.F.F.F.eXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXH.> > > > > > > r.F.F.F.F.F.F.F.L.3XtXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXeXU.G.F.F.F.F.F.F.F.F.F.F.z.0 > > > > > > > 8 6XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXLXm.> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > 8 8 9 Z.F.F.F.F.F.P.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXI.8 > > > > > 8 >.F.F.F.F.F.F.F.F.F.G.P.eXtXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtX2XG.G.F.F.F.F.F.F.F.F.F.F.F.z.w > > > > > > > > m.PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPX8XO.> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > 9 D.F.F.F.F.F.G.tXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX > > > > > <.F.F.F.F.F.F.F.F.F.F.F.F.L.2XeXtXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtXU.P.G.F.G.F.F.F.F.F.F.F.F.F.F.F.x.w > > > > > > > > 9.LXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXLXkX-Xn.0.6.4.%.y q > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > 8 9 D.F.F.F.F.F.F.U.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXI.9 > > > > > > <.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.L.P.3XtXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXeXU.G.G.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.z.w > > > > > > > > *.KXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXLXLXDXbXxXqX7X:X=Xm.v.9.5.&.y q > > > > > > > > > > > > > > > > > > > > > > > 8 8 9 C.F.F.F.F.F.F.F.rXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXK.> > > > > > > 3.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.L.2X3XrXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtXeXU.P.G.F.F.G.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.k.w > > > > > > > > O.AXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXPXLXKXAXcXkX9X7X;X&Xn.s.6.*.O.r 8 > > > > > > > > 8 8 8 C.F.F.F.F.F.F.F.P.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXB.> > > > > > > r.G.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.P.U.3XeXrXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtXrX3XU.P.L.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.j.0 > > > > > > > > y bXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXLXDXbXzXn.8 > > > > > > a.F.F.F.F.F.F.F.G.3XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXd.> > > > > > 8 j.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.G.P.P.U.U.3X2X3X3X3X3X3X3X2X2XU.U.P.L.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.p.9 > > > > > > > > r xXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXqX> > > > > > > <.F.F.F.F.F.F.F.D.G.rXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX;.> > > > > > 8 C.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.1.8 8 > > > > > > > r xXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXAX8 8 > > > > > ` D.F.F.F.F.F.F.G.F.L.rXyXyXtXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXeX0 > > > > > > w F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.Z.-.> > > > > > > > > r xXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX&.> > > > > > 8 C.F.F.F.F.F.F.F.F.F.G.yXyXuXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXB.> > > > > > > -.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.x.` > > > > > > > > > o.cXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXV.> > > > > > > <.F.F.F.F.F.F.F.F.F.F.L.rXtXtXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX;.> > > > > > > p.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.h.0 > > > > > > > > > O.bXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXxX8 > > > > > > w Z.D.F.F.F.F.F.F.F.F.F.F.eXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXP.9 > > > > > > 0 C.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.D.;.> > > > > > > > > > :.ZXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX*.> > > > > > > 1.D.F.F.F.F.F.F.F.F.F.F.F.2XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXeX-.> > > > > > > -.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.G.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.k.` > > > > > > > > 8 8 9.DXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX,X> > > > > 8 8 9 C.F.F.F.F.F.F.F.F.F.F.F.F.L.3XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXrXP.j.8 > > > > > > > z.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.D.,.8 > > > > > > > > > 9 m.LXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXO.> > > > > > > o.F.F.F.F.F.F.F.F.F.F.F.F.F.F.L.eXyXyXyXtXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXrXU.G.C.` > > > > > > > +.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.h.` > > > > > > > > > > q 6XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX7X> > > > > > > > <.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.L.2XrXyXyXyXyXtXyXyXyXuXyXyXyXyXyXyXyXyXtX3XP.G.F.D.-.> > > > > > > > k.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.C.-.8 > > > > > > > > > > y zXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLX=.> > > > > > > 8 p.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.G.L.2XeXrXyXyXyXuXtXyXyXyXyXtXeX2XP.G.F.F.F.F.<.> > > > > > > > -.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.Z.1.w > > > > > > > > > > > > 0 qXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXxX0 > > > > > > > 9 a.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.G.D.G.G.L.L.L.U.P.P.L.L.G.F.F.F.F.F.F.F.3.> > > > > > > > w Z.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.D.r.w > > > > > > > > > > > > > > 8 y bXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX;X> > > > > > > > 9 u.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.D.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.D.,.> > > > > > > > > j.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.D.u.e > > > > > > > > > > > > > > > > > > 5.LXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLX0.> > > > > > > > 8 ,.D.F.F.F.F.F.F.F.F.F.F.F.F.F.F.G.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.D.C.-.> > > > > > > > > 3.D.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.Z.r.e > > > > > > > > > > > > 8 8 > > > > > > 8 >XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXKX=.> > > > > > > > > ` x.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.h.` > > > > > > > > > -.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.x.,.0 > > > > > > > > > > > > > ;.I.o.> > > > > > > y AXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXAX*.> > > > > > > > > 8 <.Z.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.D.C.-.> > > > > > > > > > > -.k.D.F.F.F.F.F.F.F.F.F.F.F.F.F.G.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.D.h.#.8 > > > > > > > > > > > > > ` B.rXyX3X0 > > > > > > > m.PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXZX*.> > > > > > > > > > 9 1.Z.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.C.-.9 > > > > > > > > > > > > > 8 ` <.x.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.j.-.w > > > > > > > > > > > > > > 0 d.5XyXyXyXyXN.8 8 > > > > > q ZXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXDX6.> > > > > > > > > > > 9 $.a.D.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.Z.3.+.8 > > > > > > > > > > > > > > > > > > 8 ` <.k.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.Z.h.-.w > > > > > > > > > > > > > > > 8 1.1XyXyXyXyXyXyXtX;.> > > > > > 8 =XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXm.< 8 > > > > > > > > > > 8 8 +.<.h.z.Z.F.F.F.F.F.Z.z.p.,.` 9 > > > > > > > > > > > > > > > > > > > > > > > > 8 0 #.r.C.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.D.k.3.` 9 8 > > > > > > > > > > > > > > > > >. > > > 8 %.KXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX0Xy > > > > > > > > > > > > > > > 8 8 8 0 9 9 > > > 8 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > 0 +.3.h.C.D.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.D.x.p.,.` 8 8 > > 8 > > > > > > > > > > > > > > 9 >. > > > 8 8 zXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXDXv.9 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > ` > > > > > > > > > > > > > > > > > > > > > > 9 ` -.<.3.h.z.C.D.F.F.D.F.D.F.Z.F.F.F.Z.x.k.a.3.,.+.e 8 > > > > > > > > > > > > > > > > > > > > > 0 t.1XyXyXyXyXyXyXyXyXyXyXyXyXyXyXH.8 > > > > > > V.PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXlX6.9 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > w D.C.,.w > > > > > > > > > > > > > > > > > > > > > > > > > > > 8 8 8 ` w w w w 0 9 > > > > > > > > > > > > > > > > > > > > > > > > > > > > 9 @.M.5XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXrXr > > > > > > =.PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXwX9.9 8 > > > > > > > > > > > > > > > > > > > > > > > > < > > > > > > ` F.F.F.Z.h.+.9 > 8 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > 0 t.I.yXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyX1.> > > > > > q DXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXcXm.#.8 > > > > > > > > > > > > > > > > > > > > > 9 w > > > > > > +.D.F.F.D.F.D.C.j.-.9 8 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > 8 w >.D.rXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXN.> > > > > > 8 xXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXxX&X6.r 9 > > > > > > > > > > > > > 9 y 9.;XcXV.> > > > > > -.F.F.F.F.F.F.F.2XyX1XM.:.e > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > 8 > > > > > 8 8 > 8 t t.S.5XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXI.> > > > > > > 9XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXKXxX7X&Xv.9.5.*.*.=.5.9.b.-X8XcXLXPXPXPXV.> > > > > > -.F.F.F.F.F.F.F.G.yXyXyXyXrXH.d.$.0 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > 8 8 > > 8 8 t >.l. > > > > > :XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX&X> > > > > > $.F.F.F.F.F.F.F.F.3XyXyXyXyXyXyXyX5XL.l.1.+.0 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > 9 -.u.M. > > > > > =XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX-X> > > > > > +.F.F.F.F.F.F.F.F.L.yXyXyXyXyXyXyXyXyXtXyXyXeXU.B.g.t.;.o.t 0 0 9 > > > > > > > > > > 9 0 e 9 > > > > > > i.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXrXq > > > > > > &XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX,X> > > > > > ` D.F.F.F.F.F.F.F.F.2XyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtX5X3X > > > > 8 j.tXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX5Xq > > > > > > &XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXqX> > > > > > 0 Z.F.F.F.F.F.F.F.F.G.eXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXe > > > > > 8 a.tXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtX9 8 > > > > > ;XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXbX8 > > > > > 8 C.F.F.F.F.F.F.F.F.F.G.rXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX4X9 > > > > > 8 z.P.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuX > > > > > > 7XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXKX` > > > > > > p.F.F.F.F.F.F.F.F.F.F.U.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXB.> > > > > > 9 Z.F.eXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtXB.> > > > > > > lXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX6.> > > > > > #.F.F.F.F.F.F.F.F.F.F.F.U.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXt.> > > > > > ` D.F.L.rXtXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXi.> > > > > > q ZXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX&X> > > > > 8 9 C.F.F.F.F.F.F.F.F.F.F.F.P.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXrXq > > > > > > <.F.F.F.L.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtX@.> > > > > > %.LXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXxX8 > > > > 8 > u.F.F.F.F.F.F.F.F.F.F.F.F.P.tXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXB.8 > > > > > > j.F.F.F.F.L.yXyXyXyXyXyXyXyXyXyXyXyXyXyXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyX > > > > > v.PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLX%.> > > > 8 8 ` D.F.F.F.F.F.F.F.F.F.F.F.F.L.eXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtX$.8 > > > > > w D.F.F.F.F.F.L.rXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtXi.8 > > > > > > 9XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX&X8 > > > > > 8 p.F.F.F.F.F.F.F.F.F.F.F.F.F.F.P.tXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXeXk.8 > > > > > > ;.F.F.F.F.F.F.F.G.eXtXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXI.9 8 > > > > > y FXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXbX9 > > > > > 8 e D.F.F.F.F.F.F.F.F.F.F.F.F.F.F.G.2XtXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXeXP.D.` > > > > > > 8 z.F.F.F.F.F.F.F.F.G.U.tXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXU.<.8 8 > > > > > v.PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXs.> > > > > > > <.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.P.4XtXyXyXyXyXyXyXyXyXyXyXyXyXrXU.P.F.F.3.> > > > > > > o.F.F.F.F.F.F.F.F.F.F.F.L.3XtXyXuXuXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXtXU.G.k.8 8 8 > > > > 9 xXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXxX9 > > > > > > 8 j.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.L.U.3XeXeXrXrXeXeX2XP.G.F.F.F.F.z.8 > > > > > > > >.F.F.F.F.F.F.F.F.F.F.F.F.F.G.2XrXtXyXuXyXyXyXyXyXyXyXyXyXyXyXyXyX5XU.G.F.D.` > > 8 > > > > 9.PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXs.> > > > > > > 0 C.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.C.` > > > > > > > > 8 k.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.L.2XeXyXtXyXyXyXtXuXtXyXtXeXU.L.F.F.F.F.-.> > > > > > > 0 xXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXbXr > > > > > > > t C.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.Z.` > > > > > > > > > > w x.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.L.L.P.P.P.P.L.F.F.F.F.F.F.F.F.1.8 > > > > > > 8 n.PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX;X> > > > > > > > t C.F.G.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.Z.` > > > > > > > > > > > > w x.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.1.8 8 > > > > > 8 O.DXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLX9.> > > > > > > > w z.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.x.` > > > > > > > > > > > > > > 9 h.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.D.;.> > 8 > > > > > 8 qXLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXZXO.> > > > > > > > 0 r.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.p.0 > > > > > > 8 8 8 8 > > > > > > 8 3.D.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.x.+.8 8 > > > > > > 9 &XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXzXq > > > > > > > > > +.z.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.x.$.> > > > > > > > > 0 r 8 > > > > > > > > ` k.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.Z.3.9 8 > > > > > > > > v.LXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX0Xq > > > > > > > > > 9 ,.z.D.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.x.;.9 > > > > > > > > > 9 8XxXt > > > > > > > > > > @.k.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.D.Z.u.q > > > > > > > > > > 9.LXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX8Xq > > > > > > > > > > 9 +.p.C.D.F.F.F.F.F.F.F.F.F.F.C.p.+.9 > 8 > > > > > > > > 0 7XPXLXzXt > > > > > > > > > > > ` 3.C.D.F.F.F.F.F.F.F.F.F.F.F.Z.k.,.0 > > > > > > > > > > > s.HXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXqXr > > > > > > > > > > > 8 < t -.3.p.h.j.h.p.3.-.t 9 8 > > > > > > > > > > > q 9XPXPXPXPXvX%.> > > > > > > > > > > > 4 e -.3.a.k.z.C.z.k.p.<.+.0 > > > > > > > > > > > > > m.LXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXcX*.> > > > > > > > > > 8 > > > > > > > > > > > > > 8 > > > > > > > > > > %.zXPXPXPXPXPXPXFXs.> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > r ,XPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXFXn.8 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > 8 v.DXPXPXPXPXPXPXPXPXPX,Xr > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > 5.cXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXqX&.> > > > > > > > > > > > > > > > > > > > > > > > > > > > > O.9XPXPXPXPXPXPXPXPXPXPXPXPXAXv.9 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > y 6XLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLX;Xr > > > > > > > > > > > > > > > > > > > > > > > > 8 r =XKXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXxXy.9 > > > > > > > > > > > > > > > > > > > > > > > > > r -XKXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXKX:X&.> > > > > > > > > > > > > > > > > > > > 8 O.-XFXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXbXm.y 8 > > > > > > > > > > > > > > > > > > > > 4.,XKXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXLXkXm.O.> > > > > > > > > > > > 8 > 8 y n.wXLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXKXqXm.&.8 > > > > > > > > > > 9 > > q 6.;XvXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX", +"PXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXbX7XV.0.*.r 0 9 0 t &.9.m.7XbXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXFXqX:Xm.s.6.>.=.5.9.v.V.7XxXLXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPXPX" }; diff -r b3c9f5463cee -r 74ede02bc882 tools/CMakeLists.txt --- a/tools/CMakeLists.txt Sun Jul 05 02:03:08 2020 +0200 +++ b/tools/CMakeLists.txt Sun Jul 05 14:53:44 2020 +0200 @@ -8,7 +8,7 @@ if(APPLE AND NOT SKIPBUNDLE) find_package(Qt5 REQUIRED QUIET COMPONENTS Core Widgets Gui Network) - find_package(SDL2 REQUIRED) + find_package(SDL2 REQUIRED CONFIG) find_package(SDL2_image 2 REQUIRED) find_package(SDL2_net 2 REQUIRED) find_package(SDL2_ttf 2 REQUIRED) @@ -26,9 +26,9 @@ endif() endif() - #remove the ";-framework Cocoa" from the SDL2_LIBRARY variable - string(REGEX REPLACE "(.*);-.*" "\\1" sdl_library_only "${SDL2_LIBRARY}") - #remove the "libSDLmain.a" from the SDL2_LIBRARY variable + #remove the ";-framework Cocoa" from the SDL2_LIBRARIES variable + string(REGEX REPLACE "(.*);-.*" "\\1" sdl_library_only "${SDL2_LIBRARIES}") + #remove the "libSDLmain.a" from the SDL2_LIBRARIES variable string(REGEX REPLACE ".*;(.*)" "\\1" sdl_library_only "${sdl_library_only}") #get the neme of the library (harmelss if it is static) diff -r b3c9f5463cee -r 74ede02bc882 tools/replay2hwd.hs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/replay2hwd.hs Sun Jul 05 14:53:44 2020 +0200 @@ -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))