Overview
The current CTF bot AI has a solid foundation (flag state tracking, 7 CTF states, basic escort, dropped flag priority, a radio system), but bots treat CTF as an independent node-traversal problem rather than a coordinated team game. All bots run identical goal logic per-frame with no role assignment, leading to situations like all 4 bots chasing the enemy flag simultaneously while leaving the base undefended.
This issue tracks a set of layered improvements. Implementation order matters — each item builds on the one before it.
1. CTF Plan System
Add a per-team ctf_plan_t enum updated once per second (not per-frame) by a single coordinator function. All downstream role and behavior decisions read from this.
typedef enum {
CTF_PLAN_BALANCED, // Default: normal role split
CTF_PLAN_DEFEND, // Team is ahead — protect the lead
CTF_PLAN_FULL_ATTACK, // Behind + low time — everyone rushes, 1 defender max
CTF_PLAN_RUSH, // Enemy flag just dropped — all bots converge
CTF_PLAN_STALL, // Team has enemy flag and is ahead — carrier stalls for time
} ctf_plan_t;
Transition triggers:
FULL_ATTACK: score deficit ≥ 2 caps AND time remaining ≤ 3 minutes
DEFEND: team is ahead by ≥ 2 caps AND time remaining > 3 minutes
RUSH: enemy flag transitions to dropped state (broadcast immediately)
STALL: team has enemy flag AND is ahead — carrier circles/avoids rather than rushing home
BALANCED: default / fallback
2. Role Assignment
A coordinator runs once per second (not per-frame) and slots each bot into a role based on the current plan and team size. Bots in BOTLIB_CTF_Goals() check their assigned role before evaluating goals.
Role caps by plan (example with 4 bots):
| Plan |
Defenders |
Attackers |
Escorts |
| BALANCED |
1 |
2 |
1 |
| DEFEND |
2 |
1 |
1 |
| FULL_ATTACK |
1 |
3 |
0 |
| RUSH |
0 |
all |
0 |
| STALL |
1 |
1 |
2 |
Role counts should scale with team size. Bots assigned as ROLE_ESCORT shadow the flag carrier; bots assigned ROLE_DEFENDER use the defender AI below.
3. Defender AI
When assigned ROLE_DEFENDER, bots should:
- Patrol near the home flag spawn node when the flag is home
- Hold positions near base entrances rather than wandering the map
- Chase down any enemy who enters the flagroom
- Transition to retrieval if the team flag is taken, then return to defend once it's back
4. Escort AI
The current escort behavior is opportunistic (10-second window if the bot happened to be heading for the flag). ROLE_ESCORT should be intentional and persistent:
- Continuously follow the carrier, staying ~150–300 units behind
- Prioritize killing enemies targeting the carrier over chasing unrelated enemies
- Do not abandon the carrier to chase items or wander
5. Alternative / Safer Pathing for Flag Carriers
When a bot is carrying the flag, prefer less-direct paths over the shortest path:
- At minimum, randomize among near-equal-cost routes to be less predictable
- Optionally: track "this path resulted in death while carrying" per-session and temporarily deprioritize it
- Full solution would require node metadata for cover quality (longer-term)
6. Team-Only Chat Communication
The existing radio system and chat queue infrastructure in botlib_communication.c is ready to use. CTF-specific messages must use say_team only — never say/say_all. The chat queue already has delay logic; CTF messages must also respect a per-bot cooldown (suggested: no CTF-context message more than once every ~15 seconds per bot) and a team-wide cooldown on duplicate messages (e.g., if one bot already announced "I have the flag," others should not repeat it for ~20 seconds).
Proposed message triggers:
| Event |
Example say_team message |
| Bot picks up enemy flag |
"I have the flag, cover me" |
| Bot captures flag |
"Flag captured" |
| Bot dies while carrying |
"Dropped the flag, get it" |
| Bot assigned defender role |
"I'll hold the base" (once per assignment, not repeated) |
| Enemy detected entering base |
"Enemy in base" |
| Team flag taken by enemy |
"They have our flag" |
| Bot requesting escort |
"Someone back me up" |
| Plan switches to FULL_ATTACK |
"All in, we need this cap" |
Messages should have multiple candidates per trigger (chosen randomly) to avoid sounding robotic.
Human chat parsing: Scan incoming say_team text from human teammates for keywords (defend, attack, escort, rush) and factor them into plan evaluation — not as overrides, but as weighted input.
7. Weapon Coordination (Stretch Goal)
At plan transitions or capture events, the active plan can recommend a weapon loadout preference for next spawn (e.g., sniper-heavy for DEFEND, MP5 for FULL_ATTACK). Bots would apply this as a soft preference for item pickup rather than a hard rule.
Implementation Order
Dependencies flow top-to-bottom — each item requires the one above it:
- CTF Plan system (coordinator, enum, per-team state)
- Role assignment (reads from plan, slots each bot)
- Defender AI (reads role)
- Escort AI (reads role)
- CTF team chat (hooks into state transitions, respects cooldowns)
- Human chat parsing (feeds into plan evaluation)
- Alternative pathing for carriers (mostly self-contained)
- Weapon coordination (stretch, self-contained)
Affected Files
src/action/botlib/botlib_ctf.c — primary
src/action/botlib/botlib_ai.c — role check integration
src/action/botlib/botlib_communication.c — CTF chat messages
src/action/botlib/botlib.h — new enums and struct fields
src/action/acesrc/acebot.h — bot struct additions
Overview
The current CTF bot AI has a solid foundation (flag state tracking, 7 CTF states, basic escort, dropped flag priority, a radio system), but bots treat CTF as an independent node-traversal problem rather than a coordinated team game. All bots run identical goal logic per-frame with no role assignment, leading to situations like all 4 bots chasing the enemy flag simultaneously while leaving the base undefended.
This issue tracks a set of layered improvements. Implementation order matters — each item builds on the one before it.
1. CTF Plan System
Add a per-team
ctf_plan_tenum updated once per second (not per-frame) by a single coordinator function. All downstream role and behavior decisions read from this.Transition triggers:
FULL_ATTACK: score deficit ≥ 2 caps AND time remaining ≤ 3 minutesDEFEND: team is ahead by ≥ 2 caps AND time remaining > 3 minutesRUSH: enemy flag transitions to dropped state (broadcast immediately)STALL: team has enemy flag AND is ahead — carrier circles/avoids rather than rushing homeBALANCED: default / fallback2. Role Assignment
A coordinator runs once per second (not per-frame) and slots each bot into a role based on the current plan and team size. Bots in
BOTLIB_CTF_Goals()check their assigned role before evaluating goals.Role caps by plan (example with 4 bots):
Role counts should scale with team size. Bots assigned as
ROLE_ESCORTshadow the flag carrier; bots assignedROLE_DEFENDERuse the defender AI below.3. Defender AI
When assigned
ROLE_DEFENDER, bots should:4. Escort AI
The current escort behavior is opportunistic (10-second window if the bot happened to be heading for the flag).
ROLE_ESCORTshould be intentional and persistent:5. Alternative / Safer Pathing for Flag Carriers
When a bot is carrying the flag, prefer less-direct paths over the shortest path:
6. Team-Only Chat Communication
The existing radio system and chat queue infrastructure in
botlib_communication.cis ready to use. CTF-specific messages must usesay_teamonly — neversay/say_all. The chat queue already has delay logic; CTF messages must also respect a per-bot cooldown (suggested: no CTF-context message more than once every ~15 seconds per bot) and a team-wide cooldown on duplicate messages (e.g., if one bot already announced "I have the flag," others should not repeat it for ~20 seconds).Proposed message triggers:
Messages should have multiple candidates per trigger (chosen randomly) to avoid sounding robotic.
Human chat parsing: Scan incoming
say_teamtext from human teammates for keywords (defend, attack, escort, rush) and factor them into plan evaluation — not as overrides, but as weighted input.7. Weapon Coordination (Stretch Goal)
At plan transitions or capture events, the active plan can recommend a weapon loadout preference for next spawn (e.g., sniper-heavy for DEFEND, MP5 for FULL_ATTACK). Bots would apply this as a soft preference for item pickup rather than a hard rule.
Implementation Order
Dependencies flow top-to-bottom — each item requires the one above it:
Affected Files
src/action/botlib/botlib_ctf.c— primarysrc/action/botlib/botlib_ai.c— role check integrationsrc/action/botlib/botlib_communication.c— CTF chat messagessrc/action/botlib/botlib.h— new enums and struct fieldssrc/action/acesrc/acebot.h— bot struct additions