5 import java.io.IOException; |
5 import java.io.IOException; |
6 import java.util.Arrays; |
6 import java.util.Arrays; |
7 import java.util.Collections; |
7 import java.util.Collections; |
8 import java.util.LinkedList; |
8 import java.util.LinkedList; |
9 import java.util.List; |
9 import java.util.List; |
10 import java.util.Map; |
|
11 import java.util.TreeMap; |
|
12 |
10 |
13 import org.hedgewars.hedgeroid.RoomStateManager; |
11 import org.hedgewars.hedgeroid.RoomStateManager; |
14 import org.hedgewars.hedgeroid.Datastructures.GameConfig; |
12 import org.hedgewars.hedgeroid.Datastructures.GameConfig; |
15 import org.hedgewars.hedgeroid.Datastructures.MapRecipe; |
13 import org.hedgewars.hedgeroid.Datastructures.MapRecipe; |
16 import org.hedgewars.hedgeroid.Datastructures.Player; |
14 import org.hedgewars.hedgeroid.Datastructures.Player; |
17 import org.hedgewars.hedgeroid.Datastructures.PlayerInRoom; |
15 import org.hedgewars.hedgeroid.Datastructures.PlayerInRoom; |
18 import org.hedgewars.hedgeroid.Datastructures.Room; |
16 import org.hedgewars.hedgeroid.Datastructures.Room; |
19 import org.hedgewars.hedgeroid.Datastructures.Scheme; |
17 import org.hedgewars.hedgeroid.Datastructures.Scheme; |
20 import org.hedgewars.hedgeroid.Datastructures.Schemes; |
18 import org.hedgewars.hedgeroid.Datastructures.Schemes; |
21 import org.hedgewars.hedgeroid.Datastructures.Team; |
|
22 import org.hedgewars.hedgeroid.Datastructures.TeamInGame; |
19 import org.hedgewars.hedgeroid.Datastructures.TeamInGame; |
23 import org.hedgewars.hedgeroid.Datastructures.TeamIngameAttributes; |
20 import org.hedgewars.hedgeroid.Datastructures.TeamIngameAttributes; |
24 import org.hedgewars.hedgeroid.Datastructures.Weaponset; |
21 import org.hedgewars.hedgeroid.Datastructures.Weaponset; |
25 import org.hedgewars.hedgeroid.Datastructures.Weaponsets; |
22 import org.hedgewars.hedgeroid.Datastructures.Weaponsets; |
26 import org.hedgewars.hedgeroid.frontlib.Flib; |
23 import org.hedgewars.hedgeroid.frontlib.Flib; |
40 |
37 |
41 /** |
38 /** |
42 * This class manages the application's networking state. |
39 * This class manages the application's networking state. |
43 */ |
40 */ |
44 public class Netplay { |
41 public class Netplay { |
45 public static enum State { NOT_CONNECTED, CONNECTING, LOBBY, ROOM, INGAME } |
42 public static enum State { NOT_CONNECTED, CONNECTING, LOBBY, ROOM } |
46 |
43 |
47 // Extras in broadcasts |
44 // Extras in broadcasts |
48 public static final String EXTRA_PLAYERNAME = "playerName"; |
45 public static final String EXTRA_PLAYERNAME = "playerName"; |
49 public static final String EXTRA_MESSAGE = "message"; |
46 public static final String EXTRA_MESSAGE = "message"; |
50 public static final String EXTRA_HAS_ERROR = "hasError"; |
47 public static final String EXTRA_HAS_ERROR = "hasError"; |
62 public static final int DEFAULT_PORT = 46631; |
59 public static final int DEFAULT_PORT = 46631; |
63 |
60 |
64 private final Context appContext; |
61 private final Context appContext; |
65 private final LocalBroadcastManager broadcastManager; |
62 private final LocalBroadcastManager broadcastManager; |
66 private final FromNetHandler fromNetHandler = new FromNetHandler(); |
63 private final FromNetHandler fromNetHandler = new FromNetHandler(); |
|
64 public final Scheme defaultScheme; |
|
65 public final Weaponset defaultWeaponset; |
67 |
66 |
68 private State state = State.NOT_CONNECTED; |
67 private State state = State.NOT_CONNECTED; |
69 private String playerName; |
68 private String playerName; |
70 |
69 |
71 // null or stale if not in room state |
70 // null or stale if not in room state |
77 public final ObservableTreeMap<String, Player> lobbyPlayerlist = new ObservableTreeMap<String, Player>(); |
76 public final ObservableTreeMap<String, Player> lobbyPlayerlist = new ObservableTreeMap<String, Player>(); |
78 public final ObservableTreeMap<String, PlayerInRoom> roomPlayerlist = new ObservableTreeMap<String, PlayerInRoom>(); |
77 public final ObservableTreeMap<String, PlayerInRoom> roomPlayerlist = new ObservableTreeMap<String, PlayerInRoom>(); |
79 public final Roomlist roomList = new Roomlist(); |
78 public final Roomlist roomList = new Roomlist(); |
80 public final MessageLog lobbyChatlog; |
79 public final MessageLog lobbyChatlog; |
81 public final MessageLog roomChatlog; |
80 public final MessageLog roomChatlog; |
82 public final ObservableTreeMap<String, TeamInGame> roomTeamlist = new ObservableTreeMap<String, TeamInGame>(); |
|
83 private final Map<String, TeamInGame> roomRequestedTeams = new TreeMap<String, TeamInGame>(); |
|
84 |
81 |
85 private final List<GameMessageListener> gameMessageListeners = new LinkedList<GameMessageListener>(); |
82 private final List<GameMessageListener> gameMessageListeners = new LinkedList<GameMessageListener>(); |
86 private final List<RunGameListener> runGameListeners = new LinkedList<RunGameListener>(); |
83 private final List<RunGameListener> runGameListeners = new LinkedList<RunGameListener>(); |
87 |
84 |
88 public Netplay(Context appContext) { |
85 public Netplay(Context appContext, Scheme defaultScheme, Weaponset defaultWeaponset) { |
89 this.appContext = appContext; |
86 this.appContext = appContext; |
90 broadcastManager = LocalBroadcastManager.getInstance(appContext); |
87 broadcastManager = LocalBroadcastManager.getInstance(appContext); |
91 lobbyChatlog = new MessageLog(appContext); |
88 lobbyChatlog = new MessageLog(appContext); |
92 roomChatlog = new MessageLog(appContext); |
89 roomChatlog = new MessageLog(appContext); |
|
90 this.defaultScheme = defaultScheme; |
|
91 this.defaultWeaponset = defaultWeaponset; |
93 } |
92 } |
94 |
93 |
95 public RoomStateManager getRoomStateManager() { |
94 public RoomStateManager getRoomStateManager() { |
96 return netRoomState; |
95 return netRoomState; |
97 } |
96 } |
103 } |
102 } |
104 |
103 |
105 private void initRoomState(boolean chief) { |
104 private void initRoomState(boolean chief) { |
106 roomChatlog.clear(); |
105 roomChatlog.clear(); |
107 roomPlayerlist.clear(); |
106 roomPlayerlist.clear(); |
108 roomTeamlist.clear(); |
107 netRoomState.initRoomState(chief); |
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 } |
108 } |
124 |
109 |
125 public void registerGameMessageListener(GameMessageListener listener) { |
110 public void registerGameMessageListener(GameMessageListener listener) { |
126 gameMessageListeners.add(listener); |
111 gameMessageListeners.add(listener); |
127 } |
112 } |
174 public void sendFollowPlayer(String nick) { sendToNet(MSG_SEND_FOLLOW_PLAYER, nick); } |
159 public void sendFollowPlayer(String nick) { sendToNet(MSG_SEND_FOLLOW_PLAYER, nick); } |
175 public void sendJoinRoom(String name) { sendToNet(MSG_SEND_JOIN_ROOM, name); } |
160 public void sendJoinRoom(String name) { sendToNet(MSG_SEND_JOIN_ROOM, name); } |
176 public void sendCreateRoom(String name) { sendToNet(MSG_SEND_CREATE_ROOM, name); } |
161 public void sendCreateRoom(String name) { sendToNet(MSG_SEND_CREATE_ROOM, name); } |
177 public void sendLeaveRoom(String message) { sendToNet(MSG_SEND_LEAVE_ROOM, message); } |
162 public void sendLeaveRoom(String message) { sendToNet(MSG_SEND_LEAVE_ROOM, message); } |
178 public void sendKick(String player) { sendToNet(MSG_SEND_KICK, player); } |
163 public void sendKick(String player) { sendToNet(MSG_SEND_KICK, player); } |
179 public void sendAddTeam(Team newTeam, int colorIndex) { |
|
180 TeamIngameAttributes tia = new TeamIngameAttributes(playerName, colorIndex, TeamIngameAttributes.DEFAULT_HOG_COUNT, false); |
|
181 TeamInGame newTeamInGame = new TeamInGame(newTeam, tia); |
|
182 roomRequestedTeams.put(newTeam.name, newTeamInGame); |
|
183 sendToNet(MSG_SEND_ADD_TEAM, newTeamInGame); |
|
184 } |
|
185 public void sendRemoveTeam(String teamName) { sendToNet(MSG_SEND_REMOVE_TEAM, teamName); } |
|
186 public void changeTeamColorIndex(String teamName, int colorIndex) { |
|
187 if(isChief()) { |
|
188 sendToNet(MSG_SEND_TEAM_COLOR_INDEX, colorIndex, teamName); |
|
189 TeamInGame team = roomTeamlist.get(teamName); |
|
190 roomTeamlist.put(teamName, team.withAttribs(team.ingameAttribs.withColorIndex(colorIndex))); |
|
191 } |
|
192 } |
|
193 public void changeTeamHogCount(String teamName, int hogCount) { |
|
194 if(isChief()) { |
|
195 sendToNet(MSG_SEND_TEAM_HOG_COUNT, hogCount, teamName); |
|
196 TeamInGame team = roomTeamlist.get(teamName); |
|
197 roomTeamlist.put(teamName, team.withAttribs(team.ingameAttribs.withHogCount(hogCount))); |
|
198 } |
|
199 } |
|
200 public void sendEngineMessage(byte[] engineMessage) { sendToNet(MSG_SEND_ENGINE_MESSAGE, engineMessage); } |
164 public void sendEngineMessage(byte[] engineMessage) { sendToNet(MSG_SEND_ENGINE_MESSAGE, engineMessage); } |
201 public void sendRoundFinished(boolean withoutError) { sendToNet(MSG_SEND_ROUND_FINISHED, Boolean.valueOf(withoutError)); } |
165 public void sendRoundFinished(boolean withoutError) { sendToNet(MSG_SEND_ROUND_FINISHED, Boolean.valueOf(withoutError)); } |
202 public void sendToggleReady() { sendToNet(MSG_SEND_TOGGLE_READY); } |
166 public void sendToggleReady() { sendToNet(MSG_SEND_TOGGLE_READY); } |
|
167 public void sendStartGame() { sendToNet(MSG_SEND_START_GAME); } |
203 |
168 |
204 public void disconnect() { sendToNet(MSG_DISCONNECT, "User Quit"); } |
169 public void disconnect() { sendToNet(MSG_DISCONNECT, "User Quit"); } |
205 |
170 |
206 private static Netplay instance; |
171 private static Netplay instance; |
207 |
172 |
216 if(instance == null) { |
181 if(instance == null) { |
217 // We'll just do it here and never quit it again... |
182 // We'll just do it here and never quit it again... |
218 if(Flib.INSTANCE.flib_init() != 0) { |
183 if(Flib.INSTANCE.flib_init() != 0) { |
219 throw new RuntimeException("Unable to start frontlib"); |
184 throw new RuntimeException("Unable to start frontlib"); |
220 } |
185 } |
221 instance = new Netplay(applicationContext); |
186 |
|
187 // We will need some default values for rooms, best load them here |
|
188 Scheme defaultScheme = null; |
|
189 Weaponset defaultWeaponset = null; |
|
190 try { |
|
191 List<Scheme> schemes = Schemes.loadBuiltinSchemes(applicationContext); |
|
192 for(Scheme scheme : schemes) { |
|
193 if(scheme.name.equals(GameConfig.DEFAULT_SCHEME)) { |
|
194 defaultScheme = scheme; |
|
195 } |
|
196 } |
|
197 List<Weaponset> weaponsets = Weaponsets.loadBuiltinWeaponsets(applicationContext); |
|
198 for(Weaponset weaponset : weaponsets) { |
|
199 if(weaponset.name.equals(GameConfig.DEFAULT_WEAPONSET)) { |
|
200 defaultWeaponset = weaponset; |
|
201 } |
|
202 } |
|
203 } catch(IOException e) { |
|
204 throw new RuntimeException(e); |
|
205 } |
|
206 |
|
207 if(defaultScheme==null || defaultWeaponset==null) { |
|
208 throw new RuntimeException("Unable to load default scheme or weaponset"); |
|
209 } |
|
210 |
|
211 instance = new Netplay(applicationContext, defaultScheme, defaultWeaponset); |
222 } |
212 } |
223 return instance; |
213 return instance; |
224 } |
214 } |
225 |
215 |
226 public State getState() { |
216 public State getState() { |
433 break; |
423 break; |
434 } |
424 } |
435 case MSG_TEAM_ADDED: { |
425 case MSG_TEAM_ADDED: { |
436 TeamInGame newTeam = (TeamInGame)msg.obj; |
426 TeamInGame newTeam = (TeamInGame)msg.obj; |
437 if(isChief()) { |
427 if(isChief()) { |
438 int freeColor = TeamInGame.getUnusedOrRandomColorIndex(roomTeamlist.getMap().values()); |
428 int freeColor = TeamInGame.getUnusedOrRandomColorIndex(netRoomState.getTeams().values()); |
439 sendToNet(MSG_SEND_TEAM_HOG_COUNT, newTeam.ingameAttribs.hogCount, newTeam.team.name); |
429 sendToNet(MSG_SEND_TEAM_HOG_COUNT, newTeam.ingameAttribs.hogCount, newTeam.team.name); |
440 sendToNet(MSG_SEND_TEAM_COLOR_INDEX, freeColor, newTeam.team.name); |
430 sendToNet(MSG_SEND_TEAM_COLOR_INDEX, freeColor, newTeam.team.name); |
441 newTeam = newTeam.withAttribs(newTeam.ingameAttribs.withColorIndex(freeColor)); |
431 newTeam = newTeam.withAttribs(newTeam.ingameAttribs.withColorIndex(freeColor)); |
442 } |
432 } |
443 roomTeamlist.put(newTeam.team.name, newTeam); |
433 netRoomState.putTeam(newTeam); |
444 break; |
434 break; |
445 } |
435 } |
446 case MSG_TEAM_DELETED: { |
436 case MSG_TEAM_DELETED: { |
447 roomTeamlist.remove((String)msg.obj); |
437 netRoomState.removeTeam((String)msg.obj); |
448 break; |
438 break; |
449 } |
439 } |
450 case MSG_TEAM_ACCEPTED: { |
440 case MSG_TEAM_ACCEPTED: { |
451 TeamInGame requestedTeam = roomRequestedTeams.remove(msg.obj); |
441 TeamInGame requestedTeam = netRoomState.requestedTeams.remove(msg.obj); |
452 if(requestedTeam!=null) { |
442 if(requestedTeam!=null) { |
453 roomTeamlist.put(requestedTeam.team.name, requestedTeam); |
443 netRoomState.putTeam(requestedTeam); |
454 if(isChief()) { |
444 if(isChief()) { |
455 // Not strictly necessary, but QtFrontend does it... |
445 // Not strictly necessary, but QtFrontend does it... |
456 sendToNet(MSG_SEND_TEAM_HOG_COUNT, requestedTeam.ingameAttribs.hogCount, requestedTeam.team.name); |
446 sendToNet(MSG_SEND_TEAM_HOG_COUNT, requestedTeam.ingameAttribs.hogCount, requestedTeam.team.name); |
457 } |
447 } |
458 } else { |
448 } else { |
459 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."); |
460 } |
450 } |
461 break; |
451 break; |
462 } |
452 } |
463 case MSG_TEAM_COLOR_CHANGED: { |
453 case MSG_TEAM_COLOR_CHANGED: { |
464 TeamInGame oldEntry = roomTeamlist.get((String)msg.obj); |
454 TeamInGame oldEntry = netRoomState.getTeams().get((String)msg.obj); |
465 if(oldEntry != null) { |
455 if(oldEntry != null) { |
466 /* |
456 /* |
467 * If we are chief, we ignore colors from the outside. They only come from the server |
457 * If we are chief, we ignore colors from the outside. They only come from the server |
468 * when someone adds a team then, and we override that choice anyway. |
458 * when someone adds a team then, and we override that choice anyway. |
469 * Worse, that color message arrives *after* we have overridden the color, so it would |
459 * Worse, that color message arrives *after* we have overridden the color, so it would |
470 * re-override it right back. |
460 * re-override it right back. |
471 */ |
461 */ |
472 if(!isChief()) { |
462 if(!isChief()) { |
473 TeamIngameAttributes newAttribs = oldEntry.ingameAttribs.withColorIndex(msg.arg1); |
463 TeamIngameAttributes newAttribs = oldEntry.ingameAttribs.withColorIndex(msg.arg1); |
474 roomTeamlist.put(oldEntry.team.name, oldEntry.withAttribs(newAttribs)); |
464 netRoomState.putTeam(oldEntry.withAttribs(newAttribs)); |
475 } |
465 } |
476 } else { |
466 } else { |
477 Log.e("Netplay", "Color update for unknown team "+msg.obj); |
467 Log.e("Netplay", "Color update for unknown team "+msg.obj); |
478 } |
468 } |
479 break; |
469 break; |
480 } |
470 } |
481 case MSG_HOG_COUNT_CHANGED: { |
471 case MSG_HOG_COUNT_CHANGED: { |
482 TeamInGame oldEntry = roomTeamlist.get((String)msg.obj); |
472 TeamInGame oldEntry = netRoomState.getTeams().get((String)msg.obj); |
483 if(oldEntry != null) { |
473 if(oldEntry != null) { |
484 TeamIngameAttributes newAttribs = oldEntry.ingameAttribs.withHogCount(msg.arg1); |
474 TeamIngameAttributes newAttribs = oldEntry.ingameAttribs.withHogCount(msg.arg1); |
485 roomTeamlist.put(oldEntry.team.name, oldEntry.withAttribs(newAttribs)); |
475 netRoomState.putTeam(oldEntry.withAttribs(newAttribs)); |
486 } else { |
476 } else { |
487 Log.e("Netplay", "Hog count update for unknown team "+msg.obj); |
477 Log.e("Netplay", "Hog count update for unknown team "+msg.obj); |
488 } |
478 } |
489 break; |
479 break; |
490 } |
480 } |