Merge default. This branch is up-to-date and code is working. qmlfrontend
authorunc0rr
Sun, 17 Dec 2017 00:09:24 +0100
branchqmlfrontend
changeset 12860 1b2b84315d27
parent 11848 01f88c3b7b66 (diff)
parent 12859 28cb18c5e712 (current diff)
child 12861 95d903b976d0
Merge default. This branch is up-to-date and code is working.
.hgignore
CMakeLists.txt
INSTALL
QTfrontend/net/tcpBase.cpp
QTfrontend/res/btnForts.png
QTfrontend/res/btnForts@2x.png
QTfrontend/res/campaign/A_Classic_Fairytale/journey.png
QTfrontend/res/campaign/A_Classic_Fairytale/shadow.png
QTfrontend/util/platform/Xfire Game SDK.url
QTfrontend/util/platform/xfire.cpp
QTfrontend/util/platform/xfire.h
QTfrontend/util/platform/xfire_license.txt
QTfrontend/util/platform/xfiregameclient.cpp
QTfrontend/util/platform/xfiregameclient.h
gameServer/OfficialServer/checker.hs
hedgewars/CMakeLists.txt
hedgewars/SDLh.pas
hedgewars/hwengine.pas
hedgewars/uIO.pas
hedgewars/uInputHandler.pas
hedgewars/uLocale.pas
hedgewars/uMisc.pas
hedgewars/uPhysFSLayer.pas
hedgewars/uRender.pas
hedgewars/uTypes.pas
hedgewars/uUtils.pas
hedgewars/uVariables.pas
misc/loc_gen.txt
project_files/hedgewars.kdevelop
qmlFrontend/hwengine.cpp
share/hedgewars/Data/Graphics/AmmoMenu/Ammos.png
share/hedgewars/Data/Graphics/AmmoMenu/Ammos_bw.png
share/hedgewars/Data/Graphics/Missions/Training/Basic_Training_-_Sniper_Rifle.png
share/hedgewars/Data/Graphics/Missions/Training/Basic_Training_-_Sniper_Rifle@2x.png
share/hedgewars/Data/Graphics/Missions/Training/Challenge_-_Speed_Shoppa_-_Hedgelove@2x.png
share/hedgewars/Data/Graphics/Missions/Training/Challenge_-_Speed_Shoppa_-_Ropes@2x.png
share/hedgewars/Data/Graphics/Missions/Training/Challenge_-_Speed_Shoppa_-_ShoppaKing@2x.png
share/hedgewars/Data/Graphics/Missions/Training/ClimbHome.png
share/hedgewars/Data/Graphics/Missions/Training/ClimbHome@2x.png
share/hedgewars/Data/Graphics/Missions/Training/Target_Practice_-_Bazooka_easy@2x.png
share/hedgewars/Data/Graphics/Missions/Training/Target_Practice_-_Bazooka_hard@2x.png
share/hedgewars/Data/Graphics/Missions/Training/Target_Practice_-_Cluster_Bomb@2x.png
share/hedgewars/Data/Graphics/Missions/Training/Target_Practice_-_Grenade_easy@2x.png
share/hedgewars/Data/Graphics/Missions/Training/Target_Practice_-_Grenade_hard@2x.png
share/hedgewars/Data/Graphics/Missions/Training/Target_Practice_-_Homing_Bee@2x.png
share/hedgewars/Data/Graphics/Missions/Training/Target_Practice_-_Shotgun@2x.png
share/hedgewars/Data/Graphics/Missions/Training/User_Mission_-_Bamboo_Thicket.png
share/hedgewars/Data/Graphics/Missions/Training/User_Mission_-_Bamboo_Thicket@2x.png
share/hedgewars/Data/Graphics/Missions/Training/User_Mission_-_Dangerous_Ducklings.png
share/hedgewars/Data/Graphics/Missions/Training/User_Mission_-_Dangerous_Ducklings@2x.png
share/hedgewars/Data/Graphics/Missions/Training/User_Mission_-_Diver.png
share/hedgewars/Data/Graphics/Missions/Training/User_Mission_-_Diver@2x.png
share/hedgewars/Data/Graphics/Missions/Training/User_Mission_-_Newton_and_the_Hammock.png
share/hedgewars/Data/Graphics/Missions/Training/User_Mission_-_Newton_and_the_Hammock@2x.png
share/hedgewars/Data/Graphics/Missions/Training/User_Mission_-_Nobody_Laugh.png
share/hedgewars/Data/Graphics/Missions/Training/User_Mission_-_Nobody_Laugh@2x.png
share/hedgewars/Data/Graphics/Missions/Training/User_Mission_-_RCPlane_Challenge.png
share/hedgewars/Data/Graphics/Missions/Training/User_Mission_-_RCPlane_Challenge@2x.png
share/hedgewars/Data/Graphics/Missions/Training/User_Mission_-_Rope_Knock_Challenge.png
share/hedgewars/Data/Graphics/Missions/Training/User_Mission_-_Rope_Knock_Challenge@2x.png
share/hedgewars/Data/Graphics/Missions/Training/User_Mission_-_Spooky_Tree.png
share/hedgewars/Data/Graphics/Missions/Training/User_Mission_-_Spooky_Tree@2x.png
share/hedgewars/Data/Graphics/Missions/Training/User_Mission_-_Teamwork.png
share/hedgewars/Data/Graphics/Missions/Training/User_Mission_-_Teamwork@2x.png
share/hedgewars/Data/Graphics/Missions/Training/User_Mission_-_That_Sinking_Feeling.png
share/hedgewars/Data/Graphics/Missions/Training/User_Mission_-_That_Sinking_Feeling@2x.png
share/hedgewars/Data/Graphics/Missions/Training/User_Mission_-_The_Great_Escape.png
share/hedgewars/Data/Graphics/Missions/Training/User_Mission_-_The_Great_Escape@2x.png
share/hedgewars/Data/Graphics/Missions/Training/portal@2x.png
share/hedgewars/Data/Locale/CMakeLists.txt
share/hedgewars/Data/Missions/Training/Basic_Training_-_Sniper_Rifle.lua
share/hedgewars/Data/Missions/Training/Challenge_-_Speed_Shoppa_-_Hedgelove.lua
share/hedgewars/Data/Missions/Training/Challenge_-_Speed_Shoppa_-_Ropes.lua
share/hedgewars/Data/Missions/Training/Challenge_-_Speed_Shoppa_-_ShoppaKing.lua
share/hedgewars/Data/Missions/Training/ClimbHome.lua
share/hedgewars/Data/Missions/Training/Target_Practice_-_Bazooka_easy.lua
share/hedgewars/Data/Missions/Training/Target_Practice_-_Bazooka_hard.lua
share/hedgewars/Data/Missions/Training/Target_Practice_-_Cluster_Bomb.lua
share/hedgewars/Data/Missions/Training/Target_Practice_-_Grenade_easy.lua
share/hedgewars/Data/Missions/Training/Target_Practice_-_Grenade_hard.lua
share/hedgewars/Data/Missions/Training/Target_Practice_-_Homing_Bee.lua
share/hedgewars/Data/Missions/Training/Target_Practice_-_Shotgun.lua
share/hedgewars/Data/Missions/Training/User_Mission_-_Bamboo_Thicket.lua
share/hedgewars/Data/Missions/Training/User_Mission_-_Dangerous_Ducklings.lua
share/hedgewars/Data/Missions/Training/User_Mission_-_Diver.lua
share/hedgewars/Data/Missions/Training/User_Mission_-_Newton_and_the_Hammock.lua
share/hedgewars/Data/Missions/Training/User_Mission_-_Nobody_Laugh.lua
share/hedgewars/Data/Missions/Training/User_Mission_-_RCPlane_Challenge.lua
share/hedgewars/Data/Missions/Training/User_Mission_-_Rope_Knock_Challenge.lua
share/hedgewars/Data/Missions/Training/User_Mission_-_Spooky_Tree.lua
share/hedgewars/Data/Missions/Training/User_Mission_-_Teamwork.lua
share/hedgewars/Data/Missions/Training/User_Mission_-_That_Sinking_Feeling.lua
share/hedgewars/Data/Missions/Training/User_Mission_-_The_Great_Escape.lua
share/hedgewars/Data/Missions/Training/portal.lua
share/hedgewars/Data/Names/Cowboy.cfg
share/hedgewars/Data/Names/Glasses.cfg
share/hedgewars/Data/Names/Hat.cfg
share/hedgewars/Data/Names/MegaHogX.cfg
share/hedgewars/Data/Names/NoHat.cfg
share/hedgewars/Data/Names/RobinHood.cfg
share/hedgewars/Data/Names/Santa.cfg
share/hedgewars/Data/Names/Sunglasses.cfg
share/hedgewars/Data/Names/beefeater.cfg
share/hedgewars/Data/Names/brainslug.txt
share/hedgewars/Data/Names/cap_blue.cfg
share/hedgewars/Data/Names/cap_green.cfg
share/hedgewars/Data/Names/cap_red.cfg
share/hedgewars/Data/Names/cap_yellow.cfg
share/hedgewars/Data/Names/cowboy.txt
share/hedgewars/Data/Names/cyborg1.cfg
share/hedgewars/Data/Names/hair_blue.cfg
share/hedgewars/Data/Names/hair_green.cfg
share/hedgewars/Data/Names/hair_grey.cfg
share/hedgewars/Data/Names/hair_orange.cfg
share/hedgewars/Data/Names/hair_pink.cfg
share/hedgewars/Data/Names/hair_purple.cfg
share/hedgewars/Data/Names/hair_red.cfg
share/hedgewars/Data/Names/hair_yellow.cfg
share/hedgewars/Data/Names/indian.txt
share/hedgewars/Data/Names/mv_Venom.cfg
share/hedgewars/Data/Names/ntd_Kirby.cfg
share/hedgewars/Data/Names/royalguard.cfg
share/hedgewars/Data/Names/sf_ryu.cfg
share/hedgewars/Data/Names/ushanka.cfg
share/hedgewars/Data/Names/zoo_Bunny.cfg
share/hedgewars/Data/Themes/Beach/SprayObject4.png
share/hedgewars/Data/Themes/Beach/horizont-lowres.png
--- a/CMakeLists.txt	Sat Dec 16 23:26:13 2017 +0100
+++ b/CMakeLists.txt	Sun Dec 17 00:09:24 2017 +0100
@@ -34,7 +34,7 @@
     option(LUA_SYSTEM "Use system lua (on)" ON)
 endif()
 
-option(BUILD_ENGINE_LIBRARY "Enable hwengine library (off)" OFF)
+set(BUILD_ENGINE_LIBRARY ON)
 option(ANDROID "Enable Android build (off)" OFF)
 
 option(MINIMAL_FLAGS "Respect system flags as much as possible (off)" OFF)
@@ -257,7 +257,7 @@
 else(ANDROID)
     #skip frontend for javascript
     if(NOT BUILD_ENGINE_JS)
-        add_subdirectory(QTfrontend)
+        add_subdirectory(qmlFrontend)
         add_subdirectory(share)
     endif()
 
--- a/QTfrontend/net/tcpBase.cpp	Sat Dec 16 23:26:13 2017 +0100
+++ b/QTfrontend/net/tcpBase.cpp	Sun Dec 17 00:09:24 2017 +0100
@@ -111,8 +111,6 @@
     m_connected(false),
     IPCSocket(0)
 {
-    process = 0;
-
     if(!IPCServer)
     {
         IPCServer = new QTcpServer(0);
--- a/hedgewars/ArgParsers.pas	Sat Dec 16 23:26:13 2017 +0100
+++ b/hedgewars/ArgParsers.pas	Sun Dec 17 00:09:24 2017 +0100
@@ -22,21 +22,24 @@
 interface
 
 procedure GetParams;
-{$IFDEF HWLIBRARY}
-var operatingsystem_parameter_argc: LongInt = 0; {$IFNDEF PAS2C}{$IFNDEF IPHONEOS}cdecl;{$ENDIF} export;{$ENDIF}
-    operatingsystem_parameter_argv: pointer = nil; {$IFNDEF PAS2C}{$IFNDEF IPHONEOS}cdecl;{$ENDIF} export;{$ENDIF}
-    operatingsystem_parameter_envp: pointer = nil; {$IFNDEF PAS2C}{$IFNDEF IPHONEOS}cdecl;{$ENDIF} export;{$ENDIF}
+
+{$IFNDEF BSD}
+var operatingsystem_parameter_argc: NativeInt; external;
+    operatingsystem_parameter_argv: pointer; external;
+    operatingsystem_parameter_envp: pointer; external;
+{$ELSE}
+var operatingsystem_parameter_argc: LongInt; export;
+    operatingsystem_parameter_argv: pointer; export;
+    operatingsystem_parameter_envp: pointer; export;
+{$ENDIF}
 
 function ParamCount: LongInt;
 function ParamStr(i: LongInt): shortstring;
-{$ENDIF}
 
 implementation
 uses uVariables, uTypes, uUtils, uSound, uConsts;
 var isInternal: Boolean;
 
-{$IFDEF HWLIBRARY}
-
 type PCharArray = array[0..255] of PChar;
      PPCharArray = ^PCharArray;
 
@@ -50,8 +53,6 @@
     ParamStr:= StrPas(PPCharArray(operatingsystem_parameter_argv)^[i])
 end;
 
-{$ENDIF}
-
 procedure GciEasterEgg;
 begin
     WriteLn(stdout, '                                                                ');
@@ -121,17 +122,6 @@
     SetVolume(0);
 end;
 
-procedure setIpcPort(port: LongInt; var wrongParameter:Boolean);
-begin
-    if isInternal then
-        ipcPort := port
-    else
-        begin
-        WriteLn(stderr, 'ERROR: use of --port is not allowed');
-        wrongParameter := true;
-        end
-end;
-
 function parseNick(nick: shortstring): shortstring;
 begin
     if isInternal then
@@ -215,12 +205,12 @@
       otherarray: array [0..2] of string = ('--locale','--fullscreen','--showfps');
       mediaarray: array [0..9] of string = ('--fullscreen-width', '--fullscreen-height', '--width', '--height', '--depth', '--volume','--nomusic','--nosound','--locale','--fullscreen');
       allarray: array [0..17] of string = ('--fullscreen-width','--fullscreen-height', '--width', '--height', '--depth','--volume','--nomusic','--nosound','--locale','--fullscreen','--showfps','--altdmg','--frame-interval','--low-quality','--no-teamtag','--no-hogtag','--no-healthtag','--translucent-tags');
-      reallyAll: array[0..35] of shortstring = (
-                '--prefix', '--user-prefix', '--locale', '--fullscreen-width', '--fullscreen-height', '--width',
+      reallyAll: array[0..32] of shortstring = (
+                '--locale', '--fullscreen-width', '--fullscreen-height', '--width',
                 '--height', '--frame-interval', '--volume','--nomusic', '--nosound',
                 '--fullscreen', '--showfps', '--altdmg', '--low-quality', '--raw-quality', '--stereo', '--nick',
   {deprecated}  '--depth', '--set-video', '--set-audio', '--set-other', '--set-multimedia', '--set-everything',
-  {internal}    '--internal', '--port', '--recorder', '--landpreview',
+  {internal}    '--internal', '--recorder', '--landpreview',
   {misc}        '--stats-only', '--gci', '--help','--no-teamtag','--no-hogtag','--no-healthtag','--translucent-tags','--lua-test');
 var cmdIndex: byte;
 begin
@@ -232,45 +222,42 @@
 
     while (cmdIndex <= High(reallyAll)) and (cmd <> reallyAll[cmdIndex]) do inc(cmdIndex);
     case cmdIndex of
-        {--prefix}               0 : PathPrefix        := getstringParameter (arg, paramIndex, parseParameter);
-        {--user-prefix}          1 : UserPathPrefix    := getstringParameter (arg, paramIndex, parseParameter);
-        {--locale}               2 : cLocaleFName      := getstringParameter (arg, paramIndex, parseParameter);
-        {--fullscreen-width}     3 : cFullscreenWidth  := max(getLongIntParameter(arg, paramIndex, parseParameter), cMinScreenWidth);
-        {--fullscreen-height}    4 : cFullscreenHeight := max(getLongIntParameter(arg, paramIndex, parseParameter), cMinScreenHeight);
-        {--width}                5 : cWindowedWidth    := max(2 * (getLongIntParameter(arg, paramIndex, parseParameter) div 2), cMinScreenWidth);
-        {--height}               6 : cWindowedHeight   := max(2 * (getLongIntParameter(arg, paramIndex, parseParameter) div 2), cMinScreenHeight);
-        {--frame-interval}       7 : cTimerInterval    := getLongIntParameter(arg, paramIndex, parseParameter);
-        {--volume}               8 : SetVolume          ( max(getLongIntParameter(arg, paramIndex, parseParameter), 0) );
-        {--nomusic}              9 : SetMusic           ( false );
-        {--nosound}             10 : SetSound           ( false );
-        {--fullscreen}          11 : cFullScreen       := true;
-        {--showfps}             12 : cShowFPS          := true;
-        {--altdmg}              13 : cAltDamage        := true;
-        {--low-quality}         14 : cReducedQuality   := $FFFFFFFF xor rqLowRes;
-        {--raw-quality}         15 : cReducedQuality   := getLongIntParameter(arg, paramIndex, parseParameter);
-        {--stereo}              16 : setStereoMode      ( getLongIntParameter(arg, paramIndex, parseParameter) );
-        {--nick}                17 : UserNick          := parseNick( getstringParameter(arg, paramIndex, parseParameter) );
+        {--locale}               0 : cLocaleFName      := getstringParameter (arg, paramIndex, parseParameter);
+        {--fullscreen-width}     1 : cFullscreenWidth  := max(getLongIntParameter(arg, paramIndex, parseParameter), cMinScreenWidth);
+        {--fullscreen-height}    2 : cFullscreenHeight := max(getLongIntParameter(arg, paramIndex, parseParameter), cMinScreenHeight);
+        {--width}                3 : cWindowedWidth    := max(2 * (getLongIntParameter(arg, paramIndex, parseParameter) div 2), cMinScreenWidth);
+        {--height}               4 : cWindowedHeight   := max(2 * (getLongIntParameter(arg, paramIndex, parseParameter) div 2), cMinScreenHeight);
+        {--frame-interval}       5 : cTimerInterval    := getLongIntParameter(arg, paramIndex, parseParameter);
+        {--volume}               6 : SetVolume          ( max(getLongIntParameter(arg, paramIndex, parseParameter), 0) );
+        {--nomusic}              7 : SetMusic           ( false );
+        {--nosound}              8 : SetSound           ( false );
+        {--fullscreen}           9 : cFullScreen       := true;
+        {--showfps}             10 : cShowFPS          := true;
+        {--altdmg}              11 : cAltDamage        := true;
+        {--low-quality}         12 : cReducedQuality   := $FFFFFFFF xor rqLowRes;
+        {--raw-quality}         13 : cReducedQuality   := getLongIntParameter(arg, paramIndex, parseParameter);
+        {--stereo}              14 : setStereoMode      ( getLongIntParameter(arg, paramIndex, parseParameter) );
+        {--nick}                15 : UserNick          := parseNick( getstringParameter(arg, paramIndex, parseParameter) );
         {deprecated options}
-        {--depth}               18 : setDepth(paramIndex);
-        {--set-video}           19 : parseClassicParameter(videoarray,5,paramIndex);
-        {--set-audio}           20 : parseClassicParameter(audioarray,3,paramIndex);
-        {--set-other}           21 : parseClassicParameter(otherarray,3,paramIndex);
-        {--set-multimedia}      22 : parseClassicParameter(mediaarray,10,paramIndex);
-        {--set-everything}      23 : parseClassicParameter(allarray,14,paramIndex);
+        {--depth}               16 : setDepth(paramIndex);
+        {--set-video}           17 : parseClassicParameter(videoarray,5,paramIndex);
+        {--set-audio}           18 : parseClassicParameter(audioarray,3,paramIndex);
+        {--set-other}           19 : parseClassicParameter(otherarray,3,paramIndex);
+        {--set-multimedia}      20 : parseClassicParameter(mediaarray,10,paramIndex);
+        {--set-everything}      21 : parseClassicParameter(allarray,14,paramIndex);
         {"internal" options}
-        {--internal}            24 : {$IFDEF HWLIBRARY}isInternal:= true{$ENDIF};
-        {--port}                25 : setIpcPort( getLongIntParameter(arg, paramIndex, parseParameter), parseParameter );
-        {--recorder}            26 : startVideoRecording(paramIndex);
-        {--landpreview}         27 : GameType := gmtLandPreview;
+        {--internal}            22 : {$IFDEF HWLIBRARY}isInternal:= true{$ENDIF};
+        {--recorder}            23 : startVideoRecording(paramIndex);
+        {--landpreview}         24 : GameType := gmtLandPreview;
         {anything else}
-        {--stats-only}          28 : statsOnlyGame();
-        {--gci}                 29 : GciEasterEgg();
-        {--help}                30 : DisplayUsage();
-        {--no-teamtag}          31 : cTagsMask := cTagsMask and (not htTeamName);
-        {--no-hogtag}           32 : cTagsMask := cTagsMask and (not htName);
-        {--no-healthtag}        33 : cTagsMask := cTagsMask and (not htHealth);
-        {--translucent-tags}    34 : cTagsMask := cTagsMask or htTransparent;
-        {--lua-test}            35 : begin cTestLua := true; SetSound(false); cScriptName := getstringParameter(arg, paramIndex, parseParameter); WriteLn(stdout, 'Lua test file specified: ' + cScriptName);end;
+        {--stats-only}          25 : statsOnlyGame();
+        {--gci}                 26 : GciEasterEgg();
+        {--help}                27 : DisplayUsage();
+        {--no-teamtag}          28 : cTagsMask := cTagsMask and (not htTeamName);
+        {--no-hogtag}           29 : cTagsMask := cTagsMask and (not htName);
+        {--no-healthtag}        30 : cTagsMask := cTagsMask and (not htHealth);
+        {--translucent-tags}    31 : cTagsMask := cTagsMask or htTransparent;
+        {--lua-test}            32: begin cTestLua := true; SetSound(false); cScriptName := getstringParameter(arg, paramIndex, parseParameter); WriteLn(stdout, 'Lua test file specified: ' + cScriptName);end;
     else
         begin
         //Assume the first "non parameter" is the replay file, anything else is invalid
@@ -332,12 +319,12 @@
     paramTotal: LongInt;
     index, nextIndex: LongInt;
     wrongParameter: boolean;
-//var tmpInt: LongInt;
+var tmpInt: LongInt;
 begin
 
     paramIndex:= {$IFDEF HWLIBRARY}0{$ELSE}1{$ENDIF};
     paramTotal:= ParamCount; //-1 because pascal enumeration is inclusive
-    (*
+    
     WriteLn(stdout, 'total parameters: ' + inttostr(paramTotal));
     tmpInt:= 0;
     while (tmpInt <= paramTotal) do
@@ -345,7 +332,7 @@
         WriteLn(stdout, inttostr(tmpInt) + ': ' + {$IFDEF HWLIBRARY}argv[tmpInt]{$ELSE}paramCount(tmpInt){$ENDIF});
         inc(tmpInt);
         end;
-    *)
+    
     wrongParameter:= false;
     while (paramIndex <= paramTotal) do
         begin
@@ -362,26 +349,29 @@
 
 procedure GetParams;
 begin
-    isInternal:= (ParamStr(1) = '--internal');
-
-    UserPathPrefix := _S'.';
-    PathPrefix     := cDefaultPathPrefix;
-    recordFileName := '';
-    parseCommandLine();
-
-    if (isInternal) and (ParamCount<=1) then
+    if ParamCount > 0 then
         begin
-        WriteLn(stderr, '--internal should not be manually used');
-        GameType := gmtSyntax;
-        end;
+        isInternal:= (ParamStr(1) = '--internal');
+
+        recordFileName := '';
+        parseCommandLine();
+
+        if (isInternal) and (ParamCount<=1) then
+            begin
+            WriteLn(stderr, '--internal should not be manually used');
+            GameType := gmtSyntax;
+            end;
 
-    if (not cTestLua) and (not isInternal) and (recordFileName = '') then
-        begin
-        WriteLn(stderr, 'You must specify a replay file');
-        GameType := gmtSyntax;
-        end
-    else if (recordFileName <> '') then
-        WriteLn(stdout, 'Attempting to play demo file "' + recordFilename + '"');
+        if (not cTestLua) and (not isInternal) and (recordFileName = '') then
+            begin
+            WriteLn(stderr, 'You must specify a replay file');
+            GameType := gmtSyntax;
+            end
+        else if (recordFileName <> '') then
+            WriteLn(stdout, 'Attempting to play demo file "' + recordFilename + '"');
+        end 
+    else
+        GameType:= gmtSyntax;
 
     if (GameType = gmtSyntax) then
         WriteLn(stderr, 'Please use --help to see possible arguments and their usage');
--- a/hedgewars/CMakeLists.txt	Sat Dec 16 23:26:13 2017 +0100
+++ b/hedgewars/CMakeLists.txt	Sun Dec 17 00:09:24 2017 +0100
@@ -106,6 +106,22 @@
     uGearsUtils.pas
     uTeams.pas
 
+    uFLAmmo.pas
+    uFLDrawnMap.pas
+    uFLGameConfig.pas
+    uFLIPC.pas
+    uFLNet.pas
+    uFLNetProtocol.pas
+    uFLNetTypes.pas
+    uFLRunQueue.pas
+    uFLScripts.pas
+    uFLSchemes.pas
+    uFLTeams.pas
+    uFLThemes.pas
+    uFLTypes.pas
+    uFLUICallback.pas
+    uFLUtils.pas
+
     #these interact with everything, so compile last
     uScript.pas
     )
--- a/hedgewars/SDLh.pas	Sat Dec 16 23:26:13 2017 +0100
+++ b/hedgewars/SDLh.pas	Sun Dec 17 00:09:24 2017 +0100
@@ -925,6 +925,8 @@
 
     PSDL_Thread = Pointer;
     PSDL_mutex = Pointer;
+    PSDL_sem = Pointer;
+    PSDL_cond = Pointer;
 
     TSDL_GLattr = (
         SDL_GL_RED_SIZE,
@@ -1151,13 +1153,30 @@
 function  SDL_CreateThread(fn: Pointer; name: PChar; data: Pointer): PSDL_Thread; cdecl; external SDLLibName;
 {$ENDIF}
 procedure SDL_WaitThread(thread: PSDL_Thread; status: PLongInt); cdecl; external SDLLibName;
-procedure SDL_KillThread(thread: PSDL_Thread); cdecl; external SDLLibName;
+procedure SDL_DetachThread(thread: PSDL_Thread); cdecl; external SDLLibName;
 
 function  SDL_CreateMutex: PSDL_mutex; cdecl; external SDLLibName;
 procedure SDL_DestroyMutex(mutex: PSDL_mutex); cdecl; external SDLLibName;
 function  SDL_LockMutex(mutex: PSDL_mutex): LongInt; cdecl; external SDLLibName;
 function  SDL_UnlockMutex(mutex: PSDL_mutex): LongInt; cdecl; external SDLLibName;
 
+function  SDL_CreateCond: PSDL_cond; cdecl; external SDLLibName;
+procedure SDL_DestroyCond(cond: PSDL_cond); cdecl; external SDLLibName;
+function  SDL_CondSignal(cond: PSDL_cond): LongInt; cdecl; external SDLLibName;
+function  SDL_CondBroadcast(cond: PSDL_cond): LongInt; cdecl; external SDLLibName;
+function  SDL_CondWait(cond: PSDL_cond; mut: PSDL_mutex): LongInt; cdecl; external SDLLibName;
+function  SDL_CondWaitTimeout(cond: PSDL_cond; mut: PSDL_mutex; ms: Longword): LongInt; cdecl; external SDLLibName;
+
+
+function SDL_CreateSemaphore(initial_value: Longword): PSDL_sem; cdecl; external SDLLibName;
+procedure SDL_DestroySemaphore(sem: PSDL_sem); cdecl; external SDLLibName;
+function SDL_SemWait(sem: PSDL_sem): LongInt; cdecl; external SDLLibName;
+function SDL_SemTryWait(sem: PSDL_sem): LongInt; cdecl; external SDLLibName;
+function SDL_SemWaitTimeout(sem: PSDL_sem; ms: Longword): LongInt; cdecl; external SDLLibName;
+function SDL_SemPost(sem: PSDL_sem): LongInt; cdecl; external SDLLibName;
+function SDL_SemValue(sem: PSDL_sem): Longword; cdecl; external SDLLibName;
+
+
 function  SDL_GL_SetAttribute(attr: TSDL_GLattr; value: LongInt): LongInt; cdecl; external SDLLibName;
 procedure SDL_GL_SwapBuffers; cdecl; external SDLLibName;
 
--- a/hedgewars/hwLibrary.pas	Sat Dec 16 23:26:13 2017 +0100
+++ b/hedgewars/hwLibrary.pas	Sun Dec 17 00:09:24 2017 +0100
@@ -29,65 +29,100 @@
 
 Library hwLibrary;
 
-uses hwengine, uTypes, uConsts, uVariables, uSound, uCommands, uUtils,
-     uLocale{$IFDEF ANDROID}, jni{$ENDIF};
+uses hwengine
+    , uTypes
+    , uConsts
+    , uVariables
+    , uSound
+    , uCommands
+    , uUtils
+    , uLocale
+    {$IFDEF ANDROID}, jni{$ENDIF}
+    , uFLTypes
+    , uFLGameConfig
+    , uFLIPC
+    , uPhysFSLayer
+    , uFLThemes
+    , uFLTeams
+    , uFLScripts
+    , uFLSchemes
+    , uFLAmmo
+    , uFLNet
+    , uFLNetProtocol
+    , uFLUICallback
+    , uFLRunQueue
+    ;
 
 {$INCLUDE "config.inc"}
 
 // retrieve protocol information
-procedure HW_versionInfo(netProto: PLongInt; versionStr: PPChar); cdecl; export;
+procedure HW_versionInfo(netProto: PLongInt; versionStr: PPChar); cdecl;
 begin
     netProto^:= cNetProtoVersion;
     versionStr^:= cVersionString;
 end;
 
-function HW_versionString: PChar; cdecl; export;
+function HW_versionString: PChar; cdecl;
 begin
     exit(cVersionString + '-r' + cRevisionString + ' (' + cHashString + ')');
 end;
 
 // equivalent to esc+y; when closeFrontend = true the game exits after memory cleanup
-procedure HW_terminate(closeFrontend: boolean); cdecl; export;
+procedure HW_terminate(closeFrontend: boolean); cdecl;
 begin
     closeFrontend:= closeFrontend; // avoid hint
     ParseCommand('forcequit', true);
 end;
 
-function HW_getWeaponNameByIndex(whichone: LongInt): PChar; cdecl; export;
+function HW_getWeaponNameByIndex(whichone: LongInt): PChar; cdecl;
 begin
     HW_getWeaponNameByIndex:= (str2pchar(trammo[Ammoz[TAmmoType(whichone+1)].NameId]));
 end;
 
-(*function HW_getWeaponCaptionByIndex(whichone: LongInt): PChar; cdecl; export;
+(*function HW_getWeaponCaptionByIndex(whichone: LongInt): PChar; cdecl;
 begin
     HW_getWeaponCaptionByIndex:= (str2pchar(trammoc[Ammoz[TAmmoType(whichone+1)].NameId]));
 end;
 
-function HW_getWeaponDescriptionByIndex(whichone: LongInt): PChar; cdecl; export;
+function HW_getWeaponDescriptionByIndex(whichone: LongInt): PChar; cdecl;
 begin
     HW_getWeaponDescriptionByIndex:= (str2pchar(trammod[Ammoz[TAmmoType(whichone+1)].NameId]));
 end;*)
 
-function HW_getNumberOfWeapons: LongInt; cdecl; export;
+function HW_getNumberOfWeapons: LongInt; cdecl;
 begin
     HW_getNumberOfWeapons:= ord(high(TAmmoType));
 end;
 
-function HW_getMaxNumberOfHogs: LongInt; cdecl; export;
+function HW_getMaxNumberOfHogs: LongInt; cdecl;
 begin
     HW_getMaxNumberOfHogs:= cMaxHHIndex + 1;
 end;
 
-function HW_getMaxNumberOfTeams: LongInt; cdecl; export;
+function HW_getMaxNumberOfTeams: LongInt; cdecl;
 begin
     HW_getMaxNumberOfTeams:= cMaxTeams;
 end;
 
-procedure HW_memoryWarningCallback; cdecl; export;
+procedure HW_memoryWarningCallback; cdecl;
 begin
     ReleaseSound(false);
 end;
 
+procedure flibInit(localPrefix, userPrefix: PChar); cdecl;
+begin
+    initIPC;
+    uPhysFSLayer.initModule(localPrefix, userPrefix);
+    uFLNet.initModule;
+end;
+
+procedure flibFree; cdecl;
+begin
+    uFLNet.freeModule;
+    uPhysFSLayer.freemodule;
+    freeIPC;
+end;
+
 {$IFDEF ANDROID}
 function JNI_HW_versionInfoNet(env: PJNIEnv; obj: JObject):JInt;cdecl;
 begin
@@ -119,6 +154,40 @@
     Game;
 {$ELSE}
 exports
+    runQuickGame,
+    runLocalGame,
+    getPreview,
+    registerUIMessagesCallback,
+    flibInit,
+    flibFree,
+    //game config
+    resetGameConfig,
+    setSeed,
+    getSeed,
+    setTheme,
+    setScript,
+    setScheme,
+    setAmmo,
+    getThemesList,
+    freeThemesList,
+    getThemeIcon,
+    getScriptsList,
+    getSchemesList,
+    getAmmosList,
+    getTeamsList,
+    tryAddTeam,
+    tryRemoveTeam,
+    changeTeamColor,
+    // network
+    connectOfficialServer,
+    passNetData,
+    passToNet,
+    passFlibEvent,
+    sendChatLine,
+    joinRoom,
+    partRoom,
+
+    // dunno what these are
     RunEngine,
     LoadLocaleWrapper,
     HW_versionInfo,
--- a/hedgewars/hwengine.pas	Sat Dec 16 23:26:13 2017 +0100
+++ b/hedgewars/hwengine.pas	Sun Dec 17 00:09:24 2017 +0100
@@ -22,12 +22,8 @@
 {$R res/hwengine.rc}
 {$ENDIF}
 
-{$IFDEF HWLIBRARY}
 unit hwengine;
 interface
-{$ELSE}
-program hwengine;
-{$ENDIF}
 
 uses {$IFDEF IPHONEOS}cmem, {$ENDIF} SDLh, uMisc, uConsole, uGame, uConsts, uLand, uAmmos, uVisualGears, uGears, uStore, uWorld, uInputHandler
      , uSound, uScript, uTeams, uStats, uIO, uLocale, uChat, uAI, uAIMisc, uAILandMarks, uLandTexture, uCollisions
@@ -39,19 +35,13 @@
      {$IFDEF WIN32}, dynlibs{$ENDIF}
      ;
 
-{$IFDEF HWLIBRARY}
-function RunEngine(argc: LongInt; argv: PPChar): LongInt; cdecl; export;
-
+function  RunEngine(argc: LongInt; argv: PPChar): Longint; cdecl; export;
 procedure preInitEverything();
 procedure initEverything(complete:boolean);
 procedure freeEverything(complete:boolean);
 
 implementation
-{$ELSE}
-procedure preInitEverything(); forward;
-procedure initEverything(complete:boolean); forward;
-procedure freeEverything(complete:boolean); forward;
-{$ENDIF}
+uses uFLUICallback, uFLTypes;
 
 {$IFDEF WIN32}
 type TSetProcessDpiAwareness = function(value: Integer): Integer; stdcall;
@@ -353,8 +343,8 @@
 begin
     WriteLnToConsole('Hedgewars engine ' + cVersionString + '-r' + cRevisionString +
                      ' (' + cHashString + ') with protocol #' + inttostr(cNetProtoVersion));
-    AddFileLog('Prefix: "' + shortstring(PathPrefix) +'"');
-    AddFileLog('UserPrefix: "' + shortstring(UserPathPrefix) +'"');
+    //AddFileLog('Prefix: "' + shortstring(PathPrefix) +'"');
+    //AddFileLog('UserPrefix: "' + shortstring(UserPathPrefix) +'"');
 
     for i:= 0 to ParamCount do
         AddFileLog(inttostr(i) + ': ' + ParamStr(i));
@@ -417,7 +407,6 @@
         begin
         if recordFileName = '' then
             begin
-            InitIPC;
             SendIPCAndWaitReply(_S'C');        // ask for game config
             end
         else
@@ -456,6 +445,7 @@
 procedure Game;
 begin
     initEverything(true);
+    SendIPC('TG');
     GameRoutine;
     // clean up all the memory allocated
     freeEverything(true);
@@ -475,12 +465,6 @@
 
 procedure initEverything (complete:boolean);
 begin
-    PathPrefix:= PathPrefix + #0;
-    UserPathPrefix:= UserPathPrefix + #0;
-    uPhysFSLayer.initModule(@PathPrefix[1], @UserPathPrefix[1]);
-    PathPrefix:= copy(PathPrefix, 1, length(PathPrefix) - 1);
-    UserPathPrefix:= copy(UserPathPrefix, 1, length(UserPathPrefix) - 1);
-
     uUtils.initModule(complete);    // opens the debug file, must be the first
     uVariables.initModule;          // inits all global variables
     uCommands.initModule;           // helps below
@@ -491,6 +475,7 @@
     uIO.initModule;                 // sets up sockets
 
     uScript.initModule;
+    uTeams.initModule;              // clear CurrentTeam variable
 
     if complete then
     begin
@@ -514,7 +499,6 @@
         uStats.initModule;
         uStore.initModule;
         uRender.initModule;
-        uTeams.initModule;
         uVisualGears.initModule;
         uVisualGearsHandlers.initModule;
         uWorld.initModule;
@@ -561,7 +545,6 @@
     uCommands.freeModule;
     uVariables.freeModule;
     uUtils.freeModule;              // closes debug file
-    uPhysFSLayer.freeModule;
     uScript.freeModule;
 end;
 
@@ -575,7 +558,6 @@
 begin
     initEverything(false);
 
-    InitIPC;
     if allOK then
     begin
         IPCWaitPongEvent;
@@ -588,6 +570,7 @@
         GenPreviewAlpha(Preview);
     {$ENDIF}
         WriteLnToConsole('Sending preview...');
+        SendIPC('TP');
         SendIPCRaw(@Preview, sizeof(Preview));
         SendIPCRaw(@MaxHedgehogs, sizeof(byte));
         WriteLnToConsole('Preview sent, disconnect');
@@ -596,14 +579,24 @@
     freeEverything(false);
 end;
 
-{$IFDEF HWLIBRARY}
-function RunEngine(argc: LongInt; argv: PPChar): LongInt; cdecl; export;
+function EngineThread(p: pointer): Longint; cdecl; export;
+var e: TFLIBEvent;
+begin
+    if GameType = gmtLandPreview then
+        GenLandPreview()
+    else Game();
+
+    e:= flibGameFinished;
+    sendUI(mtFlibEvent, @e, sizeof(e));
+    EngineThread:= 0
+end;
+
+
+function RunEngine(argc: LongInt; argv: PPChar): Longint; cdecl; export;
+var t: PSDL_Thread;
 begin
     operatingsystem_parameter_argc:= argc;
     operatingsystem_parameter_argv:= argv;
-{$ELSE}
-begin
-{$ENDIF}
 
 {$IFDEF WIN32}
     ShcoreLibHandle := LoadLibrary('Shcore.dll');
@@ -615,10 +608,6 @@
             SetProcessDpiAwareness(1);
     end;
 {$ENDIF}
-
-///////////////////////////////////////////////////////////////////////////////
-/////////////////////////////////// m a i n ///////////////////////////////////
-///////////////////////////////////////////////////////////////////////////////
 {$IFDEF PAS2C}
     // workaround for pascal's ParamStr and ParamCount
     init(argc, argv);
@@ -627,40 +616,14 @@
 
     GetParams();
 
-    if GameType = gmtLandPreview then
-        GenLandPreview()
-    else if GameType <> gmtSyntax then
-        Game();
-
-    // return 1 when engine is not called correctly
     if GameType = gmtSyntax then
-        {$IFDEF PAS2C}
-        exit(HaltUsageError);
-        {$ELSE}
-        halt(HaltUsageError);
-        {$ENDIF}
-
-    if cTestLua then
-        begin
-        WriteLnToConsole(errmsgLuaTestTerm);
-        {$IFDEF PAS2C}
-        exit(HaltTestUnexpected);
-        {$ELSE}
-        halt(HaltTestUnexpected);
-        {$ENDIF}
-        end;
-
-    {$IFDEF PAS2C}
-        exit(HaltNoError);
-    {$ELSE}
-        {$IFDEF IPHONEOS}
-            exit(HaltNoError);
-        {$ELSE}
-            halt(HaltNoError);
-        {$ENDIF}
-    {$ENDIF}
-{$IFDEF HWLIBRARY}
+        RunEngine:= HaltUsageError
+    else
+    begin
+        t:= SDL_CreateThread(@EngineThread, 'engine', nil);
+        SDL_DetachThread(t);
+        RunEngine:= 0
+    end
 end;
-{$ENDIF}
 
 end.
--- a/hedgewars/uDebug.pas	Sat Dec 16 23:26:13 2017 +0100
+++ b/hedgewars/uDebug.pas	Sun Dec 17 00:09:24 2017 +0100
@@ -37,12 +37,7 @@
 begin
 WriteLnToConsole(Msg);
 if isFatalError then
-    begin
     ParseCommand('fatal ' + lastConsoleline, true);
-    // hint for the 'coverity' source analyzer
-    // this halt is never actually reached because ParseCommands will halt first
-    halt(HaltFatalError);
-    end;
 end;
 
 procedure TryDo(Assert: boolean; Msg: shortstring; isFatal: boolean);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hedgewars/uFLAmmo.pas	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,136 @@
+unit uFLAmmo;
+interface
+uses uFLTypes;
+
+function getAmmosList: PPChar; cdecl;
+procedure freeAmmosList;
+
+function ammoByName(s: shortstring): PAmmo;
+procedure sendAmmoConfig(var ammo: TAmmo);
+
+implementation
+uses uFLUtils, uFLIPC, uPhysFSLayer, uFLThemes;
+
+const MAX_AMMO_NAMES = 64;
+type
+    TAmmoArray = array [0..0] of TAmmo;
+    PAmmoArray = ^TAmmoArray;
+var
+    ammoList: PAmmo;
+    ammoNumber: LongInt;
+    listOfAmmoNames: array[0..MAX_AMMO_NAMES] of PChar;
+
+procedure loadAmmo;
+var f: PFSFile;
+    ammo: PAmmo;
+    ammos: PAmmoArray;
+    s: ansistring;
+    i: Longword;
+begin
+    f:= pfsOpenRead('/Config/weapons.ini');
+    ammoNumber:= 0;
+
+    if f <> nil then
+    begin
+        while (not pfsEOF(f)) do
+        begin
+            pfsReadLnA(f, s);
+
+            if (length(s) > 0) and (s[1] <> '[') then
+                inc(ammoNumber);
+        end;
+
+        //inc(ammoNumber); // add some default ammo
+
+        ammoList:= GetMem(sizeof(ammoList^) * (ammoNumber + 1));
+        ammo:= PAmmo(ammoList);
+        pfsSeek(f, 0);
+
+        while (not pfsEOF(f)) do
+        begin
+            pfsReadLnA(f, s);
+
+            i:= 1;
+            while(i <= length(s)) and (s[i] <> '=') do inc(i);
+
+            if i < length(s) then
+            begin
+                ammo^.ammoName:= copy(s, 1, i - 1);
+                delete(s, 1, i);
+                // TODO: split into 4 shortstrings
+                i:= length(s) div 4;
+                ammo^.a:= copy(s, 1, i);
+                ammo^.b:= copy(s, i + 1, i);
+                ammo^.c:= copy(s, i * 2 + 1, i);
+                ammo^.d:= copy(s, i * 3 + 1, i);
+                inc(ammo)
+            end;
+        end;
+
+        pfsClose(f)
+    end;
+end;
+
+
+function getAmmosList: PPChar; cdecl;
+var i, t, l: Longword;
+    ammo: PAmmo;
+begin
+    if ammoList = nil then
+        loadAmmo;
+
+    t:= ammoNumber;
+    if t >= MAX_AMMO_NAMES then 
+        t:= MAX_AMMO_NAMES;
+
+    ammo:= ammoList;
+    for i:= 0 to Pred(t) do
+    begin
+        l:= length(ammo^.ammoName);
+        if l >= 255 then l:= 254;
+        ammo^.ammoName[l + 1]:= #0;
+        listOfAmmoNames[i]:= @ammo^.ammoName[1];
+        inc(ammo)
+    end;
+
+    listOfAmmoNames[t]:= nil;
+
+    getAmmosList:= listOfAmmoNames
+end;
+
+function ammoByName(s: shortstring): PAmmo;
+var i: Longword;
+    ammo: PAmmo;
+begin
+    ammo:= ammoList;
+    i:= 0;
+    while (i < ammoNumber) and (ammo^.ammoName <> s) do
+    begin
+        inc(ammo);
+        inc(i)
+    end;
+
+    if i < ammoNumber then ammoByName:= ammo else ammoByName:= nil
+end;
+
+procedure freeAmmosList;
+begin
+    if ammoList <> nil then
+        FreeMem(ammoList, sizeof(ammoList^) * (ammoNumber + 1))
+end;
+
+
+procedure sendAmmoConfig(var ammo: TAmmo);
+var i: Longword;
+begin
+    with ammo do
+    begin
+        ipcToEngine('eammloadt ' + ammo.a);
+        ipcToEngine('eammprob '  + ammo.b);
+        ipcToEngine('eammdelay ' + ammo.c);
+        ipcToEngine('eammreinf ' + ammo.d);
+        ipcToEngine('eammstore');
+    end
+end;
+
+end.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hedgewars/uFLDrawnMap.pas	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,58 @@
+unit uFLDrawnMap;
+interface
+uses SDLh;
+
+procedure decodeDrawnMap(data: ansistring; dataSize: Longword; var mapData: PByteArray; var size: Longword);
+
+implementation
+uses uUtils, zlib;
+
+procedure decodeDrawnMap(data: ansistring; dataSize: Longword; var mapData: PByteArray; var size: Longword);
+var i, cl: Longword;
+    ul: uLong;
+    s: shortstring;
+    r: LongInt;
+    compressedBuf, uncompressedData: PByteArray;
+begin
+    if dataSize = 0 then
+    begin
+        mapData:= nil;
+        size:= 0;
+        exit;
+    end;
+
+    compressedBuf:= GetMem(dataSize * 3 div 4);
+    cl:= 0;
+    i:= 1;
+
+    while i < dataSize do
+    begin
+        if dataSize - i > 240 then
+            s:= copy(data, i, 240)
+        else
+            s:= copy(data, i, dataSize - i + 1);
+
+        s:= DecodeBase64(s);
+        Move(s[1], compressedBuf^[cl], byte(s[0]));
+        inc(i, 240);
+        inc(cl, byte(s[0]));
+    end;
+
+    ul:= SDLNet_Read32(compressedBuf);
+    uncompressedData:= GetMem(ul);
+    r:= uncompress(pBytef(uncompressedData), @ul, @(compressedBuf^[4]), cl - 4);
+    FreeMem(compressedBuf, dataSize * 3 div 4);
+
+    if r = Z_OK then
+    begin
+        mapData:= uncompressedData;
+        size:= ul
+    end else
+    begin
+        FreeMem(uncompressedData, ul);
+        mapData:= nil;
+        size:= 0
+    end;
+end;
+
+end.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hedgewars/uFLGameConfig.pas	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,688 @@
+unit uFLGameConfig;
+interface
+uses uFLTypes;
+
+procedure resetGameConfig; cdecl;
+procedure runQuickGame; cdecl;
+procedure runLocalGame; cdecl;
+procedure getPreview; cdecl;
+
+procedure setSeed(seed: PChar); cdecl;
+function  getSeed: PChar; cdecl;
+procedure setTheme(themeName: PChar); cdecl;
+procedure setScript(scriptName: PChar); cdecl;
+procedure setScheme(schemeName: PChar); cdecl;
+procedure setAmmo(ammoName: PChar); cdecl;
+
+procedure tryAddTeam(teamName: PChar); cdecl;
+procedure tryRemoveTeam(teamName: PChar); cdecl;
+procedure changeTeamColor(teamName: PChar; dir: LongInt); cdecl;
+
+procedure netSetSeed(seed: shortstring);
+procedure netSetTheme(themeName: shortstring);
+procedure netSetScript(scriptName: shortstring);
+procedure netSetFeatureSize(fsize: LongInt);
+procedure netSetMapGen(mapgen: LongInt);
+procedure netSetMap(map: shortstring);
+procedure netSetMazeSize(mazesize: LongInt);
+procedure netSetTemplate(template: LongInt);
+procedure netSetAmmo(name: shortstring; definition: ansistring);
+procedure netSetScheme(scheme: TScheme);
+procedure netAddTeam(team: TTeam);
+procedure netAcceptedTeam(teamName: shortstring);
+procedure netSetTeamColor(team: shortstring; color: Longword);
+procedure netSetHedgehogsNumber(team: shortstring; hogsNumber: Longword);
+procedure netRemoveTeam(teamName: shortstring);
+procedure netDrawnData(data: ansistring);
+procedure netResetTeams();
+procedure updatePreviewIfNeeded;
+
+procedure sendConfig(config: PGameConfig);
+procedure runNetGame();
+
+implementation
+uses uFLIPC, uFLUtils, uFLTeams, uFLThemes, uFLSChemes, uFLAmmo
+    , uFLUICallback, uFLRunQueue, uFLNet, uUtils, uFLDrawnMap
+    , SDLh;
+
+var
+    currentConfig: TGameConfig;
+    previewNeedsUpdate: boolean;
+
+function getScriptPath(scriptName: shortstring): shortstring;
+begin
+    getScriptPath:= '/Scripts/Multiplayer/' + scriptName + '.lua'
+end;
+
+procedure sendDrawnMap(config: PGameConfig);
+var i: Longword;
+    data: PByteArray;
+    dataLen: Longword;
+    s: shortstring;
+begin
+    decodeDrawnMap(config^.drawnData, config^.drawnDataSize, data, dataLen);
+
+    i:= 0;
+
+    s[0]:= #240;
+    while i < dataLen do
+    begin
+        if dataLen - i > 240 then
+        begin
+            Move(data^[i], s[1], 240)
+        end else
+        begin
+            Move(data^[i], s[1], dataLen - i);
+            s[0]:= char(dataLen - i)
+        end;
+
+        ipcToEngine('edraw ' + s);
+        inc(i, 240)
+    end;
+
+    if dataLen > 0 then
+        FreeMem(data, dataLen);
+end;
+
+procedure sendConfig(config: PGameConfig);
+var i: Longword;
+begin
+with config^ do
+begin
+    case gameType of
+    gtPreview: begin
+            if script <> 'Normal' then
+                ipcToEngine('escript ' + getScriptPath(script));
+            ipcToEngine('eseed ' + seed);
+            ipcToEngine('e$mapgen ' + intToStr(mapgen));
+            if (mapgen = 1) or (mapgen = 2) then
+                ipcToEngine('e$maze_size ' + intToStr(mazeSize))
+            else
+                ipcToEngine('e$template_filter ' + intToStr(template));
+            ipcToEngine('e$feature_size ' + intToStr(featureSize));
+            if mapgen = 3 then
+                sendDrawnMap(config);
+        end;
+gtLocal, gtNet: begin
+            if gameType = gtNet then
+                ipcToEngine('TN');
+            if script <> 'Normal' then
+                ipcToEngine('escript ' + getScriptPath(script));
+            ipcToEngine('eseed ' + seed);
+            ipcToEngine('e$mapgen ' + intToStr(mapgen));
+            if (mapgen = 1) or (mapgen = 2) then
+                ipcToEngine('e$maze_size ' + intToStr(mazeSize))
+            else
+                ipcToEngine('e$template_filter ' + intToStr(template));
+            ipcToEngine('e$feature_size ' + intToStr(featureSize));
+            ipcToEngine('e$theme ' + theme);
+            if mapgen = 3 then
+                sendDrawnMap(config);
+
+            sendSchemeConfig(scheme);
+
+            i:= 0;
+            while (i < 8) and (teams[i].hogsNumber > 0) do
+                begin
+                    sendTeamConfig(config^.scheme.health, teams[i]);
+                    sendAmmoConfig(config^.ammo);
+                    inc(i)
+                end;
+        end;
+    end;
+
+    ipcToEngine('!');
+end;
+end;
+
+procedure resetGameConfig; cdecl;
+var i: Longword;
+begin
+    with currentConfig do
+    begin
+        script:= 'Normal';
+
+        for i:= 0 to 7 do
+            teams[i].hogsNumber:= 0
+    end
+end;
+
+procedure setSeed(seed: PChar); cdecl;
+begin
+    sendUI(mtSeed, @seed[1], length(seed));
+    currentConfig.seed:= seed
+end;
+
+function getSeed: PChar; cdecl;
+begin
+    getSeed:= str2PChar(currentConfig.seed)
+end;
+
+function getUnusedColor: Longword;
+var i, c: Longword;
+    fColorMatched: boolean;
+begin
+    c:= 0;
+    i:= 0;
+    repeat
+        repeat
+            fColorMatched:= (currentConfig.teams[i].hogsNumber > 0) and (currentConfig.teams[i].color = c);
+            inc(i)
+        until (i >= 8) or (currentConfig.teams[i].hogsNumber = 0) or fColorMatched;
+
+        if fColorMatched then
+        begin
+            i:= 0;
+            inc(c)
+        end;
+    until not fColorMatched;
+
+    getUnusedColor:= c
+end;
+
+procedure runQuickGame; cdecl;
+begin
+    with currentConfig do
+    begin
+        gameType:= gtLocal;
+        arguments[0]:= '';
+        arguments[1]:= '--internal';
+        arguments[2]:= '--nomusic';
+        argumentsNumber:= 3;
+
+        teams[0]:= createRandomTeam;
+        teams[0].color:= 0;
+        teams[1]:= createRandomTeam;
+        teams[1].color:= 1;
+        teams[1].botLevel:= 3;
+
+        queueExecution(currentConfig);
+    end;
+end;
+
+
+procedure getPreview; cdecl;
+begin
+    previewNeedsUpdate:= false;
+
+    with currentConfig do
+    begin
+        gameType:= gtPreview;
+        arguments[0]:= '';
+        arguments[1]:= '--internal';
+        arguments[2]:= '--landpreview';
+        argumentsNumber:= 3;
+
+        queueExecution(currentConfig);
+    end;
+end;
+
+procedure runLocalGame; cdecl;
+begin
+    with currentConfig do
+    begin
+        gameType:= gtLocal;
+        arguments[0]:= '';
+        arguments[1]:= '--internal';
+        arguments[2]:= '--nomusic';
+        argumentsNumber:= 3;
+
+        queueExecution(currentConfig);
+    end;
+end;
+
+procedure runNetGame();
+begin
+    with currentConfig do
+    begin
+        gameType:= gtNet;
+        arguments[0]:= '';
+        arguments[1]:= '--internal';
+        arguments[2]:= '--nomusic';
+        argumentsNumber:= 3;
+
+        queueExecution(currentConfig);
+    end;
+end;
+
+procedure tryAddTeam(teamName: PChar); cdecl;
+var msg: ansistring;
+    i, hn, hedgehogsNumber: Longword;
+    team: PTeam;
+    c: Longword;
+begin
+    team:= teamByName(teamName);
+    if team = nil then exit;
+
+    if isConnected then
+        sendTeam(team^)
+    else
+    with currentConfig do
+    begin
+        hedgehogsNumber:= 0;
+        i:= 0;
+
+        while (i < 8) and (teams[i].hogsNumber > 0) do
+        begin
+            inc(i);
+            inc(hedgehogsNumber, teams[i].hogsNumber)
+        end;
+
+        // no free space for a team or reached hogs number maximum
+        if (i > 7) or (hedgehogsNumber >= 48) then exit;
+
+        c:= getUnusedColor;
+
+        teams[i]:= team^;
+        teams[i].extDriven:= false;
+
+        if i = 0 then hn:= 4 else hn:= teams[i - 1].hogsNumber;
+        if hn > 48 - hedgehogsNumber then hn:= 48 - hedgehogsNumber;
+        teams[i].hogsNumber:= hn;
+
+        teams[i].color:= c;
+
+        msg:= '0' + #10 + teamName;
+        sendUI(mtAddPlayingTeam, @msg[1], length(msg));
+
+        msg:= teamName + #10 + colorsSet[teams[i].color];
+        sendUI(mtTeamColor, @msg[1], length(msg));
+
+        msg:= teamName + #10 + IntToStr(hn);
+        sendUI(mtHedgehogsNumber, @msg[1], length(msg));
+
+        msg:= teamName;
+        sendUI(mtRemoveTeam, @msg[1], length(msg))
+    end
+end;
+
+
+procedure tryRemoveTeam(teamName: PChar); cdecl;
+var i: Longword;
+    tn: shortstring;
+    isLocal: boolean;
+begin
+    with currentConfig do
+    begin
+        i:= 0;
+        tn:= teamName;
+        while (i < 8) and (teams[i].teamName <> tn) do
+            inc(i);
+
+        // team not found???
+        if (i > 7) then exit;
+
+        isLocal:= not teams[i].extDriven;
+
+        if isConnected and not isLocal then
+            exit; // we cannot remove this team
+
+        while (i < 7) and (teams[i + 1].hogsNumber > 0) do
+        begin
+            teams[i]:= teams[i + 1];
+            inc(i)
+        end;
+
+        teams[i].hogsNumber:= 0
+    end;
+
+    sendUI(mtRemovePlayingTeam, @tn[1], length(tn));
+    if isConnected then
+        removeTeam(tn);
+    if isLocal then
+        sendUI(mtAddTeam, @tn[1], length(tn))
+end;
+
+
+procedure changeTeamColor(teamName: PChar; dir: LongInt); cdecl;
+var i, dc: Longword;
+    tn: shortstring;
+    msg: ansistring;
+begin
+    with currentConfig do
+    begin
+        i:= 0;
+        tn:= teamName;
+        while (i < 8) and (teams[i].teamName <> tn) do
+            inc(i);
+        // team not found???
+        if (i > 7) then exit;
+
+        if dir >= 0 then dc:= 1 else dc:= 8;
+        teams[i].color:= (teams[i].color + dc) mod 9;
+
+        msg:= tn + #10 + colorsSet[teams[i].color];
+        sendUI(mtTeamColor, @msg[1], length(msg))
+    end
+end;
+
+procedure setTheme(themeName: PChar); cdecl;
+begin
+    currentConfig.theme:= themeName
+end;
+
+procedure setScript(scriptName: PChar); cdecl;
+begin
+    currentConfig.script:= scriptName
+end;
+
+procedure setScheme(schemeName: PChar); cdecl;
+var scheme: PScheme;
+begin
+    scheme:= schemeByName(schemeName);
+
+    if scheme <> nil then
+        currentConfig.scheme:= scheme^
+end;
+
+procedure setAmmo(ammoName: PChar); cdecl;
+var ammo: PAmmo;
+begin
+    ammo:= ammoByName(ammoName);
+
+    if ammo <> nil then
+        currentConfig.ammo:= ammo^
+end;
+
+procedure netSetSeed(seed: shortstring);
+begin
+    if seed <> currentConfig.seed then
+    begin
+        currentConfig.seed:= seed;
+        sendUI(mtSeed, @seed[1], length(seed));
+
+        getPreview()
+    end
+end;
+
+procedure netSetTheme(themeName: shortstring);
+begin
+    if themeName <> currentConfig.theme then
+    begin
+        currentConfig.theme:= themeName;
+        sendUI(mtTheme, @themeName[1], length(themeName))
+    end
+end;
+
+procedure netSetScript(scriptName: shortstring);
+begin
+    if scriptName <> currentConfig.script then
+    begin
+        previewNeedsUpdate:= true;
+        currentConfig.script:= scriptName;
+        sendUI(mtScript, @scriptName[1], length(scriptName))
+    end
+end;
+
+procedure netSetFeatureSize(fsize: LongInt);
+var s: shortstring;
+begin
+    if fsize <> currentConfig.featureSize then
+    begin
+        previewNeedsUpdate:= true;
+        currentConfig.featureSize:= fsize;
+        s:= IntToStr(fsize);
+        sendUI(mtFeatureSize, @s[1], length(s))
+    end
+end;
+
+procedure netSetMapGen(mapgen: LongInt);
+var s: shortstring;
+begin
+    if mapgen <> currentConfig.mapgen then
+    begin
+        previewNeedsUpdate:= true;
+        currentConfig.mapgen:= mapgen;
+        s:= IntToStr(mapgen);
+        sendUI(mtMapGen, @s[1], length(s))
+    end
+end;
+
+procedure netSetMap(map: shortstring);
+begin
+    sendUI(mtMap, @map[1], length(map))
+end;
+
+procedure netSetMazeSize(mazesize: LongInt);
+var s: shortstring;
+begin
+    if mazesize <> currentConfig.mazesize then
+    begin
+        previewNeedsUpdate:= true;
+        currentConfig.mazesize:= mazesize;
+        s:= IntToStr(mazesize);
+        sendUI(mtMazeSize, @s[1], length(s))
+    end
+end;
+
+procedure netSetTemplate(template: LongInt);
+var s: shortstring;
+begin
+    if template <> currentConfig.template then
+    begin
+        previewNeedsUpdate:= true;
+        currentConfig.template:= template;
+        s:= IntToStr(template);
+        sendUI(mtTemplate, @s[1], length(s))
+    end
+end;
+
+procedure updatePreviewIfNeeded;
+begin
+    if previewNeedsUpdate then
+        getPreview
+end;
+
+procedure netSetAmmo(name: shortstring; definition: ansistring);
+var ammo: TAmmo;
+    i: LongInt;
+begin
+    ammo.ammoName:= name;
+    i:= length(definition) div 4;
+    ammo.a:= copy(definition, 1, i);
+    ammo.b:= copy(definition, i + 1, i);
+    ammo.c:= copy(definition, i * 2 + 1, i);
+    ammo.d:= copy(definition, i * 3 + 1, i);
+
+    currentConfig.ammo:= ammo;
+    sendUI(mtAmmo, @name[1], length(name))
+end;
+
+procedure netSetScheme(scheme: TScheme);
+begin
+    currentConfig.scheme:= scheme;
+    sendUI(mtScheme, @scheme.schemeName[1], length(scheme.schemeName))
+end;
+
+procedure netAddTeam(team: TTeam);
+var msg: ansistring;
+    i, hn, hedgehogsNumber: Longword;
+    c: Longword;
+begin
+    with currentConfig do
+    begin
+        hedgehogsNumber:= 0;
+        i:= 0;
+
+        while (i < 8) and (teams[i].hogsNumber > 0) do
+        begin
+            inc(i);
+            inc(hedgehogsNumber, teams[i].hogsNumber)
+        end;
+
+        // no free space for a team - server bug???
+        if (i > 7) or (hedgehogsNumber >= 48) then exit;
+
+        c:= getUnusedColor;
+
+        teams[i]:= team;
+        teams[i].extDriven:= true;
+
+        if i = 0 then hn:= 4 else hn:= teams[i - 1].hogsNumber;
+        if hn > 48 - hedgehogsNumber then hn:= 48 - hedgehogsNumber;
+        teams[i].hogsNumber:= hn;
+
+        teams[i].color:= c;
+
+        msg:= '0' + #10 + team.teamName;
+        sendUI(mtAddPlayingTeam, @msg[1], length(msg));
+
+        msg:= team.teamName + #10 + colorsSet[teams[i].color];
+        sendUI(mtTeamColor, @msg[1], length(msg));
+    end
+end;
+
+procedure netAcceptedTeam(teamName: shortstring);
+var msg: ansistring;
+    i, hn, hedgehogsNumber: Longword;
+    c: Longword;
+    team: PTeam;
+begin
+    with currentConfig do
+    begin
+        team:= teamByName(teamName);
+        // no such team???
+        if team = nil then exit;
+
+        hedgehogsNumber:= 0;
+        i:= 0;
+
+        while (i < 8) and (teams[i].hogsNumber > 0) do
+        begin
+            inc(i);
+            inc(hedgehogsNumber, teams[i].hogsNumber)
+        end;
+
+        // no free space for a team - server bug???
+        if (i > 7) or (hedgehogsNumber >= 48) then exit;
+
+        c:= getUnusedColor;
+
+        teams[i]:= team^;
+        teams[i].extDriven:= false;
+
+        if i = 0 then hn:= 4 else hn:= teams[i - 1].hogsNumber;
+        if hn > 48 - hedgehogsNumber then hn:= 48 - hedgehogsNumber;
+        teams[i].hogsNumber:= hn;
+
+        teams[i].color:= c;
+
+        msg:= '0' + #10 + teamName;
+        sendUI(mtAddPlayingTeam, @msg[1], length(msg));
+
+        msg:= teamName + #10 + colorsSet[teams[i].color];
+        sendUI(mtTeamColor, @msg[1], length(msg));
+
+        msg:= teamName;
+        sendUI(mtRemoveTeam, @msg[1], length(msg))        
+    end
+end;
+
+procedure netRemoveTeam(teamName: shortstring);
+var msg: shortstring;
+    i: Longword;
+    tn: shortstring;
+    isLocal: boolean;
+begin
+    with currentConfig do
+    begin
+        i:= 0;
+        tn:= teamName;
+        while (i < 8) and (teams[i].teamName <> tn) do
+            inc(i);
+
+        // team not found???
+        if (i > 7) then exit;
+
+        isLocal:= not teams[i].extDriven;
+
+        while (i < 7) and (teams[i + 1].hogsNumber > 0) do
+        begin
+            teams[i]:= teams[i + 1];
+            inc(i)
+        end;
+
+        teams[i].hogsNumber:= 0
+    end;
+
+    msg:= teamName;
+
+    sendUI(mtRemovePlayingTeam, @msg[1], length(msg));
+    if isLocal then
+        sendUI(mtAddTeam, @msg[1], length(msg))
+end;
+
+procedure netSetTeamColor(team: shortstring; color: Longword);
+var i: Longword;
+    msg: ansistring;
+begin
+    with currentConfig do
+    begin
+        i:= 0;
+
+        while (i < 8) and (teams[i].teamName <> team) do
+            inc(i);
+        // team not found???
+        if (i > 7) then exit;
+
+        teams[i].color:= color mod 9;
+
+        msg:= team + #10 + colorsSet[teams[i].color];
+        sendUI(mtTeamColor, @msg[1], length(msg))
+    end
+end;
+
+procedure netSetHedgehogsNumber(team: shortstring; hogsNumber: Longword);
+var i: Longword;
+    msg: ansistring;
+begin
+    if hogsNumber > 8 then exit;
+
+    with currentConfig do
+    begin
+        i:= 0;
+
+        while (i < 8) and (teams[i].teamName <> team) do
+            inc(i);
+        // team not found???
+        if (i > 7) then exit;
+
+        teams[i].hogsNumber:= hogsNumber;
+
+        msg:= team + #10 + IntToStr(hogsNumber);
+        sendUI(mtHedgehogsNumber, @msg[1], length(msg))
+    end
+end;
+
+procedure netResetTeams();
+var msg: shortstring;
+    i: Longword;
+begin
+    with currentConfig do
+    begin
+        i:= 0;
+
+        while (i < 8) and (teams[i].hogsNumber > 0) do
+        begin
+            msg:= teams[i].teamName;
+
+            sendUI(mtRemovePlayingTeam, @msg[1], length(msg));
+            if not teams[i].extDriven then 
+                sendUI(mtAddTeam, @msg[1], length(msg));
+
+            teams[i].hogsNumber:= 0;
+            inc(i)
+        end;
+
+    end;
+end;
+
+procedure netDrawnData(data: ansistring);
+begin
+    currentConfig.drawnDataSize:= length(data);
+    currentConfig.drawnData:= data;
+
+    getPreview
+end;
+
+end.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hedgewars/uFLIPC.pas	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,326 @@
+unit uFLIPC;
+interface
+uses SDLh, uFLTypes;
+
+procedure initIPC;
+procedure freeIPC;
+
+procedure ipcToEngine(s: shortstring);
+procedure ipcToEngineRaw(p: pointer; len: Longword);
+procedure ipcSetEngineBarrier();
+procedure ipcRemoveBarrierFromEngineQueue();
+//function  ipcReadFromEngine: shortstring;
+//function  ipcCheckFromEngine: boolean;
+
+procedure ipcToNet(s: shortstring);
+procedure ipcToNetRaw(p: pointer; len: Longword);
+
+procedure ipcToFrontend(s: shortstring);
+procedure ipcToFrontendRaw(p: pointer; len: Longword);
+function ipcReadFromFrontend: TIPCMessage;
+function ipcCheckFromFrontend: boolean;
+
+procedure registerIPCCallback(p: pointer; f: TIPCCallback);
+procedure registerNetCallback(p: pointer; f: TIPCCallback);
+
+implementation
+
+var callbackPointerF: pointer;
+    callbackFunctionF: TIPCCallback;
+    callbackListenerThreadF: PSDL_Thread;
+    callbackPointerN: pointer;
+    callbackFunctionN: TIPCCallback;
+    callbackListenerThreadN: PSDL_Thread;
+    queueFrontend, queueEngine, queueNet: PIPCQueue;
+
+procedure ipcSend(var s: TIPCMessage; queue: PIPCQueue);
+var pmsg: PIPCMessage;
+begin
+    SDL_LockMutex(queue^.mut);
+
+    s.next:= nil;
+    s.barrier:= 0;
+
+    if (queue^.msg.next = nil) and (queue^.msg.str[0] = #0) and (queue^.msg.buf = nil) and (queue^.msg.barrier = 0) then
+    begin
+        queue^.msg:= s;
+    end else
+    begin
+        new(pmsg);
+        pmsg^:= s;
+        queue^.last^.next:= pmsg;
+        queue^.last:= pmsg;
+    end;
+
+    SDL_CondSignal(queue^.cond);
+    SDL_UnlockMutex(queue^.mut);
+end;
+
+function ipcRead(queue: PIPCQueue): TIPCMessage;
+var pmsg: PIPCMessage;
+begin
+    SDL_LockMutex(queue^.mut);
+    while ((queue^.msg.str[0] = #0) and (queue^.msg.buf = nil))
+            and ((queue^.msg.barrier > 0) or (queue^.msg.next = nil) or ((queue^.msg.next^.barrier > 0) and (queue^.msg.next^.str[0] = #0) and (queue^.msg.next^.buf = nil))) do
+        SDL_CondWait(queue^.cond, queue^.mut);
+
+    if (queue^.msg.str[0] <> #0) or (queue^.msg.buf <> nil) then
+        begin
+            ipcRead:= queue^.msg;
+            queue^.msg.str[0]:= #0;
+            queue^.msg.buf:= nil;
+        end else
+        begin
+            pmsg:= queue^.msg.next;
+            ipcRead:= pmsg^;
+            if pmsg^.barrier > 0 then
+            begin
+                pmsg^.str[0]:= #0;
+                pmsg^.buf:= nil
+            end else
+            begin
+                queue^.msg.next:= pmsg^.next;
+                if queue^.msg.next = nil then queue^.last:= @queue^.msg;
+                dispose(pmsg)
+            end
+        end;
+
+    SDL_UnlockMutex(queue^.mut)
+end;
+
+function ipcCheck(queue: PIPCQueue): boolean;
+begin
+    SDL_LockMutex(queue^.mut);
+    ipcCheck:= (queue^.msg.str[0] > #0) or (queue^.msg.buf <> nil) or
+               ((queue^.msg.barrier = 0) and (queue^.msg.next <> nil) and ((queue^.msg.next^.barrier = 0) or (queue^.msg.next^.str[0] <> #0) or (queue^.msg.next^.buf <> nil)));
+    SDL_UnlockMutex(queue^.mut)
+end;
+
+procedure ipcToEngine(s: shortstring);
+var msg: TIPCMessage;
+begin
+    msg.str:= s;
+    msg.buf:= nil;
+    ipcSend(msg, queueEngine)
+end;
+
+procedure ipcToFrontend(s: shortstring);
+var msg: TIPCMessage;
+begin
+    msg.str:= s;
+    msg.buf:= nil;
+    ipcSend(msg, queueFrontend)
+end;
+
+procedure ipcSetEngineBarrier();
+begin
+    SDL_LockMutex(queueEngine^.mut);
+
+    inc(queueEngine^.last^.barrier);
+
+    SDL_UnlockMutex(queueEngine^.mut);
+end;
+
+procedure ipcRemoveBarrierFromEngineQueue();
+var pmsg, t: PIPCMessage;
+    q: PIPCQueue;
+begin
+    q:= queueEngine;
+
+    SDL_LockMutex(q^.mut);
+
+    pmsg:= @q^.msg;
+    while pmsg <> nil do
+    begin
+        t:= pmsg^.next;
+        q^.msg.next:= t;
+
+        pmsg^.str[0]:= #0;
+        if pmsg^.buf <> nil then
+        begin
+            FreeMem(pmsg^.buf, pmsg^.len);
+            pmsg^.buf:= nil
+        end;
+
+        if pmsg <> @q^.msg then
+            if pmsg^.barrier = 0 then
+                dispose(pmsg)
+            else
+            if pmsg^.barrier = 1 then
+            begin
+                dispose(pmsg);
+                t:= nil
+            end else
+            begin
+                dec(pmsg^.barrier);
+                q^.msg.next:= pmsg;
+                t:= nil
+            end
+        else
+            if pmsg^.barrier > 0 then 
+            begin
+                dec(pmsg^.barrier);
+                t:= nil
+            end;
+
+        pmsg:= t
+    end;
+
+    if q^.msg.next = nil then q^.last:= @q^.msg;
+
+    q^.msg.str[0]:= #0;
+    q^.msg.buf:= nil;
+
+    SDL_UnlockMutex(q^.mut);
+end;
+
+procedure ipcToNet(s: shortstring);
+var msg: TIPCMessage;
+begin
+    msg.str:= s;
+    msg.buf:= nil;
+    ipcSend(msg, queueNet)
+end;
+
+procedure ipcToEngineRaw(p: pointer; len: Longword);
+var msg: TIPCMessage;
+begin
+    msg.str[0]:= #0;
+    msg.len:= len;
+    msg.buf:= GetMem(len);
+    Move(p^, msg.buf^, len);
+    ipcSend(msg, queueEngine)
+end;
+
+procedure ipcToFrontendRaw(p: pointer; len: Longword);
+var msg: TIPCMessage;
+begin
+    msg.str[0]:= #0;
+    msg.len:= len;
+    msg.buf:= GetMem(len);
+    Move(p^, msg.buf^, len);
+    ipcSend(msg, queueFrontend)
+end;
+
+procedure ipcToNetRaw(p: pointer; len: Longword);
+var msg: TIPCMessage;
+begin
+    msg.str[0]:= #0;
+    msg.len:= len;
+    msg.buf:= GetMem(len);
+    Move(p^, msg.buf^, len);
+    ipcSend(msg, queueNet)
+end;
+
+function ipcReadFromEngine: TIPCMessage;
+begin
+    ipcReadFromEngine:= ipcRead(queueFrontend)
+end;
+
+function ipcReadFromFrontend: TIPCMessage;
+begin
+    ipcReadFromFrontend:= ipcRead(queueEngine)
+end;
+
+function ipcReadToNet: TIPCMessage;
+begin
+    ipcReadToNet:= ipcRead(queueNet)
+end;
+
+function ipcCheckFromEngine: boolean;
+begin
+    ipcCheckFromEngine:= ipcCheck(queueFrontend)
+end;
+
+function ipcCheckFromFrontend: boolean;
+begin
+    ipcCheckFromFrontend:= ipcCheck(queueEngine)
+end;
+
+function  engineListener(p: pointer): Longint; cdecl; export;
+var msg: TIPCMessage;
+begin
+    engineListener:= 0;
+    repeat
+        msg:= ipcReadFromEngine();
+        if msg.buf = nil then
+            callbackFunctionF(callbackPointerF, @msg.str[1], byte(msg.str[0]))
+        else
+        begin
+            callbackFunctionF(callbackPointerF, msg.buf, msg.len);
+            FreeMem(msg.buf, msg.len)
+        end
+    until false
+end;
+
+function  netListener(p: pointer): Longint; cdecl; export;
+var msg: TIPCMessage;
+begin
+    netListener:= 0;
+    repeat
+        msg:= ipcReadToNet();
+        if msg.buf = nil then
+            callbackFunctionN(callbackPointerN, @msg.str[1], byte(msg.str[0]))
+        else
+        begin
+            callbackFunctionN(callbackPointerN, msg.buf, msg.len);
+            FreeMem(msg.buf, msg.len)
+        end
+    until false
+end;
+
+procedure registerIPCCallback(p: pointer; f: TIPCCallback);
+begin
+    callbackPointerF:= p;
+    callbackFunctionF:= f;
+    callbackListenerThreadF:= SDL_CreateThread(@engineListener, 'engineListener', nil);
+end;
+
+procedure registerNetCallback(p: pointer; f: TIPCCallback);
+begin
+    callbackPointerN:= p;
+    callbackFunctionN:= f;
+    callbackListenerThreadN:= SDL_CreateThread(@netListener, 'netListener', nil);
+end;
+
+function createQueue: PIPCQueue;
+var q: PIPCQueue;
+begin
+    new(q);
+    q^.msg.str:= '';
+    q^.msg.buf:= nil;
+    q^.msg.barrier:= 0;
+    q^.mut:= SDL_CreateMutex;
+    q^.cond:= SDL_CreateCond;
+    q^.msg.next:= nil;
+    q^.last:= @q^.msg;
+    createQueue:= q
+end;
+
+procedure destroyQueue(queue: PIPCQueue);
+begin
+    SDL_DestroyCond(queue^.cond);
+    SDL_DestroyMutex(queue^.mut);
+    dispose(queue);
+end;
+
+procedure initIPC;
+begin
+    queueFrontend:= createQueue;
+    queueEngine:= createQueue;
+    queueNet:= createQueue;
+
+    callbackPointerF:= nil;
+    callbackListenerThreadF:= nil;
+end;
+
+procedure freeIPC;
+begin
+    //FIXME SDL_KillThread(callbackListenerThreadF);
+    //FIXME SDL_KillThread(callbackListenerThreadN);
+    destroyQueue(queueFrontend);
+    destroyQueue(queueEngine);
+    destroyQueue(queueNet);
+end;
+
+end.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hedgewars/uFLNet.pas	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,444 @@
+unit uFLNet;
+interface
+uses SDLh;
+
+procedure connectOfficialServer;
+
+procedure initModule;
+procedure freeModule;
+procedure sendNet(s: shortstring);
+procedure sendNetLn(s: shortstring);
+procedure passToNet(data: PByteArray; len: Longword);
+
+var isConnected: boolean = false;
+    myNickname: shortstring = 'qmlfrontend';
+
+implementation
+uses uFLIPC, uFLUICallback, uFLNetTypes, uFLUtils, uFLTypes;
+
+const endCmd: shortstring = #10 + #10;
+
+function getNextChar: char; forward;
+function getCurrChar: char; forward;
+
+type
+    TParserState = record
+                       cmd: TCmdType;
+                       l: LongInt;
+                       buf: shortstring;
+                       bufpos: byte;
+                   end;
+    PHandler = procedure;
+
+var state: TParserState;
+
+procedure handleTail; forward;
+function getShortString: shortstring; forward;
+function getLongString: ansistring; forward;
+
+procedure handler_;
+begin
+    sendUI(mtNetData, @state.cmd, sizeof(state.cmd));
+    handleTail()
+end;
+
+procedure handler_L;
+var cmd: TCmdParamL;
+    s: ansistring;
+begin
+    cmd.cmd:= state.cmd;
+    s:= getLongString;
+    cmd.str1len:= length(s);
+    if cmd.str1len = 0 then exit;
+    cmd.str1:= s;
+    sendUI(mtNetData, @cmd, sizeof(cmd));
+    handleTail()
+end;
+
+procedure handler_ML;
+var cmd: TCmdParamL;
+    f: boolean;
+    s: ansistring;
+begin
+    sendUI(mtNetData, @state.cmd, sizeof(state.cmd));
+    cmd.cmd:= Succ(state.cmd);
+
+    repeat
+        s:= getLongString;
+        cmd.str1len:= length(s);
+        f:= cmd.str1len <> 0;
+
+        if f then
+            begin
+            cmd.str1:= s;
+            sendUI(mtNetData, @cmd, sizeof(cmd));
+            end
+    until not f;
+    state.l:= 0
+end;
+
+procedure handler_MS;
+var cmd: TCmdParamS;
+    f: boolean;
+begin
+    sendUI(mtNetData, @state.cmd, sizeof(state.cmd));
+    cmd.cmd:= Succ(state.cmd);
+
+    repeat
+        cmd.str1:= getShortString;
+        f:= cmd.str1[0] <> #0;
+        if f then
+            sendUI(mtNetData, @cmd, sizeof(cmd));
+    until not f;
+    state.l:= 0
+end;
+
+procedure handler_S;
+var cmd: TCmdParamS;
+begin
+    cmd.cmd:= state.cmd;
+    cmd.str1:= getShortString;
+    if cmd.str1[0] = #0 then exit;
+    sendUI(mtNetData, @cmd, sizeof(cmd));
+    handleTail()
+end;
+
+procedure handler_SL;
+var cmd: TCmdParamSL;
+begin
+    cmd.cmd:= state.cmd;
+    cmd.str1:= getShortString;
+    if cmd.str1[0] = #0 then exit;
+    cmd.str2:= getLongString;
+    if cmd.str2[0] = #0 then exit;
+    sendUI(mtNetData, @cmd, sizeof(cmd));
+    handleTail()
+end;
+
+procedure handler_SMS;
+var cmd: TCmdParamS;
+    f: boolean;
+begin
+    cmd.cmd:= state.cmd;
+    cmd.str1:= getShortString;
+    if cmd.str1[0] = #0 then exit;
+    sendUI(mtNetData, @cmd, sizeof(cmd));
+
+    cmd.cmd:= Succ(state.cmd);
+    repeat
+        cmd.str1:= getShortString;
+        f:= cmd.str1[0] <> #0;
+        if f then
+            sendUI(mtNetData, @cmd, sizeof(cmd));
+    until not f;
+    state.l:= 0
+end;
+
+procedure handler_SS;
+var cmd: TCmdParamSS;
+begin
+    cmd.cmd:= state.cmd;
+    cmd.str1:= getShortString;
+    if cmd.str1[0] = #0 then exit;
+    cmd.str2:= getShortString;
+    if cmd.str2[0] = #0 then exit;
+    sendUI(mtNetData, @cmd, sizeof(cmd));
+    handleTail()
+end;
+
+procedure handler__i;
+var cmd: TCmdParami;
+    s: shortstring;
+begin
+    s:= getShortString();
+    if s[0] = #0 then exit;
+    cmd.cmd:= state.cmd;
+    s:= getShortString();
+    if s[0] = #0 then exit;
+    cmd.param1:= strToInt(s);
+    sendUI(mtNetData, @cmd, sizeof(cmd));
+    handleTail()
+end;
+
+procedure handler_i;
+var cmd: TCmdParami;
+    s: shortstring;
+begin
+    s:= getShortString();
+    if s[0] = #0 then exit;
+    cmd.cmd:= state.cmd;
+    cmd.param1:= strToInt(s);
+    sendUI(mtNetData, @cmd, sizeof(cmd));
+    handleTail()
+end;
+
+procedure handler__UNKNOWN_;
+begin
+    //writeln('[NET] Unknown cmd');
+    handleTail();
+    state.l:= 0
+end;
+
+const net2cmd: array[0..46] of TCmdType = (cmd_WARNING, cmd_WARNING,
+    cmd_TEAM_COLOR, cmd_TEAM_ACCEPTED, cmd_SERVER_VARS, cmd_SERVER_MESSAGE,
+    cmd_SERVER_AUTH, cmd_RUN_GAME, cmd_ROUND_FINISHED, cmd_ROOM_UPD, cmd_ROOM_DEL,
+    cmd_ROOM_ADD, cmd_ROOMS, cmd_REMOVE_TEAM, cmd_PROTO, cmd_PING, cmd_NOTICE,
+    cmd_NICK, cmd_LOBBY_LEFT, cmd_LOBBY_JOINED, cmd_LEFT, cmd_KICKED, cmd_JOINING,
+    cmd_JOINED, cmd_INFO, cmd_HH_NUM, cmd_ERROR, cmd_EM, cmd_CONNECTED,
+    cmd_CLIENT_FLAGS, cmd_CHAT, cmd_CFG_THEME, cmd_CFG_TEMPLATE, cmd_CFG_SEED,
+    cmd_CFG_SCRIPT, cmd_CFG_SCHEME, cmd_CFG_MAZE_SIZE, cmd_CFG_MAP, cmd_CFG_MAPGEN,
+    cmd_CFG_FULLMAPCONFIG, cmd_CFG_FEATURE_SIZE, cmd_CFG_DRAWNMAP, cmd_CFG_AMMO,
+    cmd_BYE, cmd_BANLIST, cmd_ASKPASSWORD, cmd_ADD_TEAM);
+const letters: array[0..332] of char = ('A', 'D', 'D', '_', 'T', 'E', 'A', 'M',
+    #10, 'S', 'K', 'P', 'A', 'S', 'S', 'W', 'O', 'R', 'D', #10, 'B', 'A', 'N', 'L',
+    'I', 'S', 'T', #10, 'Y', 'E', #10, 'C', 'F', 'G', #10, 'A', 'M', 'M', 'O', #10,
+    'D', 'R', 'A', 'W', 'N', 'M', 'A', 'P', #10, 'F', 'E', 'A', 'T', 'U', 'R', 'E',
+    '_', 'S', 'I', 'Z', 'E', #10, 'U', 'L', 'L', 'M', 'A', 'P', 'C', 'O', 'N', 'F',
+    'I', 'G', #10, 'M', 'A', 'P', 'G', 'E', 'N', #10, #10, 'Z', 'E', '_', 'S', 'I',
+    'Z', 'E', #10, 'S', 'C', 'H', 'E', 'M', 'E', #10, 'R', 'I', 'P', 'T', #10, 'E',
+    'E', 'D', #10, 'T', 'E', 'M', 'P', 'L', 'A', 'T', 'E', #10, 'H', 'E', 'M', 'E',
+    #10, 'H', 'A', 'T', #10, 'L', 'I', 'E', 'N', 'T', '_', 'F', 'L', 'A', 'G', 'S',
+    #10, 'O', 'N', 'N', 'E', 'C', 'T', 'E', 'D', #10, 'E', 'M', #10, 'R', 'R', 'O',
+    'R', #10, 'H', 'H', '_', 'N', 'U', 'M', #10, 'I', 'N', 'F', 'O', #10, 'J', 'O',
+    'I', 'N', 'E', 'D', #10, 'I', 'N', 'G', #10, 'K', 'I', 'C', 'K', 'E', 'D', #10,
+    'L', 'E', 'F', 'T', #10, 'O', 'B', 'B', 'Y', ':', 'J', 'O', 'I', 'N', 'E', 'D',
+    #10, 'L', 'E', 'F', 'T', #10, 'N', 'I', 'C', 'K', #10, 'O', 'T', 'I', 'C', 'E',
+    #10, 'P', 'I', 'N', 'G', #10, 'R', 'O', 'T', 'O', #10, 'R', 'E', 'M', 'O', 'V',
+    'E', '_', 'T', 'E', 'A', 'M', #10, 'O', 'O', 'M', 'S', #10, #10, 'A', 'D', 'D',
+    #10, 'D', 'E', 'L', #10, 'U', 'P', 'D', #10, 'U', 'N', 'D', '_', 'F', 'I', 'N',
+    'I', 'S', 'H', 'E', 'D', #10, 'U', 'N', '_', 'G', 'A', 'M', 'E', #10, 'S', 'E',
+    'R', 'V', 'E', 'R', '_', 'A', 'U', 'T', 'H', #10, 'M', 'E', 'S', 'S', 'A', 'G',
+    'E', #10, 'V', 'A', 'R', 'S', #10, 'T', 'E', 'A', 'M', '_', 'A', 'C', 'C', 'E',
+    'P', 'T', 'E', 'D', #10, 'C', 'O', 'L', 'O', 'R', #10, 'W', 'A', 'R', 'N', 'I',
+    'N', 'G', #10, #0, #10);
+const commands: array[0..332] of integer = (20, 8, 0, 0, 0, 0, 0, 0, -56, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, -55, 11, 7, 0, 0, 0, 0, 0, -54, 0, 0, -53, 115, 89, 0,
+    0, 5, 0, 0, 0, -52, 9, 0, 0, 0, 0, 0, 0, 0, -51, 26, 12, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, -50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -49, 16, 0, 6, 4, 0, 0, -48, -47,
+    0, 0, 0, 0, 0, 0, 0, -46, 16, 11, 5, 0, 0, 0, -45, 0, 0, 0, 0, -44, 0, 0, 0,
+    -43, 0, 8, 0, 0, 0, 0, 0, 0, -42, 0, 0, 0, 0, -41, 4, 0, 0, -40, 12, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, -39, 0, 0, 0, 0, 0, 0, 0, 0, -38, 8, 2, -37, 0, 0, 0, 0, -36,
+    7, 0, 0, 0, 0, 0, -35, 5, 0, 0, 0, -34, 11, 0, 0, 0, 3, 0, -33, 0, 0, 0, -32, 7,
+    0, 0, 0, 0, 0, -31, 22, 4, 0, 0, -30, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, -29, 0,
+    0, 0, 0, -28, 11, 4, 0, 0, -27, 0, 0, 0, 0, 0, -26, 10, 4, 0, 0, -25, 0, 0, 0,
+    0, -24, 51, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, -23, 31, 17, 0, 2, -22, 0, 4, 0, 0,
+    -21, 4, 0, 0, -20, 0, 0, 0, -19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -18, 0, 0,
+    0, 0, 0, 0, 0, -17, 25, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, -16, 8, 0, 0, 0, 0, 0, 0,
+    -15, 0, 0, 0, 0, -14, 20, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, -13, 0, 0, 0, 0,
+    0, -12, 8, 0, 0, 0, 0, 0, 0, -11, 0, -10);
+const handlers: array[0..46] of PHandler = (@handler__UNKNOWN_, @handler_L,
+    @handler_SS, @handler_S, @handler_SL, @handler_L, @handler_S, @handler_,
+    @handler_, @handler_MS, @handler_S, @handler_MS, @handler_MS, @handler_S,
+    @handler_i, @handler_MS, @handler_L, @handler_S, @handler_SL, @handler_MS,
+    @handler_SL, @handler_, @handler_S, @handler_MS, @handler_MS, @handler_SS,
+    @handler_L, @handler_ML, @handler__i, @handler_SMS, @handler_SL, @handler_S,
+    @handler_i, @handler_S, @handler_S, @handler_MS, @handler_i, @handler_i,
+    @handler_S, @handler_MS, @handler_i, @handler_L, @handler_SL, @handler_SL,
+    @handler_MS, @handler_S, @handler_MS);
+
+procedure handleTail;
+var cnt: Longint;
+    c: char;
+begin
+    c:= getCurrChar;
+    repeat
+        if c = #10 then cnt:= 0 else cnt:= 1;
+        repeat
+            c:= getNextChar;
+            inc(cnt)
+        until (c = #0) or (c = #10);
+    until (c = #0) or (cnt = 1);
+    state.l:= 0
+end;
+
+var sock: PTCPSocket;
+    netReaderThread: PSDL_Thread;
+
+function getCurrChar: char;
+begin
+    getCurrChar:= state.buf[state.bufpos]
+end;
+
+function getNextChar: char;
+var r: byte;
+begin
+    if state.bufpos < byte(state.buf[0]) then
+    begin
+        inc(state.bufpos);
+    end else
+    begin
+        r:= SDLNet_TCP_Recv(sock, @state.buf[1], 255);
+        if r > 0 then
+        begin
+            state.bufpos:= 1;
+            state.buf[0]:= char(r);
+        end else
+        begin
+            state.bufpos:= 0;
+            state.buf[0]:= #0;
+        end
+    end;
+
+    getNextChar:= state.buf[state.bufpos];
+end;
+
+function netReader(data: pointer): LongInt; cdecl; export;
+var c: char;
+    ipaddr: TIPAddress;
+begin
+    netReader:= 0;
+
+    if SDLNet_ResolveHost(ipaddr, PChar('netserver.hedgewars.org'), 46631) = 0 then
+        sock:= SDLNet_TCP_Open(ipaddr);
+
+    repeat
+        c:= getNextChar;
+        //writeln('>>>>> ', c, ' [', letters[state.l], '] ', commands[state.l], ' ', state.l);
+        if c = #0 then
+            isConnected:= false
+        else
+        begin
+            while (letters[state.l] <> c) and (commands[state.l] > 0) do
+                inc(state.l, commands[state.l]);
+
+            if c = letters[state.l] then
+                if commands[state.l] < 0 then
+                begin
+                    state.cmd:= net2cmd[-10 - commands[state.l]];
+                    //writeln('[NET] ', state.cmd);
+                    handlers[-10 - commands[state.l]]();
+                    state.l:= 0
+                end
+                else
+                    inc(state.l)
+            else
+            begin
+                handler__UNKNOWN_()
+            end
+        end
+    until not isConnected;
+
+    SDLNet_TCP_Close(sock);
+    sock:= nil;
+
+    writeln('[NET] netReader: disconnected');
+end;
+
+procedure sendNet(s: shortstring);
+begin
+    writeln('[NET] Send: ', s);
+    ipcToNet(s + endCmd);
+end;
+
+procedure sendNetLn(s: shortstring);
+begin
+    writeln('[NET] Send: ', s);
+    ipcToNet(s + #10);
+end;
+
+function getShortString: shortstring;
+var s: shortstring;
+    c: char;
+begin
+    s[0]:= #0;
+
+    repeat
+        inc(s[0]);
+        s[byte(s[0])]:= getNextChar
+    until (s[0] = #255) or (s[byte(s[0])] = #10) or (s[byte(s[0])] = #0);
+
+    if s[byte(s[0])] = #10 then
+        dec(s[0])
+    else
+        repeat c:= getNextChar until (c = #0) or (c = #10);
+
+    getShortString:= s
+end;
+
+function getLongString: ansistring;
+var s: shortstring;
+    l: ansistring;
+    c: char;
+begin
+    l:= '';
+
+    repeat
+        s[0]:= #0;
+            repeat
+                inc(s[0]);
+                c:= getNextChar;
+                s[byte(s[0])]:= c
+            until (s[0] = #255) or (c = #10) or (c = #0);
+
+        if s[byte(s[0])] = #10 then
+            dec(s[0]);
+
+        l:= l + s
+    until (c = #10) or (c = #0);
+
+    getLongString:= l
+end;
+
+procedure netSendCallback(p: pointer; msg: PChar; len: Longword);
+begin
+    // FIXME W A R N I N G: totally thread-unsafe due to use of sock variable
+    SDLNet_TCP_Send(sock, msg, len);
+end;
+
+procedure connectOfficialServer;
+begin
+    if sock <> nil then
+        exit;
+
+    state.bufpos:= 0;
+    state.buf:= '';
+
+    state.l:= 0;
+    isConnected:= true;
+
+    netReaderThread:= SDL_CreateThread(@netReader, 'netReader', nil);
+    SDL_DetachThread(netReaderThread)
+end;
+
+
+procedure passToNet(data: PByteArray; len: Longword);
+var i: Longword;
+    l: ansistring;
+    s: shortstring;
+begin
+    i:= 0;
+
+    while(i < len) do
+    begin
+        if data^[i + 1] = ord('s') then
+        begin
+            s[0]:= char(data^[i] - 1);
+            Move(data^[i + 2], s[1], data^[i] - 1);
+
+            l:= myNickname + #10;
+            l:= l + s;
+
+            sendUI(mtRoomChatLine, @l[1], length(l));
+            sendNetLn('CHAT');
+            sendNet(s);
+        end;
+
+        inc(i, data^[i] + 1);
+    end;
+end;
+
+procedure initModule;
+begin
+    sock:= nil;
+    isConnected:= false;
+
+    SDLNet_Init;
+
+    registerNetCallback(nil, @netSendCallback);
+end;
+
+procedure freeModule;
+begin
+end;
+
+end.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hedgewars/uFLNetProtocol.pas	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,598 @@
+unit uFLNetProtocol;
+interface
+
+procedure passNetData(p: pointer); cdecl;
+
+procedure sendChatLine(msg: PChar); cdecl;
+procedure joinRoom(roomName: PChar); cdecl;
+procedure partRoom(msg: PChar); cdecl;
+
+procedure ResetNetState;
+
+implementation
+uses uFLNetTypes, uFLTypes, uFLUICallback, uFLNet, uFLGameConfig, uFLUtils, uFLIPC, uUtils;
+
+type
+    PHandler = procedure (var t: TCmdData);
+
+var isInRoom: boolean;
+
+procedure onRoomLeaving();
+begin
+    isInRoom:= false;
+    sendUI(mtMoveToLobby, nil, 0);
+    netResetTeams
+end;
+
+var teamIndex: LongInt;
+    tmpTeam: TTeam;
+
+const teamFields: array[0..22] of PShortstring = (
+    @tmpTeam.teamName
+    , @tmpTeam.grave
+    , @tmpTeam.fort
+    , @tmpTeam.voice
+    , @tmpTeam.flag
+    , @tmpTeam.owner
+    , nil
+    , @tmpTeam.hedgehogs[0].name
+    , @tmpTeam.hedgehogs[0].hat
+    , @tmpTeam.hedgehogs[1].name
+    , @tmpTeam.hedgehogs[1].hat
+    , @tmpTeam.hedgehogs[2].name
+    , @tmpTeam.hedgehogs[2].hat
+    , @tmpTeam.hedgehogs[3].name
+    , @tmpTeam.hedgehogs[3].hat
+    , @tmpTeam.hedgehogs[4].name
+    , @tmpTeam.hedgehogs[4].hat
+    , @tmpTeam.hedgehogs[5].name
+    , @tmpTeam.hedgehogs[5].hat
+    , @tmpTeam.hedgehogs[6].name
+    , @tmpTeam.hedgehogs[6].hat
+    , @tmpTeam.hedgehogs[7].name
+    , @tmpTeam.hedgehogs[7].hat
+    );
+
+procedure handler_ADD_TEAM(var p: TCmdParam);
+begin
+    teamIndex:= 0;
+    tmpTeam.color:= 0
+end;
+
+procedure handler_ADD_TEAM_s(var s: TCmdParamS);
+begin
+    if teamIndex = 6 then
+        tmpTeam.botLevel:= strToInt(s.str1)
+    else if teamIndex < 23 then
+        teamFields[teamIndex]^:= s.str1;
+
+    if teamIndex = 22 then
+        netAddTeam(tmpTeam);
+
+    inc(teamIndex);
+end;
+
+procedure handler_ASKPASSWORD(var p: TCmdParamS);
+begin
+end;
+
+procedure handler_BANLIST(var p: TCmdParam);
+begin
+end;
+
+procedure handler_BANLIST_s(var s: TCmdParamS);
+begin
+end;
+
+procedure handler_BYE(var p: TCmdParamSL);
+begin
+    sendUI(mtDisconnected, @p.str2[1], length(p.str2));
+end;
+
+procedure handler_CFG_AMMO(var p: TCmdParamSL);
+begin
+    netSetAmmo(p.str1, p.str2)
+end;
+
+procedure handler_CFG_DRAWNMAP(var p: TCmdParamL);
+begin
+    netDrawnData(copy(ansistring(p.str1), 1, p.str1len))
+end;
+
+procedure handler_CFG_FEATURE_SIZE(var p: TCmdParami);
+begin
+    if isInRoom then
+    begin
+        netSetFeatureSize(p.param1);
+        updatePreviewIfNeeded
+    end;
+end;
+
+var fmcfgIndex: integer;
+
+procedure handler_CFG_FULLMAPCONFIG(var p: TCmdParam);
+begin
+    fmcfgIndex:= 0;
+end;
+
+procedure handler_CFG_FULLMAPCONFIG_s(var s: TCmdParamS);
+begin
+    if not isInRoom then exit;
+
+    inc(fmcfgIndex);
+    case fmcfgIndex of
+        1: netSetFeatureSize(strToInt(s.str1));
+        2: if s.str1[0] <> '+' then netSetMap(s.str1);
+        3: netSetMapGen(strToInt(s.str1));
+        4: netSetMazeSize(strToInt(s.str1));
+        5: netSetSeed(s.str1);
+        6: begin
+                netSetTemplate(strToInt(s.str1));
+                updatePreviewIfNeeded;
+            end;
+    end;
+end;
+
+procedure handler_CFG_MAP(var p: TCmdParamS);
+begin
+    if isInRoom then
+        netSetMap(p.str1);
+end;
+
+procedure handler_CFG_MAPGEN(var p: TCmdParami);
+begin
+    if isInRoom then
+    begin
+        netSetMapGen(p.param1);
+        updatePreviewIfNeeded
+    end
+end;
+
+procedure handler_CFG_MAZE_SIZE(var p: TCmdParami);
+begin
+    if isInRoom then
+    begin
+        netSetMazeSize(p.param1);
+        updatePreviewIfNeeded
+    end
+end;
+
+var schemeIndex: LongInt;
+    tmpScheme: TScheme;
+
+procedure handler_CFG_SCHEME(var p: TCmdParam);
+begin
+    schemeIndex:= 0
+end;
+
+const schemeFields: array[0..43] of pointer = (
+      @tmpScheme.schemeName          //  0
+    , @tmpScheme.fortsmode           //  1
+    , @tmpScheme.divteams            //  2
+    , @tmpScheme.solidland           //  3
+    , @tmpScheme.border              //  4
+    , @tmpScheme.lowgrav             //  5
+    , @tmpScheme.laser               //  6
+    , @tmpScheme.invulnerability     //  7
+    , @tmpScheme.resethealth         //  8
+    , @tmpScheme.vampiric            //  9
+    , @tmpScheme.karma               // 10
+    , @tmpScheme.artillery           // 11
+    , @tmpScheme.randomorder         // 12
+    , @tmpScheme.king                // 13
+    , @tmpScheme.placehog            // 14
+    , @tmpScheme.sharedammo          // 15
+    , @tmpScheme.disablegirders      // 16
+    , @tmpScheme.disablelandobjects  // 17
+    , @tmpScheme.aisurvival          // 18
+    , @tmpScheme.infattack           // 19
+    , @tmpScheme.resetweps           // 20
+    , @tmpScheme.perhogammo          // 21
+    , @tmpScheme.disablewind         // 22
+    , @tmpScheme.morewind            // 23
+    , @tmpScheme.tagteam             // 24
+    , @tmpScheme.bottomborder        // 25
+    , @tmpScheme.damagefactor        // 26
+    , @tmpScheme.turntime            // 27
+    , @tmpScheme.health              // 28
+    , @tmpScheme.suddendeath         // 29
+    , @tmpScheme.caseprobability     // 30
+    , @tmpScheme.minestime           // 31
+    , @tmpScheme.minesnum            // 32
+    , @tmpScheme.minedudpct          // 33
+    , @tmpScheme.explosives          // 34
+    , @tmpScheme.airmines            // 35
+    , @tmpScheme.healthprobability   // 36
+    , @tmpScheme.healthcaseamount    // 37
+    , @tmpScheme.waterrise           // 38
+    , @tmpScheme.healthdecrease      // 39
+    , @tmpScheme.ropepct             // 40
+    , @tmpScheme.getawaytime         // 41
+    , @tmpScheme.worldedge           // 42
+    , @tmpScheme.scriptparam         // 43
+   );
+
+procedure handler_CFG_SCHEME_s(var s: TCmdParamS);
+begin
+    if(schemeIndex = 0) then
+        tmpScheme.schemeName:= s.str1
+    else
+    if(schemeIndex = 43) then
+        tmpScheme.scriptparam:= copy(s.str1, 2, length(s.str1) - 1)
+    else
+    if(schemeIndex < 26) then
+        PBoolean(schemeFields[schemeIndex])^:= s.str1[1] = 't'
+    else
+    if(schemeIndex < 43) then
+        PLongInt(schemeFields[schemeIndex])^:= strToInt(s.str1);
+
+    if(schemeIndex = 43) then
+        netSetScheme(tmpScheme);
+
+    inc(schemeIndex);
+end;
+
+procedure handler_CFG_SCRIPT(var p: TCmdParamS);
+begin
+    if isInRoom then
+        netSetScript(p.str1)
+end;
+
+procedure handler_CFG_SEED(var p: TCmdParamS);
+begin
+    if isInRoom then
+        netSetSeed(p.str1)
+end;
+
+procedure handler_CFG_TEMPLATE(var p: TCmdParami);
+begin
+    if isInRoom then
+    begin
+        netSetTemplate(p.param1);
+        updatePreviewIfNeeded
+    end
+end;
+
+procedure handler_CFG_THEME(var p: TCmdParamS);
+begin
+    if isInRoom then
+        netSetTheme(p.str1)
+end;
+
+procedure handler_CHAT(var p: TCmdParamSL);
+var s: string;
+begin
+    s:= p.str1 + #10 + copy(p.str2, 0, p.str2len);
+    if isInRoom then
+        sendUI(mtRoomChatLine, @s[1], length(s))
+    else
+        sendUI(mtLobbyChatLine, @s[1], length(s));
+end;
+
+var flags: array[TClientFlag] of LongInt;
+    isFlagsLine: boolean;
+procedure handler_CLIENT_FLAGS(var p: TCmdParamS);
+var f: TClientFlag;
+begin
+    for f:= Low(TClientFlag) to High(TClientFlag) do
+        flags[f]:= 0;
+
+    isFlagsLine:= true;
+end;
+
+procedure handler_CLIENT_FLAGS_s(var s: TCmdParamS);
+var isRemoval: boolean;
+    flagValue, i: LongInt;
+begin
+    if isFlagsLine then
+    begin
+        if s.str1[1] = '+' then flagValue:= 1 else flagValue:= -1;
+        for i:= 2 to Length(s.str1) do
+            case s.str1[1] of
+                'r': flags[cfReady]:= flagValue;
+                'u': flags[cfRegistered]:= flagValue;
+                'i': flags[cfInRoom]:= flagValue;
+                'c': flags[cfContributor]:= flagValue;
+                'g': flags[cfInGame]:= flagValue;
+                'h': flags[cfRoomAdmin]:= flagValue;
+                'a': flags[cfServerAdmin]:= flagValue;
+            end;
+
+        isFlagsLine:= false;
+    end else
+    begin
+
+    end
+end;
+
+procedure handler_CONNECTED(var p: TCmdParami);
+begin
+    sendUI(mtConnected, nil, 0);
+    //writeln('Server features version ', p.param1);
+    sendNet('PROTO' + #10 + '52');
+    sendNet('NICK' + #10 + myNickname);
+end;
+
+procedure handler_EM(var p: TCmdParam);
+begin
+end;
+
+procedure handler_EM_s(var p: TCmdParamL);
+var i, l: Longword;
+    s: shortstring;
+begin
+    i:= 1;
+    l:= length(p.str1);
+
+    while i < l do
+    begin
+        s:= DecodeBase64(copy(p.str1, i, 240));
+        ipcToEngineRaw(@s[1], byte(s[0]));
+        inc(i, 160)
+    end;
+end;
+
+procedure handler_ERROR(var p: TCmdParamL);
+begin
+    sendUI(mtError, @p.str1[1], length(p.str1));
+end;
+
+procedure handler_HH_NUM(var p: TCmdParamSS);
+begin
+    netSetHedgehogsNumber(p.str1, StrToInt(p.str2))
+end;
+
+procedure handler_INFO(var p: TCmdParam);
+begin
+end;
+
+procedure handler_INFO_s(var s: TCmdParamS);
+begin
+end;
+
+procedure handler_JOINED(var p: TCmdParam);
+begin
+end;
+
+procedure handler_JOINED_s(var s: TCmdParamS);
+begin
+    if s.str1 = myNickname then // we joined a room
+    begin
+        isInRoom:= true;
+        sendUI(mtMoveToRoom, nil, 0);
+    end;
+
+    sendUI(mtAddRoomClient, @s.str1[1], length(s.str1));
+end;
+
+procedure handler_JOINING(var p: TCmdParamS);
+begin
+end;
+
+procedure handler_KICKED(var p: TCmdParam);
+begin
+    onRoomLeaving()
+end;
+
+procedure handler_LEFT(var p: TCmdParamSL);
+var s: string;
+begin
+    s:= p.str1 + #10 + copy(p.str2, 0, p.str2len);
+    sendUI(mtRemoveRoomClient, @s[1], length(s));
+end;
+
+procedure handler_LOBBY_JOINED(var p: TCmdParam);
+begin
+end;
+
+procedure handler_LOBBY_JOINED_s(var s: TCmdParamS);
+begin
+    if s.str1 = myNickname then
+    begin
+        sendUI(mtMoveToLobby, nil, 0);
+        sendNet('LIST');
+    end;
+
+    sendUI(mtAddLobbyClient, @s.str1[1], length(s.str1));
+end;
+
+procedure handler_LOBBY_LEFT(var p: TCmdParamSL);
+var s: string;
+begin
+    s:= p.str1 + #10 + copy(p.str2, 0, p.str2len);
+    sendUI(mtRemoveLobbyClient, @s[1], length(s));
+end;
+
+procedure handler_NICK(var p: TCmdParamS);
+begin
+    myNickname:= p.str1;
+    sendUI(mtNickname, @p.str1[1], length(p.str1));
+end;
+
+procedure handler_NOTICE(var p: TCmdParamL);
+begin
+end;
+
+procedure handler_PING(var p: TCmdParam);
+begin
+    sendNet('PONG')
+end;
+
+procedure handler_PING_s(var s: TCmdParamS);
+begin
+end;
+
+procedure handler_PROTO(var p: TCmdParami);
+begin
+    //writeln('Protocol ', p.param1)
+end;
+
+procedure handler_REMOVE_TEAM(var p: TCmdParamS);
+begin
+    netRemoveTeam(p.str1)
+end;
+
+var roomInfo: string;
+    roomLinesCount: integer;
+
+procedure handler_ROOMS(var p: TCmdParam);
+begin
+    roomInfo:= '';
+    roomLinesCount:= 0
+end;
+
+procedure handler_ROOMS_s(var s: TCmdParamS);
+begin
+    roomInfo:= roomInfo + s.str1 + #10;
+
+    if roomLinesCount = 8 then
+    begin
+        sendUI(mtAddRoom, @roomInfo[1], length(roomInfo) - 1);
+        roomLinesCount:= 0;
+        roomInfo:= ''
+    end else inc(roomLinesCount);
+end;
+
+procedure handler_ROOM_ADD(var p: TCmdParam);
+begin
+    roomInfo:= '';
+    roomLinesCount:= 0
+end;
+
+procedure handler_ROOM_ADD_s(var s: TCmdParamS);
+begin
+    roomInfo:= roomInfo + s.str1 + #10;
+    inc(roomLinesCount);
+
+    if roomLinesCount = 9 then
+    begin
+        sendUI(mtAddRoom, @roomInfo[1], length(roomInfo) - 1);
+        roomInfo:= '';
+        roomLinesCount:= 0
+    end;
+end;
+
+procedure handler_ROOM_DEL(var p: TCmdParamS);
+begin
+    sendUI(mtRemoveRoom, @p.str1[1], length(p.str1));
+end;
+
+procedure handler_ROOM_UPD(var p: TCmdParam);
+begin
+    roomInfo:= '';
+    roomLinesCount:= 0
+end;
+
+procedure handler_ROOM_UPD_s(var s: TCmdParamS);
+begin
+    roomInfo:= roomInfo + s.str1 + #10;
+    inc(roomLinesCount);
+
+    if roomLinesCount = 10 then
+        sendUI(mtUpdateRoom, @roomInfo[1], length(roomInfo) - 1);
+end;
+
+procedure handler_ROUND_FINISHED(var p: TCmdParam);
+begin
+end;
+
+procedure handler_RUN_GAME(var p: TCmdParam);
+begin
+    runNetGame
+end;
+
+procedure handler_SERVER_AUTH(var p: TCmdParamS);
+begin
+end;
+
+procedure handler_SERVER_MESSAGE(var p: TCmdParamL);
+begin
+end;
+
+procedure handler_SERVER_VARS(var p: TCmdParamSL);
+begin
+end;
+
+procedure handler_TEAM_ACCEPTED(var p: TCmdParamS);
+begin
+    netAcceptedTeam(p.str1)
+end;
+
+procedure handler_TEAM_COLOR(var p: TCmdParamSS);
+begin
+    netSetTeamColor(p.str1, StrToInt(p.str2));
+end;
+
+procedure handler_WARNING(var p: TCmdParamL);
+begin
+    sendUI(mtWarning, @p.str1[1], length(p.str1));
+end;
+
+const handlers: array[TCmdType] of PHandler = (PHandler(@handler_ADD_TEAM),
+    PHandler(@handler_ADD_TEAM_s), PHandler(@handler_ASKPASSWORD),
+    PHandler(@handler_BANLIST), PHandler(@handler_BANLIST_s),
+    PHandler(@handler_BYE), PHandler(@handler_CFG_AMMO),
+    PHandler(@handler_CFG_DRAWNMAP), PHandler(@handler_CFG_FEATURE_SIZE),
+    PHandler(@handler_CFG_FULLMAPCONFIG), PHandler(@handler_CFG_FULLMAPCONFIG_s),
+    PHandler(@handler_CFG_MAP), PHandler(@handler_CFG_MAPGEN),
+    PHandler(@handler_CFG_MAZE_SIZE), PHandler(@handler_CFG_SCHEME),
+    PHandler(@handler_CFG_SCHEME_s), PHandler(@handler_CFG_SCRIPT),
+    PHandler(@handler_CFG_SEED), PHandler(@handler_CFG_TEMPLATE),
+    PHandler(@handler_CFG_THEME), PHandler(@handler_CHAT),
+    PHandler(@handler_CLIENT_FLAGS), PHandler(@handler_CLIENT_FLAGS_s),
+    PHandler(@handler_CONNECTED), PHandler(@handler_EM), PHandler(@handler_EM_s),
+    PHandler(@handler_ERROR), PHandler(@handler_HH_NUM), PHandler(@handler_INFO),
+    PHandler(@handler_INFO_s), PHandler(@handler_JOINED),
+    PHandler(@handler_JOINED_s), PHandler(@handler_JOINING),
+    PHandler(@handler_KICKED), PHandler(@handler_LEFT),
+    PHandler(@handler_LOBBY_JOINED), PHandler(@handler_LOBBY_JOINED_s),
+    PHandler(@handler_LOBBY_LEFT), PHandler(@handler_NICK),
+    PHandler(@handler_NOTICE), PHandler(@handler_PING), PHandler(@handler_PING_s),
+    PHandler(@handler_PROTO), PHandler(@handler_REMOVE_TEAM),
+    PHandler(@handler_ROOMS), PHandler(@handler_ROOMS_s),
+    PHandler(@handler_ROOM_ADD), PHandler(@handler_ROOM_ADD_s),
+    PHandler(@handler_ROOM_DEL), PHandler(@handler_ROOM_UPD),
+    PHandler(@handler_ROOM_UPD_s), PHandler(@handler_ROUND_FINISHED),
+    PHandler(@handler_RUN_GAME), PHandler(@handler_SERVER_AUTH),
+    PHandler(@handler_SERVER_MESSAGE), PHandler(@handler_SERVER_VARS),
+    PHandler(@handler_TEAM_ACCEPTED), PHandler(@handler_TEAM_COLOR),
+    PHandler(@handler_WARNING));
+
+procedure passNetData(p: pointer); cdecl;
+begin
+    handlers[TCmdData(p^).cmd.cmd](TCmdData(p^))
+end;
+
+procedure sendChatLine(msg: PChar); cdecl;
+begin
+    sendNetLn('CHAT');
+    sendNet(msg);
+end;
+
+procedure joinRoom(roomName: PChar); cdecl;
+begin
+    sendNetLn('JOIN_ROOM');
+    sendNet(roomName);
+end;
+
+procedure partRoom(msg: PChar); cdecl;
+var s: string;
+begin
+    if isInRoom then
+    begin
+        s:= 'PART';
+        if length(msg) > 0 then
+            s:= s + #10 + msg;
+        sendNet(s);
+
+        onRoomLeaving()
+    end
+end;
+
+procedure ResetNetState;
+begin
+    isInRoom:= false;
+end;
+
+end.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hedgewars/uFLNetTypes.pas	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,57 @@
+unit uFLNetTypes;
+interface
+
+type TCmdType = (cmd_ADD_TEAM, cmd_ADD_TEAM_s, cmd_ASKPASSWORD, cmd_BANLIST,
+    cmd_BANLIST_s, cmd_BYE, cmd_CFG_AMMO, cmd_CFG_DRAWNMAP, cmd_CFG_FEATURE_SIZE,
+    cmd_CFG_FULLMAPCONFIG, cmd_CFG_FULLMAPCONFIG_s, cmd_CFG_MAP, cmd_CFG_MAPGEN,
+    cmd_CFG_MAZE_SIZE, cmd_CFG_SCHEME, cmd_CFG_SCHEME_s, cmd_CFG_SCRIPT,
+    cmd_CFG_SEED, cmd_CFG_TEMPLATE, cmd_CFG_THEME, cmd_CHAT, cmd_CLIENT_FLAGS,
+    cmd_CLIENT_FLAGS_s, cmd_CONNECTED, cmd_EM, cmd_EM_s, cmd_ERROR, cmd_HH_NUM,
+    cmd_INFO, cmd_INFO_s, cmd_JOINED, cmd_JOINED_s, cmd_JOINING, cmd_KICKED,
+    cmd_LEFT, cmd_LOBBY_JOINED, cmd_LOBBY_JOINED_s, cmd_LOBBY_LEFT, cmd_NICK,
+    cmd_NOTICE, cmd_PING, cmd_PING_s, cmd_PROTO, cmd_REMOVE_TEAM, cmd_ROOMS,
+    cmd_ROOMS_s, cmd_ROOM_ADD, cmd_ROOM_ADD_s, cmd_ROOM_DEL, cmd_ROOM_UPD,
+    cmd_ROOM_UPD_s, cmd_ROUND_FINISHED, cmd_RUN_GAME, cmd_SERVER_AUTH,
+    cmd_SERVER_MESSAGE, cmd_SERVER_VARS, cmd_TEAM_ACCEPTED, cmd_TEAM_COLOR,
+    cmd_WARNING);
+
+    type TCmdParam = packed record
+        cmd: TCmdType;
+        end;
+    type TCmdParamL = packed record
+        cmd: TCmdType;
+        str1len: Longword;
+        str1: array[word] of char;
+        end;
+    type TCmdParamS = packed record
+        cmd: TCmdType;
+        str1: shortstring;
+        end;
+    type TCmdParamSL = packed record
+        cmd: TCmdType;
+        str1: shortstring;
+        str2len: Longword;
+        str2: array[word] of char;
+        end;
+    type TCmdParamSS = packed record
+        cmd: TCmdType;
+        str1: shortstring;
+        str2: shortstring;
+        end;
+    type TCmdParami = packed record
+        cmd: TCmdType;
+        param1: LongInt;
+        end;
+
+    TCmdData = record
+                   case byte of
+                       0: (cmd: TCmdParam);
+                       1: (cpl: TCmdParamL);
+                       2: (cps: TCmdParamS);
+                       3: (cpsl: TCmdParamSL);
+                       4: (cpi: TCmdParami);
+               end;
+
+implementation
+
+end.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hedgewars/uFLRunQueue.pas	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,93 @@
+unit uFLRunQueue;
+interface
+uses uFLTypes;
+
+procedure queueExecution(var config: TGameConfig);
+procedure passFlibEvent(p: pointer); cdecl;
+
+implementation
+uses uFLGameConfig, hwengine, uFLThemes, uFLUICallback, uFLIPC;
+
+var runQueue: PGameConfig = nil;
+
+procedure nextRun;
+begin
+    if runQueue <> nil then
+    begin
+        if runQueue^.gameType = gtPreview then
+            sendUI(mtRenderingPreview, nil, 0);
+
+        ipcRemoveBarrierFromEngineQueue();
+        RunEngine(runQueue^.argumentsNumber, @runQueue^.argv);
+    end
+end;
+
+procedure cleanupConfig;
+var t: PGameConfig;
+begin
+    t:= runQueue;
+    runQueue:= t^.nextConfig;
+    dispose(t)
+end;
+
+procedure queueExecution(var config: TGameConfig);
+var pConfig, t, tt: PGameConfig;
+    i: Longword;
+begin
+    new(pConfig);
+    pConfig^:= config;
+
+    with pConfig^ do
+    begin
+        nextConfig:= nil;
+
+        for i:= 0 to Pred(MAXARGS) do
+        begin
+            if arguments[i][0] = #255 then
+                arguments[i][255]:= #0
+            else
+                arguments[i][byte(arguments[i][0]) + 1]:= #0;
+            argv[i]:= @arguments[i][1]
+        end;
+    end;
+
+    if runQueue = nil then
+    begin
+        runQueue:= pConfig;
+
+        ipcSetEngineBarrier();
+        sendConfig(pConfig);
+        nextRun
+    end else
+    begin
+        t:= runQueue;
+        while t^.nextConfig <> nil do 
+        begin
+            if false and (pConfig^.gameType = gtPreview) and (t^.nextConfig^.gameType = gtPreview) and (t <> runQueue) then
+            begin
+                tt:= t^.nextConfig;
+                pConfig^.nextConfig:= tt^.nextConfig;
+                t^.nextConfig:= pConfig;
+                dispose(tt);
+                exit // boo
+            end;
+            t:= t^.nextConfig;
+        end;
+
+        ipcSetEngineBarrier();
+        sendConfig(pConfig);
+        t^.nextConfig:= pConfig
+    end;
+end;
+
+procedure passFlibEvent(p: pointer); cdecl;
+begin
+    case TFLIBEvent(p^) of
+        flibGameFinished: begin
+            cleanupConfig;
+            nextRun
+        end;
+    end;
+end;
+
+end.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hedgewars/uFLSchemes.pas	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,246 @@
+unit uFLSchemes;
+interface
+uses uFLTypes;
+
+function getSchemesList: PPChar; cdecl;
+procedure freeSchemesList;
+
+function schemeByName(s: shortstring): PScheme;
+procedure sendSchemeConfig(var scheme: TScheme);
+
+implementation
+uses uFLUtils, uFLIPC, uPhysFSLayer, uFLThemes;
+
+const MAX_SCHEME_NAMES = 64;
+type
+    TSchemeArray = array [0..0] of TScheme;
+    PSchemeArray = ^TSchemeArray;
+var
+    schemesList: PScheme;
+    schemesNumber: LongInt;
+    listOfSchemeNames: array[0..MAX_SCHEME_NAMES] of PChar;
+    tmpScheme: TScheme;
+
+const ints: array[0 .. 17] of record
+            name: shortstring;
+            param: ^LongInt;
+        end = (
+              (name: 'damagefactor'; param: @tmpScheme.damagefactor)
+            , (name: 'turntime'; param: @tmpScheme.turntime)
+            , (name: 'health'; param: @tmpScheme.health)
+            , (name: 'suddendeath'; param: @tmpScheme.suddendeath)
+            , (name: 'caseprobability'; param: @tmpScheme.caseprobability)
+            , (name: 'minestime'; param: @tmpScheme.minestime)
+            , (name: 'landadds'; param: @tmpScheme.landadds)
+            , (name: 'minedudpct'; param: @tmpScheme.minedudpct)
+            , (name: 'explosives'; param: @tmpScheme.explosives)
+            , (name: 'minesnum'; param: @tmpScheme.minesnum)
+            , (name: 'healthprobability'; param: @tmpScheme.healthprobability)
+            , (name: 'healthcaseamount'; param: @tmpScheme.healthcaseamount)
+            , (name: 'waterrise'; param: @tmpScheme.waterrise)
+            , (name: 'healthdecrease'; param: @tmpScheme.healthdecrease)
+            , (name: 'ropepct'; param: @tmpScheme.ropepct)
+            , (name: 'getawaytime'; param: @tmpScheme.getawaytime)
+            , (name: 'worldedge'; param: @tmpScheme.worldedge)
+            , (name: 'airmines'; param: @tmpScheme.airmines)
+              );
+const bools: array[0 .. 24] of record
+            name: shortstring;
+            param: ^boolean;
+            flag: Longword;
+        end = (
+              (name: 'fortsmode'; param: @tmpScheme.fortsmode; flag: $00001000)
+            , (name: 'divteams'; param: @tmpScheme.divteams; flag: $00000010)
+            , (name: 'solidland'; param: @tmpScheme.solidland; flag: $00000004)
+            , (name: 'border'; param: @tmpScheme.border; flag: $00000008)
+            , (name: 'lowgrav'; param: @tmpScheme.lowgrav; flag: $00000020)
+            , (name: 'laser'; param: @tmpScheme.laser; flag: $00000040)
+            , (name: 'invulnerability'; param: @tmpScheme.invulnerability; flag: $00000080)
+            , (name: 'resethealth'; param: @tmpScheme.resethealth; flag: $00000100)
+            , (name: 'vampiric'; param: @tmpScheme.vampiric; flag: $00000200)
+            , (name: 'karma'; param: @tmpScheme.karma; flag: $00000400)
+            , (name: 'artillery'; param: @tmpScheme.artillery; flag: $00000800)
+            , (name: 'randomorder'; param: @tmpScheme.randomorder; flag: $00002000)
+            , (name: 'king'; param: @tmpScheme.king; flag: $00004000)
+            , (name: 'placehog'; param: @tmpScheme.placehog; flag: $00008000)
+            , (name: 'sharedammo'; param: @tmpScheme.sharedammo; flag: $00010000)
+            , (name: 'disablegirders'; param: @tmpScheme.disablegirders; flag: $00020000)
+            , (name: 'disablewind'; param: @tmpScheme.disablewind; flag: $00800000)
+            , (name: 'morewind'; param: @tmpScheme.morewind; flag: $01000000)
+            , (name: 'tagteam'; param: @tmpScheme.tagteam; flag: $02000000)
+            , (name: 'bottomborder'; param: @tmpScheme.bottomborder; flag: $04000000)
+            , (name: 'disablelandobjects'; param: @tmpScheme.disablelandobjects; flag: $00040000)
+            , (name: 'aisurvival'; param: @tmpScheme.aisurvival; flag: $00080000)
+            , (name: 'infattack'; param: @tmpScheme.infattack; flag: $00100000)
+            , (name: 'resetweps'; param: @tmpScheme.resetweps; flag: $00200000)
+            , (name: 'perhogammo'; param: @tmpScheme.perhogammo; flag: $00400000)
+              );
+
+procedure loadSchemes;
+var f: PFSFile;
+    schemes: PSchemeArray;
+    s: shortstring;
+    l, i, ii: Longword;
+    isFound: boolean;
+begin
+    f:= pfsOpenRead('/Config/schemes.ini');
+    schemesNumber:= 0;
+
+    if f <> nil then
+    begin
+        while (not pfsEOF(f)) and (schemesNumber = 0) do
+        begin
+            pfsReadLn(f, s);
+
+            if copy(s, 1, 5) = 'size=' then
+                schemesNumber:= strToInt(midStr(s, 6));
+        end;
+
+        //inc(schemesNumber); // add some default schemes
+
+        schemesList:= GetMem(sizeof(schemesList^) * (schemesNumber + 1));
+        schemes:= PSchemeArray(schemesList);
+
+        while (not pfsEOF(f)) do
+        begin
+            pfsReadLn(f, s);
+
+            i:= 1;
+            while(i <= length(s)) and (s[i] <> '\') do inc(i);
+
+            if i < length(s) then
+            begin
+                l:= strToInt(copy(s, 1, i - 1));
+                delete(s, 1, i);
+
+                if (l <= schemesNumber) and (l > 0) then
+                begin
+                    if copy(s, 1, 5) = 'name=' then
+                        schemes^[l - 1].schemeName:= midStr(s, 6)
+                    else if copy(s, 1, 12) = 'scriptparam=' then
+                        schemes^[l - 1].scriptparam:= midStr(s, 13) else
+                    begin
+                        ii:= 0;
+                        repeat
+                            isFound:= readInt(ints[ii].name, s, PLongInt(ints[ii].param - @tmpScheme + @schemes^[l - 1])^);
+                            inc(ii)
+                        until isFound or (ii > High(ints));
+
+                        if not isFound then
+                            begin
+                                ii:= 0;
+                                repeat
+                                    isFound:= readBool(bools[ii].name, s, PBoolean(bools[ii].param - @tmpScheme + @schemes^[l - 1])^);
+                                    inc(ii)
+                                until isFound or (ii > High(bools));
+                            end;
+                    end;
+                end;
+            end;
+        end;
+
+        pfsClose(f)
+    end;
+end;
+
+
+function getSchemesList: PPChar; cdecl;
+var i, t, l: Longword;
+    scheme: PScheme;
+begin
+    if schemesList = nil then
+        loadSchemes;
+
+    t:= schemesNumber;
+    if t >= MAX_SCHEME_NAMES then 
+        t:= MAX_SCHEME_NAMES;
+
+    scheme:= schemesList;
+    for i:= 0 to Pred(t) do
+    begin
+        l:= length(scheme^.schemeName);
+        if l >= 255 then l:= 254;
+        scheme^.schemeName[l + 1]:= #0;
+        listOfSchemeNames[i]:= @scheme^.schemeName[1];
+        inc(scheme)
+    end;
+
+    listOfSchemeNames[t]:= nil;
+
+    getSchemesList:= listOfSchemeNames
+end;
+
+function schemeByName(s: shortstring): PScheme;
+var i: Longword;
+    scheme: PScheme;
+begin
+    scheme:= schemesList;
+    i:= 0;
+    while (i < schemesNumber) and (scheme^.schemeName <> s) do
+    begin
+        inc(scheme);
+        inc(i)
+    end;
+
+    if i < schemesNumber then schemeByName:= scheme else schemeByName:= nil
+end;
+
+procedure freeSchemesList;
+begin
+    if schemesList <> nil then
+        FreeMem(schemesList, sizeof(schemesList^) * (schemesNumber + 1))
+end;
+
+
+procedure sendSchemeConfig(var scheme: TScheme);
+var i: Longword;
+    gf: Longword;
+begin
+    with scheme do
+    begin
+        if turntime <> 45 then
+            ipcToEngine('e$turntime ' + inttostr(turntime * 1000));
+        if minesnum <> 4 then
+            ipcToEngine('e$minesnum ' + inttostr(minesnum));
+        if damagefactor <> 100 then
+            ipcToEngine('e$damagepct ' + inttostr(damagefactor));
+        if worldedge > 0 then
+            ipcToEngine('e$worldedge ' + inttostr(worldedge));
+        if length(scriptparam) > 0 then
+            ipcToEngine('e$scriptparam ' + scriptparam);
+        if suddendeath <> 15 then
+            ipcToEngine('e$sd_turns ' + inttostr(suddendeath));
+        if waterrise <> 47 then
+            ipcToEngine('e$waterrise ' + inttostr(waterrise));
+        if ropepct <> 100 then
+            ipcToEngine('e$ropepct ' + inttostr(ropepct));
+        if getawaytime <> 100 then
+            ipcToEngine('e$getawaytime ' + inttostr(getawaytime));
+        if caseprobability <> 5 then
+            ipcToEngine('e$casefreq ' + inttostr(caseprobability));
+        if healthprobability <> 35 then
+            ipcToEngine('e$healthprob ' + inttostr(healthprobability));
+        if minestime <> 3 then
+            ipcToEngine('e$minestime ' + inttostr(minestime * 1000));
+        if minedudpct <> 0 then
+            ipcToEngine('e$minedudpct ' + inttostr(minedudpct));
+        if explosives <> 2 then
+            ipcToEngine('e$explosives ' + inttostr(explosives));
+        if airmines <> 0 then
+            ipcToEngine('e$airmines ' + inttostr(airmines));
+        if healthcaseamount <> 25 then
+            ipcToEngine('e$hcaseamount ' + inttostr(healthcaseamount));
+        if healthdecrease <> 5 then
+            ipcToEngine('e$healthdec ' + inttostr(healthdecrease));
+
+        gf:= 0;
+
+        for i:= Low(bools) to High(bools) do
+            if PBoolean(bools[i].param - @tmpScheme + @scheme)^ then
+                gf:= gf or bools[i].flag;
+
+        ipcToEngine('e$gmflags ' + inttostr(gf));
+    end
+end;
+
+end.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hedgewars/uFLScripts.pas	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,128 @@
+unit uFLScripts;
+interface
+uses uFLTypes;
+
+function getScriptsList: PPChar; cdecl;
+procedure freeScriptsList;
+
+implementation
+uses uFLUtils, uFLIPC, uPhysFSLayer, uFLThemes;
+
+const MAX_SCRIPT_NAMES = 64;
+type
+    TScript = record
+            scriptName: shortstring;
+            description: shortstring;
+            gameScheme, weapons: shortstring;
+        end;
+    PScript = ^TScript;
+var
+    scriptsList: PScript;
+    scriptsNumber: Longword;
+    listOfScriptNames: array[0..MAX_SCRIPT_NAMES] of PChar;
+
+procedure loadScript(var script: TScript; scriptName, fileName: shortstring);
+var f: PFSFile;
+begin
+    underScore2Space(scriptName);
+    script.scriptName:= scriptName;
+    script.description:= scriptName + ' script description';
+
+    f:= pfsOpenRead(copy(fileName, 1, length(fileName) - 4) + '.txt');
+
+    script.gameScheme:= '';
+    script.weapons:= '';
+
+    if f <> nil then
+    begin
+        if not pfsEOF(f) then
+        begin
+            pfsReadLn(f, script.gameScheme);
+
+            if not pfsEOF(f) then
+                pfsReadLn(f, script.weapons);
+        end;
+
+        pfsClose(f)
+    end
+end;
+
+procedure loadScripts;
+var filesList, tmp: PPChar;
+    script: PScript;
+    s: shortstring;
+    l: Longword;
+begin
+    filesList:= pfsEnumerateFiles('/Scripts/Multiplayer');
+    scriptsNumber:= 1;
+
+    tmp:= filesList;
+    while tmp^ <> nil do
+    begin
+        s:= shortstring(tmp^);
+        l:= length(s);
+        if (l > 4) and (copy(s, l - 3, 4) = '.lua') then inc(scriptsNumber);
+        inc(tmp)
+    end;
+
+    scriptsList:= GetMem(sizeof(scriptsList^) * (scriptsNumber + 1));
+
+    script:= scriptsList;
+
+    // add 'normal' script
+    script^.scriptName:= 'Normal';
+    script^.description:= 'Normal gameplay';
+    inc(script);
+
+    // fill the rest from *.lua list
+    tmp:= filesList;
+    while tmp^ <> nil do
+    begin
+        s:= shortstring(tmp^);
+        l:= length(s);
+        if (l > 4) and (copy(s, l - 3, 4) = '.lua') then 
+            begin
+                loadScript(script^, copy(s, 1, l - 4), '/Config/Scripts/' + s);
+                inc(script)
+            end;
+        inc(tmp)
+    end;
+
+    pfsFreeList(filesList)
+end;
+
+
+function getScriptsList: PPChar; cdecl;
+var i, t, l: Longword;
+    script: PScript;
+begin
+    if scriptsList = nil then
+        loadScripts;
+
+    t:= scriptsNumber;
+    if t >= MAX_SCRIPT_NAMES then 
+        t:= MAX_SCRIPT_NAMES;
+
+    script:= scriptsList;
+    for i:= 0 to Pred(t) do
+    begin
+        l:= length(script^.scriptName);
+        if l >= 255 then l:= 254;
+        script^.scriptName[l + 1]:= #0;
+        listOfScriptNames[i]:= @script^.scriptName[1];
+        inc(script)
+    end;
+
+    listOfScriptNames[t]:= nil;
+
+    getScriptsList:= listOfScriptNames
+end;
+
+
+procedure freeScriptsList;
+begin
+    if scriptsList <> nil then
+        FreeMem(scriptsList, sizeof(scriptsList^) * scriptsNumber)
+end;
+
+end.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hedgewars/uFLTeams.pas	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,225 @@
+unit uFLTeams;
+interface
+uses uFLTypes;
+
+function createRandomTeam: TTeam;
+procedure sendTeamConfig(hp: LongInt; var team: TTeam);
+
+function getTeamsList: PPChar; cdecl;
+procedure freeTeamsList;
+
+function teamByName(s: shortstring): PTeam;
+
+procedure sendTeam(var team: TTeam);
+procedure removeTeam(teamName: shortstring);
+
+implementation
+uses uFLUtils, uFLIPC, uPhysFSLayer, uFLThemes, uFLNet;
+
+const MAX_TEAM_NAMES = 128;
+var
+    teamsList: PTeam;
+    teamsNumber: Longword;
+    listOfTeamNames: array[0..MAX_TEAM_NAMES] of PChar;
+
+
+function createRandomTeam: TTeam;
+var t: TTeam;
+    i: Longword;
+begin
+    with t do
+    begin
+        teamName:= 'team' + inttostr(random(100));
+
+        for i:= 0 to 7 do
+            with hedgehogs[i] do
+            begin
+                name:= 'hedgehog ' + inttostr(i);
+                hat:= 'NoHat'
+            end;
+
+        botLevel:= 0;
+        hogsNumber:= 4
+    end;
+    createRandomTeam:= t
+end;
+
+
+procedure sendTeamConfig(hp: LongInt; var team: TTeam);
+var i: Longword;
+begin
+    with team do
+    begin
+        ipcToEngine('eaddteam <hash> ' + colorsSet[color] + ' ' + teamName);
+
+        if extDriven then
+            ipcToEngine('erdriven');
+
+        for i:= 0 to Pred(hogsNumber) do
+        begin
+            ipcToEngine('eaddhh ' + IntToStr(botLevel) + ' ' + IntToStr(hp) + ' ' + hedgehogs[i].name);
+            ipcToEngine('ehat ' + hedgehogs[i].hat);
+        end;
+    end
+end;
+
+
+procedure loadTeam(var team: TTeam; fileName: shortstring);
+var f: PFSFile;
+    section: LongInt;
+    l: shortstring;
+begin
+    section:= -1;
+    f:= pfsOpenRead(fileName);
+
+    while (not pfsEOF(f)) do
+    begin
+        pfsReadLn(f, l);
+
+        if l = '' then
+        else if l = '[Team]' then 
+            section:= -2
+        else if copy(l, 1, 9) = '[Hedgehog' then
+            section:= StrToInt(copy(l, 10, 1))
+        else if section = -2 then
+        begin // [Team]
+            if copy(l, 1, 5) = 'Name=' then
+                team.teamName:= midStr(l, 6)
+            else if copy(l, 1, 6) = 'Grave=' then
+                team.grave:= midStr(l, 7)
+            else if copy(l, 1, 5) = 'Fort=' then
+                team.fort:= midStr(l, 6)
+            else if copy(l, 1, 5) = 'Flag=' then
+                team.flag:= midStr(l, 6)
+            else if copy(l, 1, 10) = 'Voicepack=' then
+                team.voice:= midStr(l, 11)
+            else if copy(l, 1, 11) = 'Difficulty=' then
+                team.botLevel:= StrToInt(midStr(l, 12))
+        end else if (section >= 0) and (section <= 7) then
+        begin // [Hedgehog*]
+            if copy(l, 1, 5) = 'Name=' then
+                team.hedgehogs[section].name:= midStr(l, 6)
+            else if copy(l, 1, 4) = 'Hat=' then
+                team.hedgehogs[section].hat:= midStr(l, 5)
+        end;
+    end;
+
+    pfsClose(f)
+end;
+
+
+procedure loadTeams;
+var filesList, tmp: PPChar;
+    team: PTeam;
+    s: shortstring;
+    l: Longword;
+begin
+    filesList:= pfsEnumerateFiles('/Config/Teams');
+    teamsNumber:= 0;
+
+    tmp:= filesList;
+    while tmp^ <> nil do
+    begin
+        s:= shortstring(tmp^);
+        l:= length(s);
+        if (l > 4) and (copy(s, l - 3, 4) = '.hwt') then inc(teamsNumber);
+        inc(tmp)
+    end;
+
+    // TODO: no teams at all?
+    teamsList:= GetMem(sizeof(teamsList^) * teamsNumber);
+
+    team:= teamsList;
+    tmp:= filesList;
+    while tmp^ <> nil do
+    begin
+        s:= shortstring(tmp^);
+        l:= length(s);
+        if (l > 4) and (copy(s, l - 3, 4) = '.hwt') then 
+            begin
+                loadTeam(team^, '/Config/Teams/' + s);
+                inc(team)
+            end;
+        inc(tmp)
+    end;
+
+    pfsFreeList(filesList)
+end;
+
+
+function getTeamsList: PPChar; cdecl;
+var i, t, l: Longword;
+    team: PTeam;
+begin
+    if teamsList = nil then
+        loadTeams;
+
+    t:= teamsNumber;
+    if t >= MAX_TEAM_NAMES then 
+        t:= MAX_TEAM_NAMES;
+
+    team:= teamsList;
+    for i:= 0 to Pred(t) do
+    begin
+        l:= length(team^.teamName);
+        if l >= 255 then l:= 254;
+        team^.teamName[l + 1]:= #0;
+        listOfTeamNames[i]:= @team^.teamName[1];
+        inc(team)
+    end;
+
+    listOfTeamNames[t]:= nil;
+
+    getTeamsList:= listOfTeamNames
+end;
+
+function teamByName(s: shortstring): PTeam;
+var i: Longword;
+    team: PTeam;
+begin
+    team:= teamsList;
+    i:= 0;
+    while (i < teamsNumber) and (team^.teamName <> s) do
+    begin
+        inc(team);
+        inc(i)
+    end;
+
+    if i < teamsNumber then teamByName:= team else teamByName:= nil
+end;
+
+procedure freeTeamsList;
+begin
+    if teamsList <> nil then
+        FreeMem(teamsList, sizeof(teamsList^) * teamsNumber)
+end;
+
+procedure sendTeam(var team: TTeam);
+var i: Longword;
+begin
+    with team do
+    begin
+        sendNetLn('ADD_TEAM');
+        sendNetLn(teamName);
+        sendNetLn(IntToStr(color));
+        sendNetLn(grave);
+        sendNetLn(fort);
+        sendNetLn(voice);
+        sendNetLn(flag);
+        sendNetLn(IntToStr(botLevel));
+        for i := 0 to 7 do
+        begin
+            sendNetLn(hedgehogs[i].name);
+            sendNetLn(hedgehogs[i].hat);
+        end;
+        sendNetLn('')
+    end;
+end;
+
+procedure removeTeam(teamName: shortstring);
+begin
+    sendNetLn('REMOVE_TEAM');
+    sendNet(teamName)
+end;
+
+end.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hedgewars/uFLThemes.pas	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,85 @@
+unit uFLThemes;
+interface
+
+function getThemesList: PPChar; cdecl;
+procedure freeThemesList(list: PPChar); cdecl;
+function getThemeIcon(themeName: PChar; buffer: PChar; buflen: Longword): Longword; cdecl;
+
+const colorsSet: array[0..8] of shortstring = (
+                                               '16712196'
+                                               , '4817089'
+                                               , '1959610'
+                                               , '11878895'
+                                               , '10526880'
+                                               , '2146048'
+                                               , '16681742'
+                                               , '6239749'
+                                               , '16776961');
+
+implementation
+uses uPhysFSLayer;
+
+function getThemesList: PPChar; cdecl;
+var list, res, tmp: PPChar;
+    i, size: Longword;
+begin
+    list:= pfsEnumerateFiles('Themes');
+    size:= 0;
+    tmp:= list;
+    while tmp^ <> nil do
+    begin
+        inc(size);
+        inc(tmp)
+    end;
+
+    res:= GetMem((3 + size) * sizeof(PChar));
+    res^:= PChar(list);
+    inc(res);
+    res^:= PChar(res + size + 2);
+    inc(res);
+
+    getThemesList:= res;
+
+    for i:= 1 to size do
+    begin
+        if pfsExists('/Themes/' + shortstring(list^) + '/icon.png') then
+        begin
+            res^:= list^;
+            inc(res)
+        end;
+
+        inc(list)
+    end;
+
+    res^:= nil
+end;
+
+procedure freeThemesList(list: PPChar); cdecl;
+var listEnd: PPChar;
+begin
+    dec(list);
+    listEnd:= PPChar(list^);
+    dec(list);
+
+    pfsFreeList(PPChar(list^));
+    freeMem(list, (listEnd - list) * sizeof(PChar))
+end;
+
+function getThemeIcon(themeName: PChar; buffer: PChar; buflen: Longword): Longword; cdecl;
+var s: shortstring;
+    f: PFSFile;
+begin
+    s:= '/Themes/' + shortstring(themeName) + '/icon@2x.png';
+
+    f:= pfsOpenRead(s);
+
+    if f = nil then
+        getThemeIcon:= 0
+    else
+    begin
+        getThemeIcon:= pfsBlockRead(f, buffer, buflen);
+        pfsClose(f)
+    end;
+end;
+
+end.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hedgewars/uFLTypes.pas	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,142 @@
+unit uFLTypes;
+interface
+uses SDLh;
+
+const
+    MAXARGS = 32;
+
+type
+    TMessageType = (mtRenderingPreview, mtPreview, mtPreviewHogCount
+                    , mtAddPlayingTeam, mtRemovePlayingTeam
+                    , mtAddTeam, mtRemoveTeam, mtTeamColor, mtHedgehogsNumber
+                    , mtNetData, mtToNet
+                    , mtFlibEvent, mtConnected, mtDisconnected, mtAddLobbyClient
+                    , mtRemoveLobbyClient, mtLobbyChatLine, mtAddRoomClient
+                    , mtRemoveRoomClient, mtRoomChatLine, mtAddRoom, mtUpdateRoom
+                    , mtRemoveRoom, mtError, mtWarning, mtMoveToLobby, mtMoveToRoom
+                    , mtNickname, mtSeed, mtTheme, mtScript, mtFeatureSize, mtMapGen
+                    , mtMap, mtMazeSize, mtTemplate, mtAmmo, mtScheme);
+
+    TFLIBEvent = (flibGameFinished);
+
+    TClientFlag = (cfReady, cfRegistered, cfInRoom, cfContributor, cfInGame, cfRoomAdmin, cfServerAdmin);
+
+    PIPCMessage = ^TIPCMessage;
+    TIPCMessage = record
+                   str: shortstring;
+                   len: Longword;
+                   buf: Pointer;
+                   barrier: Longword;
+                   next: PIPCMessage;
+               end;
+    PIPCQueue = ^TIPCQueue;
+    TIPCQueue = record
+                   msg: TIPCMessage;
+                   mut: PSDL_Mutex;
+                   cond: PSDL_Cond;
+                   last: PIPCMessage;
+               end;
+
+    TIPCCallback = procedure (p: pointer; msg: PChar; len: Longword);
+    TUICallback = procedure (p: pointer; msgType: TMessageType; msg: PChar; len: Longword); cdecl;
+
+    TGameType = (gtPreview, gtLocal, gtNet);
+    THedgehog = record
+            name: shortstring;
+            hat: shortstring;
+            end;
+    TTeam = record
+            teamName
+            , flag
+            , grave
+            , fort
+            , voice
+            , owner: shortstring;
+            color: Longword;
+            extDriven: boolean;
+            botLevel: Longword;
+            hedgehogs: array[0..7] of THedgehog;
+            hogsNumber: Longword;
+        end;
+    PTeam = ^TTeam;
+
+    TScheme = record
+            schemeName
+            , scriptparam : shortstring;
+            fortsmode
+            , divteams
+            , solidland
+            , border
+            , lowgrav
+            , laser
+            , invulnerability
+            , mines
+            , vampiric
+            , karma
+            , artillery
+            , randomorder
+            , king
+            , placehog
+            , sharedammo
+            , disablegirders
+            , disablewind
+            , morewind
+            , tagteam
+            , resethealth
+            , disablelandobjects
+            , aisurvival
+            , infattack
+            , resetweps
+            , perhogammo
+            , bottomborder: boolean;
+            damagefactor
+            , turntime
+            , health
+            , suddendeath
+            , caseprobability
+            , minestime
+            , landadds
+            , minedudpct
+            , explosives
+            , minesnum
+            , healthprobability
+            , healthcaseamount
+            , waterrise
+            , healthdecrease
+            , ropepct
+            , getawaytime
+            , airmines
+            , worldedge: LongInt
+        end;
+    PScheme = ^TScheme;
+    TAmmo = record
+            ammoName: shortstring;
+            a, b, c, d: shortstring;
+        end;
+    PAmmo = ^TAmmo;
+
+    PGameConfig = ^TGameConfig;
+    TGameConfig = record
+            seed: shortstring;
+            theme: shortstring;
+            script: shortstring;
+            map: shortstring;
+            drawnData: array[word] of char;
+            drawnDataSize: LongWord;
+            scheme: TScheme;
+            ammo: TAmmo;
+            mapgen: LongInt;
+            featureSize: LongInt;
+            mazesize: LongInt;
+            template: LongInt;
+            gameType: TGameType;
+            teams: array[0..7] of TTeam;
+            arguments: array[0..Pred(MAXARGS)] of shortstring;
+            argv: array[0..Pred(MAXARGS)] of PChar;
+            argumentsNumber: Longword;
+            nextConfig: PGameConfig;
+            end;
+
+implementation
+
+end.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hedgewars/uFLUICallback.pas	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,48 @@
+unit uFLUICallback;
+interface
+uses uFLTypes;
+
+procedure registerUIMessagesCallback(p: pointer; f: TUICallback); cdecl;
+procedure sendUI(msgType: TMessageType; msg: PChar; len: Longword);
+
+implementation
+uses uFLIPC;
+
+var uiCallbackPointer: pointer;
+    uiCallbackFunction: TUICallback;
+    isGame: boolean;
+
+procedure engineMessageCallback(p: pointer; msg: PChar; len: Longword);
+begin
+    if (len >= 3) and (msg[1] = 'T') then
+    begin
+        isGame:= msg[2] = 'G';
+        exit;
+    end;
+
+    if isGame then
+    begin
+        uiCallbackFunction(uiCallbackPointer, mtToNet, msg, len)
+    end
+    else begin
+        if len = 128 * 256 then
+            uiCallbackFunction(uiCallbackPointer, mtPreview, msg, len)
+        else if len = 1 then
+            uiCallbackFunction(uiCallbackPointer, mtPreviewHogCount, msg, len)
+    end;
+end;
+
+procedure registerUIMessagesCallback(p: pointer; f: TUICallback); cdecl;
+begin
+    uiCallbackPointer:= p;
+    uiCallbackFunction:= f;
+
+    registerIPCCallback(nil, @engineMessageCallback)
+end;
+
+procedure sendUI(msgType: TMessageType; msg: PChar; len: Longword);
+begin
+    uiCallbackFunction(uiCallbackPointer, msgType, msg, len)
+end;
+
+end.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hedgewars/uFLUtils.pas	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,80 @@
+unit uFLUtils;
+interface
+
+function str2PChar(const s: shortstring): PChar;
+function intToStr(n: LongInt): shortstring;
+function strToInt(s: shortstring): LongInt;
+function midStr(s: shortstring; pos: byte): shortstring;
+procedure underScore2Space(var s: shortstring);
+function readInt(name, input: shortstring; var value: LongInt): boolean;
+function readBool(name, input: shortstring; var value: boolean): boolean;
+
+implementation
+
+var
+    str2PCharBuffer: array[0..255] of char;
+
+function str2PChar(const s: shortstring): PChar;
+var i: Integer;
+begin
+   for i:= 1 to Length(s) do
+      begin
+      str2PCharBuffer[i - 1] := s[i];
+      end;
+   str2PCharBuffer[Length(s)]:= #0;
+   str2PChar:= @(str2PCharBuffer[0]);
+end;
+
+function intToStr(n: LongInt): shortstring;
+begin
+    str(n, intToStr)
+end;
+
+function strToInt(s: shortstring): LongInt;
+begin
+val(s, strToInt);
+end;
+
+function midStr(s: shortstring; pos: byte): shortstring;
+begin
+    midStr:= copy(s, pos, length(s) - pos + 1)
+end;
+
+procedure underScore2Space(var s: shortstring);
+var i: LongInt;
+begin
+    for i:= length(s) downto 1 do
+        if s[i] = '_' then s[i]:= ' '
+end;
+
+function readInt(name, input: shortstring; var value: LongInt): boolean;
+var l: LongInt;
+begin
+    name:= name + '=';
+    l:= length(name);
+
+    if copy(input, 1, l) = name then
+    begin
+        value:= strToInt(midStr(input, l + 1));
+        readInt:= true
+    end
+    else
+        readInt:= false
+end;
+
+function readBool(name, input: shortstring; var value: boolean): boolean;
+var l: LongInt;
+begin
+    name:= name + '=';
+    l:= length(name);
+
+    if copy(input, 1, l) = name then
+    begin
+        value:= (length(input) > l) and (input[l + 1] <> 'f');
+        readBool:= true
+    end
+    else
+        readBool:= false
+end;
+
+end.
--- a/hedgewars/uIO.pas	Sat Dec 16 23:26:13 2017 +0100
+++ b/hedgewars/uIO.pas	Sun Dec 17 00:09:24 2017 +0100
@@ -25,7 +25,6 @@
 procedure initModule;
 procedure freeModule;
 
-procedure InitIPC;
 procedure SendIPC(s: shortstring);
 procedure SendIPCXY(cmd: char; X, Y: LongInt);
 procedure SendIPCRaw(p: pointer; len: Longword);
@@ -39,7 +38,7 @@
 procedure doPut(putX, putY: LongInt; fromAI: boolean);
 
 implementation
-uses uConsole, uConsts, uVariables, uCommands, uUtils, uDebug;
+uses uFLIPC, uFLTypes, uConsole, uConsts, uVariables, uCommands, uUtils, uDebug;
 
 const
     cSendEmptyPacketTime = 1000;
@@ -55,15 +54,14 @@
             2: (str: shortstring);
             end;
 
-var IPCSock: PTCPSocket;
-    fds: PSDLNet_SocketSet;
+var
     isPonged: boolean;
-    SocketString: shortstring;
 
     headcmd: PCmd;
     lastcmd: PCmd;
 
     flushDelayTicks: LongWord;
+    SocketString: shortstring;
     sendBuffer: record
                 buf: array[0..Pred(cSendBufferSize)] of byte;
                 count: Word;
@@ -109,23 +107,6 @@
 dispose(tmp)
 end;
 
-procedure InitIPC;
-var ipaddr: TIPAddress;
-begin
-    WriteToConsole('Init SDL_Net... ');
-    SDLCheck(SDLNet_Init = 0, 'SDLNet_Init', true);
-    fds:= SDLNet_AllocSocketSet(1);
-    SDLCheck(fds <> nil, 'SDLNet_AllocSocketSet', true);
-    WriteLnToConsole(msgOK);
-    WriteToConsole('Establishing IPC connection to tcp 127.0.0.1:' + IntToStr(ipcPort) + ' ');
-    {$HINTS OFF}
-    SDLCheck(SDLNet_ResolveHost(ipaddr, PChar('127.0.0.1'), ipcPort) = 0, 'SDLNet_ResolveHost', true);
-    {$HINTS ON}
-    IPCSock:= SDLNet_TCP_Open(ipaddr);
-    SDLCheck(IPCSock <> nil, 'SDLNet_TCP_Open', true);
-    WriteLnToConsole(msgOK)
-end;
-
 procedure ParseChatCommand(command: shortstring; message: shortstring;
                            messageStartIndex: Byte);
 var
@@ -184,31 +165,37 @@
 end;
 
 procedure IPCCheckSock;
-var i: LongInt;
-    s: shortstring;
+var i, t: LongInt;
+    msg: TIPCMessage;
 begin
-    if IPCSock = nil then
-        exit;
-
-    fds^.numsockets:= 0;
-    SDLNet_AddSocket(fds, IPCSock);
-
-    while SDLNet_CheckSockets(fds, 0) > 0 do
+    while ipcCheckFromFrontend() do
     begin
-        i:= SDLNet_TCP_Recv(IPCSock, @s[1], 255 - Length(SocketString));
-        if i > 0 then
-        begin
-            s[0]:= char(i);
-            SocketString:= SocketString + s;
-            while (Length(SocketString) > 1) and (Length(SocketString) > byte(SocketString[1])) do
+        msg:= ipcReadFromFrontend();
+        if msg.str[0] > #0 then
+            ParseIPCCommand(msg.str)
+        else begin
+            i:= 0;
+            while (i < msg.len) do
             begin
-                ParseIPCCommand(copy(SocketString, 2, byte(SocketString[1])));
-                Delete(SocketString, 1, Succ(byte(SocketString[1])))
-            end
+                if LongInt(SocketString[0]) + msg.len - i > 255 then
+                    t:= 255 - byte(SocketString[0])
+                else
+                    t:= msg.len - i;
+
+                Move(PByteArray(msg.buf)^[i], SocketString[byte(SocketString[0]) + 1], t);
+                inc(byte(SocketString[0]), t);
+                inc(i, t);
+
+                while byte(SocketString[0]) > byte(SocketString[1]) do
+                begin
+                    ParseIPCCommand(copy(SocketString, 2, byte(SocketString[1])));
+                    Delete(SocketString, 1, Succ(byte(SocketString[1])))
+                end;
+            end;
+
+            FreeMem(msg.buf, msg.len)
         end
-    else
-        OutError('IPC connection lost', true)
-    end;
+    end
 end;
 
 procedure LoadRecordFromFile(fileName: shortstring);
@@ -269,18 +256,13 @@
 
 procedure flushBuffer();
 begin
-    if IPCSock <> nil then
-        begin
-        SDLNet_TCP_Send(IPCSock, @sendBuffer.buf, sendBuffer.count);
-        flushDelayTicks:= 0;
-        sendBuffer.count:= 0
-        end
+    ipcToFrontendRaw(@sendBuffer.buf, sendBuffer.count);
+    flushDelayTicks:= 0;
+    sendBuffer.count:= 0;
 end;
 
 procedure SendIPC(s: shortstring);
 begin
-if IPCSock <> nil then
-    begin
     if s[0] > #251 then
         s[0]:= #251;
 
@@ -299,27 +281,25 @@
 
         if (s[1] = 'N') or (s[1] = '#') then
             flushBuffer();
-        end else
-        SDLNet_TCP_Send(IPCSock, @s, Succ(byte(s[0])))
-    end
+        end
+    else
+        ipcToFrontendRaw(@s, Succ(byte(s[0])))
+
 end;
 
 procedure SendIPCRaw(p: pointer; len: Longword);
 begin
-if IPCSock <> nil then
-    begin
-    SDLNet_TCP_Send(IPCSock, p, len)
-    end
+    ipcToFrontendRaw(p, len)
 end;
 
 procedure SendIPCXY(cmd: char; X, Y: LongInt);
 var s: shortstring;
 begin
-s[0]:= #9;
-s[1]:= cmd;
-SDLNet_Write32(X, @s[2]);
-SDLNet_Write32(Y, @s[6]);
-SendIPC(s)
+    s[0]:= #9;
+    s[1]:= cmd;
+    SDLNet_Write32(X, @s[2]);
+    SDLNet_Write32(Y, @s[6]);
+    SendIPC(s)
 end;
 
 procedure IPCWaitPongEvent;
@@ -453,13 +433,13 @@
 procedure chFatalError(var s: shortstring);
 begin
     SendIPC('E' + s);
-    // TODO: should we try to clean more stuff here?
+{    // TODO: should we try to clean more stuff here?
     SDL_Quit;
 
     if IPCSock <> nil then
         halt(HaltFatalError)
     else
-        halt(HaltFatalErrorNoIPC);
+        halt(HaltFatalErrorNoIPC);}
 end;
 
 procedure doPut(putX, putY: LongInt; fromAI: boolean);
@@ -515,9 +495,6 @@
 begin
     RegisterVariable('fatal', @chFatalError, true );
 
-    IPCSock:= nil;
-    fds:= nil;
-
     headcmd:= nil;
     lastcmd:= nil;
     isPonged:= false;
@@ -531,10 +508,6 @@
 procedure freeModule;
 begin
     while headcmd <> nil do RemoveCmd;
-    SDLNet_FreeSocketSet(fds);
-    SDLNet_TCP_Close(IPCSock);
-    SDLNet_Quit();
-
 end;
 
 end.
--- a/hedgewars/uLocale.pas	Sat Dec 16 23:26:13 2017 +0100
+++ b/hedgewars/uLocale.pas	Sun Dec 17 00:09:24 2017 +0100
@@ -133,28 +133,9 @@
     FormatA:= copy(fmt, 1, i - 1) + arg + FormatA(copy(fmt, i + 2, Length(fmt) - i - 1), arg)
 end;
 
-{$IFDEF HWLIBRARY}
 procedure LoadLocaleWrapper(path: pchar; userpath: pchar; filename: pchar); cdecl; export;
 begin
-    PathPrefix := Strpas(path);
-    UserPathPrefix := Strpas(userpath);
- 
-    //normally this var set in preInit of engine
-    allOK := true;
-    
-    uVariables.initModule;
- 
-    PathPrefix:= PathPrefix + #0;
-    UserPathPrefix:= UserPathPrefix + #0;
-    uPhysFSLayer.initModule(@PathPrefix[1], @UserPathPrefix[1]);
-    PathPrefix:= copy(PathPrefix, 1, length(PathPrefix) - 1);
-    UserPathPrefix:= copy(UserPathPrefix, 1, length(UserPathPrefix) - 1);
- 
     LoadLocale(Strpas(filename));
- 
-    uPhysFSLayer.freeModule;
-    uVariables.freeModule;
 end;
-{$ENDIF}
 
 end.
--- a/hedgewars/uMisc.pas	Sat Dec 16 23:26:13 2017 +0100
+++ b/hedgewars/uMisc.pas	Sun Dec 17 00:09:24 2017 +0100
@@ -281,11 +281,12 @@
 
 // allocate and fill structure that will be passed to new thread
 New(image); // will be disposed in SaveScreenshot()
-if dump = 2 then
+{if dump = 2 then
      image^.filename:= shortstring(UserPathPrefix) + filename + '_landpixels' + ext
 else if dump = 1 then
      image^.filename:= shortstring(UserPathPrefix) + filename + '_land' + ext
-else image^.filename:= shortstring(UserPathPrefix) + filename + ext;
+else image^.filename:= shortstring(UserPathPrefix) + filename + ext;}
+image^.filename:= filename + ext;
 
 if dump <> 0 then
     begin
--- a/hedgewars/uPhysFSLayer.pas	Sat Dec 16 23:26:13 2017 +0100
+++ b/hedgewars/uPhysFSLayer.pas	Sun Dec 17 00:09:24 2017 +0100
@@ -25,12 +25,15 @@
 function pfsOpenWrite(fname: shortstring): PFSFile;
 function pfsFlush(f: PFSFile): boolean;
 function pfsClose(f: PFSFile): boolean;
+function pfsSeek(f: PFSFile; pos: QWord): boolean;
 
 procedure pfsReadLn(f: PFSFile; var s: shortstring);
 procedure pfsReadLnA(f: PFSFile; var s: ansistring);
 procedure pfsWriteLn(f: PFSFile; s: shortstring);
 function pfsBlockRead(f: PFSFile; buf: pointer; size: Int64): Int64;
 function pfsEOF(f: PFSFile): boolean;
+function pfsEnumerateFiles(dir: shortstring): PPChar;
+procedure pfsFreeList(list: PPChar);
 
 function pfsExists(fname: shortstring): boolean;
 function pfsMakeDir(path: shortstring): boolean;
@@ -110,6 +113,11 @@
     exit(PHYSFS_close(f))
 end;
 
+function pfsSeek(f: PFSFile; pos: QWord): boolean;
+begin
+    exit(PHYSFS_seek(f, 0));
+end;
+
 function pfsExists(fname: shortstring): boolean;
 begin
     exit(PHYSFS_exists(Str2PChar(fname)))
@@ -186,11 +194,13 @@
 
 procedure pfsMount(path: PChar; mountpoint: PChar);
 begin
+    system.writeln('Mounting ',path,' at ', mountpoint);
     PHYSFS_mount(path, mountpoint, false)
 end;
 
 procedure pfsMountAtRoot(path: PChar);
 begin
+    system.writeln('Mounting ',path,' at root');
     pfsMount(path, PChar(_S'/'));
 end;
 
@@ -201,12 +211,8 @@
     fp: PChar;
 {$ENDIF}
 begin
-{$IFDEF HWLIBRARY}
     //TODO: http://icculus.org/pipermail/physfs/2011-August/001006.html
-    cPhysfsId:= shortstring(GetCurrentDir()) + {$IFDEF DARWIN}{$IFNDEF IPHONEOS}'/Hedgewars.app/Contents/MacOS/' + {$ENDIF}{$ENDIF} ' hedgewars';
-{$ELSE}
-    cPhysfsId:= ParamStr(0);
-{$ENDIF}
+    cPhysfsId:= {$IFDEF DARWIN}{$IFNDEF IPHONEOS}'/Hedgewars.app/Contents/MacOS/' + {$ENDIF}{$ENDIF} ' hedgewars';
 
     i:= PHYSFS_init(Str2PChar(cPhysfsId));
     //AddFileLog('[PhysFS] init: ' + inttostr(i));
@@ -230,9 +236,6 @@
 
     hedgewarsMountPackages;
 
-    // need access to teams and frontend configs (for bindings)
-    pfsMountAtRoot(userPrefix);
-
     if cTestLua then
         begin
             pfsMountAtRoot(Str2PChar(ExtractFileDir(cScriptName)));
--- a/hedgewars/uUtils.pas	Sat Dec 16 23:26:13 2017 +0100
+++ b/hedgewars/uUtils.pas	Sun Dec 17 00:09:24 2017 +0100
@@ -431,7 +431,7 @@
 
 
 function Str2PChar(const s: shortstring): PChar;
-var i :Integer ;
+var i: Integer;
 begin
    for i:= 1 to Length(s) do
       begin
--- a/hedgewars/uVariables.pas	Sat Dec 16 23:26:13 2017 +0100
+++ b/hedgewars/uVariables.pas	Sun Dec 17 00:09:24 2017 +0100
@@ -38,14 +38,11 @@
     cNewScreenWidth    : LongInt;
     cNewScreenHeight   : LongInt;
     cScreenResizeDelay : LongWord;
-    ipcPort            : Word;
     AprilOne           : boolean;
     cFullScreen        : boolean;
     cLocaleFName       : shortstring;
     cLocale            : shortstring;
     cTimerInterval     : LongInt;
-    PathPrefix         : ansistring;
-    UserPathPrefix     : ansistring;
     cShowFPS           : boolean;
     cFlattenFlakes     : boolean;
     cFlattenClouds     : boolean;
@@ -279,7 +276,7 @@
 const
     cPathzInit: array[TPathType] of shortstring = (
         '',                              // ptNone
-        '//',                            // ptData
+        '/',                             // ptData
         '/Graphics',                     // ptGraphics
         '/Themes',                       // ptThemes
         '/Themes/Bamboo',                // ptCurrTheme
@@ -2574,13 +2571,10 @@
     cLocaleFName    := 'en.txt';
     cFullScreen     := false;
 
-    UserPathPrefix  := '';
-    ipcPort         := 0;
     recordFileName  := '';
     UserNick        := '';
     cStereoMode     := smNone;
     GrayScale       := false;
-    PathPrefix      := './';
     GameType        := gmtLocal;
     cOnlyStats      := False;
     cScriptName     := '';
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/OfficialChallenges/racer_#17.hwmap	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,1 @@
+AAAA4Xic43vHWMfAc4SFi4FrB5DFrsEiy8DCyriegTGGRYeBIYldlIGfkX02A/di9qIO7sWsrxhARMf/K0AWLy9bUwffXiDBEst2qPP/b3bRzQwO7FIXGO3YpUAGmC0GGrVlMbsui9JiEPcSkLgDMn7LJRZWpkwGoIT6JSDxn4HvCWPvZq7NjHWbQc64wX2FSYmB7yVj8w0+VqZ0Bp6TLAybgY5kvcFzhOk0AwMDu+gmkNMuMFYC3ceSw3bhApM7u0AXgwgjbyvDJwZ9Bj5p9tBW/nvsoQwAuyw7Aw==
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlFrontend/CMakeLists.txt	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,27 @@
+cmake_minimum_required(VERSION 2.8.11)
+
+project(hedgewars)
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+set(CMAKE_AUTOMOC ON)
+
+find_package(OpenGL)
+
+find_package(Qt5 COMPONENTS Core Qml Quick Gui)
+
+qt5_add_resources(qresources qmlFrontend.qrc)
+
+add_executable(hedgewars WIN32
+    main
+    hwengine
+    previewimageprovider
+    themeiconprovider
+    qtquick2applicationviewer/qtquick2applicationviewer
+    flib.h
+    ${qresources}
+    )
+
+include_directories(${OPENGL_INCLUDE_DIR})
+
+target_link_libraries(hedgewars Qt5::Core Qt5::Gui Qt5::Quick Qt5::Qml)
+install(PROGRAMS "${EXECUTABLE_OUTPUT_PATH}/hedgewars${CMAKE_EXECUTABLE_SUFFIX}" DESTINATION ${target_binary_install_dir})
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlFrontend/flib.h	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,102 @@
+#ifndef FLIB_H
+#define FLIB_H
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum MessageType {
+    MSG_RENDERINGPREVIEW
+    , MSG_PREVIEW
+    , MSG_PREVIEWHOGCOUNT
+    , MSG_ADDPLAYINGTEAM
+    , MSG_REMOVEPLAYINGTEAM
+    , MSG_ADDTEAM
+    , MSG_REMOVETEAM
+    , MSG_TEAMCOLOR
+    , MSG_HEDGEHOGSNUMBER
+    , MSG_NETDATA
+    , MSG_TONET
+    , MSG_FLIBEVENT
+    , MSG_CONNECTED
+    , MSG_DISCONNECTED
+    , MSG_ADDLOBBYCLIENT
+    , MSG_REMOVELOBBYCLIENT
+    , MSG_LOBBYCHATLINE
+    , MSG_ADDROOMCLIENT
+    , MSG_REMOVEROOMCLIENT
+    , MSG_ROOMCHATLINE
+    , MSG_ADDROOM
+    , MSG_UPDATEROOM
+    , MSG_REMOVEROOM
+    , MSG_ERROR
+    , MSG_WARNING
+    , MSG_MOVETOLOBBY
+    , MSG_MOVETOROOM
+    , MSG_NICKNAME
+    , MSG_SEED
+    , MSG_THEME
+    , MSG_SCRIPT
+    , MSG_FEATURESIZE
+    , MSG_MAPGEN
+    , MSG_MAP
+    , MSG_MAZESIZE
+    , MSG_TEMPLATE
+    , MSG_AMMO
+    , MSG_SCHEME
+};
+
+typedef union string255_
+    {
+        struct {
+            unsigned char s[256];
+        };
+        struct {
+            unsigned char len;
+            unsigned char str[255];
+        };
+    } string255;
+
+typedef void RunEngine_t(int argc, const char ** argv);
+typedef void registerUIMessagesCallback_t(void * context, void (*)(void * context, MessageType mt, const char * msg, uint32_t len));
+typedef void getPreview_t();
+typedef void runQuickGame_t();
+typedef void runLocalGame_t();
+typedef void resetGameConfig_t();
+typedef void setSeed_t(const char * seed);
+typedef char *getSeed_t();
+typedef void setTheme_t(const char * themeName);
+typedef void setScript_t(const char * scriptName);
+typedef void setScheme_t(const char * schemeName);
+typedef void setAmmo_t(const char * ammoName);
+typedef void flibInit_t(const char * localPrefix, const char * userPrefix);
+typedef void flibFree_t();
+typedef void passNetData_t(const char * data);
+typedef void passToNet_t(const char * data, uint32_t size);
+typedef void passFlibEvent_t(const char * data);
+typedef void sendChatLine_t(const char * msg);
+typedef void joinRoom_t(const char * roomName);
+typedef void partRoom_t(const char * message);
+
+typedef char **getThemesList_t();
+typedef void freeThemesList_t(char **list);
+typedef uint32_t getThemeIcon_t(char * theme, char * buffer, uint32_t size);
+
+typedef char **getScriptsList_t();
+typedef char **getSchemesList_t();
+typedef char **getAmmosList_t();
+
+typedef char **getTeamsList_t();
+typedef void tryAddTeam_t(const char * teamName);
+typedef void tryRemoveTeam_t(const char * teamName);
+typedef void changeTeamColor_t(const char * teamName, int32_t dir);
+
+typedef void connectOfficialServer_t();
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // FLIB_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlFrontend/hwengine.cpp	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,442 @@
+#include <QLibrary>
+#include <QtQml>
+#include <QDebug>
+#include <QPainter>
+#include <QUuid>
+
+#include "hwengine.h"
+#include "previewimageprovider.h"
+#include "themeiconprovider.h"
+
+extern "C" {
+    RunEngine_t *flibRunEngine;
+    registerUIMessagesCallback_t *flibRegisterUIMessagesCallback;
+    setSeed_t *flibSetSeed;
+    getSeed_t *flibGetSeed;
+    setTheme_t *flibSetTheme;
+    setScript_t *flibSetScript;
+    setScheme_t *flibSetScheme;
+    setAmmo_t *flibSetAmmo;
+    getPreview_t *flibGetPreview;
+    runQuickGame_t *flibRunQuickGame;
+    runLocalGame_t *flibRunLocalGame;
+    flibInit_t *flibInit;
+    flibFree_t *flibFree;
+    resetGameConfig_t * flibResetGameConfig;
+    getThemesList_t *flibGetThemesList;
+    freeThemesList_t *flibFreeThemesList;
+    getThemeIcon_t *flibGetThemeIcon;
+    getScriptsList_t *flibGetScriptsList;
+    getSchemesList_t *flibGetSchemesList;
+    getAmmosList_t *flibGetAmmosList;
+    getTeamsList_t *flibGetTeamsList;
+    tryAddTeam_t * flibTryAddTeam;
+    tryRemoveTeam_t * flibTryRemoveTeam;
+    changeTeamColor_t * flibChangeTeamColor;
+
+    connectOfficialServer_t * flibConnectOfficialServer;
+    passNetData_t * flibPassNetData;
+    passToNet_t * flibPassToNet;
+    passFlibEvent_t * flibPassFlibEvent;
+    sendChatLine_t * flibSendChatLine;
+    joinRoom_t * flibJoinRoom;
+    partRoom_t * flibPartRoom;
+}
+
+Q_DECLARE_METATYPE(MessageType)
+
+HWEngine::HWEngine(QQmlEngine *engine, QObject *parent) :
+    QObject(parent),
+    m_engine(engine)
+{
+    qRegisterMetaType<MessageType>("MessageType");
+
+#ifdef Q_OS_WIN    
+    QLibrary hwlib("./libhwengine.dll");
+#else
+    QLibrary hwlib("./libhwengine.so");
+#endif
+
+    if(!hwlib.load())
+        qWarning() << "Engine library not found" << hwlib.errorString();
+
+    flibRunEngine = (RunEngine_t*) hwlib.resolve("RunEngine");
+    flibRegisterUIMessagesCallback = (registerUIMessagesCallback_t*) hwlib.resolve("registerUIMessagesCallback");
+    flibGetSeed = (getSeed_t*) hwlib.resolve("getSeed");
+    flibGetPreview = (getPreview_t*) hwlib.resolve("getPreview");
+    flibRunQuickGame = (runQuickGame_t*) hwlib.resolve("runQuickGame");
+    flibRunLocalGame = (runLocalGame_t*) hwlib.resolve("runLocalGame");
+    flibInit = (flibInit_t*) hwlib.resolve("flibInit");
+    flibFree = (flibFree_t*) hwlib.resolve("flibFree");
+
+    flibSetSeed = (setSeed_t*) hwlib.resolve("setSeed");
+    flibSetTheme = (setTheme_t*) hwlib.resolve("setTheme");
+    flibSetScript = (setScript_t*) hwlib.resolve("setScript");
+    flibSetScheme = (setScheme_t*) hwlib.resolve("setScheme");
+    flibSetAmmo = (setAmmo_t*) hwlib.resolve("setAmmo");
+
+    flibGetThemesList = (getThemesList_t*) hwlib.resolve("getThemesList");
+    flibFreeThemesList = (freeThemesList_t*) hwlib.resolve("freeThemesList");
+    flibGetThemeIcon = (getThemeIcon_t*) hwlib.resolve("getThemeIcon");
+
+    flibGetScriptsList = (getScriptsList_t*) hwlib.resolve("getScriptsList");
+    flibGetSchemesList = (getSchemesList_t*) hwlib.resolve("getSchemesList");
+    flibGetAmmosList = (getAmmosList_t*) hwlib.resolve("getAmmosList");
+
+    flibResetGameConfig = (resetGameConfig_t*) hwlib.resolve("resetGameConfig");
+    flibGetTeamsList = (getTeamsList_t*) hwlib.resolve("getTeamsList");
+    flibTryAddTeam = (tryAddTeam_t*) hwlib.resolve("tryAddTeam");
+    flibTryRemoveTeam = (tryRemoveTeam_t*) hwlib.resolve("tryRemoveTeam");
+    flibChangeTeamColor = (changeTeamColor_t*) hwlib.resolve("changeTeamColor");
+
+    flibConnectOfficialServer = (connectOfficialServer_t*) hwlib.resolve("connectOfficialServer");
+    flibPassNetData = (passNetData_t*) hwlib.resolve("passNetData");
+    flibPassToNet = (passToNet_t*) hwlib.resolve("passToNet");
+    flibPassFlibEvent = (passFlibEvent_t*) hwlib.resolve("passFlibEvent");
+    flibSendChatLine = (sendChatLine_t*) hwlib.resolve("sendChatLine");
+    flibJoinRoom = (joinRoom_t*) hwlib.resolve("joinRoom");
+    flibPartRoom = (partRoom_t*) hwlib.resolve("partRoom");
+
+    flibInit("/usr/home/unC0Rr/Sources/Hedgewars/MainRepo/share/hedgewars/Data", "/usr/home/unC0Rr/.hedgewars");
+    flibRegisterUIMessagesCallback(this, &guiMessagesCallback);
+
+    ThemeIconProvider * themeIcon = (ThemeIconProvider *)m_engine->imageProvider(QLatin1String("theme"));
+    themeIcon->setFileContentsFunction(flibGetThemeIcon);
+
+    fillModels();
+}
+
+HWEngine::~HWEngine()
+{
+    flibFree();
+}
+
+void HWEngine::getPreview()
+{
+    flibSetSeed(QUuid::createUuid().toString().toLatin1());
+    flibGetPreview();
+}
+
+void HWEngine::runQuickGame()
+{
+    flibSetSeed(QUuid::createUuid().toString().toLatin1());
+    flibRunQuickGame();
+}
+
+void HWEngine::runLocalGame()
+{
+    flibRunLocalGame();
+}
+
+
+static QObject *hwengine_singletontype_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
+{
+    Q_UNUSED(scriptEngine)
+
+    HWEngine *hwengine = new HWEngine(engine);
+    return hwengine;
+}
+
+void HWEngine::exposeToQML()
+{
+    qDebug("HWEngine::exposeToQML");
+    qmlRegisterSingletonType<HWEngine>("Hedgewars.Engine", 1, 0, "HWEngine", hwengine_singletontype_provider);
+}
+
+
+void HWEngine::guiMessagesCallback(void *context, MessageType mt, const char * msg, uint32_t len)
+{
+    HWEngine * obj = (HWEngine *)context;
+    QByteArray b = QByteArray(msg, len);
+
+    //qDebug() << "FLIPC in" << mt << " size = " << b.size();
+
+    QMetaObject::invokeMethod(obj, "engineMessageHandler", Qt::QueuedConnection, Q_ARG(MessageType, mt), Q_ARG(QByteArray, b));
+}
+
+void HWEngine::engineMessageHandler(MessageType mt, const QByteArray &msg)
+{
+    switch(mt)
+    {
+    case MSG_RENDERINGPREVIEW: {
+        emit previewIsRendering();
+        break;
+    }
+    case MSG_PREVIEW: {
+        PreviewImageProvider * preview = (PreviewImageProvider *)m_engine->imageProvider(QLatin1String("preview"));
+        preview->setPixmap(msg);
+        emit previewImageChanged();
+        break;
+    }
+    case MSG_PREVIEWHOGCOUNT: {
+        emit previewHogCountChanged((quint8)msg.data()[0]);
+        break;
+    }
+    case MSG_ADDPLAYINGTEAM: {
+        QStringList l = QString::fromUtf8(msg).split('\n');
+        emit playingTeamAdded(l[1], l[0].toInt(), true);
+        break;
+    }
+    case MSG_REMOVEPLAYINGTEAM: {
+        emit playingTeamRemoved(QString::fromUtf8(msg));
+        break;
+    }
+    case MSG_ADDTEAM: {
+        emit localTeamAdded(QString::fromUtf8(msg), 0);
+        break;
+    }
+    case MSG_REMOVETEAM: {
+        emit localTeamRemoved(QString::fromUtf8(msg));
+        break;
+    }
+    case MSG_TEAMCOLOR: {
+        QStringList l = QString::fromUtf8(msg).split('\n');
+        emit teamColorChanged(l[0], QColor::fromRgba(l[1].toInt()).name());
+        break;
+    }
+    case MSG_HEDGEHOGSNUMBER: {
+        QStringList l = QString::fromUtf8(msg).split('\n');
+        emit hedgehogsNumberChanged(l[0], l[1].toInt());
+        break;
+    }
+    case MSG_NETDATA: {
+        flibPassNetData(msg.constData());
+        break;
+    }
+    case MSG_TONET: {
+        flibPassToNet(msg.constData(), msg.size());
+        break;
+    }
+    case MSG_FLIBEVENT: {
+        flibPassFlibEvent(msg.constData());
+        break;
+    }
+    case MSG_CONNECTED: {
+        emit netConnected();
+        break;
+    }
+    case MSG_DISCONNECTED: {
+        emit netDisconnected(QString::fromUtf8(msg));
+        break;
+    }
+    case MSG_ADDLOBBYCLIENT: {
+        emit lobbyClientAdded(QString::fromUtf8(msg));
+        break;
+    }
+    case MSG_REMOVELOBBYCLIENT: {
+        QStringList l = QString::fromUtf8(msg).split('\n');
+        if(l.size() < 2)
+            l.append("");
+        emit lobbyClientRemoved(l[0], l[1]);
+        break;
+    }
+    case MSG_LOBBYCHATLINE: {
+        QStringList l = QString::fromUtf8(msg).split('\n');
+        emit lobbyChatLine(l[0], l[1]);
+        break;
+    }
+    case MSG_ADDROOMCLIENT: {
+        emit roomClientAdded(QString::fromUtf8(msg));
+        break;
+    }
+    case MSG_REMOVEROOMCLIENT: {
+        QStringList l = QString::fromUtf8(msg).split('\n');
+        if(l.size() < 2)
+            l.append("");
+        emit roomClientRemoved(l[0], l[1]);
+        break;
+    }
+    case MSG_ROOMCHATLINE: {
+        QStringList l = QString::fromUtf8(msg).split('\n');
+        emit roomChatLine(l[0], l[1]);
+        break;
+    }
+    case MSG_ADDROOM: {
+        QStringList l = QString::fromUtf8(msg).split('\n');
+        emit roomAdded(0, l[1], l[2].toInt(), l[3].toInt(), l[4], l[5], l[6], l[7], l[8]);
+        break;
+    }
+    case MSG_UPDATEROOM: {
+        QStringList l = QString::fromUtf8(msg).split('\n');
+        emit roomUpdated(l[0], 0, l[2], l[3].toInt(), l[4].toInt(), l[5], l[6], l[7], l[8], l[9]);
+        break;
+    }
+    case MSG_REMOVEROOM: {
+        emit roomRemoved(QString::fromUtf8(msg));
+        break;
+    }
+    case MSG_ERROR: {
+        emit errorMessage(QString::fromUtf8(msg));
+        break;
+    }
+    case MSG_WARNING: {
+        emit warningMessage(QString::fromUtf8(msg));
+        break;
+    }
+    case MSG_MOVETOLOBBY: {
+        emit movedToLobby();
+        break;
+    }
+    case MSG_MOVETOROOM: {
+        emit movedToRoom();
+        break;
+    }
+    case MSG_NICKNAME: {
+        m_myNickname = QString::fromUtf8(msg);
+        break;
+    }
+    case MSG_SEED: {
+        emit seedChanged(QString::fromUtf8(msg));
+        break;
+    }
+    case MSG_THEME: {
+        emit themeChanged(QString::fromUtf8(msg));
+        break;
+    }
+    case MSG_SCRIPT: {
+        emit scriptChanged(QString::fromUtf8(msg));
+        break;
+    }
+    case MSG_FEATURESIZE: {
+        emit featureSizeChanged(msg.toInt());
+        break;
+    }
+    case MSG_MAPGEN: {
+        emit mapGenChanged(msg.toInt());
+        break;
+    }
+    case MSG_MAP: {
+        emit mapChanged(QString::fromUtf8(msg));
+        break;
+    }
+    case MSG_MAZESIZE: {
+        emit mazeSizeChanged(msg.toInt());
+        break;
+    }
+    case MSG_TEMPLATE: {
+        emit templateChanged(msg.toInt());
+        break;
+    }
+    case MSG_AMMO: {
+        emit ammoChanged(QString::fromUtf8(msg));
+        break;
+    }
+    case MSG_SCHEME: {
+        emit schemeChanged(QString::fromUtf8(msg));
+        break;
+    }
+    }
+}
+
+QString HWEngine::currentSeed()
+{
+    return QString::fromLatin1(flibGetSeed());
+}
+
+void HWEngine::fillModels()
+{
+    QStringList resultModel;
+
+    char ** themes = flibGetThemesList();
+    for (char **i = themes; *i != NULL; i++)
+        resultModel << QString::fromUtf8(*i);
+    flibFreeThemesList(themes);
+
+    m_engine->rootContext()->setContextProperty("themesModel", QVariant::fromValue(resultModel));
+
+    // scripts model
+    resultModel.clear();
+    for (char **i = flibGetScriptsList(); *i != NULL; i++)
+        resultModel << QString::fromUtf8(*i);
+
+    m_engine->rootContext()->setContextProperty("scriptsModel", QVariant::fromValue(resultModel));
+
+    // schemes model
+    resultModel.clear();
+    for (char **i = flibGetSchemesList(); *i != NULL; i++)
+        resultModel << QString::fromUtf8(*i);
+
+    m_engine->rootContext()->setContextProperty("schemesModel", QVariant::fromValue(resultModel));
+
+    // ammos model
+    resultModel.clear();
+    for (char **i = flibGetAmmosList(); *i != NULL; i++)
+        resultModel << QString::fromUtf8(*i);
+
+    m_engine->rootContext()->setContextProperty("ammosModel", QVariant::fromValue(resultModel));
+}
+
+void HWEngine::getTeamsList()
+{
+    char ** teams = flibGetTeamsList();
+    for (char **i = teams; *i != NULL; i++) {
+        QString team = QString::fromUtf8(*i);
+
+        emit localTeamAdded(team, 0);
+    }
+}
+
+void HWEngine::tryAddTeam(const QString &teamName)
+{
+    flibTryAddTeam(teamName.toUtf8().constData());
+}
+
+void HWEngine::tryRemoveTeam(const QString &teamName)
+{
+    flibTryRemoveTeam(teamName.toUtf8().constData());
+}
+
+void HWEngine::resetGameConfig()
+{
+    flibResetGameConfig();
+}
+
+void HWEngine::changeTeamColor(const QString &teamName, int dir)
+{
+    flibChangeTeamColor(teamName.toUtf8().constData(), dir);
+}
+
+void HWEngine::connect(const QString &host, quint16 port)
+{
+    flibConnectOfficialServer();
+}
+
+void HWEngine::sendChatMessage(const QString &msg)
+{
+    flibSendChatLine(msg.toUtf8().constData());
+}
+
+void HWEngine::joinRoom(const QString &roomName)
+{
+    flibJoinRoom(roomName.toUtf8().constData());
+}
+
+void HWEngine::partRoom(const QString &message)
+{
+    flibPartRoom(message.toUtf8().constData());
+}
+
+QString HWEngine::myNickname()
+{
+    return m_myNickname;
+}
+
+void HWEngine::setTheme(const QString &theme)
+{
+    flibSetTheme(theme.toUtf8().constData());
+}
+
+void HWEngine::setScript(const QString &script)
+{
+    flibSetScript(script.toUtf8().constData());
+}
+
+void HWEngine::setScheme(const QString &scheme)
+{
+    flibSetScheme(scheme.toUtf8().constData());
+}
+
+void HWEngine::setAmmo(const QString &ammo)
+{
+    flibSetAmmo(ammo.toUtf8().constData());
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlFrontend/hwengine.h	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,122 @@
+#ifndef HWENGINE_H
+#define HWENGINE_H
+
+#include <QObject>
+#include <QByteArray>
+#include <QVector>
+#include <QPixmap>
+
+#include "flib.h"
+
+class QQmlEngine;
+
+class HWEngine : public QObject
+{
+    Q_OBJECT
+public:
+    explicit HWEngine(QQmlEngine * engine, QObject *parent = 0);
+    ~HWEngine();
+
+    static void exposeToQML();
+    Q_INVOKABLE void getPreview();
+    Q_INVOKABLE void runQuickGame();
+    Q_INVOKABLE void runLocalGame();
+    Q_INVOKABLE QString currentSeed();
+    Q_INVOKABLE void getTeamsList();
+    Q_INVOKABLE void resetGameConfig();
+
+    Q_INVOKABLE void setTheme(const QString & theme);
+    Q_INVOKABLE void setScript(const QString & script);
+    Q_INVOKABLE void setScheme(const QString & scheme);
+    Q_INVOKABLE void setAmmo(const QString & ammo);
+
+    Q_INVOKABLE void tryAddTeam(const QString & teamName);
+    Q_INVOKABLE void tryRemoveTeam(const QString & teamName);
+    Q_INVOKABLE void changeTeamColor(const QString & teamName, int dir);
+
+    Q_INVOKABLE void connect(const QString & host, quint16 port);
+
+    Q_INVOKABLE void sendChatMessage(const QString & msg);
+
+    Q_INVOKABLE void joinRoom(const QString & roomName);
+    Q_INVOKABLE void partRoom(const QString & message);
+
+    Q_INVOKABLE QString myNickname();
+
+signals:
+    void errorMessage(const QString & message);
+    void warningMessage(const QString & message);
+
+    void previewIsRendering();
+    void previewImageChanged();
+    void previewHogCountChanged(int count);
+    void localTeamAdded(const QString & teamName, int aiLevel);
+    void localTeamRemoved(const QString & teamName);
+
+    void playingTeamAdded(const QString & teamName, int aiLevel, bool isLocal);
+    void playingTeamRemoved(const QString & teamName);
+
+    void teamColorChanged(const QString & teamName, const QString & colorValue);
+    void hedgehogsNumberChanged(const QString & teamName, int hedgehogsNumber);
+
+    void netConnected();
+    void netDisconnected(const QString & message);
+
+    void lobbyClientAdded(const QString & clientName);
+    void lobbyClientRemoved(const QString & clientName, const QString & reason);
+    void lobbyChatLine(const QString & nickname, const QString & line);
+
+    void roomClientAdded(const QString & clientName);
+    void roomClientRemoved(const QString & clientName, const QString & reason);
+    void roomChatLine(const QString & nickname, const QString & line);
+
+    void movedToLobby();
+    void movedToRoom();
+
+    void seedChanged(const QString & seed);
+    void themeChanged(const QString & theme);
+    void scriptChanged(const QString & script);
+    void featureSizeChanged(int featureSize);
+    void mapGenChanged(int mapgen);
+    void mapChanged(const QString & map);
+    void mazeSizeChanged(int mazeSize);
+    void templateChanged(int templ);
+    void ammoChanged(const QString & ammo);
+    void schemeChanged(const QString & scheme);
+
+    void roomAdded(quint32 flags
+                   , const QString & name
+                   , int players
+                   , int teams
+                   , const QString & host
+                   , const QString & map
+                   , const QString & script
+                   , const QString & scheme
+                   , const QString & weapons);
+    void roomUpdated(const QString & name
+                   , quint32 flags
+                   , const QString & newName
+                   , int players
+                   , int teams
+                   , const QString & host
+                   , const QString & map
+                   , const QString & script
+                   , const QString & scheme
+                   , const QString & weapons);
+    void roomRemoved(const QString & name);
+
+public slots:
+
+private:
+    QQmlEngine * m_engine;
+    QString m_myNickname;
+
+    static void guiMessagesCallback(void * context, MessageType mt, const char * msg, uint32_t len);
+    void fillModels();
+
+private slots:
+    void engineMessageHandler(MessageType mt, const QByteArray &msg);
+};
+
+#endif // HWENGINE_H
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlFrontend/main.cpp	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,27 @@
+#include <QtGui/QGuiApplication>
+#include <QQmlEngine>
+
+#include "qtquick2applicationviewer/qtquick2applicationviewer.h"
+#include "hwengine.h"
+#include "previewimageprovider.h"
+#include "themeiconprovider.h"
+
+
+int main(int argc, char *argv[])
+{
+    QGuiApplication app(argc, argv);
+
+    HWEngine::exposeToQML();
+
+    Q_INIT_RESOURCE(qmlFrontend);
+
+    QtQuick2ApplicationViewer viewer;
+
+    viewer.engine()->addImageProvider(QLatin1String("preview"), new PreviewImageProvider());
+    viewer.engine()->addImageProvider(QLatin1String("theme"), new ThemeIconProvider());
+
+    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/previewimageprovider.cpp	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,36 @@
+#include "previewimageprovider.h"
+
+PreviewImageProvider::PreviewImageProvider()
+        : QQuickImageProvider(QQuickImageProvider::Pixmap)
+{
+}
+
+QPixmap PreviewImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize)
+{
+    Q_UNUSED(id);
+    Q_UNUSED(requestedSize);
+
+    if (size)
+        *size = m_px.size();
+
+    return m_px;
+}
+
+void PreviewImageProvider::setPixmap(const QByteArray &px)
+{
+    QVector<QRgb> colorTable;
+    colorTable.resize(256);
+    for(int i = 0; i < 256; ++i)
+        colorTable[i] = qRgba(255, 255, 0, i);
+
+    const quint8 *buf = (const quint8*) px.constData();
+    QImage im(buf, 256, 128, QImage::Format_Indexed8);
+    im.setColorTable(colorTable);
+
+    m_px = QPixmap::fromImage(im, Qt::ColorOnly);
+    //QPixmap pxres(px.size());
+    //QPainter p(&pxres);
+
+    //p.fillRect(pxres.rect(), linearGrad);
+    //p.drawPixmap(0, 0, px);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlFrontend/previewimageprovider.h	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,21 @@
+#ifndef PREVIEWIMAGEPROVIDER_H
+#define PREVIEWIMAGEPROVIDER_H
+
+#include <QQuickImageProvider>
+#include <QPixmap>
+#include <QSize>
+
+class PreviewImageProvider : public QQuickImageProvider
+{
+public:
+    PreviewImageProvider();
+
+    QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize);
+
+    void setPixmap(const QByteArray & px);
+
+private:
+    QPixmap m_px;
+};
+
+#endif // PREVIEWIMAGEPROVIDER_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlFrontend/qml/qmlFrontend/Chat.qml	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,137 @@
+import QtQuick 2.0
+import Hedgewars.Engine 1.0
+
+Rectangle {
+    color: "#15193a"
+    radius: 8
+    border.width: 4
+    border.color: "#ea761d"
+
+    ListView {
+        id: chatLines
+        x: 0
+        width: parent.width - clientsList.width
+        anchors.top: parent.top
+        anchors.bottom: input.top
+        focus: true
+        clip: true
+        highlightFollowsCurrentItem: true
+
+        model: ListModel {
+            id: chatLinesModel
+        }
+
+        delegate: Rectangle {
+            id: chatLinesDelegate
+            height: 24
+            width: parent.width
+            color: "transparent"
+
+            Row {
+                spacing: 8;
+                Text {
+                    color: "#ffffa0"
+                    text: nick
+
+                    MouseArea {
+                         z: 1
+                         anchors.fill: parent
+                         onClicked: ;
+                    }
+                }
+                Text {
+                    color: "#ffffff"
+                    text: line
+
+                    MouseArea {
+                         z: 1
+                         anchors.fill: parent
+                         onClicked: ;
+                    }
+                }
+            }
+
+        }
+
+        function addLine(nickname, line) {
+            chatLinesModel.append({"nick" : nickname, "line": line})
+            if(chatLinesModel.count > 200)
+                chatLinesModel.remove(0)
+            chatLines.currentIndex = chatLinesModel.count - 1
+        }
+    }
+
+    TextInput {
+        id: input
+        x: 0
+        width: chatLines.width
+        height: 24
+        anchors.bottom: parent.bottom
+        color: "#eccd2f"
+
+        onAccepted: {
+            HWEngine.sendChatMessage(text)
+            chatLines.addLine(HWEngine.myNickname(), text)
+            text = ""
+        }
+    }
+
+    ListView {
+        id: clientsList
+        x: parent.width - width
+        width: 100
+        height: parent.height
+        focus: true
+        clip: true
+
+        model: ListModel {
+            id: chatClientsModel
+        }
+
+        delegate: Rectangle {
+            id: chatClientDelegate
+            height: 24
+            width: parent.width
+            color: "transparent"
+
+            Row {
+                Text {
+                    color: "#ffffff"
+                    text: name
+
+                    MouseArea {
+                         z: 1
+                         anchors.fill: parent
+                         onClicked: ;
+                    }
+                }
+            }
+        }
+    }
+
+    function addChatLine(nickname, line) {
+        chatLines.addLine(nickname, line)
+    }
+
+    function addClient(clientName) {
+        chatClientsModel.append({"isAdmin": false, "name": clientName})
+        chatLines.addLine("***", qsTr("%1 joined").arg(clientName))
+    }
+
+    function removeClient(clientName, reason) {
+        var i = chatClientsModel.count - 1;
+        while ((i >= 0) && (chatClientsModel.get(i).name !== clientName)) --i;
+
+        if(i >= 0) {
+            chatClientsModel.remove(i, 1);
+            chatLines.addLine("***", qsTr("%1 quit (%2)").arg(clientName).arg(reason))
+        }
+    }
+
+    function clear() {
+        chatClientsModel.clear()
+        chatLinesModel.clear()
+    }
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlFrontend/qml/qmlFrontend/Connect.qml	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,14 @@
+import QtQuick 2.0
+import Hedgewars.Engine 1.0
+
+Rectangle {
+    HWButton {
+        id: btnNetConnect
+        x: 80
+        y: 80
+        width: 256
+        height: 128
+
+        onClicked: HWEngine.connect("netserver.hedgewars.org", 46631);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlFrontend/qml/qmlFrontend/First.qml	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,31 @@
+import QtQuick 2.0
+
+Rectangle {
+    HWButton {
+        id: btnLocalGame
+        x: 8
+        y: 80
+        width: 166
+        height: 166
+
+        onClicked: pages.currentPage = "LocalGame"
+    }
+
+    HWButton {
+        id: btnNetwork
+        x: 192
+        y: 80
+        width: 166
+        height: 166
+
+        onClicked: pages.currentPage = "Connect"
+    }
+
+    HWButton {
+        id: btnAbout
+        x: 100
+        y: 16
+        width: 200
+        height: 50
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlFrontend/qml/qmlFrontend/GameConfig.qml	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,366 @@
+import QtQuick 2.0
+import Hedgewars.Engine 1.0
+
+
+Rectangle {
+    Column {
+        spacing: 8
+
+    HWButton {
+        id: btnPreview
+        // this, as well as the positions of images, seems to have the border thickness (3px) into account
+        width: 262
+        height: 134
+
+        onClicked: HWEngine.getPreview()
+
+        Connections {
+            target: HWEngine
+            onPreviewImageChanged: {
+                previewImage.visible = true
+                previewImage.source = "image://preview/image"
+                previewWaitImage.visible = false
+            }
+            onPreviewIsRendering: {
+                previewImage.source = ""
+                previewImage.visible = false
+                previewWaitImage.visible = true
+            }
+            onPreviewHogCountChanged: {
+                hogCount.text = count
+            }
+        }
+
+        Image {
+            id: previewBackground
+            x: 3
+            y: 3
+            width: 256
+            height: 128
+            cache: true
+            source: "qrc:/res/prevBg.png"
+        }
+
+        Image {
+            id: previewImage
+            x: 3
+            y: 3
+            width: 256
+            height: 128
+            cache: false
+            source: ""
+        }
+
+        Image {
+            id: previewWaitImage
+            x: 117
+            y: 53
+            width: 26
+            height: 26
+            cache: true
+            source: "qrc:/res/iconTime.png"
+        }
+
+        Image {
+            id: hogCountBackground
+            x: 216
+            y: 6
+            width: 41
+            height: 21
+            cache: true
+            source: "qrc:/res/hogCntBg.png"
+        }
+
+        Text {
+            id: hogCount
+            x: 230
+            y: 10
+            text: "?"
+            horizontalAlignment: Text.AlignHRight
+            font.family: "MS Shell Dlg"
+            font.pixelSize: 12
+            color: "#ffcc00"
+        }
+    }
+
+    HWComboBox {
+        id: cbTheme
+        width: 256
+        height: 64
+
+        model: themesModel
+        delegate: Rectangle {
+            height: 25
+            width: 100
+            color: "transparent"
+
+            property alias itemIconSource: themeIcon.source
+            property alias itemText: themeName.text
+
+            Row {
+                Image {id: themeIcon; width: height; height: parent.height; source: "image://theme/" + modelData}
+                Text {id: themeName; text: modelData }
+            }
+
+            MouseArea {
+                 z: 1
+                 anchors.fill: parent
+                 onClicked: {
+                     cbTheme.currentIndex = index
+                     HWEngine.setTheme(themeName.text)
+                 }
+            }
+        }
+
+        Connections {
+            target: HWEngine
+            onThemeChanged: cbTheme.showItem({"iconSource" : "image://theme/" + theme, "text" : theme});
+        }
+    }
+
+    HWComboBox {
+        id: cbScript
+        width: 256
+        height: 32
+
+        model: scriptsModel
+        delegate: Rectangle {
+            height: 25
+            width: 100
+            color: "transparent"
+
+            property string itemIconSource: ""
+            property alias itemText: scriptName.text
+
+            Row {
+                //Image {id: themeIcon; width: height; height: parent.height; source: "image://theme/" + modelData}
+                Text {id: scriptName; text: modelData }
+            }
+
+            MouseArea {
+                 z: 1
+                 anchors.fill: parent
+                 onClicked: {
+                     cbScript.currentIndex = index
+                     HWEngine.setScript(scriptName.text)
+                 }
+            }
+        }
+        Connections {
+            target: HWEngine
+            onScriptChanged: cbScript.showItem({"iconSource" : "", "text" : script});
+        }
+    }
+
+    HWComboBox {
+        id: cbScheme
+        width: 256
+        height: 32
+
+        model: schemesModel
+        delegate: Rectangle {
+            height: 25
+            width: 100
+            color: "transparent"
+
+            property string itemIconSource: ""
+            property alias itemText: schemeName.text
+
+            Row {
+                //Image {id: themeIcon; width: height; height: parent.height; source: "image://theme/" + modelData}
+                Text {id: schemeName; text: modelData }
+            }
+
+            MouseArea {
+                 z: 1
+                 anchors.fill: parent
+                 onClicked: {
+                     cbScheme.currentIndex = index
+                     HWEngine.setScheme(schemeName.text)
+                 }
+            }
+        }
+        Connections {
+            target: HWEngine
+            onSchemeChanged: cbScheme.showItem({"iconSource" : "", "text" : scheme});
+        }
+    }
+
+
+    HWComboBox {
+        id: cbAmmo
+        width: 256
+        height: 32
+
+        model: ammosModel
+        delegate: Rectangle {
+            height: 25
+            width: 100
+            color: "transparent"
+
+            property string itemIconSource: ""
+            property alias itemText: ammoName.text
+
+            Row {
+                //Image {id: themeIcon; width: height; height: parent.height; source: "image://theme/" + modelData}
+                Text {id: ammoName; text: modelData }
+            }
+
+            MouseArea {
+                 z: 1
+                 anchors.fill: parent
+                 onClicked: {
+                     cbAmmo.currentIndex = index
+                     HWEngine.setAmmo(ammoName.text)
+                 }
+            }
+        }
+        Connections {
+            target: HWEngine
+            onAmmoChanged: cbAmmo.showItem({"iconSource" : "", "text" : ammo});
+        }
+    }
+    }
+
+    ListView {
+        id: playingTeamsList
+        x: 440
+        y: 16
+        width: 100
+        height: 192
+        highlight: Rectangle { color: "#eaea00"; radius: 4 }
+        focus: true
+        clip: true
+
+        model: ListModel {
+            id: playingTeamsModel
+        }
+
+        delegate: Rectangle {
+            id: teamDelegate
+            height: 24
+            width: parent.width
+            radius: 8
+            border.width: 2
+            border.color: "#eaea00"
+
+            Row {
+                Rectangle {
+                    height: 20
+                    width: height
+                    color: teamColor
+                    border.width: 2
+                    border.color: "#eaea00"                    
+
+                    MouseArea {
+                        z: 1
+                        anchors.fill: parent
+                        acceptedButtons: Qt.LeftButton | Qt.RightButton
+                        onClicked: {
+                            if (mouse.button === Qt.LeftButton)
+                                HWEngine.changeTeamColor(name, 1)
+                            else if (mouse.button === Qt.RightButton)
+                                HWEngine.changeTeamColor(name, -1)
+                        }
+                        onWheel: HWEngine.changeTeamColor(name, -wheel.angleDelta.y)
+                   }
+                }
+
+                Text {
+                    text: name
+                    MouseArea {
+                        z: 1
+                        anchors.fill: parent
+                        onClicked: HWEngine.tryRemoveTeam(name)
+                   }
+                }
+
+                Text {
+                    text: hedgehogsNumber
+                }
+            }
+
+
+        }
+
+        Connections {
+            target: HWEngine
+            onPlayingTeamAdded: playingTeamsModel.append({
+                                                             "aiLevel": aiLevel
+                                                             , "name": teamName
+                                                             , "local": isLocal
+                                                             , "hedgehogsNumber" : 4
+                                                             , "teamColor": "#000000"
+                                                         })
+            onPlayingTeamRemoved: {
+                var i = playingTeamsModel.count - 1;
+                while ((i >= 0) && (playingTeamsModel.get(i).name !== teamName)) --i
+
+                if(i >= 0) playingTeamsModel.remove(i, 1)
+            }
+            onTeamColorChanged: {
+                var i = playingTeamsModel.count - 1;
+                while ((i >= 0) && (playingTeamsModel.get(i).name !== teamName)) --i
+
+                if(i >= 0) playingTeamsModel.setProperty(i, "teamColor", colorValue)
+            }
+            onHedgehogsNumberChanged: {
+                var i = playingTeamsModel.count - 1;
+                while ((i >= 0) && (playingTeamsModel.get(i).name !== teamName)) --i
+
+                if(i >= 0) playingTeamsModel.setProperty(i, "hedgehogsNumber", hedgehogsNumber)
+            }
+        }
+    }
+
+    ListView {
+        id: localTeamsList
+        x: 440
+        y: 224
+        width: 100
+        height: 192
+        highlight: Rectangle { color: "#eaea00"; radius: 4 }
+        focus: true
+        clip: true
+
+        model: ListModel {
+            id: localTeamsModel
+        }
+
+        delegate: Rectangle {
+            id: localTeamDelegate
+            height: 24
+            width: parent.width
+            radius: 8
+            border.width: 2
+            border.color: "#eaea00"
+
+            Row {
+                Text { text: name }
+            }
+
+            MouseArea {
+                 z: 1
+                 anchors.fill: parent
+                 onClicked: HWEngine.tryAddTeam(name)
+            }
+        }
+
+        Connections {
+            target: HWEngine
+            onLocalTeamAdded: localTeamsModel.append({"aiLevel": aiLevel, "name": teamName})
+            onLocalTeamRemoved: {
+                var i = localTeamsModel.count - 1;
+                while ((i >= 0) && (localTeamsModel.get(i).name !== teamName)) --i
+
+                if(i >= 0) localTeamsModel.remove(i, 1)
+            }
+        }
+    }
+
+    Component.onCompleted: {
+        HWEngine.resetGameConfig()
+        HWEngine.getTeamsList()
+        HWEngine.getPreview()
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlFrontend/qml/qmlFrontend/HWButton.qml	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,42 @@
+import QtQuick 2.0
+
+Rectangle {
+    id: hwbutton
+    width: 360
+    height: 360
+    color: "#15193a"
+    radius: 8
+    border.width: 4
+    opacity: 1
+
+    signal clicked()
+
+    Behavior on border.color {
+        ColorAnimation {}
+    }
+
+    MouseArea {
+        id: mousearea
+        anchors.fill: parent
+        hoverEnabled: true
+        onClicked: parent.clicked()
+    }
+
+    states: [
+        State {
+            when: mousearea.containsMouse
+
+            PropertyChanges {
+                target: hwbutton
+                border.color: "#eaea00"
+            }
+        }
+        , State {
+            when: !mousearea.containsMouse
+
+            PropertyChanges {
+                target: hwbutton
+                border.color: "#ffcc00"
+            }
+    }]
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlFrontend/qml/qmlFrontend/HWComboBox.qml	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,63 @@
+import QtQuick 2.0
+import QtQuick.Window 2.1
+
+HWButton {
+    property alias model: itemsList.model
+    property alias delegate: itemsList.delegate
+    property alias currentIndex: itemsList.currentIndex
+
+    Window {
+        id: selection
+        visibility: Window.Hidden
+        modality: Qt.WindowModal
+        flags: Qt.Dialog
+
+        ListView {
+            id: itemsList
+            x: 0
+            y: 64
+            anchors.fill: parent
+            anchors.bottomMargin: 32
+            highlight: Rectangle { color: "#eaea00"; radius: 4 }
+            focus: true
+
+            onCurrentItemChanged: {
+                cbIcon.source = currentItem.itemIconSource
+                cbText.text = currentItem.itemText
+            }
+        }
+
+        HWButton {
+            x: parent.width - 32
+            y: parent.height - 32
+            width: 32
+            height: 32
+
+            onClicked: selection.visibility = Window.Hidden;
+        }
+    }
+
+    Row {
+        anchors.fill: parent
+        anchors.margins: 4
+
+        Image {
+            id: cbIcon
+            width: height
+            height: parent.height
+        }
+
+        Text {
+            id: cbText
+            height: parent.height
+            color: "#f3e520"
+        }
+    }
+
+    function showItem(item) {
+        cbIcon.source = item.iconSource
+        cbText.text = item.text
+    }
+
+    onClicked: selection.visibility = Window.Windowed
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlFrontend/qml/qmlFrontend/Lobby.qml	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,112 @@
+import QtQuick 2.0
+import Hedgewars.Engine 1.0
+
+Rectangle {
+    ListView {
+        id: roomsList
+        anchors.top: parent.top
+        anchors.left: parent.left
+        anchors.right: parent.right
+        anchors.bottom: lobbyChat.top
+        focus: true
+        clip: true
+
+        model: ListModel {
+            id: roomsListModel
+        }
+
+        delegate: Rectangle {
+            id: roomDelegate
+            height: 24
+            width: parent.width
+            color: "transparent"
+
+            Row {
+                spacing: 8;
+                Text {
+                    text: name
+                }
+                Text {
+                    text: players + " / " + teams
+                }
+                Text {
+                    text: host
+                }
+                Text {
+                    text: map
+                }
+                Text {
+                    text: script
+                }
+                Text {
+                    text: scheme
+                }
+                Text {
+                    text: weapons
+                }
+            }
+
+            MouseArea {
+                 z: 1
+                 anchors.fill: parent
+                 onDoubleClicked: HWEngine.joinRoom(name);
+            }
+        }
+
+        Connections {
+            target: HWEngine
+            onRoomAdded: roomsListModel.append({
+                               "name" : name
+                               , "players": players
+                               , "teams": teams
+                               , "host": host
+                               , "map": map
+                               , "script": script
+                               , "scheme": scheme
+                               , "weapons": weapons
+                           })
+            onRoomUpdated: {
+                var i = roomsListModel.count - 1;
+                while ((i >= 0) && (roomsListModel.get(i).name !== name)) --i
+
+                if(i >= 0) {
+                    roomsListModel.set(i, {
+                                           "name" : newName
+                                           , "players": players
+                                           , "teams": teams
+                                           , "host": host
+                                           , "map": map
+                                           , "script": script
+                                           , "scheme": scheme
+                                           , "weapons": weapons
+                                       })
+                }
+            }
+            onRoomRemoved: {
+                var i = roomsListModel.count - 1;
+                while ((i >= 0) && (roomsListModel.get(i).name !== name)) --i
+
+                if(i >= 0) roomsListModel.remove(i, 1)
+            }
+        }
+    }
+
+    Chat {
+        id: lobbyChat;
+        height: 300
+        anchors.top: undefined
+        anchors.left: parent.left
+        anchors.right: parent.right
+        anchors.bottom: parent.bottom
+
+        Connections {
+            target: HWEngine
+            onNetConnected: lobbyChat.clear()
+            onLobbyChatLine: lobbyChat.addChatLine(nickname, line)
+            onLobbyClientAdded: lobbyChat.addClient(clientName)
+            onLobbyClientRemoved: lobbyChat.removeClient(clientName, reason)
+        }
+    }
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlFrontend/qml/qmlFrontend/LocalGame.qml	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,34 @@
+import QtQuick 2.0
+import Hedgewars.Engine 1.0
+
+Rectangle {
+    HWButton {
+        id: btnQuickGame
+        x: 8
+        y: 66
+        width: 150
+        height: 150
+
+        onClicked: HWEngine.runQuickGame()
+    }
+
+    HWButton {
+        id: btnMultiplayer
+        x: 192
+        y: 66
+        width: 150
+        height: 150
+
+        onClicked: pages.currentPage = "Multiplayer"
+    }
+
+    HWButton {
+        id: btnBack
+        width: 40
+        height: 40
+        anchors.bottom: parent.bottom
+        anchors.left: parent.left
+
+        onClicked: pages.currentPage = "First"
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlFrontend/qml/qmlFrontend/Multiplayer.qml	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,32 @@
+import QtQuick 2.0
+import Hedgewars.Engine 1.0
+
+Item {
+    GameConfig {
+        anchors.left: parent.left
+        anchors.top: parent.top
+        anchors.right: parent.right
+        anchors.bottom: btnRunGame.top
+    }
+
+    HWButton {
+        id: btnBack
+        width: 40
+        height: 40
+        anchors.bottom: parent.bottom
+        anchors.left: parent.left
+
+        onClicked: pages.currentPage = "LocalGame"
+    }
+
+    HWButton {
+        id: btnRunGame
+        width: 40
+        height: 40
+        anchors.bottom: parent.bottom
+        anchors.right: parent.right
+
+        onClicked: HWEngine.runLocalGame()
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlFrontend/qml/qmlFrontend/Room.qml	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,38 @@
+import QtQuick 2.0
+import Hedgewars.Engine 1.0
+
+Rectangle {
+    HWButton {
+        id: btnBack
+        width: 40
+        height: 40
+        anchors.left: parent.left
+        anchors.bottom: parent.bottom
+
+        onClicked: HWEngine.partRoom("")
+    }
+
+    GameConfig {
+        id: gameConfig
+        anchors.left: parent.left
+        anchors.top: parent.top
+        anchors.right: parent.right
+        anchors.bottom: roomChat.top
+    }
+
+    Chat {
+        id: roomChat;
+        x: 0;
+        width: parent.width;
+        height: 250;
+        anchors.bottom: btnBack.top
+
+        Connections {
+            target: HWEngine
+            onMovedToRoom: roomChat.clear()
+            onRoomChatLine: roomChat.addChatLine(nickname, line)
+            onRoomClientAdded: roomChat.addClient(clientName)
+            onRoomClientRemoved: roomChat.removeClient(clientName, reason)
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlFrontend/qml/qmlFrontend/main.qml	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,94 @@
+import QtQuick 2.0
+import Hedgewars.Engine 1.0
+
+Rectangle {
+    id: pages
+    width: 800
+    height: 600
+
+    property variant pagesList : [
+        "First"
+        , "LocalGame"
+        , "Multiplayer"
+        , "Connect"
+        , "Lobby"
+        , "Room"
+    ];
+
+    property string  currentPage : "First";
+
+    Repeater {
+        id: pagesView
+        model: pagesList
+
+        function loadPage(page) {
+            // somehow load the page (when Loader has asynchronous == true)
+        }
+
+        delegate: Loader {
+            active: false
+            asynchronous: false
+            anchors.fill: parent
+            visible: (currentPage === modelData)
+            source: "%1.qml".arg(modelData)
+            onVisibleChanged:      loadIfNotLoaded();
+            Component.onCompleted: loadIfNotLoaded();
+
+            function loadIfNotLoaded ()
+            {
+                if (visible && !active)
+                    active = true;
+            }
+        }
+    }
+
+    Rectangle {
+        id: warningsBox
+        y: parent.height - height
+        width: parent.width - 120
+        height: 80
+        anchors.horizontalCenter: parent.horizontalCenter
+        color: "#7e3232"
+        border.color: "#d3ec2d"
+        visible: false
+        z: 2
+
+        function showMessage(message) {
+            msgBox.text = message
+            visible = true
+        }
+
+        Text {
+            id: msgBox
+            x: 0
+            y: 0
+            height: parent.height
+            font.pixelSize: 12
+            wrapMode: Text.Wrap
+        }
+        HWButton {
+            id: closeButton
+            x: parent.width - width
+            y: 0
+            width: 40
+            height: 40
+            onClicked: warningsBox.visible = false
+        }
+    }
+
+    Connections {
+        target: HWEngine
+        onNetConnected: {
+            pagesView.loadPage("Lobby");
+            pagesView.loadPage("Room");
+        }
+        onMovedToLobby: currentPage = "Lobby";
+        onMovedToRoom: currentPage = "Room";
+        onNetDisconnected: {
+            currentPage = "First";
+            warningsBox.showMessage(message);
+        }
+        onWarningMessage: warningsBox.showMessage(message);
+        onErrorMessage: warningsBox.showMessage(message);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlFrontend/qmlFrontend.qrc	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,18 @@
+<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>
+        <file>qml/qmlFrontend/Connect.qml</file>
+        <file>qml/qmlFrontend/Chat.qml</file>
+        <file>qml/qmlFrontend/Room.qml</file>
+        <file>qml/qmlFrontend/Lobby.qml</file>
+        <file>qml/qmlFrontend/Multiplayer.qml</file>
+        <file>res/iconTime.png</file>
+        <file>res/prevBg.png</file>
+        <file>res/hogCntBg.png</file>
+    </qresource>
+</RCC>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlFrontend/qtquick2applicationviewer/qtquick2applicationviewer.cpp	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,81 @@
+// checksum 0x4f6f version 0x90005
+/*
+  This file was generated by the Qt Quick 2 Application wizard of Qt Creator.
+  QtQuick2ApplicationViewer is a convenience class containing mobile device specific
+  code such as screen orientation handling. Also QML paths and debugging are
+  handled here.
+  It is recommended not to modify this file, since newer versions of Qt Creator
+  may offer an updated version of it.
+*/
+
+#include "qtquick2applicationviewer.h"
+
+#include <QtCore/QCoreApplication>
+#include <QtCore/QDir>
+#include <QtQml/QQmlEngine>
+
+class QtQuick2ApplicationViewerPrivate
+{
+    QString mainQmlFile;
+    friend class QtQuick2ApplicationViewer;
+    static QString adjustPath(const QString &path);
+};
+
+QString QtQuick2ApplicationViewerPrivate::adjustPath(const QString &path)
+{
+#if defined(Q_OS_MAC)
+    if (!QDir::isAbsolutePath(path))
+        return QString::fromLatin1("%1/../Resources/%2")
+                .arg(QCoreApplication::applicationDirPath(), path);
+#elif defined(Q_OS_BLACKBERRY)
+    if (!QDir::isAbsolutePath(path))
+        return QString::fromLatin1("app/native/%1").arg(path);
+#elif !defined(Q_OS_ANDROID)
+    QString pathInInstallDir =
+            QString::fromLatin1("%1/../%2").arg(QCoreApplication::applicationDirPath(), path);
+    if (QFileInfo(pathInInstallDir).exists())
+        return pathInInstallDir;
+    pathInInstallDir =
+            QString::fromLatin1("%1/%2").arg(QCoreApplication::applicationDirPath(), path);
+    if (QFileInfo(pathInInstallDir).exists())
+        return pathInInstallDir;
+#endif
+    return path;
+}
+
+QtQuick2ApplicationViewer::QtQuick2ApplicationViewer(QWindow *parent)
+    : QQuickView(parent)
+    , d(new QtQuick2ApplicationViewerPrivate())
+{
+    connect(engine(), SIGNAL(quit()), SLOT(close()));
+    setResizeMode(QQuickView::SizeRootObjectToView);
+}
+
+QtQuick2ApplicationViewer::~QtQuick2ApplicationViewer()
+{
+    delete d;
+}
+
+void QtQuick2ApplicationViewer::setMainQmlFile(const QString &file)
+{
+    d->mainQmlFile = QtQuick2ApplicationViewerPrivate::adjustPath(file);
+#ifdef Q_OS_ANDROID
+    setSource(QUrl(QLatin1String("assets:/")+d->mainQmlFile));
+#else
+    setSource(QUrl::fromLocalFile(d->mainQmlFile));
+#endif
+}
+
+void QtQuick2ApplicationViewer::addImportPath(const QString &path)
+{
+    engine()->addImportPath(QtQuick2ApplicationViewerPrivate::adjustPath(path));
+}
+
+void QtQuick2ApplicationViewer::showExpanded()
+{
+#if defined(Q_WS_SIMULATOR) || defined(Q_OS_QNX)
+    showFullScreen();
+#else
+    show();
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlFrontend/qtquick2applicationviewer/qtquick2applicationviewer.h	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,33 @@
+// checksum 0xfde6 version 0x90005
+/*
+  This file was generated by the Qt Quick 2 Application wizard of Qt Creator.
+  QtQuick2ApplicationViewer is a convenience class containing mobile device specific
+  code such as screen orientation handling. Also QML paths and debugging are
+  handled here.
+  It is recommended not to modify this file, since newer versions of Qt Creator
+  may offer an updated version of it.
+*/
+
+#ifndef QTQUICK2APPLICATIONVIEWER_H
+#define QTQUICK2APPLICATIONVIEWER_H
+
+#include <QtQuick/QQuickView>
+
+class QtQuick2ApplicationViewer : public QQuickView
+{
+    Q_OBJECT
+
+public:
+    explicit QtQuick2ApplicationViewer(QWindow *parent = 0);
+    virtual ~QtQuick2ApplicationViewer();
+
+    void setMainQmlFile(const QString &file);
+    void addImportPath(const QString &path);
+
+    void showExpanded();
+
+private:
+    class QtQuick2ApplicationViewerPrivate *d;
+};
+
+#endif // QTQUICK2APPLICATIONVIEWER_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlFrontend/qtquick2applicationviewer/qtquick2applicationviewer.pri	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,180 @@
+# checksum 0x7b0d version 0x90005
+# This file was generated by the Qt Quick 2 Application wizard of Qt Creator.
+# The code below adds the QtQuick2ApplicationViewer to the project and handles
+# the activation of QML debugging.
+# It is recommended not to modify this file, since newer versions of Qt Creator
+# may offer an updated version of it.
+
+QT += qml quick
+
+SOURCES += $$PWD/qtquick2applicationviewer.cpp
+HEADERS += $$PWD/qtquick2applicationviewer.h
+INCLUDEPATH += $$PWD
+# This file was generated by an application wizard of Qt Creator.
+# The code below handles deployment to Android and Maemo, aswell as copying
+# of the application data to shadow build directories on desktop.
+# It is recommended not to modify this file, since newer versions of Qt Creator
+# may offer an updated version of it.
+
+defineTest(qtcAddDeployment) {
+for(deploymentfolder, DEPLOYMENTFOLDERS) {
+    item = item$${deploymentfolder}
+    greaterThan(QT_MAJOR_VERSION, 4) {
+        itemsources = $${item}.files
+    } else {
+        itemsources = $${item}.sources
+    }
+    $$itemsources = $$eval($${deploymentfolder}.source)
+    itempath = $${item}.path
+    $$itempath= $$eval($${deploymentfolder}.target)
+    export($$itemsources)
+    export($$itempath)
+    DEPLOYMENT += $$item
+}
+
+MAINPROFILEPWD = $$PWD
+
+android-no-sdk {
+    for(deploymentfolder, DEPLOYMENTFOLDERS) {
+        item = item$${deploymentfolder}
+        itemfiles = $${item}.files
+        $$itemfiles = $$eval($${deploymentfolder}.source)
+        itempath = $${item}.path
+        $$itempath = /data/user/qt/$$eval($${deploymentfolder}.target)
+        export($$itemfiles)
+        export($$itempath)
+        INSTALLS += $$item
+    }
+
+    target.path = /data/user/qt
+
+    export(target.path)
+    INSTALLS += target
+} else:android {
+    for(deploymentfolder, DEPLOYMENTFOLDERS) {
+        item = item$${deploymentfolder}
+        itemfiles = $${item}.files
+        $$itemfiles = $$eval($${deploymentfolder}.source)
+        itempath = $${item}.path
+        $$itempath = /assets/$$eval($${deploymentfolder}.target)
+        export($$itemfiles)
+        export($$itempath)
+        INSTALLS += $$item
+    }
+
+    x86 {
+        target.path = /libs/x86
+    } else: armeabi-v7a {
+        target.path = /libs/armeabi-v7a
+    } else {
+        target.path = /libs/armeabi
+    }
+
+    export(target.path)
+    INSTALLS += target
+} else:win32 {
+    copyCommand =
+    for(deploymentfolder, DEPLOYMENTFOLDERS) {
+        source = $$MAINPROFILEPWD/$$eval($${deploymentfolder}.source)
+        source = $$replace(source, /, \\)
+        sourcePathSegments = $$split(source, \\)
+        target = $$OUT_PWD/$$eval($${deploymentfolder}.target)/$$last(sourcePathSegments)
+        target = $$replace(target, /, \\)
+        target ~= s,\\\\\\.?\\\\,\\,
+        !isEqual(source,$$target) {
+            !isEmpty(copyCommand):copyCommand += &&
+            isEqual(QMAKE_DIR_SEP, \\) {
+                copyCommand += $(COPY_DIR) \"$$source\" \"$$target\"
+            } else {
+                source = $$replace(source, \\\\, /)
+                target = $$OUT_PWD/$$eval($${deploymentfolder}.target)
+                target = $$replace(target, \\\\, /)
+                copyCommand += test -d \"$$target\" || mkdir -p \"$$target\" && cp -r \"$$source\" \"$$target\"
+            }
+        }
+    }
+    !isEmpty(copyCommand) {
+        copyCommand = @echo Copying application data... && $$copyCommand
+        copydeploymentfolders.commands = $$copyCommand
+        first.depends = $(first) copydeploymentfolders
+        export(first.depends)
+        export(copydeploymentfolders.commands)
+        QMAKE_EXTRA_TARGETS += first copydeploymentfolders
+    }
+} else:unix {
+    maemo5 {
+        desktopfile.files = $${TARGET}.desktop
+        desktopfile.path = /usr/share/applications/hildon
+        icon.files = $${TARGET}64.png
+        icon.path = /usr/share/icons/hicolor/64x64/apps
+    } else:!isEmpty(MEEGO_VERSION_MAJOR) {
+        desktopfile.files = $${TARGET}_harmattan.desktop
+        desktopfile.path = /usr/share/applications
+        icon.files = $${TARGET}80.png
+        icon.path = /usr/share/icons/hicolor/80x80/apps
+    } else { # Assumed to be a Desktop Unix
+        copyCommand =
+        for(deploymentfolder, DEPLOYMENTFOLDERS) {
+            source = $$MAINPROFILEPWD/$$eval($${deploymentfolder}.source)
+            source = $$replace(source, \\\\, /)
+            macx {
+                target = $$OUT_PWD/$${TARGET}.app/Contents/Resources/$$eval($${deploymentfolder}.target)
+            } else {
+                target = $$OUT_PWD/$$eval($${deploymentfolder}.target)
+            }
+            target = $$replace(target, \\\\, /)
+            sourcePathSegments = $$split(source, /)
+            targetFullPath = $$target/$$last(sourcePathSegments)
+            targetFullPath ~= s,/\\.?/,/,
+            !isEqual(source,$$targetFullPath) {
+                !isEmpty(copyCommand):copyCommand += &&
+                copyCommand += $(MKDIR) \"$$target\"
+                copyCommand += && $(COPY_DIR) \"$$source\" \"$$target\"
+            }
+        }
+        !isEmpty(copyCommand) {
+            copyCommand = @echo Copying application data... && $$copyCommand
+            copydeploymentfolders.commands = $$copyCommand
+            first.depends = $(first) copydeploymentfolders
+            export(first.depends)
+            export(copydeploymentfolders.commands)
+            QMAKE_EXTRA_TARGETS += first copydeploymentfolders
+        }
+    }
+    !isEmpty(target.path) {
+        installPrefix = $${target.path}
+    } else {
+        installPrefix = /opt/$${TARGET}
+    }
+    for(deploymentfolder, DEPLOYMENTFOLDERS) {
+        item = item$${deploymentfolder}
+        itemfiles = $${item}.files
+        $$itemfiles = $$eval($${deploymentfolder}.source)
+        itempath = $${item}.path
+        $$itempath = $${installPrefix}/$$eval($${deploymentfolder}.target)
+        export($$itemfiles)
+        export($$itempath)
+        INSTALLS += $$item
+    }
+
+    !isEmpty(desktopfile.path) {
+        export(icon.files)
+        export(icon.path)
+        export(desktopfile.files)
+        export(desktopfile.path)
+        INSTALLS += icon desktopfile
+    }
+
+    isEmpty(target.path) {
+        target.path = $${installPrefix}/bin
+        export(target.path)
+    }
+    INSTALLS += target
+}
+
+export (ICON)
+export (INSTALLS)
+export (DEPLOYMENT)
+export (LIBS)
+export (QMAKE_EXTRA_TARGETS)
+}
Binary file qmlFrontend/res/hogCntBg.png has changed
Binary file qmlFrontend/res/iconTime.png has changed
Binary file qmlFrontend/res/prevBg.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlFrontend/themeiconprovider.cpp	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,38 @@
+#include <QByteArray>
+#include <QDebug>
+
+#include "themeiconprovider.h"
+#include "flib.h"
+
+ThemeIconProvider::ThemeIconProvider()
+    : QQuickImageProvider(QQuickImageProvider::Image)
+{
+    getThemeIcon = 0;
+}
+
+void ThemeIconProvider::setFileContentsFunction(getThemeIcon_t *f)
+{
+    getThemeIcon = f;
+}
+
+QImage ThemeIconProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
+{
+    Q_UNUSED(requestedSize);
+
+    if(!getThemeIcon)
+        return QImage();
+
+    QByteArray buf;
+    buf.resize(65536);
+
+    char * bufptr = buf.data();
+    uint32_t fileSize = getThemeIcon(id.toUtf8().data(), bufptr, buf.size());
+    buf.truncate(fileSize);
+    //qDebug() << "ThemeIconProvider file size = " << fileSize;
+
+    QImage img = fileSize ? QImage::fromData(buf) : QImage(16, 16, QImage::Format_ARGB32);
+
+    if (size)
+        *size = img.size();
+    return img;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlFrontend/themeiconprovider.h	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,21 @@
+#ifndef THEMEICONPROVIDER_H
+#define THEMEICONPROVIDER_H
+
+#include <QQuickImageProvider>
+#include <QImage>
+
+#include "flib.h"
+
+class ThemeIconProvider : public QQuickImageProvider
+{
+public:
+    ThemeIconProvider();
+
+    void setFileContentsFunction(getThemeIcon_t *f);
+
+    QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize);
+private:
+    getThemeIcon_t *getThemeIcon;
+};
+
+#endif // THEMEICONPROVIDER_H
--- a/share/hedgewars/Data/Locale/CMakeLists.txt	Sat Dec 16 23:26:13 2017 +0100
+++ b/share/hedgewars/Data/Locale/CMakeLists.txt	Sun Dec 17 00:09:24 2017 +0100
@@ -1,5 +1,4 @@
-find_package(Qt4 REQUIRED)
-include(${QT_USE_FILE})
+find_package(Qt5 COMPONENTS LinguistTools)
 
 file(GLOB txttrans2 ??.txt)
 file(GLOB txttrans5 ?????.txt)
@@ -9,7 +8,7 @@
 file(GLOB campaignfiles campaigns_*.txt)
 file(GLOB tipfiles tips_*.xml)
 
-QT4_ADD_TRANSLATION(QM ${tsfiles})
+QT5_ADD_TRANSLATION(QM ${tsfiles})
 
 add_custom_target (release-translation ALL
         DEPENDS ${QM}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/protocolParser.hs	Sun Dec 17 00:09:24 2017 +0100
@@ -0,0 +1,203 @@
+module Main where
+
+import Text.PrettyPrint.HughesPJ
+import qualified Data.MultiMap as MM
+import Data.Maybe
+import Data.List
+import Data.Char
+import qualified Data.Set as Set
+
+data HWProtocol = Command String [CmdParam]
+    deriving Show
+
+instance Ord HWProtocol where
+    (Command a _) `compare` (Command b _) = a `compare` b    
+instance Eq HWProtocol where
+    (Command a _) == (Command b _) = a == b
+
+data CmdParam = Skip
+              | SS
+              | LS
+              | IntP
+              | Many [CmdParam]
+    deriving Show
+
+data ParseTree = PTPrefix String [ParseTree]
+               | PTCommand String HWProtocol
+    deriving Show
+
+cmd = Command
+cmd1 s p = Command s [p]
+cmd2 s p1 p2 = Command s [p1, p2]
+
+cmdName (Command n _) = n
+
+cmdParams2str (Command _ p) = "TCmdParam" ++ concatMap f p
+    where
+    f Skip = ""
+    f SS = "S"
+    f LS = "L"
+    f IntP = "i"
+    f (Many p) = ""
+
+cmdParams2handlerType (Command _ p) = "handler_" ++ concatMap f p
+    where
+    f Skip = "_"
+    f SS = "S"
+    f LS = "L"
+    f IntP = "i"
+    f (Many p) = 'M' : concatMap f p
+
+cmdParams2record cmd@(Command _ p) = renderStyle style{lineLength = 80} $ 
+    text "type " <> text (cmdParams2str cmd)
+    <> text " = record" $+$ nest 4 (
+    vcat (map (uncurry f) $ zip [1..] $ filter isRendered p) 
+    $+$ text "end;")
+    where
+    isRendered Skip = False
+    isRendered (Many _) = False
+    isRendered _ = True
+    f n Skip = empty
+    f n SS = text "str" <> int n <> text ": shortstring;"
+    f n LS = text "str" <> int n <> text ": longstring;"
+    f n IntP = text "param" <> int n <> text ": LongInt;"
+    f _ (Many _) = empty
+
+commandsDescription = [
+        cmd "CONNECTED" [Skip, IntP]
+        , cmd1 "NICK" SS
+        , cmd1 "PROTO" IntP
+        , cmd1 "ASKPASSWORD" SS
+        , cmd1 "SERVER_AUTH" SS
+        , cmd1 "JOINING" SS
+        , cmd1 "TEAM_ACCEPTED" SS
+        , cmd2 "HH_NUM" SS SS
+        , cmd2 "TEAM_COLOR" SS SS
+        , cmd1 "BANLIST" $ Many [SS]
+        , cmd1 "JOINED" $ Many [SS]
+        , cmd1 "LOBBY:JOINED" $ Many [SS]
+        , cmd2 "LOBBY:LEFT" SS LS
+        , cmd2 "CLIENT_FLAGS" SS $ Many [SS]
+        , cmd2 "LEFT" SS LS
+        , cmd1 "SERVER_MESSAGE" LS
+        , cmd1 "ERROR" LS
+        , cmd1 "NOTICE" LS
+        , cmd1 "WARNING" LS
+        , cmd1 "EM" $ Many [LS]
+        , cmd1 "PING" $ Many [SS]
+        , cmd2 "CHAT" SS LS
+        , cmd2 "SERVER_VARS" SS LS
+        , cmd2 "BYE" SS LS
+        , cmd1 "INFO" $ Many [SS]
+        , cmd1 "ROOM~ADD" $ Many [SS]
+        , cmd1 "ROOM~UPD" $ Many [SS]
+        , cmd1 "ROOM~DEL" SS
+        , cmd1 "ROOMS" $ Many [SS]
+        , cmd "KICKED" []
+        , cmd "RUN_GAME" []
+        , cmd "ROUND_FINISHED" []
+        , cmd1 "ADD_TEAM" $ Many [SS]
+        , cmd1 "REMOVE_TEAM" SS
+        , cmd1 "CFG~MAP" SS
+        , cmd1 "CFG~SEED" SS
+        , cmd1 "CFG~SCHEME" $ Many [SS]
+        , cmd1 "CFG~THEME" SS
+        , cmd1 "CFG~TEMPLATE" IntP
+        , cmd1 "CFG~MAPGEN" IntP
+        , cmd1 "CFG~FEATURE_SIZE" IntP
+        , cmd1 "CFG~MAZE_SIZE" IntP
+        , cmd1 "CFG~SCRIPT" SS
+        , cmd1 "CFG~DRAWNMAP" LS
+        , cmd2 "CFG~AMMO" SS LS
+        , cmd1 "CFG~FULLMAPCONFIG" $ Many [SS]
+    ]
+
+hasMany = any isMany
+isMany (Many _) = True
+isMany _ = False
+
+unknown = Command "__UNKNOWN__" [Many [SS]]
+unknowncmd = PTPrefix "$" [PTCommand "$" $ unknown]
+
+fixName = map fixChar
+fixChar c | isLetter c = c
+          | otherwise = '_'
+
+groupByFirstChar :: [ParseTree] -> [(Char, [ParseTree])]
+groupByFirstChar = MM.assocs . MM.fromList . map breakCmd
+    where
+    breakCmd (PTCommand (c:cs) params) = (c, PTCommand cs params)
+
+makePT cmd@(Command n p) = PTCommand n cmd
+
+buildParseTree cmds = [PTPrefix "!" $ (bpt $ map makePT cmds) ++ [unknowncmd]]
+
+bpt :: [ParseTree] -> [ParseTree]
+bpt cmds = cmdLeaf emptyNamed
+    where
+        emptyNamed = partition (\(_, (PTCommand n _:_)) -> null n) $ groupByFirstChar cmds
+        buildsub :: (Char, [ParseTree]) -> [ParseTree] -> ParseTree
+        buildsub (c, cmds) pc = let st = (bpt cmds) ++ pc in if null $ drop 1 st then maybeMerge c st else PTPrefix [c] st
+        buildsub' = flip buildsub []
+        cmdLeaf ([], assocs) = map buildsub' assocs
+        cmdLeaf ([(c, hwc:assocs1)], assocs2)
+            | null assocs1 = PTPrefix [c] [hwc] : map buildsub' assocs2
+            | otherwise = (buildsub (c, assocs1) [hwc]) : map buildsub' assocs2
+
+        maybeMerge c cmd@[PTCommand {}] = PTPrefix [c] cmd
+        maybeMerge c cmd@[PTPrefix s ss] = PTPrefix (c:s) ss
+        maybeMerge c [] = PTPrefix [c] []
+        
+dumpTree = vcat . map dt
+    where
+    dt (PTPrefix s st) = text s $$ (nest (length s) $ vcat $ map dt st)
+    dt _ = char '$'
+
+renderArrays (letters, commands, handlers) = vcat $ punctuate (char '\n') [grr, l, s, c, bodies, structs, realHandlers, realHandlersArray, cmds]
+    where
+        maybeQuotes "$" = text "#0"
+        maybeQuotes "~" = text "#10"
+        maybeQuotes s = if null $ tail s then quotes $ text s else text s
+        l = text "const letters: array[0.." <> (int $ length letters - 1) <> text "] of char = "
+            <> parens (hsep . punctuate comma $ map maybeQuotes letters) <> semi
+        s = text "const commands: array[0.." <> (int $ length commands - 1) <> text "] of integer = "
+            <> parens (hsep . punctuate comma $ map text commands) <> semi
+        c = text "const handlers: array[0.." <> (int $ length fixedNames - 1) <> text "] of PHandler = "
+            <> parens (hsep . punctuate comma $ map (text . (:) '@') handlerTypes) <> semi
+        grr = text "const net2cmd: array[0.." <> (int $ length fixedNames - 1) <> text "] of TCmdType = "
+            <> parens (hsep . punctuate comma $ map (text . (++) "cmd_") $ reverse fixedNames) <> semi
+        handlerTypes = "handler__UNKNOWN_" : (map cmdParams2handlerType $ reverse sortedCmdDescriptions)
+        sortedCmdDescriptions = sort commandsDescription
+        fixedNames = map fixName handlers
+        bodies = vcat $ punctuate (char '\n') $ map handlerBody $ nub $ sort handlerTypes
+        handlerBody n = text "procedure " <> text n <> semi
+            $+$ text "begin"
+            $+$ text "end" <> semi
+        cmds = text "type TCmdType = " <> parens (hsep $ punctuate comma $ concatMap (rhentry "cmd_") $ sortedCmdDescriptions) <> semi
+        structs = vcat (map text . Set.toList . Set.fromList $ map cmdParams2record commandsDescription)
+        realHandlers = vcat $ punctuate (char '\n') $ map rh $ sortedCmdDescriptions
+        realHandlersArray = text "const handlers: array[TCmdType] of PHandler = "
+            <> parens (hsep . punctuate comma . concatMap (map ((<>) (text "PHandler") . parens) . rhentry "@handler_") $ sortedCmdDescriptions) <> semi
+
+rh cmd@(Command n p) = text "procedure handler_" <> text (fixName n) <> parens (text "var p: " <> text (cmdParams2str cmd)) <> semi
+    $+$ emptyBody $+$ if hasMany p then vcat [space, text "procedure handler_" <> text (fixName n) <> text "_s" <> parens (text "var s: TCmdParamS") <> semi
+    , emptyBody] else empty
+    where
+        emptyBody = text "begin"  $+$ text "end" <> semi
+
+rhentry prefix cmd@(Command n p) = (text . (++) prefix . fixName . cmdName $ cmd)
+    : if hasMany p then [text . flip (++) "_s" . (++) prefix . fixName . cmdName $ cmd] else []
+
+pas = renderArrays $ buildTables $ buildParseTree commandsDescription
+    where
+        buildTables cmds = let (_, _, _, t1, t2, t3) = foldr walk (0, [0], -10, [], [], [[]]) cmds in (tail t1, tail t2, concat t3)
+        walk (PTCommand _ (Command n params)) (lc, s:sh, pc, tbl1, tbl2, (t3:tbl3)) =
+            (lc, 1:sh, pc - 1, "#10":tbl1, show pc:tbl2, (n:t3):tbl3)
+        walk (PTPrefix prefix cmds) l = lvldown $ foldr fpf (foldr walk (lvlup l) cmds) prefix
+        lvlup (lc, sh, pc, tbl1, tbl2, tbl3) = (lc, 0:sh, pc, tbl1, tbl2, []:tbl3)
+        lvldown (lc, s1:s2:sh, pc, tbl1, t:tbl2, t31:t32:tbl3) = (lc, s1+s2:sh, pc, tbl1, (if null t32 then "0" else show s1):tbl2, (t31 ++ t32):tbl3)
+        fpf c (lc, s:sh, pc, tbl1, tbl2, tbl3) = (lc + 1, s+1:sh, pc, [c]:tbl1, "0":tbl2, tbl3)
+
+main = do
+    putStrLn $ renderStyle style{mode = ZigZagMode, lineLength = 80} $ pas
+    --putStrLn $ renderStyle style{lineLength = 80} $ dumpTree $ buildParseTree commandsDescription