project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Netplay.java
changeset 7508 763d3961400b
parent 7485 0481bd74267c
child 7568 75ba91f14ed5
equal deleted inserted replaced
7504:ed1d52c5aa94 7508:763d3961400b
     1 package org.hedgewars.hedgeroid.netplay;
     1 package org.hedgewars.hedgeroid.netplay;
     2 
     2 
     3 import java.io.File;
     3 import static org.hedgewars.hedgeroid.netplay.ThreadedNetConnection.ToNetMsgType.*;
     4 import java.io.FileNotFoundException;
     4 
       
     5 import java.io.IOException;
       
     6 import java.util.Arrays;
       
     7 import java.util.Collections;
       
     8 import java.util.LinkedList;
       
     9 import java.util.List;
     5 import java.util.Map;
    10 import java.util.Map;
     6 import java.util.TreeMap;
    11 import java.util.TreeMap;
     7 
    12 
     8 import org.hedgewars.hedgeroid.R;
    13 import org.hedgewars.hedgeroid.RoomStateManager;
     9 import org.hedgewars.hedgeroid.Utils;
    14 import org.hedgewars.hedgeroid.Datastructures.GameConfig;
    10 import org.hedgewars.hedgeroid.Datastructures.RoomlistRoom;
    15 import org.hedgewars.hedgeroid.Datastructures.MapRecipe;
       
    16 import org.hedgewars.hedgeroid.Datastructures.Player;
       
    17 import org.hedgewars.hedgeroid.Datastructures.PlayerInRoom;
       
    18 import org.hedgewars.hedgeroid.Datastructures.Room;
       
    19 import org.hedgewars.hedgeroid.Datastructures.Scheme;
       
    20 import org.hedgewars.hedgeroid.Datastructures.Schemes;
    11 import org.hedgewars.hedgeroid.Datastructures.Team;
    21 import org.hedgewars.hedgeroid.Datastructures.Team;
    12 import org.hedgewars.hedgeroid.Datastructures.TeamInGame;
    22 import org.hedgewars.hedgeroid.Datastructures.TeamInGame;
    13 import org.hedgewars.hedgeroid.Datastructures.TeamIngameAttributes;
    23 import org.hedgewars.hedgeroid.Datastructures.TeamIngameAttributes;
       
    24 import org.hedgewars.hedgeroid.Datastructures.Weaponset;
       
    25 import org.hedgewars.hedgeroid.Datastructures.Weaponsets;
    14 import org.hedgewars.hedgeroid.frontlib.Flib;
    26 import org.hedgewars.hedgeroid.frontlib.Flib;
    15 import org.hedgewars.hedgeroid.frontlib.Frontlib;
    27 import org.hedgewars.hedgeroid.netplay.ThreadedNetConnection.ToNetMsgType;
    16 import org.hedgewars.hedgeroid.frontlib.Frontlib.BoolCallback;
    28 import org.hedgewars.hedgeroid.util.ObservableTreeMap;
    17 import org.hedgewars.hedgeroid.frontlib.Frontlib.IntStrCallback;
       
    18 import org.hedgewars.hedgeroid.frontlib.Frontlib.MetaschemePtr;
       
    19 import org.hedgewars.hedgeroid.frontlib.Frontlib.NetconnPtr;
       
    20 import org.hedgewars.hedgeroid.frontlib.Frontlib.RoomArrayPtr;
       
    21 import org.hedgewars.hedgeroid.frontlib.Frontlib.RoomCallback;
       
    22 import org.hedgewars.hedgeroid.frontlib.Frontlib.RoomListCallback;
       
    23 import org.hedgewars.hedgeroid.frontlib.Frontlib.RoomPtr;
       
    24 import org.hedgewars.hedgeroid.frontlib.Frontlib.StrBoolCallback;
       
    25 import org.hedgewars.hedgeroid.frontlib.Frontlib.StrCallback;
       
    26 import org.hedgewars.hedgeroid.frontlib.Frontlib.StrIntCallback;
       
    27 import org.hedgewars.hedgeroid.frontlib.Frontlib.StrRoomCallback;
       
    28 import org.hedgewars.hedgeroid.frontlib.Frontlib.StrStrCallback;
       
    29 import org.hedgewars.hedgeroid.frontlib.Frontlib.TeamCallback;
       
    30 import org.hedgewars.hedgeroid.frontlib.Frontlib.TeamPtr;
       
    31 import org.hedgewars.hedgeroid.frontlib.Frontlib.VoidCallback;
       
    32 
    29 
    33 import android.annotation.SuppressLint;
    30 import android.annotation.SuppressLint;
    34 import android.content.Context;
    31 import android.content.Context;
    35 import android.content.Intent;
    32 import android.content.Intent;
    36 import android.content.res.Resources;
       
    37 import android.os.Handler;
    33 import android.os.Handler;
    38 import android.os.HandlerThread;
       
    39 import android.os.Looper;
    34 import android.os.Looper;
    40 import android.os.Message;
    35 import android.os.Message;
    41 import android.support.v4.content.LocalBroadcastManager;
    36 import android.support.v4.content.LocalBroadcastManager;
    42 import android.util.Log;
    37 import android.util.Log;
    43 import android.util.Pair;
    38 import android.util.Pair;
    44 
    39 
    45 import com.sun.jna.Pointer;
       
    46 
    40 
    47 /**
    41 /**
    48  * This class manages the application's networking state.
    42  * This class manages the application's networking state.
    49  */
    43  */
    50 public class Netplay {
    44 public class Netplay {
    70 	private final Context appContext;
    64 	private final Context appContext;
    71 	private final LocalBroadcastManager broadcastManager;
    65 	private final LocalBroadcastManager broadcastManager;
    72 	private final FromNetHandler fromNetHandler = new FromNetHandler();
    66 	private final FromNetHandler fromNetHandler = new FromNetHandler();
    73 	
    67 	
    74 	private State state = State.NOT_CONNECTED;
    68 	private State state = State.NOT_CONNECTED;
    75 	private int foregroundUsers = 0;	// Reference counter of clients requesting foreground tick speed (fast ticks)
       
    76 	private boolean chief;				// Do we control the current room?
       
    77 	private String playerName;
    69 	private String playerName;
       
    70 	
       
    71 	// null or stale if not in room state
       
    72 	private final NetRoomState netRoomState = new NetRoomState(this);
    78 	
    73 	
    79 	// null if there is no running connection (==state is NOT_CONNECTED)
    74 	// null if there is no running connection (==state is NOT_CONNECTED)
    80 	private ThreadedNetConnection connection;
    75 	private ThreadedNetConnection connection;
    81 	
    76 	
    82 	public final LobbyPlayerlist lobbyPlayerlist = new LobbyPlayerlist();
    77 	public final ObservableTreeMap<String, Player> lobbyPlayerlist = new ObservableTreeMap<String, Player>();
    83 	public final RoomPlayerlist roomPlayerlist = new RoomPlayerlist();
    78 	public final ObservableTreeMap<String, PlayerInRoom> roomPlayerlist = new ObservableTreeMap<String, PlayerInRoom>();
    84 	public final Roomlist roomList = new Roomlist();
    79 	public final Roomlist roomList = new Roomlist();
    85 	public final MessageLog lobbyChatlog;
    80 	public final MessageLog lobbyChatlog;
    86 	public final MessageLog roomChatlog;
    81 	public final MessageLog roomChatlog;
    87 	public final Teamlist roomTeamlist = new Teamlist();
    82 	public final ObservableTreeMap<String, TeamInGame> roomTeamlist = new ObservableTreeMap<String, TeamInGame>();
    88 	private final Map<String, Team> roomRequestedTeams = new TreeMap<String, Team>();
    83 	private final Map<String, Team> roomRequestedTeams = new TreeMap<String, Team>();
       
    84 	
       
    85 	private final List<GameMessageListener> gameMessageListeners = new LinkedList<GameMessageListener>();
       
    86 	private final List<RunGameListener> runGameListeners = new LinkedList<RunGameListener>();
    89 	
    87 	
    90 	public Netplay(Context appContext) {
    88 	public Netplay(Context appContext) {
    91 		this.appContext = appContext;
    89 		this.appContext = appContext;
    92 		broadcastManager = LocalBroadcastManager.getInstance(appContext);
    90 		broadcastManager = LocalBroadcastManager.getInstance(appContext);
    93 		lobbyChatlog = new MessageLog(appContext);
    91 		lobbyChatlog = new MessageLog(appContext);
    94 		roomChatlog = new MessageLog(appContext);
    92 		roomChatlog = new MessageLog(appContext);
    95 	}
    93 	}
    96 	
    94 	
    97 	private void clearState() {
    95 	public RoomStateManager getRoomStateManager() {
       
    96 		return netRoomState;
       
    97 	}
       
    98 	
       
    99 	private void clearLobbyState() {
    98 		lobbyPlayerlist.clear();
   100 		lobbyPlayerlist.clear();
    99 		roomList.clear();
   101 		roomList.clear();
   100 		lobbyChatlog.clear();
   102 		lobbyChatlog.clear();
       
   103 	}
       
   104 	
       
   105 	private void initRoomState(boolean chief) {
       
   106 		roomChatlog.clear();
       
   107 		roomPlayerlist.clear();
       
   108 		roomTeamlist.clear();
       
   109 		roomRequestedTeams.clear();
       
   110 		
       
   111 		try {
       
   112 			netRoomState.setChief(chief);
       
   113 			netRoomState.setGameStyle(GameConfig.DEFAULT_STYLE);
       
   114 			List<Scheme> schemes = Schemes.loadBuiltinSchemes(appContext);
       
   115 			netRoomState.setScheme(schemes.get(Schemes.toNameList(schemes).indexOf(GameConfig.DEFAULT_SCHEME)));
       
   116 			netRoomState.setMapRecipe(MapRecipe.makeRandomMap(0, MapRecipe.makeRandomSeed(), GameConfig.DEFAULT_THEME));
       
   117 			List<Weaponset> weaponsets = Weaponsets.loadBuiltinWeaponsets(appContext);
       
   118 			netRoomState.setWeaponset(weaponsets.get(Weaponsets.toNameList(weaponsets).indexOf(GameConfig.DEFAULT_WEAPONSET)));
       
   119 			netRoomState.sendFullConfig();
       
   120 		} catch(IOException e) {
       
   121 			throw new RuntimeException(e);
       
   122 		}
       
   123 	}
       
   124 	
       
   125 	public void registerGameMessageListener(GameMessageListener listener) {
       
   126 		gameMessageListeners.add(listener);
       
   127 	}
       
   128 	
       
   129 	public void unregisterGameMessageListener(GameMessageListener listener) {
       
   130 		gameMessageListeners.remove(listener);
       
   131 	}
       
   132 	
       
   133 	public void registerRunGameListener(RunGameListener listener) {
       
   134 		runGameListeners.add(listener);
       
   135 	}
       
   136 	
       
   137 	public void unregisterRunGameListener(RunGameListener listener) {
       
   138 		runGameListeners.remove(listener);
   101 	}
   139 	}
   102 	
   140 	
   103 	public void connectToDefaultServer(String playerName) {
   141 	public void connectToDefaultServer(String playerName) {
   104 		connect(playerName, DEFAULT_SERVER, DEFAULT_PORT);
   142 		connect(playerName, DEFAULT_SERVER, DEFAULT_PORT);
   105 	}
   143 	}
   115 		playerName = name;
   153 		playerName = name;
   116 		if(state != State.NOT_CONNECTED) {
   154 		if(state != State.NOT_CONNECTED) {
   117 			throw new IllegalStateException("Attempt to start a new connection while the old one was still running.");
   155 			throw new IllegalStateException("Attempt to start a new connection while the old one was still running.");
   118 		}
   156 		}
   119 		
   157 		
   120 		clearState();
   158 		clearLobbyState();
   121 		changeState(State.CONNECTING);
   159 		changeState(State.CONNECTING);
   122 		connection = ThreadedNetConnection.startConnection(appContext, fromNetHandler, name, host, port);
   160 		connection = ThreadedNetConnection.startConnection(appContext, fromNetHandler, name, host, port);
   123 		connection.setFastTickRate(foregroundUsers > 0);
   161 		connection.setFastTickRate(true);
   124 	}
   162 	}
   125 	
   163 	
   126 	public void sendNick(String nick) {
   164 	public void sendNick(String nick) {
   127 		playerName = nick;
   165 		playerName = nick;
   128 		sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_NICK, nick);
   166 		sendToNet(MSG_SEND_NICK, nick);
   129 	}
   167 	}
   130 	public void sendPassword(String password) { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_PASSWORD, password); }
   168 	public void sendPassword(String password) { sendToNet(MSG_SEND_PASSWORD, password); }
   131 	public void sendQuit(String message) { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_QUIT, message); }
   169 	public void sendQuit(String message) { sendToNet(MSG_SEND_QUIT, message); }
   132 	public void sendRoomlistRequest() { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_ROOMLIST_REQUEST); }
   170 	public void sendRoomlistRequest() { sendToNet(MSG_SEND_ROOMLIST_REQUEST); }
   133 	public void sendPlayerInfoQuery(String name) { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_PLAYER_INFO_REQUEST, name); }
   171 	public void sendPlayerInfoQuery(String name) { sendToNet(MSG_SEND_PLAYER_INFO_REQUEST, name); }
   134 	public void sendChat(String s) { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_CHAT, s); }
   172 	public void sendChat(String s) { sendToNet(MSG_SEND_CHAT, s); }
   135 	public void sendFollowPlayer(String nick) { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_FOLLOW_PLAYER, nick); }
   173 	public void sendTeamChat(String s) { sendToNet(MSG_SEND_TEAMCHAT, s); }
   136 	public void sendJoinRoom(String name) { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_JOIN_ROOM, name); }
   174 	public void sendFollowPlayer(String nick) { sendToNet(MSG_SEND_FOLLOW_PLAYER, nick); }
   137 	public void sendCreateRoom(String name) { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_CREATE_ROOM, name); }
   175 	public void sendJoinRoom(String name) { sendToNet(MSG_SEND_JOIN_ROOM, name); }
   138 	public void sendLeaveRoom(String message) { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_LEAVE_ROOM, message); }
   176 	public void sendCreateRoom(String name) { sendToNet(MSG_SEND_CREATE_ROOM, name); }
   139 	public void sendKick(String player) { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_KICK, player); }
   177 	public void sendLeaveRoom(String message) { sendToNet(MSG_SEND_LEAVE_ROOM, message); }
       
   178 	public void sendKick(String player) { sendToNet(MSG_SEND_KICK, player); }
   140 	public void sendAddTeam(Team newTeam) {
   179 	public void sendAddTeam(Team newTeam) {
   141 		roomRequestedTeams.put(newTeam.name, newTeam);
   180 		roomRequestedTeams.put(newTeam.name, newTeam);
   142 		sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_ADD_TEAM, newTeam);
   181 		sendToNet(MSG_SEND_ADD_TEAM, newTeam);
   143 	}
   182 	}
   144 	public void sendRemoveTeam(String teamName) { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_REMOVE_TEAM, teamName); }
   183 	public void sendRemoveTeam(String teamName) { sendToNet(MSG_SEND_REMOVE_TEAM, teamName); }
   145 	public void sendTeamColorIndex(String teamName, int colorIndex) { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_TEAM_COLOR_INDEX, colorIndex, teamName); }
   184 	public void sendTeamColorIndex(String teamName, int colorIndex) { sendToNet(MSG_SEND_TEAM_COLOR_INDEX, colorIndex, teamName); }
   146 	public void sendTeamHogCount(String teamName, int hogCount) { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_TEAM_HOG_COUNT, hogCount, teamName); }
   185 	public void sendTeamHogCount(String teamName, int hogCount) { sendToNet(MSG_SEND_TEAM_HOG_COUNT, hogCount, teamName); }
   147 	
   186 	public void sendEngineMessage(byte[] engineMessage) { sendToNet(MSG_SEND_ENGINE_MESSAGE, engineMessage); }
   148 	public void disconnect() { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_DISCONNECT, "User Quit"); }
   187 	public void sendRoundFinished(boolean withoutError) { sendToNet(MSG_SEND_ROUND_FINISHED, Boolean.valueOf(withoutError)); }
       
   188 	public void sendToggleReady() { sendToNet(MSG_SEND_TOGGLE_READY); }
       
   189 	
       
   190 	public void disconnect() { sendToNet(MSG_DISCONNECT, "User Quit"); }
   149 	
   191 	
   150 	private static Netplay instance;
   192 	private static Netplay instance;
   151 	
   193 	
   152 	/**
   194 	/**
   153 	 * Retrieve the single app-wide instance of the netplay interface, creating it if it
   195 	 * Retrieve the single app-wide instance of the netplay interface, creating it if it
   177 			broadcastManager.sendBroadcastSync(new Intent(ACTION_STATE_CHANGED));
   219 			broadcastManager.sendBroadcastSync(new Intent(ACTION_STATE_CHANGED));
   178 		}
   220 		}
   179 	}
   221 	}
   180 	
   222 	
   181 	public boolean isChief() {
   223 	public boolean isChief() {
   182 		return chief;
   224 		if(netRoomState != null) {
       
   225 			return netRoomState.chief;
       
   226 		} else {
       
   227 			return false;
       
   228 		}
   183 	}
   229 	}
   184 	
   230 	
   185 	public String getPlayerName() {
   231 	public String getPlayerName() {
   186 		return playerName;
   232 		return playerName;
   187 	}
   233 	}
   188 	
   234 	
   189 	/**
   235 	boolean sendToNet(ToNetMsgType what) {
   190 	 * Indicate that you want network messages to be checked regularly (several times per second).
       
   191 	 * As long as nobody requests fast ticks, the network is only checked once every few seconds
       
   192 	 * to conserve battery power.
       
   193 	 * Once you no longer need fast updates, call unrequestFastTicks.
       
   194 	 */
       
   195 	public void requestFastTicks() {
       
   196 		if(foregroundUsers == Integer.MAX_VALUE) {
       
   197 			throw new RuntimeException("Reference counter overflow");
       
   198 		}
       
   199 		if(foregroundUsers == 0 && connection != null) {
       
   200 			connection.setFastTickRate(true);
       
   201 		}
       
   202 		foregroundUsers++;
       
   203 	}
       
   204 	
       
   205 	public void unrequestFastTicks() {
       
   206 		if(foregroundUsers == 0) {
       
   207 			throw new RuntimeException("Reference counter underflow");
       
   208 		}
       
   209 		foregroundUsers--;
       
   210 		if(foregroundUsers == 0 && connection != null) {
       
   211 			connection.setFastTickRate(false);
       
   212 		}
       
   213 	}
       
   214 	
       
   215 	private boolean sendToNet(int what) {
       
   216 		return sendToNet(what, 0, null);
   236 		return sendToNet(what, 0, null);
   217 	}
   237 	}
   218 	
   238 	
   219 	private boolean sendToNet(int what, Object obj) {
   239 	boolean sendToNet(ToNetMsgType what, Object obj) {
   220 		return sendToNet(what, 0, obj);
   240 		return sendToNet(what, 0, obj);
   221 	}
   241 	}
   222 	
   242 	
   223 	private boolean sendToNet(int what, int arg1, Object obj) {
   243 	boolean sendToNet(ToNetMsgType what, int arg1, Object obj) {
   224 		if(connection != null) {
   244 		if(connection != null) {
   225 			Handler handler = connection.toNetHandler;
   245 			Handler handler = connection.toNetHandler;
   226 			return handler.sendMessage(handler.obtainMessage(what, arg1, 0, obj));
   246 			return handler.sendMessage(handler.obtainMessage(what.ordinal(), arg1, 0, obj));
   227 		} else {
   247 		} else {
   228 			return false;
   248 			return false;
   229 		}
   249 		}
   230 	}
   250 	}
   231 	
   251 	
   235 		} else {
   255 		} else {
   236 			return lobbyChatlog;
   256 			return lobbyChatlog;
   237 		}
   257 		}
   238 	}
   258 	}
   239 	
   259 	
       
   260 	public static enum FromNetMsgType {
       
   261 		MSG_LOBBY_JOIN,
       
   262 		MSG_LOBBY_LEAVE,
       
   263 		MSG_ROOM_JOIN,
       
   264 		MSG_ROOM_LEAVE,
       
   265 		MSG_CHAT,
       
   266 		MSG_MESSAGE,
       
   267 		MSG_ROOM_ADD,
       
   268 		MSG_ROOM_UPDATE,
       
   269 		MSG_ROOM_DELETE,
       
   270 		MSG_ROOMLIST,
       
   271 		MSG_CONNECTED,
       
   272 		MSG_DISCONNECTED,
       
   273 		MSG_PASSWORD_REQUEST,
       
   274 		MSG_ENTER_ROOM_FROM_LOBBY,
       
   275 		MSG_LEAVE_ROOM,
       
   276 		MSG_READYSTATE,
       
   277 		MSG_TEAM_ADDED,
       
   278 		MSG_TEAM_DELETED,
       
   279 		MSG_TEAM_ACCEPTED,
       
   280 		MSG_TEAM_COLOR_CHANGED,
       
   281 		MSG_HOG_COUNT_CHANGED,
       
   282 		MSG_ENGINE_MESSAGE,
       
   283 		MSG_RUN_GAME,
       
   284 		MSG_SCHEME_CHANGED,
       
   285 		MSG_MAP_CHANGED,
       
   286 		MSG_ROOM_CHIEF_STATUS_CHANGED,
       
   287 		MSG_SCRIPT_CHANGED,
       
   288 		MSG_WEAPONSET_CHANGED;
       
   289 		
       
   290 		static final List<FromNetMsgType> values = Collections.unmodifiableList(Arrays.asList(FromNetMsgType.values()));
       
   291 	}
       
   292 	
   240 	/**
   293 	/**
   241 	 * Processes messages from the networking system. Always runs on the main thread.
   294 	 * Processes messages from the networking system. Always runs on the main thread.
   242 	 */
   295 	 */
   243 	@SuppressLint("HandlerLeak")
   296 	@SuppressLint("HandlerLeak")
   244 	final class FromNetHandler extends Handler {
   297 	final class FromNetHandler extends Handler {
   245 		public static final int MSG_LOBBY_JOIN = 0;
       
   246 		public static final int MSG_LOBBY_LEAVE = 1;
       
   247 		public static final int MSG_ROOM_JOIN = 2;
       
   248 		public static final int MSG_ROOM_LEAVE = 3;
       
   249 		public static final int MSG_CHAT = 4;
       
   250 		public static final int MSG_MESSAGE = 5;
       
   251 		public static final int MSG_ROOM_ADD = 6;
       
   252 		public static final int MSG_ROOM_UPDATE = 7;
       
   253 		public static final int MSG_ROOM_DELETE = 8;
       
   254 		public static final int MSG_ROOMLIST = 9;
       
   255 		public static final int MSG_CONNECTED = 10;
       
   256 		public static final int MSG_DISCONNECTED = 11;
       
   257 		public static final int MSG_PASSWORD_REQUEST = 12;
       
   258 		public static final int MSG_ENTER_ROOM_FROM_LOBBY = 13;
       
   259 		public static final int MSG_LEAVE_ROOM = 14;
       
   260 		public static final int MSG_READYSTATE = 15;
       
   261 		public static final int MSG_TEAM_ADDED = 16;
       
   262 		public static final int MSG_TEAM_DELETED = 17;
       
   263 		public static final int MSG_TEAM_ACCEPTED = 18;
       
   264 		public static final int MSG_TEAM_COLOR_CHANGED = 19;
       
   265 		public static final int MSG_HOG_COUNT_CHANGED = 20;
       
   266 		
       
   267 		public FromNetHandler() {
   298 		public FromNetHandler() {
   268 			super(Looper.getMainLooper());
   299 			super(Looper.getMainLooper());
   269 		}
   300 		}
   270 		
   301 		
   271 		@SuppressWarnings("unchecked")
   302 		@SuppressWarnings("unchecked")
   272 		@Override
   303 		@Override
   273 		public void handleMessage(Message msg) {
   304 		public void handleMessage(Message msg) {
   274 			switch(msg.what) {
   305 			switch(FromNetMsgType.values.get(msg.what)) {
   275 			case MSG_LOBBY_JOIN: {
   306 			case MSG_LOBBY_JOIN: {
   276 				String name = (String)msg.obj;
   307 				String name = (String)msg.obj;
   277 				lobbyPlayerlist.addPlayerWithNewId(name);
   308 				lobbyPlayerlist.put(name, new Player(name, false, false));
   278 				lobbyChatlog.appendPlayerJoin(name);
   309 				lobbyChatlog.appendPlayerJoin(name);
   279 				break;
   310 				break;
   280 			}
   311 			}
   281 			case MSG_LOBBY_LEAVE: {
   312 			case MSG_LOBBY_LEAVE: {
   282 				Pair<String, String> args = (Pair<String, String>)msg.obj;
   313 				Pair<String, String> args = (Pair<String, String>)msg.obj;
   284 				lobbyChatlog.appendPlayerLeave(args.first, args.second);
   315 				lobbyChatlog.appendPlayerLeave(args.first, args.second);
   285 				break;
   316 				break;
   286 			}
   317 			}
   287 			case MSG_ROOM_JOIN: {
   318 			case MSG_ROOM_JOIN: {
   288 				String name = (String)msg.obj;
   319 				String name = (String)msg.obj;
   289 				roomPlayerlist.addPlayerWithNewId(name);
   320 				Player p = lobbyPlayerlist.get(name);
       
   321 				if(p==null) {
       
   322 					Log.w("Netplay", "Unknown player joined room: "+name);
       
   323 					p = new Player(name, false, false);
       
   324 				}
       
   325 				roomPlayerlist.put(name, new PlayerInRoom(p, false));
   290 				roomChatlog.appendPlayerJoin(name);
   326 				roomChatlog.appendPlayerJoin(name);
   291 				break;
   327 				break;
   292 			}
   328 			}
   293 			case MSG_ROOM_LEAVE: {
   329 			case MSG_ROOM_LEAVE: {
   294 				Pair<String, String> args = (Pair<String, String>)msg.obj;
   330 				Pair<String, String> args = (Pair<String, String>)msg.obj;
   297 				break;
   333 				break;
   298 			}
   334 			}
   299 			case MSG_CHAT: {
   335 			case MSG_CHAT: {
   300 				Pair<String, String> args = (Pair<String, String>)msg.obj;
   336 				Pair<String, String> args = (Pair<String, String>)msg.obj;
   301 				getCurrentLog().appendChat(args.first, args.second);
   337 				getCurrentLog().appendChat(args.first, args.second);
       
   338 				for(GameMessageListener listener : gameMessageListeners) {
       
   339 					listener.onChatMessage(args.first, args.second);
       
   340 				}
   302 				break;
   341 				break;
   303 			}
   342 			}
   304 			case MSG_MESSAGE: {
   343 			case MSG_MESSAGE: {
   305 				getCurrentLog().appendMessage(msg.arg1, (String)msg.obj);
   344 				getCurrentLog().appendMessage(msg.arg1, (String)msg.obj);
       
   345 				for(GameMessageListener listener : gameMessageListeners) {
       
   346 					listener.onMessage(1, (String)msg.obj);
       
   347 				}
   306 				break;
   348 				break;
   307 			}
   349 			}
   308 			case MSG_ROOM_ADD: {
   350 			case MSG_ROOM_ADD: {
   309 				roomList.addRoomWithNewId((RoomlistRoom)msg.obj);
   351 				Room room = (Room)msg.obj;
       
   352 				roomList.addRoomWithNewId(room);
   310 				break;
   353 				break;
   311 			}
   354 			}
   312 			case MSG_ROOM_UPDATE: {
   355 			case MSG_ROOM_UPDATE: {
   313 				Pair<String, RoomlistRoom> args = (Pair<String, RoomlistRoom>)msg.obj;
   356 				Pair<String, Room> args = (Pair<String, Room>)msg.obj;
   314 				roomList.updateRoom(args.first, args.second);
   357 				roomList.updateRoom(args.first, args.second);
   315 				break;
   358 				break;
   316 			}
   359 			}
   317 			case MSG_ROOM_DELETE: {
   360 			case MSG_ROOM_DELETE: {
   318 				roomList.remove((String)msg.obj);
   361 				roomList.remove((String)msg.obj);
   319 				break;
   362 				break;
   320 			}
   363 			}
   321 			case MSG_ROOMLIST: {
   364 			case MSG_ROOMLIST: {
   322 				roomList.updateList((RoomlistRoom[])msg.obj);
   365 				Room[] rooms = (Room[])msg.obj;
       
   366 				roomList.updateList(rooms);
   323 				break;
   367 				break;
   324 			}
   368 			}
   325 			case MSG_CONNECTED: {
   369 			case MSG_CONNECTED: {
   326 				playerName = (String)msg.obj;
   370 				playerName = (String)msg.obj;
   327 				changeState(State.LOBBY);
   371 				changeState(State.LOBBY);
   328 				broadcastManager.sendBroadcast(new Intent(ACTION_CONNECTED));
   372 				broadcastManager.sendBroadcast(new Intent(ACTION_CONNECTED));
   329 				break;
   373 				break;
   330 			}
   374 			}
   331 			case MSG_DISCONNECTED: {
   375 			case MSG_DISCONNECTED: {
   332 				Pair<Boolean, String> args = (Pair<Boolean, String>)msg.obj;
   376 				Pair<Boolean, String> args = (Pair<Boolean, String>)msg.obj;
       
   377 				for(GameMessageListener listener : gameMessageListeners) {
       
   378 					listener.onNetDisconnected();
       
   379 				}
   333 				changeState(State.NOT_CONNECTED);
   380 				changeState(State.NOT_CONNECTED);
   334 				connection = null;
   381 				connection = null;
   335 				Intent intent = new Intent(ACTION_DISCONNECTED);
   382 				Intent intent = new Intent(ACTION_DISCONNECTED);
   336 				intent.putExtra(EXTRA_HAS_ERROR, args.first);
   383 				intent.putExtra(EXTRA_HAS_ERROR, args.first);
   337 				intent.putExtra(EXTRA_MESSAGE, args.second);
   384 				intent.putExtra(EXTRA_MESSAGE, args.second);
   343 				intent.putExtra(EXTRA_PLAYERNAME, (String)msg.obj);
   390 				intent.putExtra(EXTRA_PLAYERNAME, (String)msg.obj);
   344 				broadcastManager.sendBroadcast(intent);
   391 				broadcastManager.sendBroadcast(intent);
   345 				break;
   392 				break;
   346 			}
   393 			}
   347 			case MSG_ENTER_ROOM_FROM_LOBBY: {
   394 			case MSG_ENTER_ROOM_FROM_LOBBY: {
   348 				roomChatlog.clear();
   395 				initRoomState((Boolean)msg.obj);
   349 				roomPlayerlist.clear();
       
   350 				roomTeamlist.clear();
       
   351 				roomRequestedTeams.clear();
       
   352 				changeState(State.ROOM);
   396 				changeState(State.ROOM);
   353 				chief = (Boolean)msg.obj;
       
   354 				Intent intent = new Intent(ACTION_ENTERED_ROOM_FROM_LOBBY);
   397 				Intent intent = new Intent(ACTION_ENTERED_ROOM_FROM_LOBBY);
   355 				broadcastManager.sendBroadcastSync(intent);
   398 				broadcastManager.sendBroadcastSync(intent);
   356 				break;
   399 				break;
   357 			}
   400 			}
   358 			case MSG_LEAVE_ROOM: {
   401 			case MSG_LEAVE_ROOM: {
   363 				broadcastManager.sendBroadcastSync(intent);
   406 				broadcastManager.sendBroadcastSync(intent);
   364 				break;
   407 				break;
   365 			}
   408 			}
   366 			case MSG_READYSTATE: {
   409 			case MSG_READYSTATE: {
   367 				Pair<String, Boolean> args = (Pair<String, Boolean>)msg.obj;
   410 				Pair<String, Boolean> args = (Pair<String, Boolean>)msg.obj;
   368 				roomPlayerlist.setReady(args.first, args.second);
   411 				String name = args.first;
       
   412 				Boolean newReadyState = args.second;
       
   413 				PlayerInRoom oldEntry = roomPlayerlist.get(name);
       
   414 				if(oldEntry==null) {
       
   415 					Log.e("Netplay", "Setting readystate for unknown player "+name);
       
   416 				} else {
       
   417 					roomPlayerlist.put(name, new PlayerInRoom(oldEntry.player, newReadyState));
       
   418 				}
   369 				break;
   419 				break;
   370 			}
   420 			}
   371 			case MSG_TEAM_ADDED: {
   421 			case MSG_TEAM_ADDED: {
   372 				Team newTeam = (Team)msg.obj;
   422 				Team newTeam = (Team)msg.obj;
   373 				TeamIngameAttributes attrs = new TeamIngameAttributes(playerName, roomTeamlist.getUnusedOrRandomColorIndex(), TeamIngameAttributes.DEFAULT_HOG_COUNT, false);
   423 				int colorIndex = TeamInGame.getUnusedOrRandomColorIndex(roomTeamlist.getMap().values());
       
   424 				TeamIngameAttributes attrs = new TeamIngameAttributes(playerName, colorIndex, TeamIngameAttributes.DEFAULT_HOG_COUNT, true);
   374 				TeamInGame tig = new TeamInGame(newTeam, attrs);
   425 				TeamInGame tig = new TeamInGame(newTeam, attrs);
   375 				roomTeamlist.addTeamWithNewId(tig);
   426 				roomTeamlist.put(newTeam.name, tig);
   376 				if(chief) {
   427 				if(isChief()) {
   377 					sendTeamColorIndex(newTeam.name, attrs.colorIndex);
   428 					sendTeamColorIndex(newTeam.name, attrs.colorIndex);
   378 					sendTeamHogCount(newTeam.name, attrs.hogCount);
   429 					sendTeamHogCount(newTeam.name, attrs.hogCount);
   379 				}
   430 				}
   380 				break;
   431 				break;
   381 			}
   432 			}
   384 				break;
   435 				break;
   385 			}
   436 			}
   386 			case MSG_TEAM_ACCEPTED: {
   437 			case MSG_TEAM_ACCEPTED: {
   387 				Team requestedTeam = roomRequestedTeams.remove(msg.obj);
   438 				Team requestedTeam = roomRequestedTeams.remove(msg.obj);
   388 				if(requestedTeam!=null) {
   439 				if(requestedTeam!=null) {
   389 					TeamIngameAttributes attrs = new TeamIngameAttributes(playerName, roomTeamlist.getUnusedOrRandomColorIndex(), TeamIngameAttributes.DEFAULT_HOG_COUNT, false);
   440 					int colorIndex = TeamInGame.getUnusedOrRandomColorIndex(roomTeamlist.getMap().values());
       
   441 					TeamIngameAttributes attrs = new TeamIngameAttributes(playerName, colorIndex, TeamIngameAttributes.DEFAULT_HOG_COUNT, false);
   390 					TeamInGame tig = new TeamInGame(requestedTeam, attrs);
   442 					TeamInGame tig = new TeamInGame(requestedTeam, attrs);
   391 					roomTeamlist.addTeamWithNewId(tig);
   443 					roomTeamlist.put(requestedTeam.name, tig);
   392 					if(chief) {
   444 					if(isChief()) {
   393 						sendTeamColorIndex(requestedTeam.name, attrs.colorIndex);
   445 						sendTeamColorIndex(requestedTeam.name, attrs.colorIndex);
   394 						sendTeamHogCount(requestedTeam.name, attrs.hogCount);
   446 						sendTeamHogCount(requestedTeam.name, attrs.hogCount);
   395 					}
   447 					}
   396 				} else {
   448 				} else {
   397 					Log.e("Netplay", "Got accepted message for team that was never requested.");
   449 					Log.e("Netplay", "Got accepted message for team that was never requested.");
   398 				}
   450 				}
   399 				break;
   451 				break;
   400 			}
   452 			}
   401 			case MSG_TEAM_COLOR_CHANGED: {
   453 			case MSG_TEAM_COLOR_CHANGED: {
   402 				Pair<TeamInGame, Long> oldEntry = roomTeamlist.get((String)msg.obj);
   454 				TeamInGame oldEntry = roomTeamlist.get((String)msg.obj);
   403 				if(oldEntry != null) {
   455 				if(oldEntry != null) {
   404 					TeamInGame tig = oldEntry.first;
   456 					TeamIngameAttributes newAttribs = oldEntry.ingameAttribs.withColorIndex(msg.arg1);
   405 					TeamIngameAttributes tiga = tig.ingameAttribs.withColorIndex(msg.arg1);
   457 					roomTeamlist.put(oldEntry.team.name, oldEntry.withAttribs(newAttribs));
   406 					roomTeamlist.put(tig.team.name, Pair.create(tig.withAttribs(tiga), oldEntry.second));
       
   407 				} else {
   458 				} else {
   408 					Log.e("Netplay", "Color update for unknown team "+msg.obj);
   459 					Log.e("Netplay", "Color update for unknown team "+msg.obj);
   409 				}
   460 				}
   410 				break;
   461 				break;
   411 			}
   462 			}
   412 			case MSG_HOG_COUNT_CHANGED: {
   463 			case MSG_HOG_COUNT_CHANGED: {
   413 				Pair<TeamInGame, Long> oldEntry = roomTeamlist.get((String)msg.obj);
   464 				TeamInGame oldEntry = roomTeamlist.get((String)msg.obj);
   414 				if(oldEntry != null) {
   465 				if(oldEntry != null) {
   415 					TeamInGame tig = oldEntry.first;
   466 					TeamIngameAttributes newAttribs = oldEntry.ingameAttribs.withHogCount(msg.arg1);
   416 					TeamIngameAttributes tiga = tig.ingameAttribs.withHogCount(msg.arg1);
   467 					roomTeamlist.put(oldEntry.team.name, oldEntry.withAttribs(newAttribs));
   417 					roomTeamlist.put(tig.team.name, Pair.create(tig.withAttribs(tiga), oldEntry.second));
       
   418 				} else {
   468 				} else {
   419 					Log.e("Netplay", "Hog count update for unknown team "+msg.obj);
   469 					Log.e("Netplay", "Hog count update for unknown team "+msg.obj);
   420 				}
   470 				}
   421 				break;
   471 				break;
   422 			}
   472 			}
       
   473 			case MSG_ENGINE_MESSAGE: {
       
   474 				byte[] em = (byte[])msg.obj;
       
   475 				for(GameMessageListener listener : gameMessageListeners) {
       
   476 					listener.onEngineMessage(em);
       
   477 				}
       
   478 				break;
       
   479 			}
       
   480 			case MSG_RUN_GAME: {
       
   481 				GameConfig config = (GameConfig)msg.obj;
       
   482 				for(RunGameListener listener : runGameListeners) {
       
   483 					listener.runGame(config);
       
   484 				}
       
   485 				break;
       
   486 			}
       
   487 			case MSG_MAP_CHANGED: {
       
   488 				netRoomState.setMapRecipe((MapRecipe)msg.obj);
       
   489 				break;
       
   490 			}
       
   491 			case MSG_ROOM_CHIEF_STATUS_CHANGED: {
       
   492 				netRoomState.setChief((Boolean)msg.obj);
       
   493 				break;
       
   494 			}
       
   495 			case MSG_SCHEME_CHANGED: {
       
   496 				netRoomState.setScheme((Scheme)msg.obj);
       
   497 				break;
       
   498 			}
       
   499 			case MSG_SCRIPT_CHANGED: {
       
   500 				netRoomState.setGameStyle((String)msg.obj);
       
   501 				break;
       
   502 			}
       
   503 			case MSG_WEAPONSET_CHANGED: {
       
   504 				netRoomState.setWeaponset((Weaponset)msg.obj);
       
   505 				break;
       
   506 			}
   423 			default: {
   507 			default: {
   424 				Log.e("FromNetHandler", "Unknown message type: "+msg.what);
   508 				Log.e("FromNetHandler", "Unknown message type: "+msg.what);
   425 				break;
   509 				break;
   426 			}
   510 			}
   427 			}
   511 			}
   428 		}
   512 		}
   429 	}
   513 	}
   430 	
       
   431 	/**
       
   432 	 * This class handles the actual communication with the networking library, on a separate thread.
       
   433 	 */
       
   434 	private static class ThreadedNetConnection {
       
   435 		private static final long TICK_INTERVAL_FAST = 100;
       
   436 		private static final long TICK_INTERVAL_SLOW = 5000;
       
   437 		private static final Frontlib FLIB = Flib.INSTANCE;
       
   438 		
       
   439 		public final ToNetHandler toNetHandler;
       
   440 		
       
   441 		private final Context appContext;
       
   442 		private final FromNetHandler fromNetHandler;
       
   443 		private final TickHandler tickHandler;
       
   444 
       
   445 		/**
       
   446 		 * conn can only be null while connecting (the first thing in the thread), and directly after disconnecting,
       
   447 		 * in the same message (the looper is shut down on disconnect, so there will be no messages after that).
       
   448 		 */
       
   449 		private NetconnPtr conn;
       
   450 		private String playerName;
       
   451 		
       
   452 		private ThreadedNetConnection(Context appContext, FromNetHandler fromNetHandler) {
       
   453 			this.appContext = appContext;
       
   454 			this.fromNetHandler = fromNetHandler;
       
   455 			
       
   456 			HandlerThread thread = new HandlerThread("NetThread");
       
   457 			thread.start();
       
   458 			toNetHandler = new ToNetHandler(thread.getLooper());
       
   459 			tickHandler = new TickHandler(thread.getLooper(), TICK_INTERVAL_FAST, tickCb);
       
   460 		}
       
   461 		
       
   462 		private void connect(final String name, final String host, final int port) {
       
   463 			toNetHandler.post(new Runnable() {
       
   464 				public void run() {
       
   465 					playerName = name == null ? "Player" : name;
       
   466 					MetaschemePtr meta = null;
       
   467 					File dataPath;
       
   468 					try {
       
   469 						dataPath = Utils.getDataPathFile(appContext);
       
   470 					} catch (FileNotFoundException e) {
       
   471 						shutdown(true, appContext.getString(R.string.sdcard_not_mounted));
       
   472 						return;
       
   473 					}
       
   474 					String metaschemePath = new File(dataPath, "metasettings.ini").getAbsolutePath();
       
   475 					meta = FLIB.flib_metascheme_from_ini(metaschemePath);
       
   476 					if(meta == null) {
       
   477 						shutdown(true, appContext.getString(R.string.error_unexpected, "Missing metasettings.ini"));
       
   478 						return;
       
   479 					}
       
   480 					conn = FLIB.flib_netconn_create(playerName, meta, dataPath.getAbsolutePath(), host, port);
       
   481 					if(conn == null) {
       
   482 						shutdown(true, appContext.getString(R.string.error_connection_failed));
       
   483 						return;
       
   484 					}
       
   485 					FLIB.flib_netconn_onLobbyJoin(conn, lobbyJoinCb, null);
       
   486 					FLIB.flib_netconn_onLobbyLeave(conn, lobbyLeaveCb, null);
       
   487 					FLIB.flib_netconn_onRoomJoin(conn, roomJoinCb, null);
       
   488 					FLIB.flib_netconn_onRoomLeave(conn, roomLeaveCb, null);
       
   489 					FLIB.flib_netconn_onChat(conn, chatCb, null);
       
   490 					FLIB.flib_netconn_onMessage(conn, messageCb, null);
       
   491 					FLIB.flib_netconn_onRoomAdd(conn, roomAddCb, null);
       
   492 					FLIB.flib_netconn_onRoomUpdate(conn, roomUpdateCb, null);
       
   493 					FLIB.flib_netconn_onRoomDelete(conn, roomDeleteCb, null);
       
   494 					FLIB.flib_netconn_onConnected(conn, connectedCb, null);
       
   495 					FLIB.flib_netconn_onRoomlist(conn, roomlistCb, null);
       
   496 					FLIB.flib_netconn_onDisconnected(conn, disconnectCb, null);
       
   497 					FLIB.flib_netconn_onPasswordRequest(conn, passwordRequestCb, null);
       
   498 					FLIB.flib_netconn_onEnterRoom(conn, enterRoomCb, null);
       
   499 					FLIB.flib_netconn_onLeaveRoom(conn, leaveRoomCb, null);
       
   500 					FLIB.flib_netconn_onReadyState(conn, readyStateCb, null);
       
   501 					FLIB.flib_netconn_onTeamAdd(conn, teamAddedCb, null);
       
   502 					FLIB.flib_netconn_onTeamDelete(conn, teamDeletedCb, null);
       
   503 					FLIB.flib_netconn_onTeamAccepted(conn, teamAcceptedCb, null);
       
   504 					FLIB.flib_netconn_onTeamColorChanged(conn, teamColorChangedCb, null);
       
   505 					FLIB.flib_netconn_onHogCountChanged(conn, hogCountChangedCb, null);
       
   506 					
       
   507 					FLIB.flib_metascheme_release(meta);
       
   508 					tickHandler.start();
       
   509 				}
       
   510 			});
       
   511 		}
       
   512 		
       
   513 		public static ThreadedNetConnection startConnection(Context appContext, FromNetHandler fromNetHandler, String playerName, String host, int port) {
       
   514 			ThreadedNetConnection result = new ThreadedNetConnection(appContext, fromNetHandler);
       
   515 			result.connect(playerName, host, port);
       
   516 			return result;
       
   517 		}
       
   518 		
       
   519 		public void setFastTickRate(boolean fastTickRate) {
       
   520 			tickHandler.setInterval(fastTickRate ? TICK_INTERVAL_FAST : TICK_INTERVAL_SLOW);
       
   521 		}
       
   522 		
       
   523 		private final Runnable tickCb = new Runnable() {
       
   524 			public void run() {
       
   525 				FLIB.flib_netconn_tick(conn);
       
   526 			}
       
   527 		};
       
   528 		
       
   529 		private final StrCallback lobbyJoinCb = new StrCallback() {
       
   530 			public void callback(Pointer context, String name) {
       
   531 				sendFromNet(FromNetHandler.MSG_LOBBY_JOIN, name);
       
   532 			}
       
   533 		};
       
   534 		
       
   535 		private final StrStrCallback lobbyLeaveCb = new StrStrCallback() {
       
   536 			public void callback(Pointer context, String name, String msg) {
       
   537 				sendFromNet(FromNetHandler.MSG_LOBBY_LEAVE, Pair.create(name, msg));
       
   538 			}
       
   539 		};
       
   540 		
       
   541 		private final StrCallback roomJoinCb = new StrCallback() {
       
   542 			public void callback(Pointer context, String name) {
       
   543 				sendFromNet(FromNetHandler.MSG_ROOM_JOIN, name);
       
   544 			}
       
   545 		};
       
   546 		private final StrStrCallback roomLeaveCb = new StrStrCallback() {
       
   547 			public void callback(Pointer context, String name, String message) {
       
   548 				sendFromNet(FromNetHandler.MSG_ROOM_LEAVE, Pair.create(name, message));
       
   549 			}
       
   550 		};
       
   551 		private final StrStrCallback chatCb = new StrStrCallback() {
       
   552 			public void callback(Pointer context, String name, String msg) {
       
   553 				sendFromNet(FromNetHandler.MSG_CHAT, Pair.create(name, msg));
       
   554 			}
       
   555 		};
       
   556 		
       
   557 		private final IntStrCallback messageCb = new IntStrCallback() {
       
   558 			public void callback(Pointer context, int type, String msg) {
       
   559 				sendFromNet(FromNetHandler.MSG_MESSAGE, type, msg);
       
   560 			}
       
   561 		};
       
   562 		
       
   563 		private final RoomCallback roomAddCb = new RoomCallback() {
       
   564 			public void callback(Pointer context, RoomPtr roomPtr) {
       
   565 				sendFromNet(FromNetHandler.MSG_ROOM_ADD, roomPtr.deref());
       
   566 			}
       
   567 		};
       
   568 		
       
   569 		private final StrRoomCallback roomUpdateCb = new StrRoomCallback() {
       
   570 			public void callback(Pointer context, String name, RoomPtr roomPtr) {
       
   571 				sendFromNet(FromNetHandler.MSG_ROOM_UPDATE, Pair.create(name, roomPtr.deref()));
       
   572 			}
       
   573 		};
       
   574 		
       
   575 		private final StrCallback roomDeleteCb = new StrCallback() {
       
   576 			public void callback(Pointer context, final String name) {
       
   577 				sendFromNet(FromNetHandler.MSG_ROOM_DELETE, name);
       
   578 			}
       
   579 		};
       
   580 		
       
   581 		private final RoomListCallback roomlistCb = new RoomListCallback() {
       
   582 			public void callback(Pointer context, RoomArrayPtr arg1, int count) {
       
   583 				sendFromNet(FromNetHandler.MSG_ROOMLIST, arg1.getRooms(count));
       
   584 			}
       
   585 		};
       
   586 		
       
   587 		private final VoidCallback connectedCb = new VoidCallback() {
       
   588 			public void callback(Pointer context) {
       
   589 				FLIB.flib_netconn_send_request_roomlist(conn);
       
   590 				playerName = FLIB.flib_netconn_get_playername(conn);
       
   591 				sendFromNet(FromNetHandler.MSG_CONNECTED, playerName);
       
   592 			}
       
   593 		};
       
   594 		
       
   595 		private final StrCallback passwordRequestCb = new StrCallback() {
       
   596 			public void callback(Pointer context, String nickname) {
       
   597 				sendFromNet(FromNetHandler.MSG_PASSWORD_REQUEST, playerName);
       
   598 			}
       
   599 		};
       
   600 		
       
   601 		private final BoolCallback enterRoomCb = new BoolCallback() {
       
   602 			public void callback(Pointer context, boolean isChief) {
       
   603 				sendFromNet(FromNetHandler.MSG_ENTER_ROOM_FROM_LOBBY, isChief);
       
   604 			}
       
   605 		};
       
   606 		
       
   607 		private final IntStrCallback leaveRoomCb = new IntStrCallback() {
       
   608 			public void callback(Pointer context, int reason, String message) {
       
   609 				sendFromNet(FromNetHandler.MSG_LEAVE_ROOM, reason, message);
       
   610 			}
       
   611 		};
       
   612 		
       
   613 		private final StrBoolCallback readyStateCb = new StrBoolCallback() {
       
   614 			public void callback(Pointer context, String player, boolean ready) {
       
   615 				sendFromNet(FromNetHandler.MSG_READYSTATE, Pair.create(player, ready));
       
   616 			}
       
   617 		};
       
   618 		
       
   619 		private final TeamCallback teamAddedCb = new TeamCallback() {
       
   620 			public void callback(Pointer context, TeamPtr team) {
       
   621 				sendFromNet(FromNetHandler.MSG_TEAM_ADDED, team.deref().team);
       
   622 			}
       
   623 		};
       
   624 		
       
   625 		private final StrCallback teamDeletedCb = new StrCallback() {
       
   626 			public void callback(Pointer context, String teamName) {
       
   627 				sendFromNet(FromNetHandler.MSG_TEAM_DELETED, teamName);
       
   628 			}
       
   629 		};
       
   630 		
       
   631 		private final StrCallback teamAcceptedCb = new StrCallback() {
       
   632 			public void callback(Pointer context, String teamName) {
       
   633 				sendFromNet(FromNetHandler.MSG_TEAM_ACCEPTED, teamName);
       
   634 			}
       
   635 		};
       
   636 		
       
   637 		private final StrIntCallback teamColorChangedCb = new StrIntCallback() {
       
   638 			public void callback(Pointer context, String teamName, int colorIndex) {
       
   639 				sendFromNet(FromNetHandler.MSG_TEAM_COLOR_CHANGED, colorIndex, teamName);
       
   640 			}
       
   641 		};
       
   642 		
       
   643 		private final StrIntCallback hogCountChangedCb = new StrIntCallback() {
       
   644 			public void callback(Pointer context, String teamName, int hogCount) {
       
   645 				sendFromNet(FromNetHandler.MSG_HOG_COUNT_CHANGED, hogCount, teamName);
       
   646 			}
       
   647 		};
       
   648 		
       
   649 		private void shutdown(boolean error, String message) {
       
   650 			if(conn != null) {
       
   651 				FLIB.flib_netconn_destroy(conn);
       
   652 				conn = null;
       
   653 			}
       
   654 			tickHandler.stop();
       
   655 			toNetHandler.getLooper().quit();
       
   656 			sendFromNet(FromNetHandler.MSG_DISCONNECTED, Pair.create(error, message));
       
   657 		}
       
   658 		
       
   659 		private final IntStrCallback disconnectCb = new IntStrCallback() {
       
   660 			public void callback(Pointer context, int reason, String message) {
       
   661 				Boolean error = reason != Frontlib.NETCONN_DISCONNECT_NORMAL;
       
   662 				String messageForUser = createDisconnectUserMessage(appContext.getResources(), reason, message);
       
   663 				shutdown(error, messageForUser);
       
   664 			}
       
   665 		};
       
   666 		
       
   667 		private static String createDisconnectUserMessage(Resources res, int reason, String message) {
       
   668 			switch(reason) {
       
   669 			case Frontlib.NETCONN_DISCONNECT_AUTH_FAILED:
       
   670 				return res.getString(R.string.error_auth_failed);
       
   671 			case Frontlib.NETCONN_DISCONNECT_CONNLOST:
       
   672 				return res.getString(R.string.error_connection_lost);
       
   673 			case Frontlib.NETCONN_DISCONNECT_INTERNAL_ERROR:
       
   674 				return res.getString(R.string.error_unexpected, message);
       
   675 			case Frontlib.NETCONN_DISCONNECT_SERVER_TOO_OLD:
       
   676 				return res.getString(R.string.error_server_too_old);
       
   677 			default:
       
   678 				return message;
       
   679 			}
       
   680 		}
       
   681 		
       
   682 		private boolean sendFromNet(int what, Object obj) {
       
   683 			return fromNetHandler.sendMessage(fromNetHandler.obtainMessage(what, obj));
       
   684 		}
       
   685 		
       
   686 		private boolean sendFromNet(int what, int arg1, Object obj) {
       
   687 			return fromNetHandler.sendMessage(fromNetHandler.obtainMessage(what, arg1, 0, obj));
       
   688 		}
       
   689 		
       
   690 		/**
       
   691 		 * Processes messages to the networking system. Runs on a non-main thread.
       
   692 		 */
       
   693 		@SuppressLint("HandlerLeak")
       
   694 		public final class ToNetHandler extends Handler {
       
   695 			public static final int MSG_SEND_NICK = 0;
       
   696 			public static final int MSG_SEND_PASSWORD = 1;
       
   697 			public static final int MSG_SEND_QUIT = 2;
       
   698 			public static final int MSG_SEND_ROOMLIST_REQUEST = 3;
       
   699 			public static final int MSG_SEND_PLAYER_INFO_REQUEST = 4;
       
   700 			public static final int MSG_SEND_CHAT = 5;
       
   701 			public static final int MSG_SEND_FOLLOW_PLAYER = 6;
       
   702 			public static final int MSG_SEND_JOIN_ROOM = 7;
       
   703 			public static final int MSG_SEND_CREATE_ROOM = 8;
       
   704 			public static final int MSG_SEND_LEAVE_ROOM = 9;
       
   705 			public static final int MSG_SEND_KICK = 10;
       
   706 			public static final int MSG_SEND_ADD_TEAM = 11;
       
   707 			public static final int MSG_SEND_REMOVE_TEAM = 12;
       
   708 			public static final int MSG_DISCONNECT = 13;
       
   709 			public static final int MSG_SEND_TEAM_COLOR_INDEX = 14;
       
   710 			public static final int MSG_SEND_TEAM_HOG_COUNT = 15;
       
   711 			
       
   712 			public ToNetHandler(Looper looper) {
       
   713 				super(looper);
       
   714 			}
       
   715 			
       
   716 			@Override
       
   717 			public void handleMessage(Message msg) {
       
   718 				switch(msg.what) {
       
   719 				case MSG_SEND_NICK: {
       
   720 					FLIB.flib_netconn_send_nick(conn, (String)msg.obj);
       
   721 					break;
       
   722 				}
       
   723 				case MSG_SEND_PASSWORD: {
       
   724 					FLIB.flib_netconn_send_password(conn, (String)msg.obj);
       
   725 					break;
       
   726 				}
       
   727 				case MSG_SEND_QUIT: {
       
   728 					FLIB.flib_netconn_send_quit(conn, (String)msg.obj);
       
   729 					break;
       
   730 				}
       
   731 				case MSG_SEND_ROOMLIST_REQUEST: {
       
   732 					FLIB.flib_netconn_send_request_roomlist(conn);
       
   733 					break;
       
   734 				}
       
   735 				case MSG_SEND_PLAYER_INFO_REQUEST: {
       
   736 					FLIB.flib_netconn_send_playerInfo(conn, (String)msg.obj);
       
   737 					break;
       
   738 				}
       
   739 				case MSG_SEND_CHAT: {
       
   740 					if(FLIB.flib_netconn_send_chat(conn, (String)msg.obj) == 0) {
       
   741 						sendFromNet(FromNetHandler.MSG_CHAT, Pair.create(playerName, (String)msg.obj));
       
   742 					}
       
   743 					break;
       
   744 				}
       
   745 				case MSG_SEND_FOLLOW_PLAYER: {
       
   746 					FLIB.flib_netconn_send_playerFollow(conn, (String)msg.obj);
       
   747 					break;
       
   748 				}
       
   749 				case MSG_SEND_JOIN_ROOM: {
       
   750 					FLIB.flib_netconn_send_joinRoom(conn, (String)msg.obj);
       
   751 					break;
       
   752 				}
       
   753 				case MSG_SEND_CREATE_ROOM: {
       
   754 					FLIB.flib_netconn_send_createRoom(conn, (String)msg.obj);
       
   755 					break;
       
   756 				}
       
   757 				case MSG_SEND_LEAVE_ROOM: {
       
   758 					if(FLIB.flib_netconn_send_leaveRoom(conn, (String)msg.obj) == 0) {
       
   759 						sendFromNet(FromNetHandler.MSG_LEAVE_ROOM, -1, "");
       
   760 					}
       
   761 					break;
       
   762 				}
       
   763 				case MSG_SEND_KICK: {
       
   764 					FLIB.flib_netconn_send_kick(conn, (String)msg.obj);
       
   765 					break;
       
   766 				}
       
   767 				case MSG_SEND_ADD_TEAM: {
       
   768 					FLIB.flib_netconn_send_addTeam(conn, TeamPtr.createJavaOwned((Team)msg.obj));
       
   769 					break;
       
   770 				}
       
   771 				case MSG_SEND_REMOVE_TEAM: {
       
   772 					if(FLIB.flib_netconn_send_removeTeam(conn, (String)msg.obj)==0) {
       
   773 						sendFromNet(FromNetHandler.MSG_TEAM_DELETED, msg.obj);
       
   774 					}
       
   775 					break;
       
   776 				}
       
   777 				case MSG_DISCONNECT: {
       
   778 					FLIB.flib_netconn_send_quit(conn, (String)msg.obj);
       
   779 					shutdown(false, "User quit");
       
   780 					break;
       
   781 				}
       
   782 				case MSG_SEND_TEAM_COLOR_INDEX: {
       
   783 					if(FLIB.flib_netconn_send_teamColor(conn, (String)msg.obj, msg.arg1)==0) {
       
   784 						sendFromNet(FromNetHandler.MSG_TEAM_COLOR_CHANGED, msg.arg1, msg.obj);
       
   785 					}
       
   786 					break;
       
   787 				}
       
   788 				case MSG_SEND_TEAM_HOG_COUNT: {
       
   789 					if(FLIB.flib_netconn_send_teamHogCount(conn, (String)msg.obj, msg.arg1)==0) {
       
   790 						sendFromNet(FromNetHandler.MSG_HOG_COUNT_CHANGED, msg.arg1, msg.obj);
       
   791 					}
       
   792 					break;
       
   793 				}
       
   794 				default: {
       
   795 					Log.e("ToNetHandler", "Unknown message type: "+msg.what);
       
   796 					break;
       
   797 				}
       
   798 				}
       
   799 			}
       
   800 		}
       
   801 	}
       
   802 }
   514 }