Level Editor/Scripting/Bots
bot.gd tells the bots what their job is. It starts with extends GamemodeBotScript, runs on the server next to server.gd, and shares the server script's api, a GamemodeServerApi: the same object, so it reads the live state the rules are writing. Its hooks are on GamemodeBotScript.
Keep the rules in server.gd. The bot script can call everything the server script can, but a point scored from here is a rule nobody will find.
What a bot does with no orders
It hunts whoever is nearest and wanders when there is nobody. That is right for a mode with no objective. For anything else, an unordered team of bots ignores the objective and the round never ends, so give them jobs.
Orders
A bot is told its job, never its movement: finding a path, fighting and getting unstuck are its own business.
- api.bot_attack
(bot, target): hunt one player. - api.bot_move_to
(bot, position, arrive_radius): go somewhere. It shoots at what it passes but does not chase. - api.bot_defend
(bot, position, radius): hold ground. It fights anything insideradiusand comes back to the middle. - api.bot_collect
(bot, pickup): go and take a pickup. The order ends by itself when the pickup is gone, and is refused (returns false) for a pickup that is already gone. - api.bot_escort
(bot, player): stay with a player and fight what threatens them. Ends by itself when they die or leave. - api.bot_roam
(bot): back to hunting and wandering.
Each returns false when the entity is not a bot, so a loop over api.players can order everyone and let the humans fall through. api.bot_order says what a bot was last told.
Orders do not expire, and giving the same order again costs nothing. The pattern that works is to decide every bot's job on an interval and give it every time, rather than trying to notice when something changed:
extends GamemodeBotScript const REVIEW_SECONDS := 1.0 var _review: float = 0.0 func _tick(delta: float) -> void: _review -= delta if _review > 0.0: return _review = REVIEW_SECONDS for entity_id in api.bots(): if api.is_alive(entity_id): api.bot_roam(entity_id)
Hooks
_start(): runs after the server script's_start, so whatever the rules set up is there to read. Look up the zones and positions your orders use here, once._tick(delta): every server tick, after the rules have run for that tick._player_joined(entity_id): anybody joined; api.is_bot says whether it is a bot._player_spawned(entity_id),_player_left(entity_id)._player_died(entity_id, killer_id): drop whatever you were tracking about them. There is no respawn time here: when to respawn is a rule, and the server script decides it._entity_damaged(entity, amount, attacker)and_entity_died(entity, killer): a boss was hurt or killed, for sending bots to defend or attack it.
For training maps, the map can also spawn bots of its own with api.spawn_bot and give them training orders (bot_idle, bot_dodge, bot_shoot). See Map entities.
Skill and kits
- api.bot_set_skill
(bot, skill): how good a bot is at fighting, from 0 to 1. Set it in_player_joined; the map decides how hard its bots are. - api.bot_kit and api.bot_role: the loadout a bot was dealt and what it is for (
assault,support,defenseorsniper). Deal jobs by role: support escorts the carrier, defense holds the base. - api.bot_kits lists every kit, and api.bot_set_kit gives a bot a different one from its next life.
Lists
api.bots lists every bot, sorted. Bots are also in api.players, because everything else about them is a player.
For a full bot script, see the King of the Hill example.