Difference between revisions of "Programming robots"
(Removed now-outdated 011 references) |
(missing )) |
||
(47 intermediate revisions by 7 users not shown) | |||
Line 1: | Line 1: | ||
− | + | TODO: FlagItem:getCaptureZone & FlagItem:getShip functions (verify w/ sam); make sure all items have getLoc, getRad, getVel, getTeamIndx, and isItem; hasFlag(); engineer-deploy-object (??), ship/bot:getMountedItems(); document libopt (or at least or inclusion of such with summary and pointer to docs) -- need to do require("getopt") to activate it. mention inspect.lua under debugging section. | |
+ | |||
+ | Server admins can program their own robots in addition to designing their own levels. Robots are coded in Lua, and have a number of special functions available to them. | ||
For examples of fully functional bots, see the [[Robot_gallery|Robot Gallery]]. | For examples of fully functional bots, see the [[Robot_gallery|Robot Gallery]]. | ||
− | Robot coding is still fluid, and | + | Robot coding is still fluid, and the information here is subject to change. You might want to read about the [[Script structure|general structure of Bitfighter scripts]] before proceeding. |
How to read the function notation: | How to read the function notation: | ||
− | returnType | + | {{funcdoc|returnType|functionName|argType1, argType2}} - Description of function<br> |
Return types: | 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. | + | 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. However, relying on this automatic type coercion is bad practice and can get you into trouble. |
Points to remember in Lua: | Points to remember in Lua: | ||
<ul> | <ul> | ||
− | <li>Lua arrays are 1-based: the first index is 1 rather than 0 as is the case in | + | <li>Lua arrays are 1-based: the first index is 1 rather than 0 as is the case in programming languages such as C++ and Java</li> |
<li>Lua is case sensitive. That means that the variable foo is different than Foo and FOO</li> | <li>Lua is case sensitive. That means that the variable foo is different than Foo and FOO</li> | ||
− | <li>Any variable not declared as local will be global. Accessing global | + | <li>Any variable not declared as local will be global. Accessing global variables is slower than local ones, and being sloppy about variable scope can lead to unexpected problems. Use the local keyword whenever appropriate.</li> |
</ul> | </ul> | ||
We will follow the Lua formatting conventions used in the Programming in Lua book. | We will follow the Lua formatting conventions used in the Programming in Lua book. | ||
+ | |||
+ | == Events == | ||
+ | Lua robots are designed to respond to many in-game events using event handlers. To respond to an event your bot must have an appropriately named function (described below), and must subscribe to any events it is interested in. You can also unsubscribe to an event at any time to stop receiving event notifications. | ||
+ | |||
+ | ==== Currently defined events ==== | ||
+ | <table border=1px cellpadding=3px> | ||
+ | <tr><th>Event ID</th><th>Associated function</th><th>Description</th></tr> | ||
+ | <tr><td>TickEvent</td><td>onTick(timeSinceLastTickInMs)</td><td>Called every frame of the game. Unlike other events, robots are subscribed to this event by default.</td> | ||
+ | <tr><td>ShipSpawnedEvent</td><td>onShipSpawned(ship)</td><td>Called when any ship or robot spawns (not including your robot). Spawning ship is passed as the first argument.</td></tr> | ||
+ | <tr><td>ShipKilledEvent</td><td>onShipKilled(ship)</td><td>Called when any ship or robot is killed (not including your robot). Dead ship is passed as the first argument.</td></tr> | ||
+ | <tr><td>MsgReceivedEvent</td><td>onMsgReceived(message, senderPlayerInfo, global)</td><td>Called when a player or robot sends a message. Message itself is first argument, a PlayerInfo for the sender is the second argument, and the third argument is a boolean that will be true if the message was global, false if sent to teammates only. Note that senderPlayerInfo can be nil if message is sent by a Controller script.</td></tr> | ||
+ | <tr><td>PlayerJoinedEvent</td><td>onPlayerJoined(playerInfo)</td><td>Called when a player or robot joins the game (not when they spawn). Note that when hosting a game locally, local player does not necessarily trigger this event due to the uncertain order of creation of game objects. First argument is playerInfo for the joining player.</td></tr> | ||
+ | <tr><td>PlayerLeftEvent</td><td>onPlayerLeft(playerInfo)</td><td>Called when a player or robot leaves the game. Note that event may not be fired when robots or local player leaves game due to server shutting down. First argument is playerInfo for the leaving player.</td></tr> | ||
+ | <tr><td>NexusOpenedEvent</td><td>onNexusOpened()</td><td>Called when Nexus opens in a Nexus game. Never fires in other game types.</td></tr> | ||
+ | <tr><td>NexusClosedEvent</td><td>onNexusClosed()</td><td>Called when Nexus closes in a Nexus game. Never fires in other game types.</td></tr> | ||
+ | </table> | ||
+ | |||
+ | Example: | ||
+ | <source lang="lua"> | ||
+ | function main() | ||
+ | ... | ||
+ | subscribe(ShipSpawnedEvent) -- Subscribe so onShipSpawned() will be called | ||
+ | ... | ||
+ | end | ||
+ | |||
+ | -- Since we are subscribed to the ShipSpawned event, the onShipSpawned() function | ||
+ | -- will be called whever a ship or robot spawns. | ||
+ | function onShipSpawned(ship) | ||
+ | logprint("Ship spawned: ".. tostring(ship:getLoc())) | ||
+ | angle = bot:getLoc():angleTo(ship:getLoc()) | ||
+ | logprint("angle to spawned ship = "..angle) | ||
+ | end | ||
+ | </source> | ||
+ | |||
+ | |||
+ | Example: | ||
+ | <source lang="lua"> | ||
+ | -- Keep track of all players/robots in the game. This may be more efficient than | ||
+ | -- reading the list every time it is needed if it is used frequently. | ||
+ | |||
+ | function main() | ||
+ | subscribe(PlayerJoinedEvent) | ||
+ | subscribe(PlayerLeftEvent) | ||
+ | players = GameInfo():getPlayers() -- Vars defined in main() will be global | ||
+ | end | ||
+ | |||
+ | |||
+ | function onPlayerJoined() | ||
+ | players = GameInfo():getPlayers() -- Player joined: get updated player list | ||
+ | end | ||
+ | |||
+ | |||
+ | function onPlayerLeft() | ||
+ | players = GameInfo():getPlayers() -- Player left: get updated player list | ||
+ | end | ||
+ | |||
+ | |||
+ | </source> | ||
+ | |||
+ | |||
+ | You can also use timers and events to create a basic sleep function: | ||
+ | <source lang="lua"> | ||
+ | function sleep(time) -- Sleep time in ms | ||
+ | unsubscribe(TickEvent) -- Stop getting tick events until... | ||
+ | Timer:scheduleOnce("subscribe(TickEvent)", time) -- ...we resubscribe! | ||
+ | end | ||
+ | </source> | ||
== Navigation, configuration, and combat == | == Navigation, configuration, and combat == | ||
− | There are two general methods for getting the current game time:<br> | + | There are two general methods for getting the current game time:<br> |
− | number | + | {{funcdoc|number|getTime|}} -Returns time for this frame, in ms.<br> |
− | number | + | {{funcdoc|number|getCPUTime|}} - Returns number representing the CPU time in ms. Can be used to time events<br> |
+ | |||
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. | 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. | ||
Line 75: | Line 145: | ||
− | + | {{funcdoc|Point|getZoneCenter|x, y}} - Return point representing center of zone containing point x,y<br> | |
− | + | {{funcdoc|Point|getGatewayFromZoneToZone|a, b}} - Return point representing fastest way from zone a to zone b. If zones a & b are not neighbors, returns nil<br> | |
− | number | + | {{funcdoc|number|getZoneCount|}} - Return number of zones<br> |
− | number | + | {{funcdoc|number|getCurrentZone|}} - Return current zone that robot is in<br> |
<br> | <br> | ||
− | number | + | {{funcdoc|number|getAngle|}} - Return angle robot is currently facing<br> |
− | Point | + | {{funcdoc|Point|getLoc|}} - Return point representing location of robot<br> |
− | + | {{funcdoc|number|getTeamIndx|}} - Return index of robot's team<br> | |
+ | {{funcdoc|void|setAngle|ang}} - Point robot at angle<br> | ||
+ | {{funcdoc|void|setAngle|pt}} - Point robot toward point pt<br> | ||
Example: | Example: | ||
<source lang="lua"> | <source lang="lua"> | ||
− | -- This code in | + | -- This code in onTick() |
local items = bot:findItems(AsteroidType) -- Get list of all asteroids | local items = bot:findItems(AsteroidType) -- Get list of all asteroids | ||
Line 103: | Line 175: | ||
− | void | + | {{funcdoc|void|setAnglePt|Point}} - Point robot towards Point<br> |
− | number | + | {{funcdoc|number|getAnglePt|Point}} - Compute angle to Point<br> |
− | boolean | + | {{funcdoc|boolean|hasLosPt|Point}} - Return whether or not robot can see Point<br> |
− | number | + | {{funcdoc|boolean|hasLosPt|x, y}} - Return whether or not robot can see point x,y<br> |
+ | {{funcdoc|number|getFiringSolution|item}} - Calculate angle at which robot needs to fire to hit moving or stationary object. Will return nil if the item is out of range, behind an obstacle, or firing will hit a friendly ship. Always check the return value for nil!<br> | ||
Example: | Example: | ||
<source lang="lua"> | <source lang="lua"> | ||
− | local loc = bot:getLoc() -- | + | local loc = bot:getLoc() -- Current location |
local items = bot:findGlobalItems(SoccerBallItemType) -- Find soccerball | local items = bot:findGlobalItems(SoccerBallItemType) -- Find soccerball | ||
Line 156: | Line 229: | ||
</source> | </source> | ||
<br> | <br> | ||
− | number | + | {{funcdoc|number|getTeamIndx|}} - Return index of team this robot is on<br> |
− | boolean | + | {{funcdoc|boolean|hasFlag|}} - Return whether or not robot currently has the flag<br> |
<br> | <br> | ||
<br> | <br> | ||
− | WeaponType | + | {{funcdoc|WeaponType|getActiveWeapon|}} - Return currently selected weapon<br> |
Example: | Example: | ||
<source lang="lua"> | <source lang="lua"> | ||
Line 169: | Line 242: | ||
− | 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 | + | 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 onTick() is called. To activate a module, use one of the activateModule() commands. |
− | void | + | {{funcdoc|void|activateModule|ModuleType}} - Activate module, specified by ModuleType. If specified module is not included in the current loadout, this command will have no effect. |
Example: | Example: | ||
<source lang="lua"> | <source lang="lua"> | ||
Line 178: | Line 251: | ||
</source> | </source> | ||
− | void | + | {{funcdoc|void|activateModuleIndex|indx}} - Activate module, specified by its index (1 or 2)<br> |
Example: | Example: | ||
<source lang="lua"> | <source lang="lua"> | ||
Line 185: | Line 258: | ||
</source> | </source> | ||
− | void | + | {{funcdoc|void|setReqLoadout|Loadout}} - Set the requested loadout to Loadout<br> |
− | Loadout | + | {{funcdoc|Loadout|getCurrLoadout|}} - Returns current loadout<br> |
Example: | Example: | ||
<source lang="lua"> | <source lang="lua"> | ||
Line 207: | Line 280: | ||
− | Loadout | + | {{funcdoc|Loadout|getReqLoadout|}} - Returns requested loadout<br> |
Line 213: | Line 286: | ||
<u>Navigation</u><br> | <u>Navigation</u><br> | ||
<br> | <br> | ||
− | [Item list] | + | {{funcdoc|[Item list]|findItems|ObjectType, ...}} - Return a list of items of the specified type or types that are within the robot's viewable area<br> |
− | [Item list] | + | {{funcdoc|[Item list]|findGlobalItems|ObjectType, ...}} - Return a list of items of the specified type or types that are anywhere in the game. Note that items that would ordinarily be out of scope will <i>not</i> be included in this list!<br> |
Example: | Example: | ||
Line 235: | Line 308: | ||
− | + | {{funcdoc|Point|getWaypoint|Point}}<br> | |
+ | {{funcdoc|Point|getWaypoint|x, y}}<br> | ||
<br> | <br> | ||
<br> | <br> | ||
<u>Ship control</u><br> | <u>Ship control</u><br> | ||
− | void | + | {{funcdoc|void|setThrust|vel, angle}} - Set robot's velocity to vel (0-1), at angle<br> |
− | void | + | {{funcdoc|void|setThrustPt|vel, Point}} - Set robot's velocity to vel (0-1), toward Point. <br> |
<br> | <br> | ||
− | void | + | {{funcdoc|void|setThrustToPt|Point}} - Thrust toward Point, but adjust speed in an attempt to land right on point, and not overshoot. If Point is distant, setThrustToPt() will cause robot to go at full speed.<br> |
<br> | <br> | ||
− | void | + | {{funcdoc|void|fire|}} - Fires active weapon<br> |
− | void | + | {{funcdoc|void|setWeaponIndex|index}} - Activates weapon with index (1, 2, 3)<br> |
− | void | + | {{funcdoc|void|setWeapon|WeaponType}} - Activates weapon type specified (does nothing if WeaponType is not in current loadout)<br> |
− | boolean | + | {{funcdoc|boolean|hasWeapon |WeaponType}} - Does current configuration have specified weapon<br> |
Example: | Example: | ||
Line 259: | Line 333: | ||
==Logging & Sending Messages== | ==Logging & Sending Messages== | ||
− | void | + | {{funcdoc|void|globalMsg|msg}} - Send a message to all players<br> |
− | void | + | {{funcdoc|void|teamMsg|msg}} - Send a message to players on the same team<br> |
<br> | <br> | ||
− | void | + | {{funcdoc|void|logprint|msg}} - Print msg to game logfile and to game console<br> |
+ | |||
+ | When sending a team or global message, some basic variable substitutions are available. %playerName% will be replaced with the receiving player's name. %controlName% will be replaced with the appropriate key binding on the receiving player's machine. A full list of controlNames can be found in the KeyBinding section of the bitfighter.ini file. Examples include %SelWeapon1% and %ShowScoreboard%. While this capability is also available to player manually typing chat messages, it is mostly intended for tutorial bots to help the player along. Variable names are case insensitive. | ||
== GameItems == | == GameItems == | ||
Line 271: | Line 347: | ||
=== Ships & Robots === | === Ships & Robots === | ||
− | + | Unless otherwise noted, all of these methods will work both on a ShipItem object as well as the local bot object. | |
− | + | ||
− | + | ||
− | + | {{funcdoc|boolean|isAlive|}} - Returns true if ship/bot is still alive, false otherwise. | |
− | boolean | + | |
− | === | + | Rather than checking isAlive() every tick, it may be more efficient to keep track of dying ships by subscribing to ShipKilledEvent. |
+ | |||
+ | Example: | ||
+ | <source lang="lua"> | ||
+ | -- Assuming we've subscribed to ShipKilledEvent, and that target is a global | ||
+ | function onShipKilled(ship) | ||
+ | if target == ship then | ||
+ | target = nil -- check for target == nil elsewhere and reacquire a target | ||
+ | end | ||
+ | end | ||
+ | </source > | ||
+ | |||
+ | |||
+ | Note: All of the following functions will return nil if they are called on a dead ship. When in doubt, check for nil! | ||
+ | |||
+ | {{funcdoc|Point|getLoc|}} - Return center of item<br> | ||
+ | {{funcdoc|number|getRad|}} - Return radius of item<br> | ||
+ | {{funcdoc|Point|getVel|}} - Return speed of item<br> | ||
+ | {{funcdoc|number|getAngle|}} - Returns angle ship is currently facing<br> | ||
+ | {{funcdoc|number|getTeamIndx|}} - Index of the team this ship/robot is on<br> | ||
+ | |||
+ | {{funcdoc|PlayerInfo|getPlayerInfo|}} - Return info about player or robot controlling the ship<br> | ||
+ | |||
+ | The following two methods return a number between 0 and 1 where 1 means full health or energy, 0 means no health or energy. | ||
+ | |||
+ | {{funcdoc|number|getHealth|}} - Return health of the ship or robot<br> | ||
+ | {{funcdoc|number|getEnergy|}} - Return energy of ship or robot<br> | ||
+ | |||
+ | {{funcdoc|boolean|isModuleActive|ModuleType}} - Returns true if specified module is active<br> | ||
+ | {{funcdoc|boolean|hasFlag|}} - Returns true if ship is carrying a flag, false otherwise<br> | ||
+ | {{funcdoc|number|getFlagCount|}} - Returns number of flags a ship is carrying. Currently, ships can only have more than one flag in Nexus games<br> | ||
+ | {{funcdoc|void|dropItem|}} - Drops any items the bot may be holding<br> | ||
+ | |||
+ | |||
+ | {{funcdoc|WeaponType|getActiveWeapon|}} - Returns the active weapon constant (see WeaponType constants)<br> | ||
+ | |||
+ | === CoreItems, ResourceItems, SoccerBallItems, and TestItems === | ||
These items all have the same methods and behave in a very similar manner. | These items all have the same methods and behave in a very similar manner. | ||
Category: GameItem<br> | Category: GameItem<br> | ||
<br> | <br> | ||
− | Point | + | {{funcdoc|Point|getLoc|}} - Return center of item<br> |
− | number | + | {{funcdoc|number|getRad|}} - Return radius of item<br> |
− | Point < | + | {{funcdoc|Point|getVel|}} - Return velocity of item<br> |
+ | {{funcdoc|number|getTeamIndx|}} - Returns index of items's team (will always be NeutralTeamIndx)<br> | ||
+ | |||
+ | CoreItems have one additional method:<br> | ||
+ | {{funcdoc|number|getHealth|}} - Return item's health<br> | ||
Example: | Example: | ||
Line 320: | Line 433: | ||
Category: GameItem<br> | Category: GameItem<br> | ||
<br> | <br> | ||
− | Point | + | {{funcdoc|Point|getLoc|}} - Return center of asteroid<br> |
− | number | + | {{funcdoc|number|getRad|}} - Return radius of asteroid<br> |
− | Point | + | {{funcdoc|Point|getVel|}} - Return velocity of asteroid<br> |
− | number | + | {{funcdoc|number|getTeamIndx|}} - Returns asteroid's team index (will always be NeutralTeamIndx)<br> |
− | number | + | {{funcdoc|number|getSize|}} - Index of current asteroid size (0 = initial size, 1 = next smaller, 2 = ...)<br> |
+ | {{funcdoc|number|getSizeCount|}} - Number of indexes of size we can have<br> | ||
Example: | Example: | ||
Line 335: | Line 449: | ||
Category: GameItem<br> | Category: GameItem<br> | ||
<br> | <br> | ||
− | Point | + | {{funcdoc|Point|getLoc|}} - Return center of repairItem<br> |
− | number | + | {{funcdoc|number|getRad|}} - Return radius of repairItem<br> |
− | Point | + | {{funcdoc|Point|getVel|}} - Return velocity of repairItem (usually 0,0)<br> |
− | + | {{funcdoc|number|getTeamIndx|}} - Returns repairItems's team index (will always be NeutralTeamIndx)<br> | |
+ | {{funcdoc|boolean|isVis|}} - Is repairItem currently visible?<br> | ||
Example: | Example: | ||
Line 349: | Line 464: | ||
Category: GameItem<br> | Category: GameItem<br> | ||
<br> | <br> | ||
− | Point | + | {{funcdoc|Point|getLoc|}} - Return location of flag<br> |
− | number | + | {{funcdoc|number|getRad|}} - Return radius of flag<br> |
− | Point | + | {{funcdoc|Point|getVel|}} - Return velocity of flag<br> |
− | number | + | {{funcdoc|number|getTeamIndx|}} - Get index of owning team (-1 for neutral flag)<br> |
− | boolean | + | {{funcdoc|boolean|isInInitLoc|}} - Is flag in it's initial location?<br> |
− | boolean | + | {{funcdoc|boolean|inCaptureZone|}} - Is flag in a team's capture zone?<br> |
− | boolean | + | {{funcdoc|boolean|isOnShip|}} - Is flag being carried by a ship?<br> |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
=== Turrets and ForceFieldProjectors === | === Turrets and ForceFieldProjectors === | ||
Category: GameItem<br> | Category: GameItem<br> | ||
− | Point | + | {{funcdoc|Point|getLoc|}} - Return location of NexusFlag<br> |
− | number | + | {{funcdoc|number|getRad|}} - Return radius of NexusFlag<br> |
− | Point | + | {{funcdoc|Point|getVel|}} - Return velocity of NexusFlag<br> |
− | number | + | {{funcdoc|number|getTeamIndx|}} - Get index of owning team (see Special Team Constants)<br> |
− | number | + | {{funcdoc|number|getHealth|}} - Get health of item (1 = full heath, 0 = totally dead)<br> |
− | boolean | + | {{funcdoc|boolean|isActive|}} - True if item is active, false otherwise<br> |
+ | === GoalZones and LoadoutZones === | ||
+ | {{funcdoc|Point|getLoc|}} - Get the centroid of the zone<br> | ||
+ | {{funcdoc|number|getRad|}} - Returns 0<br> | ||
+ | {{funcdoc|Point|getVel|}} - Get the velocity of the item (will usually be (0,0)!)<br> | ||
+ | {{funcdoc|number|getTeamIndx|}} - Get index of owning team (see Special Team Constants)<br> | ||
+ | |||
+ | === Nexus Related === | ||
+ | The following functions only have meaning in a Nexus game. | ||
+ | |||
+ | {{funcdoc|number|getNexusTimeLeft|}} - Return time left until next change of the nexus open/closed status (in ms)<br> | ||
+ | {{funcdoc|boolean|isNexusOpen|}} - Returns true if nexus is open, false if closed<br> | ||
+ | |||
+ | Example: | ||
+ | <source lang="lua"> | ||
+ | if isNexusOpen() then | ||
+ | local timeLeft = getNexusTimeLeft() / 1000 | ||
+ | if timeLeft < 10 then | ||
+ | teamMsg("Hurry! Nexus closes in " .. timeLeft .. " seconds!") | ||
+ | end | ||
+ | end | ||
+ | </source> | ||
=== Bullets, Mines, and SpyBugs === | === Bullets, Mines, and SpyBugs === | ||
Line 383: | Line 511: | ||
<br><br> | <br><br> | ||
− | Point | + | {{funcdoc|Point|getLoc|}} - Return location of item<br> |
− | number | + | {{funcdoc|number|getRad|}} - Return radius of item<br> |
− | Point | + | {{funcdoc|Point|getVel|}} - Return velocity of item<br> |
− | WeaponType | + | {{funcdoc|number|getTeamIndx|}} - Get the team of the shooter/layer of the item<br> |
+ | {{funcdoc|WeaponType|getWeapon|}} - Return WeaponType for this item<br> | ||
Example: | Example: | ||
Line 398: | Line 527: | ||
logprint("Selected " .. weapInfo:getName()) | logprint("Selected " .. weapInfo:getName()) | ||
</source> | </source> | ||
− | |||
− | |||
== Weapon Information == | == 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 | + | 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 main() function, and store it in a global variable rather than instantiating a new WeaponInfo object during every iteration of the robot's onTick() method. |
Example: | Example: | ||
Line 411: | Line 538: | ||
<source lang="lua"> | <source lang="lua"> | ||
− | -- In | + | -- In main()... we'd better not declare these variables as local! |
− | turretInfo = WeaponInfo(WeaponTurret) -- Get info about | + | function main() |
− | + | ... | |
+ | turretInfo = WeaponInfo(WeaponTurret) -- Get info about the infernal turrets | ||
+ | safeDist = turretInfo:getRange() -- Be safe and stay this far away | ||
+ | ... | ||
+ | end | ||
</source> | </source> | ||
− | string | + | {{funcdoc|string|getName|}} - Name of weapon ("Phaser", "Triple", etc.)<br> |
− | WeaponType | + | {{funcdoc|WeaponType|getID|}} - ID of module (WeaponPhaser, WeaponTriple, etc.)<br> |
− | number | + | {{funcdoc|number|getRange|}} - Get range of weapon (units)<br> |
− | number | + | {{funcdoc|number|getFireDelay|}} - Delay between shots in ms<br> |
− | number | + | {{funcdoc|number|getMinEnergy|}} - Minimum energy needed to use<br> |
− | number | + | {{funcdoc|number|getEnergyDrain|}} - Amount of energy weapon consumes<br> |
− | number | + | {{funcdoc|number|getProjVel|}} - Speed of projectile (units/sec)<br> |
− | number | + | {{funcdoc|number|getProjLife|}} - Time projectile will live (ms) -1 == live forever)<br> |
− | number | + | {{funcdoc|number|getDamage|}} - Damage projectile does (0-1, where 1 = total destruction)<br> |
− | + | {{funcdoc|number|getDamageSelf|}} - Damage if shooter shoots self (0-1, where 1 = total destruction)<br> | |
− | boolean | + | {{funcdoc|boolean|getCanDamageTeammate|}} - Will weapon damage teammates?<br> |
== WeaponType constants == | == WeaponType constants == | ||
+ | Commands that return a WeaponType will return one of the following values: | ||
<table width=100%> | <table width=100%> | ||
− | <tr><td width=50%><b> | + | <tr><td width=50%><b>Weapon.Phaser</b></td><td><b>Weapon.Bounce</b></td></tr> |
− | <tr><td><b> | + | <tr><td><b>Weapon.Triple</b></td><td><b>Weapon.Burst</b></td></tr> |
− | <tr><td><b> | + | <tr><td><b>Weapon.Mine</b></td><td><b>Weapon.SpyBug</b></td></tr> |
− | <tr><td><b> | + | <tr><td><b>Weapon.Turret</b></td><td></td></tr> |
</table> | </table> | ||
== Module Information == | == Module Information == | ||
+ | All ModuleInfo data will remain constant throughout the game. Therefore, if you need some information about a module (for example, the energy consmption of shields), it might make sense to retrieve it in the bot's main() function, and store it in a global variable rather than instantiating a new ModuleInfo object during every iteration of the robot's onTick() method. | ||
+ | |||
Example: | Example: | ||
Line 445: | Line 579: | ||
logprint("This is a lame example!")</source> | logprint("This is a lame example!")</source> | ||
− | string | + | {{funcdoc|string|getName|}} - Name of module ("Shield", "Turbo", etc.)<br> |
− | ModuleType | + | {{funcdoc|ModuleType|getID|}} - ID of module (ModuleShield, ModuleBoost, etc.)<br> |
== ModuleType constants == | == ModuleType constants == | ||
+ | Functions that return a ModuleType will return one of the following values: | ||
<table width = 100%> | <table width = 100%> | ||
<tr><td width=50%><b>ModuleShield</b></td><td><b>ModuleBoost</b></td></tr> | <tr><td width=50%><b>ModuleShield</b></td><td><b>ModuleBoost</b></td></tr> | ||
<tr><td><b>ModuleSensor</b></td><td><b>ModuleRepair</b></td></tr> | <tr><td><b>ModuleSensor</b></td><td><b>ModuleRepair</b></td></tr> | ||
− | <tr><td><b>ModuleCloak</b></td><td><b>ModuleEngineer</b> | + | <tr><td><b>ModuleCloak</b></td><td><b>ModuleEngineer</b></td></tr> |
+ | <tr><td><b>ModuleArmor</b></td></tr> | ||
</table> | </table> | ||
== Loadouts == | == Loadouts == | ||
− | void | + | {{funcdoc|void|setWeapon|index, WeaponType}} - Set weapon at index<br> |
− | void | + | {{funcdoc|void|setModule|index, ModuleType}} - Set module at index<br> |
− | WeaponType | + | {{funcdoc|WeaponType|getWeapon|index}} - Return weapon at index<br> |
− | ModuleType | + | {{funcdoc|ModuleType|getModule|index}} - Return module at index<br> |
Example: | Example: | ||
Line 484: | Line 620: | ||
− | boolean | + | {{funcdoc|boolean|isValid|}} - Is loadout config valid?<br> |
− | boolean | + | {{funcdoc|boolean|equals|Loadout}} - is loadout the same as Loadout?<br> |
== GameInfo == | == GameInfo == | ||
Line 506: | Line 642: | ||
Example: | Example: | ||
<source lang="lua"> | <source lang="lua"> | ||
− | -- Create the GameInfo object in | + | -- Create the GameInfo object in main() (don't declare it local!!) |
− | game = GameInfo() | + | function main() |
− | + | ... | |
+ | game = GameInfo() -- In main non-local variables become globals | ||
+ | ... | ||
+ | end | ||
... | ... | ||
Line 515: | Line 654: | ||
... | ... | ||
− | -- Later, in | + | -- Later, in onTick(): |
local remTime = game:getGameTimeReamaining() | local remTime = game:getGameTimeReamaining() | ||
local totTime = game:getGameTimeTotal() | local totTime = game:getGameTimeTotal() | ||
Line 522: | Line 661: | ||
</source> | </source> | ||
− | GameType | + | {{funcdoc|GameType |getGameType|}} - Return current game type <br> |
− | string | + | {{funcdoc|string|getGameTypeName|}} - Return current game type <br> |
− | number | + | {{funcdoc|number|getFlagCount|}} - Return the number of flags in the game<br> |
− | number | + | {{funcdoc|number|getWinningScore|}} - Returns the score required to win the level<br> |
− | number | + | {{funcdoc|number|getGameTimeTotal|}} - Returns the time (in seconds) that the level will be played for<br> |
− | number | + | {{funcdoc|number|getGameTimeRemaining|}} - Returns the time remaining (in seconds) for this level<br> |
− | number | + | {{funcdoc|number|getLeadingScore|}} - Gets score of the leading team<br> |
− | number | + | {{funcdoc|number|getLeadingTeam|}} - Gets index of leading team<br> |
− | number | + | {{funcdoc|number|getTeamCount|}} - Return number of teams<br> |
Example: | Example: | ||
<source lang="lua"> | <source lang="lua"> | ||
− | -- Create the GameInfo object | + | -- Create the GameInfo object main() (don't declare it local!!) |
− | game = GameInfo() | + | function main() |
− | + | ... | |
− | -- Then later | + | game = GameInfo() -- In main non-local variables become globals |
− | + | ... | |
+ | end | ||
+ | |||
+ | -- Then later, in onTick() or other function | ||
+ | ... | ||
local leadingTeam = game:getLeadingTeam() | local leadingTeam = game:getLeadingTeam() | ||
local team = TeamInfo(leadingTeam) | local team = TeamInfo(leadingTeam) | ||
Line 544: | Line 687: | ||
</source> | </source> | ||
− | + | {{funcdoc|PlayerInfo list|getPlayers|}} - Return a list of PlayerInfo objects representing all players/bots in the game<br> | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
+ | Example: | ||
+ | <source lang="lua"> | ||
+ | players = GameInfo():getPlayers() | ||
+ | for player in values(players) do | ||
+ | if player:isRobot() then | ||
+ | logprint(player:getName().." is a bot!") | ||
+ | else | ||
+ | logprint("Hello "..player:getName()) | ||
+ | end | ||
+ | end | ||
+ | </source> | ||
+ | |||
+ | {{funcdoc|string|getLevelName|}} - Gets the level's name<br> | ||
+ | {{funcdoc|number|getGridSize|}} - Gets the level's gridSize parameter<br> | ||
+ | {{funcdoc|boolean|IsTeamGame|}} - Is this a team game?<br> | ||
+ | <br> | ||
+ | {{funcdoc|void|getEventScore|ScoringEvent}} - Gets score awarded for ScoringEvent<br> | ||
== GameType constants == | == GameType constants == | ||
+ | Functions that return a GameType will return one of the following values: | ||
<table width=100%> | <table width=100%> | ||
− | <tr><td width=50%><b>BitmatchGame</b></td><td><b> | + | <tr><td width=50%><b>BitmatchGame</b></td><td><b>CoreGame</b></td></tr> |
− | <tr><td><b> | + | <tr><td><b>CTFGame</b></td><td><b>HTFGame</b></td></tr> |
− | <tr><td><b> | + | <tr><td><b>NexusGame</b></td><td><b>RabbitGame</b></td></tr> |
− | <tr><td><b>SoccerGame</b></td><td><b>ZoneControlGame</b></td></tr> | + | <tr><td><b>RetrieveGame</b></td><td><b>SoccerGame</b></td></tr> |
+ | <tr><td><b>ZoneControlGame</b></td></tr> | ||
</table> | </table> | ||
== ScoringEvent constants == | == ScoringEvent constants == | ||
+ | Functions that return a ScoringEvent will return one of the following values: | ||
<table width=100%> | <table width=100%> | ||
<tr><td width=50%><b>KillEnemy</b></td><td><b>KillSelf</b></td></tr> | <tr><td width=50%><b>KillEnemy</b></td><td><b>KillSelf</b></td></tr> | ||
Line 574: | Line 733: | ||
== TeamInfo == | == TeamInfo == | ||
− | string | + | {{funcdoc|string|getName|}} - return team name<br> |
− | number | + | {{funcdoc|number|getIndex|}} - return team's index, index of first team is 1<br> |
− | number | + | {{funcdoc|number|getPlayerCount|}} - return player count<br> |
− | number | + | {{funcdoc|number|getScore|}} - return team score<br> |
+ | {{funcdoc|PlayerInfo list|getPlayers|}} - Return a list of PlayerInfo objects representing all players/bots on the team<br> | ||
Example: | Example: | ||
Line 596: | Line 756: | ||
</source> | </source> | ||
− | == | + | === Special team constants === |
− | + | Two special team constants are available to make your programs simpler and easier to read. In addition to the normal player teams, Bitfighter has two pseudo teams, "Neutral" and "Hostile". These constants can be used to identify those teams: | |
+ | |||
+ | <b>NeutralTeamIndx</b> - Neutral team<br> | ||
+ | <b>HostileTeamIndx</b> - Hostile team<br> | ||
− | |||
− | |||
− | |||
− | |||
− | |||
Example: | Example: | ||
<source lang="lua"> | <source lang="lua"> | ||
− | + | team = turret:getTeamIndx() | |
− | + | if team == NeutralTeamIndx then -- Neutral - can be repaired | |
− | + | mode = repair | |
− | - | + | else if team == HostileTeamIndx then -- Hostile - must be destroyed! |
− | + | mode = attack | |
− | if | + | |
− | + | ||
− | + | ||
end | end | ||
</source> | </source> | ||
+ | |||
+ | == PlayerInfo == | ||
+ | Provides information about a player or robot. The PlayerInfo object will remain valid and will contain current information as long as the player is in the game.<br><br> | ||
+ | string <b>getName()</b> - return player's name<br> | ||
+ | Ship <b>getShip()</b> - return player's ship (note: can be nil)<br> | ||
+ | string <b>getScriptName()</b> - for robots, returns name of script; for players, returns nil<br> | ||
+ | number <b>getTeamIndx()</b> - return team's index, index of first team is 1<br> | ||
+ | number <b>getRating()</b> - return player's rating<br> | ||
+ | number <b>getScore()</b> - return player's score<br> | ||
+ | boolean <b>isRobot()</b> - returns true if the PlayerInfo is a robot, false if it is a human<br> | ||
+ | |||
+ | == Timers == | ||
+ | Timer is a utility class that makes it easier to manage timing periodic or delayed events. The timer code is based on a very nice library written by Haywood Slap | ||
+ | |||
+ | A timer object that can be used to schedule arbitrary events to be executed | ||
+ | at some time in the future. All times are given in milliseconds. | ||
+ | |||
+ | === Usage === | ||
+ | |||
+ | There are four basic timer functions: | ||
+ | <source lang="lua"> | ||
+ | -- Execute the event once in 'delay' ms | ||
+ | Timer:scheduleOnce(event, delay) | ||
+ | |||
+ | -- Execute the event repeatedly, every 'delay' ms | ||
+ | Timer:scheduleRepeating(event, delay) | ||
+ | |||
+ | -- Execute the event every 'delay' ms while event returns true | ||
+ | Timer:scheduleRepeatWhileTrue(event, delay) | ||
+ | |||
+ | -- Remove all pending events from the timer's queue | ||
+ | Timer:clear() | ||
+ | </source> | ||
+ | |||
+ | === Timer Events === | ||
+ | |||
+ | The 'event' used can either be the name a function, a table that contains | ||
+ | a function named 'run', or a bit of arbitrary Lua code. | ||
+ | |||
+ | Any Lua function can be called by the Timer. | ||
+ | |||
+ | Examples: | ||
+ | <source lang="lua"> | ||
+ | function onLevelStart() | ||
+ | -- Compiles and runs string in five seconds. | ||
+ | -- Note that the string here is in quotes. | ||
+ | Timer:scheduleOnce("logprint(\"Once!\")", 5000) | ||
+ | |||
+ | -- Runs the always function every 30 seconds | ||
+ | Timer:scheduleRepeating(always, 30000) | ||
+ | |||
+ | -- Runs "run" method of maybe every two seconds | ||
+ | -- until method returns false | ||
+ | Timer:scheduleRepeatWhileTrue(maybe, 2000) | ||
+ | end | ||
+ | |||
+ | -- Standard function | ||
+ | function always() | ||
+ | logprint("Timer called always") | ||
+ | end | ||
+ | |||
+ | -- Table: run method will be called | ||
+ | maybe = { | ||
+ | count = 3 | ||
+ | run = function(self) | ||
+ | logprint("Count down " .. self.count) | ||
+ | self.count = self.count - 1 | ||
+ | return self.count > 0 | ||
+ | end | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | When using a table as an 'event' the first parameter to the run | ||
+ | function should always be a parameter named "self" (or "this" | ||
+ | if you prefer). The "self" parameter can be used to access the | ||
+ | other fields in the table, that is, the "self" parameter will refer | ||
+ | to the table that contains the run function. If you do not need to | ||
+ | refer to any of the other fields in the table then you do not need | ||
+ | to include the self parameter. | ||
== Points == | == Points == | ||
Line 621: | Line 855: | ||
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 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 | + | {{funcdoc|Point|Point|x, y}} - Create a new Point object with coordinates x, y. You do not need to use setxy() unless you want to change the coords later.<br> |
− | number | + | {{funcdoc|number|x|}} - Return x value<br> |
− | number | + | {{funcdoc|number|y|}} - Return y value<br> |
− | + | {{funcdoc|void|setxy|x, y}} - Update coordinates of point<br> | |
− | + | {{funcdoc|void|setx|x}} - Update x coordinate of point<br> | |
− | + | {{funcdoc|void|sety|y}} - Update y coordinate of point<br> | |
+ | {{funcdoc|void|setAngle|ang}} - Set angle of point to <i>ang</i>. This will preserve the point's current length, and can can be used in conjunction with normalize to set a point via polar coordinates<br> | ||
+ | {{funcdoc|void|setPolar|r, ang}} - Set point using polar coordinates, r = distance from origin, ang = angle<br> | ||
− | number | + | {{funcdoc|number|len|}} - Returns the length of the point, useful if point represents a velocity or other vector<br> |
− | number | + | {{funcdoc|number|lenSquared|}} - Returns the length squared of the point, useful if point represents a velocity or other vector, and you want a less computationally expensive representation of length that will work for comparative purposes<br> |
− | boolean | + | {{funcdoc|boolean|equals|Point}} - Returns true if the point is the same as Point<br> |
− | void | + | {{funcdoc|void|normalize|length}} - If Point represents a vector, set its length to <i>length</i> |
− | number | + | {{funcdoc|number|distanceTo|Point}} - Returns distance from point to Point<br> |
− | number | + | {{funcdoc|number|distSquared|Point}} - Returns distance squared from point to Point (less computationally intensive, good for comparison)<br> |
− | number | + | {{funcdoc|number|angleTo|Point}} - Returns angle from point to Point<br> |
+ | {{funcdoc|number|angleTo|x, y}} - Returns angle from point to point(x, y)<br> | ||
Example: | Example: | ||
Line 642: | Line 879: | ||
local point = Point(0, 0) -- Create a new point | local point = Point(0, 0) -- Create a new point | ||
− | if | + | if point:equals(Point(1, 1)) then |
logprint("This never gets printed") | logprint("This never gets printed") | ||
end | end | ||
− | if | + | if point:distanceTo(Point(1, 1)) < 5 then |
logprint("This always gets printed") | logprint("This always gets printed") | ||
+ | end | ||
+ | |||
+ | -- Same thing, but with a little less overhead | ||
+ | if point:distanceTo(1, 1) < 5 then | ||
+ | logprint("So does this, but more efficiently") | ||
end | end | ||
Line 659: | Line 901: | ||
== ItemTypes == | == ItemTypes == | ||
+ | Functions that return an ItemType will return one of the following values: | ||
<b>ShipType</b><br> | <b>ShipType</b><br> | ||
− | |||
<b>BulletType</b><br> | <b>BulletType</b><br> | ||
<b>MineType</b><br> | <b>MineType</b><br> | ||
<b>SpyBugType</b><br> | <b>SpyBugType</b><br> | ||
− | |||
<b>RobotType</b><br> | <b>RobotType</b><br> | ||
<b>TeleportType</b> (not yet fully implemented)<br> | <b>TeleportType</b> (not yet fully implemented)<br> | ||
<b>SpeedZoneType</b> (not yet fully implemented)<br> | <b>SpeedZoneType</b> (not yet fully implemented)<br> | ||
<b>AsteroidType</b><br> | <b>AsteroidType</b><br> | ||
+ | <b>CoreType</b><br> | ||
<b>TurretType</b><br> | <b>TurretType</b><br> | ||
<b>ForceFieldProjectorType</b><br> | <b>ForceFieldProjectorType</b><br> | ||
Line 687: | Line 929: | ||
<b>BarrierType</b> | <b>BarrierType</b> | ||
<b>ForceFieldType</b><br> | <b>ForceFieldType</b><br> | ||
+ | |||
+ | == Helper functions == | ||
+ | {{funcdoc|item|findClosest|(list of items [, teamIndx]}} - Finds item closest to bot. If teamIndx is specified, only considers items on team. If list is empty (or there are no team items in the list), funtion returns nil. |
Latest revision as of 23:23, 4 April 2013
TODO: FlagItem:getCaptureZone & FlagItem:getShip functions (verify w/ sam); make sure all items have getLoc, getRad, getVel, getTeamIndx, and isItem; hasFlag(); engineer-deploy-object (??), ship/bot:getMountedItems(); document libopt (or at least or inclusion of such with summary and pointer to docs) -- need to do require("getopt") to activate it. mention inspect.lua under debugging section.
Server admins can program their own robots in addition to designing their own levels. Robots are coded in Lua, and 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 the information here is subject to change. You might want to read about the general structure of Bitfighter scripts before proceeding.
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. However, relying on this automatic type coercion is bad practice and can 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 programming languages such as C++ and Java
- 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 variables 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 Events
- 2 Navigation, configuration, and combat
- 3 Logging & Sending Messages
- 4 GameItems
- 5 Weapon Information
- 6 WeaponType constants
- 7 Module Information
- 8 ModuleType constants
- 9 Loadouts
- 10 GameInfo
- 11 GameType constants
- 12 ScoringEvent constants
- 13 TeamInfo
- 14 PlayerInfo
- 15 Timers
- 16 Points
- 17 ItemTypes
- 18 Things that are not really items
- 19 Helper functions
Events
Lua robots are designed to respond to many in-game events using event handlers. To respond to an event your bot must have an appropriately named function (described below), and must subscribe to any events it is interested in. You can also unsubscribe to an event at any time to stop receiving event notifications.
Currently defined events
Event ID | Associated function | Description |
---|---|---|
TickEvent | onTick(timeSinceLastTickInMs) | Called every frame of the game. Unlike other events, robots are subscribed to this event by default. |
ShipSpawnedEvent | onShipSpawned(ship) | Called when any ship or robot spawns (not including your robot). Spawning ship is passed as the first argument. |
ShipKilledEvent | onShipKilled(ship) | Called when any ship or robot is killed (not including your robot). Dead ship is passed as the first argument. |
MsgReceivedEvent | onMsgReceived(message, senderPlayerInfo, global) | Called when a player or robot sends a message. Message itself is first argument, a PlayerInfo for the sender is the second argument, and the third argument is a boolean that will be true if the message was global, false if sent to teammates only. Note that senderPlayerInfo can be nil if message is sent by a Controller script. |
PlayerJoinedEvent | onPlayerJoined(playerInfo) | Called when a player or robot joins the game (not when they spawn). Note that when hosting a game locally, local player does not necessarily trigger this event due to the uncertain order of creation of game objects. First argument is playerInfo for the joining player. |
PlayerLeftEvent | onPlayerLeft(playerInfo) | Called when a player or robot leaves the game. Note that event may not be fired when robots or local player leaves game due to server shutting down. First argument is playerInfo for the leaving player. |
NexusOpenedEvent | onNexusOpened() | Called when Nexus opens in a Nexus game. Never fires in other game types. |
NexusClosedEvent | onNexusClosed() | Called when Nexus closes in a Nexus game. Never fires in other game types. |
Example:
function main() ... subscribe(ShipSpawnedEvent) -- Subscribe so onShipSpawned() will be called ... end -- Since we are subscribed to the ShipSpawned event, the onShipSpawned() function -- will be called whever a ship or robot spawns. function onShipSpawned(ship) logprint("Ship spawned: ".. tostring(ship:getLoc())) angle = bot:getLoc():angleTo(ship:getLoc()) logprint("angle to spawned ship = "..angle) end
Example:
-- Keep track of all players/robots in the game. This may be more efficient than -- reading the list every time it is needed if it is used frequently. function main() subscribe(PlayerJoinedEvent) subscribe(PlayerLeftEvent) players = GameInfo():getPlayers() -- Vars defined in main() will be global end function onPlayerJoined() players = GameInfo():getPlayers() -- Player joined: get updated player list end function onPlayerLeft() players = GameInfo():getPlayers() -- Player left: get updated player list end
You can also use timers and events to create a basic sleep function:
function sleep(time) -- Sleep time in ms unsubscribe(TickEvent) -- Stop getting tick events until... Timer:scheduleOnce("subscribe(TickEvent)", time) -- ...we resubscribe! end
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()
- number getTeamIndx()
- void setAngle(ang)
- void setAngle(pt)
Example:
-- This code in onTick() 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)
- boolean hasLosPt(x, y)
- 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 onTick() 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)
- Point getWaypoint(x, y)
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)
When sending a team or global message, some basic variable substitutions are available. %playerName% will be replaced with the receiving player's name. %controlName% will be replaced with the appropriate key binding on the receiving player's machine. A full list of controlNames can be found in the KeyBinding section of the bitfighter.ini file. Examples include %SelWeapon1% and %ShowScoreboard%. While this capability is also available to player manually typing chat messages, it is mostly intended for tutorial bots to help the player along. Variable names are case insensitive.
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
Unless otherwise noted, all of these methods will work both on a ShipItem object as well as the local bot object.
- boolean isAlive()
Rather than checking isAlive() every tick, it may be more efficient to keep track of dying ships by subscribing to ShipKilledEvent.
Example:
-- Assuming we've subscribed to ShipKilledEvent, and that target is a global function onShipKilled(ship) if target == ship then target = nil -- check for target == nil elsewhere and reacquire a target end end
Note: All of the following functions will return nil if they are called on a dead ship. When in doubt, check for nil!
- Point getLoc()
- number getRad()
- Point getVel()
- number getAngle()
- number getTeamIndx()
- PlayerInfo getPlayerInfo()
The following two methods return a number between 0 and 1 where 1 means full health or energy, 0 means no health or energy.
- number getHealth()
- number getEnergy()
- boolean isModuleActive(ModuleType)
- boolean hasFlag()
- number getFlagCount()
- void dropItem()
- WeaponType getActiveWeapon()
CoreItems, ResourceItems, SoccerBallItems, and TestItems
These items all have the same methods and behave in a very similar manner.
Category: GameItem
- Point getLoc()
- number getRad()
- Point getVel()
- number getTeamIndx()
CoreItems have one additional method:
- number getHealth()
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 getTeamIndx()
- 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()
- number getTeamIndx()
- 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()
Turrets and ForceFieldProjectors
Category: GameItem
- Point getLoc()
- number getRad()
- Point getVel()
- number getTeamIndx()
- number getHealth()
- boolean isActive()
GoalZones and LoadoutZones
- Point getLoc()
- number getRad()
- Point getVel()
- number getTeamIndx()
Nexus Related
The following functions only have meaning in a Nexus game.
- number getNexusTimeLeft()
- boolean isNexusOpen()
Example:
if isNexusOpen() then local timeLeft = getNexusTimeLeft() / 1000 if timeLeft < 10 then teamMsg("Hurry! Nexus closes in " .. timeLeft .. " seconds!") end end
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()
- number getTeamIndx()
- 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 main() function, and store it in a global variable rather than instantiating a new WeaponInfo object during every iteration of the robot's onTick() method.
Example:
local weap = bot:getActiveWeapon() -- Get bot's currently active weapon logprint(weap:getName() .. " shoots with a speed of " .. weap:getProjVel())
-- In main()... we'd better not declare these variables as local! function main() ... turretInfo = WeaponInfo(WeaponTurret) -- Get info about the infernal turrets safeDist = turretInfo:getRange() -- Be safe and stay this far away ... end
- string getName()
- WeaponType getID()
- number getRange()
- number getFireDelay()
- number getMinEnergy()
- number getEnergyDrain()
- number getProjVel()
- number getProjLife()
- number getDamage()
- number getDamageSelf()
- boolean getCanDamageTeammate()
WeaponType constants
Commands that return a WeaponType will return one of the following values:
Weapon.Phaser | Weapon.Bounce |
Weapon.Triple | Weapon.Burst |
Weapon.Mine | Weapon.SpyBug |
Weapon.Turret |
Module Information
All ModuleInfo data will remain constant throughout the game. Therefore, if you need some information about a module (for example, the energy consmption of shields), it might make sense to retrieve it in the bot's main() function, and store it in a global variable rather than instantiating a new ModuleInfo object during every iteration of the robot's onTick() method.
Example:
local mod = ModuleInfo(ModuleBoost) logprint("This is a lame example!")
- string getName()
- ModuleType getID()
ModuleType constants
Functions that return a ModuleType will return one of the following values:
ModuleShield | ModuleBoost |
ModuleSensor | ModuleRepair |
ModuleCloak | ModuleEngineer |
ModuleArmor |
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 main() (don't declare it local!!) function main() ... game = GameInfo() -- In main non-local variables become globals ... end ... -- Even though we only create our GameInfo once, it is always current ... -- Later, in onTick(): 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 main() (don't declare it local!!) function main() ... game = GameInfo() -- In main non-local variables become globals ... end -- Then later, in onTick() or other function ... local leadingTeam = game:getLeadingTeam() local team = TeamInfo(leadingTeam) bot:teamMsg("Hurry up! Team " .. team:getName() .. " is winning!" )
- PlayerInfo list getPlayers()
Example:
players = GameInfo():getPlayers() for player in values(players) do if player:isRobot() then logprint(player:getName().." is a bot!") else logprint("Hello "..player:getName()) end end
- string getLevelName()
- number getGridSize()
- boolean IsTeamGame()
- void getEventScore(ScoringEvent)
GameType constants
Functions that return a GameType will return one of the following values:
BitmatchGame | CoreGame |
CTFGame | HTFGame |
NexusGame | RabbitGame |
RetrieveGame | SoccerGame |
ZoneControlGame |
ScoringEvent constants
Functions that return a ScoringEvent will return one of the following values:
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()
- PlayerInfo list getPlayers()
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
Special team constants
Two special team constants are available to make your programs simpler and easier to read. In addition to the normal player teams, Bitfighter has two pseudo teams, "Neutral" and "Hostile". These constants can be used to identify those teams:
NeutralTeamIndx - Neutral team
HostileTeamIndx - Hostile team
Example:
team = turret:getTeamIndx() if team == NeutralTeamIndx then -- Neutral - can be repaired mode = repair else if team == HostileTeamIndx then -- Hostile - must be destroyed! mode = attack end
PlayerInfo
Provides information about a player or robot. The PlayerInfo object will remain valid and will contain current information as long as the player is in the game.
string getName() - return player's name
Ship getShip() - return player's ship (note: can be nil)
string getScriptName() - for robots, returns name of script; for players, returns nil
number getTeamIndx() - return team's index, index of first team is 1
number getRating() - return player's rating
number getScore() - return player's score
boolean isRobot() - returns true if the PlayerInfo is a robot, false if it is a human
Timers
Timer is a utility class that makes it easier to manage timing periodic or delayed events. The timer code is based on a very nice library written by Haywood Slap
A timer object that can be used to schedule arbitrary events to be executed at some time in the future. All times are given in milliseconds.
Usage
There are four basic timer functions:
-- Execute the event once in 'delay' ms Timer:scheduleOnce(event, delay) -- Execute the event repeatedly, every 'delay' ms Timer:scheduleRepeating(event, delay) -- Execute the event every 'delay' ms while event returns true Timer:scheduleRepeatWhileTrue(event, delay) -- Remove all pending events from the timer's queue Timer:clear()
Timer Events
The 'event' used can either be the name a function, a table that contains a function named 'run', or a bit of arbitrary Lua code.
Any Lua function can be called by the Timer.
Examples:
function onLevelStart() -- Compiles and runs string in five seconds. -- Note that the string here is in quotes. Timer:scheduleOnce("logprint(\"Once!\")", 5000) -- Runs the always function every 30 seconds Timer:scheduleRepeating(always, 30000) -- Runs "run" method of maybe every two seconds -- until method returns false Timer:scheduleRepeatWhileTrue(maybe, 2000) end -- Standard function function always() logprint("Timer called always") end -- Table: run method will be called maybe = { count = 3 run = function(self) logprint("Count down " .. self.count) self.count = self.count - 1 return self.count > 0 end }
When using a table as an 'event' the first parameter to the run function should always be a parameter named "self" (or "this" if you prefer). The "self" parameter can be used to access the other fields in the table, that is, the "self" parameter will refer to the table that contains the run function. If you do not need to refer to any of the other fields in the table then you do not need to include the self parameter.
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)
- void setAngle(ang)
- void setPolar(r, ang)
- number len()
- number lenSquared()
- boolean equals(Point)
- void normalize(length)
- number distanceTo(Point)
- number distSquared(Point)
- number angleTo(Point)
- number angleTo(x, y)
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 -- Same thing, but with a little less overhead if point:distanceTo(1, 1) < 5 then logprint("So does this, but more efficiently") 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
Functions that return an ItemType will return one of the following values:
ShipType
BulletType
MineType
SpyBugType
RobotType
TeleportType (not yet fully implemented)
SpeedZoneType (not yet fully implemented)
AsteroidType
CoreType
TurretType
ForceFieldProjectorType
TestItemType
FlagType
SoccerBallItemType
ResourceItemType
Things that are not really items
Polygonal-like
BotNavMeshZoneType
NexusType
GoalZoneType
LoadoutZoneType
Wall-like
BarrierType
ForceFieldType
Helper functions
- item findClosest((list of items [, teamIndx])