project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Netplay.java
changeset 7508 763d3961400b
parent 7485 0481bd74267c
child 7568 75ba91f14ed5
--- 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;
-				}
-				}
-			}
-		}
-	}
 }