- Merge default qmlfrontend
authorunc0rr
Thu, 02 Apr 2015 21:09:56 +0300
branchqmlfrontend
changeset 10886 99273b7afbff
parent 10823 1ff3dd3705b1 (current diff)
parent 10885 3ea36d8d4636 (diff)
child 10888 a04e04aaf599
- Merge default - *cough* sorry, but also in this commit: move *.qml files into qrc, include qmlFrontend into cmake build, also exclude QTfrontend
CMakeLists.txt
hedgewars/SDLh.pas
hedgewars/hwengine.pas
hedgewars/uIO.pas
hedgewars/uRender.pas
hedgewars/uTypes.pas
hedgewars/uVariables.pas
qmlFrontend/CMakeLists.txt
qmlFrontend/main.cpp
qmlFrontend/qmlFrontend.qrc
--- a/CMakeLists.txt	Mon Feb 16 22:33:15 2015 +0300
+++ b/CMakeLists.txt	Thu Apr 02 21:09:56 2015 +0300
@@ -224,7 +224,7 @@
     add_subdirectory(project_files/Android-build)
 else(ANDROID)
     add_subdirectory(bin)
-    add_subdirectory(QTfrontend)
+    add_subdirectory(qmlFrontend)
     add_subdirectory(share)
     add_subdirectory(tools)
 endif(ANDROID)
--- a/QTfrontend/game.cpp	Mon Feb 16 22:33:15 2015 +0300
+++ b/QTfrontend/game.cpp	Thu Apr 02 21:09:56 2015 +0300
@@ -16,6 +16,9 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
+#include <QApplication>
+#include <QClipboard>
+
 #include <QString>
 #include <QCheckBox>
 #include <QByteArray>
@@ -258,6 +261,18 @@
                 .arg(QString::fromUtf8(msg.mid(2).left(size - 4))));
             return;
         }
+        case 'y':
+        {
+            // copy string to clipboard
+            QApplication::clipboard()->setText(QString::fromUtf8(msg.mid(2)));
+            break;
+        }
+        case 'Y':
+        {
+            // paste clipboard to game
+            SendIPC(QString("Y").toAscii() + QApplication::clipboard()->text().toUtf8().left(250).replace('\n', ' '));
+            break;
+        }
         case 'i':
         {
             emit GameStats(msg.at(2), QString::fromUtf8(msg.mid(3)));
--- a/QTfrontend/model/ammoSchemeModel.cpp	Mon Feb 16 22:33:15 2015 +0300
+++ b/QTfrontend/model/ammoSchemeModel.cpp	Thu Apr 02 21:09:56 2015 +0300
@@ -58,14 +58,15 @@
                                 << QVariant(4)             // mines number   32
                                 << QVariant(0)             // mine dud pct   33
                                 << QVariant(2)             // explosives     34
-                                << QVariant(35)            // health case pct 35
-                                << QVariant(25)            // health case amt 36
-                                << QVariant(47)            // water rise amt 37
-                                << QVariant(5)             // health dec amt 38
-                                << QVariant(100)           // rope modfier   39
-                                << QVariant(100)           // get away time  40
-                                << QVariant(0)             // world edge     41
-                                << QVariant()              // scriptparam    42
+                                << 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
                                 ;
 
 AmmoSchemeModel::AmmoSchemeModel(QObject* parent, const QString & fileName) :
@@ -125,14 +126,15 @@
               << "minesnum"            // 32
               << "minedudpct"          // 33
               << "explosives"          // 34
-              << "healthprobability"   // 35
-              << "healthcaseamount"    // 36
-              << "waterrise"           // 37
-              << "healthdecrease"      // 38
-              << "ropepct"             // 39
-              << "getawaytime"         // 40
-              << "worldedge"           // 41
-              << "scriptparam"         // scriptparam    42
+              << "airmines"            // 35
+              << "healthprobability"   // 36
+              << "healthcaseamount"    // 37
+              << "waterrise"           // 38
+              << "healthdecrease"      // 39
+              << "ropepct"             // 40
+              << "getawaytime"         // 41
+              << "worldedge"           // 42
+              << "scriptparam"         // scriptparam    43
               ;
 
     QList<QVariant> proMode;
@@ -172,14 +174,15 @@
             << QVariant(0)             // mines number   32
             << QVariant(0)             // mine dud pct   33
             << QVariant(2)             // explosives     34
-            << QVariant(35)            // health case pct 35
-            << QVariant(25)            // health case amt 36
-            << QVariant(47)            // water rise amt 37
-            << QVariant(5)             // health dec amt 38
-            << QVariant(100)           // rope modfier   39
-            << QVariant(100)           // get away time  40
-            << QVariant(0)             // world edge     41
-            << QVariant()              // scriptparam    42
+            << 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
             ;
 
     QList<QVariant> shoppa;
@@ -215,18 +218,19 @@
             << QVariant(100)           // init health    28
             << QVariant(50)            // sudden death   29
             << QVariant(1)             // case prob      30
-            << QVariant(3)             // mines time     31
+            << QVariant(0)             // mines time     31
             << QVariant(0)             // mines number   32
             << QVariant(0)             // mine dud pct   33
             << QVariant(0)             // explosives     34
-            << QVariant(0)             // health case pct 35
-            << QVariant(25)            // health case amt 36
-            << QVariant(47)            // water rise amt 37
-            << QVariant(5)             // health dec amt 38
-            << QVariant(100)           // rope modfier   39
-            << QVariant(100)           // get away time  40
-            << QVariant(0)             // world edge     41
-            << QVariant()              // scriptparam    42
+            << QVariant(8)             // air mines      35
+            << QVariant(0)             // 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
             ;
 
     QList<QVariant> cleanslate;
@@ -266,14 +270,15 @@
             << QVariant(4)             // mines number   32
             << QVariant(0)             // mine dud pct   33
             << QVariant(2)             // explosives     34
-            << QVariant(35)            // health case pct 35
-            << QVariant(25)            // health case amt 36
-            << QVariant(47)            // water rise amt 37
-            << QVariant(5)             // health dec amt 38
-            << QVariant(100)           // rope modfier   39
-            << QVariant(100)           // get away time  40
-            << QVariant(0)             // world edge     41
-            << QVariant()              // scriptparam    42
+            << 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
             ;
 
     QList<QVariant> minefield;
@@ -313,14 +318,15 @@
             << QVariant(200)           // mines number   32
             << QVariant(0)             // mine dud pct   33
             << QVariant(0)             // explosives     34
-            << QVariant(35)            // health case pct 35
-            << QVariant(25)            // health case amt 36
-            << QVariant(47)            // water rise amt 37
-            << QVariant(5)             // health dec amt 38
-            << QVariant(100)           // rope modfier   39
-            << QVariant(100)           // get away time  40
-            << QVariant(0)             // world edge     41
-            << QVariant()              // scriptparam    42
+            << 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
             ;
 
     QList<QVariant> barrelmayhem;
@@ -360,14 +366,15 @@
             << QVariant(0)             // mines number   32
             << QVariant(0)             // mine dud pct   33
             << QVariant(200)           // explosives     34
-            << QVariant(35)            // health case pct 35
-            << QVariant(25)            // health case amt 36
-            << QVariant(47)            // water rise amt 37
-            << QVariant(5)             // health dec amt 38
-            << QVariant(100)           // rope modfier   39
-            << QVariant(100)           // get away time  40
-            << QVariant(0)             // world edge     41
-            << QVariant()              // scriptparam    42
+            << 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
             ;
 
     QList<QVariant> tunnelhogs;
@@ -407,14 +414,15 @@
             << QVariant(10)            // mines number   32
             << QVariant(10)            // mine dud pct   33
             << QVariant(10)            // explosives     34
-            << QVariant(35)            // health case pct 35
-            << QVariant(25)            // health case amt 36
-            << QVariant(47)            // water rise amt 37
-            << QVariant(5)             // health dec amt 38
-            << QVariant(100)           // rope modfier   39
-            << QVariant(100)           // get away time  40
-            << QVariant(0)             // world edge     41
-            << QVariant()              // scriptparam    42
+            << 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
             ;
 
     QList<QVariant> forts;
@@ -454,14 +462,15 @@
             << QVariant(0)             // mines number   32
             << QVariant(0)             // mine dud pct   33
             << QVariant(0)             // explosives     34
-            << QVariant(35)            // health case pct 35
-            << QVariant(25)            // health case amt 36
-            << QVariant(47)            // water rise amt 37
-            << QVariant(5)             // health dec amt 38
-            << QVariant(100)           // rope modfier   39
-            << QVariant(100)           // get away time  40
-            << QVariant(0)             // world edge     41
-            << QVariant()              // scriptparam    42
+            << 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
             ;
 
     QList<QVariant> timeless;
@@ -501,14 +510,15 @@
             << QVariant(5)             // mines number   32
             << QVariant(10)            // mine dud pct   33
             << QVariant(2)             // explosives     34
-            << QVariant(35)            // health case pct 35
-            << QVariant(30)            // health case amt 36
-            << QVariant(0)             // water rise amt 37
-            << QVariant(0)             // health dec amt 38
-            << QVariant(100)           // rope modfier   39
-            << QVariant(100)           // get away time  40
-            << QVariant(0)             // world edge     41
-            << QVariant()              // scriptparam    42
+            << 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
             ;
 
     QList<QVariant> thinkingportals;
@@ -548,14 +558,15 @@
             << QVariant(5)             // mines number   32
             << QVariant(0)             // mine dud pct   33
             << QVariant(5)             // explosives     34
-            << QVariant(25)            // health case pct 35
-            << QVariant(25)            // health case amt 36
-            << QVariant(47)            // water rise amt 37
-            << QVariant(5)             // health dec amt 38
-            << QVariant(100)           // rope modfier   39
-            << QVariant(100)           // get away time  40
-            << QVariant(0)             // world edge     41
-            << QVariant()              // scriptparam    42
+            << 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
             ;
 
     QList<QVariant> kingmode;
@@ -595,14 +606,15 @@
             << QVariant(4)             // mines number   32
             << QVariant(0)             // mine dud pct   33
             << QVariant(2)             // explosives     34
-            << QVariant(35)            // health case pct 35
-            << QVariant(25)            // health case amt 36
-            << QVariant(47)            // water rise amt 37
-            << QVariant(5)             // health dec amt 38
-            << QVariant(100)           // rope modfier   39
-            << QVariant(100)           // get away time  40
-            << QVariant(0)             // world edge     41
-            << QVariant()              // scriptparam    42
+            << 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
             ;
 
 	QList<QVariant> construction;
@@ -642,14 +654,15 @@
             << QVariant(0)             // mines number   32
             << QVariant(0)             // mine dud pct   33
             << QVariant(0)             // explosives     34
-            << QVariant(35)            // health case pct 35
-            << QVariant(25)            // health case amt 36
-            << QVariant(47)            // water rise amt 37
-            << QVariant(5)             // health dec amt 38
-            << QVariant(100)           // rope modfier   39
-            << QVariant(100)           // get away time  40
-            << QVariant(0)             // world edge     41
-            << QVariant()              // scriptparam    42
+            << 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
             ;
 			
     schemes.append(defaultScheme);
@@ -853,7 +866,7 @@
         return;
     }
 
-    cfg[42] = cfg[42].mid(1);
+    cfg[cfg.size()-1] = cfg[cfg.size()-1].mid(1);
 
     for(int i = 0; i < cfg.size(); ++i)
         netScheme[i] = QVariant(cfg[i]);
--- a/QTfrontend/ui/page/pagescheme.cpp	Mon Feb 16 22:33:15 2015 +0300
+++ b/QTfrontend/ui/page/pagescheme.cpp	Thu Apr 02 21:09:56 2015 +0300
@@ -369,27 +369,41 @@
     glBSLayout->addWidget(SB_Explosives,13,2,1,1);
 
     l = new QLabel(gbBasicSettings);
-    l->setText(QLabel::tr("% Get Away Time"));
+    l->setText(QLabel::tr("Air Mines"));
     l->setWordWrap(true);
     glBSLayout->addWidget(l,14,0,1,1);
     l = new QLabel(gbBasicSettings);
     l->setFixedSize(32,32);
+    l->setPixmap(QPixmap(":/res/iconMine.png")); // TODO: icon
+    glBSLayout->addWidget(l,14,1,1,1);
+    SB_AirMines = new QSpinBox(gbBasicSettings);
+    SB_AirMines->setRange(0, 200);
+    SB_AirMines->setValue(0);
+    SB_AirMines->setSingleStep(5);
+    glBSLayout->addWidget(SB_AirMines,14,2,1,1);
+
+    l = new QLabel(gbBasicSettings);
+    l->setText(QLabel::tr("% Get Away Time"));
+    l->setWordWrap(true);
+    glBSLayout->addWidget(l,15,0,1,1);
+    l = new QLabel(gbBasicSettings);
+    l->setFixedSize(32,32);
     l->setPixmap(QPixmap(":/res/iconTime.png"));
-    glBSLayout->addWidget(l,14,1,1,1);
+    glBSLayout->addWidget(l,15,1,1,1);
     SB_GetAwayTime = new QSpinBox(gbBasicSettings);
     SB_GetAwayTime->setRange(0, 999);
     SB_GetAwayTime->setValue(100);
     SB_GetAwayTime->setSingleStep(25);
-    glBSLayout->addWidget(SB_GetAwayTime,14,2,1,1);
+    glBSLayout->addWidget(SB_GetAwayTime,15,2,1,1);
 
     l = new QLabel(gbBasicSettings);
     l->setText(QLabel::tr("World Edge"));
     l->setWordWrap(true);
-    glBSLayout->addWidget(l,15,0,1,1);
+    glBSLayout->addWidget(l,16,0,1,1);
     l = new QLabel(gbBasicSettings);
     l->setFixedSize(32,32);
     l->setPixmap(QPixmap(":/res/iconEarth.png"));
-    glBSLayout->addWidget(l,15,1,1,1);
+    glBSLayout->addWidget(l,16,1,1,1);
 
     CB_WorldEdge = new QComboBox(gbBasicSettings);
     CB_WorldEdge->insertItem(0, tr("None (Default)"));
@@ -397,21 +411,21 @@
     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,15,2,1,1);
+    glBSLayout->addWidget(CB_WorldEdge,16,2,1,1);
 
 
     l = new QLabel(gbBasicSettings);
     l->setText(QLabel::tr("Script parameter"));
     l->setWordWrap(true);
-    glBSLayout->addWidget(l,16,0,1,1);
+    glBSLayout->addWidget(l,17,0,1,1);
     l = new QLabel(gbBasicSettings);
     l->setFixedSize(32,32);
     l->setPixmap(QPixmap(":/res/iconBox.png"));
-    glBSLayout->addWidget(l,16,1,1,1);
+    glBSLayout->addWidget(l,17,1,1,1);
 
     LE_ScriptParam = new QLineEdit(gbBasicSettings);
     LE_ScriptParam->setMaxLength(240);
-    glBSLayout->addWidget(LE_ScriptParam,16,2,1,1);
+    glBSLayout->addWidget(LE_ScriptParam,17,2,1,1);
 
 
     l = new QLabel(gbBasicSettings);
@@ -497,14 +511,15 @@
     mapper->addMapping(SB_Mines, 32);
     mapper->addMapping(SB_MineDuds, 33);
     mapper->addMapping(SB_Explosives, 34);
-    mapper->addMapping(SB_HealthCrates, 35);
-    mapper->addMapping(SB_CrateHealth, 36);
-    mapper->addMapping(SB_WaterRise, 37);
-    mapper->addMapping(SB_HealthDecrease, 38);
-    mapper->addMapping(SB_RopeModifier, 39);
-    mapper->addMapping(SB_GetAwayTime, 40);
-    mapper->addMapping(CB_WorldEdge, 41, "currentIndex");
-    mapper->addMapping(LE_ScriptParam, 42);
+    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->toFirst();
 }
--- a/QTfrontend/ui/page/pagescheme.h	Mon Feb 16 22:33:15 2015 +0300
+++ b/QTfrontend/ui/page/pagescheme.h	Thu Apr 02 21:09:56 2015 +0300
@@ -87,6 +87,7 @@
         QSpinBox * SB_CrateHealth;
         QSpinBox * SB_MinesTime;
         QSpinBox * SB_Mines;
+        QSpinBox * SB_AirMines;
         QSpinBox * SB_MineDuds;
         QSpinBox * SB_Explosives;
         QSpinBox * SB_RopeModifier;
--- a/QTfrontend/ui/widget/gamecfgwidget.cpp	Mon Feb 16 22:33:15 2015 +0300
+++ b/QTfrontend/ui/widget/gamecfgwidget.cpp	Thu Apr 02 21:09:56 2015 +0300
@@ -316,18 +316,19 @@
     bcfg << QString("e$minesnum %1").arg(schemeData(32).toInt()).toUtf8();
     bcfg << QString("e$minedudpct %1").arg(schemeData(33).toInt()).toUtf8();
     bcfg << QString("e$explosives %1").arg(schemeData(34).toInt()).toUtf8();
-    bcfg << QString("e$healthprob %1").arg(schemeData(35).toInt()).toUtf8();
-    bcfg << QString("e$hcaseamount %1").arg(schemeData(36).toInt()).toUtf8();
-    bcfg << QString("e$waterrise %1").arg(schemeData(37).toInt()).toUtf8();
-    bcfg << QString("e$healthdec %1").arg(schemeData(38).toInt()).toUtf8();
-    bcfg << QString("e$ropepct %1").arg(schemeData(39).toInt()).toUtf8();
-    bcfg << QString("e$getawaytime %1").arg(schemeData(40).toInt()).toUtf8();
-    bcfg << QString("e$worldedge %1").arg(schemeData(41).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$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(42).isNull())
-        bcfg << QString("e$scriptparam %1").arg(schemeData(42).toString()).toUtf8();
+    if(!schemeData(43).isNull())
+        bcfg << QString("e$scriptparam %1").arg(schemeData(43).toString()).toUtf8();
 
 
     switch (mapgen)
@@ -527,8 +528,8 @@
             int num = GameSchemes->findText(pMapContainer->getCurrentScheme());
             if (num != -1)
                 GameSchemes->setCurrentIndex(num);
-            else
-                GameSchemes->setCurrentIndex(GameSchemes->findText("Default"));
+            //else
+            //    GameSchemes->setCurrentIndex(GameSchemes->findText("Default"));
         }
 
         if (pMapContainer->getCurrentWeapons() == "locked")
@@ -542,8 +543,8 @@
             int num = WeaponsName->findText(pMapContainer->getCurrentWeapons());
             if (num != -1)
                 WeaponsName->setCurrentIndex(num);
-            else
-                WeaponsName->setCurrentIndex(WeaponsName->findText("Default"));
+            //else
+            //    WeaponsName->setCurrentIndex(WeaponsName->findText("Default"));
         }
 
         if (pMapContainer->getCurrentScheme() != "locked" && pMapContainer->getCurrentWeapons() != "locked")
@@ -586,7 +587,7 @@
 
     if (sl.size() >= 42)
     {
-        sl[42].prepend('!');
+        sl[sl.size()-1].prepend('!');
         emit paramChanged("SCHEME", sl);  // this is a stupid hack for the fact that SCHEME is being sent once, empty. Still need to find out why.
     }
 
@@ -628,8 +629,8 @@
             int num = GameSchemes->findText(scheme);
             if (num != -1)
                 GameSchemes->setCurrentIndex(num);
-            else
-                GameSchemes->setCurrentIndex(GameSchemes->findText("Default"));
+            //else
+            //    GameSchemes->setCurrentIndex(GameSchemes->findText("Default"));
         }
 
         if (weapons == "locked")
@@ -643,8 +644,8 @@
             int num = WeaponsName->findText(weapons);
             if (num != -1)
                 WeaponsName->setCurrentIndex(num);
-            else
-                WeaponsName->setCurrentIndex(WeaponsName->findText("Default"));
+            //else
+            //    WeaponsName->setCurrentIndex(WeaponsName->findText("Default"));
         }
 
         if (scheme != "locked" && weapons != "locked")
--- a/gameServer/CoreTypes.hs	Mon Feb 16 22:33:15 2015 +0300
+++ b/gameServer/CoreTypes.hs	Thu Apr 02 21:09:56 2015 +0300
@@ -226,6 +226,7 @@
         isRegisteredOnly :: Bool,
         isSpecial :: Bool,
         defaultHedgehogsNumber :: Int,
+        teamsNumberLimit :: Int,
         greeting :: B.ByteString,
         voting :: Maybe Voting,
         roomBansList :: ![B.ByteString],
@@ -250,6 +251,7 @@
         False
         False
         4
+        8
         ""
         Nothing
         []
--- a/gameServer/HWProtoCore.hs	Mon Feb 16 22:33:15 2015 +0300
+++ b/gameServer/HWProtoCore.hs	Thu Apr 02 21:09:56 2015 +0300
@@ -71,11 +71,14 @@
         h "WATCH" f = return [QueryReplay f]
         h "FIX" _ = handleCmd ["FIX"]
         h "UNFIX" _ = handleCmd ["UNFIX"]
-        h "GREETING" msg = handleCmd ["GREETING", msg]
+        h "GREETING" msg | not $ B.null msg = handleCmd ["GREETING", msg]
         h "CALLVOTE" msg | B.null msg = handleCmd ["CALLVOTE"]
                          | otherwise = let (c, p) = extractParameters msg in
                                            if B.null p then handleCmd ["CALLVOTE", c] else handleCmd ["CALLVOTE", c, p]
-        h "VOTE" msg = handleCmd ["VOTE", upperCase msg]
+        h "VOTE" msg | not $ B.null msg = handleCmd ["VOTE", upperCase msg]
+        h "FORCE" msg | not $ B.null msg = handleCmd ["VOTE", upperCase msg, "FORCE"]
+        h "MAXTEAMS" n | not $ B.null n = handleCmd ["MAXTEAMS", n]
+        h "INFO" n | not $ B.null n = handleCmd ["INFO", n]
         h c p = return [Warning $ B.concat ["Unknown cmd: /", c, " ", p]]
 
         extractParameters p = let (a, b) = B.break (== ' ') p in (upperCase a, B.dropWhile (== ' ') b)
--- a/gameServer/HWProtoInRoomState.hs	Mon Feb 16 22:33:15 2015 +0300
+++ b/gameServer/HWProtoInRoomState.hs	Thu Apr 02 21:09:56 2015 +0300
@@ -125,7 +125,7 @@
                     defaultHedgehogsNumber rm
         let newTeam = clNick `seq` TeamInfo clNick tName teamColor grave fort voicepack flag isRegistered dif hhNum (hhsList hhsInfo)
         return $
-            if not . null . drop (maxTeams rm - 1) $ roomTeams then
+            if not . null . drop (teamsNumberLimit rm - 1) $ roomTeams then
                 [Warning $ loc "too many teams"]
             else if canAddNumber roomTeams <= 0 then
                 [Warning $ loc "too many hedgehogs"]
@@ -389,6 +389,15 @@
     s <- roomClientsChans
     return [AnswerClients s ["CHAT", n, B.unwords $ "/rnd" : rs], Random s rs]
 
+
+handleCmd_inRoom ["MAXTEAMS", n] = roomAdminOnly $ do
+    cl <- thisClient
+    let m = readInt_ n
+    if m < 2 || m > 8 then
+        return [AnswerClients [sendChan cl] ["CHAT", "[server]", loc "/maxteams: specify number from 2 to 8"]]
+    else
+        return [ModifyRoom (\r -> r{teamsNumberLimit = m})]
+
 handleCmd_inRoom ["FIX"] = serverAdminOnly $
     return [ModifyRoom (\r -> r{isSpecial = True})]
 
@@ -473,11 +482,11 @@
         return [AnswerClients [sendChan cl] ["CHAT", "[server]", loc "callvote hedgehogs: specify number from 1 to 8"]]
 
 
-handleCmd_inRoom ["VOTE", m] = do
+handleCmd_inRoom ("VOTE" : m : p) = do
     cl <- thisClient
     let b = if m == "YES" then Just True else if m == "NO" then Just False else Nothing
     if isJust b then
-        voted (fromJust b)
+        voted (p == ["FORCE"]) (fromJust b)
         else
         return [AnswerClients [sendChan cl] ["CHAT", "[server]", "vote: 'yes' or 'no'"]]
 
--- a/gameServer/Votes.hs	Mon Feb 16 22:33:15 2015 +0300
+++ b/gameServer/Votes.hs	Thu Apr 02 21:09:56 2015 +0300
@@ -26,6 +26,7 @@
 import qualified Data.List as L
 import qualified Data.Map as Map
 import Data.Maybe
+import Control.Applicative
 -------------------
 import Utils
 import CoreTypes
@@ -33,8 +34,8 @@
 import EngineInteraction
 
 
-voted :: Bool -> Reader (ClientIndex, IRnC) [Action]
-voted vote = do
+voted :: Bool -> Bool -> Reader (ClientIndex, IRnC) [Action]
+voted forced vote = do
     cl <- thisClient
     rm <- thisRoom
     uid <- liftM clUID thisClient
@@ -43,12 +44,15 @@
         Nothing -> 
             return [AnswerClients [sendChan cl] ["CHAT", "[server]", loc "There's no voting going on"]]
         Just voting ->
-            if uid `L.notElem` entitledToVote voting then
+            if (not forced) && (uid `L.notElem` entitledToVote voting) then
                 return []
-            else if uid `L.elem` map fst (votes voting) then
+            else if (not forced) && (uid `L.elem` map fst (votes voting)) then
                 return [AnswerClients [sendChan cl] ["CHAT", "[server]", loc "You already have voted"]]
+            else if forced && (not $ isAdministrator cl) then
+                return []
             else
-                actOnVoting $ voting{votes = (uid, vote):votes voting}
+                ((:) (AnswerClients [sendChan cl] ["CHAT", "[server]", loc "Your vote counted"]))
+                <$> (actOnVoting $ voting{votes = (uid, vote):votes voting})
 
     where
     actOnVoting :: Voting -> Reader (ClientIndex, IRnC) [Action]
@@ -57,9 +61,9 @@
         let totalV = length $ entitledToVote vt 
         let successV = totalV `div` 2 + 1
 
-        if length contra > totalV - successV then
+        if (forced && not vote) || (length contra > totalV - successV) then
             closeVoting
-        else if length pro >= successV then do
+        else if (forced && vote) || (length pro >= successV) then do
             a <- act $ voteType vt
             c <- closeVoting
             return $ c ++ a
@@ -117,10 +121,11 @@
         let answers = concatMap (\t -> 
                 [ModifyRoom $ modifyTeam t{hhnum = h}
                 , AnswerClients chans ["HH_NUM", teamname t, showB h]]
-                )
-                $
+                ) $ if length curteams * h > 48 then [] else curteams
+            ;
+            curteams =
                 if isJust $ gameInfo rm then
-                    teamsAtStart . fromJust . gameInfo $ rm 
+                    teamsAtStart . fromJust . gameInfo $ rm
                 else
                     teams rm
 
--- a/hedgewars/SDLh.pas	Mon Feb 16 22:33:15 2015 +0300
+++ b/hedgewars/SDLh.pas	Thu Apr 02 21:09:56 2015 +0300
@@ -306,8 +306,12 @@
     SDLK_BACKSPACE = 8;
     SDLK_RETURN    = 13;
     SDLK_ESCAPE    = 27;
+    SDLK_a         = 97;
+    SDLK_c         = 99;
     SDLK_q         = 113;
+    SDLK_v         = 118;
     SDLK_w         = 119;
+    SDLK_x         = 120;
     SDLK_DELETE    = 127;
     SDLK_KP_ENTER  = 271;
     SDLK_UP        = 273;
--- a/hedgewars/hwengine.pas	Mon Feb 16 22:33:15 2015 +0300
+++ b/hedgewars/hwengine.pas	Thu Apr 02 21:09:56 2015 +0300
@@ -162,7 +162,7 @@
                     if GameState = gsChat then
                         begin
                     // sdl on iphone supports only ashii keyboards and the unicode field is deprecated in sdl 1.3
-                        KeyPressChat(SDL_GetKeyFromScancode(event.key.keysym.sym), event.key.keysym.sym); //TODO correct for keymodifiers
+                        KeyPressChat(SDL_GetKeyFromScancode(event.key.keysym.sym), event.key.keysym.sym, event.key.keysym.modifier);
                         end
                     else
                         if GameState >= gsGame then ProcessKey(event.key);
@@ -208,7 +208,7 @@
 {$ELSE}
                 SDL_KEYDOWN:
                     if GameState = gsChat then
-                        KeyPressChat(event.key.keysym.unicode, event.key.keysym.sym)
+                        KeyPressChat(event.key.keysym.unicode, event.key.keysym.sym, event.key.keysym.modifier)
                     else
                         if GameState >= gsGame then ProcessKey(event.key);
                 SDL_KEYUP:
--- a/hedgewars/pas2cRedo.pas	Mon Feb 16 22:33:15 2015 +0300
+++ b/hedgewars/pas2cRedo.pas	Thu Apr 02 21:09:56 2015 +0300
@@ -66,12 +66,12 @@
 
     Now : function : integer;
 
-    new, dispose, FillChar, Move : procedure;
+    new, dispose, FillChar, Insert, Delete, Move : procedure;
 
     trunc, round : function : integer;
     abs, sqr : function : integer;
 
-    StrPas, FormatDateTime, copy, delete, str, PosS, trim, LowerCase : function : shortstring;
+    StrPas, FormatDateTime, copy, str, PosS, trim, LowerCase : function : shortstring;
     pos : function : integer;
     StrToInt : function : integer;
     SetLength, SetLengthA, val, StrDispose, StrCopy : procedure;
--- a/hedgewars/uAmmos.pas	Mon Feb 16 22:33:15 2015 +0300
+++ b/hedgewars/uAmmos.pas	Thu Apr 02 21:09:56 2015 +0300
@@ -400,12 +400,12 @@
         AddCaption(s, Team^.Clan^.Color, capgrpAmmoinfo);
         if (Propz and ammoprop_NeedTarget) <> 0 then
             begin
-            if Gear <> nil then Gear^.State:= Gear^.State or      gstHHChooseTarget;
+            if Gear <> nil then Gear^.State:= Gear^.State or      gstChooseTarget;
             isCursorVisible:= true
             end
         else
             begin
-            if Gear <> nil then Gear^.State:= Gear^.State and (not gstHHChooseTarget);
+            if Gear <> nil then Gear^.State:= Gear^.State and (not gstChooseTarget);
             isCursorVisible:= false
             end;
         end
--- a/hedgewars/uChat.pas	Mon Feb 16 22:33:15 2015 +0300
+++ b/hedgewars/uChat.pas	Thu Apr 02 21:09:56 2015 +0300
@@ -28,13 +28,15 @@
 procedure CleanupInput;
 procedure AddChatString(s: shortstring);
 procedure DrawChat;
-procedure KeyPressChat(Key, Sym: Longword);
+procedure KeyPressChat(Key, Sym: Longword; Modifier: Word);
 procedure SendHogSpeech(s: shortstring);
+procedure CopyToClipboard(var newContent: shortstring);
 
 implementation
 uses SDLh, uInputHandler, uTypes, uVariables, uCommands, uUtils, uTextures, uRender, uIO, uScript, uRenderUtils;
 
 const MaxStrIndex = 27;
+      MaxInputStrLen = 240;
 
 type TChatLine = record
     Tex: PTexture;
@@ -45,22 +47,33 @@
     end;
     TChatCmd = (ccQuit, ccPause, ccFinish, ccShowHistory, ccFullScreen);
 
+type TInputStrL = array[0..260] of byte;
+
 var Strs: array[0 .. MaxStrIndex] of TChatLine;
     MStrs: array[0 .. MaxStrIndex] of shortstring;
     LocalStrs: array[0 .. MaxStrIndex] of shortstring;
+    LocalStrsL: array[0 .. MaxStrIndex] of TInputStrL;
     missedCount: LongWord;
     lastStr: LongWord;
     localLastStr: LongInt;
     history: LongInt;
     visibleCount: LongWord;
     InputStr: TChatLine;
-    InputStrL: array[0..260] of char; // for full str + 4-byte utf-8 char
+    InputStrL: TInputStrL; // for full str + 4-byte utf-8 char
     ChatReady: boolean;
     showAll: boolean;
     liveLua: boolean;
     ChatHidden: boolean;
+    firstDraw: boolean;
+    InputLinePrefix: TChatLine;
+    // cursor
+    cursorPos, cursorX, selectedPos, selectionDx: LongInt;
+    LastKeyPressTick: LongWord;
+
 
 const
+    InputStrLNoPred: byte = 255;
+
     colors: array[#0..#6] of TSDL_Color = (
             (r:$FF; g:$FF; b:$FF; a:$FF), // unused, feel free to take it for anything
             (r:$FF; g:$FF; b:$FF; a:$FF), // chat message [White]
@@ -85,6 +98,59 @@
 const Padding  = 2;
       ClHeight = 2 * Padding + 16; // font height
 
+function charIsForHogSpeech(c: char): boolean;
+begin
+exit((c = '"') or (c = '''') or (c = '-'));
+end;
+
+procedure ResetSelection();
+begin
+    selectedPos:= -1;
+end;
+
+procedure UpdateCursorCoords();
+var font: THWFont;
+    str : shortstring;
+    coff, soff: LongInt;
+begin
+    if cursorPos = selectedPos then
+        ResetSelection();
+
+    // calculate cursor offset
+
+    str:= InputStr.s;
+    font:= CheckCJKFont(ansistring(str), fnt16);
+
+    // get only substring before cursor to determine length
+    // SetLength(str, cursorPos); // makes pas2c unhappy
+    str[0]:= char(cursorPos);
+    // get render size of text
+    TTF_SizeUTF8(Fontz[font].Handle, Str2PChar(str), @coff, nil);
+
+    cursorX:= 2 + coff;
+
+    // calculate selection width on screen
+    if selectedPos >= 0 then
+        begin
+        if selectedPos > cursorPos then
+            str:= InputStr.s;
+        // SetLength(str, selectedPos); // makes pas2c unhappy
+        str[0]:= char(selectedPos);
+        TTF_SizeUTF8(Fontz[font].Handle, Str2PChar(str), @soff, nil);
+        selectionDx:= soff - coff;
+        end
+    else
+        selectionDx:= 0;
+end;
+
+
+procedure ResetCursor();
+begin
+    ResetSelection();
+    cursorPos:= 0;
+    UpdateCursorCoords();
+end;
+
 procedure RenderChatLineTex(var cl: TChatLine; var str: shortstring);
 var strSurface,
     resSurface: PSDL_Surface;
@@ -139,12 +205,19 @@
     begin
     cl.s:= str;
     color:= colors[#6];
-    str:= UserNick + '> ' + str + '_'
+    str:= str + ' ';
     end
 else
     begin
-    color:= colors[str[1]];
-    delete(str, 1, 1);
+    if str[1] <= High(colors) then
+        begin
+        color:= colors[str[1]];
+        delete(str, 1, 1);
+        end
+    // fallback if invalid color
+    else
+        color:= colors[Low(colors)];
+
     cl.s:= str;
     end;
 
@@ -190,8 +263,28 @@
 inc(visibleCount)
 end;
 
+procedure CheckPasteBuffer(); forward;
+
+procedure UpdateInputLinePrefix();
+begin
+if liveLua then
+    begin
+    InputLinePrefix.color:= colors[#1];
+    InputLinePrefix.s:= '[Lua] >';
+    end
+else
+    begin
+    InputLinePrefix.color:= colors[#6];
+    InputLinePrefix.s:= UserNick + '>';
+    end;
+
+FreeAndNilTexture(InputLinePrefix.Tex);
+end;
+
 procedure DrawChat;
 var i, t, left, top, cnt: LongInt;
+    selRect: TSDL_Rect;
+    c: char;
 begin
 ChatReady:= true; // maybe move to somewhere else?
 
@@ -204,8 +297,78 @@
 
 // draw chat input line first and under all other lines
 if (GameState = gsChat) and (InputStr.Tex <> nil) then
+    begin
+    CheckPasteBuffer();
+
+    if InputLinePrefix.Tex = nil then
+        RenderChatLineTex(InputLinePrefix, InputLinePrefix.s);
+
+    DrawTexture(left, top, InputLinePrefix.Tex);
+    inc(left, InputLinePrefix.Width);
     DrawTexture(left, top, InputStr.Tex);
 
+    if firstDraw then
+        begin
+        UpdateCursorCoords();
+        firstDraw:= false;
+        end;
+
+    if selectedPos < 0 then
+        begin
+        // draw cursor
+        if ((RealTicks - LastKeyPressTick) and 512) < 256 then
+            DrawLineOnScreen(left + cursorX, top + 2, left + cursorX, top + ClHeight - 2, 2.0, $00, $FF, $FF, $FF);
+        end
+    else // draw selection
+        begin
+        selRect.y:= top + 2;
+        selRect.h:= clHeight - 4;
+        if selectionDx < 0 then
+            begin
+            selRect.x:= left + cursorX + selectionDx;
+            selRect.w:= -selectionDx;
+            end
+        else
+            begin
+            selRect.x:= left + cursorX;
+            selRect.w:= selectionDx;
+            end;
+
+        DrawRect(selRect, $FF, $FF, $FF, $40, true);
+        end;
+
+    dec(left, InputLinePrefix.Width);
+
+
+    if (Length(InputStr.s) > 0) and ((CursorPos = 1) or (CursorPos = 2)) then
+        begin
+        c:= InputStr.s[1];
+        if charIsForHogSpeech(c) then
+            begin
+            SpeechHogNumber:= 0;
+            if Length(InputStr.s) > 1 then
+                begin
+                c:= InputStr.s[2];
+                if (c > '0') and (c < '9') then
+                    SpeechHogNumber:= byte(c) - 48;
+                end;
+            // default to current hedgehog (if own) or first hedgehog
+            if SpeechHogNumber = 0 then
+                begin
+                if not CurrentTeam^.ExtDriven then
+                    SpeechHogNumber:= CurrentTeam^.CurrHedgehog + 1
+                else
+                    SpeechHogNumber:= 1;
+                end;
+            end;
+        end
+    else
+        SpeechHogNumber:= -1;
+    end
+else
+    SpeechHogNumber:= -1;
+
+// draw chat lines
 if ((not ChatHidden) or showAll) and (UIDisplay <> uiNone) then
     begin
     if MissedCount <> 0 then // there are chat strings we missed, so print them now
@@ -264,6 +427,7 @@
     // put in input history
     localLastStr:= (localLastStr + 1) mod MaxStrIndex;
     LocalStrs[localLastStr]:= s;
+    LocalStrsL[localLastStr]:= InputStrL;
     end;
 
 t:= LocalTeam;
@@ -365,6 +529,7 @@
                 AddFileLog('[Lua] chat input string parsing disabled');
                 AddChatString(#3 + 'Lua parsing: OFF');
                 end;
+            UpdateInputLinePrefix();
             end;
         exit
         end;
@@ -410,26 +575,308 @@
     ResetKbd;
 end;
 
-procedure KeyPressChat(Key, Sym: Longword);
+procedure DelBytesFromInputStrBack(endIdx: integer; count: byte);
+var i, startIdx: integer;
+begin
+    // nothing to do if count is 0
+    if count = 0 then
+        exit;
+
+    // first byte to delete
+    startIdx:= endIdx - (count - 1);
+
+    // delete bytes from string
+    Delete(InputStr.s, startIdx, count);
+
+    // wipe utf8 info for deleted char
+    InputStrL[endIdx]:= InputStrLNoPred;
+
+    // shift utf8 char info to reflect new string
+    for i:= endIdx + 1 to Length(InputStr.s) + count do
+        begin
+        if InputStrL[i] <> InputStrLNoPred then
+            begin
+            InputStrL[i-count]:= InputStrL[i] - count;
+            InputStrL[i]:= InputStrLNoPred;
+            end;
+        end;
+
+    SetLine(InputStr, InputStr.s, true);
+end;
+
+// returns count of removed bytes
+function DelCharFromInputStr(idx: integer): integer;
+var btw: byte;
+begin
+    // note: idx is always at last byte of utf8 chars. cuz relevant for InputStrL
+
+    if (Length(InputStr.s) < 1) or (idx < 1) or (idx > Length(InputStr.s)) then
+        exit(0);
+
+    btw:= byte(idx) - InputStrL[idx];
+
+    DelCharFromInputStr:= btw;
+
+    DelBytesFromInputStrBack(idx, btw);
+end;
+
+// unchecked
+procedure DoCursorStepForward();
+begin
+    // go to end of next utf8-char
+    repeat
+        inc(cursorPos);
+    until InputStrL[cursorPos] <> InputStrLNoPred;
+end;
+
+procedure DeleteSelected();
+begin
+    if (selectedPos >= 0) and (cursorPos <> selectedPos) then
+        begin
+        DelBytesFromInputStrBack(max(cursorPos, selectedPos), abs(selectedPos-cursorPos));
+        cursorPos:= min(cursorPos, selectedPos);
+        ResetSelection();
+        end;
+    UpdateCursorCoords();
+end;
+
+procedure HandleSelection(enabled: boolean);
+begin
+if enabled then
+    begin
+    if selectedPos < 0 then
+        selectedPos:= cursorPos;
+    end
+else
+    ResetSelection();
+end;
+
+type TCharSkip = ( none, wspace, numalpha, special );
+
+function GetInputCharSkipClass(index: LongInt): TCharSkip;
+var  c: char;
+begin
+    // multi-byte chars counts as letter
+    if (index > 1) and (InputStrL[index] <> index - 1) then
+        exit(numalpha);
+
+    c:= InputStr.s[index];
+
+    // non-ascii counts as letter
+    if c > #127 then
+        exit(numalpha);
+
+    // low-ascii whitespaces and DEL
+    if (c < #33) or (c = #127) then
+        exit(wspace);
+
+    // low-ascii special chars
+    if c < #48 then
+        exit(special);
+
+    // digits
+    if c < #58 then
+        exit(numalpha);
+
+    // make c upper-case
+    if c > #96 then
+        c:= char(byte(c) - 32);
+
+    // letters
+    if (c > #64) and (c < #90) then
+        exit(numalpha);
+
+    // remaining ascii are special chars
+    exit(special);
+end;
+
+// skip from word to word, similar to Qt
+procedure SkipInputChars(skip: TCharSkip; backwards: boolean);
+begin
+if backwards then
+    begin
+    // skip trailing whitespace, similar to Qt
+    while (skip = wspace) and (cursorPos > 0) do
+        begin
+        skip:= GetInputCharSkipClass(cursorPos);
+        if skip = wspace then
+            cursorPos:= InputStrL[cursorPos];
+        end;
+    // skip same-type chars
+    while (cursorPos > 0) and (GetInputCharSkipClass(cursorPos) = skip) do
+        cursorPos:= InputStrL[cursorPos];
+    end
+else
+    begin
+    // skip same-type chars
+    while cursorPos < Length(InputStr.s) do
+        begin
+        DoCursorStepForward();
+        if (GetInputCharSkipClass(cursorPos) <> skip) then
+            begin
+            // go back 1 char
+            cursorPos:= InputStrL[cursorPos];
+            break;
+            end;
+        end;
+    // skip trailing whitespace, similar to Qt
+    while cursorPos < Length(InputStr.s) do
+        begin
+        DoCursorStepForward();
+        if (GetInputCharSkipClass(cursorPos) <> wspace) then
+            begin
+            // go back 1 char
+            cursorPos:= InputStrL[cursorPos];
+            break;
+            end;
+        end;
+    end;
+end;
+
+procedure CopyToClipboard(var newContent: shortstring);
+begin
+    SendIPC(_S'y' + copy(newContent, 1, 253) + #0);
+end;
+
+procedure CopySelectionToClipboard();
+var selection: shortstring;
+begin
+    if selectedPos >= 0 then
+        begin
+        selection:= copy(InputStr.s, min(CursorPos, selectedPos) + 1, abs(CursorPos - selectedPos));
+        CopyToClipboard(selection);
+        end;
+end;
+
+// TODO: honor utf8, don't break utf8 chars when shifting chars beyond limit
+procedure InsertIntoInputStr(s: shortstring);
+var i, l, il, lastc: integer;
+begin
+    // safe length for string
+    l:= min(MaxInputStrLen-cursorPos, Length(s));
+    s:= copy(s,1,l);
+
+    // if we insert rather than append, shift info in InputStrL accordingly
+    if cursorPos < Length(InputStr.s) then
+        begin
+        for i:= Length(InputStr.s) downto cursorPos + 1 do
+            begin
+            if InputStrL[i] <> InputStrLNoPred then
+                begin
+                il:= i + l;
+                // only shift if not overflowing
+                if il <= MaxInputStrLen then
+                    InputStrL[il]:= InputStrL[i] + l;
+                InputStrL[i]:= InputStrLNoPred;
+                end;
+            end;
+        end;
+
+    InputStrL[cursorPos + l]:= cursorPos;
+    // insert string truncated to safe length
+    Insert(s, InputStr.s, cursorPos + 1);
+    if Length(InputStr.s) > MaxInputStrLen then
+        InputStr.s[0]:= char(MaxInputStrLen);
+
+    SetLine(InputStr, InputStr.s, true);
+
+    // move cursor to end of inserted string
+    lastc:= MaxInputStrLen;
+    cursorPos:= min(lastc, cursorPos + l);
+    UpdateCursorCoords();
+end;
+
+procedure PasteFromClipboard();
+begin
+    SendIPC(_S'Y');
+end;
+
+procedure CheckPasteBuffer();
+begin
+    if Length(ChatPasteBuffer) > 0 then
+        begin
+        InsertIntoInputStr(ChatPasteBuffer);
+        ChatPasteBuffer:= '';
+        end;
+end;
+
+procedure KeyPressChat(Key, Sym: Longword; Modifier: Word);
 const firstByteMark: array[0..3] of byte = (0, $C0, $E0, $F0);
 var i, btw, index: integer;
     utf8: shortstring;
-    action: boolean;
+    action, selMode, ctrl: boolean;
+    skip: TCharSkip;
 begin
+    LastKeyPressTick:= RealTicks;
     action:= true;
+
+    CheckPasteBuffer();
+
+    selMode:= (modifier and (KMOD_LSHIFT or KMOD_RSHIFT)) <> 0;
+    ctrl:= (modifier and (KMOD_LCTRL or KMOD_RCTRL)) <> 0;
+    skip:= none;
+
     case Sym of
         SDLK_BACKSPACE:
             begin
-            if Length(InputStr.s) > 0 then
+            if selectedPos < 0 then
                 begin
-                InputStr.s[0]:= InputStrL[byte(InputStr.s[0])];
-                SetLine(InputStr, InputStr.s, true)
+                if ctrl then
+                    skip:= GetInputCharSkipClass(cursorPos);
+
+                // remove char before cursor
+                dec(cursorPos, DelCharFromInputStr(cursorPos));
+
+                // delete more if ctrl is held
+                if ctrl and (selectedPos < 0) then
+                    begin
+                    HandleSelection(true);
+                    SkipInputChars(skip, true);
+                    DeleteSelected();
+                    end
+                else
+                    UpdateCursorCoords();
+
                 end
+            else
+                DeleteSelected();
+            end;
+        SDLK_DELETE:
+            begin
+            if selectedPos < 0 then
+                begin
+                // remove char after cursor
+                if cursorPos < Length(InputStr.s) then
+                    begin
+                    DoCursorStepForward();
+                    if ctrl then
+                        skip:= GetInputCharSkipClass(cursorPos);
+
+                    // delete char
+                    dec(cursorPos, DelCharFromInputStr(cursorPos));
+
+                    // delete more if ctrl is held
+                    if ctrl and (cursorPos < Length(InputStr.s)) then
+                        begin
+                        HandleSelection(true);
+                        SkipInputChars(skip, false);
+                        DeleteSelected();
+                        end;
+                    end
+                else
+                    UpdateCursorCoords();
+                end
+            else
+                DeleteSelected();
             end;
         SDLK_ESCAPE:
             begin
             if Length(InputStr.s) > 0 then
-                SetLine(InputStr, '', true)
+                begin
+                SetLine(InputStr, '', true);
+                FillChar(InputStrL, sizeof(InputStrL), InputStrLNoPred);
+                ResetCursor();
+                end
             else CleanupInput
             end;
         SDLK_RETURN, SDLK_KP_ENTER:
@@ -437,7 +884,9 @@
             if Length(InputStr.s) > 0 then
                 begin
                 AcceptChatString(InputStr.s);
-                SetLine(InputStr, '', false)
+                SetLine(InputStr, '', false);
+                FillChar(InputStrL, sizeof(InputStrL), InputStrLNoPred);
+                ResetCursor();
                 end;
             CleanupInput
             end;
@@ -447,20 +896,150 @@
             if (Sym = SDLK_DOWN) and (history > 0) then dec(history);
             index:= localLastStr - history + 1;
             if (index > localLastStr) then
-                 SetLine(InputStr, '', true)
-            else SetLine(InputStr, LocalStrs[index], true)
+                begin
+                SetLine(InputStr, '', true);
+                FillChar(InputStrL, sizeof(InputStrL), InputStrLNoPred);
+                end
+            else
+                begin
+                SetLine(InputStr, LocalStrs[index], true);
+                InputStrL:= LocalStrsL[index];
+                end;
+            cursorPos:= Length(InputStr.s);
+            ResetSelection();
+            UpdateCursorCoords();
+            end;
+        SDLK_HOME:
+            begin
+            if cursorPos > 0 then
+                begin
+                HandleSelection(selMode);
+                cursorPos:= 0;
+                end
+            else if (not selMode) then
+                ResetSelection();
+
+            UpdateCursorCoords();
+            end;
+        SDLK_END:
+            begin
+            i:= Length(InputStr.s);
+            if cursorPos < i then
+                begin
+                HandleSelection(selMode);
+                cursorPos:= i;
+                end
+            else if (not selMode) then
+                ResetSelection();
+
+            UpdateCursorCoords();
             end;
-        SDLK_RIGHT, SDLK_LEFT, SDLK_DELETE,
-        SDLK_HOME, SDLK_END,
+        SDLK_LEFT:
+            begin
+            if cursorPos > 0 then
+                begin
+
+                if ctrl then
+                    skip:= GetInputCharSkipClass(cursorPos);
+
+                if selMode or (selectedPos < 0) then
+                    begin
+                    HandleSelection(selMode);
+                    // go to end of previous utf8-char
+                    cursorPos:= InputStrL[cursorPos];
+                    end
+                else // if we're leaving selection mode, jump to its left end
+                    begin
+                    cursorPos:= min(cursorPos, selectedPos);
+                    ResetSelection();
+                    end;
+
+                if ctrl then
+                    SkipInputChars(skip, true);
+
+                end
+            else if (not selMode) then
+                ResetSelection();
+
+            UpdateCursorCoords();
+            end;
+        SDLK_RIGHT:
+            begin
+            if cursorPos < Length(InputStr.s) then
+                begin
+
+                if selMode or (selectedPos < 0) then
+                    begin
+                    HandleSelection(selMode);
+                    DoCursorStepForward();
+                    end
+                else // if we're leaving selection mode, jump to its right end
+                    begin
+                    cursorPos:= max(cursorPos, selectedPos);
+                    ResetSelection();
+                    end;
+
+                if ctrl then
+                    SkipInputChars(GetInputCharSkipClass(cursorPos), false);
+
+                end
+            else if (not selMode) then
+                ResetSelection();
+
+            UpdateCursorCoords();
+            end;
         SDLK_PAGEUP, SDLK_PAGEDOWN:
             begin
             // ignore me!!!
             end;
+        SDLK_a:
+            begin
+            // select all
+            if ctrl then
+                begin
+                ResetSelection();
+                cursorPos:= 0;
+                HandleSelection(true);
+                cursorPos:= Length(InputStr.s);
+                UpdateCursorCoords();
+                end
+            else
+                action:= false;
+            end;
+        SDLK_c:
+            begin
+            // copy
+            if ctrl then
+                CopySelectionToClipboard()
+            else
+                action:= false;
+            end;
+        SDLK_v:
+            begin
+            // paste
+            if ctrl then
+                PasteFromClipboard()
+            else
+                action:= false;
+            end;
+        SDLK_x:
+            begin
+            // cut
+            if ctrl then
+                begin
+                CopySelectionToClipboard();
+                DeleteSelected();
+                end
+            else
+                action:= false;
+            end;
         else
             action:= false;
         end;
     if not action and (Key <> 0) then
         begin
+        DeleteSelected();
+
         if (Key < $80) then
             btw:= 1
         else if (Key < $800) then
@@ -480,11 +1059,18 @@
 
         utf8:= char(Key or firstByteMark[Pred(btw)]) + utf8;
 
-        if byte(InputStr.s[0]) + btw > 240 then
+        if Length(InputStr.s) + btw > MaxInputStrLen then
             exit;
 
-        InputStrL[byte(InputStr.s[0]) + btw]:= InputStr.s[0];
-        SetLine(InputStr, InputStr.s + utf8, true)
+        if (Length(InputStr.s) = 0) and (Length(utf8) = 1) and (charIsForHogSpeech(utf8[1])) then
+            begin
+            InsertIntoInputStr(utf8);
+            InsertIntoInputStr(utf8);
+            cursorPos:= 1;
+            UpdateCursorCoords();
+            end
+        else
+            InsertIntoInputStr(utf8);
         end
 end;
 
@@ -539,7 +1125,14 @@
     if length(s) = 0 then
         SetLine(InputStr, '', true)
     else
-        SetLine(InputStr, '/team ', true)
+        begin
+        SetLine(InputStr, '/team ', true);
+        // update InputStrL and cursor accordingly
+        // this allows cursor-jumping over '/team ' as if it was a single char
+        InputStrL[6]:= 0;
+        cursorPos:= 6;
+        UpdateCursorCoords();
+        end;
 end;
 
 procedure initModule;
@@ -560,15 +1153,25 @@
     missedCount:= 0;
     liveLua:= false;
     ChatHidden:= false;
+    firstDraw:= true;
 
+    InputLinePrefix.Tex:= nil;
+    UpdateInputLinePrefix();
+    inputStr.s:= '';
     inputStr.Tex := nil;
     for i:= 0 to MaxStrIndex do
         Strs[i].Tex := nil;
+
+    FillChar(InputStrL, sizeof(InputStrL), InputStrLNoPred);
+
+    LastKeyPressTick:= 0;
+    ResetCursor();
 end;
 
 procedure freeModule;
 var i: ShortInt;
 begin
+    FreeAndNilTexture(InputLinePrefix.Tex);
     FreeAndNilTexture(InputStr.Tex);
     for i:= 0 to MaxStrIndex do
         FreeAndNilTexture(Strs[i].Tex);
--- a/hedgewars/uCollisions.pas	Mon Feb 16 22:33:15 2015 +0300
+++ b/hedgewars/uCollisions.pas	Thu Apr 02 21:09:56 2015 +0300
@@ -52,7 +52,7 @@
 function  TestCollisionYwithXYShift(Gear: PGear; ShiftX, ShiftY: LongInt; Dir: LongInt): Word; inline;
 function  TestCollisionYwithXYShift(Gear: PGear; ShiftX, ShiftY: LongInt; Dir: LongInt; withGear: boolean): Word;
 
-function  TestRectancleForObstacle(x1, y1, x2, y2: LongInt; landOnly: boolean): boolean;
+function  TestRectangleForObstacle(x1, y1, x2, y2: LongInt; landOnly: boolean): boolean;
 
 function  CheckCoordInWater(X, Y: LongInt): boolean; inline;
 
@@ -398,11 +398,11 @@
 Gear^.Y:= Gear^.Y - int2hwFloat(ShiftY)
 end;
 
-function TestRectancleForObstacle(x1, y1, x2, y2: LongInt; landOnly: boolean): boolean;
+function TestRectangleForObstacle(x1, y1, x2, y2: LongInt; landOnly: boolean): boolean;
 var x, y: LongInt;
     TestWord: LongWord;
 begin
-TestRectancleForObstacle:= true;
+TestRectangleForObstacle:= true;
 
 if landOnly then
     TestWord:= 255
@@ -431,7 +431,7 @@
         if ((y and LAND_HEIGHT_MASK) = 0) and ((x and LAND_WIDTH_MASK) = 0) and (Land[y, x] > TestWord) then
             exit;
 
-TestRectancleForObstacle:= false
+TestRectangleForObstacle:= false
 end;
 
 function CalcSlopeTangent(Gear: PGear; collisionX, collisionY: LongInt; var outDeltaX, outDeltaY: LongInt; TestWord: LongWord): boolean;
--- a/hedgewars/uCommandHandlers.pas	Mon Feb 16 22:33:15 2015 +0300
+++ b/hedgewars/uCommandHandlers.pas	Thu Apr 02 21:09:56 2015 +0300
@@ -27,6 +27,7 @@
 
 implementation
 uses uCommands, uTypes, uVariables, uIO, uDebug, uConsts, uScript, uUtils, SDLh, uWorld, uRandom, uCaptions
+    , uVisualGearsList
      {$IFDEF USE_VIDEO_RECORDING}, uVideoRec {$ENDIF};
 
 var prevGState: TGameState = gsConfirm;
@@ -748,6 +749,11 @@
 cLandMines:= StrToInt(s)
 end;
 
+procedure chAirMines(var s: shortstring);
+begin
+cAirMines:= StrToInt(s)
+end;
+
 procedure chExplosives(var s: shortstring);
 begin
 cExplosives:= StrToInt(s)
@@ -771,7 +777,14 @@
 
 procedure chFastUntilLag(var s: shortstring);
 begin
-fastUntilLag:= StrToInt(s) <> 0
+    fastUntilLag:= StrToInt(s) <> 0;
+
+    if not fastUntilLag then
+    begin
+        // update health bars and the wind indicator
+        AddVisualGear(0, 0, vgtTeamHealthSorter);
+        AddVisualGear(0, 0, vgtSmoothWindBar)
+    end
 end;
 
 procedure chCampVar(var s:shortstring);
@@ -837,6 +850,7 @@
     RegisterVariable('getawaytime' , @chGetAwayTime , false);
     RegisterVariable('minedudpct',@chMineDudPercent, false);
     RegisterVariable('minesnum', @chLandMines     , false);
+    RegisterVariable('airmines', @chAirMines      , false);
     RegisterVariable('explosives',@chExplosives    , false);
     RegisterVariable('gmflags' , @chGameFlags      , false);
     RegisterVariable('turntime', @chHedgehogTurnTime, false);
--- a/hedgewars/uConsts.pas	Mon Feb 16 22:33:15 2015 +0300
+++ b/hedgewars/uConsts.pas	Thu Apr 02 21:09:56 2015 +0300
@@ -215,7 +215,7 @@
     gstAttacked       = $00000008;
     gstAttacking      = $00000010;
     gstCollision      = $00000020;
-    gstHHChooseTarget = $00000040;
+    gstChooseTarget   = $00000040;
     gstHHJumping      = $00000100;
     gsttmpFlag        = $00000200;
     gstHHThinking     = $00000800;
--- a/hedgewars/uGame.pas	Mon Feb 16 22:33:15 2015 +0300
+++ b/hedgewars/uGame.pas	Thu Apr 02 21:09:56 2015 +0300
@@ -102,8 +102,9 @@
         if isInLag then
             case GameType of
                 gmtNet: begin
-                        // just update the health bars
+                        // update health bars and the wind indicator
                         AddVisualGear(0, 0, vgtTeamHealthSorter);
+                        AddVisualGear(0, 0, vgtSmoothWindBar);
                         break;
                         end;
                 gmtDemo, gmtRecord: begin
--- a/hedgewars/uGears.pas	Mon Feb 16 22:33:15 2015 +0300
+++ b/hedgewars/uGears.pas	Thu Apr 02 21:09:56 2015 +0300
@@ -33,7 +33,7 @@
  *       effects are called "Visual Gears" and defined in the respective unit!
  *)
 interface
-uses uConsts, uFloat, uTypes, uChat;
+uses uConsts, uFloat, uTypes, uChat, uCollisions;
 
 procedure initModule;
 procedure freeModule;
@@ -394,7 +394,7 @@
             if (CurrentHedgehog^.Gear^.State and gstAttacked <> 0)
             and (Ammoz[CurrentHedgehog^.CurAmmoType].Ammo.Propz and ammoprop_NeedTarget <> 0) then
                 begin
-                CurrentHedgehog^.Gear^.State:= CurrentHedgehog^.Gear^.State or gstHHChooseTarget;
+                CurrentHedgehog^.Gear^.State:= CurrentHedgehog^.Gear^.State or gstChooseTarget;
                 isCursorVisible := true
                 end;
             CurrentHedgehog^.Gear^.State:= CurrentHedgehog^.Gear^.State and (not gstAttacked);
@@ -551,6 +551,9 @@
         end;
     Gear:= Gear^.NextGear
     end;
+
+if SpeechHogNumber > 0 then
+    DrawHHOrder();
 end;
 
 procedure FreeGearsList;
@@ -567,7 +570,7 @@
 end;
 
 procedure AddMiscGears;
-var p,i,j, unplaced: Longword;
+var p,i,j,t,h,unplaced: Longword;
     rx, ry: LongInt;
     rdx, rdy: hwFloat;
     Gear: PGear;
@@ -604,6 +607,70 @@
     inc(i)
     end;
 
+i:= 0;
+j:= 0;
+p:= 0; // 0 searching, 1 bad position, 2 added.
+unplaced:= 0;
+if cAirMines > 0 then
+    Gear:= AddGear(0, 0, gtAirMine, 0, _0, _0, 0);
+while (i < cAirMines) and (j < 1000*cAirMines) do
+    begin
+    p:= 0;
+    if hasBorder then
+        begin
+        rx:= leftX+GetRandom(rightX-leftX-16)+8;
+        ry:= topY+GetRandom(LAND_HEIGHT-topY-16)+8
+        end
+    else
+        begin
+        rx:= leftX+GetRandom(rightX-leftX+400)-200;
+        ry:= topY+GetRandom(LAND_HEIGHT-topY+400)-200
+        end;
+    Gear^.X:= int2hwFloat(rx);
+    Gear^.Y:= int2hwFloat(ry);
+    if CheckLandValue(rx, ry, $FFFF) and
+       (TestCollisionYwithGear(Gear,-1) = 0) and
+       (TestCollisionXwithGear(Gear, 1) = 0) and
+       (TestCollisionXwithGear(Gear,-1) = 0) and
+       (TestCollisionYwithGear(Gear, 1) = 0) then
+        begin
+        t:= 0;
+        while (t < TeamsCount) and (p = 0) do
+            begin
+            h:= 0;
+            with TeamsArray[t]^ do
+                while (h < cMaxHHIndex) and (p = 0) do
+                    begin
+                    if (Hedgehogs[h].Gear <> nil) then
+                        begin
+                        rdx:=Gear^.X-Hedgehogs[h].Gear^.X;
+                        rdy:=Gear^.Y-Hedgehogs[h].Gear^.Y;
+                        if (Gear^.Angle < $FFFFFFFF) and
+                            ((rdx.Round+rdy.Round < Gear^.Angle) and
+                            (hwRound(hwSqr(rdx) + hwSqr(rdy)) < sqr(Gear^.Angle))) then
+                            begin
+// Debug line. Remove later
+// AddFileLog('Too Close to Hog @ (' + inttostr(rx) + ',' + inttostr(ry) + ')');
+
+                            p:= 1
+                            end
+                        end;
+                    inc(h)
+                    end;
+            inc(t)
+            end;
+        if p = 0 then
+            begin
+            inc(i);
+            AddFileLog('Placed Air Mine @ (' + inttostr(rx) + ',' + inttostr(ry) + ')');
+            if i < cAirMines then
+                Gear:= AddGear(0, 0, gtAirMine, 0, _0, _0, 0)
+            end
+        end;
+    inc(j)
+    end;
+if p <> 0 then DeleteGear(Gear);
+
 if (GameFlags and gfLowGravity) <> 0 then
     begin
     cGravity:= cMaxWindSpeed;
@@ -930,6 +997,7 @@
             @doStepHedgehog,
             @doStepMine,
             @doStepCase,
+            @doStepAirMine,
             @doStepCase,
             @doStepBomb,
             @doStepShell,
@@ -991,8 +1059,7 @@
             @doStepIceGun,
             @doStepAddAmmo,
             @doStepGenericFaller,
-            @doStepKnife,
-            @doStepAirMine);
+            @doStepKnife);
 begin
     doStepHandlers:= handlers;
 
--- a/hedgewars/uGearsHandlersMess.pas	Mon Feb 16 22:33:15 2015 +0300
+++ b/hedgewars/uGearsHandlersMess.pas	Thu Apr 02 21:09:56 2015 +0300
@@ -1382,6 +1382,7 @@
         dec(TurnTimeLeft)
     else
         begin
+        HHGear^.State := HHGear^.State and (not gstNotKickable);
         DeleteGear(Gear);
         AfterAttack
         end;
@@ -1762,11 +1763,16 @@
 var i,t,targDist,tmpDist: LongWord;
     targ, tmpG: PGear;
     trackSpeed, airFriction, tX, tY: hwFloat;
+    isUnderwater: Boolean;
 begin
+    isUnderwater:= CheckCoordInWater(hwRound(Gear^.X), hwRound(Gear^.Y) + Gear^.Radius);
     if Gear^.Pos > 0 then
         begin
         airFriction:= _1;
-        dec(airFriction.QWordValue,Gear^.Pos);
+        if isUnderwater then
+            dec(airFriction.QWordValue,Gear^.Pos*2)
+        else
+            dec(airFriction.QWordValue,Gear^.Pos);
         Gear^.dX:= Gear^.dX*airFriction;
         Gear^.dY:= Gear^.dY*airFriction
         end;
@@ -1799,14 +1805,14 @@
         ((TurnTimeLeft < cHedgehogTurnTime) and (cHedgehogTurnTime-TurnTimeLeft < 5000)) or
         (Gear^.State and gsttmpFlag = 0) or
         (Gear^.Angle = 0) then
-        gear^.State:= gear^.State and (not gstHHChooseTarget)
+        gear^.State:= gear^.State and (not gstChooseTarget)
     else if
     // todo, allow not finding new target, set timeout on target retention
         (Gear^.State and gstAttacking = 0) and
         ((GameTicks and $FF) = 17) and
         (GameTicks > Gear^.FlightTime) then // recheck hunted hog
         begin
-        gear^.State:= gear^.State or gstHHChooseTarget;
+        gear^.State:= gear^.State or gstChooseTarget;
         if targ <> nil then
              targDist:= Distance(Gear^.X-targ^.X,Gear^.Y-targ^.Y).Round
         else targDist:= 0;
@@ -1837,9 +1843,12 @@
     if targ <> nil then
         begin
         trackSpeed:= _0;
-        trackSpeed.QWordValue:= Gear^.Power;
+        if isUnderwater then
+            trackSpeed.QWordValue:= Gear^.Power div 2
+        else
+            trackSpeed.QWordValue:= Gear^.Power;
         if (Gear^.X < targ^.X) and (Gear^.dX < _0_1)  then
-             Gear^.dX:= Gear^.dX+trackSpeed
+             Gear^.dX:= Gear^.dX+trackSpeed // please leave as an add.  I like the effect
         else if (Gear^.X > targ^.X) and (Gear^.dX > -_0_1) then
             Gear^.dX:= Gear^.dX-trackSpeed;
         if (Gear^.Y < targ^.Y) and (Gear^.dY < _0_1)  then
@@ -2747,7 +2756,7 @@
             end;
         HHGear^.Message := HHGear^.Message and (not gmAttack);
         HHGear^.State := HHGear^.State and (not gstAttacking);
-        HHGear^.State := HHGear^.State or gstHHChooseTarget;
+        HHGear^.State := HHGear^.State or gstChooseTarget;
         isCursorVisible := true;
         DeleteGear(Gear)
         end
@@ -2832,7 +2841,7 @@
         begin
         HHGear^.Message := HHGear^.Message and (not gmAttack);
         HHGear^.State := HHGear^.State and (not gstAttacking);
-        HHGear^.State := HHGear^.State or gstHHChooseTarget;
+        HHGear^.State := HHGear^.State or gstChooseTarget;
         isCursorVisible := true;
         warn:= AddVisualGear(Gear^.Target.X, oy, vgtNoPlaceWarn, 0, true);
         if warn <> nil then
@@ -3131,13 +3140,6 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 
-const cakeh =   27;
-var
-    CakePoints: array[0..Pred(cakeh)] of record
-        x, y: hwFloat;
-    end;
-    CakeI: Longword;
-
 procedure doStepCakeExpl(Gear: PGear);
 begin
     AllInactive := false;
@@ -3204,6 +3206,7 @@
 procedure doStepCakeWork(Gear: PGear);
 var
     tdx, tdy: hwFloat;
+    cakeData: PCakeData;
 begin
     AllInactive := false;
 
@@ -3222,24 +3225,31 @@
         Gear^.RenderTimer := false;
         Gear^.doStep := @doStepCakeDown;
         exit
-        end;
+        end
+    else if Gear^.Timer < 6000 then
+        Gear^.RenderTimer:= true;
 
     if not cakeStep(Gear) then Gear^.doStep:= @doStepCakeFall;
 
     if Gear^.Tag = 0 then
         begin
-        CakeI := (CakeI + 1) mod cakeh;
-        tdx := CakePoints[CakeI].x - Gear^.X;
-        tdy := - CakePoints[CakeI].y + Gear^.Y;
-        CakePoints[CakeI].x := Gear^.X;
-        CakePoints[CakeI].y := Gear^.Y;
-        Gear^.DirAngle := DxDy2Angle(tdx, tdy);
+        cakeData:= PCakeData(Gear^.Data);
+        with cakeData^ do
+            begin
+            CakeI := (CakeI + 1) mod cakeh;
+            tdx := CakePoints[CakeI].x - Gear^.X;
+            tdy := - CakePoints[CakeI].y + Gear^.Y;
+            CakePoints[CakeI].x := Gear^.X;
+            CakePoints[CakeI].y := Gear^.Y;
+            Gear^.DirAngle := DxDy2Angle(tdx, tdy);
+            end;
         end;
 end;
 
 procedure doStepCakeUp(Gear: PGear);
 var
     i: Longword;
+    cakeData: PCakeData;
 begin
     AllInactive := false;
 
@@ -3250,12 +3260,16 @@
 
     if Gear^.Pos = 6 then
         begin
-        for i:= 0 to Pred(cakeh) do
+        cakeData:= PCakeData(Gear^.Data);
+        with cakeData^ do
             begin
-            CakePoints[i].x := Gear^.X;
-            CakePoints[i].y := Gear^.Y
+            for i:= 0 to Pred(cakeh) do
+                begin
+                CakePoints[i].x := Gear^.X;
+                CakePoints[i].y := Gear^.Y
+                end;
+            CakeI := 0;
             end;
-        CakeI := 0;
         Gear^.doStep := @doStepCakeWork
         end
     else
@@ -3559,6 +3573,11 @@
     AllInactive := false;
     dec(Gear^.Timer);
     HHGear := Gear^.Hedgehog^.Gear;
+    if HHGear = nil then
+        begin
+        DeleteGear(gear);
+        exit
+        end;
     HedgehogChAngle(HHGear);
     gX := hwRound(Gear^.X) + GetLaunchX(amBallgun, hwSign(HHGear^.dX), HHGear^.Angle);
     gY := hwRound(Gear^.Y) + GetLaunchY(amBallgun, HHGear^.Angle);
@@ -3575,6 +3594,7 @@
 
     if (Gear^.Timer = 0) or ((HHGear^.State and gstHHDriven) = 0) then
         begin
+        HHGear^.State := HHGear^.State and (not gstNotKickable);
         DeleteGear(Gear);
         AfterAttack
         end
@@ -3631,7 +3651,7 @@
         else if Gear^.Angle < 2048 then
             inc(Gear^.Angle)
         else fChanged := false
-    end
+        end
     else
         begin
         if ((Gear^.Message and gmLeft) <> 0) then
@@ -3706,6 +3726,7 @@
                 AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtFlame, 0, dX, dY, 0);
                 AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtFlame, 0, dX, -dY, 0);
                 end;
+            if HHGear <> nil then HHGear^.State := HHGear^.State and (not gstNotKickable);
             DeleteGear(Gear)
             end;
 
@@ -4962,6 +4983,11 @@
 begin
     AllInactive := false;
     HHGear := Gear^.Hedgehog^.Gear;
+    if HHGear = nil then
+        begin
+        DeleteGear(gear);
+        exit
+        end;
     HedgehogChAngle(HHGear);
     gX := hwRound(Gear^.X) + GetLaunchX(amBallgun, hwSign(HHGear^.dX), HHGear^.Angle);
     gY := hwRound(Gear^.Y) + GetLaunchY(amBallgun, HHGear^.Angle);
@@ -5014,6 +5040,7 @@
 
     if (Gear^.Health = 0) or ((HHGear^.State and gstHHDriven) = 0) then
         begin
+        HHGear^.State := HHGear^.State and (not gstNotKickable);
         DeleteGear(Gear);
         AfterAttack
         end
@@ -5049,6 +5076,11 @@
 begin
     AllInactive := false;
     HHGear := Gear^.Hedgehog^.Gear;
+    if HHGear = nil then
+        begin
+        DeleteGear(gear);
+        exit
+        end;
     HedgehogChAngle(HHGear);
     gX := hwRound(Gear^.X) + GetLaunchX(amBallgun, hwSign(HHGear^.dX), HHGear^.Angle);
     gY := hwRound(Gear^.Y) + GetLaunchY(amBallgun, HHGear^.Angle);
@@ -5091,6 +5123,7 @@
     if (Gear^.Health = 0) or ((HHGear^.State and gstHHDriven) = 0) or ((HHGear^.Message and gmAttack) <> 0) then
         begin
         HHGear^.Message:= HHGear^.Message and (not gmAttack);
+        HHGear^.State := HHGear^.State and (not gstNotKickable);
         DeleteGear(Gear);
         AfterAttack
         end
--- a/hedgewars/uGearsHedgehog.pas	Mon Feb 16 22:33:15 2015 +0300
+++ b/hedgewars/uGearsHedgehog.pas	Thu Apr 02 21:09:56 2015 +0300
@@ -222,7 +222,7 @@
 with Gear^,
     Gear^.Hedgehog^ do
         begin
-        if ((State and gstHHDriven) <> 0) and ((State and (gstAttacked or gstHHChooseTarget)) = 0) and (((State and gstMoving) = 0)
+        if ((State and gstHHDriven) <> 0) and ((State and (gstAttacked or gstChooseTarget)) = 0) and (((State and gstMoving) = 0)
         or (Power > 0)
         or (CurAmmoType = amTeleport)
         or
@@ -1318,7 +1318,7 @@
     begin
     if Gear^.Timer = 0 then
         begin
-        Gear^.State:= Gear^.State and (not (gstWait or gstLoser or gstWinner or gstAttacked or gstNotKickable or gstHHChooseTarget));
+        Gear^.State:= Gear^.State and (not (gstWait or gstLoser or gstWinner or gstAttacked or gstNotKickable or gstChooseTarget));
         if Gear^.Hedgehog^.Effects[heFrozen] = 0 then Gear^.Active:= false;
         AddCI(Gear);
         exit
--- a/hedgewars/uGearsList.pas	Mon Feb 16 22:33:15 2015 +0300
+++ b/hedgewars/uGearsList.pas	Thu Apr 02 21:09:56 2015 +0300
@@ -41,6 +41,7 @@
 (*       gtHedgehog *) , amNothing
 (*           gtMine *) , amMine
 (*           gtCase *) , amNothing
+(*        gtAirMine *) , amAirMine
 (*     gtExplosives *) , amNothing
 (*        gtGrenade *) , amGrenade
 (*          gtShell *) , amBazooka
@@ -103,7 +104,6 @@
 (*        gtAddAmmo *) , amNothing
 (*  gtGenericFaller *) , amNothing
 (*          gtKnife *) , amKnife
-(*        gtAirMine *) , amAirMine
     );
 
 
@@ -166,6 +166,7 @@
 function AddGear(X, Y: LongInt; Kind: TGearType; State: Longword; dX, dY: hwFloat; Timer: LongWord): PGear;
 var gear: PGear;
     //c: byte;
+    cakeData: PCakeData;
 begin
 inc(GCounter);
 
@@ -193,6 +194,7 @@
 gear^.AmmoType:= GearKindAmmoTypeMap[Kind];
 gear^.CollisionMask:= $FFFF;
 gear^.Tint:= $FFFFFFFF;
+gear^.Data:= nil;
 
 if CurrentHedgehog <> nil then
     begin
@@ -342,6 +344,8 @@
                 gear^.Radius:= 3;
                 gear^.Friction:= _450 * _0_01 * cRopePercent;
                 RopePoints.Count:= 0;
+                gear^.Tint:= $D8D8D8FF;
+                gear^.Tag:= 0; // normal rope render
                 end;
         gtMine: begin
                 gear^.ImpactSound:= sndMineImpact;
@@ -364,14 +368,14 @@
                 gear^.ImpactSound:= sndDenied;
                 gear^.nImpactSounds:= 1;
                 gear^.Health:= 30;
-                gear^.State:= gear^.State or gstMoving or gstNoGravity;
+                gear^.State:= gear^.State or gstMoving or gstNoGravity or gstSubmersible;
                 gear^.Radius:= 8;
                 gear^.Elasticity:= _0_55;
                 gear^.Friction:= _0_995;
                 gear^.Density:= _1;
                 gear^.Angle:= 175; // Radius at which air bombs will start "seeking". $FFFFFFFF = unlimited. check is skipped.
                 gear^.Power:= cMaxWindSpeed.QWordValue div 2; // hwFloat converted. 1/2 g default. defines the "seek" speed when a gear is in range.
-                gear^.Pos:= cMaxWindSpeed.QWordValue; // air friction. slows it down when not hitting stuff
+                gear^.Pos:= cMaxWindSpeed.QWordValue * 3 div 2; // air friction. slows it down when not hitting stuff
                 gear^.Karma:= 30; // damage
                 if gear^.Timer = 0 then
                     begin
@@ -500,12 +504,14 @@
                 gear^.Health:= 2048;
                 gear^.Radius:= 7;
                 gear^.Z:= cOnHHZ;
-                gear^.RenderTimer:= true;
+                gear^.RenderTimer:= false;
                 gear^.DirAngle:= -90 * hwSign(Gear^.dX);
                 if not dX.isNegative then
                     gear^.Angle:= 1
                 else
-                    gear^.Angle:= 3
+                    gear^.Angle:= 3;
+                New(cakeData);
+                gear^.Data:= Pointer(cakeData);
                 end;
  gtHellishBomb: begin
                 gear^.ImpactSound:= sndHellishImpact1;
@@ -658,6 +664,7 @@
 var team: PTeam;
     t,i: Longword;
     k: boolean;
+    cakeData: PCakeData;
 begin
 
 ScriptCall('onGearDelete', gear^.uid);
@@ -673,6 +680,12 @@
         if (Gear^.LinkedGear^.LinkedGear = Gear) then
             Gear^.LinkedGear^.LinkedGear:= nil;
     end
+else if Gear^.Kind = gtCake then
+    begin
+        cakeData:= PCakeData(Gear^.Data);
+        Dispose(cakeData);
+        cakeData:= nil;
+    end
 else if Gear^.Kind = gtHedgehog then
     (*
     This behaviour dates back to revision 4, and I accidentally encountered it with TARDIS.  I don't think it must apply to any modern weapon, since if it was actually hit, the best the gear could do would be to destroy itself immediately, and you'd still end up with two graves.  I believe it should be removed
--- a/hedgewars/uGearsRender.pas	Mon Feb 16 22:33:15 2015 +0300
+++ b/hedgewars/uGearsRender.pas	Thu Apr 02 21:09:56 2015 +0300
@@ -36,6 +36,7 @@
             rounded   : array[0..MAXROPEPOINTS + 2] of TVertex2f;
          end;
 procedure RenderGear(Gear: PGear; x, y: LongInt);
+procedure DrawHHOrder();
 
 var RopePoints: record
                 Count: Longword;
@@ -68,7 +69,8 @@
     EnableTexture(false);
     //glEnable(GL_LINE_SMOOTH);
 
-    Tint($70, $70, $70, $FF);
+    
+    Tint(Gear^.Tint shr 24 div 3, Gear^.Tint shr 16 and $FF div 3, Gear^.Tint shr 8 and $FF div 3, Gear^.Tint and $FF);
 
     n:= RopePoints.Count + 2;
 
@@ -79,7 +81,7 @@
 
     glLineWidth(3.0 * cScaleFactor);
     glDrawArrays(GL_LINE_STRIP, 0, n);
-    Tint($D8, $D8, $D8, $FF);
+    Tint(Gear^.Tint);
     glLineWidth(2.0 * cScaleFactor);
     glDrawArrays(GL_LINE_STRIP, 0, n);
 
@@ -169,7 +171,7 @@
 var roplen, i: LongInt;
 begin
     if Gear^.Hedgehog^.Gear = nil then exit;
-    if (cReducedQuality and rqSimpleRope) <> 0 then
+    if (Gear^.Tag = 1) or ((cReducedQuality and rqSimpleRope) <> 0) then
         DrawRopeLinesRQ(Gear)
     else
         begin
@@ -214,6 +216,51 @@
     end;
 end;
 
+procedure DrawHHOrder();
+var HHGear: PGear;
+    hh: PHedgehog;
+    c, i, t, x, y, sprH, sprW, fSprOff: LongInt;
+begin
+t:= LocalTeam;
+
+if not CurrentTeam^.ExtDriven then
+    for i:= 0 to Pred(TeamsCount) do
+        if (TeamsArray[i] = CurrentTeam) then
+            t:= i;
+
+if t < 0 then
+    exit;
+
+if TeamsArray[t] <> nil then
+    begin
+    sprH:= SpritesData[sprBigDigit].Height;
+    sprW:= SpritesData[sprBigDigit].Width;
+    fSprOff:= sprW div 4 + SpritesData[sprFrame].Width div 4 - 1; // - 1 for overlap to avoid artifacts
+    i:= 0;
+    c:= 0;
+        repeat
+        hh:= @TeamsArray[t]^.Hedgehogs[i];
+        inc(i);
+        if (hh <> nil) and (hh^.Gear <> nil) then
+            begin
+            inc(c);
+            HHGear:= hh^.Gear;
+            x:= hwRound(HHGear^.X) + WorldDx;
+            y:= hwRound(HHGear^.Y) + WorldDy - 2;
+            if (SpeechHogNumber <> c) or ((RealTicks and 512) < 256) then
+                begin
+                DrawTextureF(SpritesData[sprFrame].Texture, 0.5, x - fSprOff, y, 0, 1, SpritesData[sprFrame].Width, SpritesData[sprFrame].Height);
+                DrawTextureF(SpritesData[sprFrame].Texture, 0.5, x + fSprOff, y, 1, 1, SpritesData[sprFrame].Width, SpritesData[sprFrame].Height);
+                DrawTextureF(SpritesData[sprBigDigit].Texture, 0.5, x, y, c, 1, sprW, sprH);
+                end
+            else
+                DrawCircle(x, y, 20, 3, 0, $FF, $FF, $80);
+            end;
+        until (i > cMaxHHIndex);
+    end
+
+end;
+
 
 procedure DrawHH(Gear: PGear; ox, oy: LongInt);
 var i, t: LongInt;
@@ -1128,7 +1175,7 @@
                         end
                     else if (Gear^.Hedgehog <> nil) and (Gear^.Hedgehog^.Gear <> nil) then  // mine is chasing a hog
                          DrawSprite(sprAirMine, x-16, y-16, (RealTicks div 25) mod 16)
-                    else if Gear^.State and gstHHChooseTarget <> 0 then   // mine is seeking for hogs
+                    else if Gear^.State and gstChooseTarget <> 0 then   // mine is seeking for hogs
                          DrawSprite(sprAirMine, x-16, y-16, (RealTicks div 125) mod 16)
                     else
                          DrawSprite(sprAirMine, x-16, y-16, 4);           // mine is active but not seeking
--- a/hedgewars/uGearsUtils.pas	Mon Feb 16 22:33:15 2015 +0300
+++ b/hedgewars/uGearsUtils.pas	Thu Apr 02 21:09:56 2015 +0300
@@ -82,6 +82,7 @@
     vg: PVisualGear;
     i, cnt: LongInt;
     wrap: boolean;
+    bubble: PVisualGear;
 begin
 if Radius > 4 then AddFileLog('Explosion: at (' + inttostr(x) + ',' + inttostr(y) + ')');
 if Radius > 25 then KickFlakes(Radius, X, Y);
@@ -89,7 +90,17 @@
 if ((Mask and EXPLNoGfx) = 0) then
     begin
     vg:= nil;
-    if Radius > 50 then vg:= AddVisualGear(X, Y, vgtBigExplosion)
+    if CheckCoordInWater(X, Y - Radius) then
+        begin
+        cnt:= 2 * Radius;
+        for i:= (Radius * Radius) div 4 downto 0 do
+            begin
+            bubble := AddVisualGear(X - Radius + random(cnt), Y - Radius + random(cnt), vgtBubble);
+            if bubble <> nil then
+                bubble^.dY:= 0.1 + random(20)/10;
+            end
+        end
+    else if Radius > 50 then vg:= AddVisualGear(X, Y, vgtBigExplosion)
     else if Radius > 10 then vg:= AddVisualGear(X, Y, vgtExplosion);
     if vg <> nil then
         vg^.Tint:= Tint;
@@ -703,11 +714,7 @@
             ScriptCall('onGearWaterSkip', Gear^.uid);
         end
     else
-        begin
-        if (not ((Gear^.Kind = gtJetpack) or (Gear^.Kind = gtBee))) then
-            Gear^.State:= (Gear^.State and (not gstSubmersible));  // making it temporary for most gears is more attractive I think
         CheckGearDrowning := false
-        end
 end;
 
 
--- a/hedgewars/uIO.pas	Mon Feb 16 22:33:15 2015 +0300
+++ b/hedgewars/uIO.pas	Thu Apr 02 21:09:56 2015 +0300
@@ -143,6 +143,7 @@
              ParseChatCommand('chatmsg ' + #4, s, 2)
           else
              isProcessed:= false;
+     'Y': ChatPasteBuffer:= copy(s, 2, Length(s) - 1);
      else
         isProcessed:= false;
      end;
@@ -370,7 +371,11 @@
 
 isInLag:= (headcmd = nil) and tmpflag and (not CurrentTeam^.hasGone);
 
-if isInLag then fastUntilLag:= false
+if isInLag and fastUntilLag then 
+begin
+    ParseCommand('spectate 0', true);
+    fastUntilLag:= false
+end;
 end;
 
 procedure chFatalError(var s: shortstring);
@@ -398,7 +403,7 @@
 
 with CurrentHedgehog^.Gear^,
     CurrentHedgehog^ do
-    if (State and gstHHChooseTarget) <> 0 then
+    if (State and gstChooseTarget) <> 0 then
         begin
         isCursorVisible:= false;
         if not CurrentTeam^.ExtDriven then
@@ -421,7 +426,7 @@
             TargetPoint.Y:= putY
             end;
         AddFileLog('put: ' + inttostr(TargetPoint.X) + ', ' + inttostr(TargetPoint.Y));
-        State:= State and (not gstHHChooseTarget);
+        State:= State and (not gstChooseTarget);
         if (Ammoz[CurAmmoType].Ammo.Propz and ammoprop_AttackingPut) <> 0 then
             Message:= Message or (gmAttack and InputMask);
         end
--- a/hedgewars/uLandGraphics.pas	Mon Feb 16 22:33:15 2015 +0300
+++ b/hedgewars/uLandGraphics.pas	Thu Apr 02 21:09:56 2015 +0300
@@ -82,7 +82,7 @@
             inc(drawPixelBG);
         end
         else if ((Land[landY, landX] and lfObject) <> 0) or (((LandPixels[pixelY, pixelX] and AMask) shr AShift) < 255) then
-            LandPixels[pixelY, pixelX]:= LandPixels[pixelY, pixelX] and (not AMASK)
+            LandPixels[pixelY, pixelX]:= ExplosionBorderColorNoA
     end;
 end;
 
@@ -199,7 +199,7 @@
             begin
             calculatePixelsCoordinates(i, y, px, py);
             if ((Land[y, i] and lfIndestructible) = 0) and (not disableLandBack or (Land[y, i] > 255))  then
-                LandPixels[py, px]:= LandPixels[py, px] and (not AMASK);
+                LandPixels[py, px]:= ExplosionBorderColorNoA;
             end;
     icePixel:
         for i:= fromPix to toPix do
@@ -920,7 +920,68 @@
     Despeckle:= false
 end;
 
+// a bit of AA for explosions
 procedure Smooth(X, Y: LongInt);
+var c, r, g, b, a, i: integer;
+    nx, ny: LongInt;
+    pixel: LongWord;
+begin
+
+// only AA inwards
+if (Land[Y, X] and lfDamaged) = 0 then
+    exit;
+
+// check location
+if (Y <= LongInt(topY) + 1) or (Y >= LAND_HEIGHT-2)
+or (X <= LongInt(leftX) + 1) or (X >= LongInt(rightX) - 1) then
+    exit;
+
+// counter for neighbor pixels that are not known to be undamaged
+c:= 8;
+
+// accumalating rgba value of relevant pixels here
+r:= 0;
+g:= 0;
+b:= 0;
+a:= 0;
+
+// iterate over all neighbor pixels (also itself, will be skipped anyway)
+for nx:= X-1 to X+1 do
+    for ny:= Y-1 to Y+1 do
+        // only consider undamaged neighbors (also leads to skipping itself)
+        if (Land[ny, nx] and lfDamaged) = 0 then
+            begin
+            pixel:= LandPixels[ny, nx];
+            inc(r, (pixel and RMask) shr RShift);
+            inc(g, (pixel and GMask) shr GShift);
+            inc(b, (pixel and BMask) shr BShift);
+            inc(a, (pixel and AMask) shr AShift);
+            dec(c);
+            end;
+
+// nothing do to if all neighbors damaged
+if c < 1 then
+    exit;
+
+// use explosion color for damaged pixels
+for i:= 1 to c do
+    begin
+    inc(r, ExplosionBorderColorR);
+    inc(g, ExplosionBorderColorG);
+    inc(b, ExplosionBorderColorB);
+    inc(a, 255);
+    end;
+
+// set resulting color value based on average of all neighbors
+r:= r div 8;
+g:= g div 8;
+b:= b div 8;
+a:= a div 8;
+LandPixels[y,x]:= (r shl RShift) or (g shl GShift) or (b shl BShift) or (a shl AShift);
+
+end;
+
+procedure Smooth_oldImpl(X, Y: LongInt);
 begin
 // a bit of AA for explosions
 if (Land[Y, X] = 0) and (Y > LongInt(topY) + 1) and
@@ -939,12 +1000,14 @@
                                 (((((LandPixels[y,x] and GMask shr GShift) div 2)+((ExplosionBorderColor and GMask) shr GShift) div 2) and $FF) shl GShift) or
                                 (((((LandPixels[y,x] and BMask shr BShift) div 2)+((ExplosionBorderColor and BMask) shr BShift) div 2) and $FF) shl BShift) or ($FF shl AShift)
             end;
+{
         if (Land[y, x-1] = lfObject) then
             Land[y,x]:= lfObject
         else if (Land[y, x+1] = lfObject) then
             Land[y,x]:= lfObject
         else
             Land[y,x]:= lfBasic;
+}
         end
     else if ((((Land[y, x-1] and lfDamaged) <> 0) and ((Land[y+1,x-1] and lfDamaged) <> 0) and ((Land[y+2,x] and lfDamaged) <> 0))
     or (((Land[y, x-1] and lfDamaged) <> 0) and ((Land[y-1,x-1] and lfDamaged) <> 0) and ((Land[y-2,x] and lfDamaged) <> 0))
@@ -965,6 +1028,7 @@
                                 (((((LandPixels[y,x] and GMask shr GShift) * 3 div 4)+((ExplosionBorderColor and GMask) shr GShift) div 4) and $FF) shl GShift) or
                                 (((((LandPixels[y,x] and BMask shr BShift) * 3 div 4)+((ExplosionBorderColor and BMask) shr BShift) div 4) and $FF) shl BShift) or ($FF shl AShift)
             end;
+{
         if (Land[y, x-1] = lfObject) then
             Land[y, x]:= lfObject
         else if (Land[y, x+1] = lfObject) then
@@ -974,6 +1038,7 @@
         else if (Land[y-1, x] = lfObject) then
         Land[y, x]:= lfObject
         else Land[y,x]:= lfBasic
+}
         end
     end
 else if ((cReducedQuality and rqBlurryLand) = 0) and ((LandPixels[Y, X] and AMask) = AMask)
@@ -1060,16 +1125,18 @@
         end;
      end;
 
-for y:= 0 to LAND_HEIGHT div 32 - 1 do
-    for x:= 0 to LAND_WIDTH div 32 - 1 do
-        if LandDirty[y, x] <> 0 then
-            begin
-            ty:= y * 32;
-            tx:= x * 32;
-            for yy:= ty to ty + 31 do
-                for xx:= tx to tx + 31 do
-                    Smooth(xx,yy)
-            end;
+// smooth explosion borders (except if land is blurry)
+if (cReducedQuality and rqBlurryLand) = 0 then
+    for y:= 0 to LAND_HEIGHT div 32 - 1 do
+        for x:= 0 to LAND_WIDTH div 32 - 1 do
+            if LandDirty[y, x] <> 0 then
+                begin
+                ty:= y * 32;
+                tx:= x * 32;
+                for yy:= ty to ty + 31 do
+                    for xx:= tx to tx + 31 do
+                        Smooth(xx,yy)
+                end;
 
 for y:= 0 to LAND_HEIGHT div 32 - 1 do
     for x:= 0 to LAND_WIDTH div 32 - 1 do
--- a/hedgewars/uLandObjects.pas	Mon Feb 16 22:33:15 2015 +0300
+++ b/hedgewars/uLandObjects.pas	Thu Apr 02 21:09:56 2015 +0300
@@ -553,7 +553,12 @@
             c2.g:= t;
             c2.b:= t
             end;
-        ExplosionBorderColor:= (c2.r shl RShift) or (c2.g shl GShift) or (c2.b shl BShift) or AMask;
+        ExplosionBorderColorR:= c2.r;
+        ExplosionBorderColorG:= c2.g;
+        ExplosionBorderColorB:= c2.b;
+        ExplosionBorderColorNoA:=
+            (c2.r shl RShift) or (c2.g shl GShift) or (c2.b shl BShift);
+        ExplosionBorderColor:= ExplosionBorderColorNoA or AMask;
         end
     else if key = 'water-top' then
         begin
--- a/hedgewars/uRender.pas	Mon Feb 16 22:33:15 2015 +0300
+++ b/hedgewars/uRender.pas	Thu Apr 02 21:09:56 2015 +0300
@@ -48,6 +48,7 @@
 
 procedure DrawCircle            (X, Y, Radius, Width: LongInt);
 procedure DrawCircle            (X, Y, Radius, Width: LongInt; r, g, b, a: Byte);
+procedure DrawCircleFilled      (X, Y, Radius: LongInt; r, g, b, a: Byte);
 
 procedure DrawLine              (X0, Y0, X1, Y1, Width: Single; color: LongWord); inline;
 procedure DrawLine              (X0, Y0, X1, Y1, Width: Single; r, g, b, a: Byte);
@@ -59,12 +60,16 @@
 procedure DrawWaves             (Dir, dX, dY, oX: LongInt; tnt: Byte);
 
 procedure RenderClear           ();
-procedure RenderSetClearColor      (r, g, b, a: real);
+procedure RenderClear           (mode: TRenderMode);
+procedure RenderSetClearColor   (r, g, b, a: real);
 procedure Tint                  (r, g, b, a: Byte); inline;
 procedure Tint                  (c: Longword); inline;
 procedure untint(); inline;
 procedure setTintAdd            (f: boolean); inline;
 
+// call this to finish the rendering of current frame
+procedure FinishRender();
+
 function isAreaOffscreen(X, Y, Width, Height: LongInt): boolean; inline;
 
 // 0 => not offscreen, <0 => left/top of screen >0 => right/below of screen
@@ -74,13 +79,14 @@
 procedure SetScale(f: GLfloat);
 procedure UpdateViewLimits();
 
-procedure RenderSetup();
+procedure RendererSetup();
+procedure RendererCleanup();
+
+procedure ChangeDepth(rm: TRenderMode; d: GLfloat);
+procedure ResetDepth(rm: TRenderMode);
 
 // TODO everything below this should not need a public interface
 
-procedure CreateFramebuffer(var frame, depth, tex: GLuint);
-procedure DeleteFramebuffer(var frame, depth, tex: GLuint);
-
 procedure EnableTexture(enable:Boolean);
 
 procedure SetTexCoordPointer(p: Pointer;n: Integer); inline;
@@ -89,14 +95,9 @@
 
 procedure UpdateModelviewProjection(); inline;
 
-procedure openglLoadIdentity    (); inline;
-procedure openglTranslProjMatrix(X, Y, Z: GLFloat); inline;
 procedure openglPushMatrix      (); inline;
 procedure openglPopMatrix       (); inline;
 procedure openglTranslatef      (X, Y, Z: GLfloat); inline;
-procedure openglScalef          (ScaleX, ScaleY, ScaleZ: GLfloat); inline;
-procedure openglRotatef         (RotX, RotY, RotZ: GLfloat; dir: LongInt); inline;
-procedure openglTint            (r, g, b, a: Byte); inline;
 
 
 implementation
@@ -123,6 +124,20 @@
     LastColorPointerN, LastTexCoordPointerN, LastVertexPointerN: Integer;
 {$ENDIF}
 
+{$IFDEF USE_S3D_RENDERING}
+    // texture/vertex buffers for left/right/default eye modes
+    texLRDtb, texLvb, texRvb: array [0..3] of TVertex2f;
+{$ENDIF}
+
+procedure openglLoadIdentity    (); forward;
+procedure openglTranslProjMatrix(X, Y, Z: GLFloat); forward;
+procedure openglScalef          (ScaleX, ScaleY, ScaleZ: GLfloat); forward;
+procedure openglRotatef         (RotX, RotY, RotZ: GLfloat; dir: LongInt); forward;
+procedure openglTint            (r, g, b, a: Byte); forward;
+
+procedure CreateFramebuffer(var frame, depth, tex: GLuint); forward;
+procedure DeleteFramebuffer(var frame, depth, tex: GLuint); forward;
+
 function isAreaOffscreen(X, Y, Width, Height: LongInt): boolean; inline;
 begin
     isAreaOffscreen:= (isDxAreaOffscreen(X, Width) <> 0) or (isDyAreaOffscreen(Y, Height) <> 0);
@@ -147,11 +162,92 @@
     glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
 end;
 
+{$IFDEF USE_S3D_RENDERING}
+procedure RenderClear(mode: TRenderMode);
+var frame: GLuint;
+begin
+    if (cStereoMode = smHorizontal) or (cStereoMode = smVertical) then
+        begin
+        case mode of
+            rmLeftEye:  frame:= frameL;
+            rmRightEye: frame:= frameR;
+            else
+                frame:= defaultFrame;
+        end;
+
+        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, frame);
+
+        RenderClear();
+        end
+    else
+        begin
+        // draw left eye in red channel only
+        if mode = rmLeftEye then
+            begin
+            glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+            RenderClear();
+            if cStereoMode = smGreenRed then
+                glColorMask(GL_FALSE, GL_TRUE, GL_FALSE, GL_TRUE)
+            else if cStereoMode = smBlueRed then
+                glColorMask(GL_FALSE, GL_FALSE, GL_TRUE, GL_TRUE)
+            else if cStereoMode = smCyanRed then
+                glColorMask(GL_FALSE, GL_TRUE, GL_TRUE, GL_TRUE)
+            else
+                glColorMask(GL_TRUE, GL_FALSE, GL_FALSE, GL_TRUE);
+            end
+        else
+            begin
+            // draw right eye in selected channel(s) only
+            if cStereoMode = smRedGreen then
+                glColorMask(GL_FALSE, GL_TRUE, GL_FALSE, GL_TRUE)
+            else if cStereoMode = smRedBlue then
+                glColorMask(GL_FALSE, GL_FALSE, GL_TRUE, GL_TRUE)
+            else if cStereoMode = smRedCyan then
+                glColorMask(GL_FALSE, GL_TRUE, GL_TRUE, GL_TRUE)
+            else
+                glColorMask(GL_TRUE, GL_FALSE, GL_FALSE, GL_TRUE);
+            end;
+        end;
+end;
+{$ENDIF}
+
 procedure RenderSetClearColor(r, g, b, a: real);
 begin
     glClearColor(r, g, b, a);
 end;
 
+procedure FinishRender();
+begin
+
+{$IFDEF USE_S3D_RENDERING}
+if (cStereoMode = smHorizontal) or (cStereoMode = smVertical) then
+    begin
+    RenderClear(rmDefault);
+
+    SetScale(cDefaultZoomLevel);
+
+
+    // same for all
+    SetTexCoordPointer(@texLRDtb, Length(texLRDtb));
+
+
+    // draw left frame
+    glBindTexture(GL_TEXTURE_2D, texl);
+    SetVertexPointer(@texLvb, Length(texLvb));
+    //UpdateModelviewProjection;
+    glDrawArrays(GL_TRIANGLE_FAN, 0, Length(texLvb));
+
+    // draw right frame
+    glBindTexture(GL_TEXTURE_2D, texl);
+    SetVertexPointer(@texRvb, Length(texRvb));
+    //UpdateModelviewProjection;
+    glDrawArrays(GL_TRIANGLE_FAN, 0, Length(texRvb));
+
+    SetScale(zoom);
+    end;
+{$ENDIF}
+end;
+
 {$IFDEF GL2}
 function CompileShader(shaderFile: string; shaderType: GLenum): GLuint;
 var
@@ -302,14 +398,42 @@
     glDeleteRenderbuffersEXT(1, @depth);
     glDeleteFramebuffersEXT(1, @frame);
 end;
+{$ENDIF}
 
+procedure RendererCleanup();
+begin
+{$IFNDEF PAS2C}
+{$IFDEF USE_VIDEO_RECORDING}
+    if defaultFrame <> 0 then
+        DeleteFramebuffer(defaultFrame, depthv, texv);
 {$ENDIF}
-procedure RenderSetup();
+{$IFDEF USE_S3D_RENDERING}
+    if (cStereoMode = smHorizontal) or (cStereoMode = smVertical) then
+        begin
+        DeleteFramebuffer(framel, depthl, texl);
+        DeleteFramebuffer(framer, depthr, texr);
+        end
+{$ENDIF}
+{$ENDIF}
+end;
+
+procedure RendererSetup();
 var AuxBufNum: LongInt = 0;
     tmpstr: ansistring;
     tmpint: LongInt;
     tmpn: LongInt;
 begin
+{$IFDEF MOBILE}
+    // TODO: this function creates an opengles1.1 context
+    // un-comment below and add proper logic to support opengles2.0
+    //SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
+    //SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
+    if SDLGLcontext = nil then
+        SDLGLcontext:= SDL_GL_CreateContext(SDLwindow);
+    SDLTry(SDLGLcontext <> nil, true);
+    SDL_GL_SetSwapInterval(1);
+{$ENDIF}
+
     // suppress hint/warning
     AuxBufNum:= AuxBufNum;
 
@@ -426,12 +550,66 @@
             CreateFramebuffer(framel, depthl, texl);
             CreateFramebuffer(framer, depthr, texr);
 
+
+
+
             // reset
             glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, defaultFrame)
             end
         else
             cStereoMode:= smNone;
     end;
+
+    // set up vertex/texture buffers for frame textures
+    texLRDtb[0].X:= 0.0;
+    texLRDtb[0].Y:= 0.0;
+    texLRDtb[1].X:= 1.0;
+    texLRDtb[1].Y:= 0.0;
+    texLRDtb[2].X:= 1.0;
+    texLRDtb[2].Y:= 1.0;
+    texLRDtb[3].X:= 0.0;
+    texLRDtb[3].Y:= 1.0;
+
+    if cStereoMode = smHorizontal then
+        begin
+        texLvb[0].X:= cScreenWidth / -2;
+        texLvb[0].Y:= cScreenHeight;
+        texLvb[1].X:= 0;
+        texLvb[1].Y:= cScreenHeight;
+        texLvb[2].X:= 0;
+        texLvb[2].Y:= 0;
+        texLvb[3].X:= cScreenWidth / -2;
+        texLvb[3].Y:= 0;
+
+        texRvb[0].X:= 0;
+        texRvb[0].Y:= cScreenHeight;
+        texRvb[1].X:= cScreenWidth / 2;
+        texRvb[1].Y:= cScreenHeight;
+        texRvb[2].X:= cScreenWidth / 2;
+        texRvb[2].Y:= 0;
+        texRvb[3].X:= 0;
+        texRvb[3].Y:= 0;
+        end
+    else
+        begin
+        texLvb[0].X:= cScreenWidth / -2;
+        texLvb[0].Y:= cScreenHeight / 2;
+        texLvb[1].X:= cScreenWidth / 2;
+        texLvb[1].Y:= cScreenHeight / 2;
+        texLvb[2].X:= cScreenWidth / 2;
+        texLvb[2].Y:= 0;
+        texLvb[3].X:= cScreenWidth / -2;
+        texLvb[3].Y:= 0;
+
+        texRvb[0].X:= cScreenWidth / -2;
+        texRvb[0].Y:= cScreenHeight;
+        texRvb[1].X:= cScreenWidth / 2;
+        texRvb[1].Y:= cScreenHeight;
+        texRvb[2].X:= cScreenWidth / 2;
+        texRvb[2].Y:= cScreenHeight / 2;
+        texRvb[3].X:= cScreenWidth / -2;
+        texRvb[3].Y:= cScreenHeight / 2;
+        end;
 {$ENDIF}
 
 // set view port to whole window
@@ -1220,6 +1398,25 @@
     glDisable(GL_LINE_SMOOTH);
 end;
 
+procedure DrawCircleFilled(X, Y, Radius: LongInt; r, g, b, a: Byte);
+var
+    i: LongInt;
+begin
+    VertexBuffer[0].X := X;
+    VertexBuffer[0].Y := Y;
+
+    for i := 1 to 19 do begin
+        VertexBuffer[i].X := X + Radius*cos(i*pi/9);
+        VertexBuffer[i].Y := Y + Radius*sin(i*pi/9);
+    end;
+
+    EnableTexture(False);
+    Tint(r, g, b, a);
+    SetVertexPointer(@VertexBuffer[0], 20);
+    glDrawArrays(GL_TRIANGLE_FAN, 0, 20);
+    Untint();
+    EnableTexture(True);
+end;
 
 procedure DrawHedgehog(X, Y: LongInt; Dir: LongInt; Pos, Step: LongWord; Angle: real);
 const VertexBuffer: array [0..3] of TVertex2f = (
@@ -1387,11 +1584,6 @@
 firsti:= -1;
 afteri:=  0;
 
-if GameTicks < 2000 then
-    lol:= 2000 - GameTicks
-else
-    lol:= 0;
-
 if InTopY < 0 then
     InTopY:= 0;
 
@@ -1403,6 +1595,13 @@
     end
 else
     begin
+
+    // animate water walls raise animation at start of game
+    if GameTicks < 2000 then
+        lol:= 2000 - GameTicks
+    else
+        lol:= 0;
+
     if InLeftX > ViewLeftX then
         begin
         VertexBuffer[0].X:= OutLeftX - lol;
@@ -1712,6 +1911,42 @@
         glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
 end;
 
+procedure ChangeDepth(rm: TRenderMode; d: GLfloat);
+var tmp: LongInt;
+begin
+{$IFNDEF USE_S3D_RENDERING}
+    rm:= rm; d:= d; tmp:= tmp; // avoid hint
+{$ELSE}
+    d:= d / 5;
+    if rm = rmDefault then
+        exit
+    else if rm = rmLeftEye then
+        d:= -d;
+    cStereoDepth:= cStereoDepth + d;
+    openglTranslProjMatrix(d, 0, 0);
+    tmp:= round(d / cScaleFactor * cScreenWidth);
+    ViewLeftX := ViewLeftX  - tmp;
+    ViewRightX:= ViewRightX - tmp;
+{$ENDIF}
+end;
+
+procedure ResetDepth(rm: TRenderMode);
+var tmp: LongInt;
+begin
+{$IFNDEF USE_S3D_RENDERING}
+    rm:= rm; tmp:= tmp; // avoid hint
+{$ELSE}
+    if rm = rmDefault then
+        exit;
+    openglTranslProjMatrix(-cStereoDepth, 0, 0);
+    tmp:= round(cStereoDepth / cScaleFactor * cScreenWidth);
+    ViewLeftX := ViewLeftX  + tmp;
+    ViewRightX:= ViewRightX + tmp;
+    cStereoDepth:= 0;
+{$ENDIF}
+end;
+
+
 procedure initModule;
 begin
     LastTint:= cWhiteColor + 1;
--- a/hedgewars/uRenderUtils.pas	Mon Feb 16 22:33:15 2015 +0300
+++ b/hedgewars/uRenderUtils.pas	Thu Apr 02 21:09:56 2015 +0300
@@ -171,7 +171,7 @@
             r0:= (r0 * (255 - LongInt(a1)) + r1 * LongInt(a1)) div 255;
             g0:= (g0 * (255 - LongInt(a1)) + g1 * LongInt(a1)) div 255;
             b0:= (b0 * (255 - LongInt(a1)) + b1 * LongInt(a1)) div 255;
-            a0:= (a0 * (255 - LongInt(a1)) + a1 * LongInt(a1)) div 255;
+            a0:= a0 + ((255 - LongInt(a0)) * a1 div 255);
             destPixels^[i]:= SDL_MapRGBA(dest^.format, r0, g0, b0, a0);
             end;
         end;
--- a/hedgewars/uScript.pas	Mon Feb 16 22:33:15 2015 +0300
+++ b/hedgewars/uScript.pas	Thu Apr 02 21:09:56 2015 +0300
@@ -2309,7 +2309,7 @@
 begin
     if CheckLuaParamCount(L, 5, 'TestRectForObstacle', 'x1, y1, x2, y2, landOnly') then
         begin
-        rtn:= TestRectancleForObstacle(
+        rtn:= TestRectangleForObstacle(
                     lua_tointeger(L, 1),
                     lua_tointeger(L, 2),
                     lua_tointeger(L, 3),
@@ -3027,7 +3027,7 @@
 ScriptSetInteger('gstAttacked'      , gstAttacked);
 ScriptSetInteger('gstAttacking'     , gstAttacking);
 ScriptSetInteger('gstCollision'     , gstCollision);
-ScriptSetInteger('gstHHChooseTarget', gstHHChooseTarget);
+ScriptSetInteger('gstChooseTarget', gstChooseTarget);
 ScriptSetInteger('gstHHJumping'     , gstHHJumping);
 ScriptSetInteger('gsttmpFlag'       , gsttmpFlag);
 ScriptSetInteger('gstHHThinking'    , gstHHThinking);
--- a/hedgewars/uStore.pas	Mon Feb 16 22:33:15 2015 +0300
+++ b/hedgewars/uStore.pas	Thu Apr 02 21:09:56 2015 +0300
@@ -561,19 +561,8 @@
                 end;
             end;
         end;
-{$IFNDEF PAS2C}
-{$IFDEF USE_VIDEO_RECORDING}
-    if defaultFrame <> 0 then
-        DeleteFramebuffer(defaultFrame, depthv, texv);
-{$ENDIF}
-{$IFDEF USE_S3D_RENDERING}
-    if (cStereoMode = smHorizontal) or (cStereoMode = smVertical) then
-        begin
-        DeleteFramebuffer(framel, depthl, texl);
-        DeleteFramebuffer(framer, depthr, texr);
-        end
-{$ENDIF}
-{$ENDIF}
+
+RendererCleanup();
 end;
 
 
@@ -756,18 +745,7 @@
     AddFileLog('Setting up OpenGL (using driver: ' + shortstring(SDL_VideoDriverName(buf, sizeof(buf))) + ')');
 {$ENDIF}
 
-{$IFDEF MOBILE}
-    // TODO: this function creates an opengles1.1 context
-    // un-comment below and add proper logic to support opengles2.0
-    //SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
-    //SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
-    if SDLGLcontext = nil then
-        SDLGLcontext:= SDL_GL_CreateContext(SDLwindow);
-    SDLTry(SDLGLcontext <> nil, true);
-    SDL_GL_SetSwapInterval(1);
-{$ENDIF}
-
-    RenderSetup();
+    RendererSetup();
 end;
 
 (*
@@ -1045,7 +1023,7 @@
     glutHideWindow();
     // we do not need to set this callback, but it is required for GLUT3 compat
     glutDisplayFunc(@SwapBuffers);
-    RenderSetup();
+    RendererSetup();
 end;
 {$ENDIF} // SDL2
 {$ENDIF} // USE_VIDEO_RECORDING
--- a/hedgewars/uTeams.pas	Mon Feb 16 22:33:15 2015 +0300
+++ b/hedgewars/uTeams.pas	Thu Apr 02 21:09:56 2015 +0300
@@ -549,10 +549,12 @@
     Gear: PGear;
 begin
 s:= '';
-if (not isDeveloperMode) or (CurrentTeam = nil) then
+if (not isDeveloperMode) then
     exit;
+TryDo((CurrentTeam <> nil), 'Can''t add hedgehogs yet, add a team first!', true);
 with CurrentTeam^ do
     begin
+    TryDo(HedgehogsNumber<=cMaxHHIndex, 'Can''t add hedgehog to "' + TeamName + '"! (already ' + intToStr(HedgehogsNumber) + ' hogs)', true);
     SplitBySpace(id, s);
     SwitchCurrentHedgehog(@Hedgehogs[HedgehogsNumber]);
     CurrentHedgehog^.BotLevel:= StrToInt(id);
--- a/hedgewars/uTypes.pas	Mon Feb 16 22:33:15 2015 +0300
+++ b/hedgewars/uTypes.pas	Thu Apr 02 21:09:56 2015 +0300
@@ -92,20 +92,21 @@
             );
 
     // Gears that interact with other Gears and/or Land
-    TGearType = (gtFlame, gtHedgehog, gtMine, gtCase, gtExplosives, // these gears should be avoided when searching a spawn place
-            gtGrenade, gtShell, gtGrave, gtBee, // 8
-            gtShotgunShot, gtPickHammer, gtRope,  // 11
-            gtDEagleShot, gtDynamite, gtClusterBomb, gtCluster, gtShover, // 16
-            gtFirePunch, gtATStartGame, // 18
-            gtATFinishGame, gtParachute, gtAirAttack, gtAirBomb, gtBlowTorch, // 23
-            gtGirder, gtTeleport, gtSwitcher, gtTarget, gtMortar, // 28
-            gtWhip, gtKamikaze, gtCake, gtSeduction, gtWatermelon, gtMelonPiece, // 34
-            gtHellishBomb, gtWaterUp, gtDrill, gtBallGun, gtBall, gtRCPlane, // 40
-            gtSniperRifleShot, gtJetpack, gtMolotov, gtBirdy, // 44
-            gtEgg, gtPortal, gtPiano, gtGasBomb, gtSineGunShot, gtFlamethrower, // 50
-            gtSMine, gtPoisonCloud, gtHammer, gtHammerHit, gtResurrector, // 55
+    // first row of gears (<gtExplosives) should be avoided when searching a spawn place
+    TGearType = (gtFlame, gtHedgehog, gtMine, gtCase, gtAirMine, gtExplosives, 
+            gtGrenade, gtShell, gtGrave, gtBee, // 9
+            gtShotgunShot, gtPickHammer, gtRope,  // 12
+            gtDEagleShot, gtDynamite, gtClusterBomb, gtCluster, gtShover, // 17
+            gtFirePunch, gtATStartGame, // 19
+            gtATFinishGame, gtParachute, gtAirAttack, gtAirBomb, gtBlowTorch, // 24
+            gtGirder, gtTeleport, gtSwitcher, gtTarget, gtMortar, // 29
+            gtWhip, gtKamikaze, gtCake, gtSeduction, gtWatermelon, gtMelonPiece, // 35
+            gtHellishBomb, gtWaterUp, gtDrill, gtBallGun, gtBall, gtRCPlane, // 41
+            gtSniperRifleShot, gtJetpack, gtMolotov, gtBirdy, // 45
+            gtEgg, gtPortal, gtPiano, gtGasBomb, gtSineGunShot, gtFlamethrower, // 51
+            gtSMine, gtPoisonCloud, gtHammer, gtHammerHit, gtResurrector, // 56
             gtNapalmBomb, gtSnowball, gtFlake, {gtStructure,} gtLandGun, gtTardis, // 61
-            gtIceGun, gtAddAmmo, gtGenericFaller, gtKnife, gtAirMine); // 66
+            gtIceGun, gtAddAmmo, gtGenericFaller, gtKnife); // 65
 
     // Gears that are _only_ of visual nature (e.g. background stuff, visual effects, speechbubbles, etc.)
     TVisualGearType = (vgtFlake, vgtCloud, vgtExplPart, vgtExplPart2, vgtFire,
@@ -282,6 +283,7 @@
             Tint: LongWord;         // Used to colour a texture
             LinkedGear: PGear;      // Used to track a related gear. Portal pairs for example.
             SoundChannel: LongInt;  // Used to track a sound the gear started
+            Data: Pointer; // pointer to gear type specific data structure (if any)
             end;
     TPGearArray = array of PGear;
     PGearArrayS = record
@@ -515,6 +517,19 @@
             getDimensions, getImageDimensions: boolean;
             end;
 
+    // gear data types
+
+    const cakeh =   27;
+
+    type TCakeData = record
+        CakeI: integer;
+        CakePoints: array[0..Pred(cakeh)] of record
+            x, y: hwFloat;
+        end;
+    end;
+
+    PCakeData = ^TCakeData;
+
 implementation
 
 end.
--- a/hedgewars/uVariables.pas	Mon Feb 16 22:33:15 2015 +0300
+++ b/hedgewars/uVariables.pas	Thu Apr 02 21:09:56 2015 +0300
@@ -123,7 +123,11 @@
     isAudioMuted     : boolean;
 
     // originally typed consts
-    ExplosionBorderColor: LongWord;
+    ExplosionBorderColorR,
+    ExplosionBorderColorG,
+    ExplosionBorderColorB,
+    ExplosionBorderColorNoA,
+    ExplosionBorderColor:  LongWord;
     IceColor            : LongWord;
     IceEdgeColor        : LongWord;
     WaterOpacity: byte;
@@ -146,6 +150,7 @@
 
     cCaseFactor     : Longword;
     cLandMines      : Longword;
+    cAirMines       : Longword;
     cExplosives     : Longword;
 
     cScriptName     : shortstring;
@@ -213,6 +218,8 @@
     WorldDx: LongInt;
     WorldDy: LongInt;
 
+    SpeechHogNumber: LongInt;
+
     // for tracking the limits of the visible grid based on cScaleFactor
     ViewLeftX, ViewRightX, ViewBottomY, ViewTopY, ViewWidth, ViewHeight: LongInt;
 
@@ -233,6 +240,8 @@
 
     MaxTextureSize: LongInt;
 
+    ChatPasteBuffer: shortstring;
+
 /////////////////////////////////////
 //Buttons
 {$IFDEF USE_TOUCH_INTERFACE}
@@ -2532,7 +2541,11 @@
     SDWaterOpacity:= $80;
 
     SDTint:= $80;
+    ExplosionBorderColorR:= 80;
+    ExplosionBorderColorG:= 80;
+    ExplosionBorderColorB:= 80;
     ExplosionBorderColor:= $FF808080;
+    ExplosionBorderColorNoA:= ExplosionBorderColor and (not AMask);
     IceColor:= ($44 shl RShift) or ($97 shl GShift) or ($A9 shl BShift) or ($A0 shl AShift);
     IceEdgeColor:= ($8A shl RShift) or ($AF shl GShift) or ($B2 shl BShift) or ($FF shl AShift);
 
@@ -2602,6 +2615,7 @@
     AttackBar       := 0; // 0 - none, 1 - just bar at the right-down corner, 2 - from weapon
     cCaseFactor     := 5;  {0..9}
     cLandMines      := 4;
+    cAirMines       := 4;
     cExplosives     := 2;
 
     GameState       := Low(TGameState);
@@ -2666,6 +2680,7 @@
     if cFullscreenHeight = 0 then
         cFullscreenHeight:= min(cWindowedHeight, 480);
 
+    SpeechHogNumber:= -1;
 
     LuaGoals:= '';
     cMapName:= '';
@@ -2678,6 +2693,8 @@
     cStereoDepth:= 0;
     cViewLimitsDebug:= false;
     AprilOne := false;
+
+    ChatPasteBuffer:= '';
 end;
 
 procedure freeModule;
--- a/hedgewars/uVisualGears.pas	Mon Feb 16 22:33:15 2015 +0300
+++ b/hedgewars/uVisualGears.pas	Thu Apr 02 21:09:56 2015 +0300
@@ -288,6 +288,14 @@
                                     i:= 1;
                                 DrawTextureRotatedF(SpritesData[TSprite(Gear^.State)].Texture, Gear^.Scale, 0, 0, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, Gear^.Frame, i, SpritesData[TSprite(Gear^.State)].Width, SpritesData[TSprite(Gear^.State)].Height, Gear^.Angle);
                                 end;
+                   vgtFeather: begin
+                               if Gear^.FrameTicks < 255 then
+                                   begin
+                                   Tint($FF, $FF, $FF, Gear^.FrameTicks);
+                                   tinted:= true
+                                   end;
+                               DrawSpriteRotatedF(sprFeather, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, Gear^.Frame, 1, Gear^.Angle);
+                             end;
            end;
            if (cReducedQuality and rqAntiBoom) = 0 then
                case Gear^.Kind of
@@ -333,14 +341,6 @@
                                  end;
                              DrawSpriteRotatedF(sprShell, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, Gear^.Frame, 1, Gear^.Angle);
                              end;
-                   vgtFeather: begin
-                               if Gear^.FrameTicks < 255 then
-                                   begin
-                                   Tint($FF, $FF, $FF, Gear^.FrameTicks);
-                                   tinted:= true
-                                   end;
-                               DrawSpriteRotatedF(sprFeather, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, Gear^.Frame, 1, Gear^.Angle);
-                             end;
                    vgtEgg: DrawSpriteRotatedF(sprEgg, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, Gear^.Frame, 1, Gear^.Angle);
                    vgtBeeTrace: begin
                                 if Gear^.FrameTicks < $FF then
--- a/hedgewars/uVisualGearsHandlers.pas	Mon Feb 16 22:33:15 2015 +0300
+++ b/hedgewars/uVisualGearsHandlers.pas	Thu Apr 02 21:09:56 2015 +0300
@@ -515,7 +515,8 @@
 procedure doStepTeamHealthSorterWork(Gear: PVisualGear; Steps: Longword);
 var i, t, h: LongInt;
 begin
-for t:= 1 to min(Steps, Gear^.Timer) do
+if currsorter = Gear then
+  for t:= 1 to min(Steps, Gear^.Timer) do
     begin
     dec(Gear^.Timer);
     if (Gear^.Timer and 15) = 0 then
@@ -872,31 +873,47 @@
 end;
 
 ////////////////////////////////////////////////////////////////////////////////
+var
+    currwindbar: PVisualGear = nil;
+
+procedure doStepSmoothWindBarWork(Gear: PVisualGear; Steps: Longword);
+begin
+    if currwindbar = Gear then
+    begin
+    inc(Gear^.Timer, Steps);
+
+    while Gear^.Timer >= 10 do
+        begin
+        dec(Gear^.Timer, 10);
+        if WindBarWidth < Gear^.Tag then
+            inc(WindBarWidth)
+        else if WindBarWidth > Gear^.Tag then
+            dec(WindBarWidth);
+        end;
+    if cWindspeedf > Gear^.dAngle then
+        begin
+        cWindspeedf := cWindspeedf - Gear^.Angle*Steps;
+        if cWindspeedf < Gear^.dAngle then cWindspeedf:= Gear^.dAngle;
+        end
+    else if cWindspeedf < Gear^.dAngle then
+        begin
+        cWindspeedf := cWindspeedf + Gear^.Angle*Steps;
+        if cWindspeedf > Gear^.dAngle then cWindspeedf:= Gear^.dAngle;
+        end;
+    end;
+
+    if ((WindBarWidth = Gear^.Tag) and (cWindspeedf = Gear^.dAngle)) or (currwindbar <> Gear) then
+    begin
+        if currwindbar = Gear then currwindbar:= nil;
+        DeleteVisualGear(Gear)
+    end
+end;
+
 procedure doStepSmoothWindBar(Gear: PVisualGear; Steps: Longword);
 begin
-inc(Gear^.Timer, Steps);
-
-while Gear^.Timer >= 10 do
-    begin
-    dec(Gear^.Timer, 10);
-    if WindBarWidth < Gear^.Tag then
-        inc(WindBarWidth)
-    else if WindBarWidth > Gear^.Tag then
-        dec(WindBarWidth);
-    end;
-if cWindspeedf > Gear^.dAngle then
-    begin
-    cWindspeedf := cWindspeedf - Gear^.Angle*Steps;
-    if cWindspeedf < Gear^.dAngle then cWindspeedf:= Gear^.dAngle;
-    end
-else if cWindspeedf < Gear^.dAngle then
-    begin
-    cWindspeedf := cWindspeedf + Gear^.Angle*Steps;
-    if cWindspeedf > Gear^.dAngle then cWindspeedf:= Gear^.dAngle;
-    end;
-
-if (WindBarWidth = Gear^.Tag) and (cWindspeedf = Gear^.dAngle)  then
-    DeleteVisualGear(Gear)
+    currwindbar:= Gear;
+    Gear^.doStep:= @doStepSmoothWindBarWork;
+    doStepSmoothWindBarWork(Gear, Steps)
 end;
 ////////////////////////////////////////////////////////////////////////////////
 procedure doStepStraightShot(Gear: PVisualGear; Steps: Longword);
--- a/hedgewars/uVisualGearsList.pas	Mon Feb 16 22:33:15 2015 +0300
+++ b/hedgewars/uVisualGearsList.pas	Thu Apr 02 21:09:56 2015 +0300
@@ -78,6 +78,7 @@
     vgtSmokeTrace,
     vgtEvilTrace,
     vgtNote,
+    vgtFeather,
     vgtSmoothWindBar])) then
 
         exit;
@@ -399,6 +400,7 @@
     vgtSmallDamageTag,
     vgtHealthTag,
     vgtStraightShot,
+    vgtFeather,
     vgtChunk: gear^.Layer:= 3;
 
     // 2: this layer is outside the screen when stereo
@@ -409,7 +411,6 @@
     vgtSteam,
     vgtAmmo,
     vgtShell,
-    vgtFeather,
     vgtEgg,
     vgtBeeTrace,
     vgtSmokeRing,
--- a/hedgewars/uWorld.pas	Mon Feb 16 22:33:15 2015 +0300
+++ b/hedgewars/uWorld.pas	Thu Apr 02 21:09:56 2015 +0300
@@ -17,7 +17,6 @@
  *)
 
 {$INCLUDE "options.inc"}
-{$IF GLunit = GL}{$DEFINE GLunit:=GL,GLext}{$ENDIF}
 
 unit uWorld;
 interface
@@ -53,7 +52,6 @@
     , uVisualGears
     , uChat
     , uLandTexture
-    , GLunit
     , uVariables
     , uUtils
     , uTextures
@@ -857,154 +855,28 @@
     else
         ZoomValue:= zoom;
 
-    // Sky
-    glClear(GL_COLOR_BUFFER_BIT);
-    //glPushMatrix;
-    //glScalef(1.0, 1.0, 1.0);
-
     if (not isPaused) and (not isAFK) and (GameType <> gmtRecord) then
         MoveCamera;
 
     if cStereoMode = smNone then
         begin
-        glClear(GL_COLOR_BUFFER_BIT);
+        RenderClear();
         DrawWorldStereo(Lag, rmDefault)
         end
 {$IFDEF USE_S3D_RENDERING}
-    else if (cStereoMode = smHorizontal) or (cStereoMode = smVertical) then
+    else
         begin
-        // create left fb
-        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framel);
-        glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
+        // draw frame for left eye
+        RenderClear(rmLeftEye);
         DrawWorldStereo(Lag, rmLeftEye);
 
-        // create right fb
-        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framer);
-        glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
+        // draw frame for right eye
+        RenderClear(rmRightEye);
         DrawWorldStereo(0, rmRightEye);
-
-        // detatch drawing from fbs
-        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, defaultFrame);
-        glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
-        SetScale(cDefaultZoomLevel);
-
-        // draw left frame
-        glBindTexture(GL_TEXTURE_2D, texl);
-        glBegin(GL_QUADS);
-            if cStereoMode = smHorizontal then
-                begin
-                glTexCoord2f(0.0, 0.0);
-                glVertex2d(cScreenWidth / -2, cScreenHeight);
-                glTexCoord2f(1.0, 0.0);
-                glVertex2d(0, cScreenHeight);
-                glTexCoord2f(1.0, 1.0);
-                glVertex2d(0, 0);
-                glTexCoord2f(0.0, 1.0);
-                glVertex2d(cScreenWidth / -2, 0);
-                end
-            else
-                begin
-                glTexCoord2f(0.0, 0.0);
-                glVertex2d(cScreenWidth / -2, cScreenHeight / 2);
-                glTexCoord2f(1.0, 0.0);
-                glVertex2d(cScreenWidth / 2, cScreenHeight / 2);
-                glTexCoord2f(1.0, 1.0);
-                glVertex2d(cScreenWidth / 2, 0);
-                glTexCoord2f(0.0, 1.0);
-                glVertex2d(cScreenWidth / -2, 0);
-                end;
-        glEnd();
+        end;
+{$ENDIF}
 
-        // draw right frame
-        glBindTexture(GL_TEXTURE_2D, texr);
-        glBegin(GL_QUADS);
-            if cStereoMode = smHorizontal then
-                begin
-                glTexCoord2f(0.0, 0.0);
-                glVertex2d(0, cScreenHeight);
-                glTexCoord2f(1.0, 0.0);
-                glVertex2d(cScreenWidth / 2, cScreenHeight);
-                glTexCoord2f(1.0, 1.0);
-                glVertex2d(cScreenWidth / 2, 0);
-                glTexCoord2f(0.0, 1.0);
-                glVertex2d(0, 0);
-                end
-            else
-                begin
-                glTexCoord2f(0.0, 0.0);
-                glVertex2d(cScreenWidth / -2, cScreenHeight);
-                glTexCoord2f(1.0, 0.0);
-                glVertex2d(cScreenWidth / 2, cScreenHeight);
-                glTexCoord2f(1.0, 1.0);
-                glVertex2d(cScreenWidth / 2, cScreenHeight / 2);
-                glTexCoord2f(0.0, 1.0);
-                glVertex2d(cScreenWidth / -2, cScreenHeight / 2);
-                end;
-        glEnd();
-        SetScale(zoom);
-        end
-    else
-        begin
-        // clear scene
-        glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
-        glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
-        // draw left eye in red channel only
-        if cStereoMode = smGreenRed then
-            glColorMask(GL_FALSE, GL_TRUE, GL_FALSE, GL_TRUE)
-        else if cStereoMode = smBlueRed then
-            glColorMask(GL_FALSE, GL_FALSE, GL_TRUE, GL_TRUE)
-        else if cStereoMode = smCyanRed then
-            glColorMask(GL_FALSE, GL_TRUE, GL_TRUE, GL_TRUE)
-        else
-            glColorMask(GL_TRUE, GL_FALSE, GL_FALSE, GL_TRUE);
-        DrawWorldStereo(Lag, rmLeftEye);
-        // draw right eye in selected channel(s) only
-        if cStereoMode = smRedGreen then
-            glColorMask(GL_FALSE, GL_TRUE, GL_FALSE, GL_TRUE)
-        else if cStereoMode = smRedBlue then
-            glColorMask(GL_FALSE, GL_FALSE, GL_TRUE, GL_TRUE)
-        else if cStereoMode = smRedCyan then
-            glColorMask(GL_FALSE, GL_TRUE, GL_TRUE, GL_TRUE)
-        else
-            glColorMask(GL_TRUE, GL_FALSE, GL_FALSE, GL_TRUE);
-        DrawWorldStereo(Lag, rmRightEye);
-        end
-{$ENDIF}
-end;
-
-procedure ChangeDepth(rm: TRenderMode; d: GLfloat);
-var tmp: LongInt;
-begin
-{$IFNDEF USE_S3D_RENDERING}
-    rm:= rm; d:= d; tmp:= tmp; // avoid hint
-{$ELSE}
-    d:= d / 5;
-    if rm = rmDefault then
-        exit
-    else if rm = rmLeftEye then
-        d:= -d;
-    cStereoDepth:= cStereoDepth + d;
-    openglTranslProjMatrix(d, 0, 0);
-    tmp:= round(d / cScaleFactor * cScreenWidth);
-    ViewLeftX := ViewLeftX  - tmp;
-    ViewRightX:= ViewRightX - tmp;
-{$ENDIF}
-end;
-
-procedure ResetDepth(rm: TRenderMode);
-var tmp: LongInt;
-begin
-{$IFNDEF USE_S3D_RENDERING}
-    rm:= rm; tmp:= tmp; // avoid hint
-{$ELSE}
-    if rm = rmDefault then
-        exit;
-    openglTranslProjMatrix(-cStereoDepth, 0, 0);
-    tmp:= round(cStereoDepth / cScaleFactor * cScreenWidth);
-    ViewLeftX := ViewLeftX  + tmp;
-    ViewRightX:= ViewRightX + tmp;
-    cStereoDepth:= 0;
-{$ENDIF}
+FinishRender();
 end;
 
 procedure RenderWorldEdge;
@@ -1291,8 +1163,8 @@
     tdx, tdy: Double;
     s: shortstring;
     offsetX, offsetY, screenBottom: LongInt;
-    VertexBuffer: array [0..3] of TVertex2f;
     replicateToLeft, replicateToRight, tmp: boolean;
+    a: Byte;
 begin
 if WorldEdge <> weWrap then
     begin
@@ -1723,27 +1595,16 @@
             end;
     if ScreenFade <> sfNone then
         begin
+        r.x:= ViewLeftX;
+        r.y:= ViewTopY;
+        r.w:= ViewWidth;
+        r.h:= ViewHeight;
+
         case ScreenFade of
-            sfToBlack, sfFromBlack: Tint(0, 0, 0, ScreenFadeValue * 255 div 1000);
-            sfToWhite, sfFromWhite: Tint($FF, $FF, $FF, ScreenFadeValue * 255 div 1000);
+            sfToBlack, sfFromBlack: DrawRect(r, 0, 0, 0, ScreenFadeValue * 255 div 1000, true);
+            sfToWhite, sfFromWhite: DrawRect(r, $FF, $FF, $FF, ScreenFadeValue * 255 div 1000, true);
             end;
 
-        VertexBuffer[0].X:= -cScreenWidth;
-        VertexBuffer[0].Y:= cScreenHeight;
-        VertexBuffer[1].X:= -cScreenWidth;
-        VertexBuffer[1].Y:= 0;
-        VertexBuffer[2].X:= cScreenWidth;
-        VertexBuffer[2].Y:= 0;
-        VertexBuffer[3].X:= cScreenWidth;
-        VertexBuffer[3].Y:= cScreenHeight;
-
-        EnableTexture(false);
-
-        SetVertexPointer(@VertexBuffer[0], 4);
-        glDrawArrays(GL_TRIANGLE_FAN, 0, High(VertexBuffer) - Low(VertexBuffer) + 1);
-
-        EnableTexture(true);
-        untint;
         if not isFirstFrame and ((ScreenFadeValue = 0) or (ScreenFadeValue = sfMax)) then
             ScreenFade:= sfNone
         end
@@ -1764,15 +1625,11 @@
         end;
     DrawTexture( -(cScreenWidth shr 1) + 50, 20, recTexture);
 
+    //a:= Byte(Round(127*(1 + sin(RealTicks*0.007))));
+    a:= Byte(min(255, abs(-255 + ((RealTicks div 2) and 511))));
+
     // draw red circle
-    glDisable(GL_TEXTURE_2D);
-    Tint($FF, $00, $00, Byte(Round(127*(1 + sin(SDL_GetTicks()*0.007)))));
-    glBegin(GL_POLYGON);
-    for i:= 0 to 20 do
-        glVertex2f(-(cScreenWidth shr 1) + 30 + sin(i*2*Pi/20)*10, 35 + cos(i*2*Pi/20)*10);
-    glEnd();
-    untint;
-    glEnable(GL_TEXTURE_2D);
+    DrawCircleFilled(-(cScreenWidth shr 1) + 30, 35, 10, $FF, $00, $00, a);
     end;
 {$ENDIF}
 
@@ -1808,7 +1665,7 @@
         begin
         if not CurrentTeam^.ExtDriven then TargetCursorPoint:= CursorPoint;
         with CurrentHedgehog^ do
-            if (Gear <> nil) and ((Gear^.State and gstHHChooseTarget) <> 0) then
+            if (Gear <> nil) and ((Gear^.State and gstChooseTarget) <> 0) then
                 begin
             if (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);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/OfficialChallenges/racer_#13.hwmap	Thu Apr 02 21:09:56 2015 +0300
@@ -0,0 +1,1 @@
+AAAEC3icHZJ9aJZVGIevc859znme930etsRKEyllVlJr2AInGDXBBSWVfdHXlFFiq5CWH39U7gNCHVE6WrnSRIq0j01nW7KxaZEtwvFMSzYCl9MWKKnEYrI5puvZ+9/hwH39rt+5T1TjHybMcJjsjXoHJpKr+Dn2KG7KtOCV2oqZrdqZGmGiLX8uE+lJj7XlXdJjTF2S9C5PJgA32ZafcZP4s25d4rvdGXy5X4A77+/AbXHDuFVuA67GGdxp24lfadvx7XZ0eqIiN0Zwzr6WBL/Zvwi+cI8TPOcO4MfdFXy9vw0/wxfidvvZuCJ3AXvK7cJud0uwa+2v2FV2JXajnMZ+I29gr8pNuHLTjzthWvFl5lt8l+khWGCuEbwjjxF0yfcEg/bOXC7hqLyehP/IMGFinyXcY38hLHd3EUbuXYKDro+gzIf4fr8Iv9o/hLuQPp2r8iXYKT8T25iWtsVuJzLkSpFddghZa9cjyy3IYqlHlsoNyFNmN1Jj7ka6dQ821JXYl/Ut2ONqELdEHcQdUI34Bep9/GfqU4J81UmwWV1MTfU9hCW6jrBWnyXsMOnyhkw34X9yb64C8WG9OYm/1qPEdWYN8TLTS3RZbiXaJquJbpatZPfKR2QXyjYyHVJB5gmZT3jN9BEeMZWEjXqccMt0xoc6Q9ipmgjHVTGZFfxJ5hC7yc5jA9km1hDlUUVUx06i8wwQ368WElerD4j3q+vTLhtzQsR7dHES1+uviFeYPKJ/zfNE1aaWyJm3U5R5lGypniQb6QYyU3oO2blqP9kKtZzsMaWIFvM3UQsjxLPU7cSVqpr4Y3UpR0YWyWAi8+2TmDH7E+aQK8A846rQF90+9Cb3A+q660K95z5BFbgX4YQLoMF+DutsEbwiHVAry6DdDKAwb6EqzH2ok8ajy9Lfro8YwRSaIswOsx5zxiTITClBiqQlZ4DJV8cSPaZL0N/pJvQjuh/Vr4dRL+kfUaF+Ewa0g1Nqe5qh5qGepg/Vw5fopTSj9zGMHlEPTqPacjxMKFcSPWJfRTfb3tQlFVcn3SxUuR1DKdsMvbYUfpbjcFleQD0ggmpJe+gC8we6QUL0uXTTxsvvOR6+0Jcl0SZfhjpKaxLOoJX/Ac8c7Ww=
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/OfficialChallenges/racer_#14.hwmap	Thu Apr 02 21:09:56 2015 +0300
@@ -0,0 +1,1 @@
+AAAFlnicHZR3bNdlEMY/771371FLixoxKEKEQAgGwzA1SMJ0D6ZARY1SLTEgKEVGGOKIDGUjEJkBIYKADFFQ2RISLFVcjQXRGnBQLEJECUgUv/398+RyefO+994ztEgLsUt+FdI35JH/itxPwa+2g7yquIUGo9M89Lj0hTKWbKPMW9Ngdw5YkushN0rJNrnRxlJ4XQ6kJNcjdoy1ywtaxFr8F5Uq/1lb4if0PvxHHYYf1Vn4N7oZP6xf4fv1PL7DrsPXWxG+3Abgs20MPtkW4sNtGz7IsnN321m8XcrHb0itcEk9SDXpcdKRNIb0QZpFWpzWkCalT0hPpCOkbukk6eZ0gYQ7Vu2NsD3eClvmRdh4744N9J5YBy/GCv1J9LQ/gx70EegqL0Mn+Ri02Mei7X0cWj+r4m8+mvipjyQu9+HE8T6EOMCfILb3AcQCfwA57V2RQ94BecdbIK95Q6TUFbkr/Ym0SNVISocJNWk7oSKtJGxJ0wmL0kjCpFRMKE2dCQ+l5oTbUyI0sxpCgVXAP/Ye1NgcOGrPQ7n1ht3WFrZaIazVWlih5bBI18FcnQoztBSmaXeYok0ziJdhaqyC1+N2mBnfhPmxDBbHXrAqtoENMR+2Sw18Kp/Bl5JdVS3T4ZwMJYg8SGgotxJay9WELuE8YUCoIowIuwlTw2rCyvAGYWcYRfguPEb4K9yDXBvaI+1CE6RPyEdGcgmZRw2yjWNIJZ8jF9lHbMyHxK5sJD7NauI0lhE3soj4FfOJF5iDNmYW2q0OhjAbfYNMp5tZgH6baVIvshK7iXVYN7ZipezCpnEIe5dK7DAnsVrOkwpCJN0aGpIeCq1Jw0Jn0rTQj7QmDCXtDS+TjobFpPPhfTw/VODNwym8oyjeU5rhJdIFf0Eex1+TifgCWYq/LTvxzXIc3yn/4gdjU/yL2A2vjE/h38cp+E9xLX4ylmfWiGdy/shm0cIfUn3tQKqnA0lBx2MXdVk2qe7BftBqrEL/wz62Jtga64TNzExio+w5rNimYJ1sKdbYNqGXbQ96zD5HP7IqdKGdQEfZKbRX9m29xc6gyX4nnrDfiHvtJ+Iy+444IdtMHGS7iJ1sQ8aCvYVcsVeRkzYMKbe+GVGZOWWFNUJmaEbeBD2KDNePkBJdhDyio5F+2gfppW2QnloP6R1/Rh6O+5FH44rMAXEiUhYHIS/Fjhn78QZkjVxGPpEfka/lAFIr64kubxJbymTiXfIssVQeI06RnsS10oNYLncQ/5Db0GukHVokbdFB0h59UW5HV0pn9IDci/4i/bEkT2MtZTR2ZyZgGywrsImyHVsgR7D18ju2N+ZhX8csFE7E+7BzcSh2Oc4gxbiJlJfpLtWPf+c4yl7LiKqDuqp/uRbVram5Sjl7Mxp9uHQvz1uVKTGvii2VeVU+mAbjcsCWXI+rOqTqygbXB8ul7dm6tD2bf0SWVhTMtS5cOVWPff8D7Vs1fw==
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/OfficialChallenges/racer_#15.hwmap	Thu Apr 02 21:09:56 2015 +0300
@@ -0,0 +1,1 @@
+AAACYnicFc9NSJRRFAbgc3/PfP8lqGGLIlpIJVktlHAgXaSRLTLMQIQGwwhJF8UIUs7umHExvCn5CcWgiSFo0SBGUrjJjSAIFZWgQdmiggqjdd9sHs7qfc/rZ2UWwnasg6hS1IMzy7ZBn9BbAEfwEXAu6kBl2FNgGTZbcMpFZSEo5+0F+VLfKMiIbczrMbk2H7RpM8+kXs3zEdlR5BLoa6w77/TqK3nvuuzPh1/lmbxXxarzsCCHYr6i0zEe1Cux14k1RY4nHyRX0CLvx3Jc/IxTa6wpxlL+KQ4n4FsctfLGmLWIVstfiArL38GCVS3wzKppPmJTOXXYukdVifWW9CHr1fAd6+dg0uIyzFj+CmOry3DUph7iPevuxT7r7sc+iPZp3zKr7hjxRlUniE4jf8sfJnVA3zb4Tx4z6peIDS6xdeNe5OkiteB8hxnjZOSkcR/rdJFa8HNi0/hZ1WXCniQvNKLC+DmcMmIR75Ju1OeJN6sBYnk5TJAVVQQd0E+8DK4S32LTxFf5ZRLPYZzkEAyR/CAYiY8yR3BBPSD2Gp8UmQKOqEmcTVDncIpwFy4TliY7cRNHCf+oDCGpbUoNq8/kDKguwlviLekGUUp6B3LknCyyzrrJbU96vXqeJl+J0+ROiD5yG+Qe8rS6Sd4X1UthMy5SsJGkBF1ijsLdRQa5obCH/aXgPSsh/xTLUNCWjInmRCdFpJooWtZjFE7oQfoP92i1IA==
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/OfficialChallenges/racer_#16.hwmap	Thu Apr 02 21:09:56 2015 +0300
@@ -0,0 +1,1 @@
+AAAAaXicY3RgdGBgWcB2gIGDAcjiTgCy+A6AxVhlgFwgwcHA9ISBgYGRYb8AkLAXYGBmsGeAEKwgLphgB3GBBCPQvD8g82pA5tWAzKsBmVcDAB2iEO4=
\ No newline at end of file
--- a/project_files/hwc/rtl/system.c	Mon Feb 16 22:33:15 2015 +0300
+++ b/project_files/hwc/rtl/system.c	Thu Apr 02 21:09:56 2015 +0300
@@ -73,6 +73,46 @@
     return result;
 }
 
+void fpcrtl_insert__vars(string255 *src, string255 *dst, SizeInt index) {
+    int num_insert;
+    int num_shift;
+    int num_preshift;
+
+    // nothing to do if empty string is inserted or index invalid
+    if ((src->len == 0) || (index < 1) || (index > 255)) {
+        return;
+    }
+
+    num_insert = src->len;
+    // number of chars from start of destination string to end of insertion
+    num_preshift = index - 1 + num_insert;
+
+    // don't overflow on insert
+    if (num_preshift > 255) {
+        num_insert = 255 - (index - 1);
+        num_shift = 0;
+    }
+    // shift trailing chars
+    else {
+        // number of bytes to be shifted
+        num_shift = dst->len - (index - 1);
+
+        if (num_shift > 0) {
+            // don't overflow when shifting
+            if (num_shift + num_preshift > 255)
+                num_shift = 255 - num_preshift;
+
+            // time to move some bytes!
+            memmove(dst->str + num_preshift, dst->str + index - 1, num_shift);
+        }
+    }
+
+    // actual byte insertion
+    memmove(dst->str + index - 1, src->str, num_insert);
+    // store new length
+    dst->len = num_shift + num_preshift;
+}
+
 void __attribute__((overloadable)) fpcrtl_delete__vars(string255 *s, SizeInt index, SizeInt count) {
     // number of chars to be move
     int num_move;
@@ -298,7 +338,7 @@
 LongInt fpcrtl_random(LongInt l) {
     // random(0) is undefined in docs but effectively returns 0 in free pascal
     if (l == 0) {
-        printf("WARNING: random(0) called!");
+        printf("WARNING: random(0) called!\n");
         return 0;
     }
     return (LongInt) (rand() / (double) RAND_MAX * l);
--- a/project_files/hwc/rtl/system.h	Mon Feb 16 22:33:15 2015 +0300
+++ b/project_files/hwc/rtl/system.h	Thu Apr 02 21:09:56 2015 +0300
@@ -26,6 +26,13 @@
 astring     fpcrtl_copyA(astring s, Integer Index, Integer Count);
 
 /*
+ * Insert a shortstring in another at a specified index
+ */
+void        fpcrtl_insert__vars(string255 *src, string255 *dst, SizeInt index);
+#define     fpcrtl_insert(src, dst, index)                  fpcrtl_insert__vars(&(src), &(dst), index);
+#define     fpcrtl_Insert                                   fpcrtl_insert
+
+/*
  * Delete removes Count characters from string S, starting at position Index.
  * All characters after the deleted characters are shifted Count positions to the left,
  * and the length of the string is adjusted.
@@ -33,6 +40,7 @@
 #define     fpcrtl_delete(s, index, count)                  fpcrtl_delete__vars(&(s), index, count)
 void        __attribute__((overloadable))                   fpcrtl_delete__vars(string255 *s, SizeInt index, SizeInt count);
 void        __attribute__((overloadable))                   fpcrtl_delete__vars(astring *s, SizeInt index, SizeInt count);
+#define     fpcrtl_Delete                                   fpcrtl_delete
 
 string255   fpcrtl_floatToStr(double n);
 
--- a/qmlFrontend/CMakeLists.txt	Mon Feb 16 22:33:15 2015 +0300
+++ b/qmlFrontend/CMakeLists.txt	Thu Apr 02 21:09:56 2015 +0300
@@ -9,6 +9,8 @@
 
 find_package(Qt5 COMPONENTS Core Qml Quick Gui)
 
+qt5_add_resources(qresources qmlFrontend.qrc)
+
 add_executable(hedgewars WIN32
     main
     hwengine
@@ -16,6 +18,7 @@
     themeiconprovider
     qtquick2applicationviewer/qtquick2applicationviewer
     flib.h
+    ${qresources}
     )
 
 include_directories(${OPENGL_INCLUDE_DIR})
--- a/qmlFrontend/main.cpp	Mon Feb 16 22:33:15 2015 +0300
+++ b/qmlFrontend/main.cpp	Thu Apr 02 21:09:56 2015 +0300
@@ -13,12 +13,14 @@
 
     HWEngine::exposeToQML();
 
+    Q_INIT_RESOURCE(qmlFrontend);
+
     QtQuick2ApplicationViewer viewer;
 
     viewer.engine()->addImageProvider(QLatin1String("preview"), new PreviewImageProvider());
     viewer.engine()->addImageProvider(QLatin1String("theme"), new ThemeIconProvider());
 
-    viewer.setMainQmlFile(QStringLiteral("qml/qmlFrontend/main.qml"));
+    viewer.setSource(QUrl("qrc:/qml/qmlFrontend/main.qml"));
     viewer.showExpanded();
 
     return app.exec();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlFrontend/qmlFrontend.qrc	Thu Apr 02 21:09:56 2015 +0300
@@ -0,0 +1,10 @@
+<RCC>
+    <qresource prefix="/">
+        <file>qml/qmlFrontend/First.qml</file>
+        <file>qml/qmlFrontend/GameConfig.qml</file>
+        <file>qml/qmlFrontend/HWButton.qml</file>
+        <file>qml/qmlFrontend/HWComboBox.qml</file>
+        <file>qml/qmlFrontend/LocalGame.qml</file>
+        <file>qml/qmlFrontend/main.qml</file>
+    </qresource>
+</RCC>
--- a/share/hedgewars/Data/Maps/ClimbHome/map.lua	Mon Feb 16 22:33:15 2015 +0300
+++ b/share/hedgewars/Data/Maps/ClimbHome/map.lua	Thu Apr 02 21:09:56 2015 +0300
@@ -43,6 +43,10 @@
 local waterAccel = 0
 local delayHeight = 32000
 local delayTime = 0
+local airMineX = {}
+local airMineY = {}
+local airMine = {}
+local init = true
 
 function onParameters()
     parseParams()
@@ -96,6 +100,8 @@
         HH[gear] = 1
         totalHedgehogs = totalHedgehogs + 1
         teams[GetHogTeamName(gear)] = 1
+    elseif init and GetGearType(gear) == gtAirMine then
+        airMine[gear] = 1
     end
 end
 
@@ -144,6 +150,23 @@
 end
 
 function onNewTurn()
+    if init then
+        init = false
+        for a,i in pairs(airMine) do
+            x,y = GetGearPosition(a)
+            airMineX[a] = x
+            airMineY[a] = y
+        end
+    else
+        for a,i in pairs(airMine) do
+            local x,y = GetGearPosition(a)
+            if not x or airMineX[a] ~= x or airMineY[a] ~= y then
+                DeleteGear(a)
+                AddGear(airMineX[a],airMineY[a], gtAirMine, gsttmpFlag, 0, 0, 0)
+            end
+        end
+    end
+        
     ready = false
     startTime = GameTime
     --disable to preserve highest over multiple turns
@@ -220,7 +243,7 @@
 
 
 function onGameTick20()
-    local x,y;
+    local x,y
     if math.random(20) == 1 then AddVisualGear(2012,56,vgtSmoke,0,false) end
     if CurrentHedgehog == dummyHog and dummySkip ~= 0 and dummySkip < GameTime then
         ParseCommand("/skip")
--- a/share/hedgewars/Data/Scripts/Multiplayer/Balanced_Random_Weapon.cfg	Mon Feb 16 22:33:15 2015 +0300
+++ b/share/hedgewars/Data/Scripts/Multiplayer/Balanced_Random_Weapon.cfg	Thu Apr 02 21:09:56 2015 +0300
@@ -1,2 +1,2 @@
-Default
+*
 locked
--- a/share/hedgewars/Data/Scripts/Multiplayer/Capture_the_Flag.cfg	Mon Feb 16 22:33:15 2015 +0300
+++ b/share/hedgewars/Data/Scripts/Multiplayer/Capture_the_Flag.cfg	Thu Apr 02 21:09:56 2015 +0300
@@ -1,2 +1,2 @@
-Default
-Default
+*
+*
--- a/share/hedgewars/Data/Scripts/Multiplayer/DiagonalMaze.cfg	Mon Feb 16 22:33:15 2015 +0300
+++ b/share/hedgewars/Data/Scripts/Multiplayer/DiagonalMaze.cfg	Thu Apr 02 21:09:56 2015 +0300
@@ -1,2 +1,2 @@
-Default
-Default
+*
+*
--- a/share/hedgewars/Data/Scripts/Multiplayer/Gravity.cfg	Mon Feb 16 22:33:15 2015 +0300
+++ b/share/hedgewars/Data/Scripts/Multiplayer/Gravity.cfg	Thu Apr 02 21:09:56 2015 +0300
@@ -1,2 +1,2 @@
-Default
-Default
+*
+*
--- a/share/hedgewars/Data/Scripts/Multiplayer/Highlander.cfg	Mon Feb 16 22:33:15 2015 +0300
+++ b/share/hedgewars/Data/Scripts/Multiplayer/Highlander.cfg	Thu Apr 02 21:09:56 2015 +0300
@@ -1,2 +1,2 @@
-Default
+*
 Highlander
--- a/share/hedgewars/Data/Scripts/Multiplayer/Highlander.lua	Mon Feb 16 22:33:15 2015 +0300
+++ b/share/hedgewars/Data/Scripts/Multiplayer/Highlander.lua	Thu Apr 02 21:09:56 2015 +0300
@@ -110,7 +110,7 @@
 					[amRope]=true, [amParachute]=true, [amTeleport]=true, [amJetpack]=true,
 					[amInvulnerable]=true, [amLaserSight]=true, [amVampiric]=true,
 					[amLowGravity]=true, [amExtraDamage]=true, [amExtraTime]=true,
-					[amLandGun]=true, [amSwitch]=true, [amRubber]=true, [amIceGun]=true,
+					[amLandGun]=true, [amRubber]=true, [amIceGun]=true,
 					}
 
 local wepArray = {}
--- a/share/hedgewars/Data/Scripts/Multiplayer/Mutant.lua	Mon Feb 16 22:33:15 2015 +0300
+++ b/share/hedgewars/Data/Scripts/Multiplayer/Mutant.lua	Thu Apr 02 21:09:56 2015 +0300
@@ -21,6 +21,7 @@
 
 HedgewarsScriptLoad("/Scripts/Locale.lua")
 HedgewarsScriptLoad("/Scripts/Tracker.lua")
+HedgewarsScriptLoad("/Scripts/Params.lua")
 
 --[[
     MUTANT SCRIPT
@@ -399,7 +400,7 @@
 
 function setAIHints()
     for i = 0, #hhs do
-        if mutant == nil or hhs[i] == mutant or CurrentHedgehog == mutant then
+        if mutant == nil or hhs[i] == mutant or CurrentHedgehog == mutant or getGearValue(CurrentHedgehog, "Feeder") then
             SetGearAIHints(hhs[i], aihUsual)
         else
             SetGearAIHints(hhs[i], aihDoesntMatter)
@@ -607,6 +608,11 @@
     end
 end
 
+function onParameters()
+    parseParams()
+    winScore = tonumber(params["winscore"]) or winScore
+end
+
 --[[
 S T A R R I N G
     prof - Coding, implementing and evangelism
--- a/share/hedgewars/Data/Scripts/Multiplayer/No_Jumping.cfg	Mon Feb 16 22:33:15 2015 +0300
+++ b/share/hedgewars/Data/Scripts/Multiplayer/No_Jumping.cfg	Thu Apr 02 21:09:56 2015 +0300
@@ -1,2 +1,2 @@
-Default
-Default
+*
+*
--- a/share/hedgewars/Data/Scripts/Multiplayer/Racer.lua	Mon Feb 16 22:33:15 2015 +0300
+++ b/share/hedgewars/Data/Scripts/Multiplayer/Racer.lua	Thu Apr 02 21:09:56 2015 +0300
@@ -1,772 +1,785 @@
-
-------------------------------------------
--- RACER 0.6
--- map-independant racing script
--- by mikade
------------------------------------------
-
------------------------------------
---0.1: took all the code from crazy racer and scrapped most of it
------------------------------------
-
--- Removed tumbler system
--- Removed extra adds like boosters etc
--- Added experimental waypoint placement system
--- More user feedback
--- Reduced race complexity limit to 5 waypoints
--- stop placement at complexity limit reached and end turn
--- guys dont keep racing after dying
--- invulnerable feasibility
--- reverted time keeping method
--- reduced feedback display time
--- colour-coded addcaptions
--- cleaned up code
--- support for more players properly added
--- tardis fix
--- remove airstrikes
-
--- i think the remainder 0 .456 sec of the tracktime isnt getting reset on newturn
-
--- update feedback
-
--------
--- 0.2
--------
-
--- allow gameflags
--- extend time to 90s
--- remove other air-attack based weps
--- turn off water rise for sd
-
--------
--- 0.3
--------
-
--- prevent WP being placed in land
--- prevent waypoints being placed outside border
-
--------
--- 0.4
--------
-
--- update user feedback
--- add more sounds
-
--------
--- 0.5
--------
-
--- fix ghost disappearing if hog falls in water or somehow dies
--- lengthen ghost tracking interval to improve performance on slower machines
--- increase waypoint limit to 8
--- allow for persistent showmission information
-
--------
--- 0.6
--------
-
--- remove hogs from racing area as per request
-
--------
--- 0.7
--------
-
--- switch to first available weapon if starting race with no weapon selected
-
------------------------------
--- SCRIPT BEGINS
------------------------------
-
-HedgewarsScriptLoad("/Scripts/Locale.lua")
-HedgewarsScriptLoad("/Scripts/OfficialChallenges.lua")
-
-------------------
--- Got Variables?
-------------------
-
-local fMod = 1000000 -- 1
-local roundLimit = 3
-local roundNumber = 0
-local firstClan = 10
-
-local fastX = {}
-local fastY = {}
-local fastCount = 0
-local fastIndex = 0
-local fastColour
-
-local currX = {}
-local currY = {}
-local currCount = 0
-
-local specialPointsX = {}
-local specialPointsY = {}
-local specialPointsCount = 0
-
---------------------------
--- hog and team tracking variales
---------------------------
-
-local numhhs = 0 -- store number of hedgehogs
-local hhs = {} -- store hedgehog gears
-
-local numTeams --  store the number of teams in the game
-local teamNameArr = {}  -- store the list of teams
-local teamClan = {}
-local teamSize = {}     -- store how many hogs per team
-local teamIndex = {} -- at what point in the hhs{} does each team begin
-
-local teamComment = {}
-local teamScore = {}
-
--------
--- racer vars
---------
-
-local cGear = nil
-
-local bestClan = nil
-local bestTime = nil
-
-local gameBegun = false
-local gameOver = false
-local racerActive = false
-local trackTime = 0
-
-local wpCirc = {}
-local wpX = {}
-local wpY = {}
-local wpCol = {}
-local wpActive = {}
-local wpRad = 450 --75
-local wpCount = 0
-local wpLimit = 8
-
-local usedWeapons = {}
-
-local roundN
-local lastRound
-local RoundHasChanged
-
--------------------
--- general methods
--------------------
-
-function RebuildTeamInfo()
-
-
-        -- make a list of individual team names
-        for i = 0, (TeamsCount-1) do
-                teamNameArr[i] = " " -- = i
-                teamSize[i] = 0
-                teamIndex[i] = 0
-                teamScore[i] = 100000
-        end
-        numTeams = 0
-
-        for i = 0, (numhhs-1) do
-
-                z = 0
-                unfinished = true
-                while(unfinished == true) do
-
-                        newTeam = true
-                        tempHogTeamName = GetHogTeamName(hhs[i]) -- this is the new name
-
-                        if tempHogTeamName == teamNameArr[z] then
-                                newTeam = false
-                                unfinished = false
-                        end
-
-                        z = z + 1
-
-                        if z == TeamsCount then
-                                unfinished = false
-                                if newTeam == true then
-                                        teamNameArr[numTeams] = tempHogTeamName
-                                        numTeams = numTeams + 1
-                                end
-                        end
-
-                end
-
-        end
-
-        -- find out how many hogs per team, and the index of the first hog in hhs
-        for i = 0, (numTeams-1) do
-                for z = 0, (numhhs-1) do
-                        if GetHogTeamName(hhs[z]) == teamNameArr[i] then
-                                teamClan[i] = GetHogClan(hhs[z])
-                                if teamSize[i] == 0 then
-                                        teamIndex[i] = z -- should give starting index
-                                end
-                                teamSize[i] = teamSize[i] + 1
-                                --add a pointer so this hog appears at i in hhs
-                        end
-                end
-
-        end
-
-end
-
-
------------------
--- RACER METHODS
------------------
-
-function CheckWaypoints()
-
-        trackFinished = true
-
-        for i = 0, (wpCount-1) do
-
-                g1X, g1Y = GetGearPosition(CurrentHedgehog)
-                g2X, g2Y = wpX[i], wpY[i]
-
-                g1X = g1X - g2X
-                g1Y = g1Y - g2Y
-                dist = (g1X*g1X) + (g1Y*g1Y)
-
-                --if i == 0 then
-                --      AddCaption(dist .. "/" .. (wpRad*wpRad) )
-                --end
-
-                NR = (48/100*wpRad)/2
-
-                if dist < (NR*NR) then
-                --if dist < (wpRad*wpRad) then
-                        --AddCaption("howdy")
-                        wpActive[i] = true
-                        wpCol[i] = GetClanColor(GetHogClan(CurrentHedgehog)) -- new                             --GetClanColor(1)
-                        SetVisualGearValues(wpCirc[i], wpX[i], wpY[i], 20, 100, 1, 10, 0, wpRad, 5, wpCol[i])
-
-                        wpRem = 0
-                        for k = 0, (wpCount-1) do
-                                if wpActive[k] == false then
-                                        wpRem = wpRem + 1
-                                end
-                        end
-
-                        AddCaption(loc("Way-Points Remaining") .. ": " .. wpRem,0xffba00ff,capgrpAmmoinfo)
-
-                end
-
-                if wpActive[i] == false then
-                        trackFinished = false
-                end
-
-        end
-
-        return(trackFinished)
-
-end
-
-function AdjustScores()
-
-        if bestTime == nil then
-                bestTime = 100000
-                bestClan = 10
-                bestTimeComment = "N/A"
-        end
-
-        newScore = false
-
-        -- update this clan's time if the new track is better
-        for i = 0, (numTeams-1) do
-                if teamClan[i] == GetHogClan(CurrentHedgehog) then
-                        if trackTime < teamScore[i] then
-                                teamScore[i] = trackTime
-                                newScore = true
-                        else
-                                newScore = false
-                        end
-                end
-        end
-
-        --bestTime = 100000
-        --bestClan = 10
-
-        -- find the best time out of those so far
-        for i = 0, (numTeams-1) do
-                if teamScore[i] < bestTime then
-                        bestTime = teamScore[i]
-                        bestClan = teamClan[i]
-                end
-        end
-
-        if bestTime ~= 100000 then
-                bestTimeComment = (bestTime/1000) ..loc("s")
-        end
-
-        if newScore == true then
-                if trackTime == bestTime then -- best time of the race
-                        ShowMission(loc("RACER"),
-                        loc("TRACK COMPLETED"),
-                        loc("NEW RACE RECORD: ") .. (trackTime/1000) ..loc("s") .. "|" ..
-                        loc("WINNING TIME: ") .. bestTimeComment, 0, 4000)
-                        PlaySound(sndHomerun)
-                else    -- best time for the clan
-                        ShowMission(loc("RACER"),
-                        loc("TRACK COMPLETED"),
-                        loc("NEW CLAN RECORD: ") .. (trackTime/1000) ..loc("s") .. "|" ..
-                        loc("WINNING TIME: ") .. bestTimeComment, 4, 4000)
-                end
-        else -- not any kind of new score
-                ShowMission(loc("RACER"),
-                loc("TRACK COMPLETED"),
-                loc("TIME: ") .. (trackTime/1000) ..loc("s") .. "|" ..
-                loc("WINNING TIME: ") .. bestTimeComment, -amSkip, 4000)
-                PlaySound(sndHellish)
-        end
-
-
-        --------
-        --new
-        --------
-
-        if bestTime == trackTime then
-                --AddCaption("wooooooooooooooooooooooooooooo")
-
-                fastColour = GetClanColor(GetHogClan(CurrentHedgehog))
-
-                for i = 0, (currCount-1) do
-                        fastX[i] = currX[i]
-                        fastY[i] = currY[i]
-                end
-
-                fastCount = currCount
-                fastIndex = 0
-
-                --currCount = 0 -- is this needed?
-
-        else
-                currCount = 0
-                fastIndex = 0
-        end
-
-
-end
-
-function onNewRound()
-
-        roundNumber = roundNumber + 1
-
-        totalComment = ""
-        for i = 0, (TeamsCount-1) do
-                        if teamNameArr[i] ~= " " then                           -- teamScore[teamClan[i]]
-                                teamComment[i] = teamNameArr[i] .. ": " .. (teamScore[i]/1000) .. loc("s|")
-                                totalComment = totalComment .. teamComment[i]
-                        elseif teamNameArr[i] == " " then
-                                teamComment[i] = "|"
-                        end
-        end
-
-        ShowMission(    loc("RACER"),
-                                        loc("STATUS UPDATE"),
-                                        loc("Rounds Complete: ") .. roundNumber .. "/" .. roundLimit .. "|" .. " " .. "|" ..
-                                        loc("Best Team Times: ") .. "|" .. totalComment, 0, 4000)
-
-        -- end game if its at round limit
-        if roundNumber >= roundLimit then
-                for i = 0, (numhhs-1) do
-                        if GetHogClan(hhs[i]) ~= bestClan then
-                                SetEffect(hhs[i], heResurrectable, 0)
-                                SetHealth(hhs[i],0)
-                        end
-                end
-                gameOver = true
-                TurnTimeLeft = 1
-        end
-
-end
-
-function CheckForNewRound()
-
-        -------------
-        ------ new
-        -------------
-
-        --[[turnN = turnN + 1
-        if gameBegun == false then
-                if turnN == 2 then
-                        for i = 0, (numhhs-1) do
-                                if hhs[i] ~= nil then
-                                        SetEffect(hhs[i], heResurrectable, 0)
-                                        SetHealth(hhs[i],0)
-                                end
-                        end
-                        gameOver = true
-                        TurnTimeLeft = 1
-                end
-        else
-
-
-        end]]
-
-        --[[if roundBegun == true then
-
-                if RoundHasChanged == true then
-                        roundN = roundN + 1
-                        RoundHasChanged = false
-                        onNewRound()
-                end
-
-                if lastRound ~= TotalRounds then -- new round, but not really
-
-                        if RoundHasChanged == false then
-                                RoundHasChanged = true
-                        end
-
-                end
-
-                AddCaption("RoundN:" .. roundN .. "; " .. "TR: " .. TotalRounds)
-
-                lastRound = TotalRounds
-
-        end]]
-
-        ------------
-        ----- old
-        ------------
-
-        if GetHogClan(CurrentHedgehog) == firstClan then
-                onNewRound()
-        end
-
-end
-
-function DisableTumbler()
-        currCount = 0
-        fastIndex = 0
-        TurnTimeLeft = 0
-        racerActive = false -- newadd
-end
-
-function HandleGhost()
-
-        -- get the current xy of the racer at this point
-        currX[currCount] = GetX(CurrentHedgehog)
-        currY[currCount] = GetY(CurrentHedgehog)
-        currCount = currCount + 1
-
-        -- draw a ping of smoke where the fastest player was at this point
-        if (fastCount ~= 0) and (fastIndex < fastCount) then
-
-                fastIndex = fastIndex + 1
-
-                tempE = AddVisualGear(fastX[fastIndex], fastY[fastIndex], vgtSmoke, 0, false)
-                g1, g2, g3, g4, g5, g6, g7, g8, g9, g10 = GetVisualGearValues(tempE)
-                SetVisualGearValues(tempE, g1, g2, g3, g4, g5, g6, g7, g8, g9, fastColour )
-
-                --AddCaption("fC: " .. fastIndex .. " / " .. fastCount)
-
-        else
-
-                --AddCaption("excep fC: " .. fastIndex .. " / " .. fastCount)
-
-        end
-
-
-
-end
-
-function TryRepositionHogs()
-
-	if MapHasBorder() == true then
-
-		for i = 0, (numhhs-1) do
-			if hhs[i] ~= nil then
-				SetGearPosition(hhs[i],GetX(hhs[i]), TopY-10)
-			end
-		end
-
-	end
-
-end
-
-----------------------------------
--- GAME METHODS / EVENT HANDLERS
-----------------------------------
-
-function onGameInit()
-        EnableGameFlags(gfInfAttack, gfInvulnerable)
-        CaseFreq = 0
-        TurnTime = 90000
-        WaterRise = 0
-end
-
-
-function onGameStart()
-
-        roundN = 0
-        lastRound = TotalRounds
-        RoundHasChanged = false -- true
-
-        for i = 0, (specialPointsCount-1) do
-                PlaceWayPoint(specialPointsX[i], specialPointsY[i])
-        end
-
-        RebuildTeamInfo()
-
-        ShowMission     (
-                                loc("RACER"),
-                                loc("a Hedgewars mini-game"),
-
-                                loc("Build a track and race.") .. "|" ..
-                                loc("Round Limit:") .. " " .. roundLimit .. "|" ..
-
-                                "", 4, 4000
-                                )
-
-        TryRepositionHogs()
-
-end
-
-function PlaceWayPoint(x,y)
-    if not racerActive then
-        if wpCount == 0 or wpX[wpCount - 1] ~= x or wpY[wpCount - 1] ~= y then
-
-            wpX[wpCount] = x
-            wpY[wpCount] = y
-            wpCol[wpCount] = 0xffffffff
-            wpCirc[wpCount] = AddVisualGear(wpX[wpCount],wpY[wpCount],vgtCircle,0,true)
-                                                                                                                                            
-            SetVisualGearValues(wpCirc[wpCount], wpX[wpCount], wpY[wpCount], 20, 100, 1, 10, 0, wpRad, 5, wpCol[wpCount])
-
-            wpCount = wpCount + 1
-
-            AddCaption(loc("Waypoint placed.") .. " " .. loc("Available points remaining: ") .. (wpLimit-wpCount))
-        end
-    end
-end
-
-function onSpecialPoint(x,y,flag)
-    specialPointsX[specialPointsCount] = x
-    specialPointsY[specialPointsCount] = y
-    specialPointsCount = specialPointsCount + 1
-end
-
-function onNewTurn()
-
-        CheckForNewRound()
-        TryRepositionHogs()
-
-        racerActive = false
-
-        trackTime = 0
-
-        currCount = 0 -- hopefully this solves problem
-        AddAmmo(CurrentHedgehog, amAirAttack, 0)
-        gTimer = 0
-
-        -- Set the waypoints to unactive on new round
-        for i = 0,(wpCount-1) do
-                wpActive[i] = false
-                wpCol[i] = 0xffffffff
-                SetVisualGearValues(wpCirc[i], wpX[i], wpY[i], 20, 100, 1, 10, 0, wpRad, 5, wpCol[i])
-        end
-
-        -- Handle Starting Stage of Game
-        if (gameOver == false) and (gameBegun == false) then
-                if wpCount >= 3 then
-                        gameBegun = true
-                        roundNumber = 0
-                        firstClan = GetHogClan(CurrentHedgehog)
-                        ShowMission(loc("RACER"),
-                        loc("GAME BEGUN!!!"),
-                        loc("Complete the track as fast as you can!"), 2, 4000)
-                else
-                        ShowMission(loc("RACER"),
-                        loc("NOT ENOUGH WAYPOINTS"),
-                        loc("Place more waypoints using the 'Air Attack' weapon."), 2, 4000)
-                        AddAmmo(CurrentHedgehog, amAirAttack, 4000)
-                        SetWeapon(amAirAttack)
-                end
-        end
-
-        if gameOver == true then
-                gameBegun = false
-                racerActive = false -- newadd
-        end
-
-        AddAmmo(CurrentHedgehog, amTardis, 0)
-        AddAmmo(CurrentHedgehog, amDrillStrike, 0)
-        AddAmmo(CurrentHedgehog, amMineStrike, 0)
-        AddAmmo(CurrentHedgehog, amNapalm, 0)
-        AddAmmo(CurrentHedgehog, amPiano, 0)
-
-end
-
-function onGameTick20()
-
-        -- airstrike detected, convert this into a potential waypoint spot
-        if cGear ~= nil then
-                x,y = GetGearPosition(cGear)
-                if x > -9000 then
-                        x,y = GetGearTarget(cGear)
-
-
-                        if TestRectForObstacle(x-20, y-20, x+20, y+20, true) then
-                                AddCaption(loc("Please place the way-point in the open, within the map boundaries."))
-                                PlaySound(sndDenied)
-                        elseif (y > WaterLine-50) then
-                                AddCaption(loc("Please place the way-point further from the waterline."))
-                                PlaySound(sndDenied)
-                        else
-                                PlaceWayPoint(x, y)
-                                if wpCount == wpLimit then
-                                        AddCaption(loc("Race complexity limit reached."))
-                                        DisableTumbler()
-                                end
-                        end
-                else
-                        DeleteGear(cGear)
-                end
-        SetGearPosition(cGear, -10000, 0)
-        end
-
-
-        -- start the player tumbling with a boom once their turn has actually begun
-        if racerActive == false then
-
-                if (TurnTimeLeft > 0) and (TurnTimeLeft ~= TurnTime) then
-
-                        -- if the gamehas started put the player in the middle of the first
-                        --waypoint that was placed
-                        if gameBegun == true then
-                                AddCaption(loc("Good to go!"))
-                                racerActive = true
-                                trackTime = 0
-
-                                SetGearPosition(CurrentHedgehog, wpX[0], wpY[0])
-                                AddGear(GetX(CurrentHedgehog), GetY(CurrentHedgehog), gtGrenade, 0, 0, 0, 1)
-                                FollowGear(CurrentHedgehog)
-
-                                HideMission()
-
-                                -- don't start empty-handed
-                                if (GetCurAmmoType() == amNothing) then
-                                        SetNextWeapon()
-                                end
-                        else
-                                -- still in placement mode
-                        end
-
-                end
-        end
-
-
-        -- has the player started his tumbling spree?
-        if (CurrentHedgehog ~= nil) then
-
-                --airstrike conversion used to be here
-
-                -- if the RACE has started, show tracktimes and keep tabs on waypoints
-                if (racerActive == true) and (gameBegun == true) then
-
-                        --ghost
-                        if GameTime%40 == 0 then
-                                HandleGhost()
-                        end
-
-                        trackTime = trackTime + 20
-
-                        if GameTime%100 == 0 then
-
-                if trackTime%1000 == 0 then
-                    AddCaption((trackTime/1000)..'.0',GetClanColor(GetHogClan(CurrentHedgehog)),capgrpMessage2)
-                else
-                    AddCaption(trackTime/1000,GetClanColor(GetHogClan(CurrentHedgehog)),capgrpMessage2)
-                end
-
-                                if (CheckWaypoints() == true) then
-                                        AdjustScores()
-                                        DisableTumbler()
-                                end
-
-                        end
-
-                end
-
-                -- if the player has expended his tunbling time, stop him tumbling
-                if TurnTimeLeft <= 20 then
-                        DisableTumbler()
-                end
-
-        end
-
-end
-
-function onGearResurrect(gear)
-
-        AddVisualGear(GetX(gear), GetY(gear), vgtBigExplosion, 0, false)
-
-        if gear == CurrentHedgehog then
-                DisableTumbler()
-        end
-
-end
-
-function onGearAdd(gear)
-
-        if GetGearType(gear) == gtHedgehog then
-                hhs[numhhs] = gear
-                numhhs = numhhs + 1
-                SetEffect(gear, heResurrectable, 1)
-        end
-
-        if GetGearType(gear) == gtAirAttack then
-                cGear = gear
-        end
-
-end
-
-function onGearDelete(gear)
-
-        if GetGearType(gear) == gtAirAttack then
-                cGear = nil
-        end
-
-end
-
-function onAttack()
-    at = GetCurAmmoType()
-    
-    usedWeapons[at] = 0
-end
-
-function onAchievementsDeclaration()
-    usedWeapons[amSkip] = nil
-    
-    usedRope = usedWeapons[amRope] ~= nil
-    usedPortal = usedWeapons[amPortalGun] ~= nil
-    usedSaucer = usedWeapons[amJetpack] ~= nil
-    
-    usedWeapons[amRope] = nil
-    usedWeapons[amPortalGun] = nil
-    usedWeapons[amJetpack] = nil
-
-    usedOther = next(usedWeapons) ~= nil
-
-    if usedOther then -- smth besides skip, rope, portal or saucer used
-        raceType = "unknown race"
-    elseif usedRope and not usedPortal and not usedSaucer then
-        raceType = "rope race"
-    elseif not usedRope and usedPortal and not usedSaucer then
-        raceType = "portal race"
-    elseif not usedRope and not usedPortal and usedSaucer then
-        raceType = "saucer race"
-    elseif (usedRope or usedPortal or usedSaucer or usedOther) == false then -- no weapons used at all?
-        raceType = "no tools race"
-    else -- at least two of rope, portal and saucer used
-        raceType = "mixed race"
-    end
-
-    map = detectMap()
-    
-    for i = 0, (numTeams-1) do
-        if teamScore[i] < 100000 then
-            DeclareAchievement(raceType, teamNameArr[i], map, teamScore[i])
-        end
-    end
-end
+
+------------------------------------------
+-- RACER 0.6
+-- map-independant racing script
+-- by mikade
+-----------------------------------------
+
+-----------------------------------
+--0.1: took all the code from crazy racer and scrapped most of it
+-----------------------------------
+
+-- Removed tumbler system
+-- Removed extra adds like boosters etc
+-- Added experimental waypoint placement system
+-- More user feedback
+-- Reduced race complexity limit to 5 waypoints
+-- stop placement at complexity limit reached and end turn
+-- guys dont keep racing after dying
+-- invulnerable feasibility
+-- reverted time keeping method
+-- reduced feedback display time
+-- colour-coded addcaptions
+-- cleaned up code
+-- support for more players properly added
+-- tardis fix
+-- remove airstrikes
+
+-- i think the remainder 0 .456 sec of the tracktime isnt getting reset on newturn
+
+-- update feedback
+
+-------
+-- 0.2
+-------
+
+-- allow gameflags
+-- extend time to 90s
+-- remove other air-attack based weps
+-- turn off water rise for sd
+
+-------
+-- 0.3
+-------
+
+-- prevent WP being placed in land
+-- prevent waypoints being placed outside border
+
+-------
+-- 0.4
+-------
+
+-- update user feedback
+-- add more sounds
+
+-------
+-- 0.5
+-------
+
+-- fix ghost disappearing if hog falls in water or somehow dies
+-- lengthen ghost tracking interval to improve performance on slower machines
+-- increase waypoint limit to 8
+-- allow for persistent showmission information
+
+-------
+-- 0.6
+-------
+
+-- remove hogs from racing area as per request
+
+-------
+-- 0.7
+-------
+
+-- switch to first available weapon if starting race with no weapon selected
+
+-----------------------------
+-- SCRIPT BEGINS
+-----------------------------
+
+HedgewarsScriptLoad("/Scripts/Locale.lua")
+HedgewarsScriptLoad("/Scripts/OfficialChallenges.lua")
+HedgewarsScriptLoad("/Scripts/Params.lua")
+
+------------------
+-- Got Variables?
+------------------
+
+local fMod = 1000000 -- 1
+local roundLimit = 3
+local roundNumber = 0
+local firstClan = 10
+
+local fastX = {}
+local fastY = {}
+local fastCount = 0
+local fastIndex = 0
+local fastColour
+
+local currX = {}
+local currY = {}
+local currCount = 0
+
+local specialPointsX = {}
+local specialPointsY = {}
+local specialPointsCount = 0
+
+local TeamRope = false
+
+--------------------------
+-- hog and team tracking variales
+--------------------------
+
+local numhhs = 0 -- store number of hedgehogs
+local hhs = {} -- store hedgehog gears
+
+local numTeams --  store the number of teams in the game
+local teamNameArr = {}  -- store the list of teams
+local teamClan = {}
+local teamSize = {}     -- store how many hogs per team
+local teamIndex = {} -- at what point in the hhs{} does each team begin
+
+local teamComment = {}
+local teamScore = {}
+
+-------
+-- racer vars
+--------
+
+local cGear = nil
+
+local bestClan = nil
+local bestTime = nil
+
+local gameBegun = false
+local gameOver = false
+local racerActive = false
+local trackTime = 0
+
+local wpCirc = {}
+local wpX = {}
+local wpY = {}
+local wpCol = {}
+local wpActive = {}
+local wpRad = 450 --75
+local wpCount = 0
+local wpLimit = 8
+
+local usedWeapons = {}
+
+local roundN
+local lastRound
+local RoundHasChanged
+
+-------------------
+-- general methods
+-------------------
+
+function onParameters()
+    parseParams()
+    if params["teamrope"] ~= nil then
+        TeamRope = true
+    end
+end
+
+function RebuildTeamInfo()
+
+
+        -- make a list of individual team names
+        for i = 0, (TeamsCount-1) do
+                teamNameArr[i] = " " -- = i
+                teamSize[i] = 0
+                teamIndex[i] = 0
+                teamScore[i] = 100000
+        end
+        numTeams = 0
+
+        for i = 0, (numhhs-1) do
+
+                z = 0
+                unfinished = true
+                while(unfinished == true) do
+
+                        newTeam = true
+                        tempHogTeamName = GetHogTeamName(hhs[i]) -- this is the new name
+
+                        if tempHogTeamName == teamNameArr[z] then
+                                newTeam = false
+                                unfinished = false
+                        end
+
+                        z = z + 1
+
+                        if z == TeamsCount then
+                                unfinished = false
+                                if newTeam == true then
+                                        teamNameArr[numTeams] = tempHogTeamName
+                                        numTeams = numTeams + 1
+                                end
+                        end
+
+                end
+
+        end
+
+        -- find out how many hogs per team, and the index of the first hog in hhs
+        for i = 0, (numTeams-1) do
+                for z = 0, (numhhs-1) do
+                        if GetHogTeamName(hhs[z]) == teamNameArr[i] then
+                                teamClan[i] = GetHogClan(hhs[z])
+                                if teamSize[i] == 0 then
+                                        teamIndex[i] = z -- should give starting index
+                                end
+                                teamSize[i] = teamSize[i] + 1
+                                --add a pointer so this hog appears at i in hhs
+                        end
+                end
+
+        end
+
+end
+
+
+-----------------
+-- RACER METHODS
+-----------------
+
+function CheckWaypoints()
+
+        trackFinished = true
+
+        for i = 0, (wpCount-1) do
+
+                g1X, g1Y = GetGearPosition(CurrentHedgehog)
+                g2X, g2Y = wpX[i], wpY[i]
+
+                g1X = g1X - g2X
+                g1Y = g1Y - g2Y
+                dist = (g1X*g1X) + (g1Y*g1Y)
+
+                --if i == 0 then
+                --      AddCaption(dist .. "/" .. (wpRad*wpRad) )
+                --end
+
+                NR = (48/100*wpRad)/2
+
+                if dist < (NR*NR) then
+                --if dist < (wpRad*wpRad) then
+                        --AddCaption("howdy")
+                        wpActive[i] = true
+                        wpCol[i] = GetClanColor(GetHogClan(CurrentHedgehog)) -- new                             --GetClanColor(1)
+                        SetVisualGearValues(wpCirc[i], wpX[i], wpY[i], 20, 100, 1, 10, 0, wpRad, 5, wpCol[i])
+
+                        wpRem = 0
+                        for k = 0, (wpCount-1) do
+                                if wpActive[k] == false then
+                                        wpRem = wpRem + 1
+                                end
+                        end
+
+                        AddCaption(loc("Way-Points Remaining") .. ": " .. wpRem,0xffba00ff,capgrpAmmoinfo)
+
+                end
+
+                if wpActive[i] == false then
+                        trackFinished = false
+                end
+
+        end
+
+        return(trackFinished)
+
+end
+
+function AdjustScores()
+
+        if bestTime == nil then
+                bestTime = 100000
+                bestClan = 10
+                bestTimeComment = "N/A"
+        end
+
+        newScore = false
+
+        -- update this clan's time if the new track is better
+        for i = 0, (numTeams-1) do
+                if teamClan[i] == GetHogClan(CurrentHedgehog) then
+                        if trackTime < teamScore[i] then
+                                teamScore[i] = trackTime
+                                newScore = true
+                        else
+                                newScore = false
+                        end
+                end
+        end
+
+        --bestTime = 100000
+        --bestClan = 10
+
+        -- find the best time out of those so far
+        for i = 0, (numTeams-1) do
+                if teamScore[i] < bestTime then
+                        bestTime = teamScore[i]
+                        bestClan = teamClan[i]
+                end
+        end
+
+        if bestTime ~= 100000 then
+                bestTimeComment = (bestTime/1000) ..loc("s")
+        end
+
+        if newScore == true then
+                if trackTime == bestTime then -- best time of the race
+                        ShowMission(loc("RACER"),
+                        loc("TRACK COMPLETED"),
+                        loc("NEW RACE RECORD: ") .. (trackTime/1000) ..loc("s") .. "|" ..
+                        loc("WINNING TIME: ") .. bestTimeComment, 0, 4000)
+                        PlaySound(sndHomerun)
+                else    -- best time for the clan
+                        ShowMission(loc("RACER"),
+                        loc("TRACK COMPLETED"),
+                        loc("NEW CLAN RECORD: ") .. (trackTime/1000) ..loc("s") .. "|" ..
+                        loc("WINNING TIME: ") .. bestTimeComment, 4, 4000)
+                end
+        else -- not any kind of new score
+                ShowMission(loc("RACER"),
+                loc("TRACK COMPLETED"),
+                loc("TIME: ") .. (trackTime/1000) ..loc("s") .. "|" ..
+                loc("WINNING TIME: ") .. bestTimeComment, -amSkip, 4000)
+                PlaySound(sndHellish)
+        end
+
+
+        --------
+        --new
+        --------
+
+        if bestTime == trackTime then
+                --AddCaption("wooooooooooooooooooooooooooooo")
+
+                fastColour = GetClanColor(GetHogClan(CurrentHedgehog))
+
+                for i = 0, (currCount-1) do
+                        fastX[i] = currX[i]
+                        fastY[i] = currY[i]
+                end
+
+                fastCount = currCount
+                fastIndex = 0
+
+                --currCount = 0 -- is this needed?
+
+        else
+                currCount = 0
+                fastIndex = 0
+        end
+
+
+end
+
+function onNewRound()
+
+        roundNumber = roundNumber + 1
+
+        totalComment = ""
+        for i = 0, (TeamsCount-1) do
+                        if teamNameArr[i] ~= " " then                           -- teamScore[teamClan[i]]
+                                teamComment[i] = teamNameArr[i] .. ": " .. (teamScore[i]/1000) .. loc("s|")
+                                totalComment = totalComment .. teamComment[i]
+                        elseif teamNameArr[i] == " " then
+                                teamComment[i] = "|"
+                        end
+        end
+
+        ShowMission(    loc("RACER"),
+                                        loc("STATUS UPDATE"),
+                                        loc("Rounds Complete: ") .. roundNumber .. "/" .. roundLimit .. "|" .. " " .. "|" ..
+                                        loc("Best Team Times: ") .. "|" .. totalComment, 0, 4000)
+
+        -- end game if its at round limit
+        if roundNumber >= roundLimit then
+                for i = 0, (numhhs-1) do
+                        if GetHogClan(hhs[i]) ~= bestClan then
+                                SetEffect(hhs[i], heResurrectable, 0)
+                                SetHealth(hhs[i],0)
+                        end
+                end
+                gameOver = true
+                TurnTimeLeft = 1
+        end
+
+end
+
+function CheckForNewRound()
+
+        -------------
+        ------ new
+        -------------
+
+        --[[turnN = turnN + 1
+        if gameBegun == false then
+                if turnN == 2 then
+                        for i = 0, (numhhs-1) do
+                                if hhs[i] ~= nil then
+                                        SetEffect(hhs[i], heResurrectable, 0)
+                                        SetHealth(hhs[i],0)
+                                end
+                        end
+                        gameOver = true
+                        TurnTimeLeft = 1
+                end
+        else
+
+
+        end]]
+
+        --[[if roundBegun == true then
+
+                if RoundHasChanged == true then
+                        roundN = roundN + 1
+                        RoundHasChanged = false
+                        onNewRound()
+                end
+
+                if lastRound ~= TotalRounds then -- new round, but not really
+
+                        if RoundHasChanged == false then
+                                RoundHasChanged = true
+                        end
+
+                end
+
+                AddCaption("RoundN:" .. roundN .. "; " .. "TR: " .. TotalRounds)
+
+                lastRound = TotalRounds
+
+        end]]
+
+        ------------
+        ----- old
+        ------------
+
+        if GetHogClan(CurrentHedgehog) == firstClan then
+                onNewRound()
+        end
+
+end
+
+function DisableTumbler()
+        currCount = 0
+        fastIndex = 0
+        TurnTimeLeft = 0
+        racerActive = false -- newadd
+end
+
+function HandleGhost()
+
+        -- get the current xy of the racer at this point
+        currX[currCount] = GetX(CurrentHedgehog)
+        currY[currCount] = GetY(CurrentHedgehog)
+        currCount = currCount + 1
+
+        -- draw a ping of smoke where the fastest player was at this point
+        if (fastCount ~= 0) and (fastIndex < fastCount) then
+
+                fastIndex = fastIndex + 1
+
+                tempE = AddVisualGear(fastX[fastIndex], fastY[fastIndex], vgtSmoke, 0, false)
+                g1, g2, g3, g4, g5, g6, g7, g8, g9, g10 = GetVisualGearValues(tempE)
+                SetVisualGearValues(tempE, g1, g2, g3, g4, g5, g6, g7, g8, g9, fastColour )
+
+                --AddCaption("fC: " .. fastIndex .. " / " .. fastCount)
+
+        else
+
+                --AddCaption("excep fC: " .. fastIndex .. " / " .. fastCount)
+
+        end
+
+
+
+end
+
+function TryRepositionHogs()
+
+	if MapHasBorder() == true then
+
+		for i = 0, (numhhs-1) do
+			if hhs[i] ~= nil then
+				SetGearPosition(hhs[i],GetX(hhs[i]), TopY-10)
+			end
+		end
+
+	end
+
+end
+
+----------------------------------
+-- GAME METHODS / EVENT HANDLERS
+----------------------------------
+
+function onGameInit()
+        EnableGameFlags(gfInfAttack, gfInvulnerable)
+        CaseFreq = 0
+        TurnTime = 90000
+        WaterRise = 0
+end
+
+
+function onGameStart()
+
+        roundN = 0
+        lastRound = TotalRounds
+        RoundHasChanged = false -- true
+
+        for i = 0, (specialPointsCount-1) do
+                PlaceWayPoint(specialPointsX[i], specialPointsY[i])
+        end
+
+        RebuildTeamInfo()
+
+        ShowMission     (
+                                loc("RACER"),
+                                loc("a Hedgewars mini-game"),
+
+                                loc("Build a track and race.") .. "|" ..
+                                loc("Round Limit:") .. " " .. roundLimit .. "|" ..
+
+                                "", 4, 4000
+                                )
+
+        TryRepositionHogs()
+
+end
+
+function PlaceWayPoint(x,y)
+    if not racerActive then
+        if wpCount == 0 or wpX[wpCount - 1] ~= x or wpY[wpCount - 1] ~= y then
+
+            wpX[wpCount] = x
+            wpY[wpCount] = y
+            wpCol[wpCount] = 0xffffffff
+            wpCirc[wpCount] = AddVisualGear(wpX[wpCount],wpY[wpCount],vgtCircle,0,true)
+                                                                                                                                            
+            SetVisualGearValues(wpCirc[wpCount], wpX[wpCount], wpY[wpCount], 20, 100, 1, 10, 0, wpRad, 5, wpCol[wpCount])
+
+            wpCount = wpCount + 1
+
+            AddCaption(loc("Waypoint placed.") .. " " .. loc("Available points remaining: ") .. (wpLimit-wpCount))
+        end
+    end
+end
+
+function onSpecialPoint(x,y,flag)
+    specialPointsX[specialPointsCount] = x
+    specialPointsY[specialPointsCount] = y
+    specialPointsCount = specialPointsCount + 1
+end
+
+function onNewTurn()
+
+        CheckForNewRound()
+        TryRepositionHogs()
+
+        racerActive = false
+
+        trackTime = 0
+
+        currCount = 0 -- hopefully this solves problem
+        AddAmmo(CurrentHedgehog, amAirAttack, 0)
+        gTimer = 0
+
+        -- Set the waypoints to unactive on new round
+        for i = 0,(wpCount-1) do
+                wpActive[i] = false
+                wpCol[i] = 0xffffffff
+                SetVisualGearValues(wpCirc[i], wpX[i], wpY[i], 20, 100, 1, 10, 0, wpRad, 5, wpCol[i])
+        end
+
+        -- Handle Starting Stage of Game
+        if (gameOver == false) and (gameBegun == false) then
+                if wpCount >= 3 then
+                        gameBegun = true
+                        roundNumber = 0
+                        firstClan = GetHogClan(CurrentHedgehog)
+                        ShowMission(loc("RACER"),
+                        loc("GAME BEGUN!!!"),
+                        loc("Complete the track as fast as you can!"), 2, 4000)
+                else
+                        ShowMission(loc("RACER"),
+                        loc("NOT ENOUGH WAYPOINTS"),
+                        loc("Place more waypoints using the 'Air Attack' weapon."), 2, 4000)
+                        AddAmmo(CurrentHedgehog, amAirAttack, 4000)
+                        SetWeapon(amAirAttack)
+                end
+        end
+
+        if gameOver == true then
+                gameBegun = false
+                racerActive = false -- newadd
+        end
+
+        AddAmmo(CurrentHedgehog, amTardis, 0)
+        AddAmmo(CurrentHedgehog, amDrillStrike, 0)
+        AddAmmo(CurrentHedgehog, amMineStrike, 0)
+        AddAmmo(CurrentHedgehog, amNapalm, 0)
+        AddAmmo(CurrentHedgehog, amPiano, 0)
+
+end
+
+function onGameTick20()
+
+        -- airstrike detected, convert this into a potential waypoint spot
+        if cGear ~= nil then
+                x,y = GetGearPosition(cGear)
+                if x > -9000 then
+                        x,y = GetGearTarget(cGear)
+
+
+                        if TestRectForObstacle(x-20, y-20, x+20, y+20, true) then
+                                AddCaption(loc("Please place the way-point in the open, within the map boundaries."))
+                                PlaySound(sndDenied)
+                        elseif (y > WaterLine-50) then
+                                AddCaption(loc("Please place the way-point further from the waterline."))
+                                PlaySound(sndDenied)
+                        else
+                                PlaceWayPoint(x, y)
+                                if wpCount == wpLimit then
+                                        AddCaption(loc("Race complexity limit reached."))
+                                        DisableTumbler()
+                                end
+                        end
+                else
+                        DeleteGear(cGear)
+                end
+        SetGearPosition(cGear, -10000, 0)
+        end
+
+
+        -- start the player tumbling with a boom once their turn has actually begun
+        if racerActive == false then
+
+                if (TurnTimeLeft > 0) and (TurnTimeLeft ~= TurnTime) then
+
+                        -- if the gamehas started put the player in the middle of the first
+                        --waypoint that was placed
+                        if gameBegun == true then
+                                AddCaption(loc("Good to go!"))
+                                racerActive = true
+                                trackTime = 0
+
+                                SetGearPosition(CurrentHedgehog, wpX[0], wpY[0])
+                                AddGear(GetX(CurrentHedgehog), GetY(CurrentHedgehog), gtGrenade, 0, 0, 0, 1)
+                                FollowGear(CurrentHedgehog)
+
+                                HideMission()
+
+                                -- don't start empty-handed
+                                if (GetCurAmmoType() == amNothing) then
+                                        SetNextWeapon()
+                                end
+                        else
+                                -- still in placement mode
+                        end
+
+                end
+        end
+
+
+        -- has the player started his tumbling spree?
+        if (CurrentHedgehog ~= nil) then
+
+                --airstrike conversion used to be here
+
+                -- if the RACE has started, show tracktimes and keep tabs on waypoints
+                if (racerActive == true) and (gameBegun == true) then
+
+                        --ghost
+                        if GameTime%40 == 0 then
+                                HandleGhost()
+                        end
+
+                        trackTime = trackTime + 20
+
+                        if GameTime%100 == 0 then
+
+                if trackTime%1000 == 0 then
+                    AddCaption((trackTime/1000)..'.0',GetClanColor(GetHogClan(CurrentHedgehog)),capgrpMessage2)
+                else
+                    AddCaption(trackTime/1000,GetClanColor(GetHogClan(CurrentHedgehog)),capgrpMessage2)
+                end
+
+                                if (CheckWaypoints() == true) then
+                                        AdjustScores()
+                                        DisableTumbler()
+                                end
+
+                        end
+
+                end
+
+                -- if the player has expended his tunbling time, stop him tumbling
+                if TurnTimeLeft <= 20 then
+                        DisableTumbler()
+                end
+
+        end
+
+end
+
+function onGearResurrect(gear)
+
+        AddVisualGear(GetX(gear), GetY(gear), vgtBigExplosion, 0, false)
+
+        if gear == CurrentHedgehog then
+                DisableTumbler()
+        end
+
+end
+
+function onGearAdd(gear)
+
+        if GetGearType(gear) == gtHedgehog then
+                hhs[numhhs] = gear
+                numhhs = numhhs + 1
+                SetEffect(gear, heResurrectable, 1)
+        elseif GetGearType(gear) == gtAirAttack then
+                cGear = gear
+        elseif GetGearType(gear) == gtRope and TeamRope then
+            SetTag(gear,1)
+            SetGearValues(gear,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,GetClanColor(GetHogClan(CurrentHedgehog)))
+        elseif GetGearType(gear) == gtAirMine then
+            DeleteGear(gear)
+        end
+end
+
+function onGearDelete(gear)
+
+        if GetGearType(gear) == gtAirAttack then
+                cGear = nil
+        end
+
+end
+
+function onAttack()
+    at = GetCurAmmoType()
+    
+    usedWeapons[at] = 0
+end
+
+function onAchievementsDeclaration()
+    usedWeapons[amSkip] = nil
+    
+    usedRope = usedWeapons[amRope] ~= nil
+    usedPortal = usedWeapons[amPortalGun] ~= nil
+    usedSaucer = usedWeapons[amJetpack] ~= nil
+    
+    usedWeapons[amNothing] = nil
+    usedWeapons[amRope] = nil
+    usedWeapons[amPortalGun] = nil
+    usedWeapons[amJetpack] = nil
+
+    usedOther = next(usedWeapons) ~= nil
+
+    if usedOther then -- smth besides nothing, skip, rope, portal or saucer used
+        raceType = "unknown race"
+    elseif usedRope and not usedPortal and not usedSaucer then
+        raceType = "rope race"
+    elseif not usedRope and usedPortal and not usedSaucer then
+        raceType = "portal race"
+    elseif not usedRope and not usedPortal and usedSaucer then
+        raceType = "saucer race"
+    elseif (usedRope or usedPortal or usedSaucer or usedOther) == false then -- no weapons used at all?
+        raceType = "no tools race"
+    else -- at least two of rope, portal and saucer used
+        raceType = "mixed race"
+    end
+
+    map = detectMap()
+    
+    for i = 0, (numTeams-1) do
+        if teamScore[i] < 100000 then
+            DeclareAchievement(raceType, teamNameArr[i], map, teamScore[i])
+        end
+    end
+end
--- a/share/hedgewars/Data/Scripts/Multiplayer/Random_Weapon.cfg	Mon Feb 16 22:33:15 2015 +0300
+++ b/share/hedgewars/Data/Scripts/Multiplayer/Random_Weapon.cfg	Thu Apr 02 21:09:56 2015 +0300
@@ -1,2 +1,2 @@
-Default
+*
 locked
--- a/share/hedgewars/Data/Scripts/Multiplayer/ShoppaMap.cfg	Mon Feb 16 22:33:15 2015 +0300
+++ b/share/hedgewars/Data/Scripts/Multiplayer/ShoppaMap.cfg	Thu Apr 02 21:09:56 2015 +0300
@@ -1,2 +1,2 @@
-Default
-Default
+*
+*
--- a/share/hedgewars/Data/Scripts/Multiplayer/Space_Invasion.cfg	Mon Feb 16 22:33:15 2015 +0300
+++ b/share/hedgewars/Data/Scripts/Multiplayer/Space_Invasion.cfg	Thu Apr 02 21:09:56 2015 +0300
@@ -1,2 +1,2 @@
-Default
-Default
+*
+*
--- a/share/hedgewars/Data/Scripts/Multiplayer/The_Specialists.cfg	Mon Feb 16 22:33:15 2015 +0300
+++ b/share/hedgewars/Data/Scripts/Multiplayer/The_Specialists.cfg	Thu Apr 02 21:09:56 2015 +0300
@@ -1,2 +1,2 @@
-Default
-Default
+*
+locked
--- a/share/hedgewars/Data/Scripts/Multiplayer/Tumbler.cfg	Mon Feb 16 22:33:15 2015 +0300
+++ b/share/hedgewars/Data/Scripts/Multiplayer/Tumbler.cfg	Thu Apr 02 21:09:56 2015 +0300
@@ -1,2 +1,2 @@
-Default
-Default
+*
+*
--- a/share/hedgewars/Data/Scripts/Multiplayer/Tunnels.cfg	Mon Feb 16 22:33:15 2015 +0300
+++ b/share/hedgewars/Data/Scripts/Multiplayer/Tunnels.cfg	Thu Apr 02 21:09:56 2015 +0300
@@ -1,2 +1,2 @@
-Default
-Default
+*
+*
--- a/share/hedgewars/Data/Scripts/OfficialChallenges.lua	Mon Feb 16 22:33:15 2015 +0300
+++ b/share/hedgewars/Data/Scripts/OfficialChallenges.lua	Thu Apr 02 21:09:56 2015 +0300
@@ -20,6 +20,12 @@
                 return("Racer Challenge #11")
             elseif LandDigest == "M706743197Scripts/Multiplayer/Racer.lua" then
                 return("Racer Challenge #12")
+            elseif LandDigest == "M157242054Scripts/Multiplayer/Racer.lua" then
+                return("Racer Challenge #13")
+            elseif LandDigest == "M-1585582638Scripts/Multiplayer/Racer.lua" then
+                return("Racer Challenge #14")
+            elseif LandDigest == "M-528106034Scripts/Multiplayer/Racer.lua" then
+                return("Racer Challenge #16")
             end
 -- challenges without border
         elseif LandDigest == "M-134869715Scripts/Multiplayer/Racer.lua" then
@@ -28,6 +34,8 @@
             return("Racer Challenge #5")
         elseif LandDigest == "M479034891Scripts/Multiplayer/Racer.lua" then
             return("Racer Challenge #6")
+        elseif LandDigest == "M256715557Scripts/Multiplayer/Racer.lua" then
+            return("Racer Challenge #15")
         end
     end
 end
--- a/tools/hwmap.hs	Mon Feb 16 22:33:15 2015 +0300
+++ b/tools/hwmap.hs	Thu Apr 02 21:09:56 2015 +0300
@@ -52,7 +52,7 @@
     mapM_ putWord8 $ BW.unpack $ BL.toStrict $ Z.compress b
 
 mapString :: B.ByteString
-mapString = B.pack . Base64.encode . BW.unpack . BL.toStrict . compressWithLength . BL.drop 8 . encode $ drawnMap04
+mapString = B.pack . Base64.encode . BW.unpack . BL.toStrict . compressWithLength . BL.drop 8 . encode $ drawnMap05
 
 main = B.writeFile "out.hwmap" mapString
 
@@ -152,3 +152,13 @@
         ]
     l = Line Solid 0
     fm = flip' . mirror
+
+drawnMap05 = sp ++ fullFill ++ lW
+    where
+        w = 320
+        sh = 420
+        basePoints = [(w, w), (1024 + w `div` 2, 2048 - w), (2048, w), (3072 - w `div` 2, 2048 - w), (4096 - w, w)]
+        lW = [Line Erasing 60 basePoints]
+        sp = [SpecialPoints $ basePoints ++ [(1024 + w `div` 2, 2048 - w - sh), (3072 - w `div` 2, 2048 - w - sh), (2048, w + sh)]]
+
+fullFill = scale 256 $ [Line Solid 63 [(0, 1), (16, 1), (16, 3), (0, 3), (0, 5), (16, 5), (16, 7), (0, 7)]]