Programming robots
Starting with release 011, server admins will be able to program their own robots in addition to designing their own levels. Robots will be coded in Lua, and will will have a number of special functions available to them.
For examples of fully functional bots, see the Robot Gallery.
Robot coding is still fluid, and everything is subject to change, but here is a list of commands being implemented for the next alpha release (see forums for the URL):
How to read the function notation:
- returnType functionName(argType1, argType2)
Return types:
Unlike most languages, Lua does not differentiate between integers and floating point numbers. Instead, everything is simply a number. In fact, Lua often will let you interchange strings and numbers, converting dynamically on an as-needed basis. For the most part, relying on this automatic type coercion is bad practice and will get you into trouble.
Points to remember in Lua:
- Lua arrays are 1-based: the first index is 1 rather than 0 as is the case in most programming languages
- Lua is case sensitive. That means that the variable foo is different than Foo and FOO
- Any variable not declared as local will be global. Accessing global varibales is slower than local ones, and being sloppy about variable scope can lead to unexpected problems. Use the local keyword whenever appropriate.
We will follow the Lua formatting conventions used in the Programming in Lua book.
Contents
- 1 Navigation, configuration, and combat
- 2 Logging & Sending Messages
- 3 GameItems
- 4 Weapon Information
- 5 WeaponType constants
- 6 Module Information
- 7 ModuleType constants
- 8 Loadouts
- 9 GameInfo
- 10 GameType constants
- 11 ScoringEvent constants
- 12 TeamInfo
- 13 Timers
- 14 Points
- 15 ItemTypes
- 16 Things that are not really items
There are two general methods for getting the current game time:
- number getTime()
- number getCPUTime()
These functions will return slightly different results. Generally speaking, if you are timing things in the "real world", i.e. you want to pause for exactly 1 second, use getCPUTime(). If you want to use a timer to make the robot move smoothly around a mathematically derived course, use getTime(), as that is what is used internally when calculating ship movement.
Example:
lastTime = thisTime thisTime = bot:getCPUTime() local delta = thisTime - lastTime logprint(delta .. " ms have elapsed in the real world since last frame.")
Example:
-- Will fly robot in a circular orbit around an item. -- Assume we've already found one and stored it in "item" local botLoc = bot:getLoc() local itemLoc = item:getLoc() local dist = botLoc:distanceTo(itemLoc) -- For this example, we'll define orbitRadius as a local variable. In a -- real robot, this might be better declared a global variable in the header. local orbitRadius = 300 -- Here we use the getTime() function to make the motion look smooth. -- If we just advanced orbitAng by a fixed amount each frame, it would -- appear jerky, as each frame represents a slightly different length in time. -- .0015 determined experimentally orbitAng = orbitAng + .0015 * bot:getTime() -- orbitAng is a global variable local dest = nil if(dist <= orbitRadius * 1.1) then dest = itemLoc dest:setxy(itemLoc:x() + orbitRadius * math.cos(orbitAng), itemLoc:y() + orbitRadius * math.sin(orbitAng) ) else dest = itemLoc end bot:setThrustToPt(dest) -- Travel towards calculated point
- Point getZoneCenter(x, y)
- Point getGatewayFromZoneToZone(a, b)
- number getZoneCount()
- number getCurrentZone()
- number getAngle()
- Point getLoc()
- void setAngle(ang)
Example:
-- This code in getMove() local items = bot:findItems(AsteroidType) -- Get list of all asteroids local asteroid = findClosest(items) -- Finds closest if(asteroid == nil) then -- Make sure we found one return end local loc = bot:getLoc() -- Our location (Point object) local ang = loc:angleTo( asteroid:getLoc() ) bot:setAngle(ang) -- Aim ship toward asteroid bot:fire()
- void setAnglePt(Point)
- number getAnglePt(Point)
- boolean hasLosPt(Point)
- number getFiringSolution(item)
Example:
local loc = bot:getLoc() -- Current location local items = bot:findGlobalItems(SoccerBallItemType) -- Find soccerball -- Loop through all returned items and find the closest -- This code here for learning purposes... could be replaced with -- much simpler single line: -- local ball = findClosest(items) local minDist = 99999999 local ball = nil for sb in values(items) do local l = sb:getLoc() -- Ball's location -- Distance from ship to ball (use distSquared because -- it is easy to compute and good for comparison) local d = loc:distSquared(l) if(d < minDist) then ball = sb minDist = d end end -- End replaceable code section -- Check to make sure we found a soccer ball ... could be out of view if(ball == nil) then return end local ang = bot:getFiringSolution(sb) -- Get shoot angle to hit moving ball -- Always check for nil: could happen if ball were behind a wall or -- friendly ship. Running setAngle(nil) will do nothing. if(ang == nil) then return end bot:setAngle(ang) -- Ready... aim... bot:fire() -- ...fire!
- number getTeamIndx()
- boolean hasFlag()
- WeaponType getActiveWeapon()
Example:
weap = bot:getActiveWeapon() -- Get info about our currently active weapon weapInfo = WeaponInfo(weap) -- Get information about it logprint(weapInfo:getName() .. " has a range of " .. weapInfo:getRange())
Modules are automatically disabled at the end of each game cycle. Therefore, if you want to keep a module on for a sustained period of time, you must activate it each time getMove() is called. To activate a module, use one of the activateModule() commands.
- void activateModule(ModuleType)
Example:
-- Note that this line will do nothing if we don't have shields bot:activateModule(ModuleShield) -- Shields up!
- void activateModuleIndex(indx)
Example:
-- Must specify an index, currently either 1 or 2 bot:activateModuleIndex(1) -- Activate first module, whatever it is
- void setReqLoadout(Loadout)
- Loadout getCurrLoadout()
Example:
loadout = bot:getCurrLoadout() -- Retrieve current bot configuration weapType1 = loadout:getWeapon(1) -- Get the first weapon weapType2 = loadout:getWeapon(2) -- Get the second weapon weapType3 = loadout:getWeapon(3) -- Get the third weapon -- Check to see if the first weapon is a phaser if (weapType1 == WeaponPhaser) then logprint("First weapon is a phaser!") end -- Print a list of our three current weapons logprint("My three weapons are: " .. WeaponInfo(weapType1):getName() .. ", " .. WeaponInfo(weapType2):getName() .. ", and " .. WeaponInfo(weapType3):getName())
- Loadout getReqLoadout()
Navigation
- [Item list] findItems(ObjectType, ...)
- [Item list] findGlobalItems(ObjectType, ...)
Example:
-- Find all resource items in the normal viewport local items = bot:findItems(ResourceItemType) -- You can specify multiple item types if you want -- Get a list of all TestItems or Asteroids that are visible anywhere -- (note that items that are out-of-scope will be omitted) local globItems = bot:findGlobalItems(TestItemType, AsteroidType) -- Now iterate over the list ... there are many examples in the documentation! for item in values(globItems) do -- Do something with item ... perhaps figure out which is closest, -- which is heading towards us, etc. end
- Point getWaypoint(Point)
Ship control
- void setThrust(vel, angle)
- void setThrustPt(vel, Point)
- void setThrustToPt(Point)
- void fire()
- void setWeaponIndex(index)
- void setWeapon(WeaponType)
- boolean hasWeapon (WeaponType)
Example:
if(bot:hasWeapon(WeaponMine)) then bot:setWeapon(WeaponMine) -- Make mine layer active if it's -- part of our current loadout bot:fire() -- Lay a mine end
Logging & Sending Messages
- void globalMsg(msg)
- void teamMsg(msg)
- void logprint(msg)
GameItems
The Lua object structure generally follows that used by Bitfighter. The GameItems group conisists of Ships, Robots, RepairItems, Asteroids, ResourceItems, SoccerBallItems, Flags, NexusFlags, and TestItems. These all share similar properties, and have similar methods. All of these implement the getLoc(), getVel(), and getRad() methods for querying location, velocity, and radius respectively. Some items have additional methods that apply only to them. See below for details on these additional methods.
Ships & Robots
- Point getLoc()
- number getRad()
- Point getVel()
- number getTeamIndx()
- boolean isModActive(ModuleType)
TestItems, ResourceItems, and SoccerBallItems
These items all have the same methods and behave in a very similar manner.
Category: GameItem
- Point getLoc()
- number getRad()
- Point getVel()
Example:
local items = bot:findItems(TestItemType) -- Find all TestItems in view -- Now cycle through all returned items and find the fastest one local maxSpeed = -1 local fastest = nil for indx, ti in ipairs(items) do -- getVel() returns a point representing the x and y components of -- the velocity. Need to use len() to get actual speed. Or, in this -- case, since actual speed isn't important, we can use lenSquared() -- which is less computationally intensive, and will yield perfectly -- good results for comparison purposes (such as finding the fastest). spd = ti:getVel():lenSquared() if(spd > maxSpeed) then fastest = ti maxSpeed = spd end end -- Better check to make sure fastest isn't nil before using it! -- Could be nil if there are no TestItems in view if(fastest != nil) then -- Do something end
Asteroids
Category: GameItem
- Point getLoc()
- number getRad()
- Point getVel()
- number getSize()
- number getSizeCount()
Example:
local asteroids = bot:findItems(AsteroidType) local target = asteroids[1]:getLoc() -- Pick first asteroid
RepairItems
Category: GameItem
- Point getLoc()
- number getRad()
- Point getVel()
- boolean isVis()
Example:
local ri = bot:findItems(RepairItemType) -- Get list of all known RepairItems local pos = ri[1]:getLoc() -- Get position of first
Flags
Category: GameItem
- Point getLoc()
- number getRad()
- Point getVel()
- number getTeamIndx()
- boolean isInInitLoc()
- boolean inCaptureZone()
- boolean isOnShip()
NexusFlags
Category: GameItem
Note: Only appears in Nexus games.
- Point getLoc()
- number getRad()
- Point getVel()
Turrets and ForceFieldProjectors
Category: GameItem
- Point getLoc()
- number getRad()
- Point getVel()
- number getTeamIndx()
- number getHealth()
- boolean isActive()
Bullets, Mines, and SpyBugs
Category: GameItem
There are three GameItem types that have to do with bullets, mines, and spybugs: BulletType, MineType, and SpyBugType respectively. They all have the same methods, and can be used somewhat interchangeably, as shown in the example below. However, as shown, they can also be found independently.
BulletType encompases Phaseser, Bounce, Triple, Burst, and Turret shots, the properties of which can be retrieved with the WeaponInfo object.
- Point getLoc()
- number getRad()
- Point getVel()
- WeaponType getWeapon()
Example:
local bullets = bot:findItems(BulletType, MineType, SpyBugType) <some code> -- Code here to select one of the found items local weap = selectedBullet:getWeapon() local weapInfo = WeaponInfo(weap) logprint("Selected " .. weapInfo:getName())
Weapon Information
All WeaponInfo data will remain constant throughout the game. Therefore, if you need some information about a weapon (mines, say, for a minesweeping bot), it might make sense to retrieve it in the bot's header and store it in a global variable rather than instantiating a new WeaponInfo object during every loop of the robot's getMove() method.
Example:
local weap = bot:getActiveWeapon() -- Get bot's currently active weapon logprint(weap:getName() .. " shoots with a speed of " .. weap:getProjVel())
-- In header... we'd better not declare these variables as local! turretInfo = WeaponInfo(WeaponTurret) -- Get info about those infernal turrets turretRange = turretInfo:getRange() -- Stay this far from turrets to be safe
- string getName()
- WeaponType getID()
- number getRange()
- number getFireDelay()
- number getMinEnergy()
- number getEnergyDrain()
- number getProjVel()
- number getProjLife()
- number getDamage()
- number getDamageSelf()
- boolean getCanDamageTeammate()
WeaponType constants
WeaponPhaser | WeaponBounce |
WeaponTriple | WeaponBurst |
WeaponMine | WeaponSpyBug |
WeaponTurret |
Module Information
Example:
local mod = ModuleInfo(ModuleBoost) logprint("This is a lame example!")
- string getName()
- ModuleType getID()
ModuleType constants
ModuleShield | ModuleBoost |
ModuleSensor | ModuleRepair |
ModuleCloak | ModuleEngineer (maybe someday) |
Loadouts
- void setWeapon(index, WeaponType)
- void setModule(index, ModuleType)
- WeaponType getWeapon(index)
- ModuleType getModule(index)
Example:
-- Get a new loadout (comes pre-filled with default values) local loadout = Loadout() -- Configure the loadout to suit our needs loadout:setWeapon(1, WeaponPhaser) loadout:setWeapon(2, WeaponBurst) loadout:setWeapon(3, WeaponMine) loadout:setModule(1, ModuleShield) loadout:setModule(2, ModuleCloak) -- Set the loadout, will become active when bot hits loadout zone -- or spawns, depending on game bot:setReqLoadout(loadout) if(loadout:getWeapon(1) == WeaponPhaser) then logprintf("This line always gets printed!") end
- boolean isValid()
- boolean equals(Loadout)
GameInfo
You can get information about the current game with the GameInfo object. You only need get this object once, then you can use it as often as you like. It will always reflect the latest data.
Example:
game = GameInfo() -- Create the GameInfo object gameType = game:getGameType() if(gameType == SoccerGame) then logprint("This bot is not very good at soccer!") else gameTypeName = game:getGameTypeName() logprintf("I just love playing " .. gameTypeName .. "!") end
Example:
-- Create the GameInfo object in header (don't declare it local!!) game = GameInfo() ... -- Even though we only create our GameInfo once, it is always current ... -- Later, in getMove(): local remTime = game:getGameTimeReamaining() local totTime = game:getGameTimeTotal() local percent = (totTime - remTime) / remTime logprint("Game is " .. percent .. "% over)
- GameType getGameType()
- string getGameTypeName()
- number getFlagCount()
- number getWinningScore()
- number getGameTimeTotal()
- number getGameTimeRemaining()
- number getLeadingScore()
- number getLeadingTeam()
- number getTeamCount()
Example:
-- Create the GameInfo object in header (don't declare it local!!) game = GameInfo() ... -- Then later ... ... local leadingTeam = game:getLeadingTeam() local team = TeamInfo(leadingTeam) bot:teamMsg("Hurry up! Team " .. team:getName() .. " is winning!" )
- string getLevelName()
- number getGridSize()
- boolean getIsTeamGame()
- void getEventScore(ScoringEvent)
GameType constants
BitmatchGame | CTFGame |
HTFGame | NexusGame |
RabbitGame | RetrieveGame |
SoccerGame | ZoneControlGame |
ScoringEvent constants
KillEnemy | KillSelf |
KillTeammate | KillEnemyTurret |
KillOwnTurret | CaptureFlag |
CaptureZone | UncaptureZone |
HoldFlagInZone | RemoveFlagFromEnemyZone |
RabbitHoldsFlag | RabbitKilled |
RabbitKills | ReturnFlagsToNexus |
ReturnFlagToZone | LostFlag |
ReturnTeamFlag | ScoreGoalEnemyTeam |
ScoreGoalHostileTeam | ScoreGoalOwnTeam |
TeamInfo
- string getName()
- number getIndex()
- number getPlayerCount()
- number getScore()
Example:
local gameInfo = GameInfo() local teams = gameInfo:getTeamCount() -- Note that while the number of teams will not change throughout the game, -- the number of players on each team might. Also, the robot may be initialized -- before the players have been added, so if you run this code in the bot header, -- it may report some teams having 0 players. for i = 1, teams do -- First team has an index of 1 team = TeamInfo(i) logprint("Team " .. i .. " is called " .. team:getName() .. " and has " .. team:getPlayerCount() .. " players") end
Timers
Timer is a utility class that makes it easier to manage timing periodic or delayed events.
- void reset()
- number getTime()
- number getFraction()
- void setPeriod(time)
- boolean update(time)
Example:
-- In header: timer = Timer(1000) -- Set timer that will go off every second ... -- Then later, in getMove() ... if(timer:update(bot:getTime())) then -- Always update with bot:getTime() logprint("1 sec has elapsed!") -- Msg will get printed every second timer:reset() -- Start timer over end
Points
Point is a utility class to make handling coordinates or vectors simpler. You can create your own point objects using the constructor, or you can get them as return values from many functions.
- Point Point(x, y)
- number x()
- number y()
- void setxy(x, y)
- void setx(x)
- void sety(y)
- number len()
- number lenSquared()
- boolean equals(Point)
- void normalize(length)
- number distanceTo(Point)
- number distSquared(Point)
- number angleTo(Point)
Example:
local point = Point(0, 0) -- Create a new point if(point:equals(Point(1, 1)) then logprint("This never gets printed") end if(point:distanceTo(Point(1, 1) < 5) then logprint("This always gets printed") end -- Here we'll use a point to represent a velocity, which has x & y components local xspeed = 10 local yspeed = 15 local velocity = Point(xspeed, yspeed) logprint("Speed = " .. velocity:len())
ItemTypes
ShipType
RobotType
BulletType
MineType
SpyBugType
NexusFlagType
RobotType
TeleportType (not yet fully implemented)
SpeedZoneType (not yet fully implemented)
AsteroidType
TurretType
ForceFieldProjectorType
TestItemType
FlagType
SoccerBallItemType
ResourceItemType
Things that are not really items
Polygonal-like
BotNavMeshZoneType
NexusType
GoalZoneType
LoadoutZoneType
Wall-like
BarrierType