//==========================================================================================
//
//  1944 Overlord - 1944_overlord.gm
//
//  Who         When            What
//------------------------------------------------------------------------------------------
//  Mateos      19/01/2020      Moved to nav
//  Mateos      02/09/2014      Lot of fixes required, was an incomplete 0.71 work...
//  jaskot      04/02/2009      Added paththrough for transmitter ladder for
//                              unopposed offense and added tank MOVER and BUILD
//                              offset sync
//  jaskot      03/02/2009      Converted old script to new format
//
//==========================================================================================
//

/****************************************************
Path mapping updated and script by Mateos
Correspondant WAY file size: 80,5 Ko (82 478 octets)
Last update: 19th January 2020
****************************************************/

/*
 * TODO:
 * - Add weapon goals
 * - Turn some ATTACK_Axis_Generator spots into CAMP (5 to 10)?
 * - Allied Transmitter: Find a way to protect it against Axis without FF issues
 * - Rear Entrance: Can jump through window to get in (testbot fails it...)
 * - Allies: Find an efficient way to clear the obj rooms (or smoke?)
 * - Manage the post-Axis Generator SNIPE goals
 *       -> Also that SNIPE_Allied_Road_Bottom_Right...
 */

// Idea: Once the Tank is at the 2nd/3rd area, some Allies could spawn at the Beach (west spawn)...
//      -> Distance is roughly the same, or just routing to travel through the hill?
//          -> Hill way is longer though safer than road

global Spawns =
{
    ALLIED_LANDER_SPAWN1 = 1,
    AXIS_BUNKER_SPAWN = 2,
    ALLIED_TRANSMITTER_SPAWN = 3,
    AXIS_RESERVE_SPAWN = 4,
    ALLIED_LANDER_SPAWN2 = 5,
};

global TankOffsets =
{
    Build =
    {
        Back = 0,
        LeftBack = 1,
        LeftFront = 2,
    },
};

global MapProgress =
{
    TankLocation =
    {
        FirstArea = 0,
        SecondArea = 1,
        ThirdArea = 2,
        Escorted = 3,
    },
};

global Goals =
{
    Allies =
    {
        Bunker = { "ATTACK_Axis_Generator.*", "PLANT_Axis_Generator" },
        Library = { "FLAG_documents_2" },
    },

    Axis =
    {
        AlliedTransmitter = { "CAMP_Allied_Transmitter.*" },

        Library =
        {
            Bottom = { "DEFEND_Lower_Docs.*" },
            Top = { "DEFEND_Upper_Docs.*" },
        },
    },
};

global Roles =
{
    Allies =
    {
        Bunker = ROLE.ATTACKER2,
        Library = ROLE.ATTACKER3,
    },

    Axis =
    {
        AlliedTransmitter = ROLE.DEFENDER3, // Idea from native12's waypoints

        Library =
        {
            Bottom = ROLE.DEFENDER1,
            Top = ROLE.DEFENDER2,
        },
    },
};

global Map =
{
    Debug = 0,  // Please set to zero before distributing your script

    // ************************************************************************

    // Objectives status
    areForwardBunkerDoorsBuilt = false,
    hasAlliedTransmitterBeenBuiltOnce = false,
    isAlliedTransmitterBuilt = false,
    isAxisBunkerWallDestroyed = false,
    isAxisGeneratorDestroyed = false,
    isLibraryRearDoorBuilt = true,

    // A variable to control the wm_announce spam upon Allied Transmitter construction...
    iAlliedTransmitterCount = 0,

    // How many documents are in the nature (stolen or dropped)
    iDocumentsCount = 0,

    iDocumentsSecured = 0,

    // Track what documents are secured or still available
    areLowerDocumentsAvailable = false,
    areUpperDocumentsAvailable = false,

    // Special case of upper documents
    areUpperDocumentsReachable = false,
    areUpperDocumentsSecured = false,

    areLowerDocumentsSecured = false,

    // Tank-related variables
    isTankBuilt = false,
    isTankEscorted = false,
    isTankStolen = false,

    tankLocation = MapProgress.TankLocation.FirstArea,

    Axis_Bunker_Wall_Dyno = 0, // Simply to avoid friendly artillery accident...

    // ************************************************************************

    Allied_Lander_Spawn_Trigger =
    {
        Name = "Allied_Lander_Spawn_Trigger",
        TriggerOnClass = CLASS.ANYPLAYER,

        OnEnter = function( ent )
        {
            Map.ClearRole( ent, Roles.Allies.Bunker );

            // In case the Allied Transmitter Spawn got unavailable meanwhile!
            Map.ClearRole( ent, Roles.Allies.Library );
        },
    },

    Allied_Transmitter_Spawn_Trigger =
    {
        Name = "Allied_Transmitter_Spawn_Trigger",
        TriggerOnClass = CLASS.ANYPLAYER,

        OnEnter = function( ent )
        {
            Map.ClearRole( ent, Roles.Allies.Library );
        },
    },

    /**
     * Clears a role from an entity,
     * if that entity is a bot.
     */
    ClearRole = function( ent, role )
    {
        bot = Util.IsBot( ent );
        if ( bot && bot.HasRole( role ) )
        {
            bot.ClearRoles( role );
            Util.MapDebugPrint( bot.Name + "^b got his role removed", true );
        }
    },

    // ************************************************************************

    Tank_At_First_Tunnel =
    {
        Name = "Tank_At_First_Tunnel",
        TriggerOnClass = { CLASS.VEHICLE, CLASS.VEHICLE_HVY },

        OnEnter = function( ent )
        {
            Map.tankLocation = MapProgress.TankLocation.SecondArea;

            Map.ManageAirstrikeGoals();

            Map.ManagePostGeneratorCampingGoals();

            Util.MapDebugPrint( "Tank_At_First_Tunnel" );
        },
    },

    Tank_At_Second_Tunnel =
    {
        Name = "Tank_At_Second_Tunnel",
        TriggerOnClass = { CLASS.VEHICLE, CLASS.VEHICLE_HVY },

        OnEnter = function( ent )
        {
            Map.tankLocation = MapProgress.TankLocation.ThirdArea;

            Map.ManageAirstrikeGoals();

            Map.ManagePostGeneratorCampingGoals();

            Util.MapDebugPrint( "Tank_At_Second_Tunnel" );
        },
    },

    TankTriggerInfo =
    {
        Name = "TankTrigger",
        TriggerOnClass = { CLASS.VEHICLE, CLASS.VEHICLE_HVY },

        OnEnter = function( ent )
        {
            Map.tankLocation = MapProgress.TankLocation.Escorted;

            Map.areLowerDocumentsAvailable = true;
            Map.isTankEscorted = true;

            // If Axis still spawn at their Bunker, move to the other spawn
            // Only if no objective in the nature
            if ( Map.iDocumentsCount == 0 )
            {
                Util.ChangeSpawn( TEAM.AXIS, Spawns.AXIS_RESERVE_SPAWN );
            }

            // Wait until the Tank shoots
            // and the blockwall flags update
            sleep ( 5 );

            Map.UpdateAreUpperDocumentsReachable();

            Map.ManageAirstrikeGoals();

            SetAvailableMapGoals( TEAM.AXIS, true, "BUILD_Axis_Command_Post" ); // Here too

            // Manage CAMP and DEFEND spots for Axis
            // in case some objective is already stolen
            Map.Documents_Returned_Or_Secured();

            SetAvailableMapGoals( TEAM.ALLIES, false, {
                "ATTACK_Third_Area.*",
                "BUILD_Tank",
                "ESCORT_Tank",
                "MOUNT_Tank",
            });
            SetAvailableMapGoals( TEAM.ALLIES, true, {
                "FLAG_documents_.*",
                "PLANT_Axis_Library_Rear_Door",
            });

            // Manage eventually trapped bots
            Map.ManageLibraryTrappedAlliedBots();

            Functions.Print( "Axis_Wall_Destroyed", true );
        },
    },

    // ************************************************************************

    Navigation =
    {
        beach_ramp =
        {
            navigate = function( _this )
            {
                // Aim...
                _this.AddAimRequest( Priority.High, "facing", Vec3( 0.00, -0.80, 0.60 ) );

                // And go!
                wpTable = {};
                Wp.GetWaypointByName( "beach_ramp_top", wpTable );
                _this.Goto( wpTable.position );
            },
        },

        jump =
        {
            navigate = function(_this)
            {
                _this.Bot.HoldButton( BTN.JUMP, 0.50 );
                sleep( 0.50 );
            },
        },

        transmitterladder =
        {
            navigate = function( _this )
            {
                _this.Bot.HoldButton( BTN.FORWARD, 1 );
                sleep( 1 );
            },
        },
    },

    // ************************************************************************

    Roles =
    {
        AXIS =
        {
            AllBots = true,

            DEFENDER =
            {
                voice = "defend",
                numbots = 4,
            },

            DEFENDER1 =
            {
                voice = "defend",
                numbots = 6,
            },

            DEFENDER2 =
            {
                voice = "defend",
                numbots = 8,
            },

            DEFENDER3 =
            {
                voice = "defend",
                numbots = 4,
            },
        },

        ALLIES =
        {
            ATTACKER =
            {
                voice = "attack",
                spawnpt = Spawns.ALLIED_LANDER_SPAWN2,
                numbots = 5,
                crucialClass = CLASS.COVERTOPS,
            },
        },
    },

    // ************************************************************************

    distanceBetweenCapPointAndFurthestCampGoal,

    /**
     * Very specific suicide strategy for Allies:
     * - Both obj are being taken OR one is secured and the other just got stolen;
     * - The Allied spawn time is close;
     * - The bot doesn't hold an obj (obviously);
     * - The bot isn't close to the one holding the obj (in other words, he's not escorting the bot having stolen it);
     * - (Extra?) The bot isn't close to the area to defend already (+ check HP? low HP = suicide).
     *
     * Here we do hope (for now) that the stolen obj isn't currently dropped...
     */
    AlliedSuicideUponObjTaken = function()
    {
        // Leave some time to OB to write to the bot object that he's carrying a flag... Yeah.
        sleep( .5 );

        condition =
            Map.iDocumentsCount == 2
            || Map.iDocumentsCount == 1 && ( Map.areLowerDocumentsSecured || Map.areUpperDocumentsSecured );
        if ( !condition )
        {
            return;
        }

        alliedSpawnTime = Functions.GetReinforceTime( TEAM.ALLIES );
        if ( alliedSpawnTime > 10 )
        {
            return;
        }

        // Get the list of bots holding an obj
        index = 0;
        alliedBotsWithObj = table();
        foreach ( id and bot in BotTable )
        {
            if ( bot.GetTeam() == TEAM.ALLIES && bot.HasEntityFlag( ENTFLAG.CARRYINGGOAL ) )
            {
                alliedBotsWithObj[ index ] = bot;
                index += 1;
            }
        }

        // Cycle through Allied bots
        // Close to obj holder? Fine
        // Low HP or not close to dropoff? Selfkill
        foreach ( id and bot in BotTable )
        {
            if ( bot.GetTeam() == TEAM.AXIS )
            {
                continue;
            }

            shouldBotStayAlive = false;
            foreach ( objBot in alliedBotsWithObj )
            {
                if ( bot == objBot )
                {
                    Util.MapDebugPrint( bot.Name + "^b holds an objective", true );

                    shouldBotStayAlive = true;

                    // Exit this loop
                    break;
                }

                // Test the current bot
                distanceFromObjBot = Functions.GetDistance( objBot, bot );
                if ( distanceFromObjBot < 1000. )
                {
                    Util.MapDebugPrint( bot.Name + "^b is close to " + objBot.Name, true );

                    shouldBotStayAlive = true;

                    // Exit this loop
                    break;
                }
            }

            if ( shouldBotStayAlive )
            {
                // Next bot
                continue;
            }

            // Here, the current bot doesn't hold an obj, and isn't close to any bot holding one
            if ( bot.Health <= 40 )
            {
                Util.MapDebugPrint( bot.Name + "^b selfkilled because he's low hp (" + bot.Health + ")", true );
                bot.ExecCommand( "kill" );
                break;
            }

            distanceFromDropoff = Functions.GetDistance( GetGoal( "CAPPOINT_Transmitter" ), bot );
            if ( distanceFromDropoff > Map.distanceBetweenCapPointAndFurthestCampGoal )
            {
                bot.ExecCommand( "kill" );
                Util.MapDebugPrint( bot.Name + "^b selfkilled because he's too far away from the Transmitter (" + distanceFromDropoff + ")", true );
            }
        }
    },

    libraryAxisDoorX = -3345,
    libraryRailingX  = -3085,
    libraryRearDoorY = GetGoal( "PLANT_Axis_Library_Rear_Door" ).GetPosition().y,

    /**
     * Indicates if a bot is either inside the Library or not.
     */
    IsBotInsideLibrary = function( bot )
    {
        botPosition = bot.GetPosition();
        return
            botPosition.x <= Map.libraryAxisDoorX // Inside
            || botPosition.x <= Map.libraryRailingX && botPosition.y <= Map.libraryRearDoorY; // Balcony
    },

    /**
     * Indicates if the Tank is escortable.
     */
    IsTankEscortable = function()
    {
        return !Map.isTankEscorted && Map.isTankBuilt;
    },

    /**
     * Manages the AIRSTRIKE goals for Axis, after their Generator destruction
     */
    ManageAirstrikeGoals = function()
    {
        // Stays disabled if no documents in the nature and Tank is escorted
        SetAvailableMapGoals( TEAM.AXIS, false, "AIRSTRIKE_Area.*" );

        if ( Map.tankLocation == MapProgress.TankLocation.FirstArea || Map.iDocumentsCount > 0 )
        {
            SetAvailableMapGoals( TEAM.AXIS, true, "AIRSTRIKE_Area1" );
        }
        else if ( Map.tankLocation == MapProgress.TankLocation.SecondArea )
        {
            SetAvailableMapGoals( TEAM.AXIS, true, "AIRSTRIKE_Area2" );
        }
        else if ( Map.tankLocation == MapProgress.TankLocation.ThirdArea )
        {
            SetAvailableMapGoals( TEAM.AXIS, true, "AIRSTRIKE_Area3" );
        }
    },

    /**
     * Manages the CAPPOINT goals for Allies, depending on their Transmitter status.
     */
    ManageCappointGoals = function()
    {
        SetAvailableMapGoals( TEAM.ALLIES, Map.isAlliedTransmitterBuilt, "CAPPOINT_Transmitter" );
        SetAvailableMapGoals( TEAM.ALLIES, !Map.isAlliedTransmitterBuilt, "CAPPOINT_TempCampUntilTransmitterGetsAvailable.*" );
    },

    /**
     * Manages the Bunker goals when some Allied bot is trapped inside it,
     * In other words, the Axis Bunker Wall isn't destroyed AND the Forward Bunker Doors are built.
     * Only if this is the correct step of the map progress.
     */
    ManageBunkerTrappedAlliedBots = function()
    {
        // Only if Allies spawn at the Beach
        if ( Map.isAlliedTransmitterBuilt )
        {
            Util.ClearRoleForTable( Goals.Allies.Bunker, Roles.Allies.Bunker );
            return;
        }

        // If the Axis Generator is destroyed,
        // other goals are already managed
        if ( Map.isAxisGeneratorDestroyed )
        {
            Util.ClearRoleForTable( Goals.Allies.Bunker, Roles.Allies.Bunker );
            return;
        }

        // Case when both Axis Bunker Wall and Forward Bunker Doors are up only
        // Otherwise we exit
        if ( Map.isAxisBunkerWallDestroyed || !Map.areForwardBunkerDoorsBuilt )
        {
            Util.ClearRoleForTable( Goals.Allies.Bunker, Roles.Allies.Bunker );
            return;
        }

        forwardBunkerDoorsY = GetGoal( "PLANT_Forward_Bunker_Doors" ).GetPosition().y;

        index = 0;
        trappedAlliedBots = table();

        // Cycle through the bot table
        foreach ( id and bot in BotTable )
        {
            if ( bot.GetTeam() == TEAM.ALLIES && bot.GetPosition().y < forwardBunkerDoorsY )
            {
                // The current bot is trapped
                trappedAlliedBots[ index ] = bot;
                index += 1;
            }
        }

        if ( index > 0 )
        {
            // Assign a role to the inside goals
            Util.SetRoleForTable( Goals.Allies.Bunker, Roles.Allies.Bunker );

            // Cycle through the trapped bots
            // in order to give them the role
            foreach ( index and bot in trappedAlliedBots )
            {
                bot.SetRoles( Roles.Allies.Bunker );
                Util.MapDebugPrint( bot.Name + "^b is trapped but kept his goals active!", true );
            }

            // Finally, enable these goals
            SetAvailableMapGoals( TEAM.ALLIES, true, Goals.Allies.Bunker );
        }
    },

    /**
     * Manages the Library (upper documents) when some Allied bot is trapped inside it.
     * In other words, the Tank hasn't been escorted yet AND the Library Rear Door is built.
     * Only if this is the correct step of the map progress.
     */
    ManageLibraryTrappedAlliedBots = function()
    {
        // If the objective is already secured...
        // Dunno why Allied bots would hang in there but heh
        if ( Map.areUpperDocumentsSecured )
        {
            Util.ClearRoleForTable( Goals.Allies.Library, Roles.Allies.Library );
            return;
        }

        // Only if Allies spawn at the Transmitter
        if ( !Map.isAlliedTransmitterBuilt )
        {
            Util.ClearRoleForTable( Goals.Allies.Library, Roles.Allies.Library );
            return;
        }

        // Case when both Tank not yet escorted and Library Rear Door is up only
        // Otherwise we exit
        if ( Map.isTankEscorted || !Map.isLibraryRearDoorBuilt )
        {
            Util.ClearRoleForTable( Goals.Allies.Library, Roles.Allies.Library );
            return;
        }

        index = 0;
        trappedAlliedBots = table();

        // Cycle through the bot table
        foreach ( id and bot in BotTable )
        {
            if ( bot.GetTeam() == TEAM.ALLIES && Map.IsBotInsideLibrary( bot ) )
            {
                // The current bot is trapped
                trappedAlliedBots[ index ] = bot;
                index += 1;
            }
        }

        if ( index > 0 )
        {
            // Assign a role to the inside goals
            Util.SetRoleForTable( Goals.Allies.Library, Roles.Allies.Library );

            // Cycle through the trapped bots
            // in order to give them the role
            foreach ( index and bot in trappedAlliedBots )
            {
                bot.SetRoles( Roles.Allies.Library );
                Util.MapDebugPrint( bot.Name + "^b is trapped but kept his goal active!", true );
            }

            // Finally, enable these goals
            SetAvailableMapGoals( TEAM.ALLIES, true, Goals.Allies.Library );
        }
    },

    /**
     * Manages the camping goals (ATTACK, CAMP, DEFEND) of the map
     * after the Axis Generator (area between Transmitter and Library).
     * Is taken into account both availability and role assignment.
     */
    ManagePostGeneratorCampingGoals = function()
    {
        // Simply disable everything
        Util.DisableGoal( "CAMP_.*" );
        SetAvailableMapGoals( TEAM.AXIS, false, "DEFEND_.*" );
        SetAvailableMapGoals( TEAM.ALLIES, false, "ATTACK_.*" );

        // If documents are in the nature,
        // just camp the Transmitter
        if ( Map.iDocumentsCount > 0 )
        {
            SetGoalPriority( "CAMP_Allied_Transmitter.*", 0.78 ); // Below FLAG goals
            Util.ClearRoleForTable( Goals.Axis.AlliedTransmitter, Roles.Axis.AlliedTransmitter );
            Util.EnableGoal( "CAMP_Allied_Transmitter.*" );
            return;
        }

        // Always available for Axis
        SetAvailableMapGoals( TEAM.AXIS, true, "CAMP_Allied_Transmitter.*" );
        SetGoalPriority( "CAMP_Allied_Transmitter.*", 0.5 ); // Default
        Util.SetRoleForTable( Goals.Axis.AlliedTransmitter, Roles.Axis.AlliedTransmitter );

        // Depending on Documents availability and reachability
        if ( Map.areLowerDocumentsAvailable )
        {
            SetAvailableMapGoals( TEAM.AXIS, true, "DEFEND_Lower_Docs.*" );
        }

        if ( Map.areUpperDocumentsReachable )
        {
            SetAvailableMapGoals( TEAM.AXIS, true, "DEFEND_Upper_Docs.*" );
        }

        // Tank location-related camping goals
        // The role for DEFEND spots is handled in the OnMapLoad section
        if ( Map.tankLocation == MapProgress.TankLocation.FirstArea )
        {
            SetAvailableMapGoals( TEAM.AXIS, true, "DEFEND_First_Area.*" );
            SetAvailableMapGoals( TEAM.ALLIES, true, "ATTACK_First_Area.*" );
        }
        else if ( Map.tankLocation == MapProgress.TankLocation.SecondArea )
        {
            SetAvailableMapGoals( TEAM.AXIS, true, "DEFEND_Second_Area.*" );
            SetAvailableMapGoals( TEAM.ALLIES, true, "ATTACK_Second_Area.*" );
        }
        else if ( Map.tankLocation == MapProgress.TankLocation.ThirdArea )
        {
            SetAvailableMapGoals( TEAM.AXIS, true, "DEFEND_Third_Area.*" );
            SetAvailableMapGoals( TEAM.ALLIES, true, "ATTACK_Third_Area.*" );
        }
    },

    /**
     * Sets (or unsets) the Beach roles for Allies.
     */
    SetBeachRoles = function( bSet )
    {
        goalTable = { ".*_Beach_East.*", "PLANT_Forward_Bunker_Doors" };
        role = ROLE.ATTACKER;

        if ( bSet )
        {
            Util.SetRoleForTable( goalTable, role );
        }
        else
        {
            Util.ClearRoleForTable( goalTable, role );
        }
    },

    /**
     * Updates the value of Map.areUpperDocumentsReachable,
     * and make available or unavailable FLAG_documents_2.
     */
    UpdateAreUpperDocumentsReachable = function()
    {
        Map.areUpperDocumentsReachable =
            !Map.areUpperDocumentsSecured
            && ( !Map.isLibraryRearDoorBuilt || Map.isTankEscorted );

        SetAvailableMapGoals( TEAM.ALLIES, Map.areUpperDocumentsReachable, "FLAG_documents_2" );

        // After Axis Generator destruction but before Allied Transmitter first construction,
        // if the Allies destroy the Library Rear Door, the upper documents are enabled,
        // but there isn't any CAPPOINT goal available yet...
        // Handle this particular case just here, instead of upon objective stealing...
        // but without condition overkill, simply the upper documents availability
        if ( Map.areUpperDocumentsReachable )
        {
            Map.ManageCappointGoals();
        }
    },

    // ************************************************************************

    /**
     * Handles Axis spawn if possible when an objective gets stolen.
     * Also enables CAMP spots around the Allied Transmitter for everyone.
     */
    Documents_Taken = function()
    {
        // If the Axis Bunker Spawn is still available, move there
        if ( !Map.isAxisGeneratorDestroyed )
        {
            Util.ChangeSpawn( TEAM.AXIS, Spawns.AXIS_BUNKER_SPAWN );
        }

        Map.ManageAirstrikeGoals();

        Map.ManagePostGeneratorCampingGoals();
    },

    /**
     * Upon returning or securing an objective, if no one is still in the nature,
     * Axis should move their spawn back to the Reserve.
     * Also disables the CAMP spots around the Allied Transmitter if needed,
     * and re-enables DEFEND spots for Axis.
     */
    Documents_Returned_Or_Secured = function()
    {
        if ( Map.iDocumentsCount == 0 )
        {
            Util.ChangeSpawn( TEAM.AXIS, Spawns.AXIS_RESERVE_SPAWN );
        }

        Map.ManageAirstrikeGoals();

        Map.ManagePostGeneratorCampingGoals();
    },

    // ************************************************************************

    // *** TRIGGER CALLBACKS ***
    Alllied_Transmitter_Built = function( trigger )
    {
        Map.iAlliedTransmitterCount += 1;

        // Just exec the rest once, upon the fifth call in a row
        if ( Map.iAlliedTransmitterCount < 5 )
        {
            return;
        }

        // Reset the counter for the next time (if destroyed)
        Map.iAlliedTransmitterCount = 0;

        Map.isAlliedTransmitterBuilt = true;

        // Just upon the first time
        if ( !Map.hasAlliedTransmitterBeenBuiltOnce )
        {
            // Important step, simply reset the available goals list
            Util.DisableGoal( ".*", true );

            Util.EnableGoal( "AMMOCAB_.*" );
            Util.EnableGoal( "HEALTHCAB_.*" );

            Map.ManageAirstrikeGoals();

            SetAvailableMapGoals( TEAM.AXIS, true, {
                "BUILD_Axis_Library_Rear_Door",
                "PLANT_Alllied_Transmitter",
            });

            SetAvailableMapGoals( TEAM.ALLIES, true, {
                "BUILD_Tank",
                "PLANT_Axis_Library_Rear_Door",
                "SNIPE_Allied_Road_Bottom_Right",
            });

            SetAvailableMapGoals( TEAM.ALLIES, Map.IsTankEscortable(), {
                "ESCORT_Tank",
                "MOUNT_Tank",
            });

            // Check if PLANT_Axis_Generator is still available (it moves Axis spawn back)
            if ( !Map.isAxisGeneratorDestroyed )
            {
                SetAvailableMapGoals( TEAM.ALLIES, true, "PLANT_Axis_Generator" );
            }

            // Because Allies are moved to a new spawn,
            // make the ones not in the Transmitter area selfkill
            Functions.SetSpawnForTeam( TEAM.ALLIES, Spawns.ALLIED_TRANSMITTER_SPAWN );
            foreach ( id and bot in BotTable )
            {
                if ( bot.GetTeam() == TEAM.ALLIES && bot.GetPosition().x < 1900 )
                {
                    bot.ExecCommand( "kill" );
                }
            }

            Map.ManagePostGeneratorCampingGoals();

            Util.MapDebugPrint( "Alllied_Transmitter_Built_For_The_First_Time", true );
        }

        Map.ManageCappointGoals();

        // The Allied Transmitter Spawn is available
        // Also indicates that the Transmitter has been built at least once
        Map.hasAlliedTransmitterBeenBuiltOnce = true;

        // Manage Beach PLANT goals if the Transmitter was built already once
        SetAvailableMapGoals( TEAM.ALLIES, false, {
            "PLANT_Axis_Bunker_Wall",
            "PLANT_Forward_Bunker_Doors",
        });

        Util.MapDebugPrint( "Alllied_Transmitter_Built" );
    },

    Axis_Command_Post_Built = function( trigger )
    {
        // Its location is hardly reachable by Allies...

        Util.MapDebugPrint( "Axis_Command_Post_Built", true );
    },

    /**
     * Both Bunker Doors and Library Rear Door having the same wm_announce,
     * this function dispatches to the correct function depending on the entity name
     */
    Axis_Doors_Built_Dispatch = function( trigger )
    {
        entName = GetEntityName( trigger.Entity );
        if ( entName == "bunkerdoors" )
        {
            Map.Forward_Bunker_Doors_Built( trigger );
        }
        else if ( entName == "axis_rear_door" )
        {
            Map.Axis_Library_Rear_Door_Built( trigger );
        }
        else
        {
            Util.MapDebugPrint( "Unexpected entity name: " + entName, true );
        }
    },

    Axis_Library_Rear_Door_Built = function( trigger )
    {
        Map.isLibraryRearDoorBuilt = true;
        Map.UpdateAreUpperDocumentsReachable();
        Map.ManagePostGeneratorCampingGoals();

        // Manage eventually trapped bots
        Map.ManageLibraryTrappedAlliedBots();

        Util.MapDebugPrint( "Axis_Library_Rear_Door_Built" );
    },

    Forward_Bunker_Doors_Built = function( trigger )
    {
        Map.areForwardBunkerDoorsBuilt = true;

        if ( !Map.isAxisBunkerWallDestroyed )
        {
            Util.DisableGoal( "ROUTE_GeneratorNode2" );

            SetAvailableMapGoals( TEAM.ALLIES, false, {
                "ATTACK_Axis_Generator.*",
                "PLANT_Axis_Generator",
            });
        }

        Util.DisableGoal( "ROUTE_GeneratorNode1" );

        if ( !Map.hasAlliedTransmitterBeenBuiltOnce && !Map.isAxisGeneratorDestroyed )
        {
            Map.SetBeachRoles( true );
        }

        // Manage eventually trapped bots
        Map.ManageBunkerTrappedAlliedBots();

        Util.MapDebugPrint( "Forward_Bunker_Doors_Built" );
    },

    Tank_Built = function( trigger )
    {
        Map.isTankBuilt = true;

        // Only the first time
        if ( !Map.isTankStolen )
        {
            ETUtil.SetStartIndex( "BUILD_Tank", TankOffsets.Build.Back );

            // So it moves a bit for reachability reasons
            sleep( 4 );
        }

        SetAvailableMapGoals( TEAM.ALLIES, Map.IsTankEscortable(), {
            "ESCORT_Tank",
            "MOUNT_Tank",
        });

        Util.MapDebugPrint( "Tank_Built" );
    },

    // ************************************************************************

    Axis_Bunker_Wall_Planted = function( trigger )
    {
        Map.Axis_Bunker_Wall_Dyno += 1;

        if ( !Map.isAxisGeneratorDestroyed )
        {
            SetAvailableMapGoals( TEAM.AXIS, false, "ARTILLERY_S_Axis_Wall" );
        }

        Util.MapDebugPrint( "Axis_Bunker_Wall_Planted" );
    },

    Axis_Bunker_Wall_Defused = function( trigger )
    {
        Map.Axis_Bunker_Wall_Dyno -= 1;

        if ( Map.Axis_Bunker_Wall_Dyno == 0 && !Map.isAxisGeneratorDestroyed )
        {
            SetAvailableMapGoals( TEAM.AXIS, true, "ARTILLERY_S_Axis_Wall" );
        }

        Util.MapDebugPrint( "Axis_Bunker_Wall_Defused" );
    },

    // ************************************************************************

    Alllied_Transmitter_Destroyed = function( trigger )
    {
        Map.iAlliedTransmitterCount += 1;

        // Just exec the rest once, upon the fifth call in a row
        if ( Map.iAlliedTransmitterCount < 5 )
        {
            return;
        }

        // Reset the counter for the next time (if destroyed)
        Map.iAlliedTransmitterCount = 0;

        Map.isAlliedTransmitterBuilt = false;

        Map.ManageCappointGoals();

        SetAvailableMapGoals( TEAM.ALLIES, true, "BUILD_Alllied_Transmitter" );

        // Allies are pushed back at the Beach,
        // re-enable the related PLANT goals
        SetAvailableMapGoals( TEAM.ALLIES, true, "PLANT_Forward_Bunker_Doors" );
        if ( !Map.isAxisBunkerWallDestroyed )
        {
            SetAvailableMapGoals( TEAM.ALLIES, true, "PLANT_Axis_Bunker_Wall" );
        }

        Util.MapDebugPrint( "Alllied_Transmitter_Destroyed" );
    },

    Axis_Bunker_Wall_Destroyed = function( trigger )
    {
        Map.isAxisBunkerWallDestroyed = true;

        // blockwall flags refresh
        sleep( 1 );

        Util.EnableGoal( "ROUTE_AlliedWestLanderNode3" );

        // In case Allies focus on this
        SetAvailableMapGoals( TEAM.AXIS, true, "BUILD_Axis_Library_Rear_Door" );

        // If Allies still spawn at the Beach,
        // no matter which spawn Axis have at this moment
        if ( !hasAlliedTransmitterBeenBuiltOnce )
        {
            SetAvailableMapGoals( TEAM.AXIS, true, "DEFEND_AxisBunkerWall.*" );
        }

        SetAvailableMapGoals( TEAM.ALLIES, true, "BUILD_Alllied_Transmitter" );

        if ( !Map.isAxisGeneratorDestroyed )
        {
            SetAvailableMapGoals( TEAM.AXIS, true, "ARTILLERY_S_Axis_Wall" );

            SetAvailableMapGoals( TEAM.ALLIES, true, {
                "ATTACK_Axis_Generator.*",
                "PLANT_Axis_Generator",
            });
        }

        // Manage eventually trapped bots
        Map.ManageBunkerTrappedAlliedBots();

        Functions.Print( "Axis_Bunker_Wall_Destroyed", true );
    },

    Axis_Command_Post_Destroyed = function( trigger )
    {
        SetAvailableMapGoals( TEAM.AXIS, true, "BUILD_Axis_Command_Post" );

        Util.MapDebugPrint( "Axis_Command_Post_Destroyed" );
    },

    Axis_Generator_Destroyed = function( trigger )
    {
        // TODO: What if this happens when the Tank is already escorted?
        // TODO: What if this happens while the obj is stolen?
        Map.isAxisGeneratorDestroyed = true;

        Util.DisableGoal( ".*", true );

        Util.EnableGoal( "AMMOCAB_.*" );
        Util.EnableGoal( "HEALTHCAB_.*" );

        Map.ManageAirstrikeGoals();

        // The only moment Axis can build their CP,
        // because it is at the bottom of the map...
        // But don't let Allies waste efforts trying to reach it
        SetAvailableMapGoals( TEAM.AXIS, true, {
            "BUILD_Axis_Command_Post",
            "MOBILEMG42_.*",
            "SNIPE_Axis_.*",
        });

        SetAvailableMapGoals( TEAM.ALLIES, true, {
            "BUILD_Alllied_Transmitter",
            "BUILD_Tank",
            "PLANT_Axis_Library_Rear_Door",
            "PLANT_Forward_Bunker_Doors",
        });
        if ( !Map.isAxisBunkerWallDestroyed )
        {
            // In case it never happens during the map progress...
            Util.EnableGoal( "ROUTE_AlliedWestLanderNode3" );

            SetAvailableMapGoals( TEAM.ALLIES, true, "PLANT_Axis_Bunker_Wall" );
        }

        SetAvailableMapGoals( TEAM.ALLIES, Map.IsTankEscortable(), {
            "ESCORT_Tank",
            "MOUNT_Tank",
        });

        Map.ManagePostGeneratorCampingGoals();

        // Finally, clear roles
        Map.SetBeachRoles( false );

        Functions.Print( "Axis_Generator_Destroyed", true );
    },

    Axis_Library_Rear_Door_Destroyed = function( trigger )
    {
        Map.isLibraryRearDoorBuilt = false;

        // TODO: Check if Allies try to reach that goal even though it isn't available
        //SetAvailableMapGoals( TEAM.ALLIES, false, "PLANT_Axis_Library_Rear_Door" );

        Map.UpdateAreUpperDocumentsReachable();
        Map.ManagePostGeneratorCampingGoals();

        // Manage eventually trapped bots
        Map.ManageLibraryTrappedAlliedBots();

        Util.MapDebugPrint( "Axis_Library_Rear_Door_Destroyed" );
    },

    Forward_Bunker_Doors_Destroyed = function( trigger )
    {
        Map.areForwardBunkerDoorsBuilt = false;

        // Let the blockwall flags update
        sleep( 2 );

        Util.EnableGoal( "ROUTE_GeneratorNode.*" );

        if ( !Map.isAxisGeneratorDestroyed )
        {
            SetAvailableMapGoals( TEAM.ALLIES, true, {
                "PLANT_Axis_Generator",
                "ATTACK_Axis_Generator.*",
            });
        }

        Map.SetBeachRoles( false );

        // Manage eventually trapped bots
        Map.ManageBunkerTrappedAlliedBots();

        Util.MapDebugPrint( "Forward_Bunker_Doors_Destroyed" );
    },

    Tank_Destroyed = function( trigger )
    {
        Map.isTankBuilt = false;

        SetAvailableMapGoals( TEAM.ALLIES, false, {
            "ESCORT_Tank",
            "MOUNT_Tank",
        });

        Util.MapDebugPrint( "Tank_Destroyed" );
    },

    Tank_Stolen = function( trigger )
    {
        Map.isTankStolen = true;

        Util.MapDebugPrint( "Tank_Stolen", true );
    },

    // ************************************************************************

    documents_1_Taken = function( trigger )
    {
        Map.areLowerDocumentsAvailable = false;
        Map.iDocumentsCount += 1;

        Map.Documents_Taken();

        Map.AlliedSuicideUponObjTaken();

        Util.MapDebugPrint( "documents_1_Taken" );
    },

    documents_2_Taken = function( trigger )
    {
        Map.areUpperDocumentsAvailable = false;
        Map.iDocumentsCount += 1;

        Map.Documents_Taken();

        Map.AlliedSuicideUponObjTaken();

        Util.MapDebugPrint( "documents_2_Taken" );
    },

    documents_1_Returned = function( trigger )
    {
        Map.areLowerDocumentsAvailable = true;
        Map.iDocumentsCount -= 1;

        Map.Documents_Returned_Or_Secured();

        Util.MapDebugPrint( "documents_1_Returned" );
    },

    documents_2_Returned = function( trigger )
    {
        Map.areUpperDocumentsAvailable = true;
        Map.iDocumentsCount -= 1;

        Map.Documents_Returned_Or_Secured();

        Util.MapDebugPrint( "documents_2_Returned" );
    },

    documents_1_Secured = function( trigger )
    {
        Map.iDocumentsSecured += 1;

        if ( Map.iDocumentsSecured < 2 )
        {
            Map.areLowerDocumentsSecured = true;

            Map.iDocumentsCount -= 1;

            Map.Documents_Returned_Or_Secured();

            Functions.Print( "documents_1_Secured", true );
        }
        else
        {
            Functions.Print( "Endgame" );
        }
    },

    documents_2_Secured = function( trigger )
    {
        Map.iDocumentsSecured += 1;

        if ( Map.iDocumentsSecured < 2 )
        {
            Map.areUpperDocumentsSecured = true;

            Map.iDocumentsCount -= 1;

            Map.Documents_Returned_Or_Secured();

            // Disable Axis Library Rear Door goals
            SetAvailableMapGoals( TEAM.AXIS, false, "BUILD_Axis_Library_Rear_Door" );
            SetAvailableMapGoals( TEAM.ALLIES, false, "PLANT_Axis_Library_Rear_Door" );

            Functions.Print( "documents_2_Secured", true );
        }
        else
        {
            Functions.Print( "Endgame" );
        }
    },
};

global OnMapLoad = function()
{
    Functions.timeLimitInMinutes = Ceil( GetGameTimeLeft() ) / 60;

    // *** TRIGGERS ***
    OnTrigger( "Allied Transmitter constructed. Charge speed increased!", Map.Alllied_Transmitter_Built );
    OnTrigger( "Axis Command Post constructed. Charge speed increased!", Map.Axis_Command_Post_Built );

    OnTrigger( "the Forward Bunker Doors have been constructed.", Map.Axis_Doors_Built_Dispatch );

    OnTrigger( "The Tank has been repaired!", Map.Tank_Built );

    OnTrigger( "Planted at The Axis Bunker Wall.", Map.Axis_Bunker_Wall_Planted );
    OnTrigger( "Defused at The Axis Bunker Wall.", Map.Axis_Bunker_Wall_Defused );

    OnTrigger( "Axis team has destroyed the Allied Transmitter!", Map.Alllied_Transmitter_Destroyed );
    OnTrigger( "The Axis Bunker Wall has been destroyed.", Map.Axis_Bunker_Wall_Destroyed );
    OnTrigger( "Allied team has destroyed the Axis Command Post!", Map.Axis_Command_Post_Destroyed );
    OnTrigger( "The Axis Generator has been destroyed.", Map.Axis_Generator_Destroyed );
    OnTrigger( "The Axis Library Rear Door has been destroyed.", Map.Axis_Library_Rear_Door_Destroyed );
    OnTrigger( "the Forward Bunker Doors have been destroyed.", Map.Forward_Bunker_Doors_Destroyed );
    OnTrigger( "The Tank has been damaged!", Map.Tank_Destroyed );

    OnTrigger( "Allied team has stolen the Tank!", Map.Tank_Stolen );

    OnTrigger( "Allies have stolen the documents set 1!", Map.documents_1_Taken );
    OnTrigger( "Allies have stolen the documents set 2!", Map.documents_2_Taken );
    OnTrigger( "Documents set 1 returned!", Map.documents_1_Returned );
    OnTrigger( "Documents set 2 returned!", Map.documents_2_Returned );
    OnTrigger( "Allies have transmitted the documents set 1!", Map.documents_1_Secured );
    OnTrigger( "Allies have transmitted the documents set 2!", Map.documents_2_Secured );

    // *** CLEAR ALL GOALS FOR BOTH TEAMS ***
    Util.DisableGoal( ".*", true ); // All but routes
    Util.DisableGoal( "ROUTE_AlliedWestLanderNode3" );

    // *** AXIS GOALS ***
    SetAvailableMapGoals( TEAM.AXIS, true, {
        ".*ARTILLERY_.*Axis_Wall.*",
        "BUILD_Forward_Bunker_Doors",
        "DEFEND_Alpha.*",
        "MOUNTMG42_1003",
        "REPAIRMG42_1003",
        "SNIPE_Axis_Ramp",
    });

    // *** ALLIED GOALS ***
    SetAvailableMapGoals( TEAM.ALLIES, true, {
        ".*ARTILLERY_.*Beach_.*",
        "ATTACK_Axis_Generator.*",
        "ATTACK_Beach_.*",
        "PLANT_Axis_Bunker_Wall",
        "PLANT_Axis_Generator",
        "PLANT_Forward_Bunker_Doors",
        "SNIPE_Beach_.*",
    });

    // *** ROLES ***
    Map.SetBeachRoles( true );
    SetGoalRole( "DEFEND_AxisBunkerWall.*", ROLE.DEFENDER );
    Util.ClearRoleForTable( Goals.Allies.Bunker, Roles.Allies.Bunker );

    // For later regarding the Tank protection
    SetGoalRole( "DEFEND_.*_Area.*", ROLE.DEFENDER2 );

    // Also for later, since Axis also defend the Allied Transmitter
    Util.SetRoleForTable( Goals.Axis.Library.Bottom, Roles.Axis.Library.Bottom );
    Util.SetRoleForTable( Goals.Axis.Library.Top, Roles.Axis.Library.Top );

    // *** GOAL PROPERTIES ***
    OnTriggerRegion( AABB( 1535.888, -2560.875, 321.125, 1928.433, -2303.135, 395.125 ), Map.Tank_At_First_Tunnel );
    OnTriggerRegion( AABB( -768.937, -2688.875, 313.125, -248.788, -2431.136, 387.125 ), Map.Tank_At_Second_Tunnel );
    OnTriggerRegion( AABB( -1951, -2564, 344, -1905, -2306, 408 ), Map.TankTriggerInfo );

    // Spawn triggers so Allied bots
    // who died inside the Bunker
    // while both Bunker Wall and Forward Bunker Doors are there
    // get their inner Bunker goals disabled
    OnTriggerRegion( AABB( -1901.688, 3133.092, 41.125, -1723.998, 3343.194, 115.125 ), Map.Allied_Lander_Spawn_Trigger );
    OnTriggerRegion( AABB(  1065.164, 2620.570, 41.337,  1256.277, 2902.938, 115.337 ), Map.Allied_Lander_Spawn_Trigger );

    // Same as above, but for the Library goals
    OnTriggerRegion( AABB( 4168.330, -2168.071, 641.125, 4670.734, -1755.702, 843.125 ), Map.Allied_Transmitter_Spawn_Trigger );

    ETUtil.SetStartIndex( "BUILD_Tank", TankOffsets.Build.LeftFront );

    SetGoalPriority( "PLANT_Axis_Generator",        0.91 ); // Above BUILD_Alllied_Transmitter
    SetGoalPriority( "BUILD_Tank",                  0.89 ); // Below BUILD_Alllied_Transmitter
    SetGoalPriority( "FLAG_documents_1",            0.79 ); // Below FLAG_documents_2
    SetGoalPriority( "BUILD_Axis_Command_Post",     0.77 ); // Below FLAG goals _AND_ CAMP_Allied_Transmitter.*
    SetGoalPriority( "ESCORT_Tank",                 0.77 ); // Below MOUNT_Tank
    SetGoalPriority( "ATTACK_Axis_Generator.*",     0.71 ); // Above ATTACK_Beach_.* and SNIPE (not (CALL)ARTILLERY)

    Util.AddUsePoint( "BUILD_Alllied_Transmitter", Vec3( 3320, 5, 500 ) );
    Util.AddUsePoint( "PLANT_Alllied_Transmitter", Vec3( 2890, -50, 505 ) );
    Util.AddUsePoint( "PLANT_Axis_Generator", Vec3( 1370, -834, 648 ) );
    Util.AddUsePoint( "PLANT_Axis_Library_Rear_Door", Vec3( -3310, -1750, 675 ) );
    Util.AddUsePoint( "PLANT_Forward_Bunker_Doors", Vec3( 400, -365, 585 ) );

    Util.LimitToClass( "PLANT_Axis_Library_Rear_Door", TEAM.ALLIES, CLASS.COVERTOPS );

    Util.SetMaxUsersInProgress( 3, "FLAG_.*" );

    // Init that field here, else the parser doesn't like it...
    Map.distanceBetweenCapPointAndFurthestCampGoal =
        Functions.GetDistance(
            GetGoal( "CAPPOINT_Transmitter" ),
            GetGoal( "CAMP_Allied_Transmitter1" ) ) + 100;

    TankArtilleryManagement.Init();

    Util.MapDebugPrint( "Omni-Bot 0.8x map script for 1944 Overlord by Mateos executed." );
};

global OnBotJoin = function( bot )
{
    bot.TargetBreakableDist = 90.0;
};

global InitializeRoutes = function()
{
    MapRoutes =
    {
        PLANT_Axis_Generator =
        {
            ROUTE_AlliedWestLanderSpawn =
            {
                ROUTE_AlliedWestLanderNode1 =
                {
                    ROUTE_AlliedWestLanderNode2 =
                    {
                        ROUTE_AlliedWestLanderNode3 =
                        {
                            ROUTE_GeneratorNode2 = {},
                        },

                        ROUTE_GeneratorNode1 =
                        {
                            ROUTE_GeneratorNode2 = {},
                        },
                    },
                },

                ROUTE_GeneratorNode1 =
                {
                    ROUTE_GeneratorNode2 = {},
                },
            },

            ROUTE_AlliedIsolatedBeachSpawn =
            {
                ROUTE_AlliedWestLanderNode1 =
                {
                    ROUTE_AlliedWestLanderNode2 =
                    {
                        ROUTE_AlliedWestLanderNode3 =
                        {
                            ROUTE_GeneratorNode2 = {},
                        },

                        ROUTE_GeneratorNode1 =
                        {
                            ROUTE_GeneratorNode2 = {},
                        },
                    },
                },

                ROUTE_GeneratorNode1 =
                {
                    ROUTE_GeneratorNode2 = {},
                },
            },
        },

        PLANT_Axis_Bunker_Wall =
        {
        },
        BUILD_Alllied_Transmitter =
        {
        },
        PLANT_Alllied_Transmitter =
        {
        },
        BUILD_Forward_Bunker_Doors =
        {
        },
        PLANT_Forward_Bunker_Doors =
        {
        },
        BUILD_Tank =
        {
        },
        FLAG_documents_1 =
        {
        },
        FLAG_documents_2 =
        {
        },
        BUILD_Axis_Library_Rear_Door =
        {
        },

        PLANT_Axis_Library_Rear_Door =
        {
            ROUTE_Allied_Transmitter_Spawn1 =
            {
                ROUTE_Transmitter_Hill =
                {
                    ROUTE_Second_Area_Top =
                    {
                        ROUTE_AlliedWestLanderNode3 = {},
                    },
                },
            },

            ROUTE_Allied_Transmitter_Spawn2 =
            {
                ROUTE_Transmitter_Hill =
                {
                    ROUTE_Second_Area_Top =
                    {
                        ROUTE_AlliedWestLanderNode3 = {},
                    },
                },
            },
        },

        BUILD_Axis_Command_Post =
        {
        },
        PLANT_Axis_Command_Post =
        {
        },

        // Route to avoid the deadly road...
        DEFEND_First_Area1 =
        {
            ROUTE_Axis_Barn_Spawn =
            {
                ROUTE_AlliedWestLanderNode3 =
                {
                    ROUTE_Second_Area_Top = {},
                },
            },

            ROUTE_Axis_Reserve_Spawn =
            {
                ROUTE_AlliedWestLanderNode3 =
                {
                    ROUTE_Second_Area_Top = {},
                },
            },
        },
    };

    // Copy some routes
    MapRoutes.ATTACK_Axis_Generator1 = MapRoutes.PLANT_Axis_Generator;
    MapRoutes.ATTACK_Axis_Generator2 = MapRoutes.PLANT_Axis_Generator;
    MapRoutes.ATTACK_Axis_Generator3 = MapRoutes.PLANT_Axis_Generator;
    MapRoutes.ATTACK_Axis_Generator4 = MapRoutes.PLANT_Axis_Generator;
    MapRoutes.ATTACK_Axis_Generator5 = MapRoutes.PLANT_Axis_Generator;
    MapRoutes.ATTACK_Axis_Generator1 = MapRoutes.PLANT_Axis_Generator;
    MapRoutes.ATTACK_Axis_Generator7 = MapRoutes.PLANT_Axis_Generator;
    MapRoutes.ATTACK_Axis_Generator8 = MapRoutes.PLANT_Axis_Generator;
    MapRoutes.ATTACK_Axis_Generator9 = MapRoutes.PLANT_Axis_Generator;
    MapRoutes.ATTACK_Axis_Generator10 = MapRoutes.PLANT_Axis_Generator;

    // Even moar routes
    MapRoutes.DEFEND_First_Area2 = MapRoutes.DEFEND_First_Area1;
    MapRoutes.DEFEND_First_Area3 = MapRoutes.DEFEND_First_Area1;
    MapRoutes.DEFEND_First_Area4 = MapRoutes.DEFEND_First_Area1;
    MapRoutes.DEFEND_First_Area5 = MapRoutes.DEFEND_First_Area1;
    MapRoutes.DEFEND_First_Area6 = MapRoutes.DEFEND_First_Area1;
    MapRoutes.DEFEND_First_Area7 = MapRoutes.DEFEND_First_Area1;
    MapRoutes.DEFEND_First_Area8 = MapRoutes.DEFEND_First_Area1;

    MapRoutes.DEFEND_Second_Area1 = MapRoutes.DEFEND_First_Area1;
    MapRoutes.DEFEND_Second_Area2 = MapRoutes.DEFEND_First_Area1;
    MapRoutes.DEFEND_Second_Area3 = MapRoutes.DEFEND_First_Area1;
    MapRoutes.DEFEND_Second_Area4 = MapRoutes.DEFEND_First_Area1;
    MapRoutes.DEFEND_Second_Area5 = MapRoutes.DEFEND_First_Area1;
    MapRoutes.DEFEND_Second_Area6 = MapRoutes.DEFEND_First_Area1;
    MapRoutes.DEFEND_Second_Area7 = MapRoutes.DEFEND_First_Area1;
    MapRoutes.DEFEND_Second_Area8 = MapRoutes.DEFEND_First_Area1;
    MapRoutes.DEFEND_Second_Area9 = MapRoutes.DEFEND_First_Area1;

    Util.Routes( MapRoutes );
};

/**
 * Artillery automatic management section
 * As long as the Tank is moving, just keep the nearest ARTILLERY_D/ARTILLERY_S enabled
 * From their position, look for their reachable CALLARTILLERY, and have them enabled
 * Note: Air strike isn't here because it's fine to just have one per road section
 */
global TankArtilleryManagement =
{
    /** Artillery goal list */
    ARTILLERY_GOAL_LIST,

    /** CallArtillery goal list */
    CALLARTILLERY_GOAL_LIST,

    /** Distance from Tank position within which the artillery goals are enabled */
    DISTANCE_FROM_TANK,

    /** Tank entity, from which MOVER_tank position is monitored */
    TANK_ENTITY,

    /** Last recorded Tank position */
    tankPreviousPosition,

    Init = function()
    {
        OnTrigger( "Start tank", TankArtilleryManagement.Tank_Moving );

        TankArtilleryManagement.ARTILLERY_GOAL_LIST = Util.GoalTable( "ARTILLERY_.*Road.*" );
        TankArtilleryManagement.CALLARTILLERY_GOAL_LIST = Util.GoalTable( "CALLARTILLERY_Road.*" );

        TankArtilleryManagement.DISTANCE_FROM_TANK = 256 + 128;

        TankArtilleryManagement.TANK_ENTITY = GetEntityByName( "tank" );
        TankArtilleryManagement.tankPreviousPosition = GetEntPosition( TankArtilleryManagement.TANK_ENTITY );
    },

    Tank_Moving = function( trigger )
    {
        //Util.MapDebugPrint( "Tank starts moving...", true );
        sleep( 1.5 ); // Let the Tank actually start moving a bit...
        TankArtilleryManagement.TrackTankMovement();
    },

    TrackTankMovement = function()
    {
        // This is a recursive loop, so let the other triggers actually happen
        yield();

        tankCurrentPosition = GetEntPosition(TankArtilleryManagement.TANK_ENTITY);
        if ( tankCurrentPosition != TankArtilleryManagement.tankPreviousPosition )
        {
            //Util.MapDebugPrint( "Tank is moving!", true );
            TankArtilleryManagement.tankPreviousPosition = tankCurrentPosition;
            sleep( 0.5 ); // Entity position refresh
            TankArtilleryManagement.ManageArtilleryGoals();
            TankArtilleryManagement.TrackTankMovement();
        }
        else
        {
            //Util.MapDebugPrint( "Tank stopped!", true );
        }
    },

    ManageArtilleryGoals = function()
    {
        // First step: disable all artillery and call artillery goals
        foreach ( artilleryGoal in TankArtilleryManagement.ARTILLERY_GOAL_LIST )
        {
            artilleryGoal.SetAvailable( TEAM.AXIS, false );
        }

        foreach ( artilleryGoal in TankArtilleryManagement.CALLARTILLERY_GOAL_LIST )
        {
            artilleryGoal.SetAvailable( TEAM.AXIS, false );
        }

        // Second step: enable the artillery goals
        // Third step: enable the call artillery goals that can reach the previously enabled artillery ones
        foreach ( artilleryGoal in TankArtilleryManagement.ARTILLERY_GOAL_LIST )
        {
            if ( DistanceBetween( artilleryGoal, TankArtilleryManagement.TANK_ENTITY ) <= TankArtilleryManagement.DISTANCE_FROM_TANK )
            {
                artilleryGoal.SetAvailable( TEAM.AXIS, true );
                TankArtilleryManagement.EnableCallArtilleryGoalsForArtilleryGoal( artilleryGoal );
            }
        }
    },

    // Note: Also renders a trace line between the CALLARTILLERY goal to its ARTILLERY goal
    EnableCallArtilleryGoalsForArtilleryGoal = function( artilleryGoal )
    {
        artilleryGoalPosition = artilleryGoal.GetPosition();
        foreach ( callArtilleryGoal in TankArtilleryManagement.CALLARTILLERY_GOAL_LIST )
        {
            // If the goal is already enabled, skip it!
            // Comment this out to draw all the debug lines
            if ( callArtilleryGoal.IsAvailable( TEAM.AXIS ) )
            {
                continue;
            }

            // Trace from the eyes!
            // callArtilleryGoal.GetEntity() sadly returns null, since it's not a game entity...
            // so it can't be used with GetEntEyePosition :'(
            callArtilleryGoalPosition = callArtilleryGoal.GetPosition();
            callArtilleryGoalEyePosition = Vector3( callArtilleryGoalPosition.x, callArtilleryGoalPosition.y, callArtilleryGoalPosition.z + 64 );

            tr = TraceLine( callArtilleryGoalEyePosition, artilleryGoalPosition, null, TRACE.FLOODFILL, 0, true );
            if ( tr.fraction == 1 )
            {
                callArtilleryGoal.SetAvailable( TEAM.AXIS, true );

                // Draw a line to see what's going on!
                DrawDebugLine( callArtilleryGoalEyePosition, artilleryGoalPosition, COLOR.GREEN, 5 );
            }
            else
            {
                // For testing purposes
                //DrawDebugLine( callArtilleryGoalEyePosition, artilleryGoalPosition, COLOR.YELLOW, 5 );
            }
        }
    },
};

global Functions =
{
    /**
     * Map time limit.
     */
    timeLimitInMinutes,

    /**
     * Prints a text with elapsed and left times.
     * Does not print active goals.
     * @param text Text to print.
     */
    Print = function( text )
    {
        Functions.Print( text, false );
    },

    /**
     * Prints a text with elapsed and left times.
     * @param text Text to print.
     * @param printActiveGoals Either print or not active goals.
     */
    Print = function( text, printActiveGoals )
    {
        Util.MapDebugPrint( text + Functions.GetFormattedElaspedTime() + Functions.GetFormattedTimeLeft(), !printActiveGoals );
    },

    /**
     * Builds and returns the elapsed time since the round has started.
     */
    GetFormattedElaspedTime = function()
    {
        totalTimeInSeconds = Functions.timeLimitInMinutes * 60;
        gameTimeLeftInSeconds = Floor( GetGameTimeLeft() );
        elapsedTimeInSeconds = totalTimeInSeconds - gameTimeLeftInSeconds;

        elapsedTimeMinutes = Floor( elapsedTimeInSeconds / 60 );
        elapsedTimeSeconds = elapsedTimeInSeconds - elapsedTimeMinutes * 60;
        if ( elapsedTimeSeconds < 10 )
        {
            elapsedTimeSeconds = "0" + elapsedTimeSeconds;
        }

        return " (" + elapsedTimeMinutes + ":" + elapsedTimeSeconds + " elapsed)";
    },

    /**
     * Builds and returns the game time left, formatted as mm:ss.
     */
    GetFormattedTimeLeft = function()
    {
        gameTimeLeft = Floor( GetGameTimeLeft() );
        minutes = Floor( gameTimeLeft / 60 );
        seconds = gameTimeLeft - minutes * 60;
        if ( seconds < 10 )
        {
            seconds = "0" + seconds;
        }

        return " (" + minutes + ":" + seconds + " left)";
    },

    GetDistance = function( ent1, ent2 )
    {
        ent1Position = ent1.GetPosition();
        ent2Position = ent2.GetPosition();
        x = ent1Position.x - ent2Position.x;
        y = ent1Position.y - ent2Position.y;
        return Sqrt( x * x + y * y );
    },

    GetReinforceTime = function( team )
    {
        foreach ( id and bot in BotTable )
        {
            if ( bot.GetTeam() == team )
            {
                return bot.GetReinforceTime();
            }
        }
    },

    SetSpawnForTeam = function( team, spawn )
    {
        foreach( bot in BotTable )
        {
            if ( bot.GetTeam() == team )
            {
                bot.ChangeSpawnPoint( spawn );
            }
        }
    },
};
