View Javadoc

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