package cz.cuni.pogamut.cup2013.bot;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;

import math.geom2d.line.Line2D;
import math.geom3d.Vector3D;
import cz.cuni.amis.pogamut.base.communication.worldview.listener.annotation.EventListener;
import cz.cuni.amis.pogamut.base.communication.worldview.listener.annotation.ObjectClassEventListener;
import cz.cuni.amis.pogamut.base.communication.worldview.object.event.WorldObjectUpdatedEvent;
import cz.cuni.amis.pogamut.base.utils.guice.AgentScoped;
import cz.cuni.amis.pogamut.base.utils.logging.LogCategory;
import cz.cuni.amis.pogamut.base.utils.math.DistanceUtils;
import cz.cuni.amis.pogamut.base3d.worldview.object.ILocated;
import cz.cuni.amis.pogamut.base3d.worldview.object.Location;
import cz.cuni.amis.pogamut.base3d.worldview.object.Rotation;
import cz.cuni.amis.pogamut.base3d.worldview.object.event.WorldObjectAppearedEvent;
import cz.cuni.amis.pogamut.base3d.worldview.object.event.WorldObjectDisappearedEvent;
import cz.cuni.amis.pogamut.unreal.communication.messages.UnrealId;
import cz.cuni.amis.pogamut.ut2004.agent.module.sensomotoric.AdrenalineCombo;
import cz.cuni.amis.pogamut.ut2004.agent.module.sensomotoric.Weapon;
import cz.cuni.amis.pogamut.ut2004.agent.module.sensomotoric.Weaponry;
import cz.cuni.amis.pogamut.ut2004.agent.module.sensor.AgentInfo;
import cz.cuni.amis.pogamut.ut2004.agent.module.sensor.Items;
import cz.cuni.amis.pogamut.ut2004.agent.module.sensor.Players;
import cz.cuni.amis.pogamut.ut2004.agent.module.sensor.WeaponPrefs;
import cz.cuni.amis.pogamut.ut2004.agent.module.sensor.visibility.Visibility;
import cz.cuni.amis.pogamut.ut2004.agent.module.utils.TabooSet;
import cz.cuni.amis.pogamut.ut2004.agent.navigation.UT2004Navigation;
import cz.cuni.amis.pogamut.ut2004.agent.navigation.UT2004PathAutoFixer;
import cz.cuni.amis.pogamut.ut2004.agent.navigation.astar.UT2004AStar;
import cz.cuni.amis.pogamut.ut2004.agent.navigation.floydwarshall.FloydWarshallMap;
import cz.cuni.amis.pogamut.ut2004.agent.navigation.stuckdetector.UT2004DistanceStuckDetector;
import cz.cuni.amis.pogamut.ut2004.agent.navigation.stuckdetector.UT2004PositionStuckDetector;
import cz.cuni.amis.pogamut.ut2004.agent.navigation.stuckdetector.UT2004TimeStuckDetector;
import cz.cuni.amis.pogamut.ut2004.bot.command.AdvancedLocomotion;
import cz.cuni.amis.pogamut.ut2004.bot.command.ImprovedShooting;
import cz.cuni.amis.pogamut.ut2004.bot.impl.UT2004Bot;
import cz.cuni.amis.pogamut.ut2004.bot.impl.UT2004BotModuleController;
import cz.cuni.amis.pogamut.ut2004.communication.messages.ItemType;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.Initialize;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.AutoTraceRay;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.BotDamaged;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.BotKilled;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.ConfigChange;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.GameInfo;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.HearNoise;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.IncomingProjectile;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.InitedMessage;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.Item;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.ItemPickedUp;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.NavPoint;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.NavPointNeighbourLink;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.Player;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.PlayerDamaged;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.PlayerKilled;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.Spawn;
import cz.cuni.amis.pogamut.ut2004.communication.translator.itemdescriptor.AdrenalineDescriptor;
import cz.cuni.amis.pogamut.ut2004.communication.translator.itemdescriptor.AmmoDescriptor;
import cz.cuni.amis.pogamut.ut2004.communication.translator.itemdescriptor.ArmorDescriptor;
import cz.cuni.amis.pogamut.ut2004.communication.translator.itemdescriptor.HealthDescriptor;
import cz.cuni.amis.pogamut.ut2004.communication.translator.itemdescriptor.OtherDescriptor;
import cz.cuni.amis.pogamut.ut2004.communication.translator.itemdescriptor.ShieldDescriptor;
import cz.cuni.amis.pogamut.ut2004.communication.translator.itemdescriptor.WeaponDescriptor;
import cz.cuni.amis.pogamut.ut2004.utils.UT2004BotRunner;
import cz.cuni.amis.pogamut.ut2004.utils.UnrealUtils;
import cz.cuni.amis.utils.Cooldown;
import cz.cuni.amis.utils.ExceptionToString;
import cz.cuni.amis.utils.Heatup;
import cz.cuni.amis.utils.IFilter;
import cz.cuni.amis.utils.collections.MyCollections;
import cz.cuni.amis.utils.exception.PogamutException;
import cz.cuni.amis.utils.maps.LazyMap;

/**
 * This class is a template for PogamutCup 2013. It serves as a basis for your competition bot.
 * 
 * <p><p>
 * First of all, you have to:
 * <ol>
 *   <li>Install Unreal Tournament 2004 game</li>
 *   <li>Install latest PogamutUT2004 platform</li>
 * </ol>
 * 
 * Unreal Tournament 2004 can be bought from Amazon or... you know ;-)
 * <p>
 * PogamutUT2004 platform installer is available: http://pogamut.cuni.cz/main/tiki-index.php?page=Download
 * <p>
 * If you have trouble using all-in-one installer, you might try manual installation according to manual from: http://pogamut.cuni.cz/main/tiki-download_file.php?fileId=22
 * 
 * <p><p>
 * Note that the only REQUIRED part is to have Unreal Tournament 2004 (UT2004) patched with the lastest patch 3369 and have latest GameBots2004 (GB2004) mod installed within UT2004.
 * 
 * <p><p>
 * Once you have UT2004+GB2004 installed, you can start GB2004 server via "${UT2004_HOME}/System/ucc.exe server DM-TrainingDay?game=GameBots2004.BotDeathMatch?timelimit=999999" (strip double-quotes)
 * <p>
 * where "DM-TrainingDay" can be substituted for arbitrary death-match map from ${UT2004_HOME}/Maps folder.
 * 
 * <p><p>
 * Once you have GB2004 dedicated server running, you can start UT2004 via "${UT2004_HOME}/System/UT2004.exe 127.0.0.1" (strip double-quotes), that will start UT2004 and connects it automatically
 * to GB2004 server.
 * 
 * <p><p>
 * Finally, you are ready to start this project.
 * 
 * <p><p>
 * If you are unable to start this example even though GB2004 server is running, check ${UT2004_HOME}/System/GameBots2004.ini file that it contains correctly set Bot/Control ports.
 * <code>
 * [GameBots2004.BotDeathMatch]
 * BotServerPort=3000
 * ControlServerPort=3001
 * bRandomPorts=False
 * </code>
 * If you alter GameBots2004.ini restart bot ucc.exe and reconnect your UT2004 GUI.
 * <p>
 * You want to get following line displayed inside GB2004 console:
 * <code>
 * BotServerPort:3000 ControlServerPort:3001 ObservingServerPort:32000
 * </code>
 * This means that GB2004 is listening on its standard ports.
 * 
 * <p><p>
 * This bot is runnable out-of-the-box and it exemplifies how basic navigation and shooting is working in PogamutUT2004 platform.
 * 
 * <p><p>
 * You should read all comments in this class carefully as they contain valuable hints how to work with PogamutUT2004 platform.
 * 
 * <p><p>
 * Note that this class has a lot of pre-instantiated "modules", see fields like (or just iterate through 'this.' auto complete hints):
 * <ul>
 *   <li>info        - {@link AgentInfo}          - contains information about YOUR/THIS bot</li>
 *   <li>players     - {@link Players}            - contains information about all OTHER bots == players</li>
 *   <li>items       - {@link Items}              - contains information about items within the map</li>
 *   <li>visibility  - {@link Visibility}         - provides visibility information between navpoints, can be used for hiding / escaping</li>
 *   <li>weaponry    - {@link Weaponry}           - provides various methods for querying your weapon inventory</li>
 *   <li>weaponPrefs - {@link WeaponPrefs}        - contain modules for definition of weapons based on range</li>
 *   <li>move        - {@link AdvancedLocomotion} - contains methods for DIRECT/STRAIGHT movement (without stuck detecting), jumping, dodging</li>
 *   <li>shoot       - {@link ImprovedShooting}   - contains methods for shooting</li>
 *   <li>navigation  - {@link UT2004Navigation}   - module for safe navigation through the environment (auto-handling teleporters, movements, stucks)</li>
 *   <li>fwMap       - {@link FloydWarshallMap}   - Floyd-Warshall algorithm implementation that contains all paths precomputed cached in itself, use it to reason about distances</li>
 *   <li>aStar       - {@link UT2004AStar}        - implementation of A* that can be used for finding custom paths (using custom heuristic / map-view)</li>
 *   <li>Utility classes:
 *   	<ul>
 *        <li>{@link MyCollections} - contains various static methods (shortcuts) for collection querying</li>
 *        <li>{@link DistanceUtils} - contains various static methods (shortcuts) for querying collections of {@link ILocated} objects</li>
 *      </ul>
 *   </li>
 * </ul>
 * 
 * <p><p>
 * You can use various methods of {@link Location} and {@link Rotation} for linear algebra (vector math).
 * <p>
 * Alternatively there are things like {@link Vector3D}, {@link Line2D}, etc.
 * 
 * <p><p>
 * Check various Pogamut tutorials from: http://pogamut.cuni.cz/pogamut_files/latest/doc/tutorials/
 * <p>
 * Especially, you might be interested in Raycasting tutorial: http://pogamut.cuni.cz/pogamut_files/latest/doc/tutorials/03-RaycastingBot.html
 * Mind the PogamutCup rules, you can use max 8 {@link AutoTraceRay}s for your bot.
 * 
 * <p><p>
 * Complete list of GameBots2004 messages / commands can be found at: http://pogamut.cuni.cz/pogamut_files/latest/doc/gamebots/ch06.html
 * 
 * <p><p>
 * Complete JavaDoc for Pogamut platform can be found at: http://pogamut.cuni.cz/pogamut_files/latest/doc/javadoc/
 * 
 * <p><p>
 * It might be beneficial for you to run through slides from Pogamut classes, they may contain valuable info that will save your time as well:
 * <p>
 * 2012 - ignore POSH and CTF - http://diana.ms.mff.cuni.cz/pogamut-devel/doku.php?id=human-like_artifical_agents_2011-12_summer_semester
 * <p>
 * 2011 - http://diana.ms.mff.cuni.cz/pogamut-devel/doku.php?id=human-like_artifical_agents_2010-11_summer_semester
 * 
 * <p><p>
 * <b>MORE PIECES OF ADVICE / HINTS</b>
 * <ol>
 *   <li>It is good in general to log.info("") all decisions of your bot + variables/states that is leading to this decision (e.g., check sources for {@link MyPathRunner}).</li>
 *   
 *   <li>Note that {@link Item} is actually not an item but "item spawning point", therefore always check {@link DMBot#isPossiblySpawned(Item)} whether item is possibly spawned on its
 *       {@link Item#getNavPoint()}, note that in case that item-spawning-point is in your field of view, that you may cross out "Possibly" from the method name ;-)</li>
 *       
 *   <li>Sole usage of {@link UT2004BotModuleController#getPathExecutor()} is deprecated by more powerful {@link UT2004BotModuleController#getNavigation()}.</li>
 *   
 *   <li>Navigation graph is obtainable by: world.getAll(NavPoint.class) and then using {@link NavPoint#getOutgoingEdges()} and {@link NavPoint#getIncomingEdges()} that
 *       can give you link information in the form of {@link NavPointNeighbourLink}.</li>
 *       
 *   <li>Note that UT2004 does not provide 'precise' MOVE command. If you try to move your bot to X,Y,Z, it typically ends somewhere near (delta 50-70 units).</li>
 *       
 *   <li>Check these slides (page 8) for more information about {@link WeaponPrefs}: http://diana.ms.mff.cuni.cz/pogamut_files/lectures/2010-2011/Pogamut3_Lecture_03.pdf</li>
 *   
 *   <li>Using classes {@link Cooldown} and {@link Heatup} may greatly speed coding of various timeouts you might need for logic of your bot.</li>
 *   
 *   <li>Check methods / inner classes of {@link MyCollections} and {@link DistanceUtils}, they may save you a lot of coding-time. See their utilization inside (for instance) 
 *       {@link DMBot#getNearestPossiblySpawnedItemsMap()}.</li>
 *   
 *   <li>Using class {@link TabooSet} may help you to quickly forbid some items you temporarily don't want to run to, etc. See {@link DMBot#botInitialized(GameInfo, ConfigChange, InitedMessage)}
 *       where is all {@link TabooSet}s should be initialized and {@link DMBot#logic()} and {@link DMBot#getRandomNavPoint()} for its utilization.</li>
 *       
 *   <li>You should (usually) try to collect following interesting (strong) items {@link ItemType#FLAK_CANNON}, {@link ItemType#MINIGUN}, {@link ItemType#LIGHTING_GUN},
 *       {@link ItemType#ROCKET_LAUNCHER}, {@link ItemType#SUPER_HEALTH}, {@link ItemType#SUPER_ARMOR}, {@link ItemType#SHIELD_PACK}, {@link ItemType#SUPER_SHIELD_PACK},
 *       {@link ItemType#U_DAMAGE_PACK}.</li>
 *       
 *   <li>If bot fails to run (ends with exception), do not be scared of lot of exception from the log... go up the log and find the FIRST exception which is the root cause.</li>
 *   
 *   <li>Note that this project contains a lot of MyXXX classes that deals with navigation. You will probably be fine not modifying them but if you want to tweak
 *       how the bot is actually navigating / running through the environment, then you should try to adjust {@link MyPathNavigator} that deals with nav-point switching
 *       and {@link MyPathRunner} that is used for straight running between 2 nav-points (you will find out that it is actually very hard to safely navigate the bot
 *       using low-level MOVE/JUMP commands.</li>
 *       
 *   <li>We advise you to pre-tweak the navigation graph for all competition maps using {@link UT2004BotModuleController#getNavBuilder()} by removing some invalid edges 
 *       (like lift-jump edges which our bots cannot travel). Use it inside {@link DMBot#botInitialized(GameInfo, ConfigChange, InitedMessage)}.
 *       
 *   <li>If you check the {@link DMBot#botInitialized(GameInfo, ConfigChange, InitedMessage)}, you will find out that we already prepared 
 *       a hook {@link MapTweaks#tweak(cz.cuni.amis.pogamut.ut2004.agent.module.sensor.NavigationGraphBuilder)} there. There is already some nav-adjusting code
 *       inside {@link MapTweaks} but we DO NOT KNOW whether more tweaks are necessary or not. Notice, that it is even possible to add new nav-link edges
 *       or even whole new navigation points into the graph. You may also set whether navigation edge requires jumping.</li>
 *       
 *   <li>When observing the game inside UT2004, press Ctrl+H to show help for GB2004 hud overlay, especially Ctrl+G (showing navigation graph in the game) is useful.</li>
 *   
 *   <li>Use {@link MyNavigation#setFocus(ILocated)} to automatically strafe during navigation, i.e., you may face your opponent while fleeing, etc.</li>
 *   
 *   <li>Note that "lift navigation" override your focus (bot has to know where the lift is, otherwise it is not possible to get on/out of it).</li>
 *   
 *   <li>You should definitely try to use {@link DMBot#getVisibility()} that provides visibility-matrix between navigation-points, i.e., it allows you to determine
 *       navigation point that is not visible by your opponent and is nearest to you (nearest hide spot), for more information see {@link Visibility} and try to checkout
 *       example from SVN: svn://artemis.ms.mff.cuni.cz/pogamut/trunk/project/Main/PogamutUT2004Examples/18-HideBot then exemplifies it.</li>
 *       
 *   <li>Note that SHOCK-COMBO is achievable as you get the "shock-ball" location via {@link DMBot#incomingProjectile(WorldObjectUpdatedEvent)} event. You can see
 *       how to perform it in the example from SVN: svn://artemis.ms.mff.cuni.cz/pogamut/trunk/project/Main/PogamutUT2004Examples/20-ShockComboShootingBot</li>
 *   
 *   <li>Adrenaline combos can be performed via {@link DMBot#getCombo()}. See {@link AdrenalineCombo} class.</li>
 *   
 *   <li>You might be interested in some constants/methods from {@link UnrealUtils}.</li>
 *   
 *   <li>Once you have your DMBot AI a bit balanced, do not make hasty changes to its implementation - always backup your code (copy it to some different dir or use some source-versioning
 *       software like SVN and see: http://www.svnhostingcomparison.com/) and then try to implement "improvements".<p> 
 *       Also we advise you to use DMMatch1v1 and DMMatch1vNative to check whether they truly were improvements.</li>
 *       
 *   <li>Note that if you do not fully face your opponent, UT2004 aims worse.</li>
 *   
 *   <li>Note that if you're in a jump/air, UT2004 aims worse.</li>
 *   
 *   <li>Note that if you crouch, UT2004 aims better.</li>
 *   
 *   <li>Note that UT2004 does not shoot rockets well (it does not aim for the ground). It is possibly to "shoot at location" so you can compensate (if you think it is a good idea).</li>
 * </ol>
 * 
 * <p><p>
 * <b>PITFALLS</b>
 * <ol>
 * 	 <li>Bot cannot connect to running GB2004 server - check GameBots2004.ini (see above) and then check Task Manager whether there is only ONE ucc.exe running at time.</li>
 *   <li>UT2004 is connected to different GB2004 instance - check Task Manager whether there is only ONE ucc.exe running at time, if it is, reconnect it by opening UT2004 console using '~' and
 *                                                          issuing command "open 127.0.0.1" (strip double-quotes).</li>
 *   <li>I'm using NetBeans and when I run my bot it acts weird as in slide-show - note that NetBeans contains inefficient implementation of the console, try to log less stuff via 'log' or use Eclipse
 *       or run your bot stand-along (via one-jar ... you need to build it manually with maven via 'mvn package' first).</li>  
 *   <li>UT2004 does not display my bot even though its logs are running, so it is clearly present in the GB2004 - just reconnect to GB2004 via console as described above ('~' open 127.0.0.1)
 *       and make sure there is single ucc.exe process running.</li>                                                     
 * </ol>
 * 
 * Contact us with any questions/problems you might have with Pogamut 3 Platform and this package!
 * <p><p>
 * PogamutCup WebPage: http://www.pogamutcup.com
 * <p><p>
 * Mailing list:       https://groups.google.com/forum/?fromgroups#!forum/pogamut-cup
 * <p><p>
 * PogamutCup forum:   http://diana.ms.mff.cuni.cz/main/tiki-view_forum.php?forumId=10
 * <p><p>
 * Or mail directly:   jakub.gemrot@gmail.com
 * 
 * @author Jakub Gemrot aka Jimmy
 */
@AgentScoped
public class DMBot extends UT2004BotModuleController<UT2004Bot> {

	// ======
	// FIELDS
	// ======
	
	/**
	 * Watch for bot-navigation-stuck within the environment and auto-delete navigation links from {@link UT2004BotModuleController#getFwMap()}.
	 */
    private UT2004PathAutoFixer autoFixer;
    
    /**
     * Used for MyPathExecutor/Navigator/Runner.
     */
    private LogCategory navigationLog;

    /**
     * USED FOR NAVIGATION EXAMPLE inside {@link DMBot#logic()}, can be deleted ... contains nav points that are forbidden for some time to visit
     */
	private TabooSet<NavPoint> tabooNavPoints;
	
	/**
     * USED FOR NAVIGATION EXAMPLE inside {@link DMBot#logic()}, can be deleted ... contains nav point we're currently running to
     */
	private NavPoint targetNavPoint;
    
    // =============
    // BOT LIFECYCLE
    // =============
    
    /**
     * Bot's preparation - called before the bot is connected to GB2004 and launched into UT2004.
     */
    @Override
    public void prepareBot(UT2004Bot bot) {    	
    	navigationLog = bot.getLogger().getCategory("NAVIGATION");
    	runStraight  = new MyRunStraight(bot, info, move, navigationLog);
    	pathExecutor = new MyPathExecutor(bot, new MyPathNavigator<ILocated>(bot, new MyPathRunner(bot, info, move, navigationLog), navigationLog), navigationLog);
    	navigation   = new MyNavigation(bot, pathExecutor, fwMap, new MyGetBackToNavGraph(bot, info, move, navigationLog), new MyRunStraight(bot, info, move, navigationLog), navigationLog);
    	
        // add stuck detector that watch over the path-following, if it (heuristicly) finds out that the bot has stuck somewhere,
        // it reports an appropriate path event and the path executor will stop following the path which in turn allows 
        // us to issue another follow-path command in the right time
        pathExecutor.addStuckDetector(new UT2004TimeStuckDetector(bot, 3000, 10000)); // if the bot does not move for 3 seconds, considered that it is stuck
        pathExecutor.addStuckDetector(new UT2004PositionStuckDetector(bot)); // watch over the position history of the bot, if the bot does not move sufficiently enough, consider that it is stuck
        pathExecutor.addStuckDetector(new UT2004DistanceStuckDetector(bot)); // watch over distances to target
        
        // Auto-delete navigation links that cannot be travelled
        autoFixer = new UT2004PathAutoFixer(bot, pathExecutor, fwMap, aStar, navBuilder); // auto-removes wrong navigation links between navpoints

        // DEFINE WEAPON PREFERENCES
        initWeaponPreferences();
    }
    
    /**
     * Define your weapon preferences here (if you are going to use weaponPrefs).
     * 
     * For more info, see slides (page 8): http://diana.ms.mff.cuni.cz/pogamut_files/lectures/2010-2011/Pogamut3_Lecture_03.pdf
     */
    private void initWeaponPreferences() {
    	weaponPrefs.addGeneralPref(ItemType.MINIGUN, false);
        weaponPrefs.addGeneralPref(ItemType.MINIGUN, true);
        weaponPrefs.addGeneralPref(ItemType.LINK_GUN, false);
        weaponPrefs.addGeneralPref(ItemType.LIGHTNING_GUN, true);
        weaponPrefs.addGeneralPref(ItemType.SHOCK_RIFLE, true);
        weaponPrefs.addGeneralPref(ItemType.ROCKET_LAUNCHER, true);
        weaponPrefs.addGeneralPref(ItemType.LINK_GUN, true);
        weaponPrefs.addGeneralPref(ItemType.ASSAULT_RIFLE, true);
        weaponPrefs.addGeneralPref(ItemType.FLAK_CANNON, false);
        weaponPrefs.addGeneralPref(ItemType.FLAK_CANNON, true);
        weaponPrefs.addGeneralPref(ItemType.BIO_RIFLE, true);
	}

	@Override
    public Initialize getInitializeCommand() {
    	// IT IS FORBIDDEN BY COMPETITION RULES TO CHANGE DESIRED SKILL TO DIFFERENT NUMBER THAN 5
    	// IT IS FORBIDDEN BY COMPETITION RULES TO ALTER ANYTHING EXCEPT NAME VIA INITIALIZE COMMAND
        return new Initialize().setName("DMBot").setDesiredSkill(5);
    }

    /**
     * Bot has been initialized inside GameBots2004 (Unreal Tournament 2004) and is about to enter the play
     * (it does not have the body materialized yet).
     *  
     * @param gameInfo
     * @param currentConfig
     * @param init
     */
    @Override
    public void botInitialized(GameInfo gameInfo, ConfigChange currentConfig, InitedMessage init) {
    	tabooNavPoints = new TabooSet<NavPoint>(bot);
    	
    	MapTweaks.tweak(navBuilder);
    }

    // ==========================
    // EVENT LISTENERS / HANDLERS
    // ==========================
	
    /**
     * {@link PlayerDamaged} listener that senses that "some other bot was hurt".
     *
     * @param event
     */
    @EventListener(eventClass = PlayerDamaged.class)
    public void playerDamaged(PlayerDamaged event) {
    	UnrealId botHurtId = event.getId();
    	if (botHurtId == null) return;
    	
    	int damage = event.getDamage();
    	Player botHurt = (Player)world.get(botHurtId);
    	
    	log.info("OTHER HURT: " + damage + " DMG to " + botHurtId.getStringId() + " [type=" + event.getDamageType() + ", weapon=" + event.getWeaponName() + "]");
    }
    
    /**
     * {@link BotDamaged} listener that senses that "I was hurt".
     *
     * @param event
     */
    @EventListener(eventClass = BotDamaged.class)
    public void botDamaged(BotDamaged event) {
    	int damage = event.getDamage();
    	
    	if (event.getInstigator() == null) {
    		log.info("HURT: " + damage + " DMG done to ME [type=" + event.getDamageType() + ", weapon=" + event.getWeaponName() + "] by UNKNOWN");
    	} else {
    		UnrealId whoCauseDmgId = event.getInstigator();
    		Player player = (Player) world.get(whoCauseDmgId);
    		log.info("HURT: " + damage + " DMG done to ME [type=" + event.getDamageType() + ", weapon=" + event.getWeaponName() + "] by " + whoCauseDmgId.getStringId());
    	}
    }
    
    /**
     * {@link PlayerKilled} listener that senses that "some other bot has died".
     *
     * @param event
     */
    @EventListener(eventClass = PlayerKilled.class)
    public void playerKilled(PlayerKilled event) {
    	UnrealId botDiedId = event.getId();
    	if (botDiedId == null) return;
    	
    	Player botDied = (Player) world.get(botDiedId);
    	
    	if (event.getKiller() == null) {
    		log.info("OTHER DIED: " + botDiedId.getStringId() + ", UNKNOWN killer");
    	} else {
    		UnrealId killerId = event.getKiller();
    		if (killerId.equals(info.getId())) {
    			log.info("OTHER KILLED: " + botDiedId.getStringId() + " by ME");
    		} else {
    			Player killer = (Player) world.get(killerId);
    			if (botDiedId.equals(killerId)) {
    				log.info("OTHER WAS KILLED: " + botDiedId.getStringId() + " comitted suicide");
    			} else {
    				log.info("OTHER WAS KILLED: " + botDiedId.getStringId() + " by " + killerId.getStringId());
    			}
    		}
    	}
    }
    
    /**
     * {@link BotKilled} listener that senses that "your bot has died".
     */
	@Override
	public void botKilled(BotKilled event) {
		if (event.getKiller() == null) {
			log.info("DEAD");
		} else {
			UnrealId killerId = event.getKiller();
			Player killer = (Player) world.get(killerId);
			log.info("KILLED by" + killerId.getStringId());
		} 
	}
	
	/**
     * {@link Spawn} listener that senses that "your bot has been respawned within the environment".
     *
     * @param event
     */
    @EventListener(eventClass = Spawn.class)
	public void botSpawned(Spawn event) {
		// WARNING! This event typically comes before SELF message,
    	//          thus at this point, information within 'info', 'weaponry' and possibly everywhere else are INACCURATE!
    	log.info("SPAWNED");
	}
    
    /**
     * {@link HearNoise} listener that senses that "some noise was heard by the bot".
     *
     * @param event
     */
    @EventListener(eventClass = HearNoise.class)
    public void hearNoise(HearNoise event) {
    	double noiseDistance = event.getDistance();   // 100 ~ 1 meter
    	Rotation faceRotation = event.getRotation();  // rotate bot to this if you want to face the location of the noise
    	log.info("HEAR NOISE: distance = " + noiseDistance);
    }
    
    /**
     * {@link ItemPickedUp} listener that senses that "your bot has picked up some item".
     * 
     * See sources for {@link ItemType} for details about item types / categories / groups.
     *
     * @param event
     */
    @EventListener(eventClass = ItemPickedUp.class)
    public void itemPickedUp(ItemPickedUp event) {
    	ItemType itemType = event.getType();
    	ItemType.Group itemGroup = itemType.getGroup();
    	ItemType.Category itemCategory = itemType.getCategory();
    	log.info("PICKED " + itemCategory.name + ": " + itemType.getName() + " [group=" + itemGroup.name + "]");
    	switch (itemCategory) {
    	case ADRENALINE: 
    		AdrenalineDescriptor descAdrenaline = (AdrenalineDescriptor) descriptors.getDescriptor(itemType);
    		// descAdrenaline describes details about currently picked item
    		break;
    	case AMMO:
    		AmmoDescriptor descAmmo = (AmmoDescriptor) descriptors.getDescriptor(itemType);
    		// descAmmo describes details about currently picked item
    		break;
    	case ARMOR:
    		ArmorDescriptor descArmor = (ArmorDescriptor) descriptors.getDescriptor(itemType);
    		// descAmmo describes details about currently picked item    		
    		break;
    	case HEALTH:
    		HealthDescriptor descHealth = (HealthDescriptor) descriptors.getDescriptor(itemType);
    		// descHealth describes details about currently picked item
    		break;
    	case SHIELD:
    		ShieldDescriptor descShield = (ShieldDescriptor) descriptors.getDescriptor(itemType);
    		// descShield describes details about currently picked item
    		break;
    	case WEAPON:
    		WeaponDescriptor descWeapon = (WeaponDescriptor) descriptors.getDescriptor(itemType);
    		// descWeapon describes details about currently picked item
    		break;
    	case OTHER:
    		OtherDescriptor descOther = (OtherDescriptor) descriptors.getDescriptor(itemType);
    		// descOther describes details about currently picked item
    		break;
    	}
    }
    
    /**
     * {@link IncomingProjectile} listener that senses that "some projectile has appeared".
     *
     * @param event
     */
    @ObjectClassEventListener(objectClass = IncomingProjectile.class, eventClass = WorldObjectAppearedEvent.class)
    public void incomingProjectileAppeared(WorldObjectAppearedEvent<IncomingProjectile> event) {
    	IncomingProjectile projectile = event.getObject();
    	log.info("PROJECTILE APPEARED: " + projectile);
    }
    
    /**
     * {@link IncomingProjectile} listener that senses that "some projectile has appeared OR moved OR disappeared".
     *
     * @param event
     */
    @ObjectClassEventListener(objectClass = IncomingProjectile.class, eventClass = WorldObjectUpdatedEvent.class)
    public void incomingProjectileUpdated(WorldObjectUpdatedEvent<IncomingProjectile> event) {
    	IncomingProjectile projectile = event.getObject();
    	log.info("PROJECTILE UPDATED: " + projectile);
    }
    
    /**
     * {@link IncomingProjectile} listener that senses that "some projectile has disappeared".
     *
     * @param event
     */
    @ObjectClassEventListener(objectClass = IncomingProjectile.class, eventClass = WorldObjectDisappearedEvent.class)
    public void incomingProjectileDisappeared(WorldObjectDisappearedEvent<IncomingProjectile> event) {
    	IncomingProjectile projectile = event.getObject();
    	log.info("PROJECTILE DISAPPEARED: " + projectile);
    }
    
    /**
     * {@link Player} listener that senses that "some other bot has entered the field of view of your bot"
     *
     * @param event
     */
    @ObjectClassEventListener(objectClass = Player.class, eventClass = WorldObjectAppearedEvent.class)
    public void playerAppeared(WorldObjectAppearedEvent<Player> event) {
    	Player player = event.getObject();
    	log.info("PLAYER APPEARED: " + player.getId().getStringId());
    }
    
    /**
     * {@link Player} listener that senses that "some other bot has appeared OR moved OR disappeared"
     *
     * WARNING: this method will also be called during handshaking GB2004.
     *
     * @param event
     */
    @ObjectClassEventListener(objectClass = Player.class, eventClass = WorldObjectUpdatedEvent.class)
    public void playerUpdated(WorldObjectUpdatedEvent<Player> event) {
    	if (info.getLocation() == null) {
    		// HANDSHAKING GB2004
    		return;
    	}
    	Player player = event.getObject();
    	log.info("PLAYER UPDATED: " + player.getId().getStringId());
    }
    
    /**
     * {@link Player} listener that senses that "some other bot has left the field of view of your bot"
     *
     * @param event
     */
    @ObjectClassEventListener(objectClass = Player.class, eventClass = WorldObjectDisappearedEvent.class)
    public void playerDisappeared(WorldObjectDisappearedEvent<Player> event) {
    	Player player = event.getObject();
    	log.info("PLAYER DISAPPEARED: " + player.getId().getStringId());
    }
    
    /**
     * {@link Item} listener that senses that "some SPAWNED item has entered the field of view of your bot"
     *
     * WARNING: this method will also be called during handshaking GB2004. 
     *
     * @param event
     */
    @ObjectClassEventListener(objectClass = Item.class, eventClass = WorldObjectAppearedEvent.class)
    public void itemAppeared(WorldObjectAppearedEvent<Item> event) {    	
    	Item item = event.getObject();
    	log.info("ITEM APPEARED: " + item.getId().getStringId());
    }
    
    /**
     * {@link Item} listener that senses that "some SPAWNED item has appeared OR moved OR disappeared"
     *
     * @param event
     */
    @ObjectClassEventListener(objectClass = Item.class, eventClass = WorldObjectUpdatedEvent.class)
    public void itemUpdated(WorldObjectUpdatedEvent<Item> event) {
    	if (info.getLocation() == null) {
    		// HANDSHAKING GB2004
    		return;
    	}
    	Item item = event.getObject();
    	log.info("ITEM UPDATED: " + item.getId().getStringId());
    }
    
    /**
     * {@link Item} listener that senses that "some SPAWNED item has left the field of view of your bot"
     *
     * @param event
     */
    @ObjectClassEventListener(objectClass = Item.class, eventClass = WorldObjectDisappearedEvent.class)
    public void itemDisappeared(WorldObjectDisappearedEvent<Item> event) {
    	Item item = event.getObject();
    	log.info("ITEM DISAPPEARED: " + item.getId().getStringId());
    }

    // ==============
    // MAIN BOT LOGIC
    // ==============
    
    /**
     * Method that is executed only once before the first {@link DMBot#logic()} 
     */
    @Override
    public void beforeFirstLogic() {
    }
    
    /**
     * Main method that controls the bot - makes decisions what to do next. It
     * is called iteratively by Pogamut engine every time a synchronous batch
     * from the environment is received. This is usually 4 times per second.
     * 
     * This is a typical place from where you start coding your bot. Even though bot
     * can be completely EVENT-DRIVEN, the reactive aproach via "ticking" logic()
     * method is more simple / straight-forward.
     */
    @Override
    public void logic() {
    	log.info("---[ LOGIC ITERATION ]---");
    	
    	long logicStartTime = System.currentTimeMillis();
    	
    	try {
	    	// LOG VARIOUS INTERESTING VALUES
	    	logMind();
	    	
	    	// SHOOT ALL OTHER PLAYERS
	    	if (players.canSeePlayers()) {
	    		Player player = players.getNearestVisiblePlayer();
	    		log.info("DECISION: SHOOTING AT " + player.getId().getStringId());
	    		shoot.shoot(weaponPrefs, player);
	    		log.info("DECISION: face player " + player.getId().getStringId());
	    		navigation.setFocus(player);
	    	} else {
	    		if (info.isShooting()) {
	    			log.info("DECISION: STOP SHOOTING");	    			
	    			shoot.stopShooting();
	    			log.info("DECISION: set focus to path");
	    			navigation.setFocus(null);
	    		}
	    	}
	    	
	    	// RANDOMLY RUN AROUND THE ENVIRONMENT
	    	if (navigation.isNavigating()) {
	            // IS TARGET CLOSE & NEXT TARGET NOT SPECIFIED?
	            while (navigation.getContinueTo() == null && navigation.getRemainingDistance() < 400) {
	                    // YES, THERE IS NO "next-target" SET AND WE'RE ABOUT TO REACH OUR TARGET!
	                    navigation.setContinueTo(getRandomNavPoint());
	                    // note that it is WHILE because navigation may immediately eat up "next target" and next target may be actually still too close!
	            }
	
	            // WE'RE NAVIGATING TO SOME NAVPOINT
	            return;
	        }
	
	        // NAVIGATION HAS STOPPED (OR FIRST ITERATION -> NEVER BEGUN)... 
	        // => we need to choose another navpoint to navigate to
	        // => possibly follow some players ...
	
	         targetNavPoint = getRandomNavPoint();
	         if (targetNavPoint == null) {
	        	 // note that this would NEVER happens in competition... it 
	             log.severe("COULD NOT CHOOSE ANY NAVIGATION POINT TO RUN TO!!!");
	             if (world.getAll(NavPoint.class).size() == 0) {
	                 log.severe("world.getAll(NavPoint.class).size() == 0, there are no navigation ponits to choose from! Is exporting of nav points enabled in GameBots2004.ini inside UT2004?");
	             }
	             return;
	         }
	         tabooNavPoints.add(targetNavPoint, 120);
	
	         log.info("DECISION: RUNNING TO " + targetNavPoint.getId().getStringId());
	         navigation.navigate(targetNavPoint);
    	} catch (Exception e) {
    		// MAKE SURE THAT YOUR BOT WON'T FAIL!
    		log.info(ExceptionToString.process(e));
    	} finally {
    		// MAKE SURE THAT YOUR LOGIC DOES NOT TAKE MORE THAN 250 MS (Honestly, we have never seen anybody reaching even 150 ms per logic cycle...)
    		// Note that it is perfectly OK, for instance, to count all path-distances between you and all possible pickup-points / items in the game
    		// sort it and do some inference based on that.
    		long timeSpentInLogic = System.currentTimeMillis() - logicStartTime;
    		log.info("Logic time: " + timeSpentInLogic + " ms");
    		if (timeSpentInLogic >= 250) {
    			log.warning("!!! LOGIC TOO DEMANDING !!!");
    		}
    		log.info("---[ END ] ---");
    	}
    }
    
    /**
     * Randomly picks some navigation point to head to.
     *
     * @return randomly chosen navpoint
     */
    public NavPoint getRandomNavPoint() {
        log.info("DECISION: Picking new target navpoint.");

        // choose one feasible navpoint (== not belonging to tabooNavPoints) randomly
        NavPoint chosen = MyCollections.getRandomFiltered(getWorldView().getAll(NavPoint.class).values(), tabooNavPoints);

        if (chosen != null) {
            return chosen;
        }

        log.warning("DECISION: All navpoints are tabooized at this moment, choosing navpoint randomly!");

        // ok, all navpoints have been visited probably, try to pick one at random
        return MyCollections.getRandom(getWorldView().getAll(NavPoint.class).values());
    }
    
    // ===========
    // MIND LOGGER
    // ===========
    
    /**
     * It is good in-general to periodically log anything that relates to your's {@link DMBot#logic()} decision making.
     * 
     * You might consider exporting these values to some custom Swing window (you crete for yourself) that will be more readable.
     */
    public void logMind() {
    	log.info("My location:    " + info.getLocation());
    	log.info("Looking to:     " + info.getRotation().toLocation());
    	log.info("My health:      " + info.getHealth());
    	log.info("My armor:       " + info.getArmor() + " (low:" + info.getLowArmor() + " / high:" + info.getHighArmor() + ")");
    	log.info("My weapon:      " + weaponry.getCurrentWeapon());
    	Item nearestAmmoForMyWeapon = getNearestPossiblySpawnedAmmoForWeapon(weaponry.getCurrentWeapon().getDescriptor());
    	if (nearestAmmoForMyWeapon == null) {
    		log.info(String.format("Nearest %-30s: %-45s / %f", "ammo for my weapon", "NONE", Float.POSITIVE_INFINITY));
    	} else {
    		log.info(String.format("Nearest %-30s: %-45s / %f", "ammo for my weapon", nearestAmmoForMyWeapon.getId().getStringId(), fwMap.getDistance(info.getNearestNavPoint(), nearestAmmoForMyWeapon.getNavPoint())));
    	}
    	Map<ItemType, List<Item>> nearestItems = getNearestPossiblySpawnedItemsMap();
    	List<ItemType> keys = new ArrayList<ItemType>(nearestItems.keySet());
    	Collections.sort(keys);
    	for (ItemType key : keys) {
    		List<Item> items = nearestItems.get(key);
    		if (items.size() == 0) continue;
    		Item item = items.get(0);
    		log.info(String.format("Nearest %-30s: %-45s / %f", key.getName(), item.getId().getStringId(), fwMap.getDistance(info.getNearestNavPoint(), item.getNavPoint())));
    	}    	
    }
    
    // ======================================
    // UT2004 DEATH-MATCH INTERESTING GETTERS
    // ======================================
    
    /**
     * Returns path-nearest {@link NavPoint} that is covered from 'enemy'. Uses {@link UT2004BotModuleController#getVisibility()}.
     * @param enemy
     * @return
     */
    public NavPoint getNearestEscapePoint(Player enemy) {
    	if (!visibility.isInitialized()) {
    		log.warning("VISIBILITY NOT INITIALIZED: returning random navpoint");    		
    		return MyCollections.getRandom(navPoints.getNavPoints().values());
    	}
    	List<NavPoint> coverPoints = new ArrayList<NavPoint>(visibility.getCoverNavPointsFrom(enemy.getLocation()));
    	return fwMap.getNearestNavPoint(coverPoints, info.getNearestNavPoint());
    }
    
    /**
     * Returns {@link WeaponDescriptor} for 'weapon', if 'weapon' does not belongs to {@link ItemType.Category#WEAPON}, returns null.
     * @param weapon
     * @return
     */
    public WeaponDescriptor getWeaponDescriptor(ItemType weapon) {
    	if (weapon.getCategory() != ItemType.Category.WEAPON) return null;
    	return (WeaponDescriptor) descriptors.getDescriptor(weapon);
    }
    
    /**
     * Returns whether 'item' is possibly spawned (to your current knowledge).
     * @param item
     * @return
     */
    public boolean isPossiblySpawned(Item item) {
    	return items.isPickupSpawned(item);
    }
    
    /**
     * Returns whether you can actually pick this 'item', based on "isSpawned" and "isPickable" in your current state and knowledge.
     */
    public boolean isCurrentlyPickable(Item item) {
    	return isPossiblySpawned(item) && items.isPickable(item);
    }
    
    /**
     * Returns random possibly spawned item of item 'type'.
     * @param type
     * @return
     */
    public Item getRandomPossiblySpawnedItemOfType(ItemType type) {
    	return MyCollections.getRandom(items.getSpawnedItems(type).values());
    }

    /**
     * Returns random possibly spawned item of item 'category'.
     * @param category
     * @return
     */
    public Item getRandomPossiblySpawnedItemOfCategory(ItemType.Category category) {
    	return MyCollections.getRandom(items.getSpawnedItems(category).values());
    }

    /**
     * Returns random possibly spawned weapon.
     * @return
     */
    public Item getRandomPossiblySpawnedWeapon() {
    	return getRandomPossiblySpawnedItemOfCategory(ItemType.Category.WEAPON);
    }
        
    /**
     * Returns random possibly spawned health.
     * @return
     */
    public Item getRandomPossiblySpawnedHealth() {
    	return getRandomPossiblySpawnedItemOfCategory(ItemType.Category.HEALTH);
    }
    
    /**
     * Returns random possibly spawned armor.
     * @return
     */
    public Item getRandomPossiblySpawnedArmor() {
    	return getRandomPossiblySpawnedItemOfCategory(ItemType.Category.ARMOR);
    }
    
    /**
     * Returns random possibly spawned ammo for the weapon described by 'weaponDescriptor' (see {@link Weaponry#getCurrentWeapon()}, {@link Weapon#getDescriptor()} 
     * and {@link DMBot#getWeaponDescriptor(ItemType)}).
     * @param weaponDescriptor
     * @return
     */
    public Item getRandomPossiblySpawnedAmmoForWeapon(final WeaponDescriptor weaponDescriptor) {
    	// weapon can be obtained from "weaponry"
    	return MyCollections.getRandomFiltered(
    			items.getSpawnedItems(ItemType.Category.AMMO).values(),
    			new IFilter<Item>() {
					@Override
					public boolean isAccepted(Item object) {
						if (object.getType().getCategory() != ItemType.Category.AMMO) return false;
						return weaponDescriptor.getPriAmmoItemType() == object.getType() || weaponDescriptor.getSecAmmoItemType() == object.getType();
					}
				}
    		   );
    }
    
    /**
     * Returns map of {@link ItemType} mapped to the list of items sorted according to path-distance in asceding order (extremely useful for decision making).
     * @return
     */
    public Map<ItemType, List<Item>> getNearestPossiblySpawnedItemsMap() {
    	final NavPoint nearestNavPoint = info.getNearestNavPoint();
    	List<Item> itemsDistanceSortedAscending = 
    			DistanceUtils.getDistanceSorted(
    					items.getSpawnedItems().values(), 
    					info.getLocation(), 
    					new DistanceUtils.IGetDistance<Item>() {
							@Override
							public double getDistance(Item object, ILocated target) {
								return fwMap.getDistance(nearestNavPoint, object.getNavPoint());
							}
						}
    			);
    	Map<ItemType, List<Item>> result = new LazyMap<ItemType, List<Item>>() {
			@Override
			public List<Item> create(ItemType key) {
				return new ArrayList<Item>();
			}
    	};
    	for (Item item : itemsDistanceSortedAscending) {
    		result.get(item.getType()).add(item);
    	}
    	return result;
    }
    
    /**
     * Returns nearest possibly spawned item of item 'type'.
     * @param type
     * @return
     */
    public Item getNearestPossiblySpawnedItemOfType(ItemType type) {
    	final NavPoint nearestNavPoint = info.getNearestNavPoint();
    	List<Item> itemsDistanceSortedAscending = 
    			DistanceUtils.getDistanceSorted(
    					items.getSpawnedItems(type).values(), 
    					info.getLocation(), 
    					new DistanceUtils.IGetDistance<Item>() {
							@Override
							public double getDistance(Item object, ILocated target) {
								return fwMap.getDistance(nearestNavPoint, object.getNavPoint());
							}
						}
    			);
    	if (itemsDistanceSortedAscending.size() == 0) return null;
    	return itemsDistanceSortedAscending.get(0);
    }
    
    /**
     * Returns nearest possibly spawned item of item 'category'.
     * @param category
     * @return
     */
    public Item getNearestPossiblySpawnedItemOfCategory(ItemType.Category category) {
    	final NavPoint nearestNavPoint = info.getNearestNavPoint();
    	List<Item> itemsDistanceSortedAscending = 
    			DistanceUtils.getDistanceSorted(
    					items.getSpawnedItems(category).values(), 
    					info.getLocation(), 
    					new DistanceUtils.IGetDistance<Item>() {
							@Override
							public double getDistance(Item object, ILocated target) {
								return fwMap.getDistance(nearestNavPoint, object.getNavPoint());
							}
						}
    			);
    	if (itemsDistanceSortedAscending.size() == 0) return null;
    	return itemsDistanceSortedAscending.get(0);
    }
    
    /**
     * Returns nearest possibly spawned WEAPON item.
     * @return
     */
    public Item getNearestPossiblySpawnedWeapon() {
    	return getNearestPossiblySpawnedItemOfCategory(ItemType.Category.WEAPON);
    }
    
    /**
     * Returns nearest possibly spawned HEALTH item.
     * @return
     */
    public Item getNearestPossiblySpawnedHealth() {
    	return getNearestPossiblySpawnedItemOfCategory(ItemType.Category.HEALTH);
    }
    
    /**
     * Returns nearest possibly spawned ARMOR item.
     * @return
     */
    public Item getNearestPossiblySpawnedArmor() {
    	return getNearestPossiblySpawnedItemOfCategory(ItemType.Category.ARMOR);
    }
    
    /**
     * Returns an item that is possibly spawned ammo for the weapon described by 'weaponDescriptor' (see {@link Weaponry#getCurrentWeapon()}, {@link Weapon#getDescriptor()} and {@link DMBot#getWeaponDescriptor(ItemType)}).
     * @param weaponDescriptor
     * @return
     */
    public Item getNearestPossiblySpawnedAmmoForWeapon(final WeaponDescriptor weaponDescriptor) {
    	final NavPoint nearestNavPoint = info.getNearestNavPoint();
    	List<Item> ammosForWeaponsDistanceSortedAscending = 
    			DistanceUtils.getDistanceSorted(
    					items.getSpawnedItems(ItemType.Category.AMMO).values(), 
    					info.getLocation(), 
    					new DistanceUtils.IGetDistance<Item>() {
							@Override
							public double getDistance(Item object, ILocated target) {
								return fwMap.getDistance(nearestNavPoint, object.getNavPoint());
							}
						},
						new DistanceUtils.IDistanceFilter<Item>() {
							@Override
							public boolean isAccepted(Item object, ILocated target, double distanceToTarget) {
								if (object.getType().getCategory() != ItemType.Category.AMMO) return false;
								return weaponDescriptor.getPriAmmoItemType() == object.getType() || weaponDescriptor.getSecAmmoItemType() == object.getType();
							}
						}
    			);
    	if (ammosForWeaponsDistanceSortedAscending.size() == 0) return null;
    	return ammosForWeaponsDistanceSortedAscending.get(0);
    }
    
    // ===========
    // MAIN METHOD
    // ===========
    
    /**
     * Main execute method of the program.
     * 
     * @param args
     * @throws PogamutException
     */
    public static void main(String args[]) throws PogamutException {
    	// Starts N agents of the same type at once
    	// WHEN YOU WILL BE SUBMITTING YOUR CODE, MAKE SURE THAT YOU RESET NUMBER OF STARTED AGENTS TO '1' !!!
    	// ALWAYS USE CONSTANT '1' FOR UTILIZATION INSIDE DMMatch1v1, DMMatch1VNative !!!
    	new UT2004BotRunner(DMBot.class, "DMBot").setMain(true).setLogLevel(Level.INFO).startAgents(1);
    }
    
}
