Hedgeroid: Frantic scrabbling toward the deadline
authorMedo <smaxein@googlemail.com>
Sat, 18 Aug 2012 00:47:51 +0200
changeset 7508 763d3961400b
parent 7504 ed1d52c5aa94
child 7550 3c4b4cb40f40
Hedgeroid: Frantic scrabbling toward the deadline - Added activities for weapon/scheme editors (unfinished) - Completed tablet version of netroom activity - Added map preview - Fixed default team files having the wrong names - Restructuring - Updated frontlib JNA bindings to respect the latest frontlib changes
hedgewars/hwLibrary.pas
project_files/Android-build/SDL-android-project/AndroidManifest.xml
project_files/Android-build/SDL-android-project/assets/Data/metasettings.ini
project_files/Android-build/SDL-android-project/assets/assetsversion.txt
project_files/Android-build/SDL-android-project/res/drawable-mdpi/hogcount.xml
project_files/Android-build/SDL-android-project/res/drawable-mdpi/hogcount1.png
project_files/Android-build/SDL-android-project/res/drawable-mdpi/hogcount2.png
project_files/Android-build/SDL-android-project/res/drawable-mdpi/hogcount3.png
project_files/Android-build/SDL-android-project/res/drawable-mdpi/hogcount4.png
project_files/Android-build/SDL-android-project/res/drawable-mdpi/hogcount5.png
project_files/Android-build/SDL-android-project/res/drawable-mdpi/hogcount6.png
project_files/Android-build/SDL-android-project/res/drawable-mdpi/hogcount7.png
project_files/Android-build/SDL-android-project/res/drawable-mdpi/hogcount8.png
project_files/Android-build/SDL-android-project/res/drawable-mdpi/team_local_by_level.xml
project_files/Android-build/SDL-android-project/res/drawable-mdpi/team_net_bot1.png
project_files/Android-build/SDL-android-project/res/drawable-mdpi/team_net_bot2.png
project_files/Android-build/SDL-android-project/res/drawable-mdpi/team_net_bot3.png
project_files/Android-build/SDL-android-project/res/drawable-mdpi/team_net_bot4.png
project_files/Android-build/SDL-android-project/res/drawable-mdpi/team_net_bot5.png
project_files/Android-build/SDL-android-project/res/drawable-mdpi/team_net_by_level.xml
project_files/Android-build/SDL-android-project/res/drawable-mdpi/team_net_human.png
project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount.xml
project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount0.png
project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount1.png
project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount2.png
project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount3.png
project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount4.png
project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount5.png
project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount6.png
project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount7.png
project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount8.png
project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount9.png
project_files/Android-build/SDL-android-project/res/drawable-mdpi/teams_number.xml
project_files/Android-build/SDL-android-project/res/layout/activity_netroom.xml
project_files/Android-build/SDL-android-project/res/layout/activity_schemelist.xml
project_files/Android-build/SDL-android-project/res/layout/activity_weaponsetlist.xml
project_files/Android-build/SDL-android-project/res/layout/fragment_map.xml
project_files/Android-build/SDL-android-project/res/layout/listview_item.xml
project_files/Android-build/SDL-android-project/res/layout/listview_team.xml
project_files/Android-build/SDL-android-project/res/layout/starting_game.xml
project_files/Android-build/SDL-android-project/res/layout/team_selection_entry.xml
project_files/Android-build/SDL-android-project/res/menu/main_options.xml
project_files/Android-build/SDL-android-project/res/values/frontend_data_pointers.xml
project_files/Android-build/SDL-android-project/res/values/strings.xml
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/ChatFragment.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/ChatlogAdapter.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/FrontendDataUtils.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/GameConfig.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/MapFile.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/MapRecipe.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/MetaScheme.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Player.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/PlayerInRoom.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Room.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/RoomWithId.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/RoomlistRoom.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Scheme.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Schemes.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Team.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/TeamInGame.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/TeamIngameAttributes.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Weaponset.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Weaponsets.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Downloader/DownloadAssets.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Downloader/DownloadPackage.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/EngineProtocol/PascalExports.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/GameConnection.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/LobbyActivity.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/LobbyPlayerlistAdapter.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/LobbyPlayerlistFragment.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/MainActivity.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/MapFragment.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/MapPreviewGenerator.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/NetplayStateFragment.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/RoomActivity.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/RoomPlayerlistAdapter.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/RoomPlayerlistFragment.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/RoomStateManager.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/RoomlistAdapter.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/RoomlistFragment.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/SDLActivity.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/SchemeCreatorActivity.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/SchemeListActivity.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/SettingsFragment.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/StartGameActivity.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/TeamAddDialog.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/TeamCreatorActivity.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/TeamSelectionActivity.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/TeamlistAdapter.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/TeamlistFragment.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Utils.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/WeaponsetCreatorActivity.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/WeaponsetListActivity.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/frontlib/Frontlib.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/ChatFragment.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/ChatlogAdapter.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/GameMessageListener.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/LobbyActivity.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/LobbyPlayerlist.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/LobbyPlayerlistAdapter.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/LobbyPlayerlistFragment.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/NetRoomState.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Netplay.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/NetplayStateFragment.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/ObservableTreeMap.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/ObservableTreeMapAdapter.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/OpenConnectionService.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomActivity.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomPlayerlist.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomPlayerlistAdapter.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomPlayerlistFragment.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Roomlist.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomlistAdapter.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomlistFragment.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RunGameListener.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/TeamAddDialog.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Teamlist.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/TeamlistAdapter.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/TeamlistFragment.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/TextInputDialog.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/ThreadedNetConnection.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/TickHandler.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/util/FileUtils.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/util/ObservableTreeMap.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/util/ObservableTreeMapAdapter.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/util/TextInputDialog.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/util/TickHandler.java
--- a/hedgewars/hwLibrary.pas	Sat Aug 18 00:22:33 2012 +0200
+++ b/hedgewars/hwLibrary.pas	Sat Aug 18 00:47:51 2012 +0200
@@ -99,10 +99,14 @@
     JNI_HW_versionInfoVersion := envderef^.NewStringUTF(env, PChar(cVersionString));
 end;
 
-function JNI_HW_GenLandPreview(env: PJNIEnv; c: JClass; port: JInt):JInt; cdecl;
+procedure JNI_HW_GenLandPreview(env: PJNIEnv; c: JClass; port: JInt); cdecl;
 begin
 	GenLandPreview(port);
-	JNI_HW_GenLandPreview := port;
+end;
+
+procedure JNI_HW_Terminate(env: PJNIEnv; c: JClass); cdecl;
+begin
+	HW_terminate(false);
 end;
 
 exports
@@ -112,7 +116,7 @@
     HW_getNumberOfweapons name Java_Prefix + 'HWgetNumberOfWeapons',
     HW_getMaxNumberOfHogs name Java_Prefix + 'HWgetMaxNumberOfHogs',
     HW_getMaxNumberOfTeams name Java_Prefix + 'HWgetMaxNumberOfTeams',
-    HW_terminate name Java_Prefix + 'HWterminate',
+    JNI_HW_Terminate name Java_Prefix + 'HWterminate',
     Game;
 {$ELSE}
 exports
--- a/project_files/Android-build/SDL-android-project/AndroidManifest.xml	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/AndroidManifest.xml	Sat Aug 18 00:47:51 2012 +0200
@@ -67,16 +67,40 @@
             android:windowSoftInputMode="stateUnchanged" >
         </activity>
         <activity
-            android:name=".netplay.LobbyActivity"
+            android:name=".LobbyActivity"
             android:label="@string/title_activity_lobby"
             android:screenOrientation="landscape"
             android:windowSoftInputMode="adjustPan" >
         </activity>
         <activity
-            android:name=".netplay.RoomActivity"
+            android:name=".RoomActivity"
             android:label="@string/title_activity_room"
             android:screenOrientation="landscape"
             android:windowSoftInputMode="adjustPan" >
         </activity>
+        <activity
+            android:name=".WeaponsetListActivity"
+            android:label="@string/title_activity_weaponset_list"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" >
+        </activity>
+        <activity
+            android:name=".WeaponsetCreatorActivity"
+            android:label="@string/title_activity_weaponset_creator"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" >
+        </activity>
+        <activity
+            android:name=".SchemeListActivity"
+            android:label="@string/title_activity_scheme_list"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" >
+        </activity>
+        <activity
+            android:name=".SchemeCreatorActivity"
+            android:label="@string/title_activity_scheme_creator"
+            android:screenOrientation="landscape"
+            android:windowSoftInputMode="adjustPan" >
+        </activity>
     </application>
 </manifest>
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/assets/Data/metasettings.ini	Sat Aug 18 00:22:33 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,234 +0,0 @@
-[mod0]
-name=fortsmode
-bitmaskindex=12
-
-[mod1]
-name=divteams
-bitmaskindex=4
-
-[mod2]
-name=solidland
-bitmaskindex=2
-
-[mod3]
-name=border
-bitmaskindex=3
-
-[mod4]
-name=lowgrav
-bitmaskindex=5
-
-[mod5]
-name=laser
-bitmaskindex=6
-
-[mod6]
-name=invulnerability
-bitmaskindex=7
-
-[mod7]
-name=resethealth
-bitmaskindex=8
-
-[mod8]
-name=vampiric
-bitmaskindex=9
-
-[mod9]
-name=karma
-bitmaskindex=10
-
-[mod10]
-name=artillery
-bitmaskindex=11
-
-[mod11]
-name=randomorder
-bitmaskindex=13
-
-[mod12]
-name=king
-bitmaskindex=14
-
-[mod13]
-name=placehog
-bitmaskindex=15
-
-[mod14]
-name=sharedammo
-bitmaskindex=16
-
-[mod15]
-name=disablegirders
-bitmaskindex=17
-
-[mod16]
-name=disablelandobjects
-bitmaskindex=18
-
-[mod17]
-name=aisurvival
-bitmaskindex=19
-
-[mod18]
-name=infattack
-bitmaskindex=20
-
-[mod19]
-name=resetweps
-bitmaskindex=21
-
-[mod20]
-name=perhogammo
-bitmaskindex=22
-
-[mod21]
-name=disablewind
-bitmaskindex=23
-
-[mod22]
-name=morewind
-bitmaskindex=24
-
-[mod23]
-name=tagteam
-bitmaskindex=25
-
-[mod24]
-name=bottomborder
-bitmaskindex=26
-
-
-[setting0]
-name=damagefactor
-times1000=false
-command=e$damagepct
-maxmeansinfinity=false
-min=10
-max=300
-default=100
-
-[setting1]
-name=turntime
-times1000=true
-command=e$turntime
-maxmeansinfinity=true
-min=1
-max=9999
-default=45
-
-[setting2]
-name=health
-times1000=false
-maxmeansinfinity=false
-min=50
-max=200
-default=100
-
-[setting3]
-name=suddendeath
-times1000=false
-command=e$sd_turns
-maxmeansinfinity=true
-min=0
-max=50
-default=15
-
-[setting4]
-name=caseprobability
-times1000=false
-command=e$casefreq
-maxmeansinfinity=false
-min=0
-max=9
-default=5
-
-[setting5]
-name=minestime
-times1000=true
-command=e$minestime
-maxmeansinfinity=false
-min=-1
-max=5
-default=3
-
-[setting6]
-name=minesnum
-times1000=false
-command=e$minesnum
-maxmeansinfinity=false
-min=0
-max=80
-default=4
-
-[setting7]
-name=minedudpct
-times1000=false
-command=e$minedudpct
-maxmeansinfinity=false
-min=0
-max=100
-default=0
-
-[setting8]
-name=explosives
-times1000=false
-command=e$explosives
-maxmeansinfinity=false
-min=0
-max=40
-default=2
-
-[setting9]
-name=healthprobability
-times1000=false
-command=e$healthprob
-maxmeansinfinity=false
-min=0
-max=100
-default=35
-
-[setting10]
-name=healthcaseamount
-times1000=false
-command=e$hcaseamount
-maxmeansinfinity=false
-min=0
-max=200
-default=25
-
-[setting11]
-name=waterrise
-times1000=false
-command=e$waterrise
-maxmeansinfinity=false
-min=0
-max=100
-default=47
-
-[setting12]
-name=healthdecrease
-times1000=false
-command=e$healthdec
-maxmeansinfinity=false
-min=0
-max=100
-default=5
-
-[setting13]
-name=ropepct
-times1000=false
-command=e$ropepct
-maxmeansinfinity=false
-min=25
-max=999
-default=100
-
-[setting14]
-name=getawaytime
-times1000=false
-command=e$getawaytime
-maxmeansinfinity=false
-min=0
-max=999
-default=100
--- a/project_files/Android-build/SDL-android-project/assets/assetsversion.txt	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/assets/assetsversion.txt	Sat Aug 18 00:47:51 2012 +0200
@@ -1,1 +1,1 @@
-5
\ No newline at end of file
+7
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/drawable-mdpi/hogcount.xml	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,10 @@
+ <level-list xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:maxLevel="1" android:drawable="@drawable/hogcount1" />
+  <item android:maxLevel="2" android:drawable="@drawable/hogcount2" />
+  <item android:maxLevel="3" android:drawable="@drawable/hogcount3" />
+  <item android:maxLevel="4" android:drawable="@drawable/hogcount4" />
+  <item android:maxLevel="5" android:drawable="@drawable/hogcount5" />
+  <item android:maxLevel="6" android:drawable="@drawable/hogcount6" />
+  <item android:maxLevel="7" android:drawable="@drawable/hogcount7" />
+  <item android:maxLevel="8" android:drawable="@drawable/hogcount8" />
+ </level-list>
\ No newline at end of file
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/hogcount1.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/hogcount2.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/hogcount3.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/hogcount4.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/hogcount5.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/hogcount6.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/hogcount7.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/hogcount8.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/drawable-mdpi/team_local_by_level.xml	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,8 @@
+<level-list xmlns:android="http://schemas.android.com/apk/res/android">
+	<item android:maxLevel="0" android:drawable="@drawable/human" />
+	<item android:maxLevel="1" android:drawable="@drawable/bot5" />
+	<item android:maxLevel="2" android:drawable="@drawable/bot4" />
+	<item android:maxLevel="3" android:drawable="@drawable/bot3" />
+	<item android:maxLevel="4" android:drawable="@drawable/bot2" />
+	<item android:maxLevel="5" android:drawable="@drawable/bot1" />
+</level-list>
\ No newline at end of file
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/team_net_bot1.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/team_net_bot2.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/team_net_bot3.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/team_net_bot4.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/team_net_bot5.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/drawable-mdpi/team_net_by_level.xml	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,8 @@
+<level-list xmlns:android="http://schemas.android.com/apk/res/android">
+	<item android:maxLevel="0" android:drawable="@drawable/team_net_human" />
+	<item android:maxLevel="1" android:drawable="@drawable/team_net_bot5" />
+	<item android:maxLevel="2" android:drawable="@drawable/team_net_bot4" />
+	<item android:maxLevel="3" android:drawable="@drawable/team_net_bot3" />
+	<item android:maxLevel="4" android:drawable="@drawable/team_net_bot2" />
+	<item android:maxLevel="5" android:drawable="@drawable/team_net_bot1" />
+</level-list>
\ No newline at end of file
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/team_net_human.png has changed
--- a/project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount.xml	Sat Aug 18 00:22:33 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
- <level-list xmlns:android="http://schemas.android.com/apk/res/android">
-  <item android:maxLevel="0" android:drawable="@drawable/teams_number0" />
-  <item android:maxLevel="1" android:drawable="@drawable/teams_number1" />
-  <item android:maxLevel="2" android:drawable="@drawable/teams_number2" />
-  <item android:maxLevel="3" android:drawable="@drawable/teams_number3" />
-  <item android:maxLevel="4" android:drawable="@drawable/teams_number4" />
-  <item android:maxLevel="5" android:drawable="@drawable/teams_number5" />
-  <item android:maxLevel="6" android:drawable="@drawable/teams_number6" />
-  <item android:maxLevel="7" android:drawable="@drawable/teams_number7" />
-  <item android:maxLevel="8" android:drawable="@drawable/teams_number8" />
-  <item android:maxLevel="9" android:drawable="@drawable/teams_number9" />
- </level-list>
\ No newline at end of file
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount0.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount1.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount2.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount3.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount4.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount5.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount6.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount7.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount8.png has changed
Binary file project_files/Android-build/SDL-android-project/res/drawable-mdpi/teamcount9.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/drawable-mdpi/teams_number.xml	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,12 @@
+ <level-list xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:maxLevel="0" android:drawable="@drawable/teams_number0" />
+  <item android:maxLevel="1" android:drawable="@drawable/teams_number1" />
+  <item android:maxLevel="2" android:drawable="@drawable/teams_number2" />
+  <item android:maxLevel="3" android:drawable="@drawable/teams_number3" />
+  <item android:maxLevel="4" android:drawable="@drawable/teams_number4" />
+  <item android:maxLevel="5" android:drawable="@drawable/teams_number5" />
+  <item android:maxLevel="6" android:drawable="@drawable/teams_number6" />
+  <item android:maxLevel="7" android:drawable="@drawable/teams_number7" />
+  <item android:maxLevel="8" android:drawable="@drawable/teams_number8" />
+  <item android:maxLevel="9" android:drawable="@drawable/teams_number9" />
+ </level-list>
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/res/layout/activity_netroom.xml	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/res/layout/activity_netroom.xml	Sat Aug 18 00:47:51 2012 +0200
@@ -21,7 +21,7 @@
             android:layout_marginBottom="10dp"
             android:baselineAligned="false"
             android:minHeight="200dp" >
-
+            
             <FrameLayout
                 android:id="@+id/mapFrame"
                 android:layout_width="0dp"
@@ -30,14 +30,12 @@
                 android:layout_weight="1"
                 android:background="@drawable/box" >
 
-                <!--
-                     <fragment
+                <fragment
                     android:id="@+id/mapFragment"
-                                    android:layout_width="fill_parent"
-                android:layout_height="fill_parent"
+                    android:layout_width="fill_parent"
+                    android:layout_height="fill_parent"
                     class="org.hedgewars.hedgeroid.netplay.MapFragment"
                     tools:layout="@layout/fragment_map" />
-                -->
             </FrameLayout>
 
             <FrameLayout
@@ -48,14 +46,12 @@
                 android:layout_weight="1"
                 android:background="@drawable/box" >
 
-                <!--
-                    <fragment
+                <fragment
                     android:id="@+id/settingsFragment"
-                                    android:layout_width="fill_parent"
-                android:layout_height="fill_parent"
+                    android:layout_width="fill_parent"
+                    android:layout_height="fill_parent"
                     class="org.hedgewars.hedgeroid.netplay.SettingsFragment"
                     tools:layout="@layout/fragment_settings" />
-                -->
             </FrameLayout>
 
             <FrameLayout
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/layout/activity_schemelist.xml	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+
+    <ListView 
+        android:id="@android:id/list"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" 
+        android:cacheColorHint="@android:color/transparent" />
+
+	<Button
+        android:id="@+id/addButton"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/schemelist_add_button_text"
+        android:background="@drawable/button" />
+
+</LinearLayout>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/layout/activity_weaponsetlist.xml	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+
+    <TextView
+        android:id="@android:id/empty"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        android:text="@string/weaponsetlist_empty" />
+
+    <ListView
+        android:id="@android:id/list"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:cacheColorHint="@android:color/transparent" />
+
+    <Button
+        android:id="@+id/addButton"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@drawable/button"
+        android:text="@string/weaponsetlist_add_button_text" />
+
+</LinearLayout>
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/res/layout/fragment_map.xml	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/res/layout/fragment_map.xml	Sat Aug 18 00:47:51 2012 +0200
@@ -17,8 +17,8 @@
         android:layout_centerHorizontal="true"
         android:layout_margin="5dip"
         android:background="@drawable/box"
-        android:scaleType="fitXY"
-        android:src="@drawable/backbutton" />
+        android:scaleType="fitCenter"
+        android:src="@drawable/roomlist_preparing" />
 
     <TableLayout
         android:id="@+id/gameOptions"
@@ -30,32 +30,56 @@
         <TableRow>
 
             <TextView
-                android:id="@+id/txtMap"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:text="@string/map_map" />
+                android:text="@string/map_gen" />
 
             <Spinner
-                android:id="@+id/spinMaps"
+                android:id="@+id/spinMapType"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:background="@drawable/dropdown" />
         </TableRow>
 
-        <TableRow>
-
+        <TableRow android:id="@+id/rowMapName">
             <TextView
-                android:id="@+id/txtType"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:text="@string/map_type" />
+                android:layout_gravity="center_vertical"
+                android:text="@string/map_name" />
 
             <Spinner
-                android:id="@+id/spinType"
-                android:layout_width="wrap_content"
+                android:id="@+id/spinMapName"
+                android:layout_width="fill_parent"
                 android:layout_height="wrap_content"
                 android:background="@drawable/dropdown" />
         </TableRow>
+		<TableRow android:id="@+id/rowTemplateFilter">
+		    <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:text="@string/map_template" />
+		    
+            <Spinner
+                android:id="@+id/spinTemplateFilter"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:background="@drawable/dropdown" />
+		</TableRow>
+		<TableRow android:id="@+id/rowMazeSize">
+		    <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:text="@string/map_maze_size" />
+		    
+            <Spinner
+                android:id="@+id/spinMazeSize"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:background="@drawable/dropdown" />
+		</TableRow>
     </TableLayout>
 
 </RelativeLayout>
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/res/layout/listview_item.xml	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/res/layout/listview_item.xml	Sat Aug 18 00:47:51 2012 +0200
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/text1"
 	android:layout_width="fill_parent"
 	android:layout_height="wrap_content"
 	android:textSize="10dip"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/layout/listview_team.xml	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:paddingTop="4dp"
+	android:paddingBottom="4dp" >
+    <TextView
+        android:id="@android:id/text1"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical|left"
+        android:layout_weight="1"
+        android:gravity="center_vertical"
+        android:drawablePadding="5dp"
+        android:textAppearance="?android:attr/textAppearanceMedium" />
+
+    <ImageButton
+        android:id="@+id/colorButton"
+        android:layout_width="48dp"
+        android:layout_height="48dp"
+        android:layout_gravity="center_vertical|right"
+        android:src="#fff"
+        android:padding="8dp"
+        android:contentDescription="@string/teamlist_color_button_description" />
+    
+    <ImageButton
+        android:id="@+id/hogCountButton"
+        android:layout_width="48dp"
+        android:layout_height="48dp"
+        android:layout_gravity="center_vertical|right"
+        android:src="@drawable/hogcount"
+        android:scaleType="centerCrop"
+        android:padding="0dp"
+        android:contentDescription="@string/teamlist_hogcount_button_description" />
+</LinearLayout>
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/res/layout/starting_game.xml	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/res/layout/starting_game.xml	Sat Aug 18 00:47:51 2012 +0200
@@ -147,7 +147,7 @@
     	android:adjustViewBounds="true"
     	android:scaleType="centerInside"
     	android:background="@android:color/transparent"
-    	android:src="@drawable/teamcount"/>
+    	android:src="@drawable/teams_number"/>
         
     </LinearLayout>
 
--- a/project_files/Android-build/SDL-android-project/res/layout/team_selection_entry.xml	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/res/layout/team_selection_entry.xml	Sat Aug 18 00:47:51 2012 +0200
@@ -31,7 +31,7 @@
   	android:layout_alignBottom="@id/imgDifficulty"
   	android:adjustViewBounds="true"
   	android:scaleType="centerInside"
-  	android:src="@drawable/teamcount7"/>
+  	android:src="@drawable/hogcount"/>
   <TextView
   	android:id="@+id/txtName"
   	android:layout_height="fill_parent"
--- a/project_files/Android-build/SDL-android-project/res/menu/main_options.xml	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/res/menu/main_options.xml	Sat Aug 18 00:47:51 2012 +0200
@@ -9,4 +9,8 @@
         android:title="@string/main_menu_preferences"
         android:icon="@android:drawable/ic_menu_preferences"
         android:showAsAction="ifRoom|withText" />
+    <item
+        android:id="@+id/edit_weaponsets"
+        android:title="@string/edit_weaponsets_menu"
+        android:showAsAction="ifRoom|withText" />
 </menu>
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/res/values/frontend_data_pointers.xml	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/res/values/frontend_data_pointers.xml	Sat Aug 18 00:47:51 2012 +0200
@@ -4,4 +4,8 @@
 	<item>@raw/team_one</item>
 	<item>@raw/team_two</item>
 </array>
+<array name="teamFilenames">
+	<item>Team 1.hwt</item>
+	<item>Team 2.hwt</item>
+</array>
 </resources>
--- a/project_files/Android-build/SDL-android-project/res/values/strings.xml	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/res/values/strings.xml	Sat Aug 18 00:47:51 2012 +0200
@@ -63,19 +63,49 @@
 
     <string name="title_activity_lobby">Hedgewars Server Lobby</string>
     <string name="title_activity_room">Room</string>
+    <string name="title_activity_weaponset_list">User-defined Weaponsets</string>
+    <string name="title_activity_weaponset_creator">Weaponset Editor</string>
+    <string name="title_activity_scheme_list">User-defined Schemes</string>
+    <string name="title_activity_scheme_creator">Scheme Editor</string>
     
     <string name="chat_hint">Type here to chat</string>
 
     <!-- Map settings -->
-    <string name="map_map">Map</string>
-    <string name="map_type">Type</string>
-    <string name="map_seed">Change seed</string>
+    <string name="map_gen">Map</string>
+    <string name="map_name">Name</string>
+    <string name="map_template">Type</string>
+    <string name="map_maze_size">Type</string>
+    <string name="map_mission_prefix">Mission: </string>
+    <string-array name="map_types">
+        <item>Generated map</item>
+        <item>Generated maze</item>
+        <item>Hand-drawn map</item>
+        <item>Map file</item>
+    </string-array>
+    <string-array name="map_templates">
+        <item>Random</item>
+        <item>Small</item>
+        <item>Medium</item>
+        <item>Large</item>
+        <item>Cavern</item>
+        <item>Wacky</item>
+    </string-array>
+    <string-array name="map_maze_sizes">
+        <item>Small tunnels</item>
+        <item>Medium tunnels</item>
+        <item>Large tunnels</item>
+        <item>Small floating islands</item>
+        <item>Medium floating islands</item>
+        <item>Large floating islands</item>
+    </string-array>
     
     <!-- Player list -->
     <string name="no_players_in_list">No players</string>
     
     <!-- Teamlist -->
     <string name="teamlist_addteam">Add team</string>
+    <string name="teamlist_color_button_description">Team color</string>
+    <string name="teamlist_hogcount_button_description">Hog count</string>
     
     <!-- Roomlist -->
     <string name="roomlist_header_roomname">Room Name</string>
@@ -140,4 +170,11 @@
     <string name="toast_disconnected">Disconnected: %1$s</string>
     <string name="toast_room_abandoned">The room was closed because the owner left.</string>
     <string name="toast_kicked">You were kicked from the room.</string>
+    
+    <!-- Weaponset editor -->
+    <string name="weaponsetlist_add_button_text">New Weaponset</string>
+    <string name="weaponsetlist_empty">No weaponsets</string>
+    <string name="edit_weaponsets_menu">Edit Weaponsets</string>
+    
+    <string name="schemelist_add_button_text">New Scheme</string>
 </resources>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/ChatFragment.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,78 @@
+package org.hedgewars.hedgeroid;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.netplay.MessageLog;
+import org.hedgewars.hedgeroid.netplay.Netplay;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.EditText;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+public class ChatFragment extends Fragment {
+	public static final String ARGUMENT_INROOM = "inRoom";
+	
+	private ChatlogAdapter adapter;
+	private Netplay netconn;
+	private MessageLog messageLog;
+	private boolean inRoom;
+	
+	public void setInRoom(boolean inRoom) {
+		this.inRoom = inRoom;
+	}
+	
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		netconn = Netplay.getAppInstance(getActivity().getApplicationContext());
+		adapter = new ChatlogAdapter(getActivity());
+	}
+	
+	@Override
+	public void onStart() {
+		super.onStart();
+		messageLog = inRoom ? netconn.roomChatlog : netconn.lobbyChatlog;
+    	adapter.setLog(messageLog.getLog());
+    	messageLog.registerObserver(adapter);
+	}
+	
+	@Override
+	public void onStop() {
+		super.onStop();
+		messageLog.unregisterObserver(adapter);
+	}
+	
+	@Override
+	public View onCreateView(LayoutInflater inflater, ViewGroup container,
+			Bundle savedInstanceState) {
+		View view = inflater.inflate(R.layout.fragment_chat, container, false);
+		
+		ListView listView = (ListView) view.findViewById(R.id.chatConsole);
+		listView.setAdapter(adapter);
+		listView.setDivider(null);
+		listView.setDividerHeight(0);
+		listView.setVerticalFadingEdgeEnabled(true);
+		
+		EditText editText = (EditText) view.findViewById(R.id.chatInput);
+        editText.setOnEditorActionListener(new ChatSendListener());
+        
+		return view;
+	}
+	
+	private final class ChatSendListener implements OnEditorActionListener {
+		public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+			String text = v.getText().toString();
+			if(text.length()>0) {
+				v.setText("");
+				netconn.sendChat(text);
+			}
+			return true;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/ChatlogAdapter.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,95 @@
+package org.hedgewars.hedgeroid;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.hedgewars.hedgeroid.netplay.MessageLog.Observer;
+
+import android.content.Context;
+import android.text.method.LinkMovementMethod;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView.LayoutParams;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
+
+/**
+ * Optimization: ListView is smart enough to try re-using the same view for an item
+ * with the same ID, but it still calls getView for those items when the list changes.
+ * Since lines with a given ID never change in our chatlog, we can avoid the effort
+ * of TextView.setText in many cases by checking if the view is already set up for the
+ * line with the right ID - but to do that, the view needs to remember the ID it's
+ * holding the text for. That's what the LoglineView does. 
+ */
+class LoglineView extends TextView {
+	long chatlogId = -1;
+	
+	public LoglineView(Context context) {
+		super(context);
+	}
+}
+
+public class ChatlogAdapter extends BaseAdapter implements Observer {
+	long idOffset = 0;
+	private List<CharSequence> log = new ArrayList<CharSequence>();
+	private Context context;
+	
+	public ChatlogAdapter(Context context) {
+		this.context = context;
+	}
+	
+	public int getCount() {
+		return log.size();
+	}
+
+	public Object getItem(int position) {
+		return log.get(position);
+	}
+
+	public long getItemId(int position) {
+		return position+idOffset;
+	}
+
+	public boolean hasStableIds() {
+		return true;
+	}
+
+	public void clear() {
+		idOffset += log.size();
+		log.clear();
+		notifyDataSetChanged();
+	}
+	
+	public void lineAdded(CharSequence text) {
+		log.add(text);
+		notifyDataSetChanged();
+	}
+	
+	public void lineRemoved() {
+		log.remove(0);
+		idOffset += 1;
+		notifyDataSetChanged();
+	}
+	
+	public void setLog(Collection<CharSequence> log) {
+		idOffset += log.size();
+		this.log = new ArrayList<CharSequence>(log);
+		notifyDataSetChanged();
+	}
+	
+	public View getView(int position, View convertView, ViewGroup parent) {
+		LoglineView v = (LoglineView)convertView;
+		if (v == null) {
+			v = new LoglineView(context);
+			v.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
+			v.setMovementMethod(LinkMovementMethod.getInstance());
+		}
+		long id = getItemId(position);
+		if(id != v.chatlogId) {
+			v.setText(log.get(position));
+			v.chatlogId = id;
+		}
+		return v;
+	}
+}
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/FrontendDataUtils.java	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/FrontendDataUtils.java	Sat Aug 18 00:47:51 2012 +0200
@@ -22,12 +22,11 @@
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 
 import org.hedgewars.hedgeroid.R;
-import org.hedgewars.hedgeroid.Utils;
+import org.hedgewars.hedgeroid.util.FileUtils;
 
 import android.content.Context;
 import android.graphics.Bitmap;
@@ -39,14 +38,13 @@
 	 * @throws FileNotFoundException if the sdcard isn't available or the Maps directory doesn't exist
 	 */
 	public static ArrayList<MapFile> getMaps(Context c) throws FileNotFoundException {
-		File[] files = Utils.getFilesFromRelativeDir(c,"Maps");
+		File[] files = FileUtils.getFilesFromRelativeDir(c,"Maps");
 		ArrayList<MapFile> ret = new ArrayList<MapFile>();
 
 		for(File f : files) {
-			boolean isMission = Utils.hasFileWithSuffix(f, ".lua");
+			boolean isMission = FileUtils.hasFileWithSuffix(f, ".lua");
 			ret.add(new MapFile(f.getName(), isMission));
 		}
-		Collections.sort(ret, MapFile.MISSIONS_FIRST_NAME_ORDER);
 
 		return ret;
 	}
@@ -56,7 +54,7 @@
 	 * @throws FileNotFoundException if the sdcard isn't available or the Scripts/Multiplayer directory doesn't exist
 	 */
 	public static List<String> getGameStyles(Context c) throws FileNotFoundException {
-		File[] files = Utils.getFilesFromRelativeDir(c, "Scripts/Multiplayer");
+		File[] files = FileUtils.getFilesFromRelativeDir(c, "Scripts/Multiplayer");
 		ArrayList<String> ret = new ArrayList<String>();
 		/*
 		 * Caution: It is important that the "empty" style has this exact name, because
@@ -72,7 +70,6 @@
 				ret.add(name.replace('_', ' ').substring(0, name.length()-4));
 			}
 		}
-		Collections.sort(ret, String.CASE_INSENSITIVE_ORDER);
 		return ret;
 	}
 
@@ -80,24 +77,15 @@
 	 * @throws FileNotFoundException if the sdcard isn't available or the Themes directory doesn't exist
 	 */
 	public static List<String> getThemes(Context c) throws FileNotFoundException {
-		List<String> list = Utils.getDirsWithFileSuffix(c, "Themes", "icon.png");
-		Collections.sort(list, String.CASE_INSENSITIVE_ORDER);
-		return list;
-	}
-
-	public static List<Weaponset> getWeaponsets(Context c) {
-		// TODO stub, re-implement
-		/*List<Weapon> list = Weapon.getWeapons(c);
-		Collections.sort(list);*/
-		return Collections.emptyList();
+		return FileUtils.getDirsWithFileSuffix(c, "Themes", "icon.png");
 	}
 
 	/**
 	 * @throws FileNotFoundException if the sdcard isn't available or the Graphics/Graves directory doesn't exist
 	 */
 	public static ArrayList<HashMap<String, ?>> getGraves(Context c) throws FileNotFoundException {
-		File gravePath = new File(new File(Utils.getDataPathFile(c), "Graphics"), "Graves");
-		ArrayList<String> names = Utils.getFileNamesFromDirWithSuffix(c,"Graphics/Graves", ".png", true);
+		File gravePath = new File(new File(FileUtils.getDataPathFile(c), "Graphics"), "Graves");
+		ArrayList<String> names = FileUtils.getFileNamesFromDirWithSuffix(c,"Graphics/Graves", ".png", true);
 		ArrayList<HashMap<String, ?>> data = new ArrayList<HashMap<String, ?>>(names.size());
 
 		for(String s : names){
@@ -123,8 +111,8 @@
 	 * @throws FileNotFoundException if the sdcard isn't available or the Graphics/Graves directory doesn't exist
 	 */
 	public static ArrayList<HashMap<String, ?>> getFlags(Context c) throws FileNotFoundException {
-		File flagsPath = new File(new File(Utils.getDataPathFile(c), "Graphics"), "Flags");
-		ArrayList<String> names = Utils.getFileNamesFromDirWithSuffix(c, "Graphics/Flags", ".png", true);
+		File flagsPath = new File(new File(FileUtils.getDataPathFile(c), "Graphics"), "Flags");
+		ArrayList<String> names = FileUtils.getFileNamesFromDirWithSuffix(c, "Graphics/Flags", ".png", true);
 		ArrayList<HashMap<String, ?>> data = new ArrayList<HashMap<String, ?>>(names.size());
 
 		for(String s : names){
@@ -141,7 +129,7 @@
 	 * @throws FileNotFoundException if the sdcard isn't available or the Sounds/voices directory doesn't exist
 	 */
 	public static ArrayList<String> getVoices(Context c) throws FileNotFoundException {
-		File[] files = Utils.getFilesFromRelativeDir(c, "Sounds/voices");
+		File[] files = FileUtils.getFilesFromRelativeDir(c, "Sounds/voices");
 		ArrayList<String> ret = new ArrayList<String>();
 
 		for(File f : files){
@@ -154,10 +142,9 @@
 	 * @throws FileNotFoundException if the sdcard isn't available or the Forts directory doesn't exist
 	 */
 	public static ArrayList<String> getForts(Context c) throws FileNotFoundException {
-		return Utils.getFileNamesFromDirWithSuffix(c,"Forts", "L.png", true);
+		return FileUtils.getFileNamesFromDirWithSuffix(c,"Forts", "L.png", true);
 	}
 	
-	// TODO wat
 	public static ArrayList<HashMap<String, ?>> getTypes(Context c){
 		ArrayList<HashMap<String, ?>> data = new ArrayList<HashMap<String, ?>>(6);
 		String[] levels = {c.getString(R.string.human), c.getString(R.string.bot5), c.getString(R.string.bot4), c.getString(R.string.bot3), c.getString(R.string.bot2), c.getString(R.string.bot1)};
@@ -167,6 +154,8 @@
 			HashMap<String, Object> map = new HashMap<String, Object>();
 			map.put("txt", levels[i]);
 			map.put("img", images[i]);
+			map.put("level", i);
+			
 			data.add(map);
 		}
 
@@ -177,8 +166,8 @@
 	 * @throws FileNotFoundException if the sdcard isn't available or the Graphics/Hats directory doesn't exist
 	 */
 	public static ArrayList<HashMap<String, ?>> getHats(Context c) throws FileNotFoundException {
-		ArrayList<String> files = Utils.getFileNamesFromDirWithSuffix(c,"Graphics/Hats", ".png", true);
-		File hatsPath = new File(new File(Utils.getDataPathFile(c), "Graphics"), "Hats");
+		ArrayList<String> files = FileUtils.getFileNamesFromDirWithSuffix(c,"Graphics/Hats", ".png", true);
+		File hatsPath = new File(new File(FileUtils.getDataPathFile(c), "Graphics"), "Hats");
 		int size = files.size();
 		ArrayList<HashMap<String, ?>> data = new ArrayList<HashMap<String, ?>>(size);
 
@@ -202,9 +191,11 @@
 		File[] teamFileNames = teamsDir.listFiles();
 		if(teamFileNames != null){
 			for(File file : teamFileNames){
-				Team team = Team.load(file);
-				if(team != null){
-					ret.add(team);
+				if(file.getName().endsWith(".hwt")) {
+					Team team = Team.load(file);
+					if(team != null){
+						ret.add(team);
+					}
 				}
 			}
 		}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/GameConfig.java	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/GameConfig.java	Sat Aug 18 00:47:51 2012 +0200
@@ -37,6 +37,7 @@
 	public static final String DEFAULT_STYLE = "Normal";
 	public static final String DEFAULT_SCHEME = "Default";
 	public static final String DEFAULT_WEAPONSET = "Default";
+	public static final String DEFAULT_THEME = "Bamboo";
 	
 	public final String style;
 	public final Scheme scheme;
@@ -51,4 +52,10 @@
 		this.teams = Collections.unmodifiableList(new ArrayList<TeamInGame>(teams));
 		this.weaponset = weaponset;
 	}
+
+	@Override
+	public String toString() {
+		return "GameConfig [style=" + style + ", scheme=" + scheme + ", map="
+				+ map + ", teams=" + teams + ", weaponset=" + weaponset + "]";
+	}
 }
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/MapFile.java	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/MapFile.java	Sat Aug 18 00:47:51 2012 +0200
@@ -2,18 +2,20 @@
 
 import java.io.File;
 import java.io.FileNotFoundException;
+import java.util.ArrayList;
 import java.util.Comparator;
+import java.util.List;
 
-import org.hedgewars.hedgeroid.Utils;
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.util.FileUtils;
 
 import android.content.Context;
-import android.widget.AdapterView.OnItemSelectedListener;
+import android.content.res.Resources;
 
 /**
  * Represents a map from the data directory
  */
 public final class MapFile {
-	public static final String MISSION_PREFIX = "Mission: "; // TODO move text generation to UI to allow translation
 	public static final String MAP_DIRECTORY = "Maps";
 	
 	public final String name;
@@ -28,7 +30,7 @@
 	 * @throws FileNotFoundException if the sdcard is not available. Does NOT throw if the requested map file does not exist.
 	 */
 	public static File getFileForMapname(Context ctx, String mapname) throws FileNotFoundException {
-		return new File(new File(Utils.getDataPathFile(ctx), MAP_DIRECTORY), mapname);
+		return new File(new File(FileUtils.getDataPathFile(ctx), MAP_DIRECTORY), mapname);
 	}
 	
 	public static final Comparator<MapFile> MISSIONS_FIRST_NAME_ORDER = new Comparator<MapFile>() {
@@ -43,10 +45,23 @@
 	
 	@Override
 	public String toString() {
-		return (isMission ? MISSION_PREFIX : "") + name;
+		return "MapFile [name=" + name + ", isMission=" + isMission + "]";
 	}
 
 	public File getPreviewFile(Context c) throws FileNotFoundException {
-		return new File(new File(new File(Utils.getDataPathFile(c), MAP_DIRECTORY), name), "preview.png");
-	};
+		return getPreviewFile(c, name);
+	}
+	
+	public static File getPreviewFile(Context c, String mapName) throws FileNotFoundException {
+		return new File(FileUtils.getDataPathFile(c), MAP_DIRECTORY + "/" + mapName + "/" + "preview.png");
+	}
+	
+	public static List<String> toDisplayNameList(List<MapFile> mapFiles, Resources res) {
+		String missionPrefix = res.getString(R.string.map_mission_prefix);
+		List<String> result = new ArrayList<String>();
+		for(MapFile mapFile : mapFiles) {
+			result.add((mapFile.isMission ? missionPrefix : "") + mapFile.name);
+		}
+		return result;
+	}
 }
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/MapRecipe.java	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/MapRecipe.java	Sat Aug 18 00:47:51 2012 +0200
@@ -18,6 +18,9 @@
 
 package org.hedgewars.hedgeroid.Datastructures;
 
+import java.util.Arrays;
+import java.util.UUID;
+
 import org.hedgewars.hedgeroid.R;
 import org.hedgewars.hedgeroid.frontlib.Frontlib;
 
@@ -65,6 +68,34 @@
 		return drawData==null ? null : drawData.clone();
 	}
 	
+	public MapRecipe withMapgen(int mapgen) {
+		return new MapRecipe(mapgen, templateFilter, mazeSize, name, seed, theme, drawData);
+	}
+	
+	public MapRecipe withTemplateFilter(int templateFilter) {
+		return new MapRecipe(mapgen, templateFilter, mazeSize, name, seed, theme, drawData);
+	}
+	
+	public MapRecipe withMazeSize(int mazeSize) {
+		return new MapRecipe(mapgen, templateFilter, mazeSize, name, seed, theme, drawData);
+	}
+	
+	public MapRecipe withName(String name) {
+		return new MapRecipe(mapgen, templateFilter, mazeSize, name, seed, theme, drawData);
+	}
+	
+	public MapRecipe withSeed(String seed) {
+		return new MapRecipe(mapgen, templateFilter, mazeSize, name, seed, theme, drawData);
+	}
+	
+	public MapRecipe withTheme(String theme) {
+		return new MapRecipe(mapgen, templateFilter, mazeSize, name, seed, theme, drawData);
+	}
+	
+	public MapRecipe withDrawData(byte[] drawData) {
+		return new MapRecipe(mapgen, templateFilter, mazeSize, name, seed, theme, drawData);
+	}
+	
 	public static String formatMapName(Resources res, String map) {
 		if(map.charAt(0)=='+') {
 			if(map.equals(MAPNAME_REGULAR)) {
@@ -77,4 +108,95 @@
 		}
 		return map;
 	}
+
+	/**
+	 * Returns the mapname corresponding to the map generator (e.g. "+rnd+" for regular maps)
+	 * If the mapgen does not have a unique name (MAPGEN_NAMED) or is not known, the def
+	 * value is returned.
+	 */
+	public static String mapnameForGenerator(int mapgen, String def) {
+		switch(mapgen) {
+		case Frontlib.MAPGEN_REGULAR: return MAPNAME_REGULAR;
+		case Frontlib.MAPGEN_MAZE: return MAPNAME_MAZE;
+		case Frontlib.MAPGEN_DRAWN: return MAPNAME_DRAWN;
+		default: return def;
+		}
+	}
+	
+	/**
+	 * In a sense this is the inverse of mapnameForGenerator. Returns the mapgen that uses
+	 * mapName as special identifier, or MAPGEN_NAMED if there is none.
+	 */
+	public static int generatorForMapname(String mapName) {
+		if(MapRecipe.MAPNAME_REGULAR.equals(mapName)) {
+			return Frontlib.MAPGEN_REGULAR;
+		} else if(MapRecipe.MAPNAME_MAZE.equals(mapName)) {
+			return Frontlib.MAPGEN_MAZE;
+		} else if(MapRecipe.MAPNAME_DRAWN.equals(mapName)) {
+			return Frontlib.MAPGEN_DRAWN;
+		} else {
+			return Frontlib.MAPGEN_NAMED;
+		}
+	}
+	
+	@Override
+	public String toString() {
+		return "MapRecipe [mapgen=" + mapgen + ", templateFilter="
+				+ templateFilter + ", mazeSize=" + mazeSize + ", name=" + name
+				+ ", seed=" + seed + ", theme=" + theme + ", drawData="
+				+ Arrays.toString(drawData) + "]";
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + Arrays.hashCode(drawData);
+		result = prime * result + mapgen;
+		result = prime * result + mazeSize;
+		result = prime * result + ((name == null) ? 0 : name.hashCode());
+		result = prime * result + ((seed == null) ? 0 : seed.hashCode());
+		result = prime * result + templateFilter;
+		result = prime * result + ((theme == null) ? 0 : theme.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		MapRecipe other = (MapRecipe) obj;
+		if (!Arrays.equals(drawData, other.drawData))
+			return false;
+		if (mapgen != other.mapgen)
+			return false;
+		if (mazeSize != other.mazeSize)
+			return false;
+		if (name == null) {
+			if (other.name != null)
+				return false;
+		} else if (!name.equals(other.name))
+			return false;
+		if (seed == null) {
+			if (other.seed != null)
+				return false;
+		} else if (!seed.equals(other.seed))
+			return false;
+		if (templateFilter != other.templateFilter)
+			return false;
+		if (theme == null) {
+			if (other.theme != null)
+				return false;
+		} else if (!theme.equals(other.theme))
+			return false;
+		return true;
+	}
+
+	public static String makeRandomSeed() {
+		return "{"+UUID.randomUUID().toString()+"}";
+	}
 }
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/MetaScheme.java	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/MetaScheme.java	Sat Aug 18 00:47:51 2012 +0200
@@ -4,7 +4,11 @@
 import java.util.Collections;
 import java.util.List;
 
+import org.hedgewars.hedgeroid.frontlib.Flib;
+
 public final class MetaScheme {
+	public static final MetaScheme INSTANCE = Flib.INSTANCE.flib_get_metascheme().deref();
+	
 	public static final class Mod {
 		public final String name;
 		public final int bitmaskIndex;
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Player.java	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Player.java	Sat Aug 18 00:47:51 2012 +0200
@@ -1,14 +1,29 @@
 package org.hedgewars.hedgeroid.Datastructures;
 
+import java.util.Comparator;
+
+/**
+ * Basic information about a player on a server.
+ */
 public final class Player {
 	public final String name;
+	public final boolean registered, admin;
 	
-	public Player(String name) {
+	public Player(String name, boolean registered, boolean admin) {
 		this.name = name;
+		this.registered = registered;
+		this.admin = admin;
 	}
 
 	@Override
 	public String toString() {
-		return "Player [name=" + name + "]";
+		return "Player [name=" + name + ", registered=" + registered
+				+ ", admin=" + admin + "]";
 	}
+
+	public static Comparator<Player> NAME_ORDER = new Comparator<Player>() {
+		public int compare(Player lhs, Player rhs) {
+			return lhs.name.compareToIgnoreCase(rhs.name);
+		}
+	};
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/PlayerInRoom.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,16 @@
+package org.hedgewars.hedgeroid.Datastructures;
+
+public final class PlayerInRoom {
+	public final Player player;
+	public final boolean ready;
+	
+	public PlayerInRoom(Player player, boolean ready) {
+		this.player = player;
+		this.ready = ready;
+	}
+
+	@Override
+	public String toString() {
+		return "PlayerInRoom [player=" + player + ", ready=" + ready + "]";
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Room.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,36 @@
+package org.hedgewars.hedgeroid.Datastructures;
+
+import android.content.res.Resources;
+
+/**
+ * A room as presented in the roomlist in a server lobby.
+ */
+public final class Room {
+	public final String name, map, scheme, weapons, owner;
+	public final int playerCount, teamCount;
+	public final boolean inProgress;
+	
+	public Room(String name, String map, String scheme, String weapons,
+			String owner, int playerCount, int teamCount, boolean inProgress) {
+		this.name = name;
+		this.map = map;
+		this.scheme = scheme;
+		this.weapons = weapons;
+		this.owner = owner;
+		this.playerCount = playerCount;
+		this.teamCount = teamCount;
+		this.inProgress = inProgress;
+	}
+	
+	public String formatMapName(Resources res) {
+		return MapRecipe.formatMapName(res, map);
+	}
+
+	@Override
+	public String toString() {
+		return "RoomlistRoom [name=" + name + ", map=" + map + ", scheme="
+				+ scheme + ", weapons=" + weapons + ", owner=" + owner
+				+ ", playerCount=" + playerCount + ", teamCount=" + teamCount
+				+ ", inProgress=" + inProgress + "]";
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/RoomWithId.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,24 @@
+package org.hedgewars.hedgeroid.Datastructures;
+
+import java.util.Comparator;
+
+public final class RoomWithId {
+	public final Room room;
+	public final long id;
+	
+	public RoomWithId(Room room, long id) {
+		this.room = room;
+		this.id = id;
+	}
+
+	@Override
+	public String toString() {
+		return "RoomWithId [room=" + room + ", id=" + id + "]";
+	}
+	
+	public static final Comparator<RoomWithId> NEWEST_FIRST_ORDER = new Comparator<RoomWithId>() {
+		public int compare(RoomWithId lhs, RoomWithId rhs) {
+			return rhs.id<lhs.id ? -1 : rhs.id>lhs.id ? 1 : 0;
+		}
+	};
+}
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/RoomlistRoom.java	Sat Aug 18 00:22:33 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,33 +0,0 @@
-package org.hedgewars.hedgeroid.Datastructures;
-
-import android.content.res.Resources;
-
-public final class RoomlistRoom {
-	public final String name, map, scheme, weapons, owner;
-	public final int playerCount, teamCount;
-	public final boolean inProgress;
-	
-	public RoomlistRoom(String name, String map, String scheme, String weapons,
-			String owner, int playerCount, int teamCount, boolean inProgress) {
-		this.name = name;
-		this.map = map;
-		this.scheme = scheme;
-		this.weapons = weapons;
-		this.owner = owner;
-		this.playerCount = playerCount;
-		this.teamCount = teamCount;
-		this.inProgress = inProgress;
-	}
-	
-	public String formatMapName(Resources res) {
-		return MapRecipe.formatMapName(res, map);
-	}
-
-	@Override
-	public String toString() {
-		return "RoomlistRoom [name=" + name + ", map=" + map + ", scheme="
-				+ scheme + ", weapons=" + weapons + ", owner=" + owner
-				+ ", playerCount=" + playerCount + ", teamCount=" + teamCount
-				+ ", inProgress=" + inProgress + "]";
-	}
-}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Scheme.java	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Scheme.java	Sat Aug 18 00:47:51 2012 +0200
@@ -23,15 +23,14 @@
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.TreeMap;
 
 public final class Scheme {
-	public final MetaScheme metascheme;
 	public final String name;
 	public final Map<String, Integer> settings;
 	public final Map<String, Boolean> mods;
 		
-	public Scheme(MetaScheme metascheme, String name, Map<String, Integer> settings, Map<String, Boolean> mods) {
-		this.metascheme = metascheme;
+	public Scheme(String name, Map<String, Integer> settings, Map<String, Boolean> mods) {
 		this.name = name;
 		this.settings = Collections.unmodifiableMap(new HashMap<String, Integer>(settings));
 		this.mods = Collections.unmodifiableMap(new HashMap<String, Boolean>(mods));
@@ -42,20 +41,66 @@
 		return health==null ? 100 : health.intValue();
 	}
 
-	/*@Override
-	public String toString() {
-		return "Scheme [metascheme=" + metascheme + ", name=" + name
-				+ ", settings=" + settings + ", mods=" + mods + "]";
-	}*/
+	public static Scheme createDefaultScheme(MetaScheme meta) {
+		String name = GameConfig.DEFAULT_SCHEME;
+		Map<String, Integer> settings = new TreeMap<String, Integer>();
+		Map<String, Boolean> mods = new TreeMap<String, Boolean>();
+		for(MetaScheme.Setting setting : meta.settings) {
+			settings.put(setting.name, setting.def);
+		}
+		for(MetaScheme.Mod mod : meta.mods) {
+			mods.put(mod.name, Boolean.FALSE);
+		}
+		return new Scheme(name, settings, mods);
+	}
 	
 	@Override
 	public String toString() {
-		return name; // TODO change back once StartGameActivity does not need this anymore
+		return "Scheme [name=" + name + ", settings=" + settings + ", mods="
+				+ mods + "]";
 	}
 	
-	public static final Comparator<Scheme> caseInsensitiveNameComparator = new Comparator<Scheme>() {
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + ((name == null) ? 0 : name.hashCode());
+		result = prime * result + ((mods == null) ? 0 : mods.hashCode());
+		result = prime * result
+				+ ((settings == null) ? 0 : settings.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		Scheme other = (Scheme) obj;
+		if (name == null) {
+			if (other.name != null)
+				return false;
+		} else if (!name.equals(other.name))
+			return false;
+		if (mods == null) {
+			if (other.mods != null)
+				return false;
+		} else if (!mods.equals(other.mods))
+			return false;
+		if (settings == null) {
+			if (other.settings != null)
+				return false;
+		} else if (!settings.equals(other.settings))
+			return false;
+		return true;
+	}
+
+	public static final Comparator<Scheme> NAME_ORDER = new Comparator<Scheme>() {
 		public int compare(Scheme lhs, Scheme rhs) {
-			return lhs.name.compareToIgnoreCase(rhs.name);
+			return String.CASE_INSENSITIVE_ORDER.compare(lhs.name, rhs.name);
 		}
 	};
 }
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Schemes.java	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Schemes.java	Sat Aug 18 00:47:51 2012 +0200
@@ -3,14 +3,9 @@
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
 
-import org.hedgewars.hedgeroid.Utils;
 import org.hedgewars.hedgeroid.frontlib.Flib;
-import org.hedgewars.hedgeroid.frontlib.Frontlib.MetaschemePtr;
 import org.hedgewars.hedgeroid.frontlib.Frontlib.SchemelistPtr;
 
 import android.content.Context;
@@ -32,57 +27,49 @@
 		return new File(c.getFilesDir(), "schemes_builtin.ini");
 	}
 	
-	public static Map<String, Scheme> loadAllSchemes(Context c) throws IOException {
-		Map<String, Scheme> result = loadUserSchemes(c);
-		result.putAll(loadBuiltinSchemes(c));
+	public static List<Scheme> loadAllSchemes(Context c) throws IOException {
+		List<Scheme> result = loadBuiltinSchemes(c);
+		result.addAll(loadUserSchemes(c));
 		return result;
 	}
 	
-	public static Map<String, Scheme> loadUserSchemes(Context c) throws IOException {
+	public static List<Scheme> loadUserSchemes(Context c) throws IOException {
 		return loadSchemes(c, getUserSchemesFile(c));
 	}
 	
-	public static Map<String, Scheme> loadBuiltinSchemes(Context c) throws IOException {
+	public static List<Scheme> loadBuiltinSchemes(Context c) throws IOException {
 		return loadSchemes(c, getBuiltinSchemesFile(c));
 	}
 	
-	public static Map<String, Scheme> loadSchemes(Context c, File schemeFile) throws IOException {
-		Map<String, Scheme> result = new TreeMap<String, Scheme>();
-		String metaschemePath = new File(Utils.getDataPathFile(c), "metasettings.ini").getAbsolutePath();
+	public static List<Scheme> loadSchemes(Context c, File schemeFile) throws IOException {
 		if(!schemeFile.isFile()) {
 			// No schemes file == no schemes, no error
-			return new TreeMap<String, Scheme>();
+			return new ArrayList<Scheme>();
 		}
-		MetaschemePtr meta = null;
 		SchemelistPtr schemeListPtr = null;
 		try {
-			meta = Flib.INSTANCE.flib_metascheme_from_ini(metaschemePath);
-			if(meta==null) {
-				throw new IOException("Unable to read metascheme");
-			}
-			schemeListPtr = Flib.INSTANCE.flib_schemelist_from_ini(meta, schemeFile.getAbsolutePath());
+			schemeListPtr = Flib.INSTANCE.flib_schemelist_from_ini(schemeFile.getAbsolutePath());
 			if(schemeListPtr == null) {
 				throw new IOException("Unable to read schemelist");
 			}
-			List<Scheme> schemeList = schemeListPtr.deref();
-			for(Scheme scheme : schemeList) {
-				result.put(scheme.name, scheme);
-			}
-			return result;
+			return schemeListPtr.deref();
 		} finally {
 			if(schemeListPtr != null) {
 				Flib.INSTANCE.flib_schemelist_destroy(schemeListPtr);
 			}
-			if(meta != null) {
-				Flib.INSTANCE.flib_metascheme_release(meta);
-			}
 		}
 	}
 	
-	public static void saveUserSchemes(Context c, Map<String, Scheme> schemes) throws IOException {
-		List<Scheme> schemeList = new ArrayList<Scheme>(schemes.values());
-		Collections.sort(schemeList, Scheme.caseInsensitiveNameComparator);
-		SchemelistPtr ptr = SchemelistPtr.createJavaOwned(schemeList);
+	public static void saveUserSchemes(Context c, List<Scheme> schemes) throws IOException {
+		SchemelistPtr ptr = SchemelistPtr.createJavaOwned(schemes);
 		Flib.INSTANCE.flib_schemelist_to_ini(getUserSchemesFile(c).getAbsolutePath(), ptr);
 	}
+	
+	public static List<String> toNameList(List<Scheme> schemes) {
+		List<String> result = new ArrayList<String>();
+		for(Scheme scheme : schemes) {
+			result.add(scheme.name);
+		}
+		return result;
+	}
 }
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Team.java	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Team.java	Sat Aug 18 00:47:51 2012 +0200
@@ -25,10 +25,10 @@
 import java.util.Comparator;
 import java.util.List;
 
-import org.hedgewars.hedgeroid.Utils;
 import org.hedgewars.hedgeroid.EngineProtocol.PascalExports;
 import org.hedgewars.hedgeroid.frontlib.Flib;
 import org.hedgewars.hedgeroid.frontlib.Frontlib.TeamPtr;
+import org.hedgewars.hedgeroid.util.FileUtils;
 
 import android.content.Context;
 
@@ -72,7 +72,7 @@
 	}
 	
 	public static File getTeamfileByName(Context c, String teamName) {
-		return new File(new File(c.getFilesDir(), DIRECTORY_TEAMS), Utils.replaceBadChars(teamName)+".hwt");
+		return new File(new File(c.getFilesDir(), DIRECTORY_TEAMS), FileUtils.replaceBadChars(teamName)+".hwt");
 	}
 	
 	@Override
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/TeamInGame.java	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/TeamInGame.java	Sat Aug 18 00:47:51 2012 +0200
@@ -1,5 +1,8 @@
 package org.hedgewars.hedgeroid.Datastructures;
 
+import java.util.Collection;
+import java.util.Comparator;
+
 /**
  * A team with per-game configuration. This is similar to the frontlib "team" structure,
  * except that it does not include weaponset and initial health, which are handled on a
@@ -17,4 +20,20 @@
 	public TeamInGame withAttribs(TeamIngameAttributes attribs) {
 		return new TeamInGame(team, attribs);
 	}
+	
+	public static int getUnusedOrRandomColorIndex(Collection<TeamInGame> teams) {
+		int[] illegalColors = new int[teams.size()];
+		int i=0;
+		for(TeamInGame team : teams) {
+			illegalColors[i] = team.ingameAttribs.colorIndex;
+			i++;
+		}
+		return TeamIngameAttributes.randomColorIndex(illegalColors);
+	}
+	
+	public static Comparator<TeamInGame> NAME_ORDER = new Comparator<TeamInGame>() {
+		public int compare(TeamInGame lhs, TeamInGame rhs) {
+			return Team.NAME_ORDER.compare(lhs.team, rhs.team);
+		}
+	};
 }
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/TeamIngameAttributes.java	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/TeamIngameAttributes.java	Sat Aug 18 00:47:51 2012 +0200
@@ -28,7 +28,7 @@
 		this.remoteDriven = remoteDriven;
 	}
 	
-	public static int randomColorIndex(int[] illegalColors){
+	public static int randomColorIndex(int[] illegalColors) {
 		Random rnd = new Random();
 		ArrayList<Integer> legalcolors = new ArrayList<Integer>();
 		for(int i=0; i<TEAM_COLORS.length; i++) {
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Weaponset.java	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Weaponset.java	Sat Aug 18 00:47:51 2012 +0200
@@ -1,5 +1,7 @@
 package org.hedgewars.hedgeroid.Datastructures;
 
+import java.util.Comparator;
+
 import org.hedgewars.hedgeroid.frontlib.Flib;
 
 public final class Weaponset {
@@ -17,6 +19,65 @@
 
 	@Override
 	public String toString() {
-		return name; // TODO use the generated one once StartGameActivity doesn't need this anymore
+		return "Weaponset [name=" + name + ", loadout=" + loadout
+				+ ", crateProb=" + crateProb + ", crateAmmo=" + crateAmmo
+				+ ", delay=" + delay + "]";
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result
+				+ ((crateAmmo == null) ? 0 : crateAmmo.hashCode());
+		result = prime * result
+				+ ((crateProb == null) ? 0 : crateProb.hashCode());
+		result = prime * result + ((delay == null) ? 0 : delay.hashCode());
+		result = prime * result + ((loadout == null) ? 0 : loadout.hashCode());
+		result = prime * result + ((name == null) ? 0 : name.hashCode());
+		return result;
 	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		Weaponset other = (Weaponset) obj;
+		if (crateAmmo == null) {
+			if (other.crateAmmo != null)
+				return false;
+		} else if (!crateAmmo.equals(other.crateAmmo))
+			return false;
+		if (crateProb == null) {
+			if (other.crateProb != null)
+				return false;
+		} else if (!crateProb.equals(other.crateProb))
+			return false;
+		if (delay == null) {
+			if (other.delay != null)
+				return false;
+		} else if (!delay.equals(other.delay))
+			return false;
+		if (loadout == null) {
+			if (other.loadout != null)
+				return false;
+		} else if (!loadout.equals(other.loadout))
+			return false;
+		if (name == null) {
+			if (other.name != null)
+				return false;
+		} else if (!name.equals(other.name))
+			return false;
+		return true;
+	}
+
+	public static Comparator<Weaponset> NAME_ORDER = new Comparator<Weaponset>() {
+		public int compare(Weaponset lhs, Weaponset rhs) {
+			return String.CASE_INSENSITIVE_ORDER.compare(lhs.name, rhs.name);
+		}
+	};
 }
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Weaponsets.java	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Datastructures/Weaponsets.java	Sat Aug 18 00:47:51 2012 +0200
@@ -3,6 +3,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 
 import org.hedgewars.hedgeroid.frontlib.Flib;
@@ -60,4 +61,24 @@
 		WeaponsetListPtr ptr = WeaponsetListPtr.createJavaOwned(weaponsets);
 		Flib.INSTANCE.flib_weaponsetlist_to_ini(getUserWeaponsetsFile(c).getAbsolutePath(), ptr);
 	}
+	
+	public static void deleteUserWeaponset(Context c, String setToDelete) throws IOException {
+		List<Weaponset> userWeaponsets = loadUserWeaponsets(c);
+		for(Iterator<Weaponset> iter = userWeaponsets.iterator(); iter.hasNext();) {
+			Weaponset set = iter.next();
+			if(set.name.equals(setToDelete)) {
+				iter.remove();
+				break;
+			}
+		}
+		saveUserWeaponsets(c, userWeaponsets);
+	}
+	
+	public static List<String> toNameList(List<Weaponset> weaponsets) {
+		List<String> result = new ArrayList<String>();
+		for(Weaponset weaponset : weaponsets) {
+			result.add(weaponset.name);
+		}
+		return result;
+	}
 }
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Downloader/DownloadAssets.java	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Downloader/DownloadAssets.java	Sat Aug 18 00:47:51 2012 +0200
@@ -6,10 +6,10 @@
 
 import org.hedgewars.hedgeroid.MainActivity;
 import org.hedgewars.hedgeroid.R;
-import org.hedgewars.hedgeroid.Utils;
 import org.hedgewars.hedgeroid.Datastructures.Schemes;
 import org.hedgewars.hedgeroid.Datastructures.Team;
 import org.hedgewars.hedgeroid.Datastructures.Weaponsets;
+import org.hedgewars.hedgeroid.util.FileUtils;
 
 import android.content.res.AssetManager;
 import android.os.AsyncTask;
@@ -25,7 +25,7 @@
 	
 	private void copyFileOrDir(AssetManager assetManager, File target, String assetPath) throws IOException {
 		try {
-			Utils.writeStreamToFile(assetManager.open(assetPath), target);
+			FileUtils.writeStreamToFile(assetManager.open(assetPath), target);
 		} catch(FileNotFoundException e) {
 			/*
 			 * I can't find a better way to figure out whether an asset entry is
@@ -44,11 +44,11 @@
 	@Override
 	protected Boolean doInBackground(Object... params) {
 		try {
-			Utils.writeStreamToFile(act.getResources().openRawResource(R.raw.schemes_builtin), Schemes.getBuiltinSchemesFile(act));
-			Utils.writeStreamToFile(act.getResources().openRawResource(R.raw.weapons_builtin), Weaponsets.getBuiltinWeaponsetsFile(act));
-			Utils.resRawToFilesDir(act, R.array.teams, Team.DIRECTORY_TEAMS);
-			copyFileOrDir(act.getAssets(), Utils.getDataPathFile(act), "Data");
-			copyFileOrDir(act.getAssets(), new File(Utils.getCachePath(act), VERSION_FILENAME), VERSION_FILENAME);
+			FileUtils.writeStreamToFile(act.getResources().openRawResource(R.raw.schemes_builtin), Schemes.getBuiltinSchemesFile(act));
+			FileUtils.writeStreamToFile(act.getResources().openRawResource(R.raw.weapons_builtin), Weaponsets.getBuiltinWeaponsetsFile(act));
+			FileUtils.resRawToFilesDir(act, R.array.teams, R.array.teamFilenames, Team.DIRECTORY_TEAMS);
+			copyFileOrDir(act.getAssets(), FileUtils.getDataPathFile(act), "Data");
+			copyFileOrDir(act.getAssets(), new File(FileUtils.getCachePath(act), VERSION_FILENAME), VERSION_FILENAME);
 			return Boolean.TRUE;
 		} catch(IOException e) {
 			Log.e("DownloadAssets", e.getMessage(), e);
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Downloader/DownloadPackage.java	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Downloader/DownloadPackage.java	Sat Aug 18 00:47:51 2012 +0200
@@ -20,7 +20,7 @@
 
 import java.io.IOException;
 
-import org.hedgewars.hedgeroid.Utils;
+import org.hedgewars.hedgeroid.util.FileUtils;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -136,7 +136,7 @@
 							version = -1;
 						}
 					}else if(name.equals("path")){
-						path = Utils.getDataPath(c) + text;
+						path = FileUtils.getDataPath(c) + text;
 					}else if(name.equals("representation")){
 						representation = text;
 					}else if(name.equals("description")){
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/EngineProtocol/PascalExports.java	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/EngineProtocol/PascalExports.java	Sat Aug 18 00:47:51 2012 +0200
@@ -32,5 +32,8 @@
 	}
 	
 	public static native int HWgetMaxNumberOfTeams();
-    public static native int HWterminate(boolean b);	
+    public static native int HWterminate(boolean b);
+    public static native int HWGenLandPreview(int port);
+    
+    public static Object engineMutex = new Object();
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/GameConnection.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,214 @@
+package org.hedgewars.hedgeroid;
+
+import org.hedgewars.hedgeroid.Datastructures.GameConfig;
+import org.hedgewars.hedgeroid.frontlib.Flib;
+import org.hedgewars.hedgeroid.frontlib.Frontlib;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.BytesCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.GameSetupPtr;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.GameconnPtr;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.IntCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.StrBoolCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.StrCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.VoidCallback;
+import org.hedgewars.hedgeroid.netplay.GameMessageListener;
+import org.hedgewars.hedgeroid.netplay.Netplay;
+import org.hedgewars.hedgeroid.util.TickHandler;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.util.Log;
+
+import com.sun.jna.Memory;
+import com.sun.jna.NativeLong;
+import com.sun.jna.Pointer;
+
+public final class GameConnection {
+	private static final Handler mainHandler = new Handler(Looper.getMainLooper());
+	
+	private final HandlerThread thread;
+	private final Handler handler;
+	private final TickHandler tickHandler;
+	private final Netplay netplay; // ==null if not a netgame
+	private GameconnPtr conn;
+
+	/**
+	 * The actual connection has to be set up on a separate thread because networking
+	 * is not allowed on the UI thread, so the port can't be queried immediately after
+	 * creating the GameConnection object. Instead, one of these interface methods is
+	 * called once we know which port we are listening on (or once we fail to set this up).
+	 * Methods will be called on the UI thread.
+	 */
+	public static interface Listener {
+		/**
+		 * We are listening for the engine at $port, go start the engine.
+		 */
+		void gameConnectionReady(int port);
+		
+		/**
+		 * The connection has stopped, either because the game has ended or was interrupted,
+		 * or maybe we failed to create the connection at all (in that case gameConnectionReady wasn't called).
+		 */
+		void gameConnectionDisconnected(int reason);
+	}
+	
+	private GameConnection(Netplay netplay) {
+		this.netplay = netplay;
+		thread = new HandlerThread("IPCThread");
+		thread.start();
+		handler = new Handler(thread.getLooper());
+		tickHandler = new TickHandler(thread.getLooper(), 50, new Runnable() {
+			public void run() {
+				if(conn != null) {
+					Flib.INSTANCE.flib_gameconn_tick(conn);
+				}
+			}
+		});
+		tickHandler.start();
+	}
+	
+	public static GameConnection forNetgame(final GameConfig config, Netplay netplay, final Listener listener) {
+		final GameConnection result = new GameConnection(netplay);
+		final String playerName = netplay.getPlayerName();
+		result.handler.post(new Runnable() {
+			public void run() {
+				GameconnPtr conn = Flib.INSTANCE.flib_gameconn_create(playerName, GameSetupPtr.createJavaOwned(config), true);
+				result.setupConnection(conn, true, listener);
+			}
+		});
+		return result;
+	}
+	
+	public static GameConnection forLocalGame(final GameConfig config, final Listener listener) {
+		final GameConnection result = new GameConnection(null);
+		result.handler.post(new Runnable() {
+			public void run() {
+				GameconnPtr conn = Flib.INSTANCE.flib_gameconn_create("Player", GameSetupPtr.createJavaOwned(config), false);
+				result.setupConnection(conn, false, listener);
+			}
+		});
+		return result;
+	}
+	
+	// runs on the IPCThread
+	private void setupConnection(GameconnPtr conn, final boolean netgame, final Listener listener) {
+		if(conn == null) {
+			mainHandler.post(new Runnable() {
+				public void run() { listener.gameConnectionDisconnected(Frontlib.GAME_END_ERROR); }
+			});
+			shutdown();
+		} else {
+			this.conn = conn;
+			final int port = Flib.INSTANCE.flib_gameconn_getport(conn);
+			mainHandler.post(new Runnable() {
+				public void run() { 
+					listener.gameConnectionReady(port);
+					if(netgame) {
+						netplay.registerGameMessageListener(gameMessageListener);
+					}
+				}
+			});
+			Flib.INSTANCE.flib_gameconn_onConnect(conn, connectCb, null);
+			Flib.INSTANCE.flib_gameconn_onDisconnect(conn, disconnectCb, null);
+			Flib.INSTANCE.flib_gameconn_onErrorMessage(conn, errorMessageCb, null);
+			if(netgame) {
+				Flib.INSTANCE.flib_gameconn_onChat(conn, chatCb, null);
+				Flib.INSTANCE.flib_gameconn_onEngineMessage(conn, engineMessageCb, null);
+			}
+		}
+	}
+	
+	// runs on the IPCThread
+	private void shutdown() {
+		tickHandler.stop();
+		thread.quit();
+		Flib.INSTANCE.flib_gameconn_destroy(conn);
+		if(netplay != null) {
+			mainHandler.post(new Runnable() {
+				public void run() {
+					netplay.unregisterGameMessageListener(gameMessageListener);
+				}
+			});
+		}
+	}
+	
+	// runs on the IPCThread
+	private final StrBoolCallback chatCb = new StrBoolCallback() {
+		public void callback(Pointer context, String message, boolean teamChat) {
+			if(teamChat) {
+				netplay.sendTeamChat(message);
+			} else {
+				netplay.sendChat(message);
+			}
+		}
+	};
+	
+	// runs on the IPCThread
+	private final VoidCallback connectCb = new VoidCallback() {
+		public void callback(Pointer context) {
+			Log.i("GameConnection", "Connected");
+		}
+	};
+	
+	// runs on the IPCThread
+	private final IntCallback disconnectCb = new IntCallback() {
+		public void callback(Pointer context, int reason) {
+			if(netplay != null) {
+				netplay.sendRoundFinished(reason==Frontlib.GAME_END_FINISHED);
+			}
+			shutdown();
+		}
+	};
+	
+	// runs on the IPCThread
+	private final BytesCallback engineMessageCb = new BytesCallback() {
+		public void callback(Pointer context, Pointer buffer, NativeLong size) {
+			netplay.sendEngineMessage(buffer.getByteArray(0, size.intValue()));
+		}
+	};
+	
+	// runs on the IPCThread
+	private final StrCallback errorMessageCb = new StrCallback() {
+		public void callback(Pointer context, String message) {
+			Log.e("GameConnection", message);
+		}
+	};
+	
+	// runs on any thread
+	private final GameMessageListener gameMessageListener = new GameMessageListener() {
+		public void onNetDisconnected() {
+			handler.post(new Runnable() {
+				public void run() {
+					shutdown();
+				}
+			});
+		}
+		
+		public void onMessage(final int type, final String message) {
+			handler.post(new Runnable() {
+				public void run() {
+					Flib.INSTANCE.flib_gameconn_send_textmsg(conn, type, message);
+				}
+			});
+		}
+		
+		public void onEngineMessage(final byte[] em) {
+			handler.post(new Runnable() {
+				public void run() {
+					Memory mem = new Memory(em.length);
+					mem.write(0, em, 0, em.length);
+					Flib.INSTANCE.flib_gameconn_send_enginemsg(conn, mem, new NativeLong(em.length));
+				}
+			});
+			
+		}
+		
+		public void onChatMessage(final String nick, final String message) {
+			handler.post(new Runnable() {
+				public void run() {
+					Flib.INSTANCE.flib_gameconn_send_chatmsg(conn, nick, message);
+				}
+			});
+		}
+	};
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/LobbyActivity.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,140 @@
+package org.hedgewars.hedgeroid;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.NetplayStateFragment.NetplayStateListener;
+import org.hedgewars.hedgeroid.netplay.Netplay;
+import org.hedgewars.hedgeroid.netplay.Netplay.State;
+import org.hedgewars.hedgeroid.util.TextInputDialog;
+import org.hedgewars.hedgeroid.util.TextInputDialog.TextInputDialogListener;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentTransaction;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TabHost;
+import android.widget.TextView;
+
+public class LobbyActivity extends FragmentActivity implements TextInputDialogListener, NetplayStateListener {
+	private static final int DIALOG_CREATE_ROOM = 0;
+	
+    private TabHost tabHost;
+    private Netplay netplay;
+    
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        
+        setContentView(R.layout.activity_lobby);
+        ChatFragment chatFragment = (ChatFragment)getSupportFragmentManager().findFragmentById(R.id.chatFragment);
+        chatFragment.setInRoom(false);
+        
+        FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
+        trans.add(new NetplayStateFragment(), "netplayFragment");
+        trans.commit();
+        
+        netplay = Netplay.getAppInstance(getApplicationContext());
+        
+        tabHost = (TabHost)findViewById(android.R.id.tabhost);
+        if(tabHost != null) {
+	        tabHost.setup();
+	        tabHost.getTabWidget().setOrientation(LinearLayout.VERTICAL);
+
+	        tabHost.addTab(tabHost.newTabSpec("rooms").setIndicator(createIndicatorView(tabHost, R.string.lobby_tab_rooms, getResources().getDrawable(R.drawable.roomlist_ingame))).setContent(R.id.roomListFragment));
+	        tabHost.addTab(tabHost.newTabSpec("chat").setIndicator(createIndicatorView(tabHost, R.string.lobby_tab_chat, getResources().getDrawable(R.drawable.edit))).setContent(R.id.chatFragment));
+	        tabHost.addTab(tabHost.newTabSpec("players").setIndicator(createIndicatorView(tabHost, R.string.lobby_tab_players, getResources().getDrawable(R.drawable.human))).setContent(R.id.playerListFragment));
+	
+	        if (icicle != null) {
+	            tabHost.setCurrentTabByTag(icicle.getString("currentTab"));
+	        }
+        }
+    }
+    
+    private View createIndicatorView(TabHost tabHost, int label, Drawable icon) {
+        LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+        View tabIndicator = inflater.inflate(R.layout.tab_indicator,
+                tabHost.getTabWidget(), // tab widget is the parent
+                false); // no inflate params
+
+        final TextView tv = (TextView) tabIndicator.findViewById(R.id.title);
+        tv.setText(label);
+        
+        if(icon != null) {
+	        final ImageView iconView = (ImageView) tabIndicator.findViewById(R.id.icon);
+	        iconView.setImageDrawable(icon);
+        }
+        
+        return tabIndicator;
+    }
+    
+	@Override
+	public boolean onCreateOptionsMenu(Menu menu) {
+		super.onCreateOptionsMenu(menu);
+		getMenuInflater().inflate(R.menu.lobby_options, menu);
+		return true;
+	}
+	
+	@Override
+	public boolean onOptionsItemSelected(MenuItem item) {
+		switch(item.getItemId()) {
+		case R.id.room_create:
+	        TextInputDialog dialog = new TextInputDialog(DIALOG_CREATE_ROOM, R.string.dialog_create_room_title, 0, R.string.dialog_create_room_hint);
+	        dialog.show(getSupportFragmentManager(), "create_room_dialog");
+			return true;
+		case R.id.disconnect:
+			netplay.disconnect();
+			return true;
+		default:
+			return super.onOptionsItemSelected(item);
+		}
+	}
+	
+	@Override
+	public void onBackPressed() {
+		super.onBackPressed();
+		netplay.disconnect();
+	}
+	
+    @Override
+    protected void onSaveInstanceState(Bundle icicle) {
+        super.onSaveInstanceState(icicle);
+        if(tabHost != null) {
+        	icicle.putString("currentTab", tabHost.getCurrentTabTag());
+        }
+    }
+    
+    public void onTextInputDialogSubmitted(int dialogId, String text) {
+    	if(text != null && text.length()>0) {
+    		netplay.sendCreateRoom(text);
+    	}
+    }
+    
+    public void onTextInputDialogCancelled(int dialogId) {
+    }
+    
+    public void onNetplayStateChanged(State newState) {
+    	switch(newState) {
+    	case CONNECTING:
+    	case NOT_CONNECTED:
+    		finish();
+    		break;
+    	case ROOM:
+    	case INGAME:
+    		startActivity(new Intent(getApplicationContext(), RoomActivity.class));
+    		break;
+    	case LOBBY:
+    		// Do nothing
+    		break;
+		default:
+			throw new IllegalStateException("Unknown connection state: "+newState);
+    	}
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/LobbyPlayerlistAdapter.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,32 @@
+package org.hedgewars.hedgeroid;
+
+import java.util.Comparator;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.Datastructures.Player;
+import org.hedgewars.hedgeroid.util.ObservableTreeMapAdapter;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+public class LobbyPlayerlistAdapter extends ObservableTreeMapAdapter<String, Player> {
+	@Override
+	protected Comparator<Player> getEntryOrder() {
+		return Player.NAME_ORDER;
+	}
+
+	public View getView(int position, View convertView, ViewGroup parent) {
+		View v = convertView;
+		if (v == null) {
+			LayoutInflater vi = LayoutInflater.from(parent.getContext());
+			v = vi.inflate(R.layout.listview_player, null);
+		}
+
+		String player = getItem(position).name;
+		TextView username = (TextView) v.findViewById(android.R.id.text1);
+		username.setText(player);
+		return v;
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/LobbyPlayerlistFragment.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,75 @@
+package org.hedgewars.hedgeroid;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.Datastructures.Player;
+import org.hedgewars.hedgeroid.netplay.Netplay;
+
+import android.os.Bundle;
+import android.support.v4.app.ListFragment;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.LayoutInflater;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+
+public class LobbyPlayerlistFragment extends ListFragment {
+	private Netplay netplay;
+	private LobbyPlayerlistAdapter adapter;
+	
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		netplay = Netplay.getAppInstance(getActivity().getApplicationContext());
+		adapter = new LobbyPlayerlistAdapter();
+		adapter.setSource(netplay.lobbyPlayerlist);
+		setListAdapter(adapter);
+	}
+
+	@Override
+	public void onDestroy() {
+		super.onDestroy();
+		adapter.invalidate();
+	}
+	
+	@Override
+	public void onActivityCreated(Bundle savedInstanceState) {
+		super.onActivityCreated(savedInstanceState);
+		registerForContextMenu(getListView());
+	}
+
+	@Override
+	public void onCreateContextMenu(ContextMenu menu, View v,
+			ContextMenuInfo menuInfo) {
+		super.onCreateContextMenu(menu, v, menuInfo);
+		AdapterContextMenuInfo info = (AdapterContextMenuInfo)menuInfo;
+		MenuInflater inflater = getActivity().getMenuInflater();
+		inflater.inflate(R.menu.lobby_playerlist_context, menu);
+		menu.setHeaderIcon(R.drawable.human);
+		menu.setHeaderTitle(adapter.getItem(info.position).name);
+	}
+	
+	@Override
+	public boolean onContextItemSelected(MenuItem item) {
+		AdapterContextMenuInfo info = (AdapterContextMenuInfo)item.getMenuInfo();
+		Player player = adapter.getItem(info.position);
+		switch(item.getItemId()) {
+		case R.id.player_info:
+			netplay.sendPlayerInfoQuery(player.name);
+			return true;
+		case R.id.player_follow:
+			netplay.sendFollowPlayer(player.name);
+			return true;
+		default:
+			return super.onContextItemSelected(item);
+		}
+	}
+	
+	@Override
+	public View onCreateView(LayoutInflater inflater, ViewGroup container,
+			Bundle savedInstanceState) {
+		return inflater.inflate(R.layout.fragment_playerlist, container, false);
+	}
+}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/MainActivity.java	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/MainActivity.java	Sat Aug 18 00:47:51 2012 +0200
@@ -25,9 +25,9 @@
 import org.hedgewars.hedgeroid.Downloader.DownloadAssets;
 import org.hedgewars.hedgeroid.Downloader.DownloadListActivity;
 import org.hedgewars.hedgeroid.frontlib.Flib;
-import org.hedgewars.hedgeroid.netplay.LobbyActivity;
 import org.hedgewars.hedgeroid.netplay.Netplay;
 import org.hedgewars.hedgeroid.netplay.Netplay.State;
+import org.hedgewars.hedgeroid.util.FileUtils;
 
 import android.app.AlertDialog;
 import android.app.Dialog;
@@ -65,19 +65,19 @@
 		startLocalGame.setOnClickListener(startGameListener);
 		startNetGame.setOnClickListener(startNetGameListener);
 
-		if(!Utils.isDataPathAvailable()){
+		if(!FileUtils.isDataPathAvailable()){
 			showDialog(DIALOG_NO_SDCARD);
 		} else {
 			String existingVersion = "";
 			try {
-				File versionFile = new File(Utils.getCachePath(this), "assetsversion.txt");
-				existingVersion = Utils.readToString(new FileInputStream(versionFile));
+				File versionFile = new File(FileUtils.getCachePath(this), "assetsversion.txt");
+				existingVersion = FileUtils.readToString(new FileInputStream(versionFile));
 			} catch(IOException e) {
 			}
 			
 			String newVersion = "";
 			try {
-				newVersion = Utils.readToString(getAssets().open("assetsversion.txt"));
+				newVersion = FileUtils.readToString(getAssets().open("assetsversion.txt"));
 			} catch(IOException e) {
 			}
 			
@@ -125,23 +125,14 @@
 		case R.id.preferences:
 			Toast.makeText(this, R.string.not_implemented_yet, Toast.LENGTH_SHORT).show();
 			return true;
+		case R.id.edit_weaponsets:
+			startActivity(new Intent(getApplicationContext(), WeaponsetListActivity.class));
+			return true;
 		default:
 			return super.onOptionsItemSelected(item);
 		}
 	}
 	
-	@Override
-	protected void onStart() {
-		super.onStart();
-		Netplay.getAppInstance(getApplicationContext()).requestFastTicks();
-	}
-	
-	@Override
-	protected void onStop() {
-		super.onStop();
-		Netplay.getAppInstance(getApplicationContext()).unrequestFastTicks();
-	}
-	
 	public Dialog onCreateDialog(int id, Bundle args){
 		switch(id) {
 		case DIALOG_NO_SDCARD:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/MapFragment.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,279 @@
+package org.hedgewars.hedgeroid;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.Datastructures.FrontendDataUtils;
+import org.hedgewars.hedgeroid.Datastructures.MapFile;
+import org.hedgewars.hedgeroid.Datastructures.MapRecipe;
+import org.hedgewars.hedgeroid.Datastructures.Scheme;
+import org.hedgewars.hedgeroid.Datastructures.Weaponset;
+import org.hedgewars.hedgeroid.frontlib.Frontlib;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.Spinner;
+import android.widget.TableRow;
+import android.widget.Toast;
+
+public class MapFragment extends Fragment implements RoomStateManager.Observer {
+	private Spinner mapTypeSpinner, mapNameSpinner, templateSpinner, mazeSizeSpinner;
+	private TableRow nameRow, templateRow, mazeSizeRow;
+	private ImageView mapPreview;
+	private List<MapFile> mapFiles;
+	private RoomStateManager stateManager;
+	private Random random = new Random();
+	private CalmDownHandler mapPreviewHandler;
+	
+	/*
+	 * Rendering the preview can take a few seconds on Android, so we want to prevent preview
+	 * requests from queueing up if maps are changed quickly. So if there is already a preview
+	 * being generated, we store our latest request in the newPreviewRequest variable instead.
+	 * Once the current preview is finished generating it will start on that one.
+	 */
+	private boolean previewGenerationInProgress;
+	private MapRecipe newPreviewRequest;
+	
+	@Override
+	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+		View v = inflater.inflate(R.layout.fragment_map, container, false);
+
+		final Context appContext = getActivity().getApplicationContext();
+		mapPreviewHandler = new CalmDownHandler(getActivity().getMainLooper(), new Runnable() {
+			public void run() {
+				if(!previewGenerationInProgress) {
+					mapPreview.setImageResource(R.drawable.roomlist_preparing);
+					MapPreviewGenerator.startPreviewGeneration(appContext, stateManager.getMapRecipe(), mapPreviewListener);
+					previewGenerationInProgress = true;
+				} else {
+					newPreviewRequest = stateManager.getMapRecipe();
+				}
+			}
+		}, 250);
+		
+		nameRow = (TableRow) v.findViewById(R.id.rowMapName);
+		templateRow = (TableRow) v.findViewById(R.id.rowTemplateFilter);
+		mazeSizeRow = (TableRow) v.findViewById(R.id.rowMazeSize);
+		mapPreview = (ImageView) v.findViewById(R.id.mapPreview);
+		mapPreview.setImageDrawable(null);;
+		mapPreview.setOnClickListener(mapClickListener);
+		
+		try {
+			mapFiles = FrontendDataUtils.getMaps(getActivity());
+		} catch (IOException e) {
+			Toast.makeText(getActivity().getApplicationContext(), R.string.error_missing_sdcard_or_files, Toast.LENGTH_LONG).show();
+			getActivity().finish();
+		}
+		Collections.sort(mapFiles, MapFile.MISSIONS_FIRST_NAME_ORDER);
+		
+		List<String> mapNames = MapFile.toDisplayNameList(mapFiles, getResources());
+		mapTypeSpinner = prepareSpinner(v, R.id.spinMapType, Arrays.asList(getResources().getStringArray(R.array.map_types)), mapTypeSelectedListener);
+		mapNameSpinner = prepareSpinner(v, R.id.spinMapName, mapNames, mapNameSelectedListener);
+		templateSpinner = prepareSpinner(v, R.id.spinTemplateFilter, Arrays.asList(getResources().getStringArray(R.array.map_templates)), mapTemplateSelectedListener);
+		mazeSizeSpinner = prepareSpinner(v, R.id.spinMazeSize, Arrays.asList(getResources().getStringArray(R.array.map_maze_sizes)), mazeSizeSelectedListener);
+
+		stateManager.registerObserver(this);
+		MapRecipe map = stateManager.getMapRecipe();
+		if(map != null) {
+			updateDisplay(map);
+		}
+		setChiefState(stateManager.getChiefStatus());
+		mapPreviewHandler.activity();
+		return v;
+	}
+	
+	private static Spinner prepareSpinner(View v, int id, List<String> items, OnItemSelectedListener itemSelectedListener) {
+		Spinner spinner = (Spinner)v.findViewById(id);
+		ArrayAdapter<String> adapter = new ArrayAdapter<String>(v.getContext(), R.layout.listview_item, items);
+		adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+		spinner.setAdapter(adapter);
+		spinner.setOnItemSelectedListener(itemSelectedListener);
+		return spinner;
+	}
+	
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		try {
+			stateManager = ((RoomStateManager.Provider)getActivity()).getRoomStateManager();
+		} catch(ClassCastException e) {
+			throw new RuntimeException("Hosting activity must implement RoomStateManager.Provider.", e);
+		}
+	}
+	
+	@Override
+	public void onDestroy() {
+		super.onDestroy();
+		mapPreviewHandler.stop();
+		stateManager.unregisterObserver(this);
+	}
+	
+	private void setChiefState(boolean chiefState) {
+		mapTypeSpinner.setEnabled(chiefState);
+		mapNameSpinner.setEnabled(chiefState);
+		templateSpinner.setEnabled(chiefState);
+		mazeSizeSpinner.setEnabled(chiefState);
+		mapPreview.setEnabled(chiefState);
+		
+		if(chiefState) {
+			sendMapnameAndGenerator();
+			stateManager.changeMapTemplate(templateSpinner.getSelectedItemPosition());
+			stateManager.changeMazeSize(mazeSizeSpinner.getSelectedItemPosition());
+		}
+	}
+	
+	private void updateDisplay(MapRecipe map) {
+		nameRow.setVisibility(map.mapgen == Frontlib.MAPGEN_NAMED ? View.VISIBLE : View.GONE);
+		templateRow.setVisibility(map.mapgen == Frontlib.MAPGEN_REGULAR ? View.VISIBLE : View.GONE);
+		mazeSizeRow.setVisibility(map.mapgen == Frontlib.MAPGEN_MAZE ? View.VISIBLE : View.GONE);
+		
+		mapTypeSpinner.setSelection(map.mapgen);
+		int mapPosition = findMapPosition(mapFiles, map.name);
+		if(mapPosition >= 0) {
+			mapNameSpinner.setSelection(mapPosition);
+		}
+		templateSpinner.setSelection(map.templateFilter);
+		mazeSizeSpinner.setSelection(map.mazeSize);
+	}
+	
+	private static int findMapPosition(List<MapFile> mapFiles, String mapName) {
+		for(int i=0; i<mapFiles.size(); i++) {
+			if(mapName.equals(mapFiles.get(i).name)) {
+				return i;
+			}
+		}
+		return -1;
+	}
+	
+	private void sendMapnameAndGenerator() {
+		int mapType = mapTypeSpinner.getSelectedItemPosition();
+		String mapName = mapFiles.get(mapNameSpinner.getSelectedItemPosition()).name;
+		stateManager.changeMapNameAndGenerator(MapRecipe.mapnameForGenerator(mapType, mapName));
+	}
+	
+	private final OnItemSelectedListener mapTypeSelectedListener = new OnItemSelectedListener() {
+		public void onItemSelected(AdapterView<?> adapter, View v, int position, long arg3) {
+			sendMapnameAndGenerator();
+		}
+		public void onNothingSelected(AdapterView<?> arg0) {}
+	};
+	
+	private final OnItemSelectedListener mapNameSelectedListener = new OnItemSelectedListener() {
+		public void onItemSelected(AdapterView<?> adapter, View v, int position, long arg3) {
+			sendMapnameAndGenerator();
+		}
+		public void onNothingSelected(AdapterView<?> arg0) {}
+	};
+	
+	private final OnItemSelectedListener mapTemplateSelectedListener = new OnItemSelectedListener() {
+		public void onItemSelected(AdapterView<?> adapter, View v, int position, long arg3) {
+			stateManager.changeMapTemplate(position);
+		}
+		public void onNothingSelected(AdapterView<?> arg0) {}
+	};
+	
+	private final OnItemSelectedListener mazeSizeSelectedListener = new OnItemSelectedListener() {
+		public void onItemSelected(AdapterView<?> adapter, View v, int position, long arg3) {
+			stateManager.changeMazeSize(position);
+		}
+		public void onNothingSelected(AdapterView<?> arg0) {}
+	};
+	
+	private final OnClickListener mapClickListener = new OnClickListener() {
+		public void onClick(View v) {
+			stateManager.changeMapSeed(MapRecipe.makeRandomSeed());
+			switch(mapTypeSpinner.getSelectedItemPosition()) {
+			case Frontlib.MAPGEN_NAMED:
+				mapNameSpinner.setSelection(random.nextInt(mapNameSpinner.getCount()));
+				break;
+			case Frontlib.MAPGEN_REGULAR:
+				templateSpinner.setSelection(Frontlib.TEMPLATEFILTER_ALL);
+				break;
+			case Frontlib.MAPGEN_MAZE:
+				mazeSizeSpinner.setSelection(random.nextInt(mazeSizeSpinner.getCount()));
+				break;
+			}
+		}
+	};
+	
+	public void onChiefStatusChanged(boolean isChief) {
+		setChiefState(isChief);
+	}
+	
+	public void onMapChanged(MapRecipe recipe) {
+		updateDisplay(recipe);
+		mapPreviewHandler.activity();
+	}
+	
+	public void onGameStyleChanged(String gameStyle) { }
+	public void onSchemeChanged(Scheme scheme) { }
+	public void onWeaponsetChanged(Weaponset weaponset) { }
+	
+	private MapPreviewGenerator.Listener mapPreviewListener = new MapPreviewGenerator.Listener() {
+		public void onMapPreviewResult(Drawable preview) {
+			if(newPreviewRequest != null) {
+				MapPreviewGenerator.startPreviewGeneration(getActivity().getApplicationContext(), newPreviewRequest, mapPreviewListener);
+				newPreviewRequest = null;
+			} else {
+				if(mapPreview != null) {
+					mapPreview.setImageDrawable(preview);
+				}
+				previewGenerationInProgress = false;
+			}
+		}
+	};
+	
+	/**
+	 * This class allows you to define a runnable that is called when there has been no activity
+	 * for a set amount of time, where activity is determined by calls to the activity() method
+	 * of the handler. It is used here to update the map preview when there have been no updates
+	 * to the relevant map information for a time, to prevent triggering several updates at once
+	 * when different parts of the information change.
+	 */
+	private static final class CalmDownHandler extends Handler {
+		int runningMessagesCounter = 0;
+		final Runnable inactivityRunnable;
+		final long inactivityMs;
+		boolean stopped;
+
+		public CalmDownHandler(Looper looper, Runnable runnable, long inactivityMs) {
+			super(looper);
+			this.inactivityRunnable = runnable;
+			this.inactivityMs = inactivityMs;
+		}
+		
+		public void activity() {
+			runningMessagesCounter++;
+			sendMessageDelayed(obtainMessage(), inactivityMs);
+		}
+		
+		@Override
+		public void handleMessage(Message msg) {
+			runningMessagesCounter--;
+			if(runningMessagesCounter==0 && !stopped) {
+				inactivityRunnable.run();
+			}
+		}
+		
+		public void stop() {
+			stopped = true;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/MapPreviewGenerator.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,200 @@
+package org.hedgewars.hedgeroid;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+
+import org.hedgewars.hedgeroid.Datastructures.MapFile;
+import org.hedgewars.hedgeroid.Datastructures.MapRecipe;
+import org.hedgewars.hedgeroid.EngineProtocol.PascalExports;
+import org.hedgewars.hedgeroid.frontlib.Flib;
+import org.hedgewars.hedgeroid.frontlib.Frontlib;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.MapRecipePtr;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.MapconnPtr;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.MapimageCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.StrCallback;
+import org.hedgewars.hedgeroid.util.FileUtils;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import com.sun.jna.Pointer;
+
+/**
+ * A class that asynchronously generates a map preview from a MapRecipe.
+ * 
+ * For named maps, this will load the preview image from the filesystem. For others,
+ * it will call the engine to generate a preview image.
+ */
+public final class MapPreviewGenerator implements Runnable {
+	private static final String TAG = MapPreviewGenerator.class.getSimpleName();
+	private static final Handler mainHandler = new Handler(Looper.getMainLooper());
+
+	private final Context appContext;
+	private final MapRecipe map;
+	private final Listener listener;
+	
+	private boolean resultAvailable;
+	private Drawable result;
+	
+	public static interface Listener {
+		/**
+		 * This is called on the UI thread once the preview is ready or failed.
+		 * In case of failure, null is passed.
+		 */
+		void onMapPreviewResult(Drawable preview);
+	}
+
+	private MapPreviewGenerator(Context appContext, MapRecipe map, Listener listener) {
+		this.appContext = appContext;
+		this.map = map;
+		this.listener = listener;
+	}
+	
+	public void run() {
+		if (map.mapgen == Frontlib.MAPGEN_NAMED) {
+			postToListener(loadPreviewFromFile(appContext, map.name));
+		} else {
+			resultAvailable = false;
+			result = null;
+			MapconnPtr conn = Flib.INSTANCE.flib_mapconn_create(MapRecipePtr.createJavaOwned(map));
+			if (conn == null) {
+				postToListener(null);
+				return;
+			}
+			try {
+				int port = Flib.INSTANCE.flib_mapconn_getport(conn);
+				Flib.INSTANCE.flib_mapconn_onSuccess(conn, successCb, null);
+				Flib.INSTANCE.flib_mapconn_onFailure(conn, failureCb, null);
+	
+				String configPath;
+				try {
+					configPath = FileUtils.getCachePath(appContext).getAbsolutePath();
+				} catch(FileNotFoundException e) {
+					return;
+				}
+				
+				startEngine(configPath, port);
+				long startTime = System.nanoTime();
+				do {
+					Flib.INSTANCE.flib_mapconn_tick(conn);
+					try {
+						Thread.sleep(50);
+					} catch (InterruptedException e) {
+						// ignore
+					}
+				} while(!resultAvailable && System.nanoTime()-startTime < 15000000000l); // 15 seconds timeout
+			} finally {
+				Flib.INSTANCE.flib_mapconn_destroy(conn);
+				postToListener(result);
+			}
+		}
+	}
+	
+	public static void startPreviewGeneration(Context appContext, MapRecipe map, Listener listener) {
+		new Thread(new MapPreviewGenerator(appContext, map, listener)).start();
+	}
+	
+	private static Drawable loadPreviewFromFile(Context appContext, String mapName) {
+		if(!mapName.startsWith("+")) {
+			try {
+				File previewFile = MapFile.getPreviewFile(appContext, mapName);
+				return Drawable.createFromPath(previewFile.getAbsolutePath());
+			} catch (FileNotFoundException e) {
+				Log.w("MapPreviewGenerator", "Preview for map "+mapName+" not found.");
+			}
+		}
+		return null;
+	}
+	
+	private static void startEngine(final String configPath, final int port) {
+		new Thread(new Runnable() {
+			public void run() {
+				Log.d(TAG, "Starting engine "+port);
+				synchronized(PascalExports.engineMutex) {
+					PascalExports.HWGenLandPreview(port);
+				}
+				Log.d(TAG, "Engine finished");
+			}
+		}).start();
+	}
+	
+	private void postToListener(final Drawable result) {
+		mainHandler.post(new Runnable() {
+			public void run() {
+				listener.onMapPreviewResult(result);
+			}
+		});
+	}
+	
+	/**
+	 * Let's be extra nice here and clip off the left and right sides, so the preview is centered...
+	 * Since the image is present in bytes, we can save some effort by checking entire byte-columns first.
+	 */
+	private final MapimageCallback successCb = new MapimageCallback() {
+		public void callback(Pointer context, Pointer buffer, int hedgehogCount) {
+			Log.d(TAG, "Running success handler");
+			byte[] mapdata = buffer.getByteArray(0, Frontlib.MAPIMAGE_BYTES);
+			
+			int leftmostPixel = Frontlib.MAPIMAGE_WIDTH;
+			int rightmostPixel = -1;
+			int bytesPerLine = Frontlib.MAPIMAGE_WIDTH/8;
+			
+			// Find the leftmost pixel
+			for(int xbyte=0; xbyte<bytesPerLine; xbyte++) {
+				for(int y=0; y<Frontlib.MAPIMAGE_HEIGHT; y++) {
+					int b = 0xff&mapdata[xbyte+y*bytesPerLine];
+					if(b != 0) {
+						leftmostPixel = Math.min(leftmostPixel, Integer.numberOfLeadingZeros(b)-24+xbyte*8);
+					}
+				}
+				if(leftmostPixel!=Frontlib.MAPIMAGE_WIDTH) break;
+			}
+			
+			// Find the rightmost pixel
+			for(int xbyte=bytesPerLine-1; xbyte>=0; xbyte--) {
+				for(int y=0; y<Frontlib.MAPIMAGE_HEIGHT; y++) {
+					int b = mapdata[xbyte+y*bytesPerLine];
+					if(b != 0) {
+						rightmostPixel = Math.max(rightmostPixel, xbyte*8+7-Integer.numberOfTrailingZeros(b));
+					}
+				}
+				if(rightmostPixel!=-1) break;
+			}
+		
+			// No pixel was set at all -> use default width
+			if(rightmostPixel==-1) {
+				leftmostPixel = 0;
+				rightmostPixel = Frontlib.MAPIMAGE_WIDTH-1;
+			}
+			
+			Bitmap bitmap = Bitmap.createBitmap(rightmostPixel-leftmostPixel+1, Frontlib.MAPIMAGE_HEIGHT, Config.ARGB_8888);
+			for(int y=0; y<Frontlib.MAPIMAGE_HEIGHT; y++) {
+				for(int x=0; x<bitmap.getWidth(); x++) {
+					bitmap.setPixel(x, y, isPixelSet(mapdata, x+leftmostPixel, y) ? Color.YELLOW : Color.TRANSPARENT);
+				}
+			}
+			result = new BitmapDrawable(bitmap);
+			resultAvailable = true;
+		}
+	};
+	
+	private static boolean isPixelSet(byte[] imgdata, int x, int y) {
+		int pixelnum = x+Frontlib.MAPIMAGE_WIDTH*y;
+		return (imgdata[pixelnum>>3] & (128>>(pixelnum&7))) != 0;
+	}
+	
+	private final StrCallback failureCb = new StrCallback() {
+		public void callback(Pointer context, String reason) {
+			Log.e(TAG, "Error generating map preview: "+reason);
+			result = null;
+			resultAvailable = true;
+		}
+	};
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/NetplayStateFragment.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,120 @@
+package org.hedgewars.hedgeroid;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.frontlib.Frontlib;
+import org.hedgewars.hedgeroid.netplay.Netplay;
+import org.hedgewars.hedgeroid.netplay.Netplay.State;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.content.LocalBroadcastManager;
+import android.widget.Toast;
+
+/**
+ * Fragment for use by an activity that depends on the state of the network
+ * connection. The activity must implement the NetplayStateListener interface.
+ * 
+ * This fragment manages a few aspects of the netplay connection: Requesting
+ * the network system loop to run at high frequency while the activity is in
+ * the foreground, and reacting to changes in the networking state by calling
+ * a callback method on the activity. 
+ */
+public class NetplayStateFragment extends Fragment {
+    private Netplay netplay;
+    private Context appContext;
+    private LocalBroadcastManager broadcastManager;
+    private NetplayStateListener listener;
+    private State knownState;
+    
+    interface NetplayStateListener {
+    	/**
+    	 * This is called while the activity is running, and every time during resume, if
+    	 * a change in the networking state is detected. It is also called once
+    	 * with the initial state (which could be called a change from the "unknown" state).
+    	 */
+    	void onNetplayStateChanged(State newState);
+    } 
+    
+    @Override
+	public void onAttach(Activity activity) {
+		super.onAttach(activity);
+		try {
+			listener = (NetplayStateListener) activity;
+		} catch(ClassCastException e) {
+			throw new ClassCastException("Activity " + activity + " must implement NetplayStateListener to use NetplayStateFragment.");
+		}
+	}
+	
+	@Override
+	public void onDetach() {
+		super.onDetach();
+		listener = null;
+	}
+	
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        appContext = getActivity().getApplicationContext();
+        broadcastManager = LocalBroadcastManager.getInstance(appContext);
+        netplay = Netplay.getAppInstance(appContext);
+    }    
+
+    @Override
+    public void onResume() {
+    	super.onResume();
+    	broadcastManager.registerReceiver(disconnectReceiver, new IntentFilter(Netplay.ACTION_DISCONNECTED));
+    	broadcastManager.registerReceiver(leaveRoomReceiver, new IntentFilter(Netplay.ACTION_LEFT_ROOM));
+    	broadcastManager.registerReceiver(stateChangeReceiver, new IntentFilter(Netplay.ACTION_STATE_CHANGED));
+    	
+    	State newState = netplay.getState();
+		if(knownState != newState) {
+    		listener.onNetplayStateChanged(newState);
+    		knownState = newState;
+    	}
+    }
+    
+    @Override
+    public void onPause() {
+    	super.onPause();
+    	broadcastManager.unregisterReceiver(disconnectReceiver);
+    	broadcastManager.unregisterReceiver(leaveRoomReceiver);
+    	broadcastManager.unregisterReceiver(stateChangeReceiver);
+    }
+
+	private final BroadcastReceiver disconnectReceiver = new BroadcastReceiver() {
+		@Override
+		public void onReceive(Context context, Intent intent) {
+			if(intent.getBooleanExtra(Netplay.EXTRA_HAS_ERROR, true)) {
+				String message = intent.getStringExtra(Netplay.EXTRA_MESSAGE);
+				String toastText = getString(R.string.toast_disconnected, message);
+				Toast.makeText(appContext, toastText, Toast.LENGTH_LONG).show();
+			}
+		}
+	};
+	
+	private final BroadcastReceiver leaveRoomReceiver = new BroadcastReceiver() {
+		@Override
+		public void onReceive(Context context, Intent intent) {
+			int reason = intent.getIntExtra(Netplay.EXTRA_REASON, -1);
+			if(reason == Frontlib.NETCONN_ROOMLEAVE_ABANDONED) {
+				Toast.makeText(appContext, R.string.toast_room_abandoned, Toast.LENGTH_LONG).show();
+			} else if(reason == Frontlib.NETCONN_ROOMLEAVE_KICKED) {
+				Toast.makeText(appContext, R.string.toast_kicked, Toast.LENGTH_LONG).show();
+			}
+		}
+	};
+	
+	private final BroadcastReceiver stateChangeReceiver = new BroadcastReceiver() {
+		@Override
+		public void onReceive(Context context, Intent intent) {
+			State newState = netplay.getState();
+			listener.onNetplayStateChanged(newState);
+			knownState = newState;
+		}
+	};
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/RoomActivity.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,85 @@
+package org.hedgewars.hedgeroid;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.Datastructures.Team;
+import org.hedgewars.hedgeroid.NetplayStateFragment.NetplayStateListener;
+import org.hedgewars.hedgeroid.netplay.Netplay;
+import org.hedgewars.hedgeroid.netplay.Netplay.State;
+
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentTransaction;
+import android.widget.TabHost;
+import android.widget.Toast;
+
+public class RoomActivity extends FragmentActivity implements NetplayStateListener, TeamAddDialog.Listener, RoomStateManager.Provider {
+	private TabHost tabHost;
+	private Netplay netplay;
+	
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        netplay = Netplay.getAppInstance(getApplicationContext());
+        
+        setContentView(R.layout.activity_netroom);
+        ChatFragment chatFragment = (ChatFragment)getSupportFragmentManager().findFragmentById(R.id.chatFragment);
+        chatFragment.setInRoom(true);
+        
+        FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
+        trans.add(new NetplayStateFragment(), "netplayFragment");
+        trans.commit();
+        
+        /*tabHost = (TabHost)findViewById(android.R.id.tabhost);
+        if(tabHost != null) {
+	        tabHost.setup();
+	        tabHost.getTabWidget().setOrientation(LinearLayout.VERTICAL);
+
+	        //tabHost.addTab(tabHost.newTabSpec("chat").setIndicator(createIndicatorView(tabHost, R.string.lobby_tab_chat, getResources().getDrawable(R.drawable.edit))).setContent(R.id.chatFragment));
+	        //tabHost.addTab(tabHost.newTabSpec("players").setIndicator(createIndicatorView(tabHost, R.string.lobby_tab_players, getResources().getDrawable(R.drawable.human))).setContent(R.id.playerListFragment));
+	
+	        if (icicle != null) {
+	            tabHost.setCurrentTabByTag(icicle.getString("currentTab"));
+	        }
+        }*/
+    }
+
+	@Override
+	public void onBackPressed() {
+		netplay.sendLeaveRoom(null);
+	}
+    
+    @Override
+    protected void onSaveInstanceState(Bundle icicle) {
+        super.onSaveInstanceState(icicle);
+        if(tabHost != null) {
+        	icicle.putString("currentTab", tabHost.getCurrentTabTag());
+        }
+    }
+    
+    public void onNetplayStateChanged(State newState) {
+    	switch(newState) {
+    	case NOT_CONNECTED:
+    	case CONNECTING:
+    	case LOBBY:
+    		finish();
+    		break;
+    	case ROOM:
+    		// Do nothing
+    		break;
+    	case INGAME:
+    		//startActivity(new Intent(getApplicationContext(), RoomActivity.class));
+    		Toast.makeText(getApplicationContext(), R.string.not_implemented_yet, Toast.LENGTH_SHORT).show();
+    		break;
+		default:
+			throw new IllegalStateException("Unknown connection state: "+newState);
+    	}
+    }
+    
+	public void onTeamAddDialogSubmitted(Team newTeam) {
+		netplay.sendAddTeam(newTeam);
+	}
+	
+	public RoomStateManager getRoomStateManager() {
+		return netplay.getRoomStateManager();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/RoomPlayerlistAdapter.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,41 @@
+package org.hedgewars.hedgeroid;
+
+import java.util.Comparator;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.Datastructures.PlayerInRoom;
+import org.hedgewars.hedgeroid.util.ObservableTreeMapAdapter;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+public class RoomPlayerlistAdapter extends ObservableTreeMapAdapter<String, PlayerInRoom> {
+	@Override
+	protected Comparator<PlayerInRoom> getEntryOrder() {
+		return AlphabeticalOrderComparator.INSTANCE;
+	}
+
+	public View getView(int position, View convertView, ViewGroup parent) {
+		View v = convertView;
+		if (v == null) {
+			LayoutInflater vi = LayoutInflater.from(parent.getContext());
+			v = vi.inflate(R.layout.listview_player, null);
+		}
+
+		PlayerInRoom player = getItem(position);
+		TextView username = (TextView) v.findViewById(android.R.id.text1);
+		username.setText(player.player.name);
+		int readyDrawable = player.ready ? R.drawable.lightbulb_on : R.drawable.lightbulb_off;
+		username.setCompoundDrawablesWithIntrinsicBounds(readyDrawable, 0, 0, 0);
+		return v;
+	}
+	
+	private static final class AlphabeticalOrderComparator implements Comparator<PlayerInRoom> {
+		public static final AlphabeticalOrderComparator INSTANCE = new AlphabeticalOrderComparator();
+		public int compare(PlayerInRoom lhs, PlayerInRoom rhs) {
+			return lhs.player.name.compareToIgnoreCase(rhs.player.name);
+		};
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/RoomPlayerlistFragment.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,103 @@
+package org.hedgewars.hedgeroid;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.Datastructures.GameConfig;
+import org.hedgewars.hedgeroid.Datastructures.Player;
+import org.hedgewars.hedgeroid.Datastructures.PlayerInRoom;
+import org.hedgewars.hedgeroid.netplay.Netplay;
+import org.hedgewars.hedgeroid.netplay.RunGameListener;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.ListFragment;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.LayoutInflater;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+import android.widget.AdapterView.OnItemClickListener;
+
+public class RoomPlayerlistFragment extends ListFragment implements OnItemClickListener, RunGameListener {
+	private Netplay netplay;
+	private RoomPlayerlistAdapter adapter;
+	
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		netplay = Netplay.getAppInstance(getActivity().getApplicationContext());
+		netplay.registerRunGameListener(this);
+		adapter = new RoomPlayerlistAdapter();
+		adapter.setSource(netplay.roomPlayerlist);
+		setListAdapter(adapter);
+	}
+
+	@Override
+	public void onDestroy() {
+		super.onDestroy();
+		adapter.invalidate();
+		netplay.unregisterRunGameListener(this);
+	}
+	
+	@Override
+	public void onActivityCreated(Bundle savedInstanceState) {
+		super.onActivityCreated(savedInstanceState);
+		registerForContextMenu(getListView());
+		getListView().setOnItemClickListener(this);
+	}
+
+	@Override
+	public void onCreateContextMenu(ContextMenu menu, View v,
+			ContextMenuInfo menuInfo) {
+		super.onCreateContextMenu(menu, v, menuInfo);
+		AdapterContextMenuInfo info = (AdapterContextMenuInfo)menuInfo;
+		String playerName = adapter.getItem(info.position).player.name;
+		
+		MenuInflater inflater = getActivity().getMenuInflater();
+		inflater.inflate(R.menu.room_playerlist_context, menu);
+		if(netplay.isChief() && !playerName.equals(netplay.getPlayerName())) {
+			inflater.inflate(R.menu.room_playerlist_chief_context, menu);
+		}
+		menu.setHeaderIcon(R.drawable.human);
+		menu.setHeaderTitle(playerName);
+	}
+	
+	@Override
+	public boolean onContextItemSelected(MenuItem item) {
+		AdapterContextMenuInfo info = (AdapterContextMenuInfo)item.getMenuInfo();
+		PlayerInRoom player = adapter.getItem(info.position);
+		switch(item.getItemId()) {
+		case R.id.player_info:
+			netplay.sendPlayerInfoQuery(player.player.name);
+			return true;
+		case R.id.player_kick:
+			netplay.sendKick(player.player.name);
+			return true;
+		default:
+			return super.onContextItemSelected(item);
+		}
+	}
+	
+	@Override
+	public View onCreateView(LayoutInflater inflater, ViewGroup container,
+			Bundle savedInstanceState) {
+		return inflater.inflate(R.layout.fragment_playerlist, container, false);
+	}
+	
+	public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+		Player player = adapter.getItem(position).player;
+		if(player.name.equals(netplay.getPlayerName())) {
+			netplay.sendToggleReady();
+		}
+	}
+	
+	// TODO this is really the wrong place for this...
+	public void runGame(GameConfig config) {
+		SDLActivity.startConfig = config;
+		SDLActivity.startNetgame = true;
+		startActivity(new Intent(getActivity().getApplicationContext(), SDLActivity.class));
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/RoomStateManager.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,70 @@
+package org.hedgewars.hedgeroid;
+
+import org.hedgewars.hedgeroid.Datastructures.MapRecipe;
+import org.hedgewars.hedgeroid.Datastructures.Scheme;
+import org.hedgewars.hedgeroid.Datastructures.Weaponset;
+
+/**
+ * This interface is supposed to abstract the handling of room state for several fragments
+ * that can display and manipulate it. The purpose of this is to allow using these fragments
+ * both for setting up networked and local games, despite the fact that for local games
+ * the settings can be changed immediately in memory, while they have to be sent out to the
+ * server for networked games.
+ * 
+ * If/when the state changes as result of calling one of the "changeX" functions, that will
+ * also trigger the corresponding change listener method. There is no guarantee that calling
+ * a changeX method will actually change the setting (e.g. if you're not room chief).
+ * 
+ * For local games, getChiefStatus is always true.
+ * 
+ * Implementations of this interface are probably not thread safe and should only be used on
+ * the UI thread.
+ */
+public interface RoomStateManager {
+	// Query current state
+	MapRecipe getMapRecipe();
+	boolean getChiefStatus();
+	Scheme getScheme();
+	String getGameStyle();
+	Weaponset getWeaponset();
+	
+	// Manipulate state
+	void changeMapRecipe(MapRecipe map);
+	void changeMapTheme(String theme);
+
+	/**
+	 * This function sets both the map's name and generator. There is no function
+	 * to change them independendly since e.g. the QtFrontend relies on them being
+	 * consistent.
+	 * 
+	 * If the name parameter is equal to one of the MapRecipe.MAPNAME_REGULAR, MAPNAME_MAZE
+	 * or MAPNAME_DRAWN constants, the map generator is set accordingly. Otherwise, the
+	 * map generator is set to represent a mapfile. The map's name is always set to
+	 * the parameter.
+	 */
+	void changeMapNameAndGenerator(String mapName);
+	void changeMapTemplate(int template);
+	void changeMazeSize(int mazeSize);
+	void changeMapSeed(String seed);
+	void changeMapDrawdata(byte[] drawdata);
+	
+	void changeScheme(Scheme scheme);
+	void changeGameStyle(String style);
+	void changeWeaponset(Weaponset weaponset);
+	
+	// Observe state
+	void registerObserver(Observer observer);
+	void unregisterObserver(Observer observer);
+	
+	public interface Observer {
+		void onMapChanged(MapRecipe recipe);
+		void onChiefStatusChanged(boolean isChief);
+		void onSchemeChanged(Scheme scheme);
+		void onGameStyleChanged(String gameStyle);
+		void onWeaponsetChanged(Weaponset weaponset);
+	}
+	
+	public interface Provider {
+		RoomStateManager getRoomStateManager();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/RoomlistAdapter.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,91 @@
+package org.hedgewars.hedgeroid;
+
+import java.util.Comparator;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.Datastructures.Room;
+import org.hedgewars.hedgeroid.Datastructures.RoomWithId;
+import org.hedgewars.hedgeroid.util.ObservableTreeMapAdapter;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+public class RoomlistAdapter extends ObservableTreeMapAdapter<String, RoomWithId> {
+	private Context context;
+	
+	public RoomlistAdapter(Context context) {
+		this.context = context;
+	}
+	
+	@Override
+	protected Comparator<RoomWithId> getEntryOrder() {
+		return RoomWithId.NEWEST_FIRST_ORDER;
+	}
+	
+	@Override
+	public long getItemId(int position) {
+		return getItem(position).id;
+	}
+	
+	@Override
+	public boolean hasStableIds() {
+		return true;
+	}
+	
+	private static CharSequence formatExtra(Resources res, Room room) {
+		String ownermsg = res.getString(R.string.roomlist_owner, room.owner);
+		String mapmsg = res.getString(R.string.roomlist_map, room.formatMapName(res));
+		String scheme = room.scheme.equals(room.weapons) ? room.scheme : room.scheme + " / " + room.weapons;
+		String schememsg = res.getString(R.string.roomlist_scheme, scheme);
+		return ownermsg + ". " + mapmsg + ", " + schememsg;
+	}
+	
+	public View getView(int position, View convertView, ViewGroup parent) {
+		View v = convertView;
+		if (v == null) {
+			LayoutInflater vi = LayoutInflater.from(context);
+			v = vi.inflate(R.layout.listview_room, null);
+		}
+		
+		Room room = getItem(position).room;
+		int iconRes = room.inProgress ? R.drawable.roomlist_ingame : R.drawable.roomlist_preparing;
+		
+		if(v.findViewById(android.R.id.text1) == null) {
+			// Tabular room list
+			TextView roomnameView = (TextView)v.findViewById(R.id.roomname);
+			TextView playerCountView = (TextView)v.findViewById(R.id.playercount);
+			TextView teamCountView = (TextView)v.findViewById(R.id.teamcount);
+			TextView ownerView = (TextView)v.findViewById(R.id.owner);
+			TextView mapView = (TextView)v.findViewById(R.id.map);
+			TextView schemeView = (TextView)v.findViewById(R.id.scheme);
+			TextView weaponView = (TextView)v.findViewById(R.id.weapons);
+			
+			roomnameView.setCompoundDrawablesWithIntrinsicBounds(iconRes, 0, 0, 0);
+			roomnameView.setText(room.name);
+			if(playerCountView != null) {
+				playerCountView.setText(String.valueOf(room.playerCount));
+			}
+			if(teamCountView != null) {
+				teamCountView.setText(String.valueOf(room.teamCount));
+			}
+			ownerView.setText(room.owner);
+			mapView.setText(room.formatMapName(context.getResources()));
+			schemeView.setText(room.scheme);
+			weaponView.setText(room.weapons);
+		} else {
+			// Small room list
+			TextView v1 = (TextView)v.findViewById(android.R.id.text1);
+			TextView v2 = (TextView)v.findViewById(android.R.id.text2);
+			
+			v1.setCompoundDrawablesWithIntrinsicBounds(iconRes, 0, 0, 0);
+			v1.setText(room.name);
+			v2.setText(formatExtra(context.getResources(), room));
+		}
+		
+		return v;
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/RoomlistFragment.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,72 @@
+package org.hedgewars.hedgeroid;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.netplay.Netplay;
+
+import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.support.v4.app.ListFragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+
+public class RoomlistFragment extends ListFragment implements OnItemClickListener {
+	private static final int AUTO_REFRESH_INTERVAL_MS = 15000;
+	
+	private Netplay netplay;
+	private RoomlistAdapter adapter;
+	private CountDownTimer autoRefreshTimer = new CountDownTimer(Long.MAX_VALUE, AUTO_REFRESH_INTERVAL_MS) {
+		@Override
+		public void onTick(long millisUntilFinished) {
+			netplay.sendRoomlistRequest();
+		}
+		
+		@Override
+		public void onFinish() { }
+	};
+
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		netplay = Netplay.getAppInstance(getActivity().getApplicationContext());
+		adapter = new RoomlistAdapter(getActivity());
+		adapter.setSource(netplay.roomList);
+		setListAdapter(adapter);
+	}
+
+	@Override
+	public View onCreateView(LayoutInflater inflater, ViewGroup container,
+			Bundle savedInstanceState) {
+		return inflater.inflate(R.layout.lobby_rooms_fragment, container, false);
+	}
+	
+	@Override
+	public void onActivityCreated(Bundle savedInstanceState) {
+		super.onActivityCreated(savedInstanceState);
+		getListView().setOnItemClickListener(this);
+	}
+	
+	@Override
+	public void onResume() {
+		super.onResume();
+		autoRefreshTimer.start();
+	}
+	
+	@Override
+	public void onPause() {
+		super.onPause();
+		autoRefreshTimer.cancel();
+	}
+	
+	@Override
+	public void onDestroy() {
+		super.onDestroy();
+		adapter.invalidate();
+	}
+	
+	public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+		netplay.sendJoinRoom(adapter.getItem(position).room.name);
+	}
+}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/SDLActivity.java	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/SDLActivity.java	Sat Aug 18 00:47:51 2012 +0200
@@ -1,5 +1,8 @@
 package org.hedgewars.hedgeroid;
 
+import java.io.UnsupportedEncodingException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
 import javax.microedition.khronos.egl.EGL10;
 import javax.microedition.khronos.egl.EGLConfig;
 import javax.microedition.khronos.egl.EGLContext;
@@ -12,7 +15,9 @@
 import org.hedgewars.hedgeroid.frontlib.Frontlib.GameSetupPtr;
 import org.hedgewars.hedgeroid.frontlib.Frontlib.GameconnPtr;
 import org.hedgewars.hedgeroid.frontlib.Frontlib.IntCallback;
-import org.hedgewars.hedgeroid.netplay.TickHandler;
+import org.hedgewars.hedgeroid.netplay.Netplay;
+import org.hedgewars.hedgeroid.util.FileUtils;
+import org.hedgewars.hedgeroid.util.TickHandler;
 
 import com.sun.jna.Pointer;
 
@@ -31,6 +36,7 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Message;
+import android.util.Base64;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.KeyEvent;
@@ -50,6 +56,7 @@
 	 * way to do this (http://developer.android.com/guide/faq/framework.html#3)
 	 */
 	public static volatile GameConfig startConfig;
+	public static volatile boolean startNetgame;
 	
 	// Main components
 	public static SDLActivity mSingleton;
@@ -87,7 +94,7 @@
 		mSingleton = this;
 
 		// Set up the surface
-		mSurface = new SDLSurface(getApplication(), startConfig);
+		mSurface = new SDLSurface(getApplication(), startConfig, startNetgame);
 		setContentView(mSurface);
 	}
 
@@ -150,8 +157,14 @@
 		commandHandler.sendMessage(msg);
 	}
 
+	public static void synchronizedNativeInit(String...args) {
+		synchronized(PascalExports.engineMutex) {
+			nativeInit(args);
+		}
+	}
+	
 	// C functions we call
-	public static native void nativeInit(String...args);
+	private static native void nativeInit(String...args);
 	public static native void nativeQuit();
 	public static native void nativePause();
 	public static native void nativeResume();
@@ -184,12 +197,29 @@
 		return mSingleton;
 	}
 
-	public static void startApp(int width, int height, GameConfig config) {
+	public static void startApp(final int width, final int height, GameConfig config, boolean netgame) {
 		synchronized(SDLActivity.class) {
-			// Start up the C app thread
+			// Start up the C app thread TODO this is silly code
 			if (mSDLThread == null) {
-				mSDLThread = new Thread(new SDLMain(width, height, config), "SDLThread");
-				mSDLThread.start();
+				final AtomicBoolean gameconnStartDone = new AtomicBoolean(false);
+				GameConnection.Listener listener = new GameConnection.Listener() {
+					public void gameConnectionReady(int port) {
+						mSDLThread = new Thread(new SDLMain(width, height, port, "Medo"));
+						mSDLThread.start();
+						gameconnStartDone.set(true);
+					}
+					
+					public void gameConnectionDisconnected(int reason) {
+						Log.e("startApp", "disconnected: "+reason);
+						gameconnStartDone.set(true);
+					}
+				};
+				if(netgame) {
+					Netplay netplay = Netplay.getAppInstance(mSingleton.getApplicationContext());
+					GameConnection.forNetgame(config, netplay, listener);
+				} else {
+					GameConnection.forLocalGame(config, listener);
+				}
 			} else {
 				SDLActivity.nativeResume();
 			}
@@ -426,61 +456,32 @@
 class SDLMain implements Runnable {
 
 	private final int surfaceWidth, surfaceHeight;
-	private final GameConfig config;
-	private GameconnPtr conn;
+	private final int port;
+	private final String playerName;
 	HandlerThread thread = new HandlerThread("IPC thread");
 	
-	private final IntCallback dccb = new IntCallback() {
-		public void callback(Pointer context, int arg1) {
-			Log.d("SDLMain", "Disconnected: "+arg1);
-			Flib.INSTANCE.flib_gameconn_destroy(conn);
-			conn = null;
-			thread.quit();
-		}
-	};
-	
-	public SDLMain(int width, int height, GameConfig _config) {
-		config = _config;
+	public SDLMain(int width, int height, int port, String playerName) {
 		surfaceWidth = width;
 		surfaceHeight = height;
+		this.port = port;
+		this.playerName = playerName;
 	}
 
 	public void run() {
 		//Set up the IPC socket server to communicate with the engine
-		
-		final GameconnPtr conn = Flib.INSTANCE.flib_gameconn_create("Xeli", GameSetupPtr.createJavaOwned(config), false);
-		if(conn == null) {
-			throw new AssertionError();
-		}
-		Flib.INSTANCE.flib_gameconn_onDisconnect(conn, dccb, null);
-		int port = Flib.INSTANCE.flib_gameconn_getport(conn);
-
-		String path = Utils.getDataPath(SDLActivity.mSingleton);//This represents the data directory
+		String path = FileUtils.getDataPath(SDLActivity.mSingleton);//This represents the data directory
 		path = path.substring(0, path.length()-1);//remove the trailing '/'
 
-		thread.start(); // TODO ensure it gets stopped
-		new TickHandler(thread.getLooper(), 500, new Runnable() {
-			public void run() {
-				Log.d("SDLMain", "pre-tick");
-				Flib.INSTANCE.flib_gameconn_tick(conn);
-				Log.d("SDLMain", "post-tick");
-			}
-		}).start();
-		
 		Log.d("SDLMain", "Starting engine");
 		// Runs SDL_main() with added parameters
-		SDLActivity.nativeInit(new String[] { String.valueOf(port),
-				String.valueOf(surfaceWidth), String.valueOf(surfaceHeight),
-				"0", "en.txt", "xeli", "1", "1", "1", path, ""  });
+		try {
+			SDLActivity.synchronizedNativeInit(new String[] { String.valueOf(port),
+					String.valueOf(surfaceWidth), String.valueOf(surfaceHeight),
+					"0", "en.txt", Base64.encodeToString(playerName.getBytes("UTF-8"), 0), "1", "1", "1", path, ""  });
+		} catch (UnsupportedEncodingException e) {
+			throw new AssertionError(e); // never happens
+		}
 		Log.d("SDLMain", "Engine stopped");
-		try {
-			thread.join();
-		} catch (InterruptedException e) {
-			throw new AssertionError();
-		}
-		Log.v("SDLMain", "thread joined");
-		Flib.INSTANCE.flib_gameconn_destroy(conn);
-		Log.v("SDLMain", "SDL thread terminated");
 	}
 }
 
@@ -495,12 +496,13 @@
 View.OnKeyListener, View.OnTouchListener, SensorEventListener  {
 
 	private GameConfig config;
-
+	private boolean netgame;
+	
 	// Sensors
 	private static SensorManager mSensorManager;
 
 	// Startup    
-	public SDLSurface(Context context, GameConfig _config) {
+	public SDLSurface(Context context, GameConfig _config, boolean netgame) {
 		super(context);
 		getHolder().addCallback(this); 
 
@@ -512,6 +514,7 @@
 
 		mSensorManager = (SensorManager)context.getSystemService("sensor");
 		config = _config;
+		this.netgame = netgame;
 	}
 
 	// Called when we have a valid drawing surface
@@ -581,7 +584,7 @@
 		SDLActivity.onNativeResize(width, height, sdlFormat);
 		Log.v("SDL", "Window size:" + width + "x"+height);
 
-		SDLActivity.startApp(width, height, config);
+		SDLActivity.startApp(width, height, config, netgame);
 	}
 
 	// unused
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/SchemeCreatorActivity.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,7 @@
+package org.hedgewars.hedgeroid;
+
+import android.app.Activity;
+
+public class SchemeCreatorActivity extends Activity {
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/SchemeListActivity.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,8 @@
+package org.hedgewars.hedgeroid;
+
+import android.app.Activity;
+
+// TODO
+public class SchemeListActivity extends Activity {
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/SettingsFragment.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,201 @@
+package org.hedgewars.hedgeroid;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.Datastructures.FrontendDataUtils;
+import org.hedgewars.hedgeroid.Datastructures.MapRecipe;
+import org.hedgewars.hedgeroid.Datastructures.Scheme;
+import org.hedgewars.hedgeroid.Datastructures.Schemes;
+import org.hedgewars.hedgeroid.Datastructures.Weaponset;
+import org.hedgewars.hedgeroid.Datastructures.Weaponsets;
+import org.hedgewars.hedgeroid.util.FileUtils;
+
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.Spinner;
+import android.widget.Toast;
+
+public class SettingsFragment extends Fragment implements RoomStateManager.Observer {
+	private Spinner styleSpinner, schemeSpinner, weaponsetSpinner, themeSpinner;
+	private ImageView themeIcon;
+	
+	private List<String> styles;
+	private List<Scheme> schemes;
+	private List<Weaponset> weaponsets;
+	private List<String> themes;
+	
+	private RoomStateManager stateManager;
+
+	@Override
+	public View onCreateView(LayoutInflater inflater, ViewGroup container,
+			Bundle savedInstanceState) {
+		View v = inflater.inflate(R.layout.fragment_settings, container, false);
+		themeIcon = (ImageView)v.findViewById(R.id.imgTheme);
+		
+		try {
+			styles = FrontendDataUtils.getGameStyles(getActivity());
+			schemes = Schemes.loadAllSchemes(getActivity());
+			weaponsets = Weaponsets.loadAllWeaponsets(getActivity());
+			themes = FrontendDataUtils.getThemes(getActivity());
+		} catch (IOException e) {
+			Toast.makeText(getActivity().getApplicationContext(), R.string.error_missing_sdcard_or_files, Toast.LENGTH_LONG).show();
+			getActivity().finish();
+		}
+		
+		Collections.sort(styles, String.CASE_INSENSITIVE_ORDER);
+		Collections.sort(schemes, Scheme.NAME_ORDER);
+		Collections.sort(weaponsets, Weaponset.NAME_ORDER);
+		Collections.sort(themes, String.CASE_INSENSITIVE_ORDER);
+		
+		styleSpinner = prepareSpinner(v, R.id.spinGameplay, styles, styleSelectedListener);
+		schemeSpinner = prepareSpinner(v, R.id.spinGamescheme, Schemes.toNameList(schemes), schemeSelectedListener);
+		weaponsetSpinner = prepareSpinner(v, R.id.spinweapons, Weaponsets.toNameList(weaponsets), weaponsetSelectedListener);
+		themeSpinner = prepareSpinner(v, R.id.spinTheme, themes, themeSelectedListener);
+		
+		stateManager.registerObserver(this);
+
+		if(stateManager.getGameStyle() != null) {
+			styleSpinner.setSelection(styles.indexOf(stateManager.getGameStyle()), false);
+		}
+		if(stateManager.getScheme() != null) {
+			schemeSpinner.setSelection(getSchemePosition(schemes, stateManager.getScheme().name), false);
+		}
+		if(stateManager.getWeaponset() != null) {
+			weaponsetSpinner.setSelection(getWeaponsetPosition(weaponsets, stateManager.getWeaponset().name), false);
+		}
+		if(stateManager.getMapRecipe() != null) {
+			themeSpinner.setSelection(themes.indexOf(stateManager.getMapRecipe().theme), false);
+		}
+		
+		setChiefState(stateManager.getChiefStatus());
+		
+		return v;
+	}
+	
+	private static Spinner prepareSpinner(View v, int id, List<String> items, OnItemSelectedListener itemSelectedListener) {
+		Spinner spinner = (Spinner)v.findViewById(id);
+		ArrayAdapter<String> adapter = new ArrayAdapter<String>(v.getContext(), R.layout.listview_item, items);
+		adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+		spinner.setAdapter(adapter);
+		spinner.setOnItemSelectedListener(itemSelectedListener);
+		return spinner;
+	}
+	
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		try {
+			stateManager = ((RoomStateManager.Provider)getActivity()).getRoomStateManager();
+		} catch(ClassCastException e) {
+			throw new RuntimeException("Hosting activity must implement RoomStateManager.Provider.", e);
+		}
+	}
+	
+	@Override
+	public void onDestroy() {
+		super.onDestroy();
+		stateManager.unregisterObserver(this);
+	}
+	
+	private static int getSchemePosition(List<Scheme> schemes, String scheme) {
+		for(int i=0; i<schemes.size(); i++) {
+			if(schemes.get(i).name.equals(scheme)) {
+				return i;
+			}
+		}
+		return -1;
+	}
+	
+	private static int getWeaponsetPosition(List<Weaponset> weaponsets, String weaponset) {
+		for(int i=0; i<weaponsets.size(); i++) {
+			if(weaponsets.get(i).name.equals(weaponset)) {
+				return i;
+			}
+		}
+		return -1;
+	}
+	
+	private void setChiefState(boolean chiefState) {
+		styleSpinner.setEnabled(chiefState);
+		schemeSpinner.setEnabled(chiefState);
+		weaponsetSpinner.setEnabled(chiefState);
+		themeSpinner.setEnabled(chiefState);
+		
+		if(chiefState) {
+			stateManager.changeGameStyle(styles.get(styleSpinner.getSelectedItemPosition()));
+			stateManager.changeScheme(schemes.get(schemeSpinner.getSelectedItemPosition()));
+			stateManager.changeWeaponset(weaponsets.get(weaponsetSpinner.getSelectedItemPosition()));
+			stateManager.changeMapTheme(themes.get(themeSpinner.getSelectedItemPosition()));
+		}
+	}
+	
+	private final OnItemSelectedListener styleSelectedListener = new OnItemSelectedListener() {
+		public void onItemSelected(AdapterView<?> adapter, View v, int position, long arg3) {
+			stateManager.changeGameStyle(styles.get(position));
+		}
+		public void onNothingSelected(AdapterView<?> arg0) {}
+	};
+	
+	private final OnItemSelectedListener schemeSelectedListener = new OnItemSelectedListener() {
+		public void onItemSelected(AdapterView<?> adapter, View v, int position, long arg3) {
+			stateManager.changeScheme(schemes.get(position));
+		}
+		public void onNothingSelected(AdapterView<?> arg0) {}
+	};
+	
+	private final OnItemSelectedListener weaponsetSelectedListener = new OnItemSelectedListener() {
+		public void onItemSelected(AdapterView<?> adapter, View v, int position, long arg3) {
+			stateManager.changeWeaponset(weaponsets.get(position));
+		}
+		public void onNothingSelected(AdapterView<?> arg0) {}
+	};
+	
+	private final OnItemSelectedListener themeSelectedListener = new OnItemSelectedListener() {
+		public void onItemSelected(AdapterView<?> adapter, View v, int position, long arg3) {
+			stateManager.changeMapTheme(themes.get(position));
+			String theme = themes.get(position);
+			try {
+				File iconFile = new File(FileUtils.getDataPathFile(getActivity()), "Themes/" + theme + "/icon@2X.png");
+				Drawable themeIconDrawable = Drawable.createFromPath(iconFile.getAbsolutePath());
+				themeIcon.setImageDrawable(themeIconDrawable);
+			} catch (FileNotFoundException e) {
+				Log.e("SettingsFragment", "Unable to find preview for theme "+theme, e);
+			}
+		};
+		public void onNothingSelected(AdapterView<?> arg0) {};
+	};
+	
+	public void onChiefStatusChanged(boolean isChief) {
+		setChiefState(isChief);
+	}
+	
+	public void onGameStyleChanged(String gameStyle) {
+		styleSpinner.setSelection(styles.indexOf(gameStyle));
+	}
+	
+	public void onMapChanged(MapRecipe recipe) {
+		themeSpinner.setSelection(themes.indexOf(recipe.theme));
+	}
+	
+	public void onSchemeChanged(Scheme scheme) {
+		schemeSpinner.setSelection(getSchemePosition(schemes, scheme.name));
+	}
+	
+	public void onWeaponsetChanged(Weaponset weaponset) {
+		weaponsetSpinner.setSelection(getWeaponsetPosition(weaponsets, weaponset.name));
+	}
+}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/StartGameActivity.java	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/StartGameActivity.java	Sat Aug 18 00:47:51 2012 +0200
@@ -36,12 +36,12 @@
 import org.hedgewars.hedgeroid.Datastructures.TeamInGame;
 import org.hedgewars.hedgeroid.Datastructures.Weaponset;
 import org.hedgewars.hedgeroid.Datastructures.Weaponsets;
+import org.hedgewars.hedgeroid.util.FileUtils;
 
 import android.app.Activity;
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
-import android.util.Log;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.AdapterView;
@@ -56,8 +56,14 @@
 	public static final int ACTIVITY_TEAM_SELECTOR = 0;
 	
 	private ImageButton start, back, team;
-	private Spinner maps, gameplay, gamescheme, weapons, themes;
+	private Spinner mapSpinner, styleSpinner, schemeSpinner, weaponsetSpinner, themeSpinner;
 	private ImageView themeIcon, mapPreview, teamCount;
+
+	private List<MapFile> mapFiles;
+	private List<String> styles;
+	private List<Scheme> schemes;
+	private List<Weaponset> weaponsets;
+	private List<String> themes;
 	
 	private List<TeamInGame> teams = new ArrayList<TeamInGame>();
 
@@ -70,12 +76,6 @@
 		team = (ImageButton) findViewById(R.id.btnTeams);
 		start = (ImageButton) findViewById(R.id.btnStart);
 
-		maps = (Spinner) findViewById(R.id.spinMaps);
-		gameplay = (Spinner) findViewById(R.id.spinGameplay);
-		gamescheme = (Spinner) findViewById(R.id.spinGamescheme);
-		weapons = (Spinner) findViewById(R.id.spinweapons);
-		themes = (Spinner) findViewById(R.id.spinTheme);
-
 		themeIcon = (ImageView) findViewById(R.id.imgTheme);
 		mapPreview = (ImageView) findViewById(R.id.mapPreview);
 		teamCount = (ImageView) findViewById(R.id.imgTeamsCount);
@@ -84,90 +84,55 @@
 		back.setOnClickListener(backClicker);
 		team.setOnClickListener(teamClicker);
 
-		List<MapFile> mapFiles;
 		try {
 			mapFiles = FrontendDataUtils.getMaps(this);
-		} catch (FileNotFoundException e) {
-			Log.e("StartGameActivity", e.getMessage(), e);
-			mapFiles = Collections.emptyList();
-		}
-		ArrayAdapter<?> adapter = new ArrayAdapter<MapFile>(this, R.layout.listview_item, mapFiles);
-		adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-		maps.setAdapter(adapter);
-		maps.setOnItemSelectedListener(mapsClicker);
-		//set to first nonmission
-		for(int i = 0; i < adapter.getCount(); i++){
-			if(!((MapFile)adapter.getItem(i)).isMission){
-				maps.setSelection(i, false);
-				break;
-			}
+			styles = FrontendDataUtils.getGameStyles(this);
+			schemes = Schemes.loadAllSchemes(this);
+			weaponsets = Weaponsets.loadAllWeaponsets(this);
+			themes = FrontendDataUtils.getThemes(this);
+		} catch (IOException e) {
+			Toast.makeText(getApplicationContext(), R.string.error_missing_sdcard_or_files, Toast.LENGTH_LONG).show();
+			finish();
 		}
-
-		List<String> gameStyles;
-		try {
-			gameStyles = FrontendDataUtils.getGameStyles(this);
-		} catch (FileNotFoundException e) {
-			Log.e("StartGameActivity", e.getMessage(), e);
-			gameStyles = Collections.emptyList();
-		}
-		adapter = new ArrayAdapter<String>(this, R.layout.listview_item, gameStyles);
-		adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-		gameplay.setAdapter(adapter);
-		//set to first nonmap
-		for(int i = 0; i < adapter.getCount(); i++){
-			if(((String)adapter.getItem(i)).equals("None")){
-				gameplay.setSelection(i, false);
+		
+		Collections.sort(mapFiles, MapFile.MISSIONS_FIRST_NAME_ORDER);
+		Collections.sort(styles, String.CASE_INSENSITIVE_ORDER);
+		Collections.sort(schemes, Scheme.NAME_ORDER);
+		Collections.sort(weaponsets, Weaponset.NAME_ORDER);
+		Collections.sort(themes, String.CASE_INSENSITIVE_ORDER);
+		
+		List<String> mapNames = MapFile.toDisplayNameList(mapFiles, getResources());
+		List<String> schemeNames = Schemes.toNameList(schemes);
+		List<String> weaponsetNames = Weaponsets.toNameList(weaponsets);
+		View rootView = findViewById(android.R.id.content);
+		mapSpinner = prepareSpinner(rootView, R.id.spinMaps, mapNames, mapsClicker);
+		styleSpinner = prepareSpinner(rootView, R.id.spinGameplay, styles, null);
+		schemeSpinner = prepareSpinner(rootView, R.id.spinGamescheme, schemeNames, null);
+		weaponsetSpinner = prepareSpinner(rootView, R.id.spinweapons, weaponsetNames, null);
+		themeSpinner = prepareSpinner(rootView, R.id.spinTheme, themes, themesClicker);
+		
+		// set map to first nonmission
+		for(int i = 0; i < mapFiles.size(); i++){
+			if(!mapFiles.get(i).isMission){
+				mapSpinner.setSelection(i, false);
 				break;
 			}
 		}
-
-		List<Scheme> schemes;
-		try {
-			schemes = new ArrayList<Scheme>(Schemes.loadAllSchemes(this).values());
-		} catch (IOException e) {
-			Log.e("StartGameActivity", e.getMessage(), e);
-			schemes = Collections.emptyList();
-		} 
-		adapter = new ArrayAdapter<Scheme>(this, R.layout.listview_item, schemes);
-		adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-		gamescheme.setAdapter(adapter);
-		for(int i = 0; i < adapter.getCount(); i++){
-			if(((Scheme)adapter.getItem(i)).name.equals("Default")){
-				gamescheme.setSelection(i, false);
-				break;
-			}
-		}
-		
-		List<Weaponset> weaponsets;
-		try {
-			weaponsets = Weaponsets.loadAllWeaponsets(this);
-		} catch(IOException e) {
-			Log.e("StartGameActivity", e.getMessage(), e);
-			weaponsets = Collections.emptyList();
-		}
-		adapter = new ArrayAdapter<Weaponset>(this, R.layout.listview_item, weaponsets);
-		adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-		weapons.setAdapter(adapter);
-		for(int i = 0; i < adapter.getCount(); i++){
-			if(((Weaponset)adapter.getItem(i)).name.equals("Crazy")){
-				weapons.setSelection(i, false);
-				break;
-			}
-		}
-		
-		List<String> themeList;
-		try {
-			themeList = FrontendDataUtils.getThemes(this);
-		} catch(FileNotFoundException e) {
-			Log.e("StartGameActivity", e.getMessage(), e);
-			themeList = Collections.emptyList();
-		}
-		adapter = new ArrayAdapter<String>(this, R.layout.listview_item, themeList);
-		adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-		themes.setAdapter(adapter);
-		themes.setOnItemSelectedListener(themesClicker);
+		styleSpinner.setSelection(styles.indexOf(GameConfig.DEFAULT_STYLE), false);
+		schemeSpinner.setSelection(schemeNames.indexOf(GameConfig.DEFAULT_SCHEME), false);
+		weaponsetSpinner.setSelection(weaponsetNames.indexOf(GameConfig.DEFAULT_WEAPONSET), false);
+		themeSpinner.setSelection(themes.indexOf(GameConfig.DEFAULT_THEME), false);
 	}
 
+	private static Spinner prepareSpinner(View v, int id, List<String> items, OnItemSelectedListener itemSelectedListener) {
+		Spinner spinner = (Spinner)v.findViewById(id);
+		ArrayAdapter<String> adapter = new ArrayAdapter<String>(v.getContext(), R.layout.listview_item, items);
+		adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+		spinner.setAdapter(adapter);
+		spinner.setOnItemSelectedListener(itemSelectedListener);
+		return spinner;
+	}
+	
 	private void startTeamsActivity(){
 		Intent i = new Intent(StartGameActivity.this, TeamSelectionActivity.class);
 		TeamSelectionActivity.activityParams = new ArrayList<TeamInGame>(teams);
@@ -190,8 +155,8 @@
 	private OnItemSelectedListener themesClicker = new OnItemSelectedListener(){
 
 		public void onItemSelected(AdapterView<?> arg0, View view, int position, long rowId) {
-			String themeName = (String) arg0.getAdapter().getItem(position);
-			Drawable themeIconDrawable = Drawable.createFromPath(Utils.getDataPath(StartGameActivity.this) + "Themes/" + themeName + "/icon@2X.png");
+			String themeName = themes.get(position);
+			Drawable themeIconDrawable = Drawable.createFromPath(FileUtils.getDataPath(StartGameActivity.this) + "Themes/" + themeName + "/icon@2X.png");
 			themeIcon.setImageDrawable(themeIconDrawable);
 		}
 
@@ -203,7 +168,7 @@
 	private OnItemSelectedListener mapsClicker = new OnItemSelectedListener(){
 
 		public void onItemSelected(AdapterView<?> arg0, View view, int position,long rowId) {
-			MapFile map = (MapFile)arg0.getAdapter().getItem(position);
+			MapFile map = mapFiles.get(position);
 			try {
 				File previewFile = map.getPreviewFile(getApplicationContext());
 				mapPreview.setImageDrawable(Drawable.createFromPath(previewFile.getAbsolutePath()));
@@ -223,13 +188,14 @@
 				Toast.makeText(getApplicationContext(), R.string.not_enough_teams, Toast.LENGTH_LONG).show();
 				startTeamsActivity();
 			} else {
-				String style = (String)gameplay.getSelectedItem();
-				Scheme scheme = (Scheme)gamescheme.getSelectedItem();
-				String mapName = ((MapFile)maps.getSelectedItem()).name;
-				String theme = (String)themes.getSelectedItem();
+				String style = styles.get(styleSpinner.getSelectedItemPosition());
+				Scheme scheme = schemes.get(schemeSpinner.getSelectedItemPosition());
+				String mapName = mapFiles.get(mapSpinner.getSelectedItemPosition()).name;
+				String theme = themes.get(themeSpinner.getSelectedItemPosition());
 				MapRecipe map = MapRecipe.makeMap(mapName, UUID.randomUUID().toString(), theme);
-				Weaponset weaponset = (Weaponset)weapons.getSelectedItem();
+				Weaponset weaponset = weaponsets.get(weaponsetSpinner.getSelectedItemPosition());
 				SDLActivity.startConfig = new GameConfig(style, scheme, map, teams, weaponset);
+				SDLActivity.startNetgame = false;
 				Intent i = new Intent(StartGameActivity.this, SDLActivity.class);
 				startActivity(i);
 			}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/TeamAddDialog.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,92 @@
+package org.hedgewars.hedgeroid;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.Datastructures.FrontendDataUtils;
+import org.hedgewars.hedgeroid.Datastructures.Team;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
+
+public class TeamAddDialog extends DialogFragment {
+	private static final String STATE_TEAMS_ALREADY_IN_GAME = "teamAlreadyInGame";
+	private ArrayList<String> teamsAlreadyInGame;
+	private List<Team> availableTeams;
+	private Listener listener;
+	
+	public static interface Listener {
+		void onTeamAddDialogSubmitted(Team newTeam);
+	}
+	
+	public TeamAddDialog() {
+		// Only for reflection-based instantiation by the framework
+	}
+	
+	TeamAddDialog(Collection<String> teamsAlreadyInGame) {
+		this.teamsAlreadyInGame = new ArrayList<String>(teamsAlreadyInGame);
+	}
+	
+	@Override
+	public void onAttach(Activity activity) {
+		super.onAttach(activity);
+		try {
+			listener = (Listener) activity;
+		} catch(ClassCastException e) {
+			throw new ClassCastException("Activity " + activity + " must implement TeamAddDialog.Listener to use TeamAddDialog.");
+		}
+	}
+	
+	@Override
+	public void onDetach() {
+		super.onDetach();
+		listener = null;
+	}
+	
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		if(savedInstanceState != null) {
+			teamsAlreadyInGame = savedInstanceState.getStringArrayList(STATE_TEAMS_ALREADY_IN_GAME);
+		}
+		availableTeams = new ArrayList<Team>();
+		List<Team> teams = FrontendDataUtils.getTeams(getActivity());
+		for(Team team : teams) {
+			if(!teamsAlreadyInGame.contains(team.name)) {
+				availableTeams.add(team);
+			}
+		}
+		Collections.sort(availableTeams, Team.NAME_ORDER);
+	}
+	
+	@Override
+	public Dialog onCreateDialog(Bundle savedInstanceState) {
+		AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+		builder.setTitle(R.string.dialog_addteam_title);
+		builder.setIcon(R.drawable.human);
+		String[] teamNames = new String[availableTeams.size()];
+		for(int i=0; i<availableTeams.size(); i++) {
+			teamNames[i] = availableTeams.get(i).name;
+		}
+		builder.setItems(teamNames, new OnClickListener() {
+			public void onClick(DialogInterface dialog, int which) {
+				listener.onTeamAddDialogSubmitted(availableTeams.get(which));
+			}
+		});
+		return builder.create();
+	}
+	
+	@Override
+	public void onSaveInstanceState(Bundle outState) {
+		super.onSaveInstanceState(outState);
+		outState.putStringArrayList(STATE_TEAMS_ALREADY_IN_GAME, teamsAlreadyInGame);
+	}
+}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/TeamCreatorActivity.java	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/TeamCreatorActivity.java	Sat Aug 18 00:47:51 2012 +0200
@@ -31,6 +31,7 @@
 import org.hedgewars.hedgeroid.Datastructures.FrontendDataUtils;
 import org.hedgewars.hedgeroid.Datastructures.Hog;
 import org.hedgewars.hedgeroid.Datastructures.Team;
+import org.hedgewars.hedgeroid.util.FileUtils;
 
 import android.app.Activity;
 import android.graphics.Bitmap;
@@ -253,41 +254,13 @@
 		super.onBackPressed();
 	}
 
-	private int getDifficultyLevelFromText(String text) {
-		if (text.equals(getString(R.string.human))) {
-			return 0;
-		} else if (text.equals(getString(R.string.bot5))) {
-			return 1;
-		} else if (text.equals(getString(R.string.bot4))) {
-			return 2;
-		} else if (text.equals(getString(R.string.bot3))) {
-			return 3;
-		} else if (text.equals(getString(R.string.bot2))) {
-			return 4;
-		} else {
-			return 5;
-		}
-	}
-	
-	private String getTxtFromDifficulty(int level) {
-		switch(level) {
-		case 0: return getString(R.string.human);
-		case 1: return getString(R.string.bot5);
-		case 2: return getString(R.string.bot4);
-		case 3: return getString(R.string.bot3);
-		case 4: return getString(R.string.bot2);
-		default: return getString(R.string.bot1);
-		}
-	}
-	
 	private void saveTeam() {
 		String teamName = name.getText().toString();
 		String teamFlag = (String)((Map<String, Object>) flag.getSelectedItem()).get("txt");
 		String teamFort = fort.getSelectedItem().toString();
 		String teamGrave = (String)((Map<String, Object>) grave.getSelectedItem()).get("txt");
 		String teamVoice = voice.getSelectedItem().toString();
-		String levelString = (String)((Map<String, Object>) difficulty.getSelectedItem()).get("txt");
-		int levelInt = getDifficultyLevelFromText(levelString);
+		int levelInt = (Integer)((Map<String, Object>) difficulty.getSelectedItem()).get("level");
 		
 		List<Hog> hogs = new ArrayList<Hog>();
 		for (int i = 0; i < hogName.size(); i++) {
@@ -307,7 +280,6 @@
 		}
 		try {
 			team.save(newFile);
-			Toast.makeText(TeamCreatorActivity.this, R.string.saved, Toast.LENGTH_SHORT).show();
 			// If the team was renamed, delete the old file.
 			if(oldFile != null && oldFile.isFile() && !oldFile.equals(newFile)) {
 				oldFile.delete();
@@ -322,7 +294,7 @@
 		public void onItemSelected(AdapterView<?> arg0, View arg1,
 				int position, long arg3) {
 			String fortName = (String) arg0.getAdapter().getItem(position);
-			Drawable fortIconDrawable = Drawable.createFromPath(Utils
+			Drawable fortIconDrawable = Drawable.createFromPath(FileUtils
 					.getDataPath(TeamCreatorActivity.this)
 					+ "Forts/"
 					+ fortName + "L.png");
@@ -343,7 +315,7 @@
 		public void onClick(View v) {
 			try {
 				File dir = new File(String.format("%sSounds/voices/%s",
-						Utils.getDataPath(TeamCreatorActivity.this),
+						FileUtils.getDataPath(TeamCreatorActivity.this),
 						voice.getSelectedItem()));
 				String file = "";
 				File[] dirs = dir.listFiles();
@@ -378,12 +350,12 @@
 			name.setText(t.name);
 			voice.setSelection(findPosition((ArrayAdapter<String>) voice.getAdapter(), t.voice));
 			fort.setSelection(findPosition((ArrayAdapter<String>) fort.getAdapter(), t.fort));
-			difficulty.setSelection(findPosition(typesData, getTxtFromDifficulty(t.hogs.get(0).level))); // TODO store actual difficulty int in typesData
-			grave.setSelection(findPosition(gravesData, t.grave));
-			flag.setSelection(findPosition(flagsData, t.flag));
+			difficulty.setSelection(findPosition(typesData, "level", Integer.valueOf(t.hogs.get(0).level)));
+			grave.setSelection(findPosition(gravesData, "txt", t.grave));
+			flag.setSelection(findPosition(flagsData, "txt", t.flag));
 	
 			for (int i = 0; i < Team.HEDGEHOGS_PER_TEAM; i++) {
-				hogHat.get(i).setSelection(findPosition(hatsData, t.hogs.get(i).hat));
+				hogHat.get(i).setSelection(findPosition(hatsData, "txt", t.hogs.get(i).hat));
 				hogName.get(i).setText(t.hogs.get(i).name);
 			}
 		} catch(NoSuchElementException e) {
@@ -392,18 +364,18 @@
 		}
 	}
 
-	int findPosition(ArrayAdapter<String> adapter, String key) throws NoSuchElementException {
-		int position = adapter.getPosition(key);
+	int findPosition(ArrayAdapter<String> adapter, String value) throws NoSuchElementException {
+		int position = adapter.getPosition(value);
 		if(position<0) {
 			throw new NoSuchElementException();
 		}
 		return position;
 	}
 	
-	int findPosition(List<? extends Map<String, ?>> data, String txtValue) throws NoSuchElementException {
+	int findPosition(List<? extends Map<String, ?>> data, String key, Object value) throws NoSuchElementException {
 		int position = 0;
 		for (Map<String, ?> map : data) {
-			if (map.get("txt").equals(txtValue)) {
+			if (map.get(key).equals(value)) {
 				return position;
 			}
 			position++;
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/TeamSelectionActivity.java	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/TeamSelectionActivity.java	Sat Aug 18 00:47:51 2012 +0200
@@ -125,7 +125,7 @@
 				setTeamColor(view, (Integer)data);
 				return true;
 			case R.id.teamCount:
-				setTeamHogCount((ImageView)view, (Integer)data);
+				((ImageView)view).getDrawable().setLevel((Integer)data);
 				return true;
 			default:
 				return false;
@@ -179,42 +179,6 @@
 		iv.setBackgroundColor(0xFF000000 + TeamIngameAttributes.TEAM_COLORS[colorIndex]);
 	}
 
-	private void setTeamHogCount(ImageView iv, int count){
-
-		switch(count){
-		case 0:
-			iv.setImageResource(R.drawable.teamcount0);
-			break;
-		case 1:
-			iv.setImageResource(R.drawable.teamcount1);
-			break;
-		case 2:
-			iv.setImageResource(R.drawable.teamcount2);
-			break;
-		case 3:
-			iv.setImageResource(R.drawable.teamcount3);
-			break;
-		case 4:
-			iv.setImageResource(R.drawable.teamcount4);
-			break;
-		case 5:
-			iv.setImageResource(R.drawable.teamcount5);
-			break;
-		case 6:
-			iv.setImageResource(R.drawable.teamcount6);
-			break;
-		case 7:
-			iv.setImageResource(R.drawable.teamcount7);
-			break;
-		case 8:
-			iv.setImageResource(R.drawable.teamcount8);
-			break;
-		case 9:
-			iv.setImageResource(R.drawable.teamcount9);
-			break;
-		}
-	}
-
 	public void onBackPressed(){
 		returnTeams();
 		super.onBackPressed();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/TeamlistAdapter.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,112 @@
+package org.hedgewars.hedgeroid;
+
+import java.util.Comparator;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.Datastructures.TeamInGame;
+import org.hedgewars.hedgeroid.Datastructures.TeamIngameAttributes;
+import org.hedgewars.hedgeroid.util.ObservableTreeMapAdapter;
+
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+import android.widget.TextView;
+
+public class TeamlistAdapter extends ObservableTreeMapAdapter<String, TeamInGame> {
+	private boolean colorHogcountEnabled = false;
+	private Listener listener;
+	
+	@Override
+	protected Comparator<TeamInGame> getEntryOrder() {
+		return TeamInGame.NAME_ORDER;
+	}
+
+	public void setColorHogcountEnabled(boolean colorHogcountEnabled) {
+		this.colorHogcountEnabled = colorHogcountEnabled;
+		notifyDataSetChanged();
+	}
+	
+	public void setListener(Listener listener) {
+		this.listener = listener;
+	}
+	
+	public View getView(int position, View convertView, ViewGroup parent) {
+		View v = convertView;
+		if (v == null) {
+			LayoutInflater vi = LayoutInflater.from(parent.getContext());
+			v = vi.inflate(R.layout.listview_team, null);
+		}
+
+		TeamInGame team = getItem(position);
+		TextView teamNameView = (TextView) v.findViewById(android.R.id.text1);
+		ImageButton colorButton = (ImageButton) v.findViewById(R.id.colorButton);
+		ImageButton hogCountButton = (ImageButton) v.findViewById(R.id.hogCountButton);
+		
+		teamNameView.setText(team.team.name);
+		int teamImage;
+		if(team.ingameAttribs.remoteDriven) {
+			teamImage = R.drawable.team_net_by_level;
+		} else {
+			teamImage = R.drawable.team_local_by_level;
+		}
+		
+		Drawable d = parent.getContext().getResources().getDrawable(teamImage).mutate();
+		d.setLevel(team.team.hogs.get(0).level);
+		teamNameView.setCompoundDrawablesWithIntrinsicBounds(d, null, null, null);
+		hogCountButton.getDrawable().setLevel(team.ingameAttribs.hogCount);
+		colorButton.setImageDrawable(new ColorDrawable(TeamIngameAttributes.TEAM_COLORS[team.ingameAttribs.colorIndex]));
+		
+		colorButton.setEnabled(colorHogcountEnabled);
+		hogCountButton.setEnabled(colorHogcountEnabled);
+		
+		colorButton.setOnClickListener(new ButtonClickListener(team, Type.COLOR_BUTTON));
+		hogCountButton.setOnClickListener(new ButtonClickListener(team, Type.HOGCOUNT_BUTTON));
+		
+		if(team.ingameAttribs.remoteDriven) {
+			teamNameView.setClickable(false);
+		} else {
+			teamNameView.setOnClickListener(new ButtonClickListener(team, Type.TEAM_VIEW));
+		}
+		
+		return v;
+	}
+	
+	private static enum Type {COLOR_BUTTON, HOGCOUNT_BUTTON, TEAM_VIEW}
+	private final class ButtonClickListener implements OnClickListener {
+		private final TeamInGame team;
+		private final Type type;
+		
+		public ButtonClickListener(TeamInGame team, Type type) {
+			this.team = team;
+			this.type = type;
+		}
+		
+		public void onClick(View v) {
+			if(listener != null) {
+				switch(type) {
+				case COLOR_BUTTON:
+					listener.onColorClicked(team);
+					break;
+				case HOGCOUNT_BUTTON:
+					listener.onHogcountClicked(team);
+					break;
+				case TEAM_VIEW:
+					listener.onTeamClicked(team);
+					break;
+				default:
+					throw new IllegalStateException();	
+				}
+			}
+		}
+	}
+	
+	public interface Listener {
+		void onTeamClicked(TeamInGame team);
+		void onColorClicked(TeamInGame team);
+		void onHogcountClicked(TeamInGame team);
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/TeamlistFragment.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,122 @@
+package org.hedgewars.hedgeroid;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.Datastructures.MapRecipe;
+import org.hedgewars.hedgeroid.Datastructures.Scheme;
+import org.hedgewars.hedgeroid.Datastructures.Team;
+import org.hedgewars.hedgeroid.Datastructures.TeamInGame;
+import org.hedgewars.hedgeroid.Datastructures.TeamIngameAttributes;
+import org.hedgewars.hedgeroid.Datastructures.Weaponset;
+import org.hedgewars.hedgeroid.netplay.Netplay;
+
+import android.database.DataSetObserver;
+import android.os.Bundle;
+import android.support.v4.app.ListFragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+/**
+ *  TODO use an interface for querying and manipulating the team list, to allow re-using this fragment
+ *  in local play
+ */
+public class TeamlistFragment extends ListFragment implements TeamlistAdapter.Listener, RoomStateManager.Observer {
+	private Netplay netplay;
+	private TeamlistAdapter adapter;
+	private Button addTeamButton;
+	private DataSetObserver teamlistObserver;
+	private RoomStateManager stateManager;
+	
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		try {
+			stateManager = ((RoomStateManager.Provider)getActivity()).getRoomStateManager();
+		} catch(ClassCastException e) {
+			throw new RuntimeException("Hosting activity must implement RoomStateManager.Provider.", e);
+		}
+		netplay = Netplay.getAppInstance(getActivity().getApplicationContext());
+		adapter = new TeamlistAdapter();
+		adapter.setSource(netplay.roomTeamlist);
+		adapter.setColorHogcountEnabled(stateManager.getChiefStatus());
+		adapter.setListener(this);
+		setListAdapter(adapter);
+		stateManager.registerObserver(this);
+	}
+
+	@Override
+	public View onCreateView(LayoutInflater inflater, ViewGroup container,
+			Bundle savedInstanceState) {
+		View v = inflater.inflate(R.layout.fragment_teamlist, container, false);
+		addTeamButton = (Button)v.findViewById(R.id.addTeamButton);
+		addTeamButton.setOnClickListener(new OnClickListener() {
+			public void onClick(View v) {
+				new TeamAddDialog(getCurrentTeamNames()).show(getFragmentManager(), "team_add_dialog");
+			}
+		});
+		
+		teamlistObserver = new DataSetObserver() {
+			@Override
+			public void onChanged() {
+				addTeamButton.setEnabled(netplay.roomTeamlist.getMap().size() < Team.maxNumberOfTeams);
+			}
+		};
+		netplay.roomTeamlist.registerObserver(teamlistObserver);
+		teamlistObserver.onChanged();
+		
+		return v;
+	}
+	
+	@Override
+	public void onDestroy() {
+		super.onDestroy();
+		adapter.invalidate();
+		adapter.setListener(null);
+		netplay.roomTeamlist.unregisterObserver(teamlistObserver);
+		stateManager.unregisterObserver(this);
+	}
+
+	@Override
+	public void onActivityCreated(Bundle savedInstanceState) {
+		super.onActivityCreated(savedInstanceState);
+	}
+	
+	private Collection<String> getCurrentTeamNames() {
+		List<String> names = new ArrayList<String>();
+		for(TeamInGame team : netplay.roomTeamlist.getMap().values()) {
+			names.add(team.team.name);
+		}
+		return names;
+	}
+	
+	public void onColorClicked(TeamInGame team) {
+		netplay.sendTeamColorIndex(team.team.name, (team.ingameAttribs.colorIndex+1)%TeamIngameAttributes.TEAM_COLORS.length);
+	}
+	
+	public void onHogcountClicked(TeamInGame team) {
+		int newHogCount = team.ingameAttribs.hogCount+1;
+		if(newHogCount>Team.HEDGEHOGS_PER_TEAM) {
+			newHogCount = 1;
+		}
+		netplay.sendTeamHogCount(team.team.name, newHogCount);
+	}
+	
+	public void onTeamClicked(TeamInGame team) {
+		netplay.sendRemoveTeam(team.team.name);
+	}
+	
+	public void onChiefStatusChanged(boolean isChief) {
+		adapter.setColorHogcountEnabled(isChief);
+	}
+	
+	public void onGameStyleChanged(String gameStyle) { }
+	public void onMapChanged(MapRecipe recipe) { }
+	public void onSchemeChanged(Scheme scheme) { }
+	public void onWeaponsetChanged(Weaponset weaponset) { }
+}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Utils.java	Sat Aug 18 00:22:33 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,273 +0,0 @@
-/*
- * Hedgewars for Android. An Android port of Hedgewars, a free turn based strategy game
- * Copyright (c) 2011-2012 Richard Deurwaarder <xeli@xelification.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 2 of the License
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
- */
-
-
-package org.hedgewars.hedgeroid;
-
-import java.io.ByteArrayOutputStream;
-import java.io.Closeable;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.List;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.os.Build;
-import android.os.Environment;
-import android.util.Log;
-
-public class Utils {
-	private static final String ROOT_DIR = "Data";
-	private static final String TAG = "org.hedgewars.hedgeroid";
-
-	/**
-	 * @return true if the data path is currently available. However, it can vanish at any time so
-	 * normally you should just try to use it and rely on the exceptions.
-	 */
-	public static boolean isDataPathAvailable() {
-		return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
-	}
-	
-	/**
-	 * get the path to which we should download all the data files
-	 * @param c context 
-	 * @return The directory
-	 * @throws FileNotFoundException if external storage is not available at the moment
-	 */
-	public static File getCachePath(Context c) throws FileNotFoundException {
-		File cachePath = null;
-		if(Build.VERSION.SDK_INT < 8){//8 == Build.VERSION_CODES.FROYO
-			cachePath = PreFroyoSDCardDir.getDownloadPath(c);
-		} else {
-			cachePath = FroyoSDCardDir.getDownloadPath(c);
-		}
-		if(cachePath==null) {
-			throw new FileNotFoundException("External storage is currently unavailable");
-		} else {
-			return cachePath;
-		}
-	}
-
-	public static File getDataPathFile(Context c) throws FileNotFoundException {
-		return new File(getCachePath(c), ROOT_DIR);
-	}
-	
-	// TODO Several callers are unaware that this may fail, so it throws an RTE now.
-	// Should be handled better though.
-	@Deprecated
-	public static String getDataPath(Context c) {
-		try {
-			return getDataPathFile(c).getAbsolutePath()+"/";
-		} catch(FileNotFoundException e) {
-			throw new RuntimeException(e);
-		}
-	}
-
-	@TargetApi(8)
-	private static class FroyoSDCardDir{
-		public static File getDownloadPath(Context c){
-			return c.getExternalCacheDir();
-		}
-	}
-
-	private static class PreFroyoSDCardDir{
-		public static File getDownloadPath(Context c){
-			if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
-				File extStorageDir = Environment.getExternalStorageDirectory();
-				if(extStorageDir != null) {
-					return new File(extStorageDir, "Hedgewars");
-				}
-			}
-			return null;
-		}
-	}
-
-	/**
-	 * Return a File array with all the files from dirName
-	 * @param c
-	 * @param dirName
-	 * @return
-	 * @throws FileNotFoundException If the sdcard is not available or the subdirectory "dirName" does not exist
-	 */
-	public static File[] getFilesFromRelativeDir(Context c, String dirName) throws FileNotFoundException {
-		File f = new File(getDataPathFile(c), dirName);
-
-		if(f.isDirectory()) {
-			return f.listFiles();
-		} else {
-			throw new FileNotFoundException("Directory "+dirName+" does not exist.");
-		}
-	}
-
-	/**
-	 * Checks if this directory has a file with suffix suffix
-	 * @param f - directory
-	 * @return
-	 */
-	public static boolean hasFileWithSuffix(File f, String suffix){
-		if(f.isDirectory()){
-			for(String s : f.list()){
-				if(s.endsWith(suffix)) return true;
-			}
-			return false;
-		}else{
-			return false;
-		}
-	}
-
-	/**
-	 * Gives back all dirs which contain a file with suffix fileSuffix
-	 * @param c
-	 * @param path
-	 * @param fileSuffix
-	 * @return
-	 * @throws FileNotFoundException If the sdcard is not available or the subdirectory "path" does not exist
-	 */
-	public static List<String> getDirsWithFileSuffix(Context c, String path, String fileSuffix) throws FileNotFoundException{
-		File[] files = getFilesFromRelativeDir(c,path);
-		ArrayList<String> ret = new ArrayList<String>();
-
-		for(File f : files){
-			if(hasFileWithSuffix(f, fileSuffix)) ret.add(f.getName());
-		}
-		return ret;
-	}
-
-	/**
-	 * Get all files from directory dir which have the given suffix
-	 * @throws FileNotFoundException If the sdcard is not available or the subdirectory "dir" does not exist
-	 */
-	public static ArrayList<String> getFileNamesFromDirWithSuffix(Context c, String dir, String suffix, boolean removeSuffix) throws FileNotFoundException{
-		File[] files = Utils.getFilesFromRelativeDir(c, dir);
-		ArrayList<String> ret = new ArrayList<String>();
-		for(File file : files){
-			String s = file.getName();
-			if(s.endsWith(suffix)){
-				if(removeSuffix) ret.add(s.substring(0, s.length()-suffix.length()));
-				else ret.add(s);
-			}
-		}
-		return ret;
-	}
-
-	/**
-	 * Close a resource (possibly null), ignoring any IOException.
-	 */
-	public static void closeQuietly(Closeable c) {
-		if(c!=null) {
-			try {
-				c.close();
-			} catch(IOException e) {
-				Log.w(TAG, e);
-			}
-		}
-	}
-	
-	/**
-	 * Write all data from the input stream to the file, creating or overwriting it.
-	 * The input stream will be closed.
-	 * 
-	 * @throws IOException
-	 */
-	public static void writeStreamToFile(InputStream is, File file) throws IOException {
-		OutputStream os = null;
-		byte[] buffer = new byte[8192];
-		try {
-			os = new FileOutputStream(file);
-			int size;
-			while((size=is.read(buffer)) != -1) {
-				os.write(buffer, 0, size);
-			}
-			os.close(); // Important to close this non-quietly, in case of exceptions when flushing
-		} finally {
-			Utils.closeQuietly(is);
-			Utils.closeQuietly(os);
-		}
-	}
-	
-	/**
-	 * Moves resources pointed to by sourceResId (from @res/raw/) to the app's private data directory
-	 * @param c
-	 * @param sourceResId
-	 * @param directory
-	 */
-	public static void resRawToFilesDir(Context c, int sourceResId, String directory) throws IOException {
-		File targetDir = new File(c.getFilesDir(), directory);
-		targetDir.mkdirs();
-
-		//Get an array with the resource files ID
-		Resources resources = c.getResources();
-		TypedArray ta = resources.obtainTypedArray(sourceResId);
-		for(int i = 0; i < ta.length(); i++){
-			int resId =  ta.getResourceId(i, 0);
-			String fileName = resources.getResourceEntryName(resId);
-			File f = new File(targetDir, fileName);
-			writeStreamToFile(resources.openRawResource(resId), f);
-		}
-	}
-	
-	/**
-	 * Crashing - at least it's better than ignoring errors.
-	 */
-	public static void assertZero(int value, String text) {
-		if(value != 0) {
-			throw new RuntimeException("Result is not zero: " + text);
-		}
-	}
-
-	public static String readToString(InputStream is) throws IOException {
-		try {
-			ByteArrayOutputStream os = new ByteArrayOutputStream();
-			byte[] buffer = new byte[8192];
-			int size;
-			while((size=is.read(buffer)) != -1) {
-				os.write(buffer, 0, size);
-			}
-			return new String(os.toByteArray());
-		} finally {
-			closeQuietly(is);
-		}
-	}
-	
-	private static final char[] badFilenameChars = new char[] { '/', '\\', ':', '*', '?', '\"', '<', '>', '|', '.', '\0' };
-	
-	/**
-	 * Modify the given String so that it can be used as part of a filename
-	 * without causing problems from illegal/special characters.
-	 * 
-	 * The result should be similar to the input, but isn't necessarily
-	 * reversible.
-	 */
-	public static String replaceBadChars(String name) {
-		if (name == null || name.trim().length()==0) {
-			return "_";
-		}
-		name = name.trim();
-		for (char badChar : badFilenameChars) {
-			name = name.replace(badChar, '_');
-		}
-		return name;
-	}
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/WeaponsetCreatorActivity.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,8 @@
+package org.hedgewars.hedgeroid;
+
+import android.support.v4.app.FragmentActivity;
+
+// TODO
+public class WeaponsetCreatorActivity extends FragmentActivity {
+	public static final String PARAMETER_EXISTING_WEAPONSETNAME="existingWeaponsetName";
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/WeaponsetListActivity.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,107 @@
+package org.hedgewars.hedgeroid;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.hedgewars.hedgeroid.Datastructures.Weaponset;
+import org.hedgewars.hedgeroid.Datastructures.Weaponsets;
+
+import android.app.ListActivity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.ContextMenu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.AdapterView;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.Button;
+import android.widget.ListAdapter;
+import android.widget.SimpleAdapter;
+import android.widget.Toast;
+
+public class WeaponsetListActivity extends ListActivity implements OnItemClickListener {
+	private List<Weaponset> userWeaponsets;
+	private Button addButton;
+	
+	@Override
+	protected void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		setContentView(R.layout.activity_weaponsetlist);
+		addButton = (Button)findViewById(R.id.addButton);
+		addButton.setOnClickListener(new OnClickListener() {
+			public void onClick(View v) {
+				editWeaponset(null);
+			}
+		});
+	}
+	
+	@Override
+	public void onResume() {
+		super.onResume();
+		updateList();
+		getListView().setOnItemClickListener(this);
+		registerForContextMenu(getListView());
+	}
+	
+	private List<Map<String, ?>> weaponsetsToMap(List<Weaponset> weaponsets) {
+		List<Map<String, ?>> result = new ArrayList<Map<String, ?>>();
+		for(Weaponset weaponset : weaponsets) {
+			result.add(Collections.singletonMap("txt", weaponset.name));
+		}
+		return result;
+	}
+	
+	public void onItemClick(AdapterView<?> adapterView, View v, int position, long arg3) {
+		editWeaponset(userWeaponsets.get(position).name);
+	}
+	
+	@Override
+	public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuinfo){
+		menu.add(ContextMenu.NONE, 0, ContextMenu.NONE, R.string.edit);
+		menu.add(ContextMenu.NONE, 1, ContextMenu.NONE, R.string.delete);
+	}
+	
+	@Override
+	public boolean onContextItemSelected(MenuItem item){
+		AdapterView.AdapterContextMenuInfo menuInfo = (AdapterContextMenuInfo) item.getMenuInfo();
+		int position = menuInfo.position;
+		Weaponset weaponset = userWeaponsets.get(position);
+		switch(item.getItemId()){
+		case 0:
+			editWeaponset(weaponset.name);
+			return true;
+		case 1:
+			try {
+				Weaponsets.deleteUserWeaponset(this, weaponset.name);
+			} catch (IOException e) {
+				Toast.makeText(this.getApplicationContext(), R.string.error_missing_sdcard_or_files, Toast.LENGTH_SHORT).show();
+			}
+			updateList();
+			return true;
+		}
+		return false;
+	}
+	
+	private void updateList() {
+		try {
+			userWeaponsets = Weaponsets.loadUserWeaponsets(this);
+		} catch (IOException e) {
+			Toast.makeText(this, R.string.error_missing_sdcard_or_files, Toast.LENGTH_LONG).show();
+			finish();
+		}
+		Collections.sort(userWeaponsets, Weaponset.NAME_ORDER);
+		ListAdapter adapter = new SimpleAdapter(this, weaponsetsToMap(userWeaponsets), android.R.layout.simple_list_item_1, new String[]{"txt"}, new int[]{android.R.id.text1});
+		setListAdapter(adapter);
+	}
+	
+	private void editWeaponset(String weaponsetName) {
+		Intent i = new Intent(this, WeaponsetCreatorActivity.class);
+		i.putExtra(WeaponsetCreatorActivity.PARAMETER_EXISTING_WEAPONSETNAME, weaponsetName);
+		startActivity(i);
+	}
+}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/frontlib/Frontlib.java	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/frontlib/Frontlib.java	Sat Aug 18 00:47:51 2012 +0200
@@ -12,7 +12,7 @@
 import org.hedgewars.hedgeroid.Datastructures.MetaScheme.Mod;
 import org.hedgewars.hedgeroid.Datastructures.MetaScheme.Setting;
 import org.hedgewars.hedgeroid.Datastructures.GameConfig;
-import org.hedgewars.hedgeroid.Datastructures.RoomlistRoom;
+import org.hedgewars.hedgeroid.Datastructures.Room;
 import org.hedgewars.hedgeroid.Datastructures.Scheme;
 import org.hedgewars.hedgeroid.Datastructures.Team;
 import org.hedgewars.hedgeroid.Datastructures.TeamInGame;
@@ -26,7 +26,6 @@
 import com.sun.jna.Pointer;
 import com.sun.jna.PointerType;
 import com.sun.jna.Structure;
-import com.sun.jna.ptr.IntByReference;
 
 /**
  * Here is an introduction to the most important aspects of the JNA code.
@@ -51,14 +50,12 @@
  * representing the data (e.g. SchemePtr.deref() will give you a Scheme object).
  * 
  * Remember that you usually have to destroy structs that you receive from the
- * library, because they are owned by the native code, not Java. For example, if
- * you obtain a {@link MetaschemePtr} metaPtr using flib_metascheme_from_ini,
- * you have to call flib_metascheme_release(metaPtr) after you are done using
- * it. The recommended pattern for most cases is to call deref() on the pointer
- * to get a Java object (that you can keep as long as you like), and then
- * immediately destroy the struct if it needs destroying. To find out whether
- * and how the struct needs to be destroyed, see the library's documentation of
- * the function that you got the struct from.
+ * library, because they are owned by the native code, not Java. The recommended
+ * pattern for most cases is to call deref() on the pointer to get a Java object
+ * (that you can keep as long as you like), and then immediately destroy the
+ * struct if it needs destroying. To find out whether and how the struct needs
+ * to be destroyed, see the library's documentation of the function that you got
+ * the struct from.
  * 
  * To pass new structs to the library, you can use the static createJavaOwned()
  * function in each PointerType, which creates a new struct from the Java object
@@ -146,13 +143,13 @@
 	}
 	
 	public static class RoomArrayPtr extends PointerType { 
-		public RoomlistRoom[] getRooms(int count) {
+		public Room[] getRooms(int count) {
 			Pointer ptr = getPointer();
 			if(ptr == null) {
-				return new RoomlistRoom[0];
+				return new Room[0];
 			}
 			Pointer[] untypedPtrs = ptr.getPointerArray(0, count);
-			RoomlistRoom[] result = new RoomlistRoom[count];
+			Room[] result = new Room[count];
 			for(int i=0; i<count; i++) {
 				result[i] = RoomPtr.deref(untypedPtrs[i]);
 			}
@@ -161,11 +158,11 @@
 	}
 	
 	public static class RoomPtr extends PointerType {
-		public RoomlistRoom deref() {
+		public Room deref() {
 			return deref(getPointer());
 		}
 		
-		public static RoomlistRoom deref(Pointer p) {
+		public static Room deref(Pointer p) {
 			RoomStruct struct = new RoomStruct(p);
 			struct.read();
 			return struct.toRoomlistRoom();
@@ -420,13 +417,12 @@
 	static class WeaponsetStruct extends Structure {
 		public static class ByVal extends WeaponsetStruct implements Structure.ByValue {}
 		public static class ByRef extends WeaponsetStruct implements Structure.ByReference {}
-		private static String[] FIELD_ORDER = new String[] {"_referenceCount", "loadout", "crateprob", "crateammo", "delay", "name"};
+		private static String[] FIELD_ORDER = new String[] {"loadout", "crateprob", "crateammo", "delay", "name"};
 		
 		public WeaponsetStruct() { super(); setFieldOrder(FIELD_ORDER); }
 		public WeaponsetStruct(Pointer ptr) { super(ptr); setFieldOrder(FIELD_ORDER); }
 		
 		public void fillFrom(Weaponset weaponset) {
-			_referenceCount = 0;
 			fillWeaponInfo(loadout, weaponset.loadout);
 			fillWeaponInfo(crateprob, weaponset.crateProb);
 			fillWeaponInfo(crateammo, weaponset.crateAmmo);
@@ -453,7 +449,6 @@
 			}
 		}
 		
-		public int _referenceCount;
 		public byte[] loadout = new byte[Weaponset.WEAPONS_COUNT+1];
 		public byte[] crateprob = new byte[Weaponset.WEAPONS_COUNT+1];
 		public byte[] crateammo = new byte[Weaponset.WEAPONS_COUNT+1];
@@ -483,13 +478,17 @@
 		
 		public void fillFrom(List<Weaponset> list) {
 			weaponsetCount = list.size();
-			weaponsets = new WeaponsetPointerByReference();
-			Structure[] structs = weaponsets.toArray(weaponsetCount);
-			
-			for(int i=0; i<weaponsetCount; i++) {
-				WeaponsetPointerByReference pstruct = (WeaponsetPointerByReference)structs[i];
-				pstruct.weaponset = new WeaponsetStruct.ByRef();
-				pstruct.weaponset.fillFrom(list.get(i));
+			if(weaponsetCount<=0) {
+				weaponsets = null;
+			} else {
+				weaponsets = new WeaponsetPointerByReference();
+				Structure[] structs = weaponsets.toArray(weaponsetCount);
+				
+				for(int i=0; i<weaponsetCount; i++) {
+					WeaponsetPointerByReference pstruct = (WeaponsetPointerByReference)structs[i];
+					pstruct.weaponset = new WeaponsetStruct.ByRef();
+					pstruct.weaponset.fillFrom(list.get(i));
+				}
 			}
 		}
 		
@@ -525,8 +524,8 @@
 		public RoomStruct() { super(); setFieldOrder(FIELD_ORDER); }
 		public RoomStruct(Pointer ptr) { super(ptr); setFieldOrder(FIELD_ORDER); }
 
-		public RoomlistRoom toRoomlistRoom() {
-			return new RoomlistRoom(name, map, scheme, weapons, owner, playerCount, teamCount, inProgress);
+		public Room toRoomlistRoom() {
+			return new Room(name, map, scheme, weapons, owner, playerCount, teamCount, inProgress);
 		}
 		
 	    public boolean inProgress;
@@ -641,31 +640,11 @@
 		public static class ByVal extends MetaschemeStruct implements Structure.ByValue {}
 		public static class ByRef extends MetaschemeStruct implements Structure.ByReference {}
 
-		private static String[] FIELD_ORDER = new String[] {"_referenceCount", "settingCount", "modCount", "settings", "mods"};
+		private static String[] FIELD_ORDER = new String[] {"settingCount", "modCount", "settings", "mods"};
 		
 		public MetaschemeStruct() { super(); setFieldOrder(FIELD_ORDER); }
 		public MetaschemeStruct(Pointer ptr) { super(ptr); setFieldOrder(FIELD_ORDER); }
 		
-		public void fillFrom(MetaScheme metascheme) {
-			settingCount = metascheme.settings.size();
-			modCount = metascheme.mods.size();
-			
-			settings = new MetaschemeSettingStruct.ByRef();
-			Structure[] settingStructs = settings.toArray(settingCount);
-			mods = new MetaschemeModStruct.ByRef();
-			Structure[] modStructs = mods.toArray(modCount);
-			
-			for(int i=0; i<settingCount; i++) {
-				MetaschemeSettingStruct mss = (MetaschemeSettingStruct)settingStructs[i];
-				mss.fillFrom(metascheme.settings.get(i));
-			}
-			
-			for(int i=0; i<modCount; i++) {
-				MetaschemeModStruct mms = (MetaschemeModStruct)modStructs[i];
-				mms.fillFrom(metascheme.mods.get(i));
-			}
-		}
-		
 		/**
 		 * Only use on native-owned structs!
 		 * Calling this method on a Java-owned struct could cause garbage collection of referenced
@@ -691,7 +670,6 @@
 			return new MetaScheme(modList, settingList);
 		}
 		
-		public int _referenceCount;
 		public int settingCount;
 		public int modCount;
 		public MetaschemeSettingStruct.ByRef settings;
@@ -701,41 +679,39 @@
 	static class SchemeStruct extends Structure {
 		public static class ByVal extends SchemeStruct implements Structure.ByValue {}
 		public static class ByRef extends SchemeStruct implements Structure.ByReference {}
-		private static String[] FIELD_ORDER = new String[] {"meta", "name", "settings", "mod"};
+		private static String[] FIELD_ORDER = new String[] {"name", "settings", "mod"};
 		
 		public SchemeStruct() { super(); setFieldOrder(FIELD_ORDER); }
 		public SchemeStruct(Pointer ptr) { super(ptr); setFieldOrder(FIELD_ORDER); }
 		
 		public void fillFrom(Scheme scheme) {
-			meta = new MetaschemeStruct.ByRef();
-			meta.fillFrom(scheme.metascheme);
+			MetaScheme meta = MetaScheme.INSTANCE;
 			name = scheme.name;
-			settings = new Memory(NATIVE_INT_SIZE * scheme.metascheme.settings.size());
-			for(int i=0; i<scheme.metascheme.settings.size(); i++) {
-				Integer value = scheme.settings.get(scheme.metascheme.settings.get(i).name);
+			settings = new Memory(NATIVE_INT_SIZE * meta.settings.size());
+			for(int i=0; i<meta.settings.size(); i++) {
+				Integer value = scheme.settings.get(meta.settings.get(i).name);
 				settings.setInt(NATIVE_INT_SIZE*i, value);
 			}
-			mods = new Memory(NATIVE_BOOL_SIZE * scheme.metascheme.mods.size());
-			for(int i=0; i<scheme.metascheme.mods.size(); i++) {
-				Boolean value = scheme.mods.get(scheme.metascheme.mods.get(i).name);
+			mods = new Memory(NATIVE_BOOL_SIZE * meta.mods.size());
+			for(int i=0; i<meta.mods.size(); i++) {
+				Boolean value = scheme.mods.get(meta.mods.get(i).name);
 				mods.setByte(NATIVE_BOOL_SIZE*i, (byte)(value ? 1 : 0));
 			}
 		}
 
 		public Scheme toScheme() {
-			MetaScheme metaScheme = meta.toMetaScheme();
 			Map<String, Integer> settingsMap = new HashMap<String, Integer>();
-			for(int i=0; i<metaScheme.settings.size(); i++) {
-				settingsMap.put(metaScheme.settings.get(i).name, settings.getInt(NATIVE_INT_SIZE*i));
+			MetaScheme meta = MetaScheme.INSTANCE;
+			for(int i=0; i<meta.settings.size(); i++) {
+				settingsMap.put(meta.settings.get(i).name, settings.getInt(NATIVE_INT_SIZE*i));
 			}
 			Map<String, Boolean> modsMap = new HashMap<String, Boolean>();
-			for(int i=0; i<metaScheme.mods.size(); i++) {
-				modsMap.put(metaScheme.mods.get(i).name, mods.getByte(i) != 0 ? Boolean.TRUE : Boolean.FALSE);
+			for(int i=0; i<meta.mods.size(); i++) {
+				modsMap.put(meta.mods.get(i).name, mods.getByte(i) != 0 ? Boolean.TRUE : Boolean.FALSE);
 			}
-			return new Scheme(metaScheme, name, settingsMap, modsMap);
+			return new Scheme(name, settingsMap, modsMap);
 		}
 		
-		public MetaschemeStruct.ByRef meta;
 		public String name;
 		public Pointer settings;
 		public Pointer mods;
@@ -763,13 +739,17 @@
 		
 		public void fillFrom(List<Scheme> schemeList) {
 			schemeCount = schemeList.size();
-			schemes = new SchemePointerByReference();
-			Structure[] schemePtrStructs = schemes.toArray(schemeCount);
-			
-			for(int i=0; i<this.schemeCount; i++) {
-				SchemePointerByReference spbr = (SchemePointerByReference)schemePtrStructs[i];
-				spbr.scheme = new SchemeStruct.ByRef();
-				spbr.scheme.fillFrom(schemeList.get(i));
+			if(schemeCount<=0) {
+				schemes = null;
+			} else {
+				schemes = new SchemePointerByReference();
+				Structure[] schemePtrStructs = schemes.toArray(schemeCount);
+				
+				for(int i=0; i<this.schemeCount; i++) {
+					SchemePointerByReference spbr = (SchemePointerByReference)schemePtrStructs[i];
+					spbr.scheme = new SchemeStruct.ByRef();
+					spbr.scheme.fillFrom(schemeList.get(i));
+				}
 			}
 		}
 
@@ -821,13 +801,17 @@
 		
 		public void fillFrom(List<TeamInGame> teamList, WeaponsetStruct.ByRef weaponset, int initialHealth) {
 			teamCount = teamList.size();
-			teams = new TeamPointerByReference();
-			Structure[] teamPtrStructs = teams.toArray(teamCount);
-			
-			for(int i=0; i<this.teamCount; i++) {
-				TeamPointerByReference tpbr = (TeamPointerByReference)teamPtrStructs[i];
-				tpbr.team = new TeamStruct.ByRef();
-				tpbr.team.fillFrom(teamList.get(i), weaponset, initialHealth);
+			if(teamCount <= 0) {
+				teams = null;
+			} else {
+				teams = new TeamPointerByReference();
+				Structure[] teamPtrStructs = teams.toArray(teamCount);
+				
+				for(int i=0; i<this.teamCount; i++) {
+					TeamPointerByReference tpbr = (TeamPointerByReference)teamPtrStructs[i];
+					tpbr.team = new TeamStruct.ByRef();
+					tpbr.team.fillFrom(teamList.get(i), weaponset, initialHealth);
+				}
 			}
 		}
 
@@ -986,7 +970,8 @@
     int flib_get_teamcolor_count();
     int flib_get_hedgehogs_per_team();
     int flib_get_weapons_count();
-    
+	MetaschemePtr flib_get_metascheme();
+	
     // net/netconn.h
 	static final int NETCONN_STATE_CONNECTING = 0;
 	static final int NETCONN_STATE_LOBBY = 1;
@@ -1016,7 +1001,7 @@
 	static final int NETCONN_MAPCHANGE_THEME = 6;
 	static final int NETCONN_MAPCHANGE_SEED = 7;
     
-	NetconnPtr flib_netconn_create(String playerName, MetaschemePtr meta, String dataDirPath, String host, int port);
+	NetconnPtr flib_netconn_create(String playerName, String dataDirPath, String host, int port);
 	void flib_netconn_destroy(NetconnPtr conn);
 
 	void flib_netconn_tick(NetconnPtr conn);
@@ -1036,7 +1021,7 @@
 	int flib_netconn_send_toggleReady(NetconnPtr conn);
 	int flib_netconn_send_addTeam(NetconnPtr conn, TeamPtr team);
 	int flib_netconn_send_removeTeam(NetconnPtr conn, String teamname);
-	int flib_netconn_send_engineMessage(NetconnPtr conn, Buffer message, NativeLong size);
+	int flib_netconn_send_engineMessage(NetconnPtr conn, Pointer message, NativeLong size);
 	int flib_netconn_send_teamHogCount(NetconnPtr conn, String teamname, int hogcount);
 	int flib_netconn_send_teamColor(NetconnPtr conn, String teamname, int colorIndex);
 	int flib_netconn_send_weaponset(NetconnPtr conn, WeaponsetPtr weaponset);
@@ -1047,7 +1032,7 @@
 	int flib_netconn_send_mapMazeSize(NetconnPtr conn, int mazeSize);
 	int flib_netconn_send_mapSeed(NetconnPtr conn, String seed);
 	int flib_netconn_send_mapTheme(NetconnPtr conn, String theme);
-	int flib_netconn_send_mapDrawdata(NetconnPtr conn, Buffer drawData, NativeLong size);
+	int flib_netconn_send_mapDrawdata(NetconnPtr conn, Pointer drawData, NativeLong size);
 	int flib_netconn_send_script(NetconnPtr conn, String scriptName);
 	int flib_netconn_send_scheme(NetconnPtr conn, SchemePtr scheme);
 	int flib_netconn_send_roundfinished(NetconnPtr conn, boolean withoutError);
@@ -1109,7 +1094,7 @@
 	int flib_gameconn_getport(GameconnPtr conn);
 	void flib_gameconn_tick(GameconnPtr conn);
 
-	int flib_gameconn_send_enginemsg(GameconnPtr conn, Buffer data, NativeLong len);
+	int flib_gameconn_send_enginemsg(GameconnPtr conn, Pointer data, NativeLong len);
 	int flib_gameconn_send_textmsg(GameconnPtr conn, int msgtype, String msg);
 	int flib_gameconn_send_chatmsg(GameconnPtr conn, String playername, String msg);
 	int flib_gameconn_send_quit(GameconnPtr conn);
@@ -1122,6 +1107,10 @@
 	void flib_gameconn_onEngineMessage(GameconnPtr conn, BytesCallback callback, Pointer context);
 	
 	// ipc/mapconn.h
+	public static final int MAPIMAGE_WIDTH = 256;
+	public static final int MAPIMAGE_HEIGHT = 128;
+	public static final int MAPIMAGE_BYTES = (MAPIMAGE_WIDTH/8*MAPIMAGE_HEIGHT);
+	
 	MapconnPtr flib_mapconn_create(MapRecipePtr mapdesc);
 	void flib_mapconn_destroy(MapconnPtr conn);
 	int flib_mapconn_getport(MapconnPtr conn);
@@ -1149,13 +1138,8 @@
 	public static final int MAZE_SIZE_MEDIUM_ISLANDS = 4;
 	public static final int MAZE_SIZE_LARGE_ISLANDS = 5;
 		
-	// model/scheme.h
-	MetaschemePtr flib_metascheme_from_ini(String filename);
-	MetaschemePtr flib_metascheme_retain(MetaschemePtr metainfo);
-	void flib_metascheme_release(MetaschemePtr metainfo);
-
 	// model/schemelist.h
-	SchemelistPtr flib_schemelist_from_ini(MetaschemePtr meta, String filename);
+	SchemelistPtr flib_schemelist_from_ini(String filename);
 	int flib_schemelist_to_ini(String filename, SchemelistPtr list);
 	void flib_schemelist_destroy(SchemelistPtr list);
 	
@@ -1169,6 +1153,9 @@
 	int flib_weaponsetlist_to_ini(String filename, WeaponsetListPtr weaponsets);
 	void flib_weaponsetlist_destroy(WeaponsetListPtr list);
 	
+	// model/gamesetup.h
+	void flib_gamesetup_destroy(GameSetupPtr gamesetup);
+	
 	// util/logging.h
 	public static final int FLIB_LOGLEVEL_ALL = -100;
 	public static final int FLIB_LOGLEVEL_DEBUG = -1;
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/ChatFragment.java	Sat Aug 18 00:22:33 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-package org.hedgewars.hedgeroid.netplay;
-
-import org.hedgewars.hedgeroid.R;
-
-import android.os.Bundle;
-import android.support.v4.app.Fragment;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.EditText;
-import android.widget.ListView;
-import android.widget.TextView;
-import android.widget.TextView.OnEditorActionListener;
-
-public class ChatFragment extends Fragment {
-	public static final String ARGUMENT_INROOM = "inRoom";
-	
-	private ChatlogAdapter adapter;
-	private Netplay netconn;
-	private MessageLog messageLog;
-	private boolean inRoom;
-	
-	public void setInRoom(boolean inRoom) {
-		this.inRoom = inRoom;
-	}
-	
-	@Override
-	public void onCreate(Bundle savedInstanceState) {
-		super.onCreate(savedInstanceState);
-		netconn = Netplay.getAppInstance(getActivity().getApplicationContext());
-		adapter = new ChatlogAdapter(getActivity());
-	}
-	
-	@Override
-	public void onStart() {
-		super.onStart();
-		messageLog = inRoom ? netconn.roomChatlog : netconn.lobbyChatlog;
-    	adapter.setLog(messageLog.getLog());
-    	messageLog.registerObserver(adapter);
-	}
-	
-	@Override
-	public void onStop() {
-		super.onStop();
-		messageLog.unregisterObserver(adapter);
-	}
-	
-	@Override
-	public View onCreateView(LayoutInflater inflater, ViewGroup container,
-			Bundle savedInstanceState) {
-		View view = inflater.inflate(R.layout.fragment_chat, container, false);
-		
-		ListView listView = (ListView) view.findViewById(R.id.chatConsole);
-		listView.setAdapter(adapter);
-		listView.setDivider(null);
-		listView.setDividerHeight(0);
-		listView.setVerticalFadingEdgeEnabled(true);
-		
-		EditText editText = (EditText) view.findViewById(R.id.chatInput);
-        editText.setOnEditorActionListener(new ChatSendListener());
-        
-		return view;
-	}
-	
-	private final class ChatSendListener implements OnEditorActionListener {
-		public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
-			String text = v.getText().toString();
-			if(text.length()>0) {
-				v.setText("");
-				netconn.sendChat(text);
-			}
-			return true;
-		}
-	}
-}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/ChatlogAdapter.java	Sat Aug 18 00:22:33 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,95 +0,0 @@
-package org.hedgewars.hedgeroid.netplay;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-import org.hedgewars.hedgeroid.netplay.MessageLog.Observer;
-
-import android.content.Context;
-import android.text.method.LinkMovementMethod;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AbsListView.LayoutParams;
-import android.widget.BaseAdapter;
-import android.widget.TextView;
-
-/**
- * Optimization: ListView is smart enough to try re-using the same view for an item
- * with the same ID, but it still calls getView for those items when the list changes.
- * Since lines with a given ID never change in our chatlog, we can avoid the effort
- * of TextView.setText in many cases by checking if the view is already set up for the
- * line with the right ID - but to do that, the view needs to remember the ID it's
- * holding the text for. That's what the LoglineView does. 
- */
-class LoglineView extends TextView {
-	long chatlogId = -1;
-	
-	public LoglineView(Context context) {
-		super(context);
-	}
-}
-
-public class ChatlogAdapter extends BaseAdapter implements Observer {
-	long idOffset = 0;
-	private List<CharSequence> log = new ArrayList<CharSequence>();
-	private Context context;
-	
-	public ChatlogAdapter(Context context) {
-		this.context = context;
-	}
-	
-	public int getCount() {
-		return log.size();
-	}
-
-	public Object getItem(int position) {
-		return log.get(position);
-	}
-
-	public long getItemId(int position) {
-		return position+idOffset;
-	}
-
-	public boolean hasStableIds() {
-		return true;
-	}
-
-	public void clear() {
-		idOffset += log.size();
-		log.clear();
-		notifyDataSetChanged();
-	}
-	
-	public void lineAdded(CharSequence text) {
-		log.add(text);
-		notifyDataSetChanged();
-	}
-	
-	public void lineRemoved() {
-		log.remove(0);
-		idOffset += 1;
-		notifyDataSetChanged();
-	}
-	
-	public void setLog(Collection<CharSequence> log) {
-		idOffset += log.size();
-		this.log = new ArrayList<CharSequence>(log);
-		notifyDataSetChanged();
-	}
-	
-	public View getView(int position, View convertView, ViewGroup parent) {
-		LoglineView v = (LoglineView)convertView;
-		if (v == null) {
-			v = new LoglineView(context);
-			v.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
-			v.setMovementMethod(LinkMovementMethod.getInstance());
-		}
-		long id = getItemId(position);
-		if(id != v.chatlogId) {
-			v.setText(log.get(position));
-			v.chatlogId = id;
-		}
-		return v;
-	}
-}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/GameMessageListener.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,15 @@
+package org.hedgewars.hedgeroid.netplay;
+
+/**
+ * Interface with several event callbacks that represent network messages which are interesting
+ * for a running game, e.g. because they concern the lifecycle of the game or because they contain
+ * data that needs to be passed on.
+ * 
+ * These functions might be called on any thread.
+ */
+public interface GameMessageListener {
+	void onChatMessage(String nick, String message);
+	void onEngineMessage(byte[] em);
+	void onMessage(int type, String message);
+	void onNetDisconnected();
+}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/LobbyActivity.java	Sat Aug 18 00:22:33 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,138 +0,0 @@
-package org.hedgewars.hedgeroid.netplay;
-
-import org.hedgewars.hedgeroid.R;
-import org.hedgewars.hedgeroid.netplay.Netplay.State;
-import org.hedgewars.hedgeroid.netplay.NetplayStateFragment.NetplayStateListener;
-import org.hedgewars.hedgeroid.netplay.TextInputDialog.TextInputDialogListener;
-
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.FragmentTransaction;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TabHost;
-import android.widget.TextView;
-
-public class LobbyActivity extends FragmentActivity implements TextInputDialogListener, NetplayStateListener {
-	private static final int DIALOG_CREATE_ROOM = 0;
-	
-    private TabHost tabHost;
-    private Netplay netplay;
-    
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-        
-        setContentView(R.layout.activity_lobby);
-        ChatFragment chatFragment = (ChatFragment)getSupportFragmentManager().findFragmentById(R.id.chatFragment);
-        chatFragment.setInRoom(false);
-        
-        FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
-        trans.add(new NetplayStateFragment(), "netplayFragment");
-        trans.commit();
-        
-        netplay = Netplay.getAppInstance(getApplicationContext());
-        
-        tabHost = (TabHost)findViewById(android.R.id.tabhost);
-        if(tabHost != null) {
-	        tabHost.setup();
-	        tabHost.getTabWidget().setOrientation(LinearLayout.VERTICAL);
-
-	        tabHost.addTab(tabHost.newTabSpec("rooms").setIndicator(createIndicatorView(tabHost, R.string.lobby_tab_rooms, getResources().getDrawable(R.drawable.roomlist_ingame))).setContent(R.id.roomListFragment));
-	        tabHost.addTab(tabHost.newTabSpec("chat").setIndicator(createIndicatorView(tabHost, R.string.lobby_tab_chat, getResources().getDrawable(R.drawable.edit))).setContent(R.id.chatFragment));
-	        tabHost.addTab(tabHost.newTabSpec("players").setIndicator(createIndicatorView(tabHost, R.string.lobby_tab_players, getResources().getDrawable(R.drawable.human))).setContent(R.id.playerListFragment));
-	
-	        if (icicle != null) {
-	            tabHost.setCurrentTabByTag(icicle.getString("currentTab"));
-	        }
-        }
-    }
-    
-    private View createIndicatorView(TabHost tabHost, int label, Drawable icon) {
-        LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-
-        View tabIndicator = inflater.inflate(R.layout.tab_indicator,
-                tabHost.getTabWidget(), // tab widget is the parent
-                false); // no inflate params
-
-        final TextView tv = (TextView) tabIndicator.findViewById(R.id.title);
-        tv.setText(label);
-        
-        if(icon != null) {
-	        final ImageView iconView = (ImageView) tabIndicator.findViewById(R.id.icon);
-	        iconView.setImageDrawable(icon);
-        }
-        
-        return tabIndicator;
-    }
-    
-	@Override
-	public boolean onCreateOptionsMenu(Menu menu) {
-		super.onCreateOptionsMenu(menu);
-		getMenuInflater().inflate(R.menu.lobby_options, menu);
-		return true;
-	}
-	
-	@Override
-	public boolean onOptionsItemSelected(MenuItem item) {
-		switch(item.getItemId()) {
-		case R.id.room_create:
-	        TextInputDialog dialog = new TextInputDialog(DIALOG_CREATE_ROOM, R.string.dialog_create_room_title, 0, R.string.dialog_create_room_hint);
-	        dialog.show(getSupportFragmentManager(), "create_room_dialog");
-			return true;
-		case R.id.disconnect:
-			netplay.disconnect();
-			return true;
-		default:
-			return super.onOptionsItemSelected(item);
-		}
-	}
-	
-	@Override
-	public void onBackPressed() {
-		super.onBackPressed();
-		netplay.disconnect();
-	}
-	
-    @Override
-    protected void onSaveInstanceState(Bundle icicle) {
-        super.onSaveInstanceState(icicle);
-        if(tabHost != null) {
-        	icicle.putString("currentTab", tabHost.getCurrentTabTag());
-        }
-    }
-    
-    public void onTextInputDialogSubmitted(int dialogId, String text) {
-    	if(text != null && text.length()>0) {
-    		netplay.sendCreateRoom(text);
-    	}
-    }
-    
-    public void onTextInputDialogCancelled(int dialogId) {
-    }
-    
-    public void onNetplayStateChanged(State newState) {
-    	switch(newState) {
-    	case CONNECTING:
-    	case NOT_CONNECTED:
-    		finish();
-    		break;
-    	case ROOM:
-    	case INGAME:
-    		startActivity(new Intent(getApplicationContext(), RoomActivity.class));
-    		break;
-    	case LOBBY:
-    		// Do nothing
-    		break;
-		default:
-			throw new IllegalStateException("Unknown connection state: "+newState);
-    	}
-    }
-}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/LobbyPlayerlist.java	Sat Aug 18 00:22:33 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-package org.hedgewars.hedgeroid.netplay;
-
-import org.hedgewars.hedgeroid.Datastructures.Player;
-
-import android.util.Pair;
-
-public class LobbyPlayerlist extends ObservableTreeMap<String, Pair<Player, Long>> {
-	private long nextId = 1;
-	
-	public void addPlayerWithNewId(String name) {
-		put(name, Pair.create(new Player(name), nextId++));
-	}
-}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/LobbyPlayerlistAdapter.java	Sat Aug 18 00:22:33 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,58 +0,0 @@
-package org.hedgewars.hedgeroid.netplay;
-
-import java.util.Comparator;
-
-import org.hedgewars.hedgeroid.R;
-import org.hedgewars.hedgeroid.Datastructures.Player;
-
-import android.content.Context;
-import android.util.Pair;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-public class LobbyPlayerlistAdapter extends ObservableTreeMapAdapter<String, Pair<Player, Long>> {
-	private Context context;
-	
-	public LobbyPlayerlistAdapter(Context context) {
-		this.context = context;
-	}
-	
-	@Override
-	protected Comparator<Pair<Player, Long>> getEntryOrder() {
-		return AlphabeticalOrderComparator.INSTANCE;
-	}
-
-	public Player getItem(int position) {
-		return getEntries().get(position).first;
-	}
-
-	public long getItemId(int position) {
-		return getEntries().get(position).second;
-	}
-
-	public boolean hasStableIds() {
-		return true;
-	}
-	
-	public View getView(int position, View convertView, ViewGroup parent) {
-		View v = convertView;
-		if (v == null) {
-			LayoutInflater vi = LayoutInflater.from(context);
-			v = vi.inflate(R.layout.listview_player, null);
-		}
-
-		String player = getItem(position).name;
-		TextView username = (TextView) v.findViewById(android.R.id.text1);
-		username.setText(player);
-		return v;
-	}
-	
-	private static final class AlphabeticalOrderComparator implements Comparator<Pair<Player, Long>> {
-		public static final AlphabeticalOrderComparator INSTANCE = new AlphabeticalOrderComparator();
-		public int compare(Pair<Player, Long> lhs, Pair<Player, Long> rhs) {
-			return lhs.first.name.compareToIgnoreCase(rhs.first.name);
-		};
-	}
-}
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/LobbyPlayerlistFragment.java	Sat Aug 18 00:22:33 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,74 +0,0 @@
-package org.hedgewars.hedgeroid.netplay;
-
-import org.hedgewars.hedgeroid.R;
-import org.hedgewars.hedgeroid.Datastructures.Player;
-
-import android.os.Bundle;
-import android.support.v4.app.ListFragment;
-import android.view.ContextMenu;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.LayoutInflater;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView.AdapterContextMenuInfo;
-
-public class LobbyPlayerlistFragment extends ListFragment {
-	private Netplay netplay;
-	private LobbyPlayerlistAdapter adapter;
-	
-	@Override
-	public void onCreate(Bundle savedInstanceState) {
-		super.onCreate(savedInstanceState);
-		netplay = Netplay.getAppInstance(getActivity().getApplicationContext());
-		adapter = new LobbyPlayerlistAdapter(getActivity());
-		adapter.setSource(netplay.lobbyPlayerlist);
-		setListAdapter(adapter);
-	}
-
-	@Override
-	public void onDestroy() {
-		super.onDestroy();
-		adapter.invalidate();
-	}
-	
-	@Override
-	public void onActivityCreated(Bundle savedInstanceState) {
-		super.onActivityCreated(savedInstanceState);
-		registerForContextMenu(getListView());
-	}
-
-	@Override
-	public void onCreateContextMenu(ContextMenu menu, View v,
-			ContextMenuInfo menuInfo) {
-		super.onCreateContextMenu(menu, v, menuInfo);
-		AdapterContextMenuInfo info = (AdapterContextMenuInfo)menuInfo;
-		MenuInflater inflater = getActivity().getMenuInflater();
-		inflater.inflate(R.menu.lobby_playerlist_context, menu);
-		menu.setHeaderIcon(R.drawable.human);
-		menu.setHeaderTitle(adapter.getItem(info.position).name);
-	}
-	
-	@Override
-	public boolean onContextItemSelected(MenuItem item) {
-		AdapterContextMenuInfo info = (AdapterContextMenuInfo)item.getMenuInfo();
-		Player player = adapter.getItem(info.position);
-		switch(item.getItemId()) {
-		case R.id.player_info:
-			netplay.sendPlayerInfoQuery(player.name);
-			return true;
-		case R.id.player_follow:
-			netplay.sendFollowPlayer(player.name);
-			return true;
-		default:
-			return super.onContextItemSelected(item);
-		}
-	}
-	
-	@Override
-	public View onCreateView(LayoutInflater inflater, ViewGroup container,
-			Bundle savedInstanceState) {
-		return inflater.inflate(R.layout.fragment_playerlist, container, false);
-	}
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/NetRoomState.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,207 @@
+package org.hedgewars.hedgeroid.netplay;
+
+import static org.hedgewars.hedgeroid.netplay.ThreadedNetConnection.ToNetMsgType.MSG_SEND_GAMESTYLE;
+import static org.hedgewars.hedgeroid.netplay.ThreadedNetConnection.ToNetMsgType.MSG_SEND_MAP;
+import static org.hedgewars.hedgeroid.netplay.ThreadedNetConnection.ToNetMsgType.MSG_SEND_MAP_DRAWDATA;
+import static org.hedgewars.hedgeroid.netplay.ThreadedNetConnection.ToNetMsgType.MSG_SEND_MAP_GENERATOR;
+import static org.hedgewars.hedgeroid.netplay.ThreadedNetConnection.ToNetMsgType.MSG_SEND_MAP_NAME;
+import static org.hedgewars.hedgeroid.netplay.ThreadedNetConnection.ToNetMsgType.MSG_SEND_MAP_SEED;
+import static org.hedgewars.hedgeroid.netplay.ThreadedNetConnection.ToNetMsgType.MSG_SEND_MAP_TEMPLATE;
+import static org.hedgewars.hedgeroid.netplay.ThreadedNetConnection.ToNetMsgType.MSG_SEND_MAP_THEME;
+import static org.hedgewars.hedgeroid.netplay.ThreadedNetConnection.ToNetMsgType.MSG_SEND_MAZE_SIZE;
+import static org.hedgewars.hedgeroid.netplay.ThreadedNetConnection.ToNetMsgType.MSG_SEND_SCHEME;
+import static org.hedgewars.hedgeroid.netplay.ThreadedNetConnection.ToNetMsgType.MSG_SEND_WEAPONSET;
+
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.hedgewars.hedgeroid.RoomStateManager;
+import org.hedgewars.hedgeroid.Datastructures.GameConfig;
+import org.hedgewars.hedgeroid.Datastructures.MapRecipe;
+import org.hedgewars.hedgeroid.Datastructures.Scheme;
+import org.hedgewars.hedgeroid.Datastructures.Weaponset;
+import org.hedgewars.hedgeroid.netplay.ThreadedNetConnection.ToNetMsgType;
+
+/**
+ * This class manages the room state in a network game.
+ */
+class NetRoomState implements RoomStateManager {
+	private List<RoomStateManager.Observer> observers = new LinkedList<RoomStateManager.Observer>();
+	private Netplay netplay;
+	
+	boolean chief;
+	String gameStyle;
+	Scheme scheme;
+	MapRecipe map;
+	Weaponset weaponset;
+	
+	public NetRoomState(Netplay netplay) {
+		this.netplay = netplay;
+		this.map = MapRecipe.makeRandomMap(0, "seed", GameConfig.DEFAULT_THEME);
+	}
+
+	public MapRecipe getMapRecipe() {
+		return map;
+	}
+
+	public boolean getChiefStatus() {
+		return chief;
+	}
+
+	public Scheme getScheme() {
+		return scheme;
+	}
+
+	public String getGameStyle() {
+		return gameStyle;
+	}
+
+	public Weaponset getWeaponset() {
+		return weaponset;
+	}
+
+	public void changeWeaponset(Weaponset weaponset) {
+		if(chief && !weaponset.equals(this.weaponset)) {
+			sendToNet(MSG_SEND_WEAPONSET, weaponset);
+			setWeaponset(weaponset);
+		}
+	}
+	
+	public void changeMapRecipe(MapRecipe mapRecipe) {
+		if(chief && !mapRecipe.equals(this.map)) {
+			sendToNet(MSG_SEND_MAP, mapRecipe);
+			setMapRecipe(mapRecipe);
+		}
+	}
+	
+	public void changeMapNameAndGenerator(String mapName) {
+		if(chief && !mapName.equals(this.map.name)) {
+			int newGenerator = MapRecipe.generatorForMapname(mapName);
+			if(newGenerator != this.map.mapgen) {
+				sendToNet(MSG_SEND_MAP_GENERATOR, newGenerator, null);
+			}
+			sendToNet(MSG_SEND_MAP_NAME, mapName);
+			setMapRecipe(map.withName(mapName).withMapgen(newGenerator));
+		}
+	}
+	
+	public void changeMapTemplate(int template) {
+		if(chief && template != this.map.templateFilter) {
+			sendToNet(MSG_SEND_MAP_TEMPLATE, template, null);
+			setMapRecipe(map.withTemplateFilter(template));
+		}
+	}
+	
+	public void changeMazeSize(int mazeSize) {
+		if(chief && mazeSize != this.map.mazeSize) {
+			sendToNet(MSG_SEND_MAZE_SIZE, mazeSize, 0);
+			setMapRecipe(map.withMazeSize(mazeSize));
+		}
+	}
+	
+	public void changeMapSeed(String seed) {
+		if(chief && !seed.equals(this.map.seed)) {
+			sendToNet(MSG_SEND_MAP_SEED, seed);
+			setMapRecipe(map.withSeed(seed));
+		}
+	}
+	
+	public void changeMapTheme(String theme) {
+		if(chief && !theme.equals(this.map.theme)) {
+			sendToNet(MSG_SEND_MAP_THEME, theme);
+			setMapRecipe(map.withTheme(theme));
+		}
+	}
+	
+	public void changeMapDrawdata(byte[] drawdata) {
+		if(chief && !Arrays.equals(drawdata, this.map.getDrawData())) {
+			sendToNet(MSG_SEND_MAP_DRAWDATA, drawdata);
+			setMapRecipe(map.withDrawData(drawdata));
+		}
+	}
+	
+	public void changeGameStyle(String gameStyle) {
+		if(chief && !gameStyle.equals(this.gameStyle)) {
+			sendToNet(MSG_SEND_GAMESTYLE, gameStyle);
+			setGameStyle(gameStyle);
+		}
+	}
+	
+	public void changeScheme(Scheme scheme) {
+		if(chief && !scheme.equals(this.scheme)) {
+			sendToNet(MSG_SEND_SCHEME, scheme);
+			setScheme(scheme);
+		}
+	}
+	
+	void setWeaponset(Weaponset weaponset) {
+		if(!weaponset.equals(this.weaponset)) {
+			this.weaponset = weaponset;
+			for(RoomStateManager.Observer observer : observers) {
+				observer.onWeaponsetChanged(weaponset);
+			}
+		}
+	}
+	
+	void setMapRecipe(MapRecipe map) {
+		if(!map.equals(this.map)) { 
+			this.map = map;
+			for(RoomStateManager.Observer observer : observers) {
+				observer.onMapChanged(map);
+			}
+		}
+	}
+	
+	void setGameStyle(String gameStyle) {
+		if(!gameStyle.equals(this.gameStyle)) {
+			this.gameStyle = gameStyle;
+			for(RoomStateManager.Observer observer : observers) {
+				observer.onGameStyleChanged(gameStyle);
+			}
+		}
+	}
+	
+	void setScheme(Scheme scheme) {
+		if(!scheme.equals(this.scheme)) {
+			this.scheme = scheme;
+			for(RoomStateManager.Observer observer : observers) {
+				observer.onSchemeChanged(scheme);
+			}
+		}
+	}
+	
+	void setChief(boolean chief) {
+		if(chief != this.chief) {
+			this.chief = chief;
+			for(RoomStateManager.Observer observer : observers) {
+				observer.onChiefStatusChanged(chief);
+			}
+		}
+	}
+	
+	void sendFullConfig() {
+		if(chief) {
+			sendToNet(MSG_SEND_GAMESTYLE, gameStyle);
+			sendToNet(MSG_SEND_SCHEME, scheme);
+			sendToNet(MSG_SEND_WEAPONSET, weaponset);
+			sendToNet(MSG_SEND_MAP, map);
+		}
+	}
+	
+	public void registerObserver(Observer observer) {
+		observers.add(observer);
+	}
+
+	public void unregisterObserver(Observer observer) {
+		observers.remove(observer);
+	}
+	
+	private boolean sendToNet(ToNetMsgType what, Object obj) {
+		return netplay.sendToNet(what, 0, obj);
+	}
+	
+	private boolean sendToNet(ToNetMsgType what, int arg1, Object obj) {
+		return netplay.sendToNet(what, arg1, obj);
+	}
+}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Netplay.java	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Netplay.java	Sat Aug 18 00:47:51 2012 +0200
@@ -1,48 +1,42 @@
 package org.hedgewars.hedgeroid.netplay;
 
-import java.io.File;
-import java.io.FileNotFoundException;
+import static org.hedgewars.hedgeroid.netplay.ThreadedNetConnection.ToNetMsgType.*;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
 
-import org.hedgewars.hedgeroid.R;
-import org.hedgewars.hedgeroid.Utils;
-import org.hedgewars.hedgeroid.Datastructures.RoomlistRoom;
+import org.hedgewars.hedgeroid.RoomStateManager;
+import org.hedgewars.hedgeroid.Datastructures.GameConfig;
+import org.hedgewars.hedgeroid.Datastructures.MapRecipe;
+import org.hedgewars.hedgeroid.Datastructures.Player;
+import org.hedgewars.hedgeroid.Datastructures.PlayerInRoom;
+import org.hedgewars.hedgeroid.Datastructures.Room;
+import org.hedgewars.hedgeroid.Datastructures.Scheme;
+import org.hedgewars.hedgeroid.Datastructures.Schemes;
 import org.hedgewars.hedgeroid.Datastructures.Team;
 import org.hedgewars.hedgeroid.Datastructures.TeamInGame;
 import org.hedgewars.hedgeroid.Datastructures.TeamIngameAttributes;
+import org.hedgewars.hedgeroid.Datastructures.Weaponset;
+import org.hedgewars.hedgeroid.Datastructures.Weaponsets;
 import org.hedgewars.hedgeroid.frontlib.Flib;
-import org.hedgewars.hedgeroid.frontlib.Frontlib;
-import org.hedgewars.hedgeroid.frontlib.Frontlib.BoolCallback;
-import org.hedgewars.hedgeroid.frontlib.Frontlib.IntStrCallback;
-import org.hedgewars.hedgeroid.frontlib.Frontlib.MetaschemePtr;
-import org.hedgewars.hedgeroid.frontlib.Frontlib.NetconnPtr;
-import org.hedgewars.hedgeroid.frontlib.Frontlib.RoomArrayPtr;
-import org.hedgewars.hedgeroid.frontlib.Frontlib.RoomCallback;
-import org.hedgewars.hedgeroid.frontlib.Frontlib.RoomListCallback;
-import org.hedgewars.hedgeroid.frontlib.Frontlib.RoomPtr;
-import org.hedgewars.hedgeroid.frontlib.Frontlib.StrBoolCallback;
-import org.hedgewars.hedgeroid.frontlib.Frontlib.StrCallback;
-import org.hedgewars.hedgeroid.frontlib.Frontlib.StrIntCallback;
-import org.hedgewars.hedgeroid.frontlib.Frontlib.StrRoomCallback;
-import org.hedgewars.hedgeroid.frontlib.Frontlib.StrStrCallback;
-import org.hedgewars.hedgeroid.frontlib.Frontlib.TeamCallback;
-import org.hedgewars.hedgeroid.frontlib.Frontlib.TeamPtr;
-import org.hedgewars.hedgeroid.frontlib.Frontlib.VoidCallback;
+import org.hedgewars.hedgeroid.netplay.ThreadedNetConnection.ToNetMsgType;
+import org.hedgewars.hedgeroid.util.ObservableTreeMap;
 
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.Intent;
-import android.content.res.Resources;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
 import android.support.v4.content.LocalBroadcastManager;
 import android.util.Log;
 import android.util.Pair;
 
-import com.sun.jna.Pointer;
 
 /**
  * This class manages the application's networking state.
@@ -72,21 +66,25 @@
 	private final FromNetHandler fromNetHandler = new FromNetHandler();
 	
 	private State state = State.NOT_CONNECTED;
-	private int foregroundUsers = 0;	// Reference counter of clients requesting foreground tick speed (fast ticks)
-	private boolean chief;				// Do we control the current room?
 	private String playerName;
 	
+	// null or stale if not in room state
+	private final NetRoomState netRoomState = new NetRoomState(this);
+	
 	// null if there is no running connection (==state is NOT_CONNECTED)
 	private ThreadedNetConnection connection;
 	
-	public final LobbyPlayerlist lobbyPlayerlist = new LobbyPlayerlist();
-	public final RoomPlayerlist roomPlayerlist = new RoomPlayerlist();
+	public final ObservableTreeMap<String, Player> lobbyPlayerlist = new ObservableTreeMap<String, Player>();
+	public final ObservableTreeMap<String, PlayerInRoom> roomPlayerlist = new ObservableTreeMap<String, PlayerInRoom>();
 	public final Roomlist roomList = new Roomlist();
 	public final MessageLog lobbyChatlog;
 	public final MessageLog roomChatlog;
-	public final Teamlist roomTeamlist = new Teamlist();
+	public final ObservableTreeMap<String, TeamInGame> roomTeamlist = new ObservableTreeMap<String, TeamInGame>();
 	private final Map<String, Team> roomRequestedTeams = new TreeMap<String, Team>();
 	
+	private final List<GameMessageListener> gameMessageListeners = new LinkedList<GameMessageListener>();
+	private final List<RunGameListener> runGameListeners = new LinkedList<RunGameListener>();
+	
 	public Netplay(Context appContext) {
 		this.appContext = appContext;
 		broadcastManager = LocalBroadcastManager.getInstance(appContext);
@@ -94,12 +92,52 @@
 		roomChatlog = new MessageLog(appContext);
 	}
 	
-	private void clearState() {
+	public RoomStateManager getRoomStateManager() {
+		return netRoomState;
+	}
+	
+	private void clearLobbyState() {
 		lobbyPlayerlist.clear();
 		roomList.clear();
 		lobbyChatlog.clear();
 	}
 	
+	private void initRoomState(boolean chief) {
+		roomChatlog.clear();
+		roomPlayerlist.clear();
+		roomTeamlist.clear();
+		roomRequestedTeams.clear();
+		
+		try {
+			netRoomState.setChief(chief);
+			netRoomState.setGameStyle(GameConfig.DEFAULT_STYLE);
+			List<Scheme> schemes = Schemes.loadBuiltinSchemes(appContext);
+			netRoomState.setScheme(schemes.get(Schemes.toNameList(schemes).indexOf(GameConfig.DEFAULT_SCHEME)));
+			netRoomState.setMapRecipe(MapRecipe.makeRandomMap(0, MapRecipe.makeRandomSeed(), GameConfig.DEFAULT_THEME));
+			List<Weaponset> weaponsets = Weaponsets.loadBuiltinWeaponsets(appContext);
+			netRoomState.setWeaponset(weaponsets.get(Weaponsets.toNameList(weaponsets).indexOf(GameConfig.DEFAULT_WEAPONSET)));
+			netRoomState.sendFullConfig();
+		} catch(IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+	
+	public void registerGameMessageListener(GameMessageListener listener) {
+		gameMessageListeners.add(listener);
+	}
+	
+	public void unregisterGameMessageListener(GameMessageListener listener) {
+		gameMessageListeners.remove(listener);
+	}
+	
+	public void registerRunGameListener(RunGameListener listener) {
+		runGameListeners.add(listener);
+	}
+	
+	public void unregisterRunGameListener(RunGameListener listener) {
+		runGameListeners.remove(listener);
+	}
+	
 	public void connectToDefaultServer(String playerName) {
 		connect(playerName, DEFAULT_SERVER, DEFAULT_PORT);
 	}
@@ -117,35 +155,39 @@
 			throw new IllegalStateException("Attempt to start a new connection while the old one was still running.");
 		}
 		
-		clearState();
+		clearLobbyState();
 		changeState(State.CONNECTING);
 		connection = ThreadedNetConnection.startConnection(appContext, fromNetHandler, name, host, port);
-		connection.setFastTickRate(foregroundUsers > 0);
+		connection.setFastTickRate(true);
 	}
 	
 	public void sendNick(String nick) {
 		playerName = nick;
-		sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_NICK, nick);
+		sendToNet(MSG_SEND_NICK, nick);
 	}
-	public void sendPassword(String password) { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_PASSWORD, password); }
-	public void sendQuit(String message) { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_QUIT, message); }
-	public void sendRoomlistRequest() { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_ROOMLIST_REQUEST); }
-	public void sendPlayerInfoQuery(String name) { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_PLAYER_INFO_REQUEST, name); }
-	public void sendChat(String s) { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_CHAT, s); }
-	public void sendFollowPlayer(String nick) { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_FOLLOW_PLAYER, nick); }
-	public void sendJoinRoom(String name) { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_JOIN_ROOM, name); }
-	public void sendCreateRoom(String name) { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_CREATE_ROOM, name); }
-	public void sendLeaveRoom(String message) { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_LEAVE_ROOM, message); }
-	public void sendKick(String player) { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_KICK, player); }
+	public void sendPassword(String password) { sendToNet(MSG_SEND_PASSWORD, password); }
+	public void sendQuit(String message) { sendToNet(MSG_SEND_QUIT, message); }
+	public void sendRoomlistRequest() { sendToNet(MSG_SEND_ROOMLIST_REQUEST); }
+	public void sendPlayerInfoQuery(String name) { sendToNet(MSG_SEND_PLAYER_INFO_REQUEST, name); }
+	public void sendChat(String s) { sendToNet(MSG_SEND_CHAT, s); }
+	public void sendTeamChat(String s) { sendToNet(MSG_SEND_TEAMCHAT, s); }
+	public void sendFollowPlayer(String nick) { sendToNet(MSG_SEND_FOLLOW_PLAYER, nick); }
+	public void sendJoinRoom(String name) { sendToNet(MSG_SEND_JOIN_ROOM, name); }
+	public void sendCreateRoom(String name) { sendToNet(MSG_SEND_CREATE_ROOM, name); }
+	public void sendLeaveRoom(String message) { sendToNet(MSG_SEND_LEAVE_ROOM, message); }
+	public void sendKick(String player) { sendToNet(MSG_SEND_KICK, player); }
 	public void sendAddTeam(Team newTeam) {
 		roomRequestedTeams.put(newTeam.name, newTeam);
-		sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_ADD_TEAM, newTeam);
+		sendToNet(MSG_SEND_ADD_TEAM, newTeam);
 	}
-	public void sendRemoveTeam(String teamName) { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_REMOVE_TEAM, teamName); }
-	public void sendTeamColorIndex(String teamName, int colorIndex) { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_TEAM_COLOR_INDEX, colorIndex, teamName); }
-	public void sendTeamHogCount(String teamName, int hogCount) { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_TEAM_HOG_COUNT, hogCount, teamName); }
+	public void sendRemoveTeam(String teamName) { sendToNet(MSG_SEND_REMOVE_TEAM, teamName); }
+	public void sendTeamColorIndex(String teamName, int colorIndex) { sendToNet(MSG_SEND_TEAM_COLOR_INDEX, colorIndex, teamName); }
+	public void sendTeamHogCount(String teamName, int hogCount) { sendToNet(MSG_SEND_TEAM_HOG_COUNT, hogCount, teamName); }
+	public void sendEngineMessage(byte[] engineMessage) { sendToNet(MSG_SEND_ENGINE_MESSAGE, engineMessage); }
+	public void sendRoundFinished(boolean withoutError) { sendToNet(MSG_SEND_ROUND_FINISHED, Boolean.valueOf(withoutError)); }
+	public void sendToggleReady() { sendToNet(MSG_SEND_TOGGLE_READY); }
 	
-	public void disconnect() { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_DISCONNECT, "User Quit"); }
+	public void disconnect() { sendToNet(MSG_DISCONNECT, "User Quit"); }
 	
 	private static Netplay instance;
 	
@@ -179,51 +221,29 @@
 	}
 	
 	public boolean isChief() {
-		return chief;
+		if(netRoomState != null) {
+			return netRoomState.chief;
+		} else {
+			return false;
+		}
 	}
 	
 	public String getPlayerName() {
 		return playerName;
 	}
 	
-	/**
-	 * Indicate that you want network messages to be checked regularly (several times per second).
-	 * As long as nobody requests fast ticks, the network is only checked once every few seconds
-	 * to conserve battery power.
-	 * Once you no longer need fast updates, call unrequestFastTicks.
-	 */
-	public void requestFastTicks() {
-		if(foregroundUsers == Integer.MAX_VALUE) {
-			throw new RuntimeException("Reference counter overflow");
-		}
-		if(foregroundUsers == 0 && connection != null) {
-			connection.setFastTickRate(true);
-		}
-		foregroundUsers++;
-	}
-	
-	public void unrequestFastTicks() {
-		if(foregroundUsers == 0) {
-			throw new RuntimeException("Reference counter underflow");
-		}
-		foregroundUsers--;
-		if(foregroundUsers == 0 && connection != null) {
-			connection.setFastTickRate(false);
-		}
-	}
-	
-	private boolean sendToNet(int what) {
+	boolean sendToNet(ToNetMsgType what) {
 		return sendToNet(what, 0, null);
 	}
 	
-	private boolean sendToNet(int what, Object obj) {
+	boolean sendToNet(ToNetMsgType what, Object obj) {
 		return sendToNet(what, 0, obj);
 	}
 	
-	private boolean sendToNet(int what, int arg1, Object obj) {
+	boolean sendToNet(ToNetMsgType what, int arg1, Object obj) {
 		if(connection != null) {
 			Handler handler = connection.toNetHandler;
-			return handler.sendMessage(handler.obtainMessage(what, arg1, 0, obj));
+			return handler.sendMessage(handler.obtainMessage(what.ordinal(), arg1, 0, obj));
 		} else {
 			return false;
 		}
@@ -237,33 +257,44 @@
 		}
 	}
 	
+	public static enum FromNetMsgType {
+		MSG_LOBBY_JOIN,
+		MSG_LOBBY_LEAVE,
+		MSG_ROOM_JOIN,
+		MSG_ROOM_LEAVE,
+		MSG_CHAT,
+		MSG_MESSAGE,
+		MSG_ROOM_ADD,
+		MSG_ROOM_UPDATE,
+		MSG_ROOM_DELETE,
+		MSG_ROOMLIST,
+		MSG_CONNECTED,
+		MSG_DISCONNECTED,
+		MSG_PASSWORD_REQUEST,
+		MSG_ENTER_ROOM_FROM_LOBBY,
+		MSG_LEAVE_ROOM,
+		MSG_READYSTATE,
+		MSG_TEAM_ADDED,
+		MSG_TEAM_DELETED,
+		MSG_TEAM_ACCEPTED,
+		MSG_TEAM_COLOR_CHANGED,
+		MSG_HOG_COUNT_CHANGED,
+		MSG_ENGINE_MESSAGE,
+		MSG_RUN_GAME,
+		MSG_SCHEME_CHANGED,
+		MSG_MAP_CHANGED,
+		MSG_ROOM_CHIEF_STATUS_CHANGED,
+		MSG_SCRIPT_CHANGED,
+		MSG_WEAPONSET_CHANGED;
+		
+		static final List<FromNetMsgType> values = Collections.unmodifiableList(Arrays.asList(FromNetMsgType.values()));
+	}
+	
 	/**
 	 * Processes messages from the networking system. Always runs on the main thread.
 	 */
 	@SuppressLint("HandlerLeak")
 	final class FromNetHandler extends Handler {
-		public static final int MSG_LOBBY_JOIN = 0;
-		public static final int MSG_LOBBY_LEAVE = 1;
-		public static final int MSG_ROOM_JOIN = 2;
-		public static final int MSG_ROOM_LEAVE = 3;
-		public static final int MSG_CHAT = 4;
-		public static final int MSG_MESSAGE = 5;
-		public static final int MSG_ROOM_ADD = 6;
-		public static final int MSG_ROOM_UPDATE = 7;
-		public static final int MSG_ROOM_DELETE = 8;
-		public static final int MSG_ROOMLIST = 9;
-		public static final int MSG_CONNECTED = 10;
-		public static final int MSG_DISCONNECTED = 11;
-		public static final int MSG_PASSWORD_REQUEST = 12;
-		public static final int MSG_ENTER_ROOM_FROM_LOBBY = 13;
-		public static final int MSG_LEAVE_ROOM = 14;
-		public static final int MSG_READYSTATE = 15;
-		public static final int MSG_TEAM_ADDED = 16;
-		public static final int MSG_TEAM_DELETED = 17;
-		public static final int MSG_TEAM_ACCEPTED = 18;
-		public static final int MSG_TEAM_COLOR_CHANGED = 19;
-		public static final int MSG_HOG_COUNT_CHANGED = 20;
-		
 		public FromNetHandler() {
 			super(Looper.getMainLooper());
 		}
@@ -271,10 +302,10 @@
 		@SuppressWarnings("unchecked")
 		@Override
 		public void handleMessage(Message msg) {
-			switch(msg.what) {
+			switch(FromNetMsgType.values.get(msg.what)) {
 			case MSG_LOBBY_JOIN: {
 				String name = (String)msg.obj;
-				lobbyPlayerlist.addPlayerWithNewId(name);
+				lobbyPlayerlist.put(name, new Player(name, false, false));
 				lobbyChatlog.appendPlayerJoin(name);
 				break;
 			}
@@ -286,7 +317,12 @@
 			}
 			case MSG_ROOM_JOIN: {
 				String name = (String)msg.obj;
-				roomPlayerlist.addPlayerWithNewId(name);
+				Player p = lobbyPlayerlist.get(name);
+				if(p==null) {
+					Log.w("Netplay", "Unknown player joined room: "+name);
+					p = new Player(name, false, false);
+				}
+				roomPlayerlist.put(name, new PlayerInRoom(p, false));
 				roomChatlog.appendPlayerJoin(name);
 				break;
 			}
@@ -299,18 +335,25 @@
 			case MSG_CHAT: {
 				Pair<String, String> args = (Pair<String, String>)msg.obj;
 				getCurrentLog().appendChat(args.first, args.second);
+				for(GameMessageListener listener : gameMessageListeners) {
+					listener.onChatMessage(args.first, args.second);
+				}
 				break;
 			}
 			case MSG_MESSAGE: {
 				getCurrentLog().appendMessage(msg.arg1, (String)msg.obj);
+				for(GameMessageListener listener : gameMessageListeners) {
+					listener.onMessage(1, (String)msg.obj);
+				}
 				break;
 			}
 			case MSG_ROOM_ADD: {
-				roomList.addRoomWithNewId((RoomlistRoom)msg.obj);
+				Room room = (Room)msg.obj;
+				roomList.addRoomWithNewId(room);
 				break;
 			}
 			case MSG_ROOM_UPDATE: {
-				Pair<String, RoomlistRoom> args = (Pair<String, RoomlistRoom>)msg.obj;
+				Pair<String, Room> args = (Pair<String, Room>)msg.obj;
 				roomList.updateRoom(args.first, args.second);
 				break;
 			}
@@ -319,7 +362,8 @@
 				break;
 			}
 			case MSG_ROOMLIST: {
-				roomList.updateList((RoomlistRoom[])msg.obj);
+				Room[] rooms = (Room[])msg.obj;
+				roomList.updateList(rooms);
 				break;
 			}
 			case MSG_CONNECTED: {
@@ -330,6 +374,9 @@
 			}
 			case MSG_DISCONNECTED: {
 				Pair<Boolean, String> args = (Pair<Boolean, String>)msg.obj;
+				for(GameMessageListener listener : gameMessageListeners) {
+					listener.onNetDisconnected();
+				}
 				changeState(State.NOT_CONNECTED);
 				connection = null;
 				Intent intent = new Intent(ACTION_DISCONNECTED);
@@ -345,12 +392,8 @@
 				break;
 			}
 			case MSG_ENTER_ROOM_FROM_LOBBY: {
-				roomChatlog.clear();
-				roomPlayerlist.clear();
-				roomTeamlist.clear();
-				roomRequestedTeams.clear();
+				initRoomState((Boolean)msg.obj);
 				changeState(State.ROOM);
-				chief = (Boolean)msg.obj;
 				Intent intent = new Intent(ACTION_ENTERED_ROOM_FROM_LOBBY);
 				broadcastManager.sendBroadcastSync(intent);
 				break;
@@ -365,15 +408,23 @@
 			}
 			case MSG_READYSTATE: {
 				Pair<String, Boolean> args = (Pair<String, Boolean>)msg.obj;
-				roomPlayerlist.setReady(args.first, args.second);
+				String name = args.first;
+				Boolean newReadyState = args.second;
+				PlayerInRoom oldEntry = roomPlayerlist.get(name);
+				if(oldEntry==null) {
+					Log.e("Netplay", "Setting readystate for unknown player "+name);
+				} else {
+					roomPlayerlist.put(name, new PlayerInRoom(oldEntry.player, newReadyState));
+				}
 				break;
 			}
 			case MSG_TEAM_ADDED: {
 				Team newTeam = (Team)msg.obj;
-				TeamIngameAttributes attrs = new TeamIngameAttributes(playerName, roomTeamlist.getUnusedOrRandomColorIndex(), TeamIngameAttributes.DEFAULT_HOG_COUNT, false);
+				int colorIndex = TeamInGame.getUnusedOrRandomColorIndex(roomTeamlist.getMap().values());
+				TeamIngameAttributes attrs = new TeamIngameAttributes(playerName, colorIndex, TeamIngameAttributes.DEFAULT_HOG_COUNT, true);
 				TeamInGame tig = new TeamInGame(newTeam, attrs);
-				roomTeamlist.addTeamWithNewId(tig);
-				if(chief) {
+				roomTeamlist.put(newTeam.name, tig);
+				if(isChief()) {
 					sendTeamColorIndex(newTeam.name, attrs.colorIndex);
 					sendTeamHogCount(newTeam.name, attrs.hogCount);
 				}
@@ -386,10 +437,11 @@
 			case MSG_TEAM_ACCEPTED: {
 				Team requestedTeam = roomRequestedTeams.remove(msg.obj);
 				if(requestedTeam!=null) {
-					TeamIngameAttributes attrs = new TeamIngameAttributes(playerName, roomTeamlist.getUnusedOrRandomColorIndex(), TeamIngameAttributes.DEFAULT_HOG_COUNT, false);
+					int colorIndex = TeamInGame.getUnusedOrRandomColorIndex(roomTeamlist.getMap().values());
+					TeamIngameAttributes attrs = new TeamIngameAttributes(playerName, colorIndex, TeamIngameAttributes.DEFAULT_HOG_COUNT, false);
 					TeamInGame tig = new TeamInGame(requestedTeam, attrs);
-					roomTeamlist.addTeamWithNewId(tig);
-					if(chief) {
+					roomTeamlist.put(requestedTeam.name, tig);
+					if(isChief()) {
 						sendTeamColorIndex(requestedTeam.name, attrs.colorIndex);
 						sendTeamHogCount(requestedTeam.name, attrs.hogCount);
 					}
@@ -399,27 +451,59 @@
 				break;
 			}
 			case MSG_TEAM_COLOR_CHANGED: {
-				Pair<TeamInGame, Long> oldEntry = roomTeamlist.get((String)msg.obj);
+				TeamInGame oldEntry = roomTeamlist.get((String)msg.obj);
 				if(oldEntry != null) {
-					TeamInGame tig = oldEntry.first;
-					TeamIngameAttributes tiga = tig.ingameAttribs.withColorIndex(msg.arg1);
-					roomTeamlist.put(tig.team.name, Pair.create(tig.withAttribs(tiga), oldEntry.second));
+					TeamIngameAttributes newAttribs = oldEntry.ingameAttribs.withColorIndex(msg.arg1);
+					roomTeamlist.put(oldEntry.team.name, oldEntry.withAttribs(newAttribs));
 				} else {
 					Log.e("Netplay", "Color update for unknown team "+msg.obj);
 				}
 				break;
 			}
 			case MSG_HOG_COUNT_CHANGED: {
-				Pair<TeamInGame, Long> oldEntry = roomTeamlist.get((String)msg.obj);
+				TeamInGame oldEntry = roomTeamlist.get((String)msg.obj);
 				if(oldEntry != null) {
-					TeamInGame tig = oldEntry.first;
-					TeamIngameAttributes tiga = tig.ingameAttribs.withHogCount(msg.arg1);
-					roomTeamlist.put(tig.team.name, Pair.create(tig.withAttribs(tiga), oldEntry.second));
+					TeamIngameAttributes newAttribs = oldEntry.ingameAttribs.withHogCount(msg.arg1);
+					roomTeamlist.put(oldEntry.team.name, oldEntry.withAttribs(newAttribs));
 				} else {
 					Log.e("Netplay", "Hog count update for unknown team "+msg.obj);
 				}
 				break;
 			}
+			case MSG_ENGINE_MESSAGE: {
+				byte[] em = (byte[])msg.obj;
+				for(GameMessageListener listener : gameMessageListeners) {
+					listener.onEngineMessage(em);
+				}
+				break;
+			}
+			case MSG_RUN_GAME: {
+				GameConfig config = (GameConfig)msg.obj;
+				for(RunGameListener listener : runGameListeners) {
+					listener.runGame(config);
+				}
+				break;
+			}
+			case MSG_MAP_CHANGED: {
+				netRoomState.setMapRecipe((MapRecipe)msg.obj);
+				break;
+			}
+			case MSG_ROOM_CHIEF_STATUS_CHANGED: {
+				netRoomState.setChief((Boolean)msg.obj);
+				break;
+			}
+			case MSG_SCHEME_CHANGED: {
+				netRoomState.setScheme((Scheme)msg.obj);
+				break;
+			}
+			case MSG_SCRIPT_CHANGED: {
+				netRoomState.setGameStyle((String)msg.obj);
+				break;
+			}
+			case MSG_WEAPONSET_CHANGED: {
+				netRoomState.setWeaponset((Weaponset)msg.obj);
+				break;
+			}
 			default: {
 				Log.e("FromNetHandler", "Unknown message type: "+msg.what);
 				break;
@@ -427,376 +511,4 @@
 			}
 		}
 	}
-	
-	/**
-	 * This class handles the actual communication with the networking library, on a separate thread.
-	 */
-	private static class ThreadedNetConnection {
-		private static final long TICK_INTERVAL_FAST = 100;
-		private static final long TICK_INTERVAL_SLOW = 5000;
-		private static final Frontlib FLIB = Flib.INSTANCE;
-		
-		public final ToNetHandler toNetHandler;
-		
-		private final Context appContext;
-		private final FromNetHandler fromNetHandler;
-		private final TickHandler tickHandler;
-
-		/**
-		 * conn can only be null while connecting (the first thing in the thread), and directly after disconnecting,
-		 * in the same message (the looper is shut down on disconnect, so there will be no messages after that).
-		 */
-		private NetconnPtr conn;
-		private String playerName;
-		
-		private ThreadedNetConnection(Context appContext, FromNetHandler fromNetHandler) {
-			this.appContext = appContext;
-			this.fromNetHandler = fromNetHandler;
-			
-			HandlerThread thread = new HandlerThread("NetThread");
-			thread.start();
-			toNetHandler = new ToNetHandler(thread.getLooper());
-			tickHandler = new TickHandler(thread.getLooper(), TICK_INTERVAL_FAST, tickCb);
-		}
-		
-		private void connect(final String name, final String host, final int port) {
-			toNetHandler.post(new Runnable() {
-				public void run() {
-					playerName = name == null ? "Player" : name;
-					MetaschemePtr meta = null;
-					File dataPath;
-					try {
-						dataPath = Utils.getDataPathFile(appContext);
-					} catch (FileNotFoundException e) {
-						shutdown(true, appContext.getString(R.string.sdcard_not_mounted));
-						return;
-					}
-					String metaschemePath = new File(dataPath, "metasettings.ini").getAbsolutePath();
-					meta = FLIB.flib_metascheme_from_ini(metaschemePath);
-					if(meta == null) {
-						shutdown(true, appContext.getString(R.string.error_unexpected, "Missing metasettings.ini"));
-						return;
-					}
-					conn = FLIB.flib_netconn_create(playerName, meta, dataPath.getAbsolutePath(), host, port);
-					if(conn == null) {
-						shutdown(true, appContext.getString(R.string.error_connection_failed));
-						return;
-					}
-					FLIB.flib_netconn_onLobbyJoin(conn, lobbyJoinCb, null);
-					FLIB.flib_netconn_onLobbyLeave(conn, lobbyLeaveCb, null);
-					FLIB.flib_netconn_onRoomJoin(conn, roomJoinCb, null);
-					FLIB.flib_netconn_onRoomLeave(conn, roomLeaveCb, null);
-					FLIB.flib_netconn_onChat(conn, chatCb, null);
-					FLIB.flib_netconn_onMessage(conn, messageCb, null);
-					FLIB.flib_netconn_onRoomAdd(conn, roomAddCb, null);
-					FLIB.flib_netconn_onRoomUpdate(conn, roomUpdateCb, null);
-					FLIB.flib_netconn_onRoomDelete(conn, roomDeleteCb, null);
-					FLIB.flib_netconn_onConnected(conn, connectedCb, null);
-					FLIB.flib_netconn_onRoomlist(conn, roomlistCb, null);
-					FLIB.flib_netconn_onDisconnected(conn, disconnectCb, null);
-					FLIB.flib_netconn_onPasswordRequest(conn, passwordRequestCb, null);
-					FLIB.flib_netconn_onEnterRoom(conn, enterRoomCb, null);
-					FLIB.flib_netconn_onLeaveRoom(conn, leaveRoomCb, null);
-					FLIB.flib_netconn_onReadyState(conn, readyStateCb, null);
-					FLIB.flib_netconn_onTeamAdd(conn, teamAddedCb, null);
-					FLIB.flib_netconn_onTeamDelete(conn, teamDeletedCb, null);
-					FLIB.flib_netconn_onTeamAccepted(conn, teamAcceptedCb, null);
-					FLIB.flib_netconn_onTeamColorChanged(conn, teamColorChangedCb, null);
-					FLIB.flib_netconn_onHogCountChanged(conn, hogCountChangedCb, null);
-					
-					FLIB.flib_metascheme_release(meta);
-					tickHandler.start();
-				}
-			});
-		}
-		
-		public static ThreadedNetConnection startConnection(Context appContext, FromNetHandler fromNetHandler, String playerName, String host, int port) {
-			ThreadedNetConnection result = new ThreadedNetConnection(appContext, fromNetHandler);
-			result.connect(playerName, host, port);
-			return result;
-		}
-		
-		public void setFastTickRate(boolean fastTickRate) {
-			tickHandler.setInterval(fastTickRate ? TICK_INTERVAL_FAST : TICK_INTERVAL_SLOW);
-		}
-		
-		private final Runnable tickCb = new Runnable() {
-			public void run() {
-				FLIB.flib_netconn_tick(conn);
-			}
-		};
-		
-		private final StrCallback lobbyJoinCb = new StrCallback() {
-			public void callback(Pointer context, String name) {
-				sendFromNet(FromNetHandler.MSG_LOBBY_JOIN, name);
-			}
-		};
-		
-		private final StrStrCallback lobbyLeaveCb = new StrStrCallback() {
-			public void callback(Pointer context, String name, String msg) {
-				sendFromNet(FromNetHandler.MSG_LOBBY_LEAVE, Pair.create(name, msg));
-			}
-		};
-		
-		private final StrCallback roomJoinCb = new StrCallback() {
-			public void callback(Pointer context, String name) {
-				sendFromNet(FromNetHandler.MSG_ROOM_JOIN, name);
-			}
-		};
-		private final StrStrCallback roomLeaveCb = new StrStrCallback() {
-			public void callback(Pointer context, String name, String message) {
-				sendFromNet(FromNetHandler.MSG_ROOM_LEAVE, Pair.create(name, message));
-			}
-		};
-		private final StrStrCallback chatCb = new StrStrCallback() {
-			public void callback(Pointer context, String name, String msg) {
-				sendFromNet(FromNetHandler.MSG_CHAT, Pair.create(name, msg));
-			}
-		};
-		
-		private final IntStrCallback messageCb = new IntStrCallback() {
-			public void callback(Pointer context, int type, String msg) {
-				sendFromNet(FromNetHandler.MSG_MESSAGE, type, msg);
-			}
-		};
-		
-		private final RoomCallback roomAddCb = new RoomCallback() {
-			public void callback(Pointer context, RoomPtr roomPtr) {
-				sendFromNet(FromNetHandler.MSG_ROOM_ADD, roomPtr.deref());
-			}
-		};
-		
-		private final StrRoomCallback roomUpdateCb = new StrRoomCallback() {
-			public void callback(Pointer context, String name, RoomPtr roomPtr) {
-				sendFromNet(FromNetHandler.MSG_ROOM_UPDATE, Pair.create(name, roomPtr.deref()));
-			}
-		};
-		
-		private final StrCallback roomDeleteCb = new StrCallback() {
-			public void callback(Pointer context, final String name) {
-				sendFromNet(FromNetHandler.MSG_ROOM_DELETE, name);
-			}
-		};
-		
-		private final RoomListCallback roomlistCb = new RoomListCallback() {
-			public void callback(Pointer context, RoomArrayPtr arg1, int count) {
-				sendFromNet(FromNetHandler.MSG_ROOMLIST, arg1.getRooms(count));
-			}
-		};
-		
-		private final VoidCallback connectedCb = new VoidCallback() {
-			public void callback(Pointer context) {
-				FLIB.flib_netconn_send_request_roomlist(conn);
-				playerName = FLIB.flib_netconn_get_playername(conn);
-				sendFromNet(FromNetHandler.MSG_CONNECTED, playerName);
-			}
-		};
-		
-		private final StrCallback passwordRequestCb = new StrCallback() {
-			public void callback(Pointer context, String nickname) {
-				sendFromNet(FromNetHandler.MSG_PASSWORD_REQUEST, playerName);
-			}
-		};
-		
-		private final BoolCallback enterRoomCb = new BoolCallback() {
-			public void callback(Pointer context, boolean isChief) {
-				sendFromNet(FromNetHandler.MSG_ENTER_ROOM_FROM_LOBBY, isChief);
-			}
-		};
-		
-		private final IntStrCallback leaveRoomCb = new IntStrCallback() {
-			public void callback(Pointer context, int reason, String message) {
-				sendFromNet(FromNetHandler.MSG_LEAVE_ROOM, reason, message);
-			}
-		};
-		
-		private final StrBoolCallback readyStateCb = new StrBoolCallback() {
-			public void callback(Pointer context, String player, boolean ready) {
-				sendFromNet(FromNetHandler.MSG_READYSTATE, Pair.create(player, ready));
-			}
-		};
-		
-		private final TeamCallback teamAddedCb = new TeamCallback() {
-			public void callback(Pointer context, TeamPtr team) {
-				sendFromNet(FromNetHandler.MSG_TEAM_ADDED, team.deref().team);
-			}
-		};
-		
-		private final StrCallback teamDeletedCb = new StrCallback() {
-			public void callback(Pointer context, String teamName) {
-				sendFromNet(FromNetHandler.MSG_TEAM_DELETED, teamName);
-			}
-		};
-		
-		private final StrCallback teamAcceptedCb = new StrCallback() {
-			public void callback(Pointer context, String teamName) {
-				sendFromNet(FromNetHandler.MSG_TEAM_ACCEPTED, teamName);
-			}
-		};
-		
-		private final StrIntCallback teamColorChangedCb = new StrIntCallback() {
-			public void callback(Pointer context, String teamName, int colorIndex) {
-				sendFromNet(FromNetHandler.MSG_TEAM_COLOR_CHANGED, colorIndex, teamName);
-			}
-		};
-		
-		private final StrIntCallback hogCountChangedCb = new StrIntCallback() {
-			public void callback(Pointer context, String teamName, int hogCount) {
-				sendFromNet(FromNetHandler.MSG_HOG_COUNT_CHANGED, hogCount, teamName);
-			}
-		};
-		
-		private void shutdown(boolean error, String message) {
-			if(conn != null) {
-				FLIB.flib_netconn_destroy(conn);
-				conn = null;
-			}
-			tickHandler.stop();
-			toNetHandler.getLooper().quit();
-			sendFromNet(FromNetHandler.MSG_DISCONNECTED, Pair.create(error, message));
-		}
-		
-		private final IntStrCallback disconnectCb = new IntStrCallback() {
-			public void callback(Pointer context, int reason, String message) {
-				Boolean error = reason != Frontlib.NETCONN_DISCONNECT_NORMAL;
-				String messageForUser = createDisconnectUserMessage(appContext.getResources(), reason, message);
-				shutdown(error, messageForUser);
-			}
-		};
-		
-		private static String createDisconnectUserMessage(Resources res, int reason, String message) {
-			switch(reason) {
-			case Frontlib.NETCONN_DISCONNECT_AUTH_FAILED:
-				return res.getString(R.string.error_auth_failed);
-			case Frontlib.NETCONN_DISCONNECT_CONNLOST:
-				return res.getString(R.string.error_connection_lost);
-			case Frontlib.NETCONN_DISCONNECT_INTERNAL_ERROR:
-				return res.getString(R.string.error_unexpected, message);
-			case Frontlib.NETCONN_DISCONNECT_SERVER_TOO_OLD:
-				return res.getString(R.string.error_server_too_old);
-			default:
-				return message;
-			}
-		}
-		
-		private boolean sendFromNet(int what, Object obj) {
-			return fromNetHandler.sendMessage(fromNetHandler.obtainMessage(what, obj));
-		}
-		
-		private boolean sendFromNet(int what, int arg1, Object obj) {
-			return fromNetHandler.sendMessage(fromNetHandler.obtainMessage(what, arg1, 0, obj));
-		}
-		
-		/**
-		 * Processes messages to the networking system. Runs on a non-main thread.
-		 */
-		@SuppressLint("HandlerLeak")
-		public final class ToNetHandler extends Handler {
-			public static final int MSG_SEND_NICK = 0;
-			public static final int MSG_SEND_PASSWORD = 1;
-			public static final int MSG_SEND_QUIT = 2;
-			public static final int MSG_SEND_ROOMLIST_REQUEST = 3;
-			public static final int MSG_SEND_PLAYER_INFO_REQUEST = 4;
-			public static final int MSG_SEND_CHAT = 5;
-			public static final int MSG_SEND_FOLLOW_PLAYER = 6;
-			public static final int MSG_SEND_JOIN_ROOM = 7;
-			public static final int MSG_SEND_CREATE_ROOM = 8;
-			public static final int MSG_SEND_LEAVE_ROOM = 9;
-			public static final int MSG_SEND_KICK = 10;
-			public static final int MSG_SEND_ADD_TEAM = 11;
-			public static final int MSG_SEND_REMOVE_TEAM = 12;
-			public static final int MSG_DISCONNECT = 13;
-			public static final int MSG_SEND_TEAM_COLOR_INDEX = 14;
-			public static final int MSG_SEND_TEAM_HOG_COUNT = 15;
-			
-			public ToNetHandler(Looper looper) {
-				super(looper);
-			}
-			
-			@Override
-			public void handleMessage(Message msg) {
-				switch(msg.what) {
-				case MSG_SEND_NICK: {
-					FLIB.flib_netconn_send_nick(conn, (String)msg.obj);
-					break;
-				}
-				case MSG_SEND_PASSWORD: {
-					FLIB.flib_netconn_send_password(conn, (String)msg.obj);
-					break;
-				}
-				case MSG_SEND_QUIT: {
-					FLIB.flib_netconn_send_quit(conn, (String)msg.obj);
-					break;
-				}
-				case MSG_SEND_ROOMLIST_REQUEST: {
-					FLIB.flib_netconn_send_request_roomlist(conn);
-					break;
-				}
-				case MSG_SEND_PLAYER_INFO_REQUEST: {
-					FLIB.flib_netconn_send_playerInfo(conn, (String)msg.obj);
-					break;
-				}
-				case MSG_SEND_CHAT: {
-					if(FLIB.flib_netconn_send_chat(conn, (String)msg.obj) == 0) {
-						sendFromNet(FromNetHandler.MSG_CHAT, Pair.create(playerName, (String)msg.obj));
-					}
-					break;
-				}
-				case MSG_SEND_FOLLOW_PLAYER: {
-					FLIB.flib_netconn_send_playerFollow(conn, (String)msg.obj);
-					break;
-				}
-				case MSG_SEND_JOIN_ROOM: {
-					FLIB.flib_netconn_send_joinRoom(conn, (String)msg.obj);
-					break;
-				}
-				case MSG_SEND_CREATE_ROOM: {
-					FLIB.flib_netconn_send_createRoom(conn, (String)msg.obj);
-					break;
-				}
-				case MSG_SEND_LEAVE_ROOM: {
-					if(FLIB.flib_netconn_send_leaveRoom(conn, (String)msg.obj) == 0) {
-						sendFromNet(FromNetHandler.MSG_LEAVE_ROOM, -1, "");
-					}
-					break;
-				}
-				case MSG_SEND_KICK: {
-					FLIB.flib_netconn_send_kick(conn, (String)msg.obj);
-					break;
-				}
-				case MSG_SEND_ADD_TEAM: {
-					FLIB.flib_netconn_send_addTeam(conn, TeamPtr.createJavaOwned((Team)msg.obj));
-					break;
-				}
-				case MSG_SEND_REMOVE_TEAM: {
-					if(FLIB.flib_netconn_send_removeTeam(conn, (String)msg.obj)==0) {
-						sendFromNet(FromNetHandler.MSG_TEAM_DELETED, msg.obj);
-					}
-					break;
-				}
-				case MSG_DISCONNECT: {
-					FLIB.flib_netconn_send_quit(conn, (String)msg.obj);
-					shutdown(false, "User quit");
-					break;
-				}
-				case MSG_SEND_TEAM_COLOR_INDEX: {
-					if(FLIB.flib_netconn_send_teamColor(conn, (String)msg.obj, msg.arg1)==0) {
-						sendFromNet(FromNetHandler.MSG_TEAM_COLOR_CHANGED, msg.arg1, msg.obj);
-					}
-					break;
-				}
-				case MSG_SEND_TEAM_HOG_COUNT: {
-					if(FLIB.flib_netconn_send_teamHogCount(conn, (String)msg.obj, msg.arg1)==0) {
-						sendFromNet(FromNetHandler.MSG_HOG_COUNT_CHANGED, msg.arg1, msg.obj);
-					}
-					break;
-				}
-				default: {
-					Log.e("ToNetHandler", "Unknown message type: "+msg.what);
-					break;
-				}
-				}
-			}
-		}
-	}
 }
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/NetplayStateFragment.java	Sat Aug 18 00:22:33 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,121 +0,0 @@
-package org.hedgewars.hedgeroid.netplay;
-
-import org.hedgewars.hedgeroid.R;
-import org.hedgewars.hedgeroid.frontlib.Frontlib;
-import org.hedgewars.hedgeroid.netplay.Netplay.State;
-
-import android.app.Activity;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.support.v4.app.Fragment;
-import android.support.v4.content.LocalBroadcastManager;
-import android.widget.Toast;
-
-/**
- * Fragment for use by an activity that depends on the state of the network
- * connection. The activity must implement the NetplayStateListener interface.
- * 
- * This fragment manages a few aspects of the netplay connection: Requesting
- * the network system loop to run at high frequency while the activity is in
- * the foreground, and reacting to changes in the networking state by calling
- * a callback method on the activity.
- */
-public class NetplayStateFragment extends Fragment {
-    private Netplay netplay;
-    private Context appContext;
-    private LocalBroadcastManager broadcastManager;
-    private NetplayStateListener listener;
-    private State knownState;
-    
-    interface NetplayStateListener {
-    	/**
-    	 * This is called while the activity is running, and every time during resume, if
-    	 * a change in the networking state is detected. It is also called once
-    	 * with the initial state (which could be called a change from the "unknown" state).
-    	 */
-    	void onNetplayStateChanged(State newState);
-    }
-    
-    @Override
-	public void onAttach(Activity activity) {
-		super.onAttach(activity);
-		try {
-			listener = (NetplayStateListener) activity;
-		} catch(ClassCastException e) {
-			throw new ClassCastException("Activity " + activity + " must implement NetplayStateListener to use NetplayStateFragment.");
-		}
-	}
-	
-	@Override
-	public void onDetach() {
-		super.onDetach();
-		listener = null;
-	}
-	
-    @Override
-    public void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-        appContext = getActivity().getApplicationContext();
-        broadcastManager = LocalBroadcastManager.getInstance(appContext);
-        netplay = Netplay.getAppInstance(appContext);
-    }    
-
-    @Override
-    public void onResume() {
-    	super.onResume();
-    	broadcastManager.registerReceiver(disconnectReceiver, new IntentFilter(Netplay.ACTION_DISCONNECTED));
-    	broadcastManager.registerReceiver(leaveRoomReceiver, new IntentFilter(Netplay.ACTION_LEFT_ROOM));
-    	broadcastManager.registerReceiver(stateChangeReceiver, new IntentFilter(Netplay.ACTION_STATE_CHANGED));
-    	netplay.requestFastTicks();
-    	
-    	State newState = netplay.getState();
-		if(knownState != newState) {
-    		listener.onNetplayStateChanged(newState);
-    		knownState = newState;
-    	}
-    }
-    
-    @Override
-    public void onPause() {
-    	super.onPause();
-    	broadcastManager.unregisterReceiver(disconnectReceiver);
-    	broadcastManager.unregisterReceiver(leaveRoomReceiver);
-    	broadcastManager.unregisterReceiver(stateChangeReceiver);
-    	netplay.unrequestFastTicks();
-    }
-
-	private final BroadcastReceiver disconnectReceiver = new BroadcastReceiver() {
-		@Override
-		public void onReceive(Context context, Intent intent) {
-			if(intent.getBooleanExtra(Netplay.EXTRA_HAS_ERROR, true)) {
-				String message = intent.getStringExtra(Netplay.EXTRA_MESSAGE);
-				String toastText = getString(R.string.toast_disconnected, message);
-				Toast.makeText(appContext, toastText, Toast.LENGTH_LONG).show();
-			}
-		}
-	};
-	
-	private final BroadcastReceiver leaveRoomReceiver = new BroadcastReceiver() {
-		@Override
-		public void onReceive(Context context, Intent intent) {
-			int reason = intent.getIntExtra(Netplay.EXTRA_REASON, -1);
-			if(reason == Frontlib.NETCONN_ROOMLEAVE_ABANDONED) {
-				Toast.makeText(appContext, R.string.toast_room_abandoned, Toast.LENGTH_LONG).show();
-			} else if(reason == Frontlib.NETCONN_ROOMLEAVE_KICKED) {
-				Toast.makeText(appContext, R.string.toast_kicked, Toast.LENGTH_LONG).show();
-			}
-		}
-	};
-	
-	private final BroadcastReceiver stateChangeReceiver = new BroadcastReceiver() {
-		@Override
-		public void onReceive(Context context, Intent intent) {
-			State newState = netplay.getState();
-			listener.onNetplayStateChanged(newState);
-			knownState = newState;
-		}
-	};
-}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/ObservableTreeMap.java	Sat Aug 18 00:22:33 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,43 +0,0 @@
-package org.hedgewars.hedgeroid.netplay;
-
-import java.util.Collections;
-import java.util.Map;
-import java.util.TreeMap;
-
-import android.database.DataSetObservable;
-
-public class ObservableTreeMap<K,V> extends DataSetObservable {
-	private final Map<K, V> map = new TreeMap<K, V>();
-	
-	public void replaceContent(Map<? extends K, ? extends V> newMap) {
-		map.clear();
-		map.putAll(newMap);
-		notifyChanged();
-	}
-	
-	public void put(K key, V value) {
-		map.put(key, value);
-		notifyChanged();
-	}
-	
-	public V get(K key) {
-		return map.get(key);
-	}
-	
-	public void remove(K key) {
-		if(map.remove(key) != null) {
-			notifyChanged();
-		}
-	}
-	
-	public void clear() {
-		if(!map.isEmpty()) {
-			map.clear();
-			notifyChanged();
-		}
-	}
-	
-	public Map<K, V> getMap() {
-		return Collections.unmodifiableMap(map);
-	}
-}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/ObservableTreeMapAdapter.java	Sat Aug 18 00:22:33 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +0,0 @@
-package org.hedgewars.hedgeroid.netplay;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-import android.database.DataSetObserver;
-import android.widget.BaseAdapter;
-
-public abstract class ObservableTreeMapAdapter<K,V> extends BaseAdapter {
-	private boolean sourceChanged = true;
-	private List<V> entries = new ArrayList<V>();
-	private ObservableTreeMap<K, V> source;
-	
-	private DataSetObserver observer = new DataSetObserver() {
-		@Override
-		public void onChanged() {
-			sourceChanged = true;
-			notifyDataSetChanged();
-		}
-		
-		@Override
-		public void onInvalidated() {
-			invalidate();
-		}
-	};
-	
-	abstract protected Comparator<V> getEntryOrder();
-	
-	protected List<V> getEntries() {
-		if(sourceChanged) {
-			entries.clear();
-			entries.addAll(source.getMap().values());
-			Collections.sort(entries, getEntryOrder());
-			sourceChanged = false;
-		}
-		return entries;
-	}
-	
-	public int getCount() {
-		return getEntries().size();
-	}
-
-	public void setSource(ObservableTreeMap<K,V> source) {
-		if(this.source != null) {
-			this.source.unregisterObserver(observer);
-		}
-		this.source = source;
-		this.source.registerObserver(observer);
-		sourceChanged = true;
-		notifyDataSetChanged();
-	}
-	
-	public void invalidate() {
-		if(source != null) {
-			source.unregisterObserver(observer);
-		}
-		source = null;
-		notifyDataSetInvalidated();
-	}
-}
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/OpenConnectionService.java	Sat Aug 18 00:22:33 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-package org.hedgewars.hedgeroid.netplay;
-
-import android.app.Service;
-import android.content.Intent;
-import android.os.IBinder;
-
-public class OpenConnectionService extends Service {
-	@Override
-	public IBinder onBind(Intent intent) {
-		return null;
-	}
-
-}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomActivity.java	Sat Aug 18 00:22:33 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,80 +0,0 @@
-package org.hedgewars.hedgeroid.netplay;
-
-import org.hedgewars.hedgeroid.R;
-import org.hedgewars.hedgeroid.Datastructures.Team;
-import org.hedgewars.hedgeroid.netplay.Netplay.State;
-import org.hedgewars.hedgeroid.netplay.NetplayStateFragment.NetplayStateListener;
-
-import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.FragmentTransaction;
-import android.widget.TabHost;
-import android.widget.Toast;
-
-public class RoomActivity extends FragmentActivity implements NetplayStateListener, TeamAddDialog.Listener {
-	private TabHost tabHost;
-	private Netplay netplay;
-	
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-        netplay = Netplay.getAppInstance(getApplicationContext());
-        
-        setContentView(R.layout.activity_netroom);
-        ChatFragment chatFragment = (ChatFragment)getSupportFragmentManager().findFragmentById(R.id.chatFragment);
-        chatFragment.setInRoom(true);
-        
-        FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
-        trans.add(new NetplayStateFragment(), "netplayFragment");
-        trans.commit();
-        
-        /*tabHost = (TabHost)findViewById(android.R.id.tabhost);
-        if(tabHost != null) {
-	        tabHost.setup();
-	        tabHost.getTabWidget().setOrientation(LinearLayout.VERTICAL);
-
-	        //tabHost.addTab(tabHost.newTabSpec("chat").setIndicator(createIndicatorView(tabHost, R.string.lobby_tab_chat, getResources().getDrawable(R.drawable.edit))).setContent(R.id.chatFragment));
-	        //tabHost.addTab(tabHost.newTabSpec("players").setIndicator(createIndicatorView(tabHost, R.string.lobby_tab_players, getResources().getDrawable(R.drawable.human))).setContent(R.id.playerListFragment));
-	
-	        if (icicle != null) {
-	            tabHost.setCurrentTabByTag(icicle.getString("currentTab"));
-	        }
-        }*/
-    }
-
-	@Override
-	public void onBackPressed() {
-		netplay.sendLeaveRoom(null);
-	}
-    
-    @Override
-    protected void onSaveInstanceState(Bundle icicle) {
-        super.onSaveInstanceState(icicle);
-        if(tabHost != null) {
-        	icicle.putString("currentTab", tabHost.getCurrentTabTag());
-        }
-    }
-    
-    public void onNetplayStateChanged(State newState) {
-    	switch(newState) {
-    	case NOT_CONNECTED:
-    	case CONNECTING:
-    	case LOBBY:
-    		finish();
-    		break;
-    	case ROOM:
-    		// Do nothing
-    		break;
-    	case INGAME:
-    		//startActivity(new Intent(getApplicationContext(), RoomActivity.class));
-    		Toast.makeText(getApplicationContext(), R.string.not_implemented_yet, Toast.LENGTH_SHORT).show();
-    		break;
-		default:
-			throw new IllegalStateException("Unknown connection state: "+newState);
-    	}
-    }
-    
-	public void onTeamAddDialogSubmitted(Team newTeam) {
-		netplay.sendAddTeam(newTeam);
-	}
-}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomPlayerlist.java	Sat Aug 18 00:22:33 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
-package org.hedgewars.hedgeroid.netplay;
-
-import org.hedgewars.hedgeroid.Datastructures.Player;
-import org.hedgewars.hedgeroid.netplay.RoomPlayerlist.PlayerInRoom;
-
-import android.util.Log;
-
-public class RoomPlayerlist extends ObservableTreeMap<String, PlayerInRoom> {
-	private long nextId = 1;
-	
-	public void addPlayerWithNewId(String name) {
-		put(name, new PlayerInRoom(new Player(name), nextId++, false));
-	}
-	
-	public void setReady(String name, boolean ready) {
-		PlayerInRoom oldEntry = get(name);
-		if(oldEntry==null) {
-			Log.e("RoomPlayerlist", "Setting readystate for unknown player "+name);
-		} else {
-			put(name, new PlayerInRoom(oldEntry.player, oldEntry.id, ready));
-		}
-	}
-	
-	// Immutable
-	public static class PlayerInRoom {
-		public final Player player;
-		public final long id;
-		public final boolean ready;
-		
-		public PlayerInRoom(Player player, long id, boolean ready) {
-			this.player = player;
-			this.id = id;
-			this.ready = ready;
-		}
-	}
-}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomPlayerlistAdapter.java	Sat Aug 18 00:22:33 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-package org.hedgewars.hedgeroid.netplay;
-
-import java.util.Comparator;
-
-import org.hedgewars.hedgeroid.R;
-import org.hedgewars.hedgeroid.netplay.RoomPlayerlist.PlayerInRoom;
-
-import android.content.Context;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-public class RoomPlayerlistAdapter extends ObservableTreeMapAdapter<String, PlayerInRoom> {
-	private Context context;
-	
-	public RoomPlayerlistAdapter(Context context) {
-		this.context = context;
-	}
-	
-	@Override
-	protected Comparator<PlayerInRoom> getEntryOrder() {
-		return AlphabeticalOrderComparator.INSTANCE;
-	}
-
-	public PlayerInRoom getItem(int position) {
-		return getEntries().get(position);
-	}
-
-	public long getItemId(int position) {
-		return getEntries().get(position).id;
-	}
-
-	public boolean hasStableIds() {
-		return true;
-	}
-	
-	public View getView(int position, View convertView, ViewGroup parent) {
-		View v = convertView;
-		if (v == null) {
-			LayoutInflater vi = LayoutInflater.from(context);
-			v = vi.inflate(R.layout.listview_player, null);
-		}
-
-		PlayerInRoom player = getItem(position);
-		TextView username = (TextView) v.findViewById(android.R.id.text1);
-		username.setText(player.player.name);
-		int readyDrawable = player.ready ? R.drawable.lightbulb_on : R.drawable.lightbulb_off;
-		username.setCompoundDrawablesWithIntrinsicBounds(readyDrawable, 0, 0, 0);
-		return v;
-	}
-	
-	private static final class AlphabeticalOrderComparator implements Comparator<PlayerInRoom> {
-		public static final AlphabeticalOrderComparator INSTANCE = new AlphabeticalOrderComparator();
-		public int compare(PlayerInRoom lhs, PlayerInRoom rhs) {
-			return lhs.player.name.compareToIgnoreCase(rhs.player.name);
-		};
-	}
-}
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomPlayerlistFragment.java	Sat Aug 18 00:22:33 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,79 +0,0 @@
-package org.hedgewars.hedgeroid.netplay;
-
-import org.hedgewars.hedgeroid.R;
-import org.hedgewars.hedgeroid.netplay.RoomPlayerlist.PlayerInRoom;
-
-import android.os.Bundle;
-import android.support.v4.app.ListFragment;
-import android.view.ContextMenu;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.LayoutInflater;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView.AdapterContextMenuInfo;
-
-public class RoomPlayerlistFragment extends ListFragment {
-	private Netplay netplay;
-	private RoomPlayerlistAdapter adapter;
-	
-	@Override
-	public void onCreate(Bundle savedInstanceState) {
-		super.onCreate(savedInstanceState);
-		netplay = Netplay.getAppInstance(getActivity().getApplicationContext());
-		adapter = new RoomPlayerlistAdapter(getActivity());
-		adapter.setSource(netplay.roomPlayerlist);
-		setListAdapter(adapter);
-	}
-
-	@Override
-	public void onDestroy() {
-		super.onDestroy();
-		adapter.invalidate();
-	}
-	
-	@Override
-	public void onActivityCreated(Bundle savedInstanceState) {
-		super.onActivityCreated(savedInstanceState);
-		registerForContextMenu(getListView());
-	}
-
-	@Override
-	public void onCreateContextMenu(ContextMenu menu, View v,
-			ContextMenuInfo menuInfo) {
-		super.onCreateContextMenu(menu, v, menuInfo);
-		AdapterContextMenuInfo info = (AdapterContextMenuInfo)menuInfo;
-		String playerName = adapter.getItem(info.position).player.name;
-		
-		MenuInflater inflater = getActivity().getMenuInflater();
-		inflater.inflate(R.menu.room_playerlist_context, menu);
-		if(netplay.isChief() && !playerName.equals(netplay.getPlayerName())) {
-			inflater.inflate(R.menu.room_playerlist_chief_context, menu);
-		}
-		menu.setHeaderIcon(R.drawable.human);
-		menu.setHeaderTitle(playerName);
-	}
-	
-	@Override
-	public boolean onContextItemSelected(MenuItem item) {
-		AdapterContextMenuInfo info = (AdapterContextMenuInfo)item.getMenuInfo();
-		PlayerInRoom player = adapter.getItem(info.position);
-		switch(item.getItemId()) {
-		case R.id.player_info:
-			netplay.sendPlayerInfoQuery(player.player.name);
-			return true;
-		case R.id.player_kick:
-			netplay.sendKick(player.player.name);
-			return true;
-		default:
-			return super.onContextItemSelected(item);
-		}
-	}
-	
-	@Override
-	public View onCreateView(LayoutInflater inflater, ViewGroup container,
-			Bundle savedInstanceState) {
-		return inflater.inflate(R.layout.fragment_playerlist, container, false);
-	}
-}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Roomlist.java	Sat Aug 18 00:22:33 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Roomlist.java	Sat Aug 18 00:47:51 2012 +0200
@@ -3,37 +3,37 @@
 import java.util.Map;
 import java.util.TreeMap;
 
-import org.hedgewars.hedgeroid.Datastructures.RoomlistRoom;
+import org.hedgewars.hedgeroid.Datastructures.Room;
+import org.hedgewars.hedgeroid.Datastructures.RoomWithId;
+import org.hedgewars.hedgeroid.util.ObservableTreeMap;
 
-import android.util.Pair;
-
-public class Roomlist extends ObservableTreeMap<String, Pair<RoomlistRoom, Long>> {
+public class Roomlist extends ObservableTreeMap<String, RoomWithId> {
 	private long nextId = 1;
 	
-	public void updateList(RoomlistRoom[] newRooms) {
-		Map<String, Pair<RoomlistRoom, Long>> newMap = new TreeMap<String, Pair<RoomlistRoom, Long>>();
-		for(RoomlistRoom room : newRooms) {
-			Pair<RoomlistRoom, Long> oldEntry = get(room.name);
+	public void updateList(Room[] newRooms) {
+		Map<String, RoomWithId> newMap = new TreeMap<String, RoomWithId>();
+		for(Room room : newRooms) {
+			RoomWithId oldEntry = get(room.name);
 			if(oldEntry == null) {
-				newMap.put(room.name, Pair.create(room, nextId++));
+				newMap.put(room.name, new RoomWithId(room, nextId++));
 			} else {
-				newMap.put(room.name, Pair.create(room, oldEntry.second));
+				newMap.put(room.name, new RoomWithId(room, oldEntry.id));
 			}
 		}
 		replaceContent(newMap);
 	}
 	
-	public void addRoomWithNewId(RoomlistRoom room) {
-		put(room.name, Pair.create(room, nextId++));
+	public void addRoomWithNewId(Room room) {
+		put(room.name, new RoomWithId(room, nextId++));
 	}
 	
-	public void updateRoom(String name, RoomlistRoom room) {
-		Pair<RoomlistRoom, Long> oldEntry = get(name);
+	public void updateRoom(String name, Room room) {
+		RoomWithId oldEntry = get(name);
 		if(oldEntry == null) {
 			addRoomWithNewId(room);
 		} else {
 			remove(name);
-			put(room.name, Pair.create(room, oldEntry.second));
+			put(room.name, new RoomWithId(room, oldEntry.id));
 		}
 	}
 }
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomlistAdapter.java	Sat Aug 18 00:22:33 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,99 +0,0 @@
-package org.hedgewars.hedgeroid.netplay;
-
-import java.util.Comparator;
-
-import org.hedgewars.hedgeroid.R;
-import org.hedgewars.hedgeroid.Datastructures.RoomlistRoom;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.util.Pair;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-public class RoomlistAdapter extends ObservableTreeMapAdapter<String, Pair<RoomlistRoom, Long>> {
-	private Context context;
-	
-	public RoomlistAdapter(Context context) {
-		this.context = context;
-	}
-	
-	@Override
-	protected Comparator<Pair<RoomlistRoom, Long>> getEntryOrder() {
-		return RoomAgeComparator.INSTANCE;
-	}
-	
-	public RoomlistRoom getItem(int position) {
-		return getEntries().get(position).first;
-	}
-
-	public long getItemId(int position) {
-		return getEntries().get(position).second;
-	}
-
-	public boolean hasStableIds() {
-		return true;
-	}
-	
-	private static CharSequence formatExtra(Resources res, RoomlistRoom room) {
-		String ownermsg = res.getString(R.string.roomlist_owner, room.owner);
-		String mapmsg = res.getString(R.string.roomlist_map, room.formatMapName(res));
-		String scheme = room.scheme.equals(room.weapons) ? room.scheme : room.scheme + " / " + room.weapons;
-		String schememsg = res.getString(R.string.roomlist_scheme, scheme);
-		return ownermsg + ". " + mapmsg + ", " + schememsg;
-	}
-	
-	public View getView(int position, View convertView, ViewGroup parent) {
-		View v = convertView;
-		if (v == null) {
-			LayoutInflater vi = LayoutInflater.from(context);
-			v = vi.inflate(R.layout.listview_room, null);
-		}
-		
-		RoomlistRoom room = getItem(position);
-		int iconRes = room.inProgress ? R.drawable.roomlist_ingame : R.drawable.roomlist_preparing;
-		
-		if(v.findViewById(android.R.id.text1) == null) {
-			// Tabular room list
-			TextView roomnameView = (TextView)v.findViewById(R.id.roomname);
-			TextView playerCountView = (TextView)v.findViewById(R.id.playercount);
-			TextView teamCountView = (TextView)v.findViewById(R.id.teamcount);
-			TextView ownerView = (TextView)v.findViewById(R.id.owner);
-			TextView mapView = (TextView)v.findViewById(R.id.map);
-			TextView schemeView = (TextView)v.findViewById(R.id.scheme);
-			TextView weaponView = (TextView)v.findViewById(R.id.weapons);
-			
-			roomnameView.setCompoundDrawablesWithIntrinsicBounds(iconRes, 0, 0, 0);
-			roomnameView.setText(room.name);
-			if(playerCountView != null) {
-				playerCountView.setText(String.valueOf(room.playerCount));
-			}
-			if(teamCountView != null) {
-				teamCountView.setText(String.valueOf(room.teamCount));
-			}
-			ownerView.setText(room.owner);
-			mapView.setText(room.formatMapName(context.getResources()));
-			schemeView.setText(room.scheme);
-			weaponView.setText(room.weapons);
-		} else {
-			// Small room list
-			TextView v1 = (TextView)v.findViewById(android.R.id.text1);
-			TextView v2 = (TextView)v.findViewById(android.R.id.text2);
-			
-			v1.setCompoundDrawablesWithIntrinsicBounds(iconRes, 0, 0, 0);
-			v1.setText(room.name);
-			v2.setText(formatExtra(context.getResources(), room));
-		}
-		
-		return v;
-	}
-	
-	private static final class RoomAgeComparator implements Comparator<Pair<RoomlistRoom, Long>> {
-		public static final RoomAgeComparator INSTANCE = new RoomAgeComparator();
-		public int compare(Pair<RoomlistRoom, Long> lhs, Pair<RoomlistRoom, Long> rhs) {
-			return rhs.second.compareTo(lhs.second);
-		}
-	}
-}
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomlistFragment.java	Sat Aug 18 00:22:33 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +0,0 @@
-package org.hedgewars.hedgeroid.netplay;
-
-import org.hedgewars.hedgeroid.R;
-
-import android.os.Bundle;
-import android.os.CountDownTimer;
-import android.support.v4.app.ListFragment;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-
-public class RoomlistFragment extends ListFragment implements OnItemClickListener {
-	private static final int AUTO_REFRESH_INTERVAL_MS = 15000;
-	
-	private Netplay netplay;
-	private RoomlistAdapter adapter;
-	private CountDownTimer autoRefreshTimer = new CountDownTimer(Long.MAX_VALUE, AUTO_REFRESH_INTERVAL_MS) {
-		@Override
-		public void onTick(long millisUntilFinished) {
-			netplay.sendRoomlistRequest();
-		}
-		
-		@Override
-		public void onFinish() { }
-	};
-
-	@Override
-	public void onCreate(Bundle savedInstanceState) {
-		super.onCreate(savedInstanceState);
-		netplay = Netplay.getAppInstance(getActivity().getApplicationContext());
-		adapter = new RoomlistAdapter(getActivity());
-		adapter.setSource(netplay.roomList);
-		setListAdapter(adapter);
-	}
-
-	@Override
-	public View onCreateView(LayoutInflater inflater, ViewGroup container,
-			Bundle savedInstanceState) {
-		return inflater.inflate(R.layout.lobby_rooms_fragment, container, false);
-	}
-	
-	@Override
-	public void onActivityCreated(Bundle savedInstanceState) {
-		super.onActivityCreated(savedInstanceState);
-		getListView().setOnItemClickListener(this);
-	}
-	
-	@Override
-	public void onResume() {
-		super.onResume();
-		autoRefreshTimer.start();
-	}
-	
-	@Override
-	public void onPause() {
-		super.onPause();
-		autoRefreshTimer.cancel();
-	}
-	
-	@Override
-	public void onDestroy() {
-		super.onDestroy();
-		adapter.invalidate();
-	}
-	
-	public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-		netplay.sendJoinRoom(adapter.getItem(position).name);
-	}
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RunGameListener.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,7 @@
+package org.hedgewars.hedgeroid.netplay;
+
+import org.hedgewars.hedgeroid.Datastructures.GameConfig;
+
+public interface RunGameListener {
+	void runGame(GameConfig config);
+}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/TeamAddDialog.java	Sat Aug 18 00:22:33 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,92 +0,0 @@
-package org.hedgewars.hedgeroid.netplay;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-import org.hedgewars.hedgeroid.R;
-import org.hedgewars.hedgeroid.Datastructures.FrontendDataUtils;
-import org.hedgewars.hedgeroid.Datastructures.Team;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
-import android.os.Bundle;
-import android.support.v4.app.DialogFragment;
-
-public class TeamAddDialog extends DialogFragment {
-	private static final String STATE_TEAMS_ALREADY_IN_GAME = "teamAlreadyInGame";
-	private ArrayList<String> teamsAlreadyInGame;
-	private List<Team> availableTeams;
-	private Listener listener;
-	
-	public static interface Listener {
-		void onTeamAddDialogSubmitted(Team newTeam);
-	}
-	
-	public TeamAddDialog() {
-		// Only for reflection-based instantiation by the framework
-	}
-	
-	TeamAddDialog(Collection<String> teamsAlreadyInGame) {
-		this.teamsAlreadyInGame = new ArrayList<String>(teamsAlreadyInGame);
-	}
-	
-	@Override
-	public void onAttach(Activity activity) {
-		super.onAttach(activity);
-		try {
-			listener = (Listener) activity;
-		} catch(ClassCastException e) {
-			throw new ClassCastException("Activity " + activity + " must implement TeamAddDialog.Listener to use TeamAddDialog.");
-		}
-	}
-	
-	@Override
-	public void onDetach() {
-		super.onDetach();
-		listener = null;
-	}
-	
-	@Override
-	public void onCreate(Bundle savedInstanceState) {
-		super.onCreate(savedInstanceState);
-		if(savedInstanceState != null) {
-			teamsAlreadyInGame = savedInstanceState.getStringArrayList(STATE_TEAMS_ALREADY_IN_GAME);
-		}
-		availableTeams = new ArrayList<Team>();
-		List<Team> teams = FrontendDataUtils.getTeams(getActivity());
-		for(Team team : teams) {
-			if(!teamsAlreadyInGame.contains(team.name)) {
-				availableTeams.add(team);
-			}
-		}
-		Collections.sort(availableTeams, Team.NAME_ORDER);
-	}
-	
-	@Override
-	public Dialog onCreateDialog(Bundle savedInstanceState) {
-		AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
-		builder.setTitle(R.string.dialog_addteam_title);
-		builder.setIcon(R.drawable.human);
-		String[] teamNames = new String[availableTeams.size()];
-		for(int i=0; i<availableTeams.size(); i++) {
-			teamNames[i] = availableTeams.get(i).name;
-		}
-		builder.setItems(teamNames, new OnClickListener() {
-			public void onClick(DialogInterface dialog, int which) {
-				listener.onTeamAddDialogSubmitted(availableTeams.get(which));
-			}
-		});
-		return builder.create();
-	}
-	
-	@Override
-	public void onSaveInstanceState(Bundle outState) {
-		super.onSaveInstanceState(outState);
-		outState.putStringArrayList(STATE_TEAMS_ALREADY_IN_GAME, teamsAlreadyInGame);
-	}
-}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Teamlist.java	Sat Aug 18 00:22:33 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-package org.hedgewars.hedgeroid.netplay;
-
-import java.util.Collection;
-
-import org.hedgewars.hedgeroid.Datastructures.TeamInGame;
-import org.hedgewars.hedgeroid.Datastructures.TeamIngameAttributes;
-
-import android.util.Pair;
-
-public class Teamlist extends ObservableTreeMap<String, Pair<TeamInGame, Long>> {
-	private long nextId = 1;
-	
-	public void addTeamWithNewId(TeamInGame team) {
-		put(team.team.name, Pair.create(team, nextId++));
-	}
-	
-	public int getUnusedOrRandomColorIndex() {
-		Collection<Pair<TeamInGame, Long>> teams = getMap().values();
-		int[] illegalColors = new int[teams.size()];
-		int i=0;
-		for(Pair<TeamInGame, Long> item : teams) {
-			illegalColors[i] = item.first.ingameAttribs.colorIndex;
-			i++;
-		}
-		return TeamIngameAttributes.randomColorIndex(illegalColors);
-	}
-}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/TeamlistAdapter.java	Sat Aug 18 00:22:33 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +0,0 @@
-package org.hedgewars.hedgeroid.netplay;
-
-import java.util.Comparator;
-
-import org.hedgewars.hedgeroid.Datastructures.TeamInGame;
-
-import android.content.Context;
-import android.util.Pair;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-public class TeamlistAdapter extends ObservableTreeMapAdapter<String, Pair<TeamInGame, Long>> {
-	private Context context;
-	
-	public TeamlistAdapter(Context context) {
-		this.context = context;
-	}
-	
-	@Override
-	protected Comparator<Pair<TeamInGame, Long>> getEntryOrder() {
-		return AlphabeticalOrderComparator.INSTANCE;
-	}
-
-	public TeamInGame getItem(int position) {
-		return getEntries().get(position).first;
-	}
-
-	public long getItemId(int position) {
-		return getEntries().get(position).second;
-	}
-
-	public boolean hasStableIds() {
-		return true;
-	}
-	
-	public View getView(int position, View convertView, ViewGroup parent) {
-		View v = convertView;
-		if (v == null) {
-			LayoutInflater vi = LayoutInflater.from(context);
-			v = vi.inflate(android.R.layout.simple_list_item_2, null);
-		}
-
-		TeamInGame team = getItem(position);
-		TextView tv1 = (TextView) v.findViewById(android.R.id.text1);
-		TextView tv2 = (TextView) v.findViewById(android.R.id.text2);
-		
-		tv1.setText(team.team.name);
-		tv2.setText("Hogs: "+team.ingameAttribs.hogCount);
-		return v;
-	}
-	
-	private static final class AlphabeticalOrderComparator implements Comparator<Pair<TeamInGame, Long>> {
-		public static final AlphabeticalOrderComparator INSTANCE = new AlphabeticalOrderComparator();
-		public int compare(Pair<TeamInGame, Long> lhs, Pair<TeamInGame, Long> rhs) {
-			return lhs.first.team.name.compareToIgnoreCase(rhs.first.team.name);
-		};
-	}
-}
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/TeamlistFragment.java	Sat Aug 18 00:22:33 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,85 +0,0 @@
-package org.hedgewars.hedgeroid.netplay;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-import org.hedgewars.hedgeroid.R;
-import org.hedgewars.hedgeroid.Datastructures.Team;
-import org.hedgewars.hedgeroid.Datastructures.TeamInGame;
-
-import android.database.DataSetObserver;
-import android.os.Bundle;
-import android.support.v4.app.ListFragment;
-import android.util.Pair;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.AdapterView;
-import android.widget.Button;
-
-public class TeamlistFragment extends ListFragment implements OnItemClickListener {
-	private Netplay netplay;
-	private TeamlistAdapter adapter;
-	private Button addTeamButton;
-	private DataSetObserver teamlistObserver;
-	
-	@Override
-	public void onCreate(Bundle savedInstanceState) {
-		super.onCreate(savedInstanceState);
-		netplay = Netplay.getAppInstance(getActivity().getApplicationContext());
-		adapter = new TeamlistAdapter(getActivity());
-		adapter.setSource(netplay.roomTeamlist);
-		setListAdapter(adapter);
-	}
-
-	@Override
-	public View onCreateView(LayoutInflater inflater, ViewGroup container,
-			Bundle savedInstanceState) {
-		View v = inflater.inflate(R.layout.fragment_teamlist, container, false);
-		addTeamButton = (Button)v.findViewById(R.id.addTeamButton);
-		addTeamButton.setOnClickListener(new OnClickListener() {
-			public void onClick(View v) {
-				new TeamAddDialog(getCurrentTeamNames()).show(getFragmentManager(), "team_add_dialog");
-			}
-		});
-		
-		teamlistObserver = new DataSetObserver() {
-			@Override
-			public void onChanged() {
-				addTeamButton.setEnabled(netplay.roomTeamlist.getMap().size() < Team.maxNumberOfTeams);
-			}
-		};
-		netplay.roomTeamlist.registerObserver(teamlistObserver);
-		teamlistObserver.onChanged();
-		
-		return v;
-	}
-	
-	@Override
-	public void onDestroy() {
-		super.onDestroy();
-		adapter.invalidate();
-		netplay.roomTeamlist.unregisterObserver(teamlistObserver);
-	}
-
-	@Override
-	public void onActivityCreated(Bundle savedInstanceState) {
-		super.onActivityCreated(savedInstanceState);
-		getListView().setOnItemClickListener(this);
-	}
-	
-	private Collection<String> getCurrentTeamNames() {
-		List<String> names = new ArrayList<String>();
-		for(Pair<TeamInGame, Long> teamWithId : netplay.roomTeamlist.getMap().values()) {
-			names.add(teamWithId.first.team.name);
-		}
-		return names;
-	}
-	
-	public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-		netplay.sendRemoveTeam(adapter.getItem(position).team.name);
-	}
-}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/TextInputDialog.java	Sat Aug 18 00:22:33 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,128 +0,0 @@
-package org.hedgewars.hedgeroid.netplay;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.content.DialogInterface;
-import android.os.Bundle;
-import android.support.v4.app.DialogFragment;
-import android.view.KeyEvent;
-import android.view.inputmethod.EditorInfo;
-import android.widget.EditText;
-import android.widget.TextView;
-import android.widget.TextView.OnEditorActionListener;
-
-/**
- * A generic text input dialog with configurable text. The Activity must implement the callback
- * interface TextInputDialogListener, which will be called by the dialog if it is submitted or cancelled.
- */
-public class TextInputDialog extends DialogFragment {
-	private static final String BUNDLE_DIALOG_ID = "dialogId";
-	private static final String BUNDLE_TITLE_TEXT = "title";
-	private static final String BUNDLE_MESSAGE_TEXT = "message";
-	private static final String BUNDLE_HINT_TEXT = "hint";
-	
-	private int dialogId, titleText, messageText, hintText;
-	private TextInputDialogListener listener;
-	
-	public interface TextInputDialogListener {
-		void onTextInputDialogSubmitted(int dialogId, String text);
-		void onTextInputDialogCancelled(int dialogId);
-	}
-	
-	/**
-	 * The dialogId is only used for passing back to the callback on the activity, the
-	 * other parameters are text resource IDs. Pass 0 for any of them to not use this
-	 * text.
-	 */
-	public TextInputDialog(int dialogId, int titleText, int messageText, int hintText) {
-		this.dialogId = dialogId;
-		this.titleText = titleText;
-		this.messageText = messageText;
-		this.hintText = hintText;
-	}
-	
-	public TextInputDialog() {
-		// Only for reflection-based instantiation by the framework
-	}
-	
-	@Override
-	public void onAttach(Activity activity) {
-		super.onAttach(activity);
-		try {
-			listener = (TextInputDialogListener) activity;
-		} catch(ClassCastException e) {
-			throw new ClassCastException("Activity " + activity + " must implement TextInputDialogListener to use TextInputDialog.");
-		}
-	}
-	
-	@Override
-	public void onDetach() {
-		super.onDetach();
-		listener = null;
-	}
-	
-	@Override
-	public Dialog onCreateDialog(Bundle savedInstanceState) {
-		if(savedInstanceState != null) {
-			dialogId = savedInstanceState.getInt(BUNDLE_DIALOG_ID, dialogId);
-			titleText = savedInstanceState.getInt(BUNDLE_TITLE_TEXT, titleText);
-			messageText = savedInstanceState.getInt(BUNDLE_MESSAGE_TEXT, messageText);
-			hintText = savedInstanceState.getInt(BUNDLE_HINT_TEXT, hintText);
-		}
-		
-		final EditText editText = new EditText(getActivity());
-		AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
-		
-		if(titleText != 0) {
-			builder.setTitle(titleText);
-		}
-		if(messageText != 0) {
-			builder.setTitle(messageText);
-		}
-		if(hintText != 0) {
-			editText.setHint(hintText);
-		}
-		
-		editText.setId(android.R.id.text1);
-		editText.setImeOptions(EditorInfo.IME_ACTION_DONE);
-		editText.setSingleLine();
-
-		builder.setView(editText);
-		builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
-			public void onClick(DialogInterface dialog, int which) {
-				dialog.cancel();
-			}
-		});
-		
-		editText.setOnEditorActionListener(new OnEditorActionListener() {
-			public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
-				listener.onTextInputDialogSubmitted(dialogId, v.getText().toString());
-				return true;
-			}
-		});
-		
-		builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
-			public void onClick(DialogInterface dialog, int which) {
-				listener.onTextInputDialogSubmitted(dialogId, editText.getText().toString());
-			}
-		});
-
-		return builder.create();
-	}
-	
-	@Override
-	public void onSaveInstanceState(Bundle icicle) {
-		super.onSaveInstanceState(icicle);
-		icicle.putInt(BUNDLE_DIALOG_ID, dialogId);
-		icicle.putInt(BUNDLE_TITLE_TEXT, titleText);
-		icicle.putInt(BUNDLE_MESSAGE_TEXT, messageText);
-		icicle.putInt(BUNDLE_HINT_TEXT, hintText);
-	}
-	
-	@Override
-	public void onCancel(DialogInterface dialog) {
-		super.onCancel(dialog);
-		listener.onTextInputDialogCancelled(dialogId);
-	}
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/ThreadedNetConnection.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,566 @@
+package org.hedgewars.hedgeroid.netplay;
+
+import static org.hedgewars.hedgeroid.netplay.Netplay.FromNetMsgType.*;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.Datastructures.MapRecipe;
+import org.hedgewars.hedgeroid.Datastructures.Scheme;
+import org.hedgewars.hedgeroid.Datastructures.Team;
+import org.hedgewars.hedgeroid.Datastructures.Weaponset;
+import org.hedgewars.hedgeroid.frontlib.Flib;
+import org.hedgewars.hedgeroid.frontlib.Frontlib;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.BoolCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.BytesCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.GameSetupPtr;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.IntStrCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.MapIntCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.MapRecipePtr;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.NetconnPtr;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.RoomArrayPtr;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.RoomCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.RoomListCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.RoomPtr;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.SchemeCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.SchemePtr;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.StrBoolCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.StrCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.StrIntCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.StrRoomCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.StrStrCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.TeamCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.TeamPtr;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.VoidCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.WeaponsetCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.WeaponsetPtr;
+import org.hedgewars.hedgeroid.netplay.Netplay.FromNetHandler;
+import org.hedgewars.hedgeroid.netplay.Netplay.FromNetMsgType;
+import org.hedgewars.hedgeroid.util.FileUtils;
+import org.hedgewars.hedgeroid.util.TickHandler;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.util.Pair;
+
+import com.sun.jna.Memory;
+import com.sun.jna.NativeLong;
+import com.sun.jna.Pointer;
+
+/**
+ * This class handles the actual communication with the networking library, running on a separate thread.
+ * 
+ * In order to process net messages, this class regularly runs a tick() function on the frontlib. This
+ * usually happens several times per second, but it can be slowed down a lot if no fast reaction to
+ * events is required (e.g. to conserve battery if the application is in the background).
+ */
+class ThreadedNetConnection {
+	private static final long TICK_INTERVAL_FAST = 100;
+	private static final long TICK_INTERVAL_SLOW = 5000;
+	private static final Frontlib FLIB = Flib.INSTANCE;
+	
+	public final ToNetHandler toNetHandler;
+	
+	private final Context appContext;
+	private final FromNetHandler fromNetHandler;
+	private final TickHandler tickHandler;
+
+	/**
+	 * conn can only be null while connecting (the first thing in the thread), and directly after disconnecting,
+	 * in the same message (the looper is shut down on disconnect, so there will be no messages after that).
+	 */
+	private NetconnPtr conn;
+	private String playerName;
+	
+	private ThreadedNetConnection(Context appContext, FromNetHandler fromNetHandler) {
+		this.appContext = appContext;
+		this.fromNetHandler = fromNetHandler;
+		
+		HandlerThread thread = new HandlerThread("NetThread");
+		thread.start();
+		toNetHandler = new ToNetHandler(thread.getLooper());
+		tickHandler = new TickHandler(thread.getLooper(), TICK_INTERVAL_FAST, tickCb);
+	}
+	
+	private void connect(final String name, final String host, final int port) {
+		toNetHandler.post(new Runnable() {
+			public void run() {
+				playerName = name == null ? "Player" : name;
+				File dataPath;
+				try {
+					dataPath = FileUtils.getDataPathFile(appContext);
+				} catch (FileNotFoundException e) {
+					shutdown(true, appContext.getString(R.string.sdcard_not_mounted));
+					return;
+				}
+				conn = FLIB.flib_netconn_create(playerName, dataPath.getAbsolutePath()+"/", host, port);
+				if(conn == null) {
+					shutdown(true, appContext.getString(R.string.error_connection_failed));
+					return;
+				}
+
+				//FLIB.flib_netconn_onAdminAccess(conn, adminAccessCb, null)
+				FLIB.flib_netconn_onCfgScheme(conn, cfgSchemeCb, null);
+				FLIB.flib_netconn_onChat(conn, chatCb, null);
+				FLIB.flib_netconn_onConnected(conn, connectedCb, null);
+				FLIB.flib_netconn_onDisconnected(conn, disconnectCb, null);
+				FLIB.flib_netconn_onEngineMessage(conn, engineMessageCb, null);
+				FLIB.flib_netconn_onEnterRoom(conn, enterRoomCb, null);
+				FLIB.flib_netconn_onHogCountChanged(conn, hogCountChangedCb, null);
+				FLIB.flib_netconn_onLeaveRoom(conn, leaveRoomCb, null);
+				FLIB.flib_netconn_onLobbyJoin(conn, lobbyJoinCb, null);
+				FLIB.flib_netconn_onLobbyLeave(conn, lobbyLeaveCb, null);
+				FLIB.flib_netconn_onMapChanged(conn, mapChangedCb, null);
+				FLIB.flib_netconn_onMessage(conn, messageCb, null);
+				FLIB.flib_netconn_onPasswordRequest(conn, passwordRequestCb, null);
+				FLIB.flib_netconn_onReadyState(conn, readyStateCb, null);
+				FLIB.flib_netconn_onRoomAdd(conn, roomAddCb, null);
+				FLIB.flib_netconn_onRoomChiefStatus(conn, roomChiefStatusCb, null);
+				FLIB.flib_netconn_onRoomDelete(conn, roomDeleteCb, null);
+				FLIB.flib_netconn_onRoomJoin(conn, roomJoinCb, null);
+				FLIB.flib_netconn_onRoomLeave(conn, roomLeaveCb, null);
+				FLIB.flib_netconn_onRoomlist(conn, roomlistCb, null);
+				FLIB.flib_netconn_onRoomUpdate(conn, roomUpdateCb, null);
+				FLIB.flib_netconn_onRunGame(conn, runGameCb, null);
+				FLIB.flib_netconn_onScriptChanged(conn, scriptChangedCb, null);
+				// FLIB.flib_netconn_onServerVar(conn, serverVarCb, null);
+				FLIB.flib_netconn_onTeamAccepted(conn, teamAcceptedCb, null);
+				FLIB.flib_netconn_onTeamAdd(conn, teamAddedCb, null);
+				FLIB.flib_netconn_onTeamColorChanged(conn, teamColorChangedCb, null);
+				FLIB.flib_netconn_onTeamDelete(conn, teamDeletedCb, null);
+				FLIB.flib_netconn_onWeaponsetChanged(conn, weaponsetChangedCb, null);
+				
+				tickHandler.start();
+			}
+		});
+	}
+	
+	public static ThreadedNetConnection startConnection(Context appContext, FromNetHandler fromNetHandler, String playerName, String host, int port) {
+		ThreadedNetConnection result = new ThreadedNetConnection(appContext, fromNetHandler);
+		result.connect(playerName, host, port);
+		return result;
+	}
+	
+	public void setFastTickRate(boolean fastTickRate) {
+		tickHandler.setInterval(fastTickRate ? TICK_INTERVAL_FAST : TICK_INTERVAL_SLOW);
+	}
+	
+	private final Runnable tickCb = new Runnable() {
+		public void run() {
+			FLIB.flib_netconn_tick(conn);
+		}
+	};
+	
+	private final SchemeCallback cfgSchemeCb = new SchemeCallback() {
+		public void callback(Pointer context, SchemePtr schemePtr) {
+			sendFromNet(MSG_SCHEME_CHANGED, schemePtr.deref());
+		}
+	};
+	
+	private final MapIntCallback mapChangedCb = new MapIntCallback() {
+		public void callback(Pointer context, MapRecipePtr mapPtr, int updateType) {
+			sendFromNet(MSG_MAP_CHANGED, updateType, mapPtr.deref());
+		}
+	};
+	
+	private final BoolCallback roomChiefStatusCb = new BoolCallback() {
+		public void callback(Pointer context, boolean chief) {
+			sendFromNet(MSG_ROOM_CHIEF_STATUS_CHANGED, chief);
+		}
+	};
+	
+	private final StrCallback scriptChangedCb = new StrCallback() {
+		public void callback(Pointer context, String script) {
+			sendFromNet(MSG_SCRIPT_CHANGED, script);
+		}
+	};
+	
+	private final WeaponsetCallback weaponsetChangedCb = new WeaponsetCallback() {
+		public void callback(Pointer context, WeaponsetPtr weaponsetPtr) {
+			sendFromNet(MSG_WEAPONSET_CHANGED, weaponsetPtr.deref());				
+		}
+	};
+	
+	private final StrCallback lobbyJoinCb = new StrCallback() {
+		public void callback(Pointer context, String name) {
+			sendFromNet(MSG_LOBBY_JOIN, name);
+		}
+	};
+	
+	private final StrStrCallback lobbyLeaveCb = new StrStrCallback() {
+		public void callback(Pointer context, String name, String msg) {
+			sendFromNet(MSG_LOBBY_LEAVE, Pair.create(name, msg));
+		}
+	};
+	
+	private final StrCallback roomJoinCb = new StrCallback() {
+		public void callback(Pointer context, String name) {
+			sendFromNet(MSG_ROOM_JOIN, name);
+		}
+	};
+	private final StrStrCallback roomLeaveCb = new StrStrCallback() {
+		public void callback(Pointer context, String name, String message) {
+			sendFromNet(MSG_ROOM_LEAVE, Pair.create(name, message));
+		}
+	};
+	private final StrStrCallback chatCb = new StrStrCallback() {
+		public void callback(Pointer context, String name, String msg) {
+			sendFromNet(MSG_CHAT, Pair.create(name, msg));
+		}
+	};
+	
+	private final IntStrCallback messageCb = new IntStrCallback() {
+		public void callback(Pointer context, int type, String msg) {
+			sendFromNet(MSG_MESSAGE, type, msg);
+		}
+	};
+	
+	private final RoomCallback roomAddCb = new RoomCallback() {
+		public void callback(Pointer context, RoomPtr roomPtr) {
+			sendFromNet(MSG_ROOM_ADD, roomPtr.deref());
+		}
+	};
+	
+	private final StrRoomCallback roomUpdateCb = new StrRoomCallback() {
+		public void callback(Pointer context, String name, RoomPtr roomPtr) {
+			sendFromNet(MSG_ROOM_UPDATE, Pair.create(name, roomPtr.deref()));
+		}
+	};
+	
+	private final StrCallback roomDeleteCb = new StrCallback() {
+		public void callback(Pointer context, final String name) {
+			sendFromNet(MSG_ROOM_DELETE, name);
+		}
+	};
+	
+	private final RoomListCallback roomlistCb = new RoomListCallback() {
+		public void callback(Pointer context, RoomArrayPtr arg1, int count) {
+			sendFromNet(MSG_ROOMLIST, arg1.getRooms(count));
+		}
+	};
+	
+	private final VoidCallback connectedCb = new VoidCallback() {
+		public void callback(Pointer context) {
+			FLIB.flib_netconn_send_request_roomlist(conn);
+			playerName = FLIB.flib_netconn_get_playername(conn);
+			sendFromNet(MSG_CONNECTED, playerName);
+		}
+	};
+	
+	private final StrCallback passwordRequestCb = new StrCallback() {
+		public void callback(Pointer context, String nickname) {
+			sendFromNet(MSG_PASSWORD_REQUEST, playerName);
+		}
+	};
+	
+	private final BoolCallback enterRoomCb = new BoolCallback() {
+		public void callback(Pointer context, boolean isChief) {
+			sendFromNet(MSG_ENTER_ROOM_FROM_LOBBY, isChief);
+		}
+	};
+	
+	private final IntStrCallback leaveRoomCb = new IntStrCallback() {
+		public void callback(Pointer context, int reason, String message) {
+			sendFromNet(MSG_LEAVE_ROOM, reason, message);
+		}
+	};
+	
+	private final StrBoolCallback readyStateCb = new StrBoolCallback() {
+		public void callback(Pointer context, String player, boolean ready) {
+			sendFromNet(MSG_READYSTATE, Pair.create(player, ready));
+		}
+	};
+	
+	private final TeamCallback teamAddedCb = new TeamCallback() {
+		public void callback(Pointer context, TeamPtr team) {
+			sendFromNet(MSG_TEAM_ADDED, team.deref().team);
+		}
+	};
+	
+	private final StrCallback teamDeletedCb = new StrCallback() {
+		public void callback(Pointer context, String teamName) {
+			sendFromNet(MSG_TEAM_DELETED, teamName);
+		}
+	};
+	
+	private final StrCallback teamAcceptedCb = new StrCallback() {
+		public void callback(Pointer context, String teamName) {
+			sendFromNet(MSG_TEAM_ACCEPTED, teamName);
+		}
+	};
+	
+	private final StrIntCallback teamColorChangedCb = new StrIntCallback() {
+		public void callback(Pointer context, String teamName, int colorIndex) {
+			sendFromNet(MSG_TEAM_COLOR_CHANGED, colorIndex, teamName);
+		}
+	};
+	
+	private final StrIntCallback hogCountChangedCb = new StrIntCallback() {
+		public void callback(Pointer context, String teamName, int hogCount) {
+			sendFromNet(MSG_HOG_COUNT_CHANGED, hogCount, teamName);
+		}
+	};
+	
+	private final BytesCallback engineMessageCb = new BytesCallback() {
+		public void callback(Pointer context, Pointer buffer, NativeLong size) {
+			sendFromNet(MSG_ENGINE_MESSAGE, buffer.getByteArray(0, size.intValue()));
+		}
+	};
+	
+	private final VoidCallback runGameCb = new VoidCallback() {
+		public void callback(Pointer context) {
+			GameSetupPtr configPtr = FLIB.flib_netconn_create_gamesetup(conn);
+			sendFromNet(MSG_RUN_GAME, configPtr.deref());
+			FLIB.flib_gamesetup_destroy(configPtr);
+		}
+	};
+	
+	private void shutdown(boolean error, String message) {
+		if(conn != null) {
+			FLIB.flib_netconn_destroy(conn);
+			conn = null;
+		}
+		tickHandler.stop();
+		toNetHandler.getLooper().quit();
+		sendFromNet(MSG_DISCONNECTED, Pair.create(error, message));
+	}
+	
+	private final IntStrCallback disconnectCb = new IntStrCallback() {
+		public void callback(Pointer context, int reason, String message) {
+			Boolean error = reason != Frontlib.NETCONN_DISCONNECT_NORMAL;
+			String messageForUser = createDisconnectUserMessage(appContext.getResources(), reason, message);
+			shutdown(error, messageForUser);
+		}
+	};
+	
+	private static String createDisconnectUserMessage(Resources res, int reason, String message) {
+		switch(reason) {
+		case Frontlib.NETCONN_DISCONNECT_AUTH_FAILED:
+			return res.getString(R.string.error_auth_failed);
+		case Frontlib.NETCONN_DISCONNECT_CONNLOST:
+			return res.getString(R.string.error_connection_lost);
+		case Frontlib.NETCONN_DISCONNECT_INTERNAL_ERROR:
+			return res.getString(R.string.error_unexpected, message);
+		case Frontlib.NETCONN_DISCONNECT_SERVER_TOO_OLD:
+			return res.getString(R.string.error_server_too_old);
+		default:
+			return message;
+		}
+	}
+	
+	private boolean sendFromNet(FromNetMsgType what, Object obj) {
+		return fromNetHandler.sendMessage(fromNetHandler.obtainMessage(what.ordinal(), obj));
+	}
+	
+	private boolean sendFromNet(FromNetMsgType what, int arg1, Object obj) {
+		return fromNetHandler.sendMessage(fromNetHandler.obtainMessage(what.ordinal(), arg1, 0, obj));
+	}
+	
+	static enum ToNetMsgType {
+		MSG_SEND_NICK,
+		MSG_SEND_PASSWORD,
+		MSG_SEND_QUIT,
+		MSG_SEND_ROOMLIST_REQUEST,
+		MSG_SEND_PLAYER_INFO_REQUEST,
+		MSG_SEND_CHAT,
+		MSG_SEND_TEAMCHAT,
+		MSG_SEND_FOLLOW_PLAYER,
+		MSG_SEND_JOIN_ROOM,
+		MSG_SEND_CREATE_ROOM,
+		MSG_SEND_LEAVE_ROOM,
+		MSG_SEND_KICK,
+		MSG_SEND_ADD_TEAM,
+		MSG_SEND_REMOVE_TEAM,
+		MSG_DISCONNECT,
+		MSG_SEND_TEAM_COLOR_INDEX,
+		MSG_SEND_TEAM_HOG_COUNT,
+		MSG_SEND_ENGINE_MESSAGE,
+		MSG_SEND_ROUND_FINISHED,
+		MSG_SEND_TOGGLE_READY,
+		MSG_SEND_WEAPONSET,
+		MSG_SEND_MAP,
+		MSG_SEND_MAP_NAME,
+		MSG_SEND_MAP_GENERATOR,
+		MSG_SEND_MAP_TEMPLATE,
+		MSG_SEND_MAZE_SIZE,
+		MSG_SEND_MAP_SEED,
+		MSG_SEND_MAP_THEME,
+		MSG_SEND_MAP_DRAWDATA,
+		MSG_SEND_GAMESTYLE,
+		MSG_SEND_SCHEME;
+		
+		static final List<ThreadedNetConnection.ToNetMsgType> values = Collections.unmodifiableList(Arrays.asList(ToNetMsgType.values()));
+	}
+	
+	/**
+	 * Processes messages to the networking system. Runs on a non-main thread.
+	 */
+	@SuppressLint("HandlerLeak")
+	public final class ToNetHandler extends Handler {
+		
+		public ToNetHandler(Looper looper) {
+			super(looper);
+		}
+		
+		@Override
+		public void handleMessage(Message msg) {
+			switch(ToNetMsgType.values.get(msg.what)) {
+			case MSG_SEND_NICK: {
+				FLIB.flib_netconn_send_nick(conn, (String)msg.obj);
+				break;
+			}
+			case MSG_SEND_PASSWORD: {
+				FLIB.flib_netconn_send_password(conn, (String)msg.obj);
+				break;
+			}
+			case MSG_SEND_QUIT: {
+				FLIB.flib_netconn_send_quit(conn, (String)msg.obj);
+				break;
+			}
+			case MSG_SEND_ROOMLIST_REQUEST: {
+				FLIB.flib_netconn_send_request_roomlist(conn);
+				break;
+			}
+			case MSG_SEND_PLAYER_INFO_REQUEST: {
+				FLIB.flib_netconn_send_playerInfo(conn, (String)msg.obj);
+				break;
+			}
+			case MSG_SEND_CHAT: {
+				if(FLIB.flib_netconn_send_chat(conn, (String)msg.obj) == 0) {
+					sendFromNet(MSG_CHAT, Pair.create(playerName, (String)msg.obj));
+				}
+				break;
+			}
+			case MSG_SEND_TEAMCHAT: {
+				FLIB.flib_netconn_send_teamchat(conn, (String)msg.obj);
+				break;
+			}
+			case MSG_SEND_FOLLOW_PLAYER: {
+				FLIB.flib_netconn_send_playerFollow(conn, (String)msg.obj);
+				break;
+			}
+			case MSG_SEND_JOIN_ROOM: {
+				FLIB.flib_netconn_send_joinRoom(conn, (String)msg.obj);
+				break;
+			}
+			case MSG_SEND_CREATE_ROOM: {
+				FLIB.flib_netconn_send_createRoom(conn, (String)msg.obj);
+				break;
+			}
+			case MSG_SEND_LEAVE_ROOM: {
+				if(FLIB.flib_netconn_send_leaveRoom(conn, (String)msg.obj) == 0) {
+					sendFromNet(MSG_LEAVE_ROOM, -1, "");
+				}
+				break;
+			}
+			case MSG_SEND_KICK: {
+				FLIB.flib_netconn_send_kick(conn, (String)msg.obj);
+				break;
+			}
+			case MSG_SEND_ADD_TEAM: {
+				FLIB.flib_netconn_send_addTeam(conn, TeamPtr.createJavaOwned((Team)msg.obj));
+				break;
+			}
+			case MSG_SEND_REMOVE_TEAM: {
+				if(FLIB.flib_netconn_send_removeTeam(conn, (String)msg.obj)==0) {
+					sendFromNet(MSG_TEAM_DELETED, msg.obj);
+				}
+				break;
+			}
+			case MSG_DISCONNECT: {
+				FLIB.flib_netconn_send_quit(conn, (String)msg.obj);
+				shutdown(false, "User quit");
+				break;
+			}
+			case MSG_SEND_TEAM_COLOR_INDEX: {
+				if(FLIB.flib_netconn_send_teamColor(conn, (String)msg.obj, msg.arg1)==0) {
+					sendFromNet(MSG_TEAM_COLOR_CHANGED, msg.arg1, msg.obj);
+				}
+				break;
+			}
+			case MSG_SEND_TEAM_HOG_COUNT: {
+				if(FLIB.flib_netconn_send_teamHogCount(conn, (String)msg.obj, msg.arg1)==0) {
+					sendFromNet(MSG_HOG_COUNT_CHANGED, msg.arg1, msg.obj);
+				}
+				break;
+			}
+			case MSG_SEND_ENGINE_MESSAGE: {
+				byte[] message = (byte[])msg.obj;
+				Memory mem = new Memory(message.length);
+				mem.write(0, message, 0, message.length);
+				FLIB.flib_netconn_send_engineMessage(conn, mem, new NativeLong(message.length));
+				break;
+			}
+			case MSG_SEND_ROUND_FINISHED: {
+				FLIB.flib_netconn_send_roundfinished(conn, (Boolean)msg.obj);
+				break;
+			}
+			case MSG_SEND_TOGGLE_READY: {
+				FLIB.flib_netconn_send_toggleReady(conn);
+				break;
+			}
+			case MSG_SEND_WEAPONSET: {
+				FLIB.flib_netconn_send_weaponset(conn, WeaponsetPtr.createJavaOwned((Weaponset)msg.obj));
+				break;
+			}
+			case MSG_SEND_MAP: {
+				FLIB.flib_netconn_send_map(conn, MapRecipePtr.createJavaOwned((MapRecipe)msg.obj));
+				break;
+			}
+			case MSG_SEND_MAP_NAME: {
+				FLIB.flib_netconn_send_mapName(conn, (String)msg.obj);
+				break;
+			}
+			case MSG_SEND_MAP_GENERATOR: {
+				FLIB.flib_netconn_send_mapGen(conn, msg.arg1);
+				break;
+			}
+			case MSG_SEND_MAP_TEMPLATE: {
+				FLIB.flib_netconn_send_mapTemplate(conn, msg.arg1);
+				break;
+			}
+			case MSG_SEND_MAZE_SIZE: {
+				FLIB.flib_netconn_send_mapMazeSize(conn, msg.arg1);
+				break;
+			}
+			case MSG_SEND_MAP_SEED: {
+				FLIB.flib_netconn_send_mapSeed(conn, (String) msg.obj);
+				break;
+			}
+			case MSG_SEND_MAP_THEME: {
+				FLIB.flib_netconn_send_mapTheme(conn, (String) msg.obj);
+				break;
+			}
+			case MSG_SEND_MAP_DRAWDATA: {
+				byte[] message = (byte[])msg.obj;
+				Memory mem = new Memory(message.length);
+				mem.write(0, message, 0, message.length);
+				FLIB.flib_netconn_send_mapDrawdata(conn, mem, new NativeLong(message.length));
+				break;
+			}
+			case MSG_SEND_GAMESTYLE: {
+				FLIB.flib_netconn_send_script(conn, (String) msg.obj);
+				break;
+			}
+			case MSG_SEND_SCHEME: {
+				FLIB.flib_netconn_send_scheme(conn, SchemePtr.createJavaOwned((Scheme) msg.obj));
+				break;
+			}
+			default: {
+				Log.e("ToNetHandler", "Unknown message type: "+msg.what);
+				break;
+			}
+			}
+		}
+	}
+}
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/TickHandler.java	Sat Aug 18 00:22:33 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +0,0 @@
-package org.hedgewars.hedgeroid.netplay;
-
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-
-/**
- * This class handles regularly calling a specified runnable
- * on the looper provided in the constructor. The first call
- * occurs without delay (though still via the looper), all
- * following calls are delayed by (approximately) the interval.
- * The interval can be changed at any time, which will cause
- * an immediate execution of the runnable again.
- */
-public class TickHandler extends Handler {
-	private final Runnable callback;
-	private int messageId;
-	private long interval;
-	private boolean running;
-	
-	public TickHandler(Looper looper, long interval, Runnable callback) {
-		super(looper);
-		this.callback = callback;
-		this.interval = interval;
-	}
-	
-	public synchronized void stop() {
-		messageId++;
-		running = false;
-	}
-	
-	public synchronized void start() {
-		messageId++;
-		sendMessage(obtainMessage(messageId));
-		running = true;
-	}
-	
-	public synchronized void setInterval(long interval) {
-		this.interval = interval;
-		if(running) {
-			start();
-		}
-	}
-	
-	@Override
-	public synchronized void handleMessage(Message msg) {
-		if(msg.what == messageId) {
-			callback.run();
-		}
-		if(msg.what == messageId) {
-			sendMessageDelayed(obtainMessage(messageId), interval);
-		}
-	}
-}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/util/FileUtils.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,265 @@
+/*
+ * Hedgewars for Android. An Android port of Hedgewars, a free turn based strategy game
+ * Copyright (c) 2011-2012 Richard Deurwaarder <xeli@xelification.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+
+package org.hedgewars.hedgeroid.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.os.Build;
+import android.os.Environment;
+import android.util.Log;
+
+public class FileUtils {
+	private static final String ROOT_DIR = "Data";
+	private static final String TAG = FileUtils.class.getSimpleName();
+
+	/**
+	 * @return true if the data path is currently available. However, it can vanish at any time so
+	 * normally you should just try to use it and rely on the exceptions.
+	 */
+	public static boolean isDataPathAvailable() {
+		return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
+	}
+	
+	/**
+	 * get the path to which we should download all the data files
+	 * @param c context 
+	 * @return The directory
+	 * @throws FileNotFoundException if external storage is not available at the moment
+	 */
+	public static File getCachePath(Context c) throws FileNotFoundException {
+		File cachePath = null;
+		if(Build.VERSION.SDK_INT < 8){//8 == Build.VERSION_CODES.FROYO
+			cachePath = PreFroyoSDCardDir.getDownloadPath(c);
+		} else {
+			cachePath = FroyoSDCardDir.getDownloadPath(c);
+		}
+		if(cachePath==null) {
+			throw new FileNotFoundException("External storage is currently unavailable");
+		} else {
+			return cachePath;
+		}
+	}
+
+	public static File getDataPathFile(Context c) throws FileNotFoundException {
+		return new File(getCachePath(c), ROOT_DIR);
+	}
+	
+	// TODO Several callers are unaware that this may fail, so it throws an RTE now.
+	// Should be handled better though.
+	@Deprecated
+	public static String getDataPath(Context c) {
+		try {
+			return getDataPathFile(c).getAbsolutePath()+"/";
+		} catch(FileNotFoundException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	@TargetApi(8)
+	private static class FroyoSDCardDir{
+		public static File getDownloadPath(Context c){
+			return c.getExternalCacheDir();
+		}
+	}
+
+	private static class PreFroyoSDCardDir{
+		public static File getDownloadPath(Context c){
+			if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
+				File extStorageDir = Environment.getExternalStorageDirectory();
+				if(extStorageDir != null) {
+					return new File(extStorageDir, "Hedgewars");
+				}
+			}
+			return null;
+		}
+	}
+
+	/**
+	 * Return a File array with all the files from dirName
+	 * @param c
+	 * @param dirName
+	 * @return
+	 * @throws FileNotFoundException If the sdcard is not available or the subdirectory "dirName" does not exist
+	 */
+	public static File[] getFilesFromRelativeDir(Context c, String dirName) throws FileNotFoundException {
+		File f = new File(getDataPathFile(c), dirName);
+
+		if(f.isDirectory()) {
+			return f.listFiles();
+		} else {
+			throw new FileNotFoundException("Directory "+dirName+" does not exist.");
+		}
+	}
+
+	/**
+	 * Checks if this directory has a file with suffix suffix
+	 * @param f - directory
+	 * @return
+	 */
+	public static boolean hasFileWithSuffix(File f, String suffix){
+		if(f.isDirectory()){
+			for(String s : f.list()){
+				if(s.endsWith(suffix)) return true;
+			}
+			return false;
+		}else{
+			return false;
+		}
+	}
+
+	/**
+	 * Gives back all dirs which contain a file with suffix fileSuffix
+	 * @param c
+	 * @param path
+	 * @param fileSuffix
+	 * @return
+	 * @throws FileNotFoundException If the sdcard is not available or the subdirectory "path" does not exist
+	 */
+	public static List<String> getDirsWithFileSuffix(Context c, String path, String fileSuffix) throws FileNotFoundException{
+		File[] files = getFilesFromRelativeDir(c,path);
+		ArrayList<String> ret = new ArrayList<String>();
+
+		for(File f : files){
+			if(hasFileWithSuffix(f, fileSuffix)) ret.add(f.getName());
+		}
+		return ret;
+	}
+
+	/**
+	 * Get all files from directory dir which have the given suffix
+	 * @throws FileNotFoundException If the sdcard is not available or the subdirectory "dir" does not exist
+	 */
+	public static ArrayList<String> getFileNamesFromDirWithSuffix(Context c, String dir, String suffix, boolean removeSuffix) throws FileNotFoundException{
+		File[] files = FileUtils.getFilesFromRelativeDir(c, dir);
+		ArrayList<String> ret = new ArrayList<String>();
+		for(File file : files){
+			String s = file.getName();
+			if(s.endsWith(suffix)){
+				if(removeSuffix) ret.add(s.substring(0, s.length()-suffix.length()));
+				else ret.add(s);
+			}
+		}
+		return ret;
+	}
+
+	/**
+	 * Close a resource (possibly null), ignoring any IOException.
+	 */
+	public static void closeQuietly(Closeable c) {
+		if(c!=null) {
+			try {
+				c.close();
+			} catch(IOException e) {
+				Log.w(TAG, e);
+			}
+		}
+	}
+	
+	/**
+	 * Write all data from the input stream to the file, creating or overwriting it.
+	 * The input stream will be closed.
+	 * 
+	 * @throws IOException
+	 */
+	public static void writeStreamToFile(InputStream is, File file) throws IOException {
+		OutputStream os = null;
+		byte[] buffer = new byte[8192];
+		try {
+			os = new FileOutputStream(file);
+			int size;
+			while((size=is.read(buffer)) != -1) {
+				os.write(buffer, 0, size);
+			}
+			os.close(); // Important to close this non-quietly, in case of exceptions when flushing
+		} finally {
+			FileUtils.closeQuietly(is);
+			FileUtils.closeQuietly(os);
+		}
+	}
+	
+	/**
+	 * Moves resources pointed to by sourceResId (from @res/raw/) to the app's private data directory
+	 * @param c
+	 * @param sourceResId
+	 * @param directory
+	 */
+	public static void resRawToFilesDir(Context c, int sourceResId, int targetFilenames, String directory) throws IOException {
+		File targetDir = new File(c.getFilesDir(), directory);
+		targetDir.mkdirs();
+
+		//Get an array with the resource files ID
+		Resources resources = c.getResources();
+		TypedArray ta = resources.obtainTypedArray(sourceResId);
+		TypedArray filenames = resources.obtainTypedArray(targetFilenames);
+		for(int i = 0; i < ta.length(); i++){
+			int resId =  ta.getResourceId(i, 0);
+			String fileName = filenames.getString(i);
+			File f = new File(targetDir, fileName);
+			writeStreamToFile(resources.openRawResource(resId), f);
+		}
+	}
+
+	public static String readToString(InputStream is) throws IOException {
+		try {
+			ByteArrayOutputStream os = new ByteArrayOutputStream();
+			byte[] buffer = new byte[8192];
+			int size;
+			while((size=is.read(buffer)) != -1) {
+				os.write(buffer, 0, size);
+			}
+			return new String(os.toByteArray());
+		} finally {
+			closeQuietly(is);
+		}
+	}
+	
+	private static final char[] badFilenameChars = new char[] { '/', '\\', ':', '*', '?', '\"', '<', '>', '|', '.', '\0' };
+	
+	/**
+	 * Modify the given String so that it can be used as part of a filename
+	 * without causing problems from illegal/special characters.
+	 * 
+	 * The result should be similar to the input, but isn't necessarily
+	 * reversible.
+	 */
+	public static String replaceBadChars(String name) {
+		if (name == null || name.trim().length()==0) {
+			return "_";
+		}
+		name = name.trim();
+		for (char badChar : badFilenameChars) {
+			name = name.replace(badChar, '_');
+		}
+		return name;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/util/ObservableTreeMap.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,43 @@
+package org.hedgewars.hedgeroid.util;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
+import android.database.DataSetObservable;
+
+public class ObservableTreeMap<K,V> extends DataSetObservable {
+	private final Map<K, V> map = new TreeMap<K, V>();
+	
+	public void replaceContent(Map<? extends K, ? extends V> newMap) {
+		map.clear();
+		map.putAll(newMap);
+		notifyChanged();
+	}
+	
+	public void put(K key, V value) {
+		map.put(key, value);
+		notifyChanged();
+	}
+	
+	public V get(K key) {
+		return map.get(key);
+	}
+	
+	public void remove(K key) {
+		if(map.remove(key) != null) {
+			notifyChanged();
+		}
+	}
+	
+	public void clear() {
+		if(!map.isEmpty()) {
+			map.clear();
+			notifyChanged();
+		}
+	}
+	
+	public Map<K, V> getMap() {
+		return Collections.unmodifiableMap(map);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/util/ObservableTreeMapAdapter.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,75 @@
+package org.hedgewars.hedgeroid.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import android.database.DataSetObserver;
+import android.widget.BaseAdapter;
+
+public abstract class ObservableTreeMapAdapter<K,V> extends BaseAdapter {
+	private boolean sourceChanged = true;
+	private List<V> entries = new ArrayList<V>();
+	private ObservableTreeMap<K, V> source;
+	
+	private DataSetObserver observer = new DataSetObserver() {
+		@Override
+		public void onChanged() {
+			sourceChanged = true;
+			notifyDataSetChanged();
+		}
+		
+		@Override
+		public void onInvalidated() {
+			invalidate();
+		}
+	};
+	
+	abstract protected Comparator<V> getEntryOrder();
+	
+	protected List<V> getEntries() {
+		if(sourceChanged) {
+			entries.clear();
+			entries.addAll(source.getMap().values());
+			Collections.sort(entries, getEntryOrder());
+			sourceChanged = false;
+		}
+		return entries;
+	}
+	
+	public int getCount() {
+		return getEntries().size();
+	}
+
+	public V getItem(int position) {
+		return getEntries().get(position);
+	}
+	
+	public long getItemId(int position) {
+		return position;
+	}
+	
+	@Override
+	public boolean hasStableIds() {
+		return false;
+	}
+	
+	public void setSource(ObservableTreeMap<K,V> source) {
+		if(this.source != null) {
+			this.source.unregisterObserver(observer);
+		}
+		this.source = source;
+		this.source.registerObserver(observer);
+		sourceChanged = true;
+		notifyDataSetChanged();
+	}
+	
+	public void invalidate() {
+		if(source != null) {
+			source.unregisterObserver(observer);
+		}
+		source = null;
+		notifyDataSetInvalidated();
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/util/TextInputDialog.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,128 @@
+package org.hedgewars.hedgeroid.util;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
+import android.view.KeyEvent;
+import android.view.inputmethod.EditorInfo;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+/**
+ * A generic text input dialog with configurable text. The Activity must implement the callback
+ * interface TextInputDialogListener, which will be called by the dialog if it is submitted or cancelled.
+ */
+public class TextInputDialog extends DialogFragment {
+	private static final String BUNDLE_DIALOG_ID = "dialogId";
+	private static final String BUNDLE_TITLE_TEXT = "title";
+	private static final String BUNDLE_MESSAGE_TEXT = "message";
+	private static final String BUNDLE_HINT_TEXT = "hint";
+	
+	private int dialogId, titleText, messageText, hintText;
+	private TextInputDialogListener listener;
+	
+	public interface TextInputDialogListener {
+		void onTextInputDialogSubmitted(int dialogId, String text);
+		void onTextInputDialogCancelled(int dialogId);
+	}
+	
+	/**
+	 * The dialogId is only used for passing back to the callback on the activity, the
+	 * other parameters are text resource IDs. Pass 0 for any of them to not use this
+	 * text.
+	 */
+	public TextInputDialog(int dialogId, int titleText, int messageText, int hintText) {
+		this.dialogId = dialogId;
+		this.titleText = titleText;
+		this.messageText = messageText;
+		this.hintText = hintText;
+	}
+	
+	public TextInputDialog() {
+		// Only for reflection-based instantiation by the framework
+	}
+	
+	@Override
+	public void onAttach(Activity activity) {
+		super.onAttach(activity);
+		try {
+			listener = (TextInputDialogListener) activity;
+		} catch(ClassCastException e) {
+			throw new ClassCastException("Activity " + activity + " must implement TextInputDialogListener to use TextInputDialog.");
+		}
+	}
+	
+	@Override
+	public void onDetach() {
+		super.onDetach();
+		listener = null;
+	}
+	
+	@Override
+	public Dialog onCreateDialog(Bundle savedInstanceState) {
+		if(savedInstanceState != null) {
+			dialogId = savedInstanceState.getInt(BUNDLE_DIALOG_ID, dialogId);
+			titleText = savedInstanceState.getInt(BUNDLE_TITLE_TEXT, titleText);
+			messageText = savedInstanceState.getInt(BUNDLE_MESSAGE_TEXT, messageText);
+			hintText = savedInstanceState.getInt(BUNDLE_HINT_TEXT, hintText);
+		}
+		
+		final EditText editText = new EditText(getActivity());
+		AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+		
+		if(titleText != 0) {
+			builder.setTitle(titleText);
+		}
+		if(messageText != 0) {
+			builder.setTitle(messageText);
+		}
+		if(hintText != 0) {
+			editText.setHint(hintText);
+		}
+		
+		editText.setId(android.R.id.text1);
+		editText.setImeOptions(EditorInfo.IME_ACTION_DONE);
+		editText.setSingleLine();
+
+		builder.setView(editText);
+		builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+			public void onClick(DialogInterface dialog, int which) {
+				dialog.cancel();
+			}
+		});
+		
+		editText.setOnEditorActionListener(new OnEditorActionListener() {
+			public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+				listener.onTextInputDialogSubmitted(dialogId, v.getText().toString());
+				return true;
+			}
+		});
+		
+		builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+			public void onClick(DialogInterface dialog, int which) {
+				listener.onTextInputDialogSubmitted(dialogId, editText.getText().toString());
+			}
+		});
+
+		return builder.create();
+	}
+	
+	@Override
+	public void onSaveInstanceState(Bundle icicle) {
+		super.onSaveInstanceState(icicle);
+		icicle.putInt(BUNDLE_DIALOG_ID, dialogId);
+		icicle.putInt(BUNDLE_TITLE_TEXT, titleText);
+		icicle.putInt(BUNDLE_MESSAGE_TEXT, messageText);
+		icicle.putInt(BUNDLE_HINT_TEXT, hintText);
+	}
+	
+	@Override
+	public void onCancel(DialogInterface dialog) {
+		super.onCancel(dialog);
+		listener.onTextInputDialogCancelled(dialogId);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/util/TickHandler.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,54 @@
+package org.hedgewars.hedgeroid.util;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+/**
+ * This class handles regularly calling a specified runnable
+ * on the looper provided in the constructor. The first call
+ * occurs without delay (though still via the looper), all
+ * following calls are delayed by (approximately) the interval.
+ * The interval can be changed at any time, which will cause
+ * an immediate execution of the runnable again.
+ */
+public class TickHandler extends Handler {
+	private final Runnable callback;
+	private int messageId;
+	private long interval;
+	private boolean running;
+	
+	public TickHandler(Looper looper, long interval, Runnable callback) {
+		super(looper);
+		this.callback = callback;
+		this.interval = interval;
+	}
+	
+	public synchronized void stop() {
+		messageId++;
+		running = false;
+	}
+	
+	public synchronized void start() {
+		messageId++;
+		sendMessage(obtainMessage(messageId));
+		running = true;
+	}
+	
+	public synchronized void setInterval(long interval) {
+		this.interval = interval;
+		if(running) {
+			start();
+		}
+	}
+	
+	@Override
+	public synchronized void handleMessage(Message msg) {
+		if(msg.what == messageId) {
+			callback.run();
+		}
+		if(msg.what == messageId) {
+			sendMessageDelayed(obtainMessage(messageId), interval);
+		}
+	}
+}
\ No newline at end of file