View Javadoc

1   package cz.cuni.amis.pogamut.ut2004.agent.module.sensomotoric;
2   
3   import java.util.Collections;
4   import java.util.HashMap;
5   import java.util.Map;
6   import java.util.logging.Level;
7   
8   import cz.cuni.amis.pogamut.base.agent.module.SensomotoricModule;
9   import cz.cuni.amis.pogamut.base.communication.worldview.IWorldView;
10  import cz.cuni.amis.pogamut.base.communication.worldview.event.IWorldEventListener;
11  import cz.cuni.amis.pogamut.base.communication.worldview.object.IWorldObjectEventListener;
12  import cz.cuni.amis.pogamut.base.communication.worldview.object.event.WorldObjectUpdatedEvent;
13  import cz.cuni.amis.pogamut.base.utils.logging.LogCategory;
14  import cz.cuni.amis.pogamut.unreal.communication.messages.UnrealId;
15  import cz.cuni.amis.pogamut.ut2004.agent.module.sensor.AgentInfo;
16  import cz.cuni.amis.pogamut.ut2004.agent.module.sensor.ItemDescriptors;
17  import cz.cuni.amis.pogamut.ut2004.bot.IUT2004BotController;
18  import cz.cuni.amis.pogamut.ut2004.bot.impl.UT2004Bot;
19  import cz.cuni.amis.pogamut.ut2004.communication.messages.ItemType;
20  import cz.cuni.amis.pogamut.ut2004.communication.messages.ItemType.Category;
21  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.ChangeWeapon;
22  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.AddInventoryMsg;
23  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.BotKilled;
24  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.Item;
25  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.ItemPickedUp;
26  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.Self;
27  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.Thrown;
28  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.WeaponUpdate;
29  import cz.cuni.amis.pogamut.ut2004.communication.translator.itemdescriptor.AmmoDescriptor;
30  import cz.cuni.amis.pogamut.ut2004.communication.translator.itemdescriptor.ItemDescriptor;
31  import cz.cuni.amis.pogamut.ut2004.communication.translator.itemdescriptor.WeaponDescriptor;
32  import cz.cuni.amis.utils.maps.LazyMap;
33  
34  /**
35   * Memory module specialized on info about the bot's weapon inventory.
36   * <p><p>
37   * It listens to various events that provides information about weapons the bot picks up as well as weapon's ammo
38   * grouping it together and providing {@link Weapon} abstraction of bot's weaponry.
39   * <p><p>
40   * It also provides a way for easy weapon changing via {@link Weaponry#changeWeapon(ItemType)} and {@link Weaponry#changeWeapon(Weapon)}.
41   * <p><p>
42   * It is designed to be initialized inside {@link IUT2004BotController#prepareBot(UT2004Bot)} method call
43   * and may be used since {@link IUT2004BotController#botInitialized(cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.GameInfo, cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.ConfigChange, cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.InitedMessage)}
44   * is called.
45   * <p><p>
46   * Hardened version - immune to NPEs (hopefully).
47   *
48   * @author Jimmy
49   */
50  public class Weaponry extends SensomotoricModule<UT2004Bot> {
51  	
52  	/**
53  	 * Returns {@link WeaponDescriptor} for a given inventory {@link UnrealId} of the weapon.
54  	 * <p><p>
55  	 * Note that there exists two types of {@link UnrealId} for every weapon:
56  	 * <ol>
57  	 * <li>item unreal id (i.e. from {@link Item})</li>
58  	 * <li>inventory unreal id (i.e. id of the weapon inside bot's inventory from {@link AddInventoryMsg})</li>
59  	 * </ol>
60  	 * 
61  	 * @param inventoryWeaponId
62  	 * @return weapon descriptor
63  	 */
64  	public WeaponDescriptor getDescriptorForId(UnrealId inventoryWeaponId) {
65  		WeaponDescriptor desc = inventoryUnrealIdToWeaponDescriptor.get(inventoryWeaponId);
66  		if (desc == null) {
67  			if (log.isLoggable(Level.WARNING)) log.warning("getDescriptorForId(): There is no WeaponDescriptor for the inventory weapon id '" + inventoryWeaponId.getStringId() + "'.");
68  		}
69  		return desc;
70  	}
71  	
72  	/**
73  	 * Returns an item type for a given inventory {@link UnrealId} of the weapon.
74  	 * <p><p>
75  	 * Note that there exists two types of {@link UnrealId} for every weapon:
76  	 * <ol>
77  	 * <li>item unreal id (i.e. from {@link Item})</li>
78  	 * <li>inventory unreal id (i.e. id of the weapon inside bot's inventory from {@link AddInventoryMsg})</li>
79  	 * </ol>
80  	 * 
81  	 * @param inventoryWeaponId
82  	 * @return
83  	 */
84  	public ItemType getItemTypeForId(UnrealId inventoryWeaponId) {
85  		WeaponDescriptor desc = inventoryUnrealIdToWeaponDescriptor.get(inventoryWeaponId);
86  		if (desc == null) {
87  			if (log.isLoggable(Level.WARNING)) log.warning("getItemTypeForId(): There is no WeaponDescriptor for the inventory weapon id '" + inventoryWeaponId.getStringId() + "'.");
88  			return null;
89  		}
90  		return desc.getPickupType();		
91  	}
92  	
93  	/**
94  	 * Returns inventory {@link UnrealId} of the weapon the bot has inside its inventory (if the bot does not have
95  	 * it, returns null).
96  	 * <p><p>
97  	 * Note that there exists two types of {@link UnrealId} for every weapon:
98  	 * <ol>
99  	 * <li>item unreal id (i.e. from {@link Item})</li>
100 	 * <li>inventory unreal id (i.e. id of the weapon inside bot's inventory from {@link AddInventoryMsg})</li>
101 	 * </ol>
102 	 * This (inventory) unreal id is the one that must be used together with {@link ChangeWeapon} command.
103 	 * 
104 	 * @param weaponType
105 	 * @return inventory unreal id (or null if the bot does not have the weapon)
106 	 */
107 	public UnrealId getWeaponInventoryId(ItemType weaponType) {
108 		return weaponTypeToInventoryUnrealId.get(weaponType);
109 	}
110 	
111 	/**
112 	 * Returns inventory {@link UnrealId} of the weapon the bot has inside its inventory (if the bot does not have
113 	 * it, returns null).
114 	 * <p><p>
115 	 * Note that there exists two types of {@link UnrealId} for every weapon:
116 	 * <ol>
117 	 * <li>item unreal id (i.e. from {@link Item})</li>
118 	 * <li>inventory unreal id (i.e. id of the weapon inside bot's inventory from {@link AddInventoryMsg})</li>
119 	 * </ol>
120 	 * This (inventory) unreal id is the one that must be used together with {@link ChangeWeapon} command.
121 	 * 
122 	 * @param weaponType
123 	 * @return inventory unreal id (or null if the bot does not have the weapon)
124 	 */
125 	public UnrealId getWeaponInventoryId(WeaponDescriptor weaponDescriptor) {
126 		if (weaponDescriptor == null) return null;
127 		if (weaponDescriptor.getPickupType() == null) {
128 			if (log.isLoggable(Level.WARNING)) log.warning("getWeaponInventoryId(): WeaponDescriptor does not have PickupType assigned!");
129 			return null;
130 		}
131 		return weaponTypeToInventoryUnrealId.get(weaponDescriptor.getPickupType());
132 	}
133 	
134 	/**
135 	 * Changes the weapon the bot is currently holding (if the bot has the weapon and its ammo > 0).
136 	 * 
137 	 * @param weaponType
138 	 * @return whether we have changed the weapon (i.e., we have some ammo for it), or we already have it prepared (i.e., is our current weapon)
139 	 */
140 	public boolean changeWeapon(ItemType weaponType) {
141 		if (weaponType == null) return false;
142 		if (weaponType.getCategory() != ItemType.Category.WEAPON) return false;
143 		Weapon weapon = getWeapon(weaponType);
144 		return changeWeapon(getWeapon(weaponType));
145 	}
146 	
147 	/**
148 	 * Changes the weapon the bot is currently holding (if the weapon's ammo is > 0).
149 	 * 
150 	 * @param weapon
151 	 * @return whether we have changed the weapon (i.e., we have some ammo for it), or we already have it prepared (i.e., is our current weapon).
152 	 */
153 	public boolean changeWeapon(Weapon weapon) {
154 		if (weapon == null) return false;
155 		if (weapon == getCurrentWeapon()) return true;
156 		if (weapon.getAmmo() <= 0) return false;
157 		if (weaponsByItemType.all.get(weapon.getType()) == null) return false;
158 		act.act(new ChangeWeapon().setId(weapon.getInventoryId().getStringId()));
159 		return true;
160 	}
161 	
162 	/**
163 	 * Returns an amount of ammo of 'ammoOrWeaponType' the bot currently has. 
164 	 * <p><p>
165 	 * If an ammo type is passed - exact number is returned.
166 	 * <p><p>
167 	 * If an weapon type is passed - its primary + secondary ammo amount is returned.
168 	 * <p><p>
169 	 * If an invalid 'ammoType' is passed, 0 is returned.
170 	 *  
171 	 * @param ammoType
172 	 * @return amount of ammo of 'ammoType'
173 	 */
174 	public int getAmmo(ItemType ammoOrWeaponType) {
175 		if (ammoOrWeaponType == null) return 0;
176 		if (ammoOrWeaponType.getCategory() == ItemType.Category.WEAPON) {
177 			if (hasSecondaryAmmoType(ammoOrWeaponType)) {
178     			return getPrimaryWeaponAmmo(ammoOrWeaponType) + getSecondaryWeaponAmmo(ammoOrWeaponType);
179     		} else {
180     			return getPrimaryWeaponAmmo(ammoOrWeaponType);
181     		}			
182 		}
183 		if (ammoOrWeaponType.getCategory() == ItemType.Category.AMMO) {
184 			return ammo.getAmmo(ammoOrWeaponType);
185 		}
186 		return 0;
187 	}
188 	
189 	/**
190 	 * Returns an amount of primary ammo of a given 'weaponType' the bot currently has.
191 	 * <p><p>
192 	 * If an invalid 'weaponType' is passed, 0 is returned.
193 	 * 
194 	 * @param weaponType
195 	 * @return amount of primary ammo of the given 'weaponType'
196 	 */
197 	public int getPrimaryWeaponAmmo(ItemType weaponType) {
198 		if (weaponType == null) return 0;
199 		if (weaponType.getCategory() != ItemType.Category.WEAPON) return 0;
200 		return ammo.getPriAmmoForWeapon(weaponType);
201 	}
202 	
203 	/**
204 	 * Returns an amount of secondary ammo of a given 'weaponType' the bot currently has.
205 	 * <p><p>
206 	 * If an invalid 'weaponType' is passed, 0 is returned.
207 	 * 
208 	 * @param weaponType
209 	 * @return amount of secondary ammo of the given 'weaponType'
210 	 */
211 	public int getSecondaryWeaponAmmo(ItemType weaponType) {
212 		if (weaponType == null) return 0;
213 		if (weaponType.getCategory() != ItemType.Category.WEAPON) return 0;
214 		return ammo.getSecAmmoForWeapon(weaponType);
215 	}
216 	
217 	/**
218 	 * Returns an amount of primary+secondary ammo of a given 'weaponType' the bot currently has.
219 	 * <p><p>
220 	 * If an invalid 'weaponType' is passed, 0 is returned.
221 	 * 
222 	 * @param weaponType
223 	 * @return amount of primary+secondary ammo of the given 'weaponType'
224 	 */
225 	public int getWeaponAmmo(ItemType weaponType) {
226 		if (weaponType == null) return 0;
227 		if (weaponType.getCategory() != ItemType.Category.WEAPON) return 0;
228 		return ammo.getAmmoForWeapon(weaponType);
229 	}
230 	
231 	/**
232 	 * Tells whether the bot has low-ammo for "weaponType" (either primary/secondary).
233 	 * <p><p>
234 	 * Low ammo is: currAmmmo / maxAmmo &lt; lowRatio
235 	 * 
236 	 * @param weaponType
237 	 * @param lowRatio
238 	 * @return
239 	 */
240 	public boolean hasLowAmmoForWeapon(ItemType weaponType, double lowRatio) {
241 		return hasPrimaryLowAmmoForWeapon(weaponType, lowRatio) || hasSecondaryLowAmmoForWeapon(weaponType, lowRatio);		
242 	}
243 
244 	/**
245 	 * Tells whether the bot has secondary low-ammo for "weaponType"
246 	 * <p><p>
247 	 * Low ammo is: currAmmmo / maxAmmo &lt; lowRatio
248 	 * 
249 	 * @param weaponType
250 	 * @param lowRatio
251 	 * @return
252 	 */
253 	public boolean hasSecondaryLowAmmoForWeapon(ItemType weaponType, double lowRatio) {
254 		int ammo = getSecondaryWeaponAmmo(weaponType);
255 		WeaponDescriptor desc = getWeaponDescriptor(weaponType);
256 		return ammo / desc.getPriMaxAmount() < lowRatio;	
257 	}
258 
259 	/**
260 	 * Tells whether the bot has primary low-ammo for "weaponType"
261 	 * <p><p>
262 	 * Low ammo is: currAmmmo / maxAmmo &lt; lowRatio
263 	 * 
264 	 * @param weaponType
265 	 * @param lowRatio
266 	 * @return
267 	 */
268 	public boolean hasPrimaryLowAmmoForWeapon(ItemType weaponType, double lowRatio) {
269 		if (!hasSecondaryAmmoType(weaponType)) return false;
270 		int ammo = getSecondaryWeaponAmmo(weaponType);
271 		WeaponDescriptor desc = getWeaponDescriptor(weaponType);
272 		return ammo / desc.getSecMaxAmount() < lowRatio;		
273 	}
274 
275 	/**
276 	 * Tells whether the bot has an ammo of 'ammoType'.
277 	 * <p><p>
278 	 * If an invalid 'ammoType' is passed, false is returned.
279 	 * 
280 	 * @param ammoType
281 	 * @return True, if the bot has at least one ammo of 'ammoType'.
282 	 */
283 	public boolean hasAmmo(ItemType ammoType) {
284 		if (ammoType == null) return false;
285 		if (ammoType.getCategory() != ItemType.Category.AMMO) return false;
286 		return ammo.getAmmo(ammoType) > 0;
287 	}
288 	
289 	/**
290 	 * Alias for {@link Weaponry#hasWeaponAmmo(ItemType)}.
291 	 * 
292 	 * @see hasWeaponAmmo(ItemType)
293 	 * 
294 	 * @param weaponType
295 	 * @return True, if the bot has any ammo for a 'weaponType'.
296 	 */
297 	public boolean hasAmmoForWeapon(ItemType weaponType) {
298 		return hasWeaponAmmo(weaponType);
299 	}
300 	
301 	
302 	/**
303 	 * Tells whether the bot has an ammo (either primary or secondary) for a given 'weaponType'.
304 	 * <p><p>
305 	 * If an invalid 'weaponType' is passed, false is returned.
306 	 * 
307 	 * @param weaponType
308 	 * @return True, if the bot has any ammo for a 'weaponType'.
309 	 */
310 	public boolean hasWeaponAmmo(ItemType weaponType) {
311 		if (weaponType == null) return false;
312 		if (weaponType.getCategory() != ItemType.Category.WEAPON) return false;
313 		return ammo.getAmmoForWeapon(weaponType) > 0;
314 	}
315 	
316 	/**
317 	 * Tells whether the bot has a primary ammo for a given 'weaponType'.
318 	 * <p><p>
319 	 * If an invalid 'weaponType' is passed, false is returned.
320 	 * 
321 	 * @param weaponType
322 	 * @return True, if the bot has primary ammo for a 'weaponType'.
323 	 */
324 	public boolean hasPrimaryWeaponAmmo(ItemType weaponType) {
325 		if (weaponType == null) return false;
326 		if (weaponType.getCategory() != ItemType.Category.WEAPON) return false;
327 		return ammo.getPriAmmoForWeapon(weaponType) > 0;
328 	}
329 	
330 	/**
331 	 * Tells whether the bot has a secondary ammo for a given 'weaponType'.
332 	 * <p><p>
333 	 * If an invalid 'weaponType' is passed, false is returned.
334 	 * 
335 	 * @param weaponType
336 	 * @return True, if the bot has secondary ammo for a 'weaponType'.
337 	 */
338 	public boolean hasSecondaryWeaponAmmo(ItemType weaponType) {
339 		if (weaponType == null) return false;
340 		if (weaponType.getCategory() != ItemType.Category.WEAPON) return false;
341 		return ammo.getSecAmmoForWeapon(weaponType) > 0;
342 	}
343 		
344 	/**
345      * Tells, whether specific weapon is in the agent's inventory.
346      * <p><p>
347 	 * If an invalid 'weaponType' is passed, false is returned.
348      *
349      * @param weaponType type of the weapon
350      * @return True, if the requested weapon is present; false otherwise.
351      */
352     public boolean hasWeapon(ItemType weaponType) {
353     	if (weaponType == null) return false;
354     	if (weaponType.getCategory() != ItemType.Category.WEAPON) return false;
355     	return weaponsByItemType.all.containsKey(weaponType);
356     }
357     
358     /**
359      * Tells, whether a weapon from the specific group is in the agent's inventory.
360      * <p><p>
361      * If an invalid 'weaponGroup' is passed, false is returned.
362      *
363      * @param weaponGroup group of the weapon
364      * @return True, if the requested weapon is present; false otherwise.
365      */
366     public boolean hasWeapon(ItemType.Group weaponGroup) {
367     	if (weaponGroup == null) return false;
368     	return weaponsByGroup.all.containsKey(weaponGroup);
369     }    
370     
371     /**
372      * Tells, whether specific weapon is in the agent's inventory && is loaded (has at least 1 primary or secondary ammo).
373      * <p><p>
374      * If an invalid 'weaponType' is passed, false is returned.
375      *
376      * @param weaponType type of the weapon
377      * @return True, if the requested weapon is present && is loaded; false otherwise.
378      */
379     public boolean isLoaded(ItemType weaponType) {
380     	if (weaponType == null) return false;
381     	if (weaponType.getCategory() != ItemType.Category.WEAPON) return false;
382     	return weaponsByItemType.allLoaded.containsKey(weaponType);
383     }
384     
385     /**
386      * Tells, whether a weapon from a specific group is in the agent's inventory && is loaded (has at least 1 primary or secondary ammo).
387      * <p><p>
388      * If an invalid 'weaponGroup' is passed, false is returned.
389      *
390      * @param weaponType type of the weapon
391      * @return True, if the requested weapon is present && is loaded; false otherwise.
392      */
393     public boolean isLoaded(ItemType.Group weaponGroup) {
394     	if (weaponGroup == null) return false;
395     	return weaponsByGroup.allLoaded.containsKey(weaponGroup);
396     }
397     
398     /**
399      * Whether the weapon has secondary ammo different from the primary.
400      * <p><p>
401      * If an invalid 'weaponType' is passed, false is returned.
402      * 
403      * @param weaponType
404      * @return
405      */
406     public boolean hasSecondaryAmmoType(ItemType weaponType) {
407     	if (weaponType == null) return false;
408     	if (weaponType.getCategory() != ItemType.Category.WEAPON) return false;
409     	WeaponDescriptor desc = (WeaponDescriptor) itemDescriptors.getDescriptor(weaponType);
410     	if (desc == null) {
411     		if (log.isLoggable(Level.WARNING)) log.warning("hasSecondaryAmmoType(): There is no weapon descriptor for the item type " + weaponType + "!");
412     		return false;
413     	}
414     	return desc.getSecAmmoItemType() != null && desc.getPriAmmoItemType() != desc.getSecAmmoItemType();    		
415     }
416     
417     /**
418      * Returns a {@link WeaponDescriptor} for a given 'weaponType' (if it is not a weapon, returns null).
419      * <p><p>
420      * The descriptor can be used to reason about the weapon suitability for actual combat.
421      * 
422      * @param weaponType
423      * @return weapon descriptor
424      */
425     public WeaponDescriptor getWeaponDescriptor(ItemType weaponType) {
426     	if (weaponType == null) return null;
427     	if (weaponType.getCategory() != ItemType.Category.WEAPON) return null;
428     	WeaponDescriptor desc = (WeaponDescriptor) itemDescriptors.getDescriptor(weaponType);
429     	if (desc == null) {
430     		if (log.isLoggable(Level.WARNING)) log.warning("getWeaponDescriptor(): There is no weapon descriptor for the item type " + weaponType + "!");
431     	}
432     	return desc;
433     }
434     
435     /**
436      * Retrieves current weapon from the agent's inventory.
437      *
438      * @return Current weapon from inventory; or null upon no current weapon.
439      *
440      * @see getCurrentPrimaryAmmo()
441      * @see getCurrentAlternateAmmo()
442      * @see AgentInfo#getCurrentWeapon()
443      */
444     public Weapon getCurrentWeapon() {
445         if (self == null) {
446             return null;
447         }
448         if (self.getWeapon() == null) {
449         	return null;
450         }
451     	WeaponDescriptor desc = inventoryUnrealIdToWeaponDescriptor.get(UnrealId.get(self.getWeapon()));
452     	if (desc == null) {
453     		if (self.getWeapon().equalsIgnoreCase(AgentInfo.NONE_WEAPON_ID)) return null;
454     		if (log.isLoggable(Level.WARNING)) log.warning("getCurrentWeapon(): There is no weapon descriptor for current bot's weapon of id: '" + self.getWeapon() + "'");
455     		return null;
456     	}
457     	return weaponsByItemType.all.get(desc.getPickupType());
458     }
459     
460     /**
461      * Tells, how much ammo is in the agent's inventory for current weapon - primary + (if has different secondary ammo type) alternate (secondary).
462      *
463      * @return Amount of ammo in the inventory.
464      * 
465      * @see getCurrentPrimaryAmmo()
466      * @see getCurrentAlternateAmmo()
467      * @see AgentInfo#getCurrentAmmo()
468      */
469     public int getCurrentAmmo() {
470     	if (getCurrentWeapon() == null) return 0;
471     	if (getCurrentWeapon().hasSecondaryAmmoType()) {
472     		return getCurrentPrimaryAmmo() + getCurrentAlternateAmmo();
473     	} else {
474     		return getCurrentPrimaryAmmo();
475     	}
476     }
477    
478     /**
479      * Tells, how much ammo is in the agent's inventory for current weapon.
480      *
481      * @return Amount of primary ammo for the current weapon.
482      *
483      * @see getCurrentAlternateAmmo()
484      * @see AgentInfo#getCurrentAmmo()
485      */
486     public int getCurrentPrimaryAmmo() {
487         // retreive from self
488         if (self == null) {
489             return 0;
490         }
491         return self.getPrimaryAmmo();
492     }
493     
494     /**
495      * Tells, how much ammo is in the agent's inventory for current weapon for
496      * alternate firing mode. (If the weapon consumes primary ammo for alternate
497      * firing mode it returns the same number as {@link Weaponry#getCurrentPrimaryAmmo()}.
498      * @return Amount of ammo in the inventory for alternate fire mode.
499      *
500      * @see getCurrentPrimaryAmmo()
501      * @see AgentInfo#getCurrentSecondaryAmmo()
502      */
503     public int getCurrentAlternateAmmo() {
504         if (self == null) {
505             return 0;
506         }
507         if (self.getWeapon() == null) {
508         	return 0;
509         }
510         WeaponDescriptor weaponDesc = inventoryUnrealIdToWeaponDescriptor.get(UnrealId.get(self.getWeapon()));
511         if (weaponDesc == null) {
512         	if (self.getWeapon().equals(AgentInfo.NONE_WEAPON_ID)) return 0;
513         	if (log.isLoggable(Level.WARNING)) log.warning("getCurrentAlternateAmmo(): There is no weapon descriptor for current bot's weapon of id: '" + self.getWeapon() + "'");
514         	return 0;
515         }
516         if (weaponDesc.getSecAmmoItemType() != null) {
517         	return self.getSecondaryAmmo();
518         } else {
519         	return self.getPrimaryAmmo();
520         }
521     }
522 
523     /**
524      * Retrieves all weapons from the agent's inventory.
525      * <p><p>
526      * NOTE: Returned map can't be modified.
527      * <p><p>
528      * NOTE: The {@link Weapon} instance is invalidated by {@link BotKilled} event, discard the instance upon bot's death.
529      *
530      * @return List of all available weapons from inventory.
531      *
532      * @see hasLoadedWeapon()
533      * @see getLoadedMeleeWeapons()
534      * @see getLoadedRangedWeapons()
535      */
536     public Map<ItemType, Weapon> getWeapons() {
537     	return Collections.unmodifiableMap(weaponsByItemType.all);
538     }
539     
540     /**
541      * Returns {@link Weapon} instance for given 'weaponType' if the bot posses it.
542      * 
543      * @see getWeapons()
544      * @param weaponType
545      * @return {@link Weapon}
546      */
547     public Weapon getWeapon(ItemType weaponType) {
548     	if (weaponType == null) return null;
549     	if (weaponType.getCategory() != ItemType.Category.WEAPON) return null;
550     	return weaponsByItemType.all.get(weaponType);    	
551     }
552     
553    
554     /**
555      * Retrieves all loaded weapons from the agent's inventory.
556      *
557      * <p>Note: <b>Shield guns</b> are never treated as loaded weapons, though
558      * they are usually <i>loaded</i>, i.e. ready to be fired.</p>
559      * <p><p>
560      * NOTE: Returned map can't be modified.
561      * <p><p>
562      * NOTE: The {@link Weapon} instance is invalidated by {@link BotKilled} event, discard the instance upon bot's death.
563      *
564      * @return List of all available weapons from inventory.
565      *
566      * @see hasLoadedWeapon()
567      * @see getLoadedMeleeWeapons()
568      * @see getLoadedRangedWeapons()
569      */
570     public Map<ItemType, Weapon> getLoadedWeapons() {
571         return Collections.unmodifiableMap(weaponsByItemType.allLoaded);
572     }
573 
574     /**
575      * Retrieves melee weapons from the agent's inventory.
576      *<p><p>
577      * NOTE: Returned map can't be modified.
578      * <p><p>
579      * NOTE: The {@link Weapon} instance is invalidated by {@link BotKilled} event, discard the instance upon bot's death.
580      *
581      * @return List of all available melee weapons from inventory.
582      *
583      * @see getLoadedMeleeWeapons()
584      */
585     public Map<ItemType, Weapon> getMeleeWeapons() {
586         return Collections.unmodifiableMap(weaponsByItemType.allMelee);
587     }
588 
589     /**
590      * Retrieves ranged weapons from the agent's inventory.
591      * <p><p>
592      * NOTE: Returned map can't be modified.
593      * <p><p>
594      * NOTE: The {@link Weapon} instance is invalidated by {@link BotKilled} event, discard the instance upon bot's death.
595      *
596      * @return List of all available ranged weapons from inventory.
597      *
598      * @see getLoadedRangedWeapons()
599      */
600     public Map<ItemType, Weapon> getRangedWeapons() {
601         return Collections.unmodifiableMap(weaponsByItemType.allRanged);
602     }
603 
604     /**
605      * Retrieves loaded melee weapons from the agent's inventory.
606      * <p><p>
607      * NOTE: Returned map can't be modified.
608      * <p><p>
609      * NOTE: The {@link Weapon} instance is invalidated by {@link BotKilled} event, discard the instance upon bot's death.
610      *
611      * @return List of all available melee weapons from inventory.
612      *
613      * @see getMeleeWeapons()
614      */
615     public Map<ItemType, Weapon> getLoadedMeleeWeapons() {
616         return Collections.unmodifiableMap(weaponsByItemType.allLoadedMelee);
617     }
618 
619     /**
620      * Retrieves loaded ranged weapons from the agent's inventory.
621      * <p><p>
622      * NOTE: Returned map can't be modified.
623      * <p><p>
624      * NOTE: The {@link Weapon} instance is invalidated by {@link BotKilled} event, discard the instance upon bot's death.
625      *
626      * @return List of all available loaded ranged weapons from inventory.
627      *
628      * @see getRangedWeapons()
629      */
630     public Map<ItemType, Weapon> getLoadedRangedWeapons() {
631         return Collections.unmodifiableMap(weaponsByItemType.allLoadedRanged);
632     }    
633     
634     /**
635      * Returns a map with current state of ammo (ammo for owned weapons as well
636      * as ammo for weapons that the bot do not have yet).
637      * <p><p>
638      * NOTE: Returned map can't be modified.
639      * 
640      * @return
641      */
642     public Map<ItemType, Integer> getAmmos() {
643     	return Collections.unmodifiableMap(ammo.ammo);
644     }
645 
646     /**
647      * Tells, whether the agent has any loaded weapon in the inventory.
648      *
649      * <p>Note: <b>Shield guns</b> are never treated as loaded weapons, though
650      * they are usually <i>loaded</i>, i.e. ready to be fired.</p>
651      *
652      * @return True, if there is a loaded weapon in the inventory.
653      *
654      * @see getLoadedWeapons()
655      */
656     public boolean hasLoadedWeapon() {
657         // are there any in the list of loaded weapons?
658         return !getLoadedWeapons().isEmpty();
659     }
660     
661     /**
662      * Tells, whether the agent has any loaded ranged weapon in the inventory.
663      *
664      *
665      * @return True, if there is a loaded ranged weapon in the inventory.
666      *
667      * @see getLoadedRangedWeapons()
668      */
669     public boolean hasLoadedRangedWeapon() {
670         // are there any in the list of loaded weapons?
671         return !getLoadedRangedWeapons().isEmpty();
672     }
673     
674     /**
675      * Tells, whether the agent has any loaded melee weapon in the inventory.
676      *
677      * <p>Note: <b>Shield guns</b> are never treated as loaded weapons, though
678      * they are usually <i>loaded</i>, i.e. ready to be fired.</p>
679      *
680      * @return True, if there is a loaded melee weapon in the inventory.
681      *
682      * @see getLoadedMeleeWeapons()
683      */
684     public boolean hasLoadedMeleeWeapon() {
685         // are there any in the list of loaded weapons?
686         return !getLoadedMeleeWeapons().isEmpty();
687     }
688     
689     /**
690      * Whether the bot possess 'weapon' that has primary or secondary ammo.
691      * @param weapon
692      * @return
693      */
694 	public boolean hasLoadedWeapon(ItemType weapon) {
695 		return hasPrimaryLoadedWeapon(weapon) || hasSecondaryLoadedWeapon(weapon);
696 	}
697     
698     /**
699      * Whether the bot possess 'weapon' that has primary ammo.
700      * @param weapon
701      * @return
702      */
703 	public boolean hasPrimaryLoadedWeapon(ItemType weapon) {
704 		Weapon w = getWeapon(weapon);
705 		if (w == null) return false;
706 		return w.getPrimaryAmmo() > 0;
707 	}
708 	
709 	/**
710      * Whether the bot possess 'weapon' that has secondary ammo.
711      * @param weapon
712      * @return
713      */
714 	public boolean hasSecondaryLoadedWeapon(ItemType weapon) {
715 		Weapon w = getWeapon(weapon);
716 		if (w == null) return false;
717 		return w.getSecondaryAmmo() > 0;
718 	}
719 
720 	/**
721 	 * Returns weaponType for a given ammoType (regardles whether it is primary or secondary ammo type).
722 	 * 
723 	 * @param priOrSecAmmoType
724 	 * @return
725 	 */
726 	public ItemType getWeaponForAmmo(ItemType priOrSecAmmoType) {
727 		for (ItemType weaponType : Category.WEAPON.getTypes()) {
728 			WeaponDescriptor desc = (WeaponDescriptor)itemDescriptors.getDescriptor(weaponType);
729 			if (desc == null) continue;
730 			if (desc.getPriAmmoItemType() == priOrSecAmmoType) return weaponType;
731 			if (desc.getSecAmmoItemType() == priOrSecAmmoType) return weaponType;
732 		}
733 		return null;
734 	}
735 
736 	/**
737 	 * Return primary-ammo {@link ItemType} for a weapon.
738 	 * @param weaponType
739 	 * @return
740 	 */
741 	public ItemType getPrimaryWeaponAmmoType(ItemType weaponType) {
742 		WeaponDescriptor desc = (WeaponDescriptor)itemDescriptors.getDescriptor(weaponType);
743 		if (desc == null) return null;
744 		return desc.getPriAmmoItemType();
745 	}
746     
747 	/**
748 	 * Return secondary-ammo {@link ItemType} for a weapon.
749 	 * @param weaponType
750 	 * @return
751 	 */
752 	public ItemType getSecondaryWeaponAmmoType(ItemType weaponType) {
753 		WeaponDescriptor desc = (WeaponDescriptor)itemDescriptors.getDescriptor(weaponType);
754 		if (desc == null) return null;
755 		return desc.getSecAmmoItemType();
756 	}
757 		
758 
759     /*========================================================================*/
760     
761     /**
762      * Storage class for ammo - taking inputs from {@link WeaponUpdateListener} and {@link ItemPickedUpListener}
763      * storing how many ammo the bot has.
764      * <p><p>
765      * Additionally it contains weapon-ammo-update method {@link Ammunition#updateWeaponAmmo(Weapon)} that
766      * notifies {@link WeaponsByKey} about ammo amount changes via {@link WeaponsByKey#ammoChanged(Object)} as well. 
767      */
768     private class Ammunition {
769     	
770     	/**
771     	 * Contains amount of ammos the bot has.
772     	 */
773     	private LazyMap<ItemType, Integer> ammo = new LazyMap<ItemType, Integer>() {
774 			@Override
775 			protected Integer create(ItemType key) {
776 				return 0;
777 			}    		
778     	};
779     
780     	/**
781     	 * Returns amount of ammo for a given type.
782     	 * @param ammoType must be from the AMMO category otherwise returns 0
783     	 * @return amount of ammo of the given type
784     	 */
785      	public int getAmmo(ItemType ammoType) {
786     		if (ammoType == null) return 0;
787     		if (ammoType.getCategory() != ItemType.Category.AMMO) return 0;
788     		return ammo.get(ammoType);
789     	}
790     	
791      	/**
792      	 * Returns amount of primary ammo for a given weapon.
793      	 * @param weapon must be from the WEAPON category otherwise returns 0
794      	 * @return amount of primary ammo for a weapon
795      	 */
796     	public int getPriAmmoForWeapon(ItemType weapon) {
797     		if (weapon == null) return 0;
798     		if (weapon.getCategory() != ItemType.Category.WEAPON) return 0;
799     		WeaponDescriptor desc = (WeaponDescriptor)itemDescriptors.getDescriptor(weapon);
800     		if (desc == null) {
801     			if (log.isLoggable(Level.WARNING)) log.warning("Ammunition.getPriAmmoForWeapon(): There is no WeaponDescriptor for the item type " + weapon + "!");
802     			return 0;
803     		}
804     		return getAmmo(desc.getPriAmmoItemType());
805     	}
806     	
807     	/**
808      	 * Returns amount of secondary ammo for a given weapon. If secondary ammo does not exist for the weapon
809      	 * returns the same value as {@link Ammunition#getPriAmmoForWeapon(ItemType)}. 
810      	 * @param weaponType must be from the WEAPON category otherwise returns 0
811      	 * @return amount of secondary ammo for a weapon
812      	 */
813     	public int getSecAmmoForWeapon(ItemType weaponType) {
814     		if (weaponType == null) return 0;
815     		if (weaponType.getCategory() != ItemType.Category.WEAPON) return 0;
816     		WeaponDescriptor desc = (WeaponDescriptor)itemDescriptors.getDescriptor(weaponType);
817     		if (desc == null) {
818     			if (log.isLoggable(Level.WARNING)) log.warning("Ammunition.getSecAmmoForWeapon(): There is no WeaponDescriptor for the item type " + weaponType + "!");
819     			return 0;
820     		}
821     		if (desc.getSecAmmoItemType() == null) return getPriAmmoForWeapon(weaponType);
822     		return getAmmo(desc.getSecAmmoItemType());
823     	}
824     	
825     	/**
826      	 * Returns amount of primary+secondary (if exists) ammo for a given weapon.
827      	 * @param weapon must be from the WEAPON category otherwise returns 0
828      	 * @return amount of primary+secondary (if exists) ammo for a weapon
829      	 */
830     	public int getAmmoForWeapon(ItemType weaponType) {
831     		if (weaponType == null) return 0;
832     		if (weaponType.getCategory() != ItemType.Category.WEAPON) return 0;
833     		WeaponDescriptor desc = (WeaponDescriptor) itemDescriptors.getDescriptor(weaponType);
834     		if (desc == null) {
835     			if (log.isLoggable(Level.WARNING)) log.warning("Ammunition.getAmmoForWeapon(): There is no WeaponDescriptor for the item type " + weaponType + "!");
836     			return 0;
837     		}
838     		if (desc.getSecAmmoItemType() != null && desc.getPriAmmoItemType()!= desc.getSecAmmoItemType()) {
839     			return getPriAmmoForWeapon(weaponType) + getSecAmmoForWeapon(weaponType);
840     		} else {
841     			return getPriAmmoForWeapon(weaponType);
842     		}
843     	}
844     	
845     	/**
846     	 * Called by {@link ItemPickedUpListener} to process ammo/weapon pickups (increase state of ammunition in the inventory).
847     	 * @param pickedUp
848     	 */
849     	public void itemPickedUp(ItemPickedUp pickedUp) {
850     		if (pickedUp == null) return;
851     		ItemDescriptor descriptor = itemDescriptors.getDescriptor(pickedUp.getType());
852     		if (descriptor == null) {
853     			if (log.isLoggable(Level.WARNING)) log.warning("Ammunition.itemPickedUp(): There is no ItemDescriptor for the item type " + pickedUp.getType() + "!");
854     			return;
855     		}
856     		
857     		if (pickedUp.getDescriptor().getPickupType() == ItemType.REDEEMER 
858     				|| pickedUp.getDescriptor().getPickupType() == ItemType.REDEEMER_AMMO
859     				|| pickedUp.getDescriptor().getPickupType() == ItemType.ION_PAINTER
860     				|| pickedUp.getDescriptor().getPickupType() == ItemType.ION_PAINTER_AMMO
861     				)
862     		
863     		{
864     			// UT2004 BUG !!! ... desc.getAmount()/priAmmo()/secAmmo() is 0 !!! ... it should be 1 !!!
865     			if (descriptor.getItemCategory() == Category.AMMO) {
866     				if (ammo.get(pickedUp.getType()) <= 0) {
867     					ammo.put(pickedUp.getType(), 1);
868     				}    				
869     			} else
870     			if (descriptor.getItemCategory() == Category.WEAPON) {
871     				WeaponDescriptor desc = (WeaponDescriptor)descriptor;
872     				if (ammo.get(desc.getPriAmmoItemType()) == 0) {
873     					ammo.put(desc.getPriAmmoItemType(), 1);
874     				}
875     			}
876     			return;
877     		}
878     		
879     		if (descriptor.getItemCategory() == Category.AMMO) {
880     			AmmoDescriptor desc = (AmmoDescriptor)descriptor;
881     			int current = getAmmo(pickedUp.getType());
882     			if (current + pickedUp.getAmount() > desc.getPriMaxAmount()) {
883     				ammo.put(pickedUp.getType(), desc.getPriMaxAmount());
884     			} else {
885     				ammo.put(pickedUp.getType(), current + pickedUp.getAmount());
886     			}
887     		} else 
888     		if (descriptor.getItemCategory() == Category.WEAPON) {
889     			WeaponDescriptor desc = (WeaponDescriptor)descriptor;
890     			
891 				if (desc.getPriAmmoItemType() != null) {
892 	    			int priAmmo = ammo.get(desc.getPriAmmoItemType());
893 
894 	    			int priWeaponAmmoPlus = pickedUp.getAmount();
895 	    			if (priAmmo + priWeaponAmmoPlus <= desc.getPriMaxAmount()) {
896 	    				ammo.put(desc.getPriAmmoItemType(), priAmmo + priWeaponAmmoPlus);
897 	    			} else {
898 	    				ammo.put(desc.getPriAmmoItemType(), desc.getPriMaxAmount());
899 	    			}
900     			}
901     			
902     			if (desc.getSecAmmoItemType() != null && desc.getSecAmmoItemType() != desc.getPriAmmoItemType()) {
903     				int secAmmo = ammo.get(desc.getSecAmmoItemType());
904     				int secWeaponAmmoPlus = pickedUp.getAmountSec();
905         			if (secAmmo + secWeaponAmmoPlus <= desc.getSecMaxAmount()) {
906         				ammo.put(desc.getSecAmmoItemType(), secAmmo + secWeaponAmmoPlus);
907         			} else {
908         				ammo.put(desc.getSecAmmoItemType(), desc.getSecMaxAmount());
909         			}        			
910     			}	    			
911     		}
912     	}
913     	
914     	/**
915     	 * Caused by 1. {@link WeaponUpdate} event, called by the {@link WeaponUpdateListener}
916          * and by 2. {@link Self} object updated event, called by the {@link SelfUpdateListener}.
917     	 * @param ammoType
918     	 * @param amount
919     	 */
920     	public void weaponUpdate(ItemType ammoType, int amount) {
921     		//log.severe("WEAPON UPDATE: " + ammoType + ", amount " + amount);
922     		
923     		if (ammoType == null) return;
924     		if (ammoType.getCategory() != ItemType.Category.AMMO) {
925     			if (log.isLoggable(Level.SEVERE)) log.severe("Ammunition.weaponUpdate: Can't update weapon ammo, unknown ammo type=" + ammoType.getName() + ", category=" + ammoType.getCategory() + ", group=" + ammoType.getGroup());
926     			return;
927     		}
928     		ammo.put(ammoType, amount);
929 		}
930             	
931     	/**
932     	 * Called by {@link BotKilledListener} to clear the storage.
933     	 */
934     	public void botKilled() {
935     		ammo.clear();
936     	}
937     	
938     	/**
939     	 * Updates weapon ammo based on current values inside 'ammo' storage.
940     	 */
941     	public void updateWeaponAmmo(Weapon weapon) {
942     		if (weapon == null) return;
943     		weapon.primaryAmmo = getAmmo(weapon.getDescriptor().getPriAmmoItemType());
944         	if (weapon.getDescriptor().getSecAmmoItemType() != null) {
945         		weapon.secondaryAmmo = getAmmo(weapon.getDescriptor().getSecAmmoItemType());
946         	}
947         	weaponsByGroup.ammoChanged(weapon.getGroup());
948         	weaponsByItemType.ammoChanged(weapon.getType());
949         	weaponsById.ammoChanged(weapon.getInventoryId());
950     	}    	
951     }
952     
953     private Ammunition ammo = new Ammunition();
954         
955     /*========================================================================*/
956     
957     /**
958      * Storage class for weapons according to some KEY.
959      */
960     private class WeaponsByKey<KEY> {
961     	/** All items picked up according by their KEY. */
962     	private HashMap<KEY, Weapon> all = new HashMap<KEY, Weapon>();
963     	/** All loaded weapons mapped by their KEY. */
964         private HashMap<KEY, Weapon> allLoaded = new HashMap<KEY, Weapon>();
965         /** All loaded weapons mapped by their KEY. */
966         private HashMap<KEY, Weapon> allMelee = new HashMap<KEY, Weapon>();
967         /** All loaded weapons mapped by their KEY. */
968         private HashMap<KEY, Weapon> allRanged = new HashMap<KEY, Weapon>();
969         /** All loaded weapons mapped by their KEY. */
970         private HashMap<KEY, Weapon> allLoadedMelee = new HashMap<KEY, Weapon>();
971         /** All loaded weapons mapped by their KEY. */
972         private HashMap<KEY, Weapon> allLoadedRanged = new HashMap<KEY, Weapon>();
973         
974         /**
975          * Adds weapon under the key into the storage. Called by {@link AddInventoryMsgListener} to introduce
976          * new weapon into the storage.
977          * @param key
978          * @param inv
979          */
980         public void add(KEY key, Weapon inv) {
981         	if (key == null || inv == null) return;
982         	if (inv.getDescriptor() == null) {
983         		if (log.isLoggable(Level.WARNING)) log.warning("WeaponsByKey.add(): Can't add weapon " + inv.getType() + " that has associated weapon descriptor == null!");
984         		return;
985         	}
986         	if (inv.getDescriptor() instanceof WeaponDescriptor) {
987         		WeaponDescriptor desc = inv.getDescriptor();
988         		all.put(key, inv);
989         		if (desc.isMelee()) {
990         			allMelee.put(key, inv);
991         			if (inv.getAmmo() > 0) {
992         				allLoadedMelee.put(key, inv);
993         				allLoaded.put(key, inv);
994         			}
995         		} else {
996         			allRanged.put(key, inv);
997         			if (inv.getAmmo() > 0) {
998         				allLoadedRanged.put(key, inv);
999         				allLoaded.put(key, inv);
1000         			}
1001         		}
1002         	}	
1003         }
1004         
1005         /**
1006          * Removes a weapon under the KEY from the storage.
1007          */
1008         public void remove(KEY key) {
1009         	all.remove(key);
1010         	allLoaded.remove(key);
1011         	allMelee.remove(key);
1012         	allRanged.remove(key);
1013         	allLoadedMelee.remove(key);
1014         	allLoadedRanged.remove(key);
1015         }
1016         
1017         /**
1018          * Inform the storage that the amount of ammo for the weapon under the 'KEY' has changed.
1019          * Called from {@link Ammunition#weaponUpdate(ItemType, int)}.
1020          */
1021         public void ammoChanged(KEY key) {
1022         	if (key == null) return;
1023         	Weapon weapon = all.get(key);
1024         	if (weapon == null) {
1025         		// we currently do not have such weapon
1026         		return;        		
1027         	}
1028         	if (weapon.getAmmo() > 0) {
1029         		if (!allLoaded.containsKey(key)) {
1030         			// we have ammo for the weapon that is not marked as LOADED!
1031         			WeaponDescriptor desc = (weapon.getDescriptor());
1032         			allLoaded.put(key, weapon);
1033         			if (desc.isMelee()) {
1034         				allLoadedMelee.put(key, weapon);
1035         			} else {
1036         				allLoadedRanged.put(key, weapon);
1037         			}
1038         		}
1039         	} else {
1040         		// ammo == 0
1041         		if (allLoaded.containsKey(key)) {
1042         			// we do not have ammo for the weapon that is marked as LOADED!
1043         			allLoaded.remove(key);
1044         			allLoadedMelee.remove(key);
1045         			allLoadedRanged.remove(key);
1046         		}
1047         	}
1048         }
1049                
1050         /**
1051          * Called by {@link BotKilledListener} to clear the storage.
1052          */
1053         public void botKilled() {
1054         	all.clear();
1055         	allLoaded.clear();
1056         	allLoadedMelee.clear();
1057         	allLoadedRanged.clear();
1058         	allMelee.clear();
1059         	allRanged.clear();
1060         }
1061         
1062     }
1063     
1064     WeaponsByKey<ItemType.Group> weaponsByGroup = new WeaponsByKey<ItemType.Group>();
1065 
1066     WeaponsByKey<ItemType> weaponsByItemType    = new WeaponsByKey<ItemType>();
1067     
1068     WeaponsByKey<UnrealId> weaponsById          = new WeaponsByKey<UnrealId>();
1069     
1070     /*===================================================================================*/
1071     
1072     private Map<ItemType, UnrealId> weaponTypeToInventoryUnrealId = new HashMap<ItemType, UnrealId>();
1073     private Map<UnrealId, WeaponDescriptor> inventoryUnrealIdToWeaponDescriptor = new HashMap<UnrealId, WeaponDescriptor>();
1074     
1075     /*===================================================================================*/
1076     
1077     /**
1078      * AddInventoryMsg listener.
1079      */
1080     private class AddInventoryMsgListener implements IWorldEventListener<AddInventoryMsg> {
1081 
1082         @Override
1083         public void notify(AddInventoryMsg event) {
1084         	if (event == null) return;
1085         	if (event.getPickupType() == null) return;
1086         	if (event.getPickupType().getCategory() != ItemType.Category.WEAPON) return;
1087         	
1088         	// DO NOT NOTIFY THE AMMO STORAGE - Another ItemPickedUp event will came.
1089         	
1090         	// create a weapon
1091         	if (event.getPickupType() == ItemType.REDEEMER) {
1092         		log.info("REDEEMER!");
1093         	}
1094         	
1095         	Weapon weapon = new Weapon(event, ammo.getPriAmmoForWeapon(event.getPickupType()), ammo.getSecAmmoForWeapon(event.getPickupType()));
1096         	
1097         	if (weapon.getDescriptor() == null) {
1098         		if (log.isLoggable(Level.SEVERE)) log.severe("AddInventoryMsgListener.notify(): There is no weapon descriptor for " + weapon.getType() + "!!! The newly gained weapon is not added to the Weaponry!");
1099         		return;
1100         	}
1101         	
1102         	// then weapon storage
1103         	weaponsByGroup.add(event.getPickupType().getGroup(), weapon);
1104         	weaponsByItemType.add(event.getPickupType(), weapon);
1105         	weaponsById.add(event.getId(), weapon);
1106         	// finally add unreal id mapping
1107         	weaponTypeToInventoryUnrealId.put(event.getPickupType(), event.getId());
1108         	inventoryUnrealIdToWeaponDescriptor.put(event.getId(), (WeaponDescriptor) event.getDescriptor());
1109         }
1110 
1111         /**
1112          * Constructor. Registers itself on the given WorldView object.
1113          * @param worldView WorldView object to listent to.
1114          */
1115         public AddInventoryMsgListener(IWorldView worldView) {
1116             worldView.addEventListener(AddInventoryMsg.class, this);
1117         }
1118     }
1119     /** ItemPickedUp listener */
1120     AddInventoryMsgListener addInventoryMsgListener;
1121 
1122     /*========================================================================*/
1123     
1124     /**
1125      * {@link ItemPickedUp} listener.
1126      * Here we will count the ammo properly.
1127      */
1128     private class ItemPickedUpListener implements IWorldEventListener<ItemPickedUp> {
1129 
1130         @Override
1131         public void notify(ItemPickedUp event) {
1132         	if (event == null) return;
1133         	if (event.getType() == null) return;
1134         	if (event.getType().getCategory() == Category.AMMO || event.getType().getCategory() == Category.WEAPON) {
1135         		ammo.itemPickedUp(event);
1136         		Weapon weapon;
1137         		if (event.getType().getCategory() == Category.AMMO) {
1138         			ItemType weaponType = itemDescriptors.getWeaponForAmmo(event.getType());
1139         			if (weaponType == null) {
1140         				if (log.isLoggable(Level.WARNING)) log.warning("ItemPickedUpListener.notify(): There is no weapon for the ammo " + event.getType() + ", the weapon probably can not be found in this map.");
1141         				return;
1142         			}
1143         			weapon = weaponsByItemType.all.get(weaponType);        			        		
1144         		} else {
1145         			// Category.WEAPON
1146         			ItemType weaponType = event.getType();
1147         			weapon = weaponsByItemType.all.get(weaponType);        			
1148         		}
1149         		if (weapon != null) {
1150         			ammo.updateWeaponAmmo(weapon);            		
1151         		}
1152         	}
1153         }        
1154 
1155 		/**
1156          * Constructor. Registers itself on the given WorldView object.
1157          * @param worldView WorldView object to listent to.
1158          */
1159         public ItemPickedUpListener(IWorldView worldView) {
1160             worldView.addEventListener(ItemPickedUp.class, this);
1161         }
1162     }
1163     /** ItemPickedUp listener */
1164     ItemPickedUpListener itemPickedUpListener;
1165 
1166     /*========================================================================*/
1167     
1168     /**
1169      * WeaponUpdate listener.
1170      * When we change weapon, we need to update ammo of the old weapon - because of
1171      * the delay in synchronous batches.
1172      */
1173     private class WeaponUpdateListener implements IWorldEventListener<WeaponUpdate> {
1174 
1175         @Override
1176         public void notify(WeaponUpdate event) {
1177         	if (event == null) return;
1178         	if (event.getInventoryType() == null) return;
1179         	WeaponDescriptor weaponDesc = (WeaponDescriptor)itemDescriptors.getDescriptor(event.getInventoryType());
1180         	if (weaponDesc == null) {
1181         		Weapon weapon = weaponsById.all.get(event.getId());
1182         		if (weapon != null) {
1183         			weaponDesc = weapon.getDescriptor();
1184         		}
1185         	}
1186         	if (weaponDesc == null) {    
1187         		if (log.isLoggable(Level.WARNING)) log.warning("WeaponUpdateListener.notify(): There is no weapon descriptor for the weapon for the event: " + event);
1188         		return;
1189         	}
1190         	if (weaponDesc.getPriAmmoItemType() != null) {
1191         		ammo.weaponUpdate(weaponDesc.getPriAmmoItemType(), event.getPrimaryAmmo());
1192         	}
1193         	if (weaponDesc.getSecAmmoItemType() != null && weaponDesc.getSecAmmoItemType() != weaponDesc.getPriAmmoItemType()) {
1194         		ammo.weaponUpdate(weaponDesc.getSecAmmoItemType(), event.getSecondaryAmmo());
1195         	}
1196         	
1197         	Weapon weapon = weaponsByItemType.all.get(weaponDesc.getPickupType());
1198         	if (weapon != null) {
1199         		ammo.updateWeaponAmmo(weapon);
1200         	}
1201         }
1202 
1203         /**
1204          * Constructor. Registers itself on the given WorldView object.
1205          * @param worldView WorldView object to listent to.
1206          */
1207         public WeaponUpdateListener(IWorldView worldView) {
1208             worldView.addEventListener(WeaponUpdate.class, this);
1209         }
1210     }
1211     /** WeaponUpdate listener */
1212     WeaponUpdateListener weaponUpdateListener;
1213 
1214 
1215     /*========================================================================*/
1216 
1217     /**
1218      * SelfUpdate listener.
1219      * When the bot shoot, information about current ammo in Self message is changing.
1220      * We will update ammo according to this information.
1221      */
1222     private class SelfUpdateListener implements IWorldObjectEventListener<Self, WorldObjectUpdatedEvent<Self>> {
1223 
1224         public void notify(WorldObjectUpdatedEvent<Self> event) {
1225         	if (event == null) return;
1226         	
1227             //set up self object for the first time
1228             if (self == null) {
1229                 self = event.getObject();
1230             }   
1231             
1232             Weapon weaponToUpdate = getCurrentWeapon();
1233             if (weaponToUpdate != null) {
1234 	        	WeaponDescriptor weaponDesc = weaponToUpdate.getDescriptor();
1235 	        	if (weaponDesc == null) {
1236 	        		if (log.isLoggable(Level.WARNING)) log.warning("SelfUpdateListener.notify(): There is no weapon descriptor for the weapon " + weaponToUpdate);
1237 	        		return;
1238 	        	}
1239 	        	if (weaponDesc.getPriAmmoItemType() != null) {
1240 	        		ammo.weaponUpdate(weaponDesc.getPriAmmoItemType(), self.getPrimaryAmmo());
1241 	        	}
1242 	        	if (weaponDesc.getSecAmmoItemType() != null && weaponDesc.getSecAmmoItemType() != weaponDesc.getPriAmmoItemType()) {
1243 	        		ammo.weaponUpdate(weaponDesc.getSecAmmoItemType(), self.getSecondaryAmmo());
1244 	        	}  
1245 	        	ammo.updateWeaponAmmo(weaponToUpdate);
1246             }               
1247         }
1248 
1249         /**
1250          * Constructor. Registers itself on the given WorldView object.
1251          * @param worldView WorldView object to listent to.
1252          */
1253         public SelfUpdateListener(IWorldView worldView) {
1254             worldView.addObjectListener(Self.class, WorldObjectUpdatedEvent.class, this);
1255         }
1256     }
1257     
1258     /** SelfUpdate listener */
1259     SelfUpdateListener selfUpdateListener;
1260 
1261     /*========================================================================*/
1262     
1263     /**
1264      * Thrown listener.
1265      * When we loose some weapon from the inventory we want to know about it.
1266      */
1267     private class ThrownListener implements IWorldEventListener<Thrown> {
1268 
1269         @Override
1270         public void notify(Thrown event) {
1271         	WeaponDescriptor desc = inventoryUnrealIdToWeaponDescriptor.get(event.getId());
1272         	if (desc == null) {
1273         		if (log.isLoggable(Level.WARNING)) log.warning("ThrownListener.notify(): There is no known weapon descriptor for id " + event.getId() + " inside Weaponary.");
1274         		return;
1275         	}
1276         	ItemType weaponType = desc.getPickupType();
1277         	weaponsByGroup.remove(weaponType.getGroup());
1278         	weaponsByItemType.remove(weaponType);        	
1279         }
1280 
1281         /**
1282          * Constructor. Registers itself on the given WorldView object.
1283          * @param worldView WorldView object to listen to.
1284          */
1285         public ThrownListener(IWorldView worldView) {
1286             worldView.addEventListener(Thrown.class, this);
1287         }
1288     }
1289     /** Thrown listener */
1290     ThrownListener thrownListener;
1291 
1292     /*========================================================================*/
1293     
1294     /**
1295      * Thrown listener.
1296      * When we loose some weapon from the inventory we want to know about it.
1297      */
1298     private class BotKilledListener implements IWorldEventListener<BotKilled> {
1299 
1300         @Override
1301         public void notify(BotKilled event) {
1302         	ammo.botKilled();
1303         	weaponsByGroup.botKilled();
1304         	weaponsByItemType.botKilled();
1305         	weaponsById.botKilled();
1306         	weaponTypeToInventoryUnrealId.clear();
1307         	inventoryUnrealIdToWeaponDescriptor.clear();
1308         }
1309 
1310         /**
1311          * Constructor. Registers itself on the given WorldView object.
1312          * @param worldView WorldView object to listent to.
1313          */
1314         public BotKilledListener(IWorldView worldView) {
1315             worldView.addEventListener(BotKilled.class, this);
1316         }
1317         
1318     }
1319     /** BotKilled listener */
1320     BotKilledListener botKilledListener;
1321     
1322     /*========================================================================*/
1323     
1324 	private ItemDescriptors itemDescriptors;
1325 
1326         /** Self object holding information about agent current state.
1327          Is set up in SelfUpdateListener */
1328         private Self self = null;
1329 
1330         /**
1331      * Constructor. Setups the memory module for a given bot.
1332      * @param bot owner of the module that is using it
1333      */
1334     public Weaponry(UT2004Bot bot) {
1335         this(bot, new ItemDescriptors(bot));
1336     }
1337 	
1338     /**
1339      * Constructor. Setups the memory module for a given bot.
1340      * @param bot owner of the module that is using it
1341      * @param agentInfo AgentInfo memory module.
1342      * @param itemDescriptors ItemDescriptors memory module.
1343      */
1344     public Weaponry(UT2004Bot bot, ItemDescriptors itemDescriptors) {
1345         this(bot, itemDescriptors, null);
1346     }
1347     
1348     /**
1349      * Constructor. Setups the memory module for a given bot.
1350      * @param bot owner of the module that is using it
1351      * @param agentInfo AgentInfo memory module.
1352      * @param itemDescriptors ItemDescriptors memory module.
1353      * @param moduleLog
1354      */
1355     public Weaponry(UT2004Bot bot, ItemDescriptors descriptors, LogCategory moduleLog) {
1356     	super(bot);
1357         
1358         this.itemDescriptors = descriptors;
1359         
1360         if (this.itemDescriptors == null) {
1361         	this.itemDescriptors = new ItemDescriptors(bot, moduleLog);
1362         }
1363 
1364         // create listeners
1365         addInventoryMsgListener = new AddInventoryMsgListener(worldView);
1366         itemPickedUpListener    = new ItemPickedUpListener(worldView);
1367         weaponUpdateListener    = new WeaponUpdateListener(worldView);
1368         selfUpdateListener      = new SelfUpdateListener(worldView);
1369         thrownListener          = new ThrownListener(worldView);
1370         botKilledListener       = new BotKilledListener(worldView);
1371         
1372         cleanUp();
1373 	}
1374 
1375     @Override
1376     protected void cleanUp() {
1377     	super.cleanUp();
1378     	// reset (clear) the weapon inventory
1379     	ammo.botKilled();
1380     	weaponsByGroup.botKilled();
1381     	weaponsByItemType.botKilled();
1382     	weaponsById.botKilled();
1383     	weaponTypeToInventoryUnrealId.clear();
1384     	inventoryUnrealIdToWeaponDescriptor.clear();
1385     }
1386 
1387     /**
1388      * Returns max ammo that the bot may have for a specified ammo type.
1389      * <p><p>
1390      * Contributed by: David Holan
1391 	 * 
1392      * @param ammoType
1393      * @return
1394      */
1395 	public int getMaxAmmo(ItemType ammoType) {
1396 		if (ammoType == null) return 0;
1397 		WeaponDescriptor weapon = getWeaponDescriptor( getWeaponForAmmo(ammoType) );
1398 		if (weapon == null) {
1399 			if (log.isLoggable(Level.WARNING)) log.warning("There is no known weapon descriptor for item type " + ammoType + " inside Weaponary.");
1400 			return 0;
1401 		}
1402     	if ( weapon.getPriAmmoItemType() == ammoType ) {
1403     		return weapon.getPriMaxAmount();
1404     	} else if ( weapon.getSecAmmoItemType() == ammoType ) {
1405     		return weapon.getSecMaxAmount();
1406     	} else {
1407     		return 0;
1408     	}
1409 	}
1410 	
1411 }