View Javadoc

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