Page 1 of 1

n_bot

PostPosted: Wed Mar 19, 2014 12:05 am
by tazinator
Our beloved N_Bot, short for normal bot.. :P

He eschews shield in favor of turbo. very minor change (in the code) that you will appreciate

It gets beat by shield users pretty easily... so you practice fighting without shield!

  Code:
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
--
-- N_Bot, our beloved Normal Bot
--
-- Works on all game modes, some better than others
--
-- Authors:
--  - Unknown    (original QuickBot v2 code)
--  - sam686     (AI code for gametypes, other improvements)
--  - watusimoto (maintenance, upgrades)
--  - raptor     (maintenance, upgrades)
--  - Fordcars   (AI improvements)
--  - Tazinator  (Changed shield to Turbo)
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------


goalPt = point.new(0,0)

prevtarget = nil

gotoPositionWasNil = true
 
o = point.new(0,0)
 
botPos = nil        -- Bot's location, will be updated when onTick() is run

-- This function gets run once during setup.  It is the only time we can declare variables
-- and have them be defined as globals.
function main()
    botRadius = bot:getRad()
    pathTimerMax = 250
    pathTimer = pathTimerMax
    dirToGo = 0
    game = bf:getGameInfo()

    difficulty         = tonumber(arg[1]) or .5
    agression          = tonumber(arg[2]) or 0.5
    defense            = tonumber(arg[3]) or 0
    speed              = tonumber(arg[4]) or 1
    directionThreshold = tonumber(arg[5]) or .25

    gameType = game:getGameType()
   
    averageIndex = 1
    averageMax = 20

    averageArray = { }
    for i = 1, averageMax do
        averageArray[i] = false
    end

    myOrbitalDirection = 1
    myObjective = math.random(0, 10)
end
 

items = { }     -- Global variable, reusable container for bf:findAllObjects.  Reusing this table avoids costs of
                -- constructing and destructing it every time we call bf:findAllObjects().  Be sure to clear the
                -- table before reusing it!
function shieldSelf()
    table.clear(items)
    bot:findVisibleObjects(items, ObjType.Bullet, ObjType.Seeker, ObjType.Asteroid, ObjType.Mine, ObjType.Burst)

    local distToTurbo = botRadius * 2 + (1 - difficulty) * 100
    if (items ~= nil) then
        for i,bullet in ipairs(items) do
            local bulletPos = bullet:getPos()
            local bulletVel = bullet:getVel()
            local angleDiff = math.abs(angleDifference(point.angleTo(o, bulletVel), point.angleTo(bulletPos, botPos)))
            --logprint(angleDiff)

            local bulletFromTeam = false
            if (game:isTeamGame() == true) and (bullet:getTeamIndex() == bot:getTeamIndex()) then -- If bullet is from team
                bulletFromTeam = true                               -- bullet from team is true
            end

            if (bulletFromTeam == false) and (point.distanceTo(bulletPos, botPos) < distToTurbo + bullet:getRad() + point.distanceTo(o, bulletVel) * 50 and angleDiff < math.pi / 4) then
                bot:fireModule(Module.Turbo)
                return(true)
            end
        end
    end
end

function angleDifference(angleA, angleB)
    return (math.mod(math.mod(angleA - angleB, math.pi * 2) + math.pi * 3, math.pi * 2) - math.pi)
end

function fireAtObjects()
    table.clear(items)
    bot:findVisibleObjects(items, ObjType.Robot, ObjType.Turret, ObjType.Ship, ObjType.Asteroid,
        ObjType.ForceFieldProjector, ObjType.SpyBug, ObjType.Core)

    -- Cycle through list of potential items until we find one that we can attack
    for index, enemy in ipairs(items) do
        if(fireAtObject(enemy, Weapon.Phaser)) then
            break
        end
    end
end
 
 
-- Fires at the specified object with the specified weapon if the obj is a good target.
-- Does not fire if object is on the same team or if there is something in the way.
-- Returns whether it fired or not.
function fireAtObject(obj, weapon)
    local classId = obj:getObjType()

    if(classId == ObjType.Turret or classId == ObjType.ForceFieldProjector) then
        -- Ignore all same-team engineered objects...  even in single-team games
        if obj:getTeamIndex() == bot:getTeamIndex() then
            return false                                 -- Cancel fireAtObject function
        end

        if obj:getHealth() < .1  then                                             -- If item is essentially dead
            return false                                                          
        end
    end
   
   
    -- No shooting various team related objects
    if (obj:getTeamIndex() == bot:getTeamIndex() and game:isTeamGame()) or  -- Same team
        obj:getTeamIndex() == Team.Neutral then                            -- Neutral team
        if (classId == ObjType.Ship    or   classId == ObjType.Robot   or    -- Turrets and FFs handled above
            classId == ObjType.Core    or   classId == ObjType.SpyBug) then
            return false
        end
    end
   
    -- No shooting non-flag carriers in single player rabbit
    if gameType == GameType.Rabbit and not game:isTeamGame() and
        (classId == ObjType.Ship or classId == ObjType.Robot) and
        not bot:hasFlag() and not obj:hasFlag() then
        return false
    end

    -- We made it here!  We have a valid target..
    local angle = getFiringSolution(obj)
    if angle ~= nil and bot:hasWeapon(weapon) then
        bot:setAngle(angle + math.rad((math.random()-0.5)*20*(1-difficulty)))
        bot:fireWeapon(weapon)
        --logprint("bot:fireWeapon() called!");
        return(true)
    end
    --logprint("Firing solution not found.");
    return(false)
end
 
 
function getName()
     return("Nbot")
end


function shield()
    averageArray[averageIndex] = shieldSelf()
    averageIndex = math.mod(averageIndex,averageMax) + 1

    local shieldPercent = 0

    for i = 1, averageMax do
        if(averageArray[averageIndex]) then
            shieldPercent = shieldPercent + 1
        end
    end

    shieldPercent = shieldPercent / averageMax

    if(shieldPercent > directionThreshold) then
        myOrbitalDirection = -myOrbitalDirection
        for i = 1, averageMax do
            averageArray[i] = false
        end
    end
end
 
function orbitPoint(pt, dir, inputDist, inputStrictness)
    local distAway = botRadius * 7
    local strictness = 2
    local direction = 1

    if(dir ~= nil) then
        direction = dir
    end


    if(inputDist ~= nil) then
        distAway = inputDist
    end

    if(inputStrictness ~= nil) then
        strictness = inputStrictness
    end

    if(pt ~= nil) then
        local dist = point.distanceTo(pt, botPos)
        local deltaDistance = (dist - distAway) * strictness / distAway
        local sign = 1
        if(deltaDistance > 0) then
            sign = 1
        elseif(deltaDistance < 0) then
            sign = -1
        end

        local changeInAngle = (math.abs(deltaDistance)/(deltaDistance + sign)) * math.pi/2
        local angleToPoint = point.angleTo(pt, bot:getPos())
        dirToGo = angleToPoint + (math.pi/2 + changeInAngle)*direction
        --bot:setThrust(speed, dirToGo)
    end
end

function gotoPosition(pt)
    if pt ~= nil then
        gotoPositionWasNil = false
        if pathTimer < .01 then
            goalPt = bot:getWaypoint(pt)
            if(goalPt ~= nil) then
                dirToGo = point.angleTo(botPos, goalPt)
            end
        end
    end
end

function gotoAndOrbitPosition(pt)
    if pt ~= nil then
        gotoPositionWasNil = false
        if not bot:canSeePoint(pt) then
            gotoPositionWasNil = false
            gotoPosition(pt)
        else
            gotoPositionWasNil = false
            orbitPoint(pt, myOrbitalDirection, botRadius * 5, 2)
        end
    end
end
 
-- Returns true if it found an enemy to fight
function attackNearbyEnemies(target, agressionLevel)
    if target ~= nil then
        local targetPos = target:getPos()
     
        -- local dist    = point.distanceTo(botPos, targetPos)
        local myPow   = bot:getEnergy() + bot:getHealth()

        table.clear(items)
        bot:findVisibleObjects(items, ObjType.Ship, ObjType.Robot)
     
        local otherPow = target:getEnergy() + target:getHealth() * #items
     
        --advantage is between -1 and 1, -1 meaning an extreme disadvantage and 1 meaning an extreme advantage
        local advantage = (myPow - otherPow) / math.max(myPow, otherPow)
        if(advantage  / 2 + .5  >agressionLevel) then
            --orbitPoint(targetPos, myOrbitalDirection, botRadius * 9, 2)
            prevtarget = targetPos
            return(false)      -- was true
        else
        end
    end
    return(false)
end


-- Returns the objective for the bot, in the form of an object the bot can navigate towards.  This makes bots choose different defending locations.
-- If onTeam is true, will only return items on specified team.  If onTeam is false, will return items *not* on
-- specified team.  If called with fewer than three args, will ignore team altogether.
function getObjective(objType, team, onTeam)

    table.clear(items)
    bf:findAllObjects(items, objType)       -- Returns a list of all items of type objType in the game

    local itemsOnMyTeam = {}
    local currentIndex = 1

    for index, item in ipairs(items) do         -- Iterate through all found items
        local itemTeamIndex = item:getTeamIndex()

        if (objType == ObjType.Flag) and (gameType == GameType.Nexus) then
            if not item:isOnShip() then
                itemsOnMyTeam[currentIndex] = item
                currentIndex = currentIndex + 1
            end
        elseif (objType == ObjType.Flag) and
               ((gameType == GameType.HTF) or (gameType == GameType.Retrieve)) and
               item:isInCaptureZone() then
                --logprint(item:getCaptureZone():getTeamIndex());
            if item:getCaptureZone():getTeamIndex() ~= bot:getTeamIndex() then
                itemsOnMyTeam[currentIndex] = item
                currentIndex = currentIndex + 1
            end
        elseif (objType == ObjType.GoalZone) and (gameType == GameType.HTF or gameType == GameType.Retrieve) then
            if onTeam == nil or ((itemTeamIndex == team) == onTeam) then
                if not item:hasFlag() then
                    itemsOnMyTeam[currentIndex] = item
                    currentIndex = currentIndex + 1
                end
            end
        else
            if (itemTeamIndex == Team.Neutral ) and (objType ~= ObjType.GoalZone or gameType ~= GameType.ZC) then
                itemTeamIndex = team   -- anything Neutral is on our team (except zone control neutral goal zone)
            end
            if onTeam == nil or ((itemTeamIndex == team) == onTeam) then
                itemsOnMyTeam[currentIndex] = item
                currentIndex = currentIndex + 1
            end
        end
    end
    local listMax = 0
    --find max
    if itemsOnMyTeam[1] ~= nil then
        for index,item in ipairs(itemsOnMyTeam) do
            if(item ~= nil) then
                listMax = listMax + 1
            end
        end
        local targetNum = math.mod(myObjective, listMax) + 1
        return(itemsOnMyTeam[targetNum])
    else
        return(nil)
    end

--  local closestitem = 0 --itemsOnMyTeam[1]
--  local cur = 1
--  local closestdist = 99999999
--  while itemsOnMyTeam[cur] ~= nil do
--      local item1 = itemsOnMyTeam[cur]
--      if item1 ~= nil then
--          local loc = item1.getPos()
--          if loc ~= nil then
--              local dist = point.distanceTo(botPos, loc)
--              if dist < closestdist then
--                  closestdist = dist
--                  closesetitem = item1
--              end
--          end
--      end
--      cur=cur+1
--  end
--  return(closestitem)
end


function doObjective(closestEnemy)
    gotoPositionWasNil = true
    if(gameType == GameType.Bitmatch) then
        --Nothing to do here.          
    elseif(gameType == GameType.Nexus) then
        -- Grab any flags that are found, and go to nexus when it opens
        local otherFlag = getObjective(ObjType.Flag)            -- Find any flags
        if otherFlag ~= nil then                                -- If there is a flag avalible
            gotoPosition(otherFlag:getPos())
        end

        --  If bot has more than 4 flags and the nexus is open or we're within 10 seconds of opening
        if bot:getFlagCount() > 4 and (game:isNexusOpen() or game:getNexusTimeLeft() < 10) then  --  Need to know if nexus is open
            local nexusOrFlag = getObjective(ObjType.Nexus)  -- unimplemented push function error.
            if nexusOrFlag ~= nil then
                gotoPosition(nexusOrFlag:getPos())
            end
        end
    elseif(gameType == GameType.Rabbit) then
        --Grab a flag, or go after the flag.            
        if not bot:hasFlag() then
            local otherFlag = getObjective(ObjType.Flag, bot:getTeamIndex(), true)       -- Find flags on our team
            gotoPosition(otherFlag:getPos())
        end
    elseif(gameType == GameType.HTF or gameType == GameType.Retrieve) then
        -- Grab the flag and put it into goal zones
        -- Robot keeps trying to pick up the flags that is already in the goalZones
        if bot:hasFlag() then                                                           -- If the bot has the flag
            local otherFlag = getObjective(ObjType.GoalZone, bot:getTeamIndex(), true)   -- Find an avalible GoalZone on our team
            if otherFlag ~= nil then                                                    -- If there is an avalible GoalZone
                gotoPosition(otherFlag:getPos())                                        -- Go to it
            end
        else                                                                            -- If the bot doesn't have the flag
            local otherFlag = getObjective(ObjType.Flag, bot:getTeamIndex(), true)       -- Find flags on our team
            if otherFlag ~= nil then                                                    -- If there is a flag avalible
                gotoPosition(otherFlag:getPos())                                        -- Go to it
            end
        end
    elseif(gameType == GameType.CTF) then
        --defend the flag
        local myFlag    = getObjective(ObjType.Flag, bot:getTeamIndex(), true)    -- Find flags on our team
        local otherFlag = getObjective(ObjType.Flag, bot:getTeamIndex(), false)   -- Find flags not on our team

        if(defense < .5) then                                                    -- If bot doesn't defend allot (default is 0)
            if bot:hasFlag() then                                                -- If the bot has a flag
                if not myFlag:isOnShip() then                                    -- If my flag is not on a ship
                    gotoPosition(myFlag:getPos())                                -- Go to position of my flag
                else
                    gotoAndOrbitPosition(myFlag:getPos())                        -- Otherwise, go and orbit the flag on enemy
                end
                --gotoPosition(myFlag:getPos())
            else                                                                 -- If the bot doesn't have the flag
                local retrievingFlag = false
                if myFlag ~= nil then
                    if not myFlag:isInInitLoc() and not myFlag:isOnShip() and                     -- If my flag is not in its initial location and my flag is not on a ship
                            point.distSquared(myFlag:getPos(), bot:getPos()) <= 2000 * 2000 then  -- .. and we're within some sane range of the flag
                        gotoPosition(myFlag:getPos())                                             -- Go to and return my flag
                        retrievingFlag = true
                    end
                end

                if otherFlag ~= nil then                                         -- If there is an enemy flag
                    if not retrievingFlag then                                   -- .. and this bot isn't already retrieving a flag
                        if myFlag:isOnShip() then                                -- If my flag is on a ship
                            gotoPosition(myFlag:getPos())                        -- Go to position of my flag
                        elseif not otherFlag:isOnShip() then                     -- If enemy flag is not on a ship
                            gotoPosition(otherFlag:getPos())                     -- Go to that flag
                        else
                            gotoAndOrbitPosition(otherFlag:getPos())             -- Go and orbit our team flag's carrier
                        end
                    end
                end
            end
        else
            if bot:hasFlag() then                                                -- If the bot has a flag and has more than .5 defense
                gotoPosition(myFlag:getPos())                                    -- Go to team flag
            elseif myFlag:isInInitLoc() then                                     -- If the bot doesn't have a flag and the team flag is in intial location
                gotoAndOrbitPosition(myFlag:getPos())                            -- Go and orbit team flag
            else
                if myFlag:isOnShip() then                                        -- If team flag is on a ship
                    gotoAndOrbitPosition(myFlag:getPos())                        -- Go and orbit team flag
                else
                    gotoPosition(myFlag:getPos())                                -- If team flag is not on a ship, go to team flag
                end
            end
        end
    elseif(gameType == GameType.Soccer) then
        --grab soccer and put into enemy goal
        -- How do we know if we are holding soccer ? (not supported when cannot pickup soccer (016)
        if bot:getMountedItems(ObjType.SoccerBallItem)[1] ~= nil then
            local otherFlag = getObjective(ObjType.GoalZone, bot:getTeamIndex(), false)   -- Find GoalZones not on our team
            if otherFlag ~= nil then
                gotoPosition(otherFlag:getPos())
            end          
        else
            local otherFlag = getObjective(ObjType.SoccerBallItem)                        -- Find SoccerBall
            if otherFlag ~= nil then
                gotoPosition(otherFlag:getPos())
            end
        end
    elseif(gameType == GameType.ZC) then
        -- Grab flag, then go after zones that is not ours.
        if not bot:hasFlag() then
            local otherFlag = getObjective(ObjType.Flag, bot:getTeamIndex(), true)           -- Find flags on our team
            if otherFlag ~= nil then
                if otherFlag:isOnShip() then
                    gotoAndOrbitPosition(otherFlag:getPos())
                else
                    gotoPosition(otherFlag:getPos())
                end
            end
        else
            local otherFlag = getObjective(ObjType.GoalZone, bot:getTeamIndex(), false)      -- Find GoalZones on our team
            if otherFlag then
                gotoPosition(otherFlag:getPos())
            end
        end

    elseif(gameType == GameType.Core) then
        local obj = getObjective(ObjType.Core, bot:getTeamIndex(), false)                    -- Find enemy Core
        if obj ~= nil then
            gotoAndOrbitPosition(obj:getPos())
        end
    end

    -- If we have no where to go, go to nearest enemy.
    if gotoPositionWasNil then
        if(closestEnemy ~= nil) then
            prevtarget = closestEnemy:getPos()
        end
        if(prevtarget ~= nil) then
            gotoAndOrbitPosition(prevtarget)
        end
    end
end
 

function goInDirection()
    bot:setThrust(speed, dirToGo)
end


-- This function gets called every game tick; deltaTime is the time that has elapsed since it was last called
function onTick(deltaTime)
    botPos = bot:getPos()
    pathTimer = pathTimer - deltaTime
    assert(bot:getPos() ~= nil)

    local closestEnemy = bot:findClosestEnemy()
   
    -- attackNearbyEnemies returns true if there is an enemy to fight, false if the bot can do something else
    attackNearbyEnemies(closestEnemy, 1 - agression)
    doObjective(closestEnemy)   -- Set bot's objective

    goInDirection()             -- Move the ship
    fireAtObjects()             -- Fire weapons
    shield()                    -- Apply shield

    if(pathTimer < 0) then
        pathTimer = pathTimerMax + math.random(0, pathTimerMax)
    end
end