View Javadoc

1   package cz.cuni.amis.pogamut.udk.server.impl;
2   
3   import java.util.Collection;
4   import java.util.List;
5   import java.util.logging.Level;
6   import java.util.logging.Logger;
7   
8   import com.google.inject.Inject;
9   
10  import cz.cuni.amis.pogamut.base.agent.IAgentId;
11  import cz.cuni.amis.pogamut.base.agent.state.impl.AgentStateStarting;
12  import cz.cuni.amis.pogamut.base.communication.command.IAct;
13  import cz.cuni.amis.pogamut.base.communication.connection.impl.socket.SocketConnection;
14  import cz.cuni.amis.pogamut.base.communication.connection.impl.socket.SocketConnectionAddress;
15  import cz.cuni.amis.pogamut.base.communication.worldview.IWorldView;
16  import cz.cuni.amis.pogamut.base.communication.worldview.event.IWorldEventListener;
17  import cz.cuni.amis.pogamut.base.component.bus.IComponentBus;
18  import cz.cuni.amis.pogamut.base.server.AbstractWorldServer;
19  import cz.cuni.amis.pogamut.base.utils.collections.adapters.WVVisibleObjectsSetAdapter;
20  import cz.cuni.amis.pogamut.base.utils.guice.AgentScoped;
21  import cz.cuni.amis.pogamut.base.utils.logging.IAgentLogger;
22  import cz.cuni.amis.pogamut.udk.bot.IUDKBot;
23  import cz.cuni.amis.pogamut.udk.bot.impl.NativeUDKBotAdapter;
24  import cz.cuni.amis.pogamut.udk.bot.jmx.BotJMXProxy;
25  import cz.cuni.amis.pogamut.udk.communication.messages.gbcommands.AddBot;
26  import cz.cuni.amis.pogamut.udk.communication.messages.gbcommands.GetMaps;
27  import cz.cuni.amis.pogamut.udk.communication.messages.gbcommands.PasswordReply;
28  import cz.cuni.amis.pogamut.udk.communication.messages.gbcommands.Ready;
29  import cz.cuni.amis.pogamut.udk.communication.messages.gbinfomessages.GameInfo;
30  import cz.cuni.amis.pogamut.udk.communication.messages.gbinfomessages.MapList;
31  import cz.cuni.amis.pogamut.udk.communication.messages.gbinfomessages.Mutator;
32  import cz.cuni.amis.pogamut.udk.communication.messages.gbinfomessages.Password;
33  import cz.cuni.amis.pogamut.udk.communication.messages.gbinfomessages.Player;
34  import cz.cuni.amis.pogamut.udk.communication.translator.shared.events.MapPointListObtained;
35  import cz.cuni.amis.pogamut.udk.communication.translator.shared.events.PlayerListObtained;
36  import cz.cuni.amis.pogamut.udk.communication.translator.shared.events.ReadyCommandRequest;
37  import cz.cuni.amis.pogamut.udk.communication.worldview.map.UDKMap;
38  import cz.cuni.amis.pogamut.udk.server.IUDKServer;
39  import cz.cuni.amis.utils.collections.ObservableCollection;
40  import cz.cuni.amis.utils.collections.TranslatedObservableCollection;
41  import cz.cuni.amis.utils.flag.Flag;
42  
43  /**
44   * Abstract class - ancestor of all UT2004 server controls.
45   * <p>
46   * <p>
47   * It counts with GameBots2004 protocol therefore taking care of:
48   * <ol>
49   * <li>ReadyCommandRequest - sending automatically ready(), override
50   * readyCommandRequested() if you're not comfortable with this</li>
51   * <li>Password - when password is requested it calls method
52   * createPasswordReply()</li>
53   * </ol>
54   * <p>
55   * <p>
56   * Also introducing user-method for setting up custom worldview listeners that
57   * is called before Ready message is sent - prePrepareServer().
58   * <p>
59   * <p>
60   * You may use setPassword() method to specify the password before starting the
61   * agent.
62   * 
63   * @author Jimmy
64   */
65  @AgentScoped
66  public abstract class AbstractUDKServer<WORLD_VIEW extends IWorldView, ACT extends IAct> extends AbstractWorldServer<WORLD_VIEW, ACT, IUDKBot> implements IUDKServer {
67  
68  	ObservableCollection<Player> players = null;
69  	
70  	List<Mutator> mutators = null;
71  	
72  	Flag<Double> gameSpeed = new Flag<Double>();
73  	
74  	List<MapList> maps = null;
75  	
76  	Flag<String> mapName = new Flag<String>();
77  	
78  	private UDKMap map;
79  	
80  	ObservableCollection<? extends NativeUDKBotAdapter> nativeAgents = null;
81  	
82  	/**
83  	 * Collection of all connected Pogamut bots.
84  	 */
85  	ObservableCollection<IUDKBot> agents = null;
86  	
87  	/**
88  	 * If specified - used for the construction of the PasswordReply in
89  	 * createPasswordReply() method.
90  	 */
91  	private String desiredPassword = null;
92  	
93  	private IWorldEventListener<PlayerListObtained> playerListObtainedListener = new IWorldEventListener<PlayerListObtained>() {
94  
95  		@Override
96  		public void notify(PlayerListObtained event) {
97  			players.addAll(event.getPlayers());
98  			// players list is received as the last in initial communication
99  		}
100 	};
101 
102 	private IWorldEventListener<MapPointListObtained> mapPointListObtainedListener = new IWorldEventListener<MapPointListObtained>() {
103 
104 		@Override
105 		public void notify(MapPointListObtained event) {
106 			// TODO process the navpoints
107 			// ask for maps on the server
108 			getAct().act(new GetMaps());
109 		}
110 	};
111 	private SocketConnection connection;
112 
113 	@Inject
114 	public AbstractUDKServer(IAgentId agentId, IAgentLogger agentLogger,
115 			IComponentBus bus, SocketConnection connection,
116 			WORLD_VIEW worldView, ACT act) {
117 		super(agentId, agentLogger, bus, worldView, act);
118 
119 		this.connection = connection;
120 
121 		getWorldView().addEventListener(ReadyCommandRequest.class, readyCommandRequestListener);
122 		getWorldView().addEventListener(Password.class, passwordRequestedListener);
123 
124 		// listen for initial players list
125 		getWorldView().addEventListener(PlayerListObtained.class, playerListObtainedListener);
126 
127 		getWorldView().addEventListener(MapPointListObtained.class, mapPointListObtainedListener);
128 		
129 		players = new WVVisibleObjectsSetAdapter<Player>(Player.class, getWorldView());
130 	}
131 
132 	public void setAddress(String host, int port) { 
133 		if (log.isLoggable(Level.WARNING)) log.warning("Setting address to: " + host + ":" + port);
134 		this.connection.setAddress(new SocketConnectionAddress(host, port));
135 	}
136 
137 	/**
138 	 * Specify the password that should be used if required by the world.
139 	 * 
140 	 * @param password
141 	 */
142 	public void setPassword(String password) {
143 		this.desiredPassword = password;
144 	}
145 
146 	// --------------
147 	// -=-=-=-=-=-=-=
148 	// READY LISTENER
149 	// -=-=-=-=-=-=-=
150 	// --------------
151 	/**
152 	 * This method is called whenever HelloBot message is parsed - the
153 	 * GameBots2004 is awaiting the bot to reply with Ready command to begin the
154 	 * handshake.
155 	 */
156 	protected void readyCommandRequested() {
157 		getAct().act(new Ready());
158 	}
159 
160 	/**
161 	 * Listener that is hooked to WorldView awaiting event ReadyCommandRequest
162 	 * calling setupWorldViewListeners() and then readyCommandRequested() method
163 	 * upon receiving the event.
164 	 */
165 	private IWorldEventListener<ReadyCommandRequest> readyCommandRequestListener = new IWorldEventListener<ReadyCommandRequest>() {
166 
167 		@Override
168 		public void notify(ReadyCommandRequest event) {
169 			setState(new AgentStateStarting("GameBots2004 greeted us, sending READY."));
170 			readyCommandRequested();
171 			setState(new AgentStateStarting("READY sent."));
172 		}
173 	};
174 	// -----------------
175 	// -=-=-=-=-=-=-=-=-
176 	// PASSWORD LISTENER
177 	// -=-=-=-=-=-=-=-=-
178 	// -----------------
179 	/**
180 	 * Instance of the password reply command that was sent upon receivieng
181 	 * request for the password (the world is locked).
182 	 * <p>
183 	 * <p>
184 	 * If null the password was not required by the time the bot connected to
185 	 * the world.
186 	 */
187 	private PasswordReply passwordReply = null;
188 
189 	/**
190 	 * Instance of the password reply command that was sent upon receivieng
191 	 * request for the password (the world is locked).
192 	 * <p>
193 	 * <p>
194 	 * If null the password was not required by the time the bot connected to
195 	 * the world.
196 	 * 
197 	 * @return
198 	 */
199 	public PasswordReply getPasswordReply() {
200 		return passwordReply;
201 	}
202 
203 	/**
204 	 * This method is called whenever the Password event is caught telling us
205 	 * the world is locked and is requiring a password.
206 	 * <p>
207 	 * <p>
208 	 * May return null - in that case an empty password is sent to the server
209 	 * (which will probably result in closing the connection and termination of
210 	 * the agent).
211 	 * <p>
212 	 * <p>
213 	 * This message is then saved to private field passwordReply and is
214 	 * accessible via getPasswordReply() method if required to be probed during
215 	 * the bot's runtime.
216 	 * <p>
217 	 * <p>
218 	 * Note that if setPassword() method is called before this one it will use
219 	 * provided password via that method.
220 	 */
221 	protected PasswordReply createPasswordReply() {
222 		return desiredPassword != null ? new PasswordReply(desiredPassword)
223 				: null;
224 	}
225 
226 	/**
227 	 * Listener that is hooked to WorldView awaiting event InitCommandRequest
228 	 * calling initCommandRequested() method upon receiving the event.
229 	 */
230 	private IWorldEventListener<Password> passwordRequestedListener = new IWorldEventListener<Password>() {
231 
232 		@Override
233 		public void notify(Password event) {
234 			setState(new AgentStateStarting("Password requested by the world."));
235 			passwordReply = createPasswordReply();
236 			if (passwordReply == null) {
237 				passwordReply = new PasswordReply("");
238 			}
239 			if (log.isLoggable(Level.INFO)) log.info("Password required for the world, replying with '"
240 					+ passwordReply.getPassword() + "'.");
241 			getAct().act(passwordReply);
242 		}
243 	};
244 	// -------------------------
245 	// -=-=-=-=-=-=-=-=-=-=-=-=-
246 	// GAMEINFO MESSAGE LISTENER
247 	// -=-=-=-=-=-=-=-=-=-=-=-=-
248 	// -------------------------
249 	/**
250 	 * Contains information about the game.
251 	 */
252 	private GameInfo gameInfo = null;
253 
254 	public GameInfo getGameInfo() {
255 		if (gameInfo == null) {
256 			gameInfo = getWorldView().getSingle(GameInfo.class);
257 		}
258 		return gameInfo;
259 	}
260 
261 	@Override
262 	public WORLD_VIEW getWorldView() {
263 		return super.getWorldView();
264 	}
265 
266 	@Override
267 	public Collection<MapList> getAvailableMaps() {
268 		return maps;
269 	}
270 
271 	@Override
272 	public Flag<Double> getGameSpeedFlag() {
273 		return gameSpeed;
274 	}
275 
276 	@Override
277 	public String getMapName() {
278 		if (getGameInfo() == null) return null;
279 		return getGameInfo().getLevel();
280 	}
281 
282 	@Override
283 	public ObservableCollection<Player> getPlayers() {
284 		return players;
285 	}
286 	
287 	@Override
288 	public List<Mutator> getMutators() {
289 		return mutators;
290 	}
291 
292 	@Override
293 	public ObservableCollection<IUDKBot> getAgents() {
294 		if (agents != null) {
295 			return agents;
296 		}
297 
298 		if (getPlayers() == null) {
299 			// the info has not been initialized yet
300 			return null;
301 		} else {
302 			agents = new TranslatedObservableCollection<IUDKBot, Player>(
303 					getPlayers()) {
304 
305 				@Override
306 				protected IUDKBot translate(Player obj) {
307 					if (obj.getJmx() != null) {
308 						try {
309 							// the player represents Pogamut agent
310 							return new BotJMXProxy(obj.getJmx());
311 						} catch (Exception ex) {
312 							// communication failed
313 							Logger.getLogger(
314 									AbstractUDKServer.class.getName()).log(
315 									Level.SEVERE, "JMX error", ex);
316 							throw new RuntimeException(ex);
317 						}
318 					} else {
319 						return null;
320 					}
321 				}
322 
323 				@Override
324 				protected Object getKeyForObj(Player elem) {
325 					return elem.getId();
326 				}
327 			};
328 		}
329 		return agents;
330 	}
331 
332 	@Override
333 	public ObservableCollection<? extends NativeUDKBotAdapter> getNativeAgents() {
334 		if (nativeAgents != null) {
335 			return nativeAgents;
336 		}
337 
338 		if (getPlayers() == null) {
339 			// the info has not been initialized yet
340 			return null;
341 		} else {
342 
343 			nativeAgents = new TranslatedObservableCollection<NativeUDKBotAdapter, Player>(
344 					getPlayers()) {
345 
346 				@Override
347 				protected NativeUDKBotAdapter translate(Player obj) {
348 					if (obj.getJmx() == null) {
349 						try {
350 							// the player representing native bot
351 							return new NativeUDKBotAdapter(obj,
352 									AbstractUDKServer.this, getAct(),
353 									getWorldView());
354 						} catch (Exception ex) {
355 							// communication failed
356 							Logger.getLogger(
357 									AbstractUDKServer.class.getName()).log(
358 									Level.SEVERE, "JMX error", ex);
359 							throw new RuntimeException(ex);
360 						}
361 					} else {
362 						return null;
363 					}
364 				}
365 
366 				@Override
367 				protected Object getKeyForObj(Player elem) {
368 					return elem.getId();
369 				}
370 			};
371 		}
372 		return nativeAgents;
373 
374 	}
375 
376 	@Override
377 	public void connectNativeBot(String botName, String botType) {
378 		getAct().act(new AddBot(botName, null, null, 3, botType));
379 	}
380 
381 	@Override
382 	public UDKMap getMap() {
383 		if (map == null) {
384 			map = new UDKMap(getWorldView());
385 		}
386 		return map;
387 	}
388 	
389 	/////
390 	//
391 	// LIFECYCLE METHODS (starting/stopping server)
392 	//
393 	/////
394 	
395 	/**
396 	 * Called during stop/kill/reset events.
397 	 */
398 	protected void reset() {
399 		map = null;
400 		gameInfo = null;
401 		if (players != null) players.clear();
402 		if (mutators != null) mutators.clear();
403 		if (maps != null) maps.clear();
404 		if (nativeAgents != null) nativeAgents.clear();
405 		if (agents != null) agents.clear();
406 	}
407 	
408 	@Override
409 	protected void resetAgent() {
410 		super.resetAgent();
411 		reset();
412 	}
413 		
414 	@Override
415 	protected void stopAgent() {
416 		super.stopAgent();
417 		reset();
418 	}
419 	
420 	@Override
421 	protected void killAgent() {
422 		super.killAgent();
423 		reset();
424 	}
425 	
426 	@Override
427 	protected void startAgent() {
428 		super.startAgent();
429 	}	
430 
431 }