Merge pull request #62 from hedgewars/ci-patch-osx-atomic
authorAnton Malmygin <antonc27@mail.ru>
Wed, 15 May 2019 18:30:59 +0200
changeset 14960 372b25c6bdee
parent 14958 364b67d48279 (diff)
parent 14959 5cd4edd71d22 (current diff)
child 14961 575dbd7b5f43
Merge pull request #62 from hedgewars/ci-patch-osx-atomic Fix pas2c compilation on macOS
--- a/ChangeLog.txt	Wed May 15 00:24:53 2019 +0200
+++ b/ChangeLog.txt	Wed May 15 18:30:59 2019 +0200
@@ -99,11 +99,13 @@
  * Remove Arabic translation from release
 
 Frontend:
+ + Quick games are more random: More map types, random team size and difficulty
  + Add button in main menu at top left corner to open credits page
  + Restructure credits page
  + More intelligent automatic mission selection in campaign screen
  + New data directory for video thumbnails: Data/VideoThumbnails
  + Display a warning when the same key is used multiple times
+ + Stats screen now hides empty sections
  * Fix broken handling of /watch chat command on official server
  * Fix renaming a video leading to loss of thumbnail after restart
  * Fix controls list failing to display correct key names with regards to keyboard layout
@@ -114,6 +116,7 @@
 Sounds and voicepacks:
  + sndYoohoo has been split to sndYoohoo and sndKiss
  + Voice files sndPoisonCough and sndPoisonMoan are now optional (fall back to Default voicepack)
+ + Add taunt: sndFlyAway / Flyaway.ogg: When hedgehog flies off the map
  + Tweak some taunts: sndFirstBlood, sndLeaveMeAlone, sndCutItOut
  * Fix English voicepack selection of team being overwritten when playing in non-English locale
 
--- a/QTfrontend/game.cpp	Wed May 15 00:24:53 2019 +0200
+++ b/QTfrontend/game.cpp	Wed May 15 18:30:59 2019 +0200
@@ -144,33 +144,135 @@
     QAbstractItemModel * themeModel = DataManager::instance().themeModel()->withoutHidden();
 
     HWProto::addStringToBuffer(teamscfg, "TL");
-    HWProto::addStringToBuffer(teamscfg, QString("etheme %1")
-                               .arg((themeModel->rowCount() > 0) ? themeModel->index(rand() % themeModel->rowCount(), 0).data(ThemeModel::ActualNameRole).toString() : "Nature"));
+
+    // Random seed
     HWProto::addStringToBuffer(teamscfg, "eseed " + QUuid::createUuid().toString());
 
-    HWProto::addStringToBuffer(teamscfg, "e$template_filter 2");
-    HWProto::addStringToBuffer(teamscfg, "e$feature_size "+QString::number(rand()%18+4));
+    int r, minhogs, maxhogs;
 
+    // Random map
+    r = rand() % 10000;
+    if(r < 3000) { // 30%
+        r = 0;
+    } else if(r < 5250) { // 22.5%
+        r = 1;
+    } else if(r < 7500) { // 22.5%
+        r = 2;
+    } else if(r < 8750) { // 12.5%
+        r = 3;
+    } else { // 12.5%
+        r = 4;
+    }
+    switch(r)
+    {
+        // Random
+        default:
+        case 0: {
+            r = rand() % 3;
+            if(r == 0)
+            {
+                HWProto::addStringToBuffer(teamscfg, "e$template_filter 1");
+                minhogs = 3;
+                maxhogs = 4;
+            }
+            else if(r == 1)
+            {
+                HWProto::addStringToBuffer(teamscfg, "e$template_filter 2");
+                minhogs = 4;
+                maxhogs = 5;
+            }
+            else
+            {
+                HWProto::addStringToBuffer(teamscfg, "e$template_filter 4");
+                minhogs = 4;
+                maxhogs = 6;
+            }
+            HWProto::addStringToBuffer(teamscfg, "e$feature_size "+QString::number(rand()%18+4));
+            break;
+        }
+        // Maze
+        case 1: {
+            minhogs = 4;
+            maxhogs = 6;
+            HWProto::addStringToBuffer(teamscfg, "e$mapgen 1");
+            HWProto::addStringToBuffer(teamscfg, "e$template_filter "+QString::number(rand()%6));
+            HWProto::addStringToBuffer(teamscfg, "e$feature_size "+QString::number(rand()%16+6));
+            break;
+        }
+        // Perlin
+        case 2: {
+            minhogs = 4;
+            maxhogs = 6;
+            HWProto::addStringToBuffer(teamscfg, "e$mapgen 2");
+            HWProto::addStringToBuffer(teamscfg, "e$template_filter "+QString::number(rand()%6));
+            HWProto::addStringToBuffer(teamscfg, "e$feature_size "+QString::number(rand()%18+4));
+            break;
+        }
+        // Image map
+        case 3: {
+            minhogs = 4;
+            maxhogs = 6;
+            HWProto::addStringToBuffer(teamscfg, "e$mapgen 3");
+            // Select map from hardcoded list.
+            // TODO: find a more dynamic solution.
+            r = rand() % cQuickGameMaps.count();
+            HWProto::addStringToBuffer(teamscfg, "e$map " + cQuickGameMaps[r]);
+            break;
+        }
+        // Forts
+        case 4: {
+            minhogs = 4;
+            maxhogs = 6;
+            HWProto::addStringToBuffer(teamscfg, "e$mapgen 4");
+            HWProto::addStringToBuffer(teamscfg, "e$feature_size "+QString::number(rand()%20+1));
+            break;
+        }
+    }
+
+    // Theme
+    HWProto::addStringToBuffer(teamscfg, QString("etheme %1")
+        .arg((themeModel->rowCount() > 0) ? themeModel->index(rand() % themeModel->rowCount(), 0).data(ThemeModel::ActualNameRole).toString() : "Nature"));
+
+    int hogs = minhogs + rand() % (maxhogs-minhogs+1);
+
+    // Teams
+    // Player team
     HWTeam team1;
     team1.setDifficulty(0);
     team1.setColor(0);
-    team1.setNumHedgehogs(4);
+    team1.setNumHedgehogs(hogs);
     HWNamegen::teamRandomEverything(team1);
     team1.setVoicepack("Default_qau");
-    HWProto::addStringListToBuffer(teamscfg,
-                                   team1.teamGameConfig(100));
 
+    // Computer team
     HWTeam team2;
-    team2.setDifficulty(4);
+    // Random difficulty
+    // TODO: Select difficulty based on previous player successes/failures.
+    r = 1 + rand() % 5;
+    team2.setDifficulty(r);
     team2.setColor(1);
-    team2.setNumHedgehogs(4);
+    team2.setNumHedgehogs(hogs);
+    // Make sure the team names are not equal
     do
         HWNamegen::teamRandomEverything(team2);
     while(!team2.name().compare(team1.name()) || !team2.hedgehog(0).Hat.compare(team1.hedgehog(0).Hat));
     team2.setVoicepack("Default_qau");
-    HWProto::addStringListToBuffer(teamscfg,
-                                   team2.teamGameConfig(100));
 
+    // Random team play order
+    r = rand() % 2;
+    if(r == 0)
+    {
+        HWProto::addStringListToBuffer(teamscfg, team1.teamGameConfig(100));
+        HWProto::addStringListToBuffer(teamscfg, team2.teamGameConfig(100));
+    }
+    else
+    {
+        HWProto::addStringListToBuffer(teamscfg, team2.teamGameConfig(100));
+        HWProto::addStringListToBuffer(teamscfg, team1.teamGameConfig(100));
+    }
+
+    // Ammo scheme "Default"
+    // TODO: Random schemes
     HWProto::addStringToBuffer(teamscfg, QString("eammloadt %1").arg(cDefaultAmmoStore->mid(0, cAmmoNumber)));
     HWProto::addStringToBuffer(teamscfg, QString("eammprob %1").arg(cDefaultAmmoStore->mid(cAmmoNumber, cAmmoNumber)));
     HWProto::addStringToBuffer(teamscfg, QString("eammdelay %1").arg(cDefaultAmmoStore->mid(2 * cAmmoNumber, cAmmoNumber)));
--- a/QTfrontend/hwconsts.cpp.in	Wed May 15 00:24:53 2019 +0200
+++ b/QTfrontend/hwconsts.cpp.in	Wed May 15 18:30:59 2019 +0200
@@ -96,6 +96,33 @@
             AMMOLINE_HEDGEEDITOR_DELAY AMMOLINE_HEDGEEDITOR_CRATE ))
         ;
 
+QStringList cQuickGameMaps = QStringList()
+    << "Bamboo"
+    << "Bath"
+    << "Battlefield"
+    << "Blox"
+    << "Bubbleflow"
+    << "Cake"
+    << "Castle"
+    << "Cheese"
+    << "Cogs"
+    << "CrazyMission"
+    << "EarthRise"
+    << "Eyes"
+    << "Hammock"
+    << "HedgeFortress"
+    << "Hedgelove"
+    << "Hedgewars"
+    << "Hydrant"
+    << "Lonely_Island"
+    << "Mushrooms"
+    << "Octorama"
+    << "PirateFlag"
+    << "Plane"
+    << "Sheep"
+    << "Trash"
+    << "Tree";
+
 unsigned int colors[] = HW_TEAMCOLOR_ARRAY;
 
 QString * netHost = new QString();
--- a/QTfrontend/hwconsts.h	Wed May 15 00:24:53 2019 +0200
+++ b/QTfrontend/hwconsts.h	Wed May 15 18:30:59 2019 +0200
@@ -48,6 +48,7 @@
 extern QString * cEmptyAmmoStore;
 extern int cAmmoNumber;
 extern QList< QPair<QString, QString> > cDefaultAmmos;
+extern QStringList cQuickGameMaps;
 
 extern unsigned int colors[];
 
Binary file QTfrontend/res/StatsH.png has changed
--- a/QTfrontend/ui/page/pagegamestats.cpp	Wed May 15 00:24:53 2019 +0200
+++ b/QTfrontend/ui/page/pagegamestats.cpp	Wed May 15 18:30:59 2019 +0200
@@ -42,38 +42,36 @@
 {
     kindOfPoints = QString("");
     defaultGraphTitle = true;
-    QGridLayout * pageLayout = new QGridLayout();
-    pageLayout->setSpacing(20);
-    pageLayout->setColumnStretch(0, 1);
-    pageLayout->setColumnStretch(1, 1);
+    pageLayout = new QGridLayout();
     pageLayout->setRowStretch(0, 1);
     pageLayout->setRowStretch(1, 20);
-    //pageLayout->setRowStretch(1, -1); this should work but there is unnecessary empty space betwin lines if used
+    pageLayout->setVerticalSpacing(20);
     pageLayout->setContentsMargins(7, 7, 7, 0);
 
-    QGroupBox * gb = new QGroupBox(this);
+    gbDetails = new QGroupBox(this);
+    gbDetails->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
     QVBoxLayout * gbl = new QVBoxLayout;
 
     // details
     labelGameStats = new QLabel(this);
-    QLabel * l = new QLabel(this);
-    l->setTextFormat(Qt::RichText);
-    l->setText("<h1><img src=\":/res/StatsD.png\"> " + PageGameStats::tr("Details").toHtmlEscaped() + "</h1>");
-    l->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+    labelDetails = new QLabel(this);
+    labelDetails->setTextFormat(Qt::RichText);
+    labelDetails->setText("<h1><img src=\":/res/StatsD.png\"> " + PageGameStats::tr("Details").toHtmlEscaped() + "</h1>");
+    labelDetails->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
     labelGameStats->setTextFormat(Qt::RichText);
     labelGameStats->setAlignment(Qt::AlignTop);
     labelGameStats->setWordWrap(true);
-    gbl->addWidget(l);
+    gbl->addWidget(labelDetails);
     gbl->addWidget(labelGameStats);
-    gb->setLayout(gbl);
-    pageLayout->addWidget(gb, 1, 1);
+    gbDetails->setLayout(gbl);
+    pageLayout->addWidget(gbDetails, 1, 1);
 
     // graph
-    graphic = new FitGraphicsView(gb);
+    graphic = new FitGraphicsView(gbDetails);
     graphic->setObjectName("gameStatsView");
     labelGraphTitle = new QLabel(this);
     labelGraphTitle->setTextFormat(Qt::RichText);
-    labelGraphTitle->setText("<br><h1><img src=\":/res/StatsH.png\"> " + PageGameStats::tr("Health graph").toHtmlEscaped() + "</h1>");
+    labelGraphTitle->setText("<h1><img src=\":/res/StatsH.png\"> " + PageGameStats::tr("Health graph").toHtmlEscaped() + "</h1>");
     labelGraphTitle->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
     gbl->addWidget(labelGraphTitle);
     gbl->addWidget(graphic);
@@ -86,20 +84,21 @@
     pageLayout->addWidget(labelGameWin, 0, 0, 1, 2);
 
     // ranking box
-    gb = new QGroupBox(this);
+    gbRanks = new QGroupBox(this);
+    gbRanks->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
     gbl = new QVBoxLayout;
-    labelGameRank = new QLabel(gb);
-    l = new QLabel(this);
+    labelGameRank = new QLabel(gbRanks);
+    QLabel* l = new QLabel(this);
     l->setTextFormat(Qt::RichText);
     l->setText("<h1><img src=\":/res/StatsR.png\"> " + PageGameStats::tr("Ranking").toHtmlEscaped() + "</h1>");
     l->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
     gbl->addWidget(l);
     gbl->addWidget(labelGameRank);
-    gb->setLayout(gbl);
+    gbRanks->setLayout(gbl);
 
     labelGameRank->setTextFormat(Qt::RichText);
     labelGameRank->setAlignment(Qt::AlignTop);
-    pageLayout->addWidget(gb, 1, 0);
+    pageLayout->addWidget(gbRanks, 1, 0);
 
     return pageLayout;
 }
@@ -143,6 +142,9 @@
 void PageGameStats::AddStatText(const QString & msg)
 {
     labelGameStats->setText(labelGameStats->text() + msg);
+    labelDetails->show();
+    labelGameStats->show();
+    gbDetails->show();
 }
 
 void PageGameStats::clear()
@@ -154,6 +156,14 @@
     playerPosition = 0;
     scriptPlayerPosition = 0;
     lastColor = 0;
+    graphic->hide();
+    labelDetails->hide();
+    labelGameStats->hide();
+    gbDetails->hide();
+    gbRanks->hide();
+    pageLayout->setColumnStretch(0, 0);
+    pageLayout->setColumnStretch(1, 0);
+    pageLayout->setHorizontalSpacing(0);
 }
 
 void PageGameStats::restartBtnVisible(bool visible)
@@ -164,7 +174,7 @@
 void PageGameStats::renderStats()
 {
     if(defaultGraphTitle) {
-        labelGraphTitle->setText("<br><h1><img src=\":/res/StatsH.png\"> " + PageGameStats::tr("Health graph").toHtmlEscaped() + "</h1>");
+        labelGraphTitle->setText("<h1><img src=\":/res/StatsH.png\"> " + PageGameStats::tr("Health graph").toHtmlEscaped() + "</h1>");
     } else {
         defaultGraphTitle = true;
     }
@@ -174,6 +184,7 @@
         graphic->hide();
     } else {
         graphic->setScene(Q_NULLPTR);
+        gbDetails->show();
         m_scene.reset(new QGraphicsScene(this));
 
         // min and max value across the entire chart
@@ -262,6 +273,17 @@
 
         graphic->show();
         labelGraphTitle->show();
+        gbDetails->show();
+    }
+    if (!labelGameStats->isHidden())
+    {
+        labelGraphTitle->setText("<br>" + labelGraphTitle->text());
+    }
+    if ((!gbDetails->isHidden()) && (!gbRanks->isHidden()))
+    {
+        pageLayout->setColumnStretch(0, 1);
+        pageLayout->setColumnStretch(1, 1);
+        pageLayout->setHorizontalSpacing(20);
     }
 }
 
@@ -309,7 +331,7 @@
         {
             // TODO: change default picture or add change pic capability
             defaultGraphTitle = false;
-            labelGraphTitle->setText("<br><h1><img src=\":/res/StatsR.png\"> " + info.toHtmlEscaped() + "</h1>");
+            labelGraphTitle->setText("<h1><img src=\":/res/StatsR.png\"> " + info.toHtmlEscaped() + "</h1>");
             break;
         }
         case 'T':   // local team stats
@@ -395,6 +417,7 @@
 
             labelGameRank->setText(labelGameRank->text() + message);
             scriptPlayerPosition = 0;
+            gbRanks->show();
             break;
         }
         case 's' :
--- a/QTfrontend/ui/page/pagegamestats.h	Wed May 15 00:24:53 2019 +0200
+++ b/QTfrontend/ui/page/pagegamestats.h	Wed May 15 18:30:59 2019 +0200
@@ -73,6 +73,11 @@
         bool defaultGraphTitle;
         QScopedPointer<QGraphicsScene> m_scene;
 
+        QLabel* labelDetails;
+        QGroupBox* gbDetails;
+        QGroupBox* gbRanks;
+        QGridLayout* pageLayout;
+
     protected:
         QLayout * bodyLayoutDefinition();
         QLayout * footerLayoutDefinition();
--- a/QTfrontend/ui/page/pagenet.cpp	Wed May 15 00:24:53 2019 +0200
+++ b/QTfrontend/ui/page/pagenet.cpp	Wed May 15 18:30:59 2019 +0200
@@ -109,6 +109,7 @@
     tvServersList->setModel(new HWNetUdpModel(tvServersList));
 
     tvServersList->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
+    tvServersList->horizontalHeader()->setSectionsClickable(false);
 
     static_cast<HWNetServersModel *>(tvServersList->model())->updateList();
 
--- a/QTfrontend/ui/page/pagevideos.cpp	Wed May 15 00:24:53 2019 +0200
+++ b/QTfrontend/ui/page/pagevideos.cpp	Wed May 15 18:30:59 2019 +0200
@@ -133,6 +133,7 @@
         header->setSectionResizeMode(vcSize, QHeaderView::Fixed);
         header->resizeSection(vcSize, 100);
         header->setStretchLastSection(true);
+        header->setSectionsClickable(false);
 
         btnOpenDir = new QPushButton(QPushButton::tr("Open videos directory"), pTableGroup);
         btnOpenDir->setWhatsThis(QPushButton::tr("Open the video directory in your system"));
--- a/QTfrontend/ui/widget/frameTeam.cpp	Wed May 15 00:24:53 2019 +0200
+++ b/QTfrontend/ui/widget/frameTeam.cpp	Wed May 15 18:30:59 2019 +0200
@@ -163,3 +163,11 @@
         setStyleSheet("FrameTeams{ border: transparent }");
     }
 }
+
+void FrameTeams::resizeEvent(QResizeEvent * event)
+{
+    Q_UNUSED(event);
+
+    QResizeEvent* pevent=new QResizeEvent(parentWidget()->size(), parentWidget()->size());
+    QCoreApplication::postEvent(parentWidget(), pevent);
+}
--- a/QTfrontend/ui/widget/frameTeam.h	Wed May 15 00:24:53 2019 +0200
+++ b/QTfrontend/ui/widget/frameTeam.h	Wed May 15 18:30:59 2019 +0200
@@ -53,6 +53,9 @@
         void addTeam(HWTeam team, bool willPlay);
         void removeTeam(HWTeam team);
 
+    protected:
+        virtual void resizeEvent(QResizeEvent * event);
+
     private:
         int currentColor;
 
--- a/README.md	Wed May 15 00:24:53 2019 +0200
+++ b/README.md	Wed May 15 18:30:59 2019 +0200
@@ -1,5 +1,6 @@
 Hedgewars - a turn-based strategy game
 ======================================
+[![Build Status](https://travis-ci.org/hedgewars/hw.svg)](https://travis-ci.org/hedgewars/hw)
 
 Description
 -----------
@@ -134,8 +135,6 @@
 Mercurial as DVCS. A Git repository is also available (mirrored daily)
 at <https://github.com/hedgewars/hw>.
 
-[![Build Status](https://travis-ci.org/hedgewars/hw.svg)](https://travis-ci.org/hedgewars/hw)
-
 Contribute
 ----------
 If you see a bug or have any suggestion please use the official bug tracker at
--- a/hedgewars/avwrapper/avwrapper.c	Wed May 15 00:24:53 2019 +0200
+++ b/hedgewars/avwrapper/avwrapper.c	Wed May 15 18:30:59 2019 +0200
@@ -367,6 +367,8 @@
         VideoTime = (double)g_pVFrame->pts * g_pVStream->time_base.num/g_pVStream->time_base.den;
         do
         {
+            if (!g_pAFrame)
+                return FatalError("Error while writing video frame: g_pAFrame does not exist");
             AudioTime = (double)g_pAFrame->pts * g_pAStream->time_base.num/g_pAStream->time_base.den;
             ret = WriteAudioFrame();
         }
--- a/hedgewars/uConsts.pas	Wed May 15 00:24:53 2019 +0200
+++ b/hedgewars/uConsts.pas	Wed May 15 18:30:59 2019 +0200
@@ -37,7 +37,6 @@
     HaltFatalError      =  52; // Fatal internal error. See logs for more. Also reports error to frontend
     HaltStartupError    =  53; // Failure loading critical resources
     HaltFatalErrorNoIPC =  54; // Fatal internal error, IPC socket is not available
-    HaltVideoRec        =  55; // Failure while video recording
 
     // for automatic tests
     HaltTestSuccess     =  0;  // Test result: success
--- a/hedgewars/uGears.pas	Wed May 15 00:24:53 2019 +0200
+++ b/hedgewars/uGears.pas	Wed May 15 18:30:59 2019 +0200
@@ -729,6 +729,7 @@
     GearsList:= nil;
     while tt <> nil do
     begin
+        FreeAndNilTexture(tt^.Tex);
         t:= tt;
         tt:= tt^.NextGear;
         Dispose(t)
--- a/hedgewars/uGearsHandlersMess.pas	Wed May 15 00:24:53 2019 +0200
+++ b/hedgewars/uGearsHandlersMess.pas	Wed May 15 18:30:59 2019 +0200
@@ -2394,13 +2394,19 @@
             AddVisualGear(hwRound(Gear^.X) - 16 + Random(32), hwRound(Gear^.Y) - 2, vgtSmoke)
     else
         AddVisualGear(hwRound(Gear^.X) - 16 + Random(32), hwRound(Gear^.Y) - 2, vgtSmokeWhite);
-    // health texture
-    FreeAndNilTexture(Gear^.Tex);
-    Gear^.Tex := RenderStringTex(ansistring(inttostr(Gear^.Health)), $ff808080, fnt16);
+
     dec(Gear^.Health, Gear^.Damage);
     Gear^.Damage := 0;
     if Gear^.Health <= 0 then
-        doStepCase(Gear);
+        doStepCase(Gear)
+    else
+        // health texture (FlightTime = health when the last texture was generated)
+        if Gear^.Health <> Gear^.FlightTime then
+            begin
+            Gear^.FlightTime:= Gear^.Health;
+            FreeAndNilTexture(Gear^.Tex);
+            Gear^.Tex := RenderStringTex(ansistring(inttostr(Gear^.Health)), $ff808080, fnt16);
+            end;
 end;
 
 procedure doStepCase(Gear: PGear);
@@ -2469,18 +2475,22 @@
                 AddVisualGear(hwRound(Gear^.X) - 16 + Random(32), hwRound(Gear^.Y) - 2, vgtSmoke)
             else
                 AddVisualGear(hwRound(Gear^.X) - 16 + Random(32), hwRound(Gear^.Y) - 2, vgtSmokeWhite);
+
         dec(Gear^.Health, Gear^.Damage);
         Gear^.Damage := 0;
-        // health texture
-        FreeAndNilTexture(Gear^.Tex);
-        Gear^.Tex := RenderStringTex(ansistring(inttostr(Gear^.Health)), $ff808080, fnt16);
+        // health texture (FlightTime = health when the last texture was generated)
+        if Gear^.Health <> Gear^.FlightTime then
+            begin
+            Gear^.FlightTime:= Gear^.Health;
+            FreeAndNilTexture(Gear^.Tex);
+            Gear^.Tex := RenderStringTex(ansistring(inttostr(Gear^.Health)), $ff808080, fnt16);
+            end;
         end
     else
         begin
         // health texture for health crate
-        if (k = gtCase) and ((Gear^.Pos and $02) <> 0) then
-            begin
-            FreeAndNilTexture(Gear^.Tex);
+        if (k = gtCase) and ((Gear^.Pos and posCaseHealth) <> 0) then
+            begin
             if ((Gear^.State and gstFrozen) = 0) then
                 begin
                 // Karma=2: Always hide health
@@ -2496,9 +2506,23 @@
                 else
                     i:= 1;
                 if i = 1 then
-                    Gear^.Tex := RenderStringTex(ansistring(inttostr(Gear^.Health)), $ff80ff80, fnt16)
+                    begin
+                    if Gear^.Health <> Gear^.FlightTime then
+                        begin
+                        Gear^.FlightTime:= Gear^.Health;
+                        FreeAndNilTexture(Gear^.Tex);
+                        Gear^.Tex := RenderStringTex(ansistring(inttostr(Gear^.Health)), $ff80ff80, fnt16)
+                        end
+                    end
                 else
-                    Gear^.Tex := RenderStringTex(trmsg[sidUnknownGearValue], $ff80ff80, fnt16)
+                    begin
+                    if Gear^.FlightTime <> $ffffffff then
+                        begin
+                        Gear^.FlightTime:= $ffffffff;
+                        FreeAndNilTexture(Gear^.Tex);
+                        Gear^.Tex := RenderStringTex(trmsg[sidUnknownGearValue], $ff80ff80, fnt16)
+                        end
+                    end
                 end;
             end;
         if Gear^.Timer = 500 then
--- a/hedgewars/uGearsHedgehog.pas	Wed May 15 00:24:53 2019 +0200
+++ b/hedgewars/uGearsHedgehog.pas	Wed May 15 18:30:59 2019 +0200
@@ -1282,6 +1282,11 @@
     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
+    begin
+    PlaySoundV(sndFlyAway, Gear^.Hedgehog^.Team^.voicepack);
+    Gear^.Hedgehog^.FlownOffMap:= true;
+    end;
 
 end;
 
--- a/hedgewars/uGearsRender.pas	Wed May 15 00:24:53 2019 +0200
+++ b/hedgewars/uGearsRender.pas	Wed May 15 18:30:59 2019 +0200
@@ -99,6 +99,7 @@
 var  eX, eY, dX, dY: LongInt;
     i, sX, sY, x, y, d: LongInt;
     b: boolean;
+    angle: real;
 begin
     if (X1 = X2) and (Y1 = Y2) then
         begin
@@ -110,6 +111,7 @@
     eY:= 0;
     dX:= X2 - X1;
     dY:= Y2 - Y1;
+    angle:= arctan2(dY, dX) * 180 / PI - 90;
 
     if (dX > 0) then
         sX:= 1
@@ -160,8 +162,8 @@
         if b then
             begin
             inc(roplen);
-            if (roplen mod 4) = 0 then
-                DrawSprite(sprRopeNode, x - 2, y - 2, 0)
+            if (roplen mod cRopeNodeStep) = 0 then
+                DrawSpriteRotatedF(sprRopeNode, x, y, roplen div cRopeNodeStep, 1, angle);
             end
     end;
     DrawRopeLine:= roplen;
@@ -1654,7 +1656,7 @@
 begin
 if isShowGearInfo and (Gear^.RenderHealth) and (Gear^.Tex <> nil) then
     begin
-    if (Gear^.Kind = gtCase) and ((Gear^.Pos and $02) <> 0) then
+    if (Gear^.Kind = gtCase) and ((Gear^.Pos and posCaseHealth) <> 0) then
         DrawTextureCentered(x, y - 38, Gear^.Tex);
     if (Gear^.Kind = gtExplosives) then
         DrawTextureCentered(x, y - 38, Gear^.Tex);
--- a/hedgewars/uLandObjects.pas	Wed May 15 00:24:53 2019 +0200
+++ b/hedgewars/uLandObjects.pas	Wed May 15 18:30:59 2019 +0200
@@ -114,9 +114,15 @@
     BlitImageAndGenerateCollisionInfo(cpX, cpY, Width, Image, LandFlags, false);
 end;
 
+function LerpByte(src, dst: Byte; l: LongWord): LongWord; inline;
+begin
+    LerpByte:= ((255 - l) * src + l * dst) div 255;
+end;
+
 procedure BlitImageAndGenerateCollisionInfo(cpX, cpY, Width: Longword; Image: PSDL_Surface; LandFlags: Word; Flip: boolean);
 var p: PLongwordArray;
-    px, x, y: Longword;
+    pLandColor: PLongWord;
+    alpha, color, landColor, x, y: LongWord;
     bpp: LongInt;
 begin
 WriteToConsole('Generating collision info... ');
@@ -142,21 +148,28 @@
         begin
         // map image pixels per line backwards if in flip mode
         if Flip then
-            px:= Pred(Image^.w) - x
+            color:= p^[Pred(Image^.w) - x]
+        else
+            color:= p^[x];
+
+        if (cReducedQuality and rqBlurryLand) = 0 then
+            pLandColor:= @LandPixels[cpY + y, cpX + x]
         else
-            px:= x;
+            pLandColor:= @LandPixels[(cpY + y) div 2, (cpX + x) div 2];
 
-        if (p^[px] and AMask) <> 0 then
+        landColor:= pLandColor^;
+        alpha:= (landColor and AMask) shr AShift;
+
+        if ((color and AMask) <> 0) and (alpha <> 255)  then
             begin
-            if (cReducedQuality and rqBlurryLand) = 0 then
-                begin
-                if (LandPixels[cpY + y, cpX + x] = 0)
-                or (((LandPixels[cpY + y, cpX + x] and AMask) shr AShift) < 255) then
-                    LandPixels[cpY + y, cpX + x]:= p^[px];
-                end
+            if alpha = 0 then
+                pLandColor^:= color
             else
-                if LandPixels[(cpY + y) div 2, (cpX + x) div 2] = 0 then
-                    LandPixels[(cpY + y) div 2, (cpX + x) div 2]:= p^[px];
+                pLandColor^:=
+                   (LerpByte((color and RMask) shr RShift, (landColor and RMask) shr RShift, alpha) shl RShift)
+                    or (LerpByte((color and GMask) shr GShift, (landColor and GMask) shr GShift, alpha) shl GShift)
+                    or (LerpByte((color and BMask) shr BShift, (landColor and BMask) shr BShift, alpha) shl BShift)
+                    or (LerpByte(alpha, 255, (color and AMask) shr AShift) shl AShift);
 
             if Land[cpY + y, cpX + x] <= lfAllObjMask then
                 Land[cpY + y, cpX + x]:= lfObject or LandFlags
@@ -170,11 +183,6 @@
 WriteLnToConsole(msgOK)
 end;
 
-function LerpByte(src, dst: Byte; l: LongWord): LongWord; inline;
-begin
-    LerpByte:= ((255 - l) * src + l * dst) div 255;
-end;
-
 procedure BlitOverlayAndGenerateCollisionInfo(cpX, cpY: Longword; Image: PSDL_Surface);
 var p: PLongwordArray;
     pLandColor: PLongWord;
@@ -229,7 +237,8 @@
 
 procedure BlitImageUsingMask(cpX, cpY: Longword;  Image, Mask: PSDL_Surface);
 var p, mp: PLongwordArray;
-    x, y: Longword;
+    pLandColor: PLongWord;
+    alpha, color, landColor, x, y: Longword;
     bpp: LongInt;
 begin
 WriteToConsole('Generating collision info... ');
@@ -250,19 +259,32 @@
     begin
     for x:= 0 to Pred(Image^.w) do
         begin
+        color:= p^[x];
+
         if (cReducedQuality and rqBlurryLand) = 0 then
-            begin
-            if (LandPixels[cpY + y, cpX + x] = 0)
-            or (((p^[x] and AMask) <> 0) and (((LandPixels[cpY + y, cpX + x] and AMask) shr AShift) < 255)) then
-                LandPixels[cpY + y, cpX + x]:= p^[x];
-            end
+            pLandColor:= @LandPixels[cpY + y, cpX + x]
         else
-            if LandPixels[(cpY + y) div 2, (cpX + x) div 2] = 0 then
-                LandPixels[(cpY + y) div 2, (cpX + x) div 2]:= p^[x];
+            pLandColor:= @LandPixels[(cpY + y) div 2, (cpX + x) div 2];
+
+        landColor:= pLandColor^;
+        alpha:= (landColor and AMask) shr AShift;
+
+        if ((color and AMask) <> 0) and (alpha <> 255)  then
+        begin
+            if alpha = 0 then
+                pLandColor^:= color
+            else
+                pLandColor^:=
+                   (LerpByte((color and RMask) shr RShift, (landColor and RMask) shr RShift, alpha) shl RShift)
+                   or (LerpByte((color and GMask) shr GShift, (landColor and GMask) shr GShift, alpha) shl GShift)
+                   or (LerpByte((color and BMask) shr BShift, (landColor and BMask) shr BShift, alpha) shl BShift)
+                   or (LerpByte(alpha, 255, (color and AMask) shr AShift) shl AShift);
+        end;
 
         if (Land[cpY + y, cpX + x] <= lfAllObjMask) or (Land[cpY + y, cpX + x] and lfObject <> 0)  then
             SetLand(Land[cpY + y, cpX + x], mp^[x]);
         end;
+
     p:= PLongwordArray(@(p^[Image^.pitch shr 2]));
     mp:= PLongwordArray(@(mp^[Mask^.pitch shr 2]))
     end;
@@ -1036,6 +1058,8 @@
         cIce:= true
     else if key = 'snow' then
         cSnow:= true
+    else if key = 'rope-step' then
+        cRopeNodeStep:= StrToInt(s)
     else if key = 'sd-water-top' then
         begin
         i:= Pos(',', s);
--- a/hedgewars/uPhysFSLayer.pas	Wed May 15 00:24:53 2019 +0200
+++ b/hedgewars/uPhysFSLayer.pas	Wed May 15 18:30:59 2019 +0200
@@ -35,6 +35,7 @@
 procedure pfsReadLn(f: PFSFile; var s: shortstring);
 procedure pfsReadLnA(f: PFSFile; var s: ansistring);
 procedure pfsWriteLn(f: PFSFile; s: shortstring);
+procedure pfsWriteRaw(f: PFSFile; s: PChar; len: QWord);
 function pfsBlockRead(f: PFSFile; buf: pointer; size: Int64): Int64;
 function pfsEOF(f: PFSFile): boolean;
 
@@ -179,6 +180,11 @@
     PHYSFS_writeBytes(f, @c, 1);
 end;
 
+procedure pfsWriteRaw(f: PFSFile; s: PChar; len: QWord);
+begin
+    PHYSFS_writeBytes(f, s, len);
+end;
+
 function pfsBlockRead(f: PFSFile; buf: pointer; size: Int64): Int64;
 var r: Int64;
 begin
--- a/hedgewars/uSound.pas	Wed May 15 00:24:53 2019 +0200
+++ b/hedgewars/uSound.pas	Wed May 15 18:30:59 2019 +0200
@@ -330,7 +330,8 @@
             (FileName:         'Leavemealone.ogg'; Path: ptVoices; AltPath: ptNone),// sndLeaveMeAlone
             (FileName:                 'Ouch.ogg'; Path: ptVoices; AltPath: ptNone),// sndOuch
             (FileName:                  'Hmm.ogg'; Path: ptVoices; AltPath: ptNone),// sndHmm
-            (FileName:                 'Kiss.ogg'; Path: ptSounds; AltPath: ptNone) // sndKiss
+            (FileName:                 'Kiss.ogg'; Path: ptSounds; AltPath: ptNone),// sndKiss
+            (FileName:              'Flyaway.ogg'; Path: ptVoices; AltPath: ptNone) // sndFlyAway
             );
 
 
--- a/hedgewars/uTeams.pas	Wed May 15 00:24:53 2019 +0200
+++ b/hedgewars/uTeams.pas	Wed May 15 18:30:59 2019 +0200
@@ -714,6 +714,7 @@
     if checkFails((Health > 0) and (Health <= cMaxHogHealth), 'Invalid hedgehog health (must be between 1 and '+IntToStr(cMaxHogHealth)+')', true) then exit;
     CurrentHedgehog^.Name:= id;
     CurrentHedgehog^.InitialHealth:= Health;
+    CurrentHedgehog^.RevengeHog:= nil;
     inc(HedgehogsNumber)
     end
 end;
@@ -749,6 +750,7 @@
     CurrentHedgehog^.Name:= id;
     CurrentHedgehog^.InitialHealth:= Gear^.Health;
     CurrentHedgehog^.RevengeHog:= nil;
+    CurrentHedgehog^.FlownOffMap:= false;
     CurrHedgehog:= HedgehogsNumber;
     inc(HedgehogsNumber)
     end
--- a/hedgewars/uTypes.pas	Wed May 15 00:24:53 2019 +0200
+++ b/hedgewars/uTypes.pas	Wed May 15 18:30:59 2019 +0200
@@ -156,7 +156,7 @@
             sndLandGun, sndCaseImpact, sndExtraDamage, sndFirePunchHit, sndGrenade, sndThisOneIsMine,
             sndWhatThe, sndSoLong, sndOhDear, sndGonnaGetYou, sndDrat, sndBugger, sndAmazing,
             sndBrilliant, sndExcellent, sndFire, sndWatchThis, sndRunAway, sndRevenge, sndCutItOut,
-            sndLeaveMeAlone, sndOuch, sndHmm, sndKiss);
+            sndLeaveMeAlone, sndOuch, sndHmm, sndKiss, sndFlyAway);
 
     // Available ammo types to be used by hedgehogs
     TAmmoType  = (amNothing, amGrenade, amClusterBomb, amBazooka, amBee, amShotgun, amPickHammer, // 6
@@ -422,6 +422,7 @@
             HealthBarHealth: LongInt;
             Effects: array[THogEffect] of LongInt;
             RevengeHog: PHedgehog;   // For which hog this hog wants revenge most. For sndRevenge taunt
+            FlownOffMap: boolean; // When hedgehog has flown far away off the map left or right
             end;
 
     TTeam = record
--- a/hedgewars/uUtils.pas	Wed May 15 00:24:53 2019 +0200
+++ b/hedgewars/uUtils.pas	Wed May 15 18:30:59 2019 +0200
@@ -526,6 +526,7 @@
 end;
 
 procedure AddFileLogRaw(s: pchar); cdecl;
+var msgLine: PChar;
 begin
 s:= s;
 {$IFNDEF PAS2C}
@@ -534,9 +535,17 @@
 if SDL_LockMutex(logMutex) <> 0 then
     OutError('Logging mutex could not be locked!', true);
 {$ENDIF}
-// TODO: uncomment next two lines
-// write(logFile, s);
-// flush(logFile);
+msgLine:= Str2PChar(IntToStr(GameTicks) + ': ');
+if (logFile <> nil) then
+    begin
+    pfsWriteRaw(logFile, msgLine, StrLen(msgLine));
+    pfsWriteRaw(logFile, s, StrLen(s));
+    end
+else
+    begin
+    Write(stdout, msgLine);
+    Flush(stdout);
+    end;
 {$IFDEF USE_VIDEO_RECORDING}
 if SDL_UnlockMutex(logMutex) <> 0 then
     OutError('Logging mutex could not be unlocked!', true);
--- a/hedgewars/uVariables.pas	Wed May 15 00:24:53 2019 +0200
+++ b/hedgewars/uVariables.pas	Wed May 15 18:30:59 2019 +0200
@@ -116,6 +116,7 @@
     cFeatureSize    : LongInt;
     cMapGen         : TMapGen;
     cRopePercent    : LongWord;
+    cRopeNodeStep   : LongWord;
     cGetAwayTime    : LongWord;
 
     cAdvancedMapGenMode: boolean;
@@ -465,8 +466,8 @@
             Width:  48; Height: 48; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpMedium; getDimensions: false; getImageDimensions: true),// sprParachute
             (FileName:     'Target'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
             Width:  32; Height: 32; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpMedium; getDimensions: false; getImageDimensions: true),// sprTarget
-            (FileName:   'RopeNode'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
-            Width:   6; Height:  6; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpHighest; getDimensions: false; getImageDimensions: true),// sprRopeNode
+            (FileName:   'RopeNode'; Path: ptCurrTheme; AltPath: ptGraphics; Texture: nil; Surface: nil;
+            Width:   16; Height:  16; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpHighest; getDimensions: false; getImageDimensions: true),// sprRopeNode
             (FileName:   'thinking'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
             Width:  32; Height: 32; imageWidth: 0; imageHeight: 0; saveSurf: false; critical: true; checkSum: false; priority: tpLowest; getDimensions: false; getImageDimensions: true),// sprQuestion
             (FileName:   'PowerBar'; Path: ptGraphics; AltPath: ptNone; Texture: nil; Surface: nil;
@@ -2848,6 +2849,7 @@
     cInitHealth         := 100;
     cDamagePercent      := 100;
     cRopePercent        := 100;
+    cRopeNodeStep       := 4;
     cGetAwayTime        := 100;
     cMineDudPercent     := 0;
     cTemplateFilter     := 0;
--- a/hedgewars/uVideoRec.pas	Wed May 15 00:24:53 2019 +0200
+++ b/hedgewars/uVideoRec.pas	Wed May 15 18:30:59 2019 +0200
@@ -48,7 +48,7 @@
 procedure freeModule;
 
 implementation
-uses uVariables, GLunit, SDLh, SysUtils, uUtils, uIO, uMisc, uConsts, uTypes, uDebug;
+uses uVariables, GLunit, SDLh, SysUtils, uUtils, uIO, uMisc, uTypes, uDebug;
 
 type TAddFileLogRaw = procedure (s: pchar); cdecl;
 const AvwrapperLibName = 'libavwrapper';
@@ -162,8 +162,7 @@
     Close(cameraFile);
     if AVWrapper_Close() < 0 then
         begin
-        AddFileLog('AVWrapper_Close() has failed.');
-        halt(HaltVideoRec);
+        OutError('AVWrapper_Close() has failed.', true);
         end;
 {$IOCHECKS OFF}
     if FileExists(cameraFileName) then
@@ -184,8 +183,7 @@
 
     if AVWrapper_WriteFrame(RGB_Buffer) < 0 then
         begin
-        AddFileLog('AVWrapper_WriteFrame(RGB_Buffer) has failed.');
-        halt(HaltVideoRec);
+        OutError('AVWrapper_WriteFrame(RGB_Buffer) has failed.', true);
         end;
 
     // inform frontend that we have encoded new frame
Binary file share/hedgewars/Data/Graphics/RopeNode.png has changed
--- a/share/hedgewars/Data/Maps/ClimbHome/map.lua	Wed May 15 00:24:53 2019 +0200
+++ b/share/hedgewars/Data/Maps/ClimbHome/map.lua	Wed May 15 18:30:59 2019 +0200
@@ -32,7 +32,6 @@
 local addCake = true
 local takeASeat = false
 local Stars = {}
-local tauntNoo = false
 local jokeAwardNavy = nil
 local jokeAwardSpeed = nil
 local jokeAwardDamage = nil
@@ -189,7 +188,6 @@
     SetWaterLine(32768)
     YouWon = false
     YouLost = false
-    tauntNoo = false
     takeASeat = false
     recordBroken = false
     currTeam = GetHogTeamName(CurrentHedgehog)
@@ -503,17 +501,6 @@
                 takeASeat = true
             end
     
-            -- play taunts
-            if not YouWon and not YouLost then
-                local nooDistance = 500
-                if ((x < -nooDistance and vx < 0) or (x > LAND_WIDTH+nooDistance and vx > 0)) then
-                    if (tauntNoo == false and distanceFromWater > 80) then
-                        PlaySound(sndNooo, CurrentHedgehog)
-                        tauntNoo = true
-                    end
-                end
-            end
-
             if addCake and CakeTries < 10 and y < 32600 and y > 3000 and Cake == nil then 
                 -- doing this just after the start the first time to take advantage of randomness sources
                 -- Pick a clear y to start with
--- a/share/hedgewars/Data/Missions/Campaign/A_Classic_Fairytale/first_blood.lua	Wed May 15 00:24:53 2019 +0200
+++ b/share/hedgewars/Data/Missions/Campaign/A_Classic_Fairytale/first_blood.lua	Wed May 15 18:30:59 2019 +0200
@@ -550,6 +550,9 @@
   AddEvent(CheckTimesUp, {}, DoTimesUp, {}, 1)
   -- Remove up the old mole blockade from the parachute challenge
   EraseSprite(rope2GirderX, rope2GirderY, sprAmGirder, 6)
+  for i=-4,4 do
+    AddVisualGear(rope2GirderX, rope2GirderY + i * 18, vgtSteam, false, 0)
+  end
 end
 
 function DoChoice()
--- a/share/hedgewars/Data/Missions/Training/Basic_Training_-_Bazooka.lua	Wed May 15 00:24:53 2019 +0200
+++ b/share/hedgewars/Data/Missions/Training/Basic_Training_-_Bazooka.lua	Wed May 15 18:30:59 2019 +0200
@@ -53,6 +53,7 @@
 	SetEffect(hog, heResurrectable, 1)
 
 	SendHealthStatsOff()
+	SendRankingStatsOff()
 end
 
 function onGearResurrect(gear, vGear)
@@ -228,7 +229,6 @@
 		end
 		SendStat(siCustomAchievement, loc("Good job!"))
 		SendStat(siGameResult, loc("You have completed the Basic Bazooka Training!"))
-		SendStat(siPlayerKills, "0", GetHogTeamName(hog))
 		EndGame()
 		SetState(hog, gstWinner)
 		gameOver = true
--- a/share/hedgewars/Data/Missions/Training/Basic_Training_-_Flying_Saucer.lua	Wed May 15 00:24:53 2019 +0200
+++ b/share/hedgewars/Data/Missions/Training/Basic_Training_-_Flying_Saucer.lua	Wed May 15 18:30:59 2019 +0200
@@ -112,7 +112,8 @@
 	MessageTime = 6500,
 	Message = loc("Now dive just one more time and collect the next crate.") .. "|" ..
 		loc("Tip: Don't remain for too long in the water, or you won't make it."),
-	Ammo = { [amJetpack] = 2 }, }
+	Ammo = { [amJetpack] = 2 },
+	Respawn = { X = 1968, Y = -1, FaceLeft = true }, }
 
 -- The Grenade Drop Target
 local BoomTarget = 8
@@ -214,7 +215,6 @@
 
 	SendStat(siGameResult, loc("You have finished the Flying Saucer Training!"))
 	SendStat(siCustomAchievement, loc("Good job!"))
-	SendStat(siPlayerKills, "0", GetHogTeamName(Player))
 
 	EndTurn(true)
 	EndGame()
@@ -242,6 +242,9 @@
 	if Barrels[2] == nil then
 		Barrels[2] = AddGear(1648, 463, gtExplosives, 0, 0, 0, 0)
 	end
+	if Barrels[3] == nil then
+		Barrels[3] = AddGear(1513, 575, gtExplosives, 0, 0, 0, 0)
+	end
 
 	for i=1,#Barrels do
 		SetHealth(Barrels[i], 1)
@@ -412,6 +415,7 @@
 
 function onGameStart()
 	SendHealthStatsOff()
+	SendRankingStatsOff()
 
 	-- Girder near first crate
 	PlaceGirder(1257, 204, 6)
@@ -519,6 +523,9 @@
 		Barrels[2] = nil
 		AddCaption(loc("Kaboom!"), capcolDefault, capgrpMessage)
 	end
+	if Gear == Barrels[3] then
+		Barrels[3] = nil
+	end
 end
 
 
--- a/share/hedgewars/Data/Missions/Training/Basic_Training_-_Grenade.lua	Wed May 15 00:24:53 2019 +0200
+++ b/share/hedgewars/Data/Missions/Training/Basic_Training_-_Grenade.lua	Wed May 15 18:30:59 2019 +0200
@@ -45,6 +45,7 @@
 	SetEffect(hog, heResurrectable, 1)
 
 	SendHealthStatsOff()
+	SendRankingStatsOff()
 end
 
 function onGearResurrect(gear, vGear)
@@ -197,7 +198,6 @@
 		end
 		SendStat(siCustomAchievement, loc("Good job!"))
 		SendStat(siGameResult, loc("You have completed the Basic Grenade Training!"))
-		SendStat(siPlayerKills, "0", GetHogTeamName(hog))
 		EndGame()
 		gameOver = true
 		SetState(hog, gstWinner)
--- a/share/hedgewars/Data/Missions/Training/Basic_Training_-_Movement.lua	Wed May 15 00:24:53 2019 +0200
+++ b/share/hedgewars/Data/Missions/Training/Basic_Training_-_Movement.lua	Wed May 15 18:30:59 2019 +0200
@@ -154,6 +154,7 @@
 	end
 	
 	SendHealthStatsOff()
+	SendRankingStatsOff()
 end
 
 local function LoadGearData()
@@ -265,7 +266,6 @@
 	SendStat(siGameResult, loc("You have completed the Basic Movement Training!"))
 	SendStat(siCustomAchievement, loc("Congratulations!"))
 	SendStat(siCustomAchievement, loc("Return to the training menu by pressing the “Go back” button."))
-	SendStat(siPlayerKills, "0", GetHogTeamName(hog_greenhorn))
 	PlaySound(sndVictory, CurrentHedgehog)
 	-- Disable controls, end game
 	SetInputMask(0)
--- a/share/hedgewars/Data/Missions/Training/Basic_Training_-_Rope.lua	Wed May 15 00:24:53 2019 +0200
+++ b/share/hedgewars/Data/Missions/Training/Basic_Training_-_Rope.lua	Wed May 15 18:30:59 2019 +0200
@@ -105,6 +105,7 @@
 	drawMap()
 
 	SendHealthStatsOff()
+	SendRankingStatsOff()
 
 end
 
@@ -130,7 +131,31 @@
 local function eraseGirder(id)
 	EraseSprite(girderData[id][1], girderData[id][2], sprAmGirder, girderData[id][3], false, false, false, false)
 	PlaySound(sndVaporize)
-	AddVisualGear(girderData[id][1], girderData[id][2], vgtSteam, false, 0)
+	local dir = girderData[id][3]
+	if dir == 4 then
+		-- long horizontal
+		for i=-4,4 do
+			AddVisualGear(girderData[id][1] + i * 18, girderData[id][2], vgtSteam, false, 0)
+		end
+	elseif dir == 0 then
+		-- short horizontal
+		for i=-2,1 do
+			AddVisualGear(10 + girderData[id][1] + i * 20, girderData[id][2], vgtSteam, false, 0)
+		end
+	elseif dir == 6 then
+		-- long vertical
+		for i=-4,4 do
+			AddVisualGear(girderData[id][1], girderData[id][2] + i * 18, vgtSteam, false, 0)
+		end
+	elseif dir == 2 then
+		-- short vertical
+		for i=-2,1 do
+			AddVisualGear(girderData[id][1], 10 + girderData[id][2] + i * 20, vgtSteam, false, 0)
+		end
+	else
+		AddVisualGear(girderData[id][1], girderData[id][2], vgtSteam, false, 0)
+	end
+
 	AddCaption(loc("Barrier unlocked!"))
 end
 
@@ -354,7 +379,6 @@
 			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!"))
-			SendStat(siPlayerKills, "0", teamName)
 			EndGame()
 			SetState(hog, gstWinner)
 			gameOver = true
--- a/share/hedgewars/Data/Scripts/Multiplayer/Space_Invasion.lua	Wed May 15 00:24:53 2019 +0200
+++ b/share/hedgewars/Data/Scripts/Multiplayer/Space_Invasion.lua	Wed May 15 18:30:59 2019 +0200
@@ -1103,6 +1103,8 @@
 
 	SI.wepCount = 3
 
+	SetSoundMask(sndFlyAway, true)
+
 end
 
 function onGameStart()
--- a/share/hedgewars/Data/Scripts/Multiplayer/Tumbler.lua	Wed May 15 00:24:53 2019 +0200
+++ b/share/hedgewars/Data/Scripts/Multiplayer/Tumbler.lua	Wed May 15 18:30:59 2019 +0200
@@ -632,6 +632,7 @@
 	wepCount = 3
 
 	DisableGameFlags(gfArtillery + gfSharedAmmo + gfPerHogAmmo + gfTagTeam + gfPlaceHog + gfInvulnerable)
+	SetSoundMask(sndFlyAway, true)
 
 end
 
--- a/share/hedgewars/Data/Sounds/voices/British/CMakeLists.txt	Wed May 15 00:24:53 2019 +0200
+++ b/share/hedgewars/Data/Sounds/voices/British/CMakeLists.txt	Wed May 15 18:30:59 2019 +0200
@@ -14,6 +14,7 @@
 Fire.ogg
 Firepunch*.ogg
 Flawless.ogg
+Flyaway.ogg
 Gonnagetyou.ogg
 Grenade.ogg
 Hello.ogg
Binary file share/hedgewars/Data/Sounds/voices/British/Flyaway.ogg has changed
--- a/share/hedgewars/Data/Sounds/voices/Classic/CMakeLists.txt	Wed May 15 00:24:53 2019 +0200
+++ b/share/hedgewars/Data/Sounds/voices/Classic/CMakeLists.txt	Wed May 15 18:30:59 2019 +0200
@@ -14,6 +14,7 @@
 Fire.ogg
 Firepunch*.ogg
 Flawless.ogg
+Flyaway.ogg
 Gonnagetyou.ogg
 Grenade.ogg
 Hello.ogg
Binary file share/hedgewars/Data/Sounds/voices/Classic/Flyaway.ogg has changed
--- a/share/hedgewars/Data/Sounds/voices/Default/CMakeLists.txt	Wed May 15 00:24:53 2019 +0200
+++ b/share/hedgewars/Data/Sounds/voices/Default/CMakeLists.txt	Wed May 15 18:30:59 2019 +0200
@@ -14,6 +14,7 @@
 Fire.ogg
 Firepunch*.ogg
 Flawless.ogg
+Flyaway.ogg
 Gonnagetyou.ogg
 Grenade.ogg
 Hello.ogg
Binary file share/hedgewars/Data/Sounds/voices/Default/Flyaway.ogg has changed
--- a/share/hedgewars/Data/Sounds/voices/Default_es/CMakeLists.txt	Wed May 15 00:24:53 2019 +0200
+++ b/share/hedgewars/Data/Sounds/voices/Default_es/CMakeLists.txt	Wed May 15 18:30:59 2019 +0200
@@ -7,6 +7,7 @@
 Firstblood.ogg
 Firepunch*.ogg
 Flawless.ogg
+Flyaway.ogg
 Hello.ogg
 Hurry.ogg
 Illgetyou.ogg
Binary file share/hedgewars/Data/Sounds/voices/Default_es/Flyaway.ogg has changed
--- a/share/hedgewars/Data/Sounds/voices/Default_pl/CMakeLists.txt	Wed May 15 00:24:53 2019 +0200
+++ b/share/hedgewars/Data/Sounds/voices/Default_pl/CMakeLists.txt	Wed May 15 18:30:59 2019 +0200
@@ -7,6 +7,7 @@
 Firstblood.ogg
 Firepunch*.ogg
 Flawless.ogg
+Flyaway.ogg
 Hello.ogg
 Hurry.ogg
 Illgetyou.ogg
Binary file share/hedgewars/Data/Sounds/voices/Default_pl/Flyaway.ogg has changed
--- a/share/hedgewars/Data/Sounds/voices/Default_ru/CMakeLists.txt	Wed May 15 00:24:53 2019 +0200
+++ b/share/hedgewars/Data/Sounds/voices/Default_ru/CMakeLists.txt	Wed May 15 18:30:59 2019 +0200
@@ -7,6 +7,7 @@
 Firstblood.ogg
 Firepunch*.ogg
 Flawless.ogg
+Flyaway.ogg
 Hello.ogg
 Hmm.ogg
 Hurry.ogg
--- a/share/hedgewars/Data/Sounds/voices/Default_uk/CMakeLists.txt	Wed May 15 00:24:53 2019 +0200
+++ b/share/hedgewars/Data/Sounds/voices/Default_uk/CMakeLists.txt	Wed May 15 18:30:59 2019 +0200
@@ -14,6 +14,7 @@
 Fire.ogg
 Firepunch*.ogg
 Flawless.ogg
+Flyaway.ogg
 Gonnagetyou.ogg
 Grenade.ogg
 Hello.ogg
Binary file share/hedgewars/Data/Sounds/voices/Default_uk/Flyaway.ogg has changed
--- a/share/hedgewars/Data/Sounds/voices/Mobster/CMakeLists.txt	Wed May 15 00:24:53 2019 +0200
+++ b/share/hedgewars/Data/Sounds/voices/Mobster/CMakeLists.txt	Wed May 15 18:30:59 2019 +0200
@@ -14,6 +14,7 @@
 Fire.ogg
 Firepunch*.ogg
 Flawless.ogg
+Flyaway.ogg
 Gonnagetyou.ogg
 Grenade.ogg
 Hello.ogg
Binary file share/hedgewars/Data/Sounds/voices/Mobster/Flyaway.ogg has changed
--- a/share/hedgewars/Data/Sounds/voices/Pirate/CMakeLists.txt	Wed May 15 00:24:53 2019 +0200
+++ b/share/hedgewars/Data/Sounds/voices/Pirate/CMakeLists.txt	Wed May 15 18:30:59 2019 +0200
@@ -14,6 +14,7 @@
 Fire.ogg
 Firepunch*.ogg
 Flawless.ogg
+Flyaway.ogg
 Gonnagetyou.ogg
 Grenade.ogg
 Hello.ogg
Binary file share/hedgewars/Data/Sounds/voices/Pirate/Flyaway.ogg has changed
--- a/share/hedgewars/Data/Sounds/voices/Robot/CMakeLists.txt	Wed May 15 00:24:53 2019 +0200
+++ b/share/hedgewars/Data/Sounds/voices/Robot/CMakeLists.txt	Wed May 15 18:30:59 2019 +0200
@@ -14,6 +14,7 @@
 Fire.ogg
 Firepunch*.ogg
 Flawless.ogg
+Flyaway.ogg
 Gonnagetyou.ogg
 Grenade.ogg
 Hello.ogg
Binary file share/hedgewars/Data/Sounds/voices/Robot/Flyaway.ogg has changed
--- a/share/hedgewars/Data/Sounds/voices/Russian/CMakeLists.txt	Wed May 15 00:24:53 2019 +0200
+++ b/share/hedgewars/Data/Sounds/voices/Russian/CMakeLists.txt	Wed May 15 18:30:59 2019 +0200
@@ -14,6 +14,7 @@
 Fire.ogg
 Firepunch*.ogg
 Flawless.ogg
+Flyaway.ogg
 Gonnagetyou.ogg
 Grenade.ogg
 Hello.ogg
Binary file share/hedgewars/Data/Sounds/voices/Russian/Flyaway.ogg has changed
--- a/share/hedgewars/Data/Sounds/voices/Russian_pl/CMakeLists.txt	Wed May 15 00:24:53 2019 +0200
+++ b/share/hedgewars/Data/Sounds/voices/Russian_pl/CMakeLists.txt	Wed May 15 18:30:59 2019 +0200
@@ -7,6 +7,7 @@
 Firstblood.ogg
 Firepunch*.ogg
 Flawless.ogg
+Flyaway.ogg
 Hello.ogg
 Hurry.ogg
 Illgetyou.ogg
Binary file share/hedgewars/Data/Sounds/voices/Russian_pl/Flyaway.ogg has changed
--- a/share/hedgewars/Data/Sounds/voices/Singer/CMakeLists.txt	Wed May 15 00:24:53 2019 +0200
+++ b/share/hedgewars/Data/Sounds/voices/Singer/CMakeLists.txt	Wed May 15 18:30:59 2019 +0200
@@ -14,6 +14,7 @@
 Fire.ogg
 Firepunch*.ogg
 Flawless.ogg
+Flyaway.ogg
 Gonnagetyou.ogg
 Grenade.ogg
 Hello.ogg
Binary file share/hedgewars/Data/Sounds/voices/Singer/Flyaway.ogg has changed