Skip to content

titaniummachine1/Medbot

Repository files navigation

LMAOBox-Navbot

Pathfinding and navigation bot made with LMAOBox Lua.

Support: https://dsc.gg/rosnehook

Credits for original code: Inx00

For an overview of the lightweight nav-mesh rendering method, see FAST_NAV_DRAWING.md.

How to use

First, go to https://nodejs.org/ and download and install the stable version of Node.JS

Next, go to releases and download the source code (zip) and unzip it to any place you want.

After that, you open the Node.JS command prompt and execute

npm install luabundle

Once it has installed, run Bundle.bat and after it finishes run BundleAndDeploy.bat

When those batch scripts have finished, run MakeNavs.bat to get nav meshes for all causal maps

Start TF2, inject LMAOBox (not beta build), and go into a map.

Go to the Lua tab in the menu and load "MedBot.lua"

Enjoy NavBot on LMAOBox!

I'll update you shortly with a breakdown.

Team Fortress 2 Navigation Mesh Area Data and Bot Pathfinding

Overview of TF2 Navmesh and Nav Areas

Team Fortress 2 (TF2) uses a Navigation Mesh (.nav) file for each map to define all walkable surfaces for bots. The navmesh is composed of many navigation areas (or nav areas), each representing a convex region of walkable space. A .nav file stores data for each nav area, including its geometry, connectivity, and various annotations that guide bot behavior. TF2’s navmesh format (version 16, sub-version 2) extends the base Source Engine format with TF2-specific attributes.

Below we break down all the data contained in a single nav area and then explain how TF2 bots perform pathfinding on the navmesh.

Data Contained in a Single Navmesh Area

Each nav area in TF2’s .nav file is defined by a structured set of fields. These fields include spatial dimensions, adjacency links, flags for navigation behavior, tactical spots, and other metadata. Understanding these allows map designers and AI developers to utilize navmeshes for bot navigation, debugging, and even custom rendering of navigation overlays.

1. Area Geometry (Position and Size): Each nav area is essentially a rectangular region on the ground (usually axis-aligned in the XY plane). The file stores the 3D coordinates of the area’s opposite corners: the North-West corner and the South-East corner (nwCorner and seCorner vectors). In addition, it stores the height (Z coordinate) for the other two corners (North-East and South-West) as separate values. This means each of the four corners of the area can have its own Z value, allowing the area to represent sloped surfaces or stairs (a nav area can be tilted, approximating ramps). For example, nwCorner has an (x,y,z) position, seCorner has an (x,y,z), and the NorthEastZ and SouthWestZ fields give the Z heights of the remaining corners. From these, one can derive the complete polygon (usually a convex quadrilateral) that outlines the area on the ground. This spatial information is used both for rendering the mesh (e.g. in nav_edit overlays) and for in-game computations like finding points on the area or checking if an entity is inside it.

2. Connectivity (Adjacency Links): Nav areas form a graph via connections to neighboring areas. Each area stores up to four connection lists corresponding to the four cardinal directions – North, East, South, West. In the nav file, this appears as an array connectionData[4], where each element holds a count and list of adjacent area IDs in that direction. An adjacent area is typically one that shares a border or overlaps along that side. For example, if area A has a neighbor B directly to its north, B’s ID will appear in A’s north connection list, and vice-versa for A in B’s south list. Connections can also encode one-way links in special cases – e.g. a drop-down ledge might connect only one-way (the bot can drop down but not climb back up without a jump). These directional connections allow the AI to query what areas can be reached from a given area.

Ladder Connections: In addition to planar adjacencies, nav areas can be connected via ladders. Ladders are stored as separate objects in the nav file (with their own IDs and endpoints), but each area that is at the top or bottom of a ladder maintains references to that ladder. In the area data, there are two sequences for ladder IDs: one for ladders leading up from this area, and one for ladders leading down. For example, if area A is at the bottom of a ladder, that ladder’s ID will appear in A’s “up” ladder list, and the area at the top of the ladder will list the same ladder in its “down” list. The ladder definitions (global in the nav file) include detailed info such as the ladder’s position, length, orientation, and which nav areas are connected at the top and bottom. Bots treat ladder transitions as special movement links – when pathing, climbing a ladder is considered moving from the bottom area to the ladder, then to the top area. The ladder data provides the points where bots should mount/dismount and which directions are accessible at the top (forward, left, right, behind).

3. Navigation Attributes (Area Flags): Each nav area has a bitfield of flags that affect bot navigation behavior on that area. These area attributes (sometimes called NavAttributes) are general purpose flags (common to all Source engine games) that can be toggled during nav mesh editing and stored in the file. They influence how bots move through or use the area. In TF2’s nav format (version ≥13), this attribute field is 32 bits, allowing many flags. Notable base attributes include:

  • CROUCH: Bots are forced to crouch when moving through this area. This is used if the area is low-ceiling or requires crawling (e.g. a vent).
  • JUMP: Indicates bots must jump to traverse from this area to a higher adjacent area. It’s typically set on areas right before a ledge or obstacle that requires a jump. It also signals that this area isn’t a valid hiding spot since it’s unstable footing.
  • PRECISE: Tells bots to move precisely through this area, avoiding any path simplification or obstacle detouring. Marking an area as precise prevents bots from “skipping” it or cutting corners – useful on narrow catwalks, tricky jumps, or any place where leaving the center of the area could cause falls. (In practice, TF2 bots largely ignore most area flags except PRECISE; TF2 and L4D’s AI only actively honor the precise flag among base attributes.)
  • NO_JUMP: Bots should not jump in this area. This can be used to discourage unwanted jumping (e.g. to prevent bots from bunny-hopping or jumping in low-ceiling spots).
  • STOP: Bots will momentarily pause upon entering this area. Map makers use this to force bots to slow down (for example, to line up a subsequent tricky maneuver or just to humanize movement).
  • RUN / WALK: These flags explicitly tell the bot to run (move at full speed) or walk (move slowly) through the area. In Counter-Strike these control noise (running vs. walking silently), but in TF2 they might not be relevant (TF2 bots typically always run unless role-playing a walk).
  • AVOID: Marks this area as something to avoid unless absolutely necessary. This increases the traversal cost so that bots pick alternate routes unless this is the only path. (Avoid is mainly utilized in CS:S/CS:GO; TF2’s default bots might not leverage it, as their AI doesn’t heavily use this flag by default.)
  • TRANSIENT: Indicates the area may become blocked intermittently. This was intended for areas under moving doors or areas that change – the AI could periodically re-check if the area is now impassable. (In practice this flag was experimental/unimplemented.)
  • DONT_HIDE: Bots should not use this area for hiding spots. For TF2, this prevents AI (like Snipers or Spies) from considering this area as a place to wait in ambush.
  • STAND: If a bot uses this area as a hiding spot, it should remain standing (don’t crouch). This helps ensure, for example, a Heavy bot won’t crouch in cover (since crouching might reduce their firing effectiveness).
  • NO_HOSTAGES: (Not relevant to TF2; used in CS for hostage navigation) Marks that hostages or NPC followers shouldn’t path through here.
  • STAIRS: An area automatically marked as stairs (green grid overlay in the editor). This is not a user-set flag but rather a generated hint – it tells bots the area is a staircase so they should simply walk up (don’t attempt special jumps).
  • NO_MERGE: Used during nav mesh generation to prevent this area from merging with others. (This is an editing aid – it remains as a flag but mainly affects the mesh creation rather than bot behavior at runtime.)

These base attributes are stored in the area’s AttributeBitField and can be marked or cleared via console commands (e.g. nav_mark_attribute nav_jump, etc.). However, note: TF2’s bots (which use the NextBot system) do not obey many of these older flags, except for PRECISE. According to Valve’s documentation, in L4D and TF2 “only the precise area attribute is considered” by the AI, while other base attributes exist for completeness or mod use. In practice, mapping these flags in TF2 is mainly for modders or for using common nav tools, but the stock TF bots won’t necessarily crouch or slow down just because an area is flagged (they rely more on dynamic queries like “is there enough space?”).

4. TF2-Specific Nav Attributes: In addition to the base flags above, TF2 defines a set of “TF Nav Attributes” tailored to its game mode logic. These are stored as two additional 32-bit integers in each area’s data (the custom area data for sub-version 2). TF2’s nav attributes are used to mark areas for team-specific or objective-specific behaviors, and many of them are toggled by the game as objectives are captured or gates open/close. Some important TF2 nav attributes (prefixed with TF_NAV_) include:

  • Spawn Room designations: TF_NAV_SPAWN_ROOM_RED and TF_NAV_SPAWN_ROOM_BLUE mark areas that lie within RED or BLU spawn rooms, and TF_NAV_SPAWN_ROOM_EXIT marks the immediate exit doorway from a spawn. These flags allow the game to know which areas are “inside spawn” (typically inaccessible to the other team). For example, enemy bots will not enter a spawn-room area. Spawn exits are also used to handle spawn door logic (see one-way doors below).
  • Resource pickups: TF_NAV_HAS_AMMO and TF_NAV_HAS_HEALTH are automatically set on areas that contain ammo packs or health kits. This helps bots prioritize resupply – they can find the nearest nav area with health or ammo when low, since those areas carry a flag indicating the presence of an item spawn.
  • Control Point zones: TF_NAV_CONTROL_POINT marks that a Control Point entity is located on that nav area. Bots can use this to identify objective locations (e.g. where to stand to capture/defend a point).
  • Sentry danger areas: TF_NAV_BLUE_SENTRY_DANGER and TF_NAV_RED_SENTRY_DANGER denote areas from which a team’s sentry gun is visible (and thus dangerous to the opposing team). For instance, if a RED Engineer has built a sentry that covers a courtyard, the nav mesh can mark certain areas as blue_sentry_danger (meaning a BLU bot standing there would be exposed to a RED sentry’s fire). These tags are dynamic – as sentries are built or destroyed, the game updates the nav areas accordingly so bots know which zones are covered by enemy sentry fire.
  • Setup gates (spawn gates): TF_NAV_BLUE_SETUP_GATE and TF_NAV_RED_SETUP_GATE mark areas that are blocked for one team during the setup phase of a round (e.g. in Attack/Defend maps). During the pre-round setup time, these areas (typically the area immediately outside the enemy’s spawn or in front of a gate) are off-limits until the gates open. The game uses these flags to prevent bots from attempting to pass through doors that are initially closed. Once setup time ends, the engine unblocks these areas.
  • One-way doors: TF_NAV_BLUE_ONE_WAY_DOOR (and the RED equivalent) flag an area that acts as a one-way passage for a specific team. For example, many TF2 maps have doorways that only open for one team (and are blocked for the other). An area behind a one-way door might be marked TF_NAV_BLUE_ONE_WAY_DOOR meaning BLU team can traverse it freely, but RED bots should consider it blocked. The game enforces this by closing the door for one team and marking the nav area appropriately so pathfinding will treat it as non-navigable for that team.
  • Capture-blocked areas: TF_NAV_BLOCKED_UNTIL_POINT_CAPTURE and TF_NAV_BLOCKED_AFTER_POINT_CAPTURE are used on maps with multiple stages or control points that unlock routes. They work in conjunction with modifier flags TF_NAV_WITH_SECOND_POINT, ...THIRD_POINT, etc.. Essentially, these allow mapping nav areas to specific control point states. For example, an area might be flagged BLOCKED_UNTIL_POINT_CAPTURE (meaning it’s initially blocked), and also flagged WITH_SECOND_POINT. This would mean “this area is blocked until the second point is captured.” When the specified point is captured in-game, the engine clears the blocked flag so bots can start using that area. Conversely, BLOCKED_AFTER_POINT_CAPTURE would mean the area becomes blocked once a point is taken (for instance, areas in the previous stage of a map that attackers shouldn’t go back into once they’ve moved on).
  • Sniper/Sentry spots: TF_NAV_SNIPER_SPOT and TF_NAV_SENTRY_SPOT identify areas deemed good for snipers to stand or engineers to build sentries. In Mann vs. Machine, these might be pre-placed to guide AI Snipers and Engineers. However, outside of MvM, TF2 doesn’t automatically use these flags (sniper bots typically use func_tfbot_hint entities or predefined “theater” positions rather than solely relying on nav area flags). The nav file can carry these markers (map authors or tools can set them), but by default they’re noted as “currently unused” in TF2’s code.
  • Escape route markers: TF_NAV_ESCAPE_ROUTE (and _VISIBLE) are unused in TF2 but appear to be vestigial flags for a “raid mode” or special scenarios. They can be ignored for standard TF2 play.
  • No Spawning: TF_NAV_NO_SPAWNING prevents spawning bots in that area. While TF2 usually spawns bots at spawn points, this could be used in scenarios with dynamic spawning or to ensure certain areas (perhaps map edges) don’t get chosen by AI director logic for spawning NPCs (if any).
  • Bomb drop area: TF_NAV_BOMB_CAN_DROP_HERE is specific to Mann vs. Machine – it flags areas where the bomb carrier can drop the bomb safely. MvM bots will drop the bomb when killed, and this ensures the dropped bomb is in a reachable spot for other bots to pick up (usually along the intended path to the hatch).
  • Door blocking rules: TF_NAV_DOOR_NEVER_BLOCKS and TF_NAV_DOOR_ALWAYS_BLOCKS fine-tune how nav areas interact with door entities. For example, some doorways might never be considered obstacles (the area is marked never_blocks, perhaps because the door is decorative or opens instantly), versus always_blocks which means if a door entity that overlaps this area is closed, treat area as entirely blocked. These help in maps with complex doors or gates.
  • Unblockable area: TF_NAV_UNBLOCKABLE indicates the area should never be marked as blocked (possibly used to override any dynamic blocking—e.g. perhaps the final capture point area is always considered open even if a door graphic is closed, etc.).

All these TF2-specific flags are stored per area and can be visualized with the tf_show_mesh_decoration 1 console command, which color-codes areas by their TF attributes. They are often set up by Valve in official maps to control bot flow. For instance, when a capture point is taken, the game will internally flip the relevant nav area flags (such as clearing BLOCKED_UNTIL_POINT_CAPTURE on routes that should now open, or adding BLOCKED_AFTER_POINT_CAPTURE on areas that should close off). This ensures bots dynamically adjust their path choices as the game state changes (e.g. not trying to use a door that’s now closed or a route that only opens later).

5. Hiding Spots: Within each nav area, the generator may have placed one or more hiding spots. A hiding spot is a specific point in the area (with X, Y, Z coordinates) where a bot can take cover or ambush enemies. Each hiding spot has an ID and a one-byte attribute flag indicating the type of cover it provides. The four defined hiding spot attributes are: IN_COVER (the spot is in a corner with solid cover protecting it), GOOD_SNIPER_SPOT (the spot provides a decent long-range view for sniping), IDEAL_SNIPER_SPOT (an even better vantage point, e.g. far visibility or wide view), and EXPOSED (the spot is in the open, like a cliff or ledge, offering little cover). These are bit flags, so a single hide spot could potentially be marked with multiple qualities (e.g. in cover and a good sniper angle).

In TF2, hiding spots are primarily used by Spy bots – TF2’s AI spies will lurk at hiding spots to ambush targets or wait while cloaked. Other classes generally don’t explicitly “hide” at these spots in regular play. (In CS:Source/CS:GO, hiding spots are used for bots to take cover or camp, but TF2’s combat is more dynamic so only spies and sometimes snipers use specific lurking positions). The nav editor can display hiding spots as small colored markers in each area. When nav_analyze is run, it automatically generates hiding spots by looking for concave corners and cover within the area. Each hiding spot’s coordinates can be used for debugging (e.g. to teleport bots or visualize cover nodes) or by AI logic to choose safe places.

6. Encounter Paths (Waypoints): The nav file format includes encounter paths and encounter spots – these are sequences of intermediate waypoints that bots (in some games) use to coordinate movement when paths intersect. An encounter path in the data is essentially a precomputed route from one nav area to another, with “spots” along the way. In Counter-Strike, this was used for analyzing likely encounter points between teams (e.g., routes where bots might meet each other, to decide areas to cover). However, TF2 does not use encounter path data – they are generated by the nav analyzer tool but ignored by TF2’s bot code. In TF2’s nav version, the encounter sections remain present for compatibility but are usually empty or can be safely ignored. (Valve’s wiki explicitly warns that encounter paths/spots are only used in CS:S/CS:GO, not in TF2.) Thus, while the nav file can contain encounterPathCount and associated encounterPaths, these are not “usable data” for TF2’s AI, but they might be present if the nav file was generated with the nav_analyze command without the TF2 quick-skip option. For completeness: an encounter path entry links an “entry area” to a “destination area” along with a series of intermediate area waypoints (encounter spots) and directions – but again, TF2’s bots do not utilize this.

7. Place Names (Area “Place” ID): Each nav area can be assigned a “place name”, which is a human-readable label for regions of the map (e.g. “Bridge”, “Spawn Room”, “Courtyard”). In the nav file, there is a directory of place strings (stored once in the file), and each area has a PlaceID that indexes into that list. By default, most areas might be “Unnamed” (0 index), but map editors can set places via the nav_place command. In TF2, place names are not as crucial as in CS:GO (where bots radio call “Enemy at Bombsite A”, etc.), but they can still be used for debugging and mod scripting. For example, an AI script could ask “is the bot in area with Place ‘Lobby’?” to make decisions. TF2’s training maps do define some place names for tutorial prompts. The navmesh editing tools allow adding and merging place names, and they are useful for designers to communicate or visualize sections of the navmesh.

8. Visibility and “Area Binds”: TF2’s nav version introduced a concept of Area Bindings for visibility. Each area can list a number of area binds, which associate another Area ID with a small bitfield describing the visibility relationship. In simpler terms, the nav mesh can store precomputed visibility info: e.g., “From area 25, area 30 is visible (line of sight) and area 31 is not,” or “area 25 can see area 30 only partially.” The exact meaning of the attribute bits in an area-bind isn’t fully documented, but it “determines the visibility type” between the parent area and the target area. This is likely used as an optimization so that bots can quickly determine if an enemy standing in one nav area could possibly see/shoot a bot in another – without doing ray casts at runtime for every pair. Area binds were added in nav version 16 (TF2), indicating Valve added this for TF2 or L4D. Along with area binds, there is an InheritVisibilityFromAreaID field on each area. This suggests that some areas can defer to another area’s visibility set. For example, a very small doorway area might “inherit” visibility from a larger adjacent room area, to simplify computations (since if you’re in the doorway, your view is almost the same as being in the room itself). Essentially, areas can be clustered for vis calculations.

For AI, this vis data is mainly used in tactical decisions (e.g. deciding line-of-sight coverage, sniper targeting, or avoiding areas visible to sentries). It’s also useful in debugging: TF2 has a command tf_show_visibility (or similar) that can highlight what areas are visible from a selected area. When nav meshes are analyzed (nav_analyze), the engine likely performs a series of line-of-sight checks between areas and stores results in these area bind structures. This helps bots avoid redundant checks at runtime and can be used for things like finding sniper positions that cover a control point (by looking up which nav areas see the nav area containing the control point).

9. “Earliest Occupy Time” (Team Distances): Each nav area stores two floating-point values called Earliest Occupy Times (one for each team, typically). These values represent the earliest time (in seconds) it would take for a member of each team to reach that area from their respective spawn. In other words, it’s a precomputed travel time distance for RED and BLU. These times are computed during nav analysis by essentially performing a breadth-first search or Dijkstra propagation from each team’s spawn points through the nav mesh (taking into account walking speeds) to find how long it takes to get to every area. They are mainly used for game design and bot decisions like determining “frontline” and map control. For example, if BLU can reach area X in 10 seconds but RED in 5 seconds, area X might be considered initially under RED control until BLU pushes up. In Counter-Strike, similar timings are used to figure out likely encounter points between Ts and CTs. In TF2, these values could be used by bots to decide where to position early in a round or which areas are contested versus safely owned (though how much the default TF2 bots use this is unclear – it might be more for the game director logic in PvE modes). At the very least, it’s data that can be extracted for analysis or debugging balance (map authors could see if travel times are skewed). The nav file stores these as EarliestOccupyTimes[2] per area (for team 1 and 2). These fields were added in older nav versions (v8) when CS:S bot logic was created, and TF2 retains them.

10. Light Intensity Data: Each nav area has four floats for light intensity, one for each corner. This is basically the ambient light level sampled at the area’s corners in the map. It’s not typically used by TF2 bots, but in some games AI might use it to decide how visible they are (e.g. in stealth games or Condition Zero, bots avoided brightly lit open areas if possible). In Left 4 Dead, the AI director might spawn witches in dark areas, etc. In TF2, this is likely unused for gameplay, but the data exists (perhaps carried over from the engine). It could potentially be used in mods or for debug visualization (e.g. highlighting dark corners). Since TF2 isn’t a stealth game, this data can largely be ignored, but it is “extractable” from a nav area if needed (each corner’s light value).

11. Miscellaneous: There are a few other fields mostly for internal or unused purposes:

  • Approach Spots: A legacy concept (from older nav versions) that was removed by the time TF2’s version came around. These were small markers for how a bot can approach this area from another area (possibly for coordinating complex moves). TF2’s nav version (16) does not include approach spots (they were removed in version 15), so they will be absent in TF2 nav files.
  • Custom Data Placeholder: The nav file format has a provision for custom data sections (e.g. after all areas or per area), but in stock TF2 these are not used (besides the TF attributes already mentioned).
  • Magic Number, Version, BSP size: at the file header level, not per area. The nav file begins with a magic constant 0xFEEDFACE and a version number (16 for TF2) and a saved BSP file checksum to ensure the nav corresponds to the right map file. These aren’t per-area but relevant if you’re parsing the file.

Summary of an Area’s Data: To recap, a single nav area in TF2’s .nav contains:

  • Unique ID of the area.
  • Bitfield flags (base attributes like crouch/jump/etc.).
  • Corner coordinates (NW and SE corners, plus height of NE and SW corners).
  • Neighbor connections (lists of adjacent area IDs for N, E, S, W sides).
  • Hiding spot count and list (each with ID, position, and type flags).
  • (Approach spot count & list – not used in TF2 >=v15).
  • Encounter path count and list (each with entry area, exit area, intermediate spots) – present but unused by TF2 bots.
  • Place ID (index to place name).
  • Ladder links: two lists of ladder IDs (areas accessible via ladder up or down).
  • Earliest occupy times (two floats for team 1 and 2).
  • Light intensities (4 floats for each corner).
  • Area bind count and list (visiblity info to other areas).
  • Inherit visibility from area ID (if this area piggybacks vis data from another).
  • TF2-specific attribute flags (two 32-bit ints, combined as one 32-bit in Kaitai’s view or sometimes conceptually one 64-bit set) covering all the TF_NAV_* flags discussed.

All these pieces together constitute the full extent of data one can extract per nav area. This data is used by the game’s bot navigation system for decision making and by developers for debugging. For example, running nav_edit 1 in TF2 will render the nav areas in-game (drawing the polygons), and nav_show_area_info will print out many of these details (area ID, flags like “JUMP, PRECISE”, hide spots, etc.) on the screen when you point at an area. TF2 also has specialized debug commands for its additional attributes (e.g. tf_show_mesh_decoration to color TF attributes, tf_show_blocked_areas to highlight areas blocked to each team). This makes the nav mesh a powerful tool not only for AI pathfinding but also for debugging AI behavior (you can literally see what the bot sees as walkable and what it considers blocked) and for custom rendering (e.g. heatmaps of travel time or coverage can be built off the nav areas).

With a firm grasp of what’s in the navmesh, we can now explain how the TF2 bots use this data to navigate.

Pathfinding Mechanics for TF2 Bots on the Navmesh

TF2 bots (often referred to as “TFBots”) rely on the navmesh to make decisions about movement. Under the hood, the navmesh provides an abstraction of the walkable space as a graph of nodes (nav areas) connected by edges (adjacency or ladder links). The actual pathfinding algorithm used is essentially A* (A-star), a common heuristic search for shortest paths. The Source engine provides utilities for this – in fact, Garry’s Mod (which uses Source navmeshes) exposes a similar API where one can see the A* process on CNavAreas.

1. Graph Nodes and Edges: Each nav area is treated as a node in a graph. When a bot needs to find a path from point A to point B, the game will determine which nav area contains A (start area) and which contains B (goal area). Then it performs a graph search over nav areas. Two areas are considered connected in this graph if you can walk from one to the other directly – this is precisely what the connection lists and ladder links define. So graph edges exist for:

  • Adjacent walkable areas: If area X is listed as adjacent (north/east/south/west) to area Y, then there is a graph edge between X and Y. This implies the bot can simply walk across the shared boundary from one to the other.
  • Ladder connections: There’s a conceptual edge between an area at the foot of a ladder and the area at the top of that ladder. Climbing the ladder is an action, but for path planning it’s treated as moving from one node to another via a special edge (with a higher cost reflecting climb time). The navmesh doesn’t directly list an edge from bottom-area to top-area, but the pathfinding logic knows to check ladder links: e.g., if current area has a ladder Up list containing ladder L, and L’s top connects to area Z, then from current area we can “go to Z via ladder L”. Similarly, top to bottom via Down list.

The cost (weight) of moving along an edge is generally the distance the bot would travel or the time it would take. For flat adjacent areas, this is roughly the straight-line distance between area centers or perhaps between the closest points of the two areas. In practice, Source games often use the center-to-center distance as the edge weight approximation. This is an easy heuristic: treat each area as if the bot will traverse to its center, then to the neighbor’s center. If areas are reasonably small, this works fine. If one area is very large or oddly shaped, a more precise cost would be the minimum distance between any point in area X to any point in area Y (which often is the distance across the shared edge). But since nav areas in TF2 maps are usually not huge and share significant edge spans, center-to-center is a decent metric.

In the open-source GMod example of A* on navmeshes, the step cost used is current_area.GetCenter().Distance(neighbor_area.GetCenter()) – effectively Euclidean distance between area centers. The heuristic (for A*) is similarly the distance from a neighbor’s center to the goal area’s center. This heuristic is admissible (straight-line distance underestimates actual path distance on the mesh) and guides the search efficiently.

Special cases: if an area has the “AVOID” flag and the game honors it, the cost to enter that area might be inflated (so A* will only go through it if no much shorter path exists). In MvM, func_nav_avoid and func_nav_prefer triggers temporarily modify costs on certain areas (e.g. to force bots along a certain route). These essentially multiply or add to the cost for those areas. For example, Valve often sets up multiple possible paths for MvM robots and uses func_nav_prefer on the chosen path and func_nav_avoid on the others to weight the random selection. The pathfinding algorithm itself doesn’t change – it still does A* – but the cost of entering certain nav areas is changed by those triggers. (In the nav mesh, a func_nav_avoid sets a “danger” or cost value internally, and there is a nav attribute bit NAV_MESH_FUNC_COST that indicates an area has a custom cost modifier. TF2 likely uses that to multiply the distance cost.)

Another special case is blocked areas. If an area is marked as currently blocked (e.g. a spawn room door is closed to this team, or a func_nav_blocker entity is active on it), the pathfinder will treat that area as impassable. In implementation, this might mean the area is simply omitted from adjacency for that team or given an infinite cost. There is even a nav attribute bit for NAV_MESH_NAV_BLOCKER (0x80000000) which is set on areas currently blocked by a nav blocker entity. TF2’s spawn room doors automatically mark areas behind them as “Blocked” state for the opposing team. The pathfinding will not consider edges through blocked areas at all, effectively removing them from the search space until unblocked.

*2. A Pathfinding Process:** The bot navigation system runs A* from the start area to the goal area. This algorithm will explore connected nav areas, accumulating cost (distance) and using a heuristic to guide the search. As it runs, it keeps track of the **“came from”** links to reconstruct the path once the goal is reached. If no path is found (goal not reachable), it returns failure; if the start and goal are the same area, it trivially succeeds.

Notably, the navmesh’s adjacency lists already limit which moves are possible (so it inherently prunes out moves that are invalid due to barriers or height differences beyond what nav generation allowed). The heuristic is typically the straight-line distance (in 2D or 3D) from the current area’s center to the goal’s center. Since bots can’t fly, using horizontal distance is usually fine (though for long ladder climbs, a purely horizontal distance underestimates time – sometimes a slightly tweaked heuristic might be used that accounts for vertical distance or ladder penalty).

During the search, areas can be skipped or revisited; the algorithm maintains an open list (fringe) and closed list of areas as usual. Valve’s nav code has utility functions like CNavArea::AddToOpenList(), etc., which GMod demonstrates. When the search completes, it yields a sequence of nav areas from the start to the goal.

It’s worth noting that the nav mesh path is abstract – it doesn’t yet give exact coordinates the bot will walk, just which areas to go through. By default, the engine will give the bot a list: e.g. Area 12 → Area 37 → Area 45 → ... → Area 88 (goal). The bot must then translate that into actual movement.

3. From Nav Areas to Actual Movement (Area Centers vs Edges): Once the sequence of nav areas is obtained, the bot needs to move through them in order. The simplest strategy (which early Source bots used) is to aim for the center point of the next area and travel towards it. Indeed, the nav areas have a GetCenter() method and the path nodes are often represented by area centers. This approach ensures the bot fully leaves the current area and gets well into the next area. However, it’s not always the optimal geometric path – going center-to-center can cause zig-zag patterns or unnecessary detours, especially if areas are large or laid out diagonally.

To improve efficiency, the TF2 bot navigation employs some path smoothing. One sign of this is the PRECISE attribute: if PRECISE is not set, bots are allowed to “skip” areas or cut corners when possible. What this means is that after obtaining the rough path (list of areas), the bot will try to streamline the path by not strictly visiting the center of every intermediate area if it isn’t needed. For example, suppose the path goes through areas A → B → C in a straight hallway. The bot could potentially see from area A straight to area C (if B is in between with no obstruction). A non-precise pathfinder might realize that and go straight toward area C, effectively cutting across the corner of area B instead of going through B’s center. The nav mesh being convex polygons allows the use of the “string pulling” or funnel algorithm common in navmesh pathfinding: one can treat the sequence of convex areas as a corridor and compute the shortest polygonal chain through them. Valve’s implementation isn’t explicitly documented as using the funnel algorithm, but the behavior of bots and the existence of the precise flag suggests they do some form of line-of-sight skipping. For instance, if area B is just a narrow connector, a bot might enter B and immediately head for C without squarely moving to B’s center.

In Source engine terms, bots often pick the next significant waypoint. They might use functions like GetClosestPointOnArea() to find where to aim on the border towards the next area. If two areas share an edge, the ideal crossing point is usually somewhere along that edge aligned with the bot’s current trajectory. The navmesh doesn’t explicitly store a “preferred portal point” for each connection (since any point along the shared edge is valid to cross), so the bot calculates it. Typically, the bot will project its current position towards the next area and find the intersection point on the shared boundary. If the movement is not too constrained, heading to the next area’s center is a safe default that guarantees entry. But modern TF2 bots exhibit fairly natural movement that doesn’t look like sharp center-to-center turns, indicating there is some continuous steering.

To summarize this behavior:

  • If an area is marked PRECISE: the bot will navigate through it deliberately, likely touching its center or at least ensuring it fully covers it. This is used for narrow or dangerous areas (like a narrow bridge) to prevent the bot from trying to “cut the corner” and possibly falling off or hitting an obstacle.
  • If not precise: the bot can attempt to skip ahead if the path is visible. In practical terms, as the bot moves, it can look at the next-next area on the path and see if it has line of sight (no walls) from the bot’s current position. If yes, it can skip directly to that area, removing an unnecessary intermediate turn. This is akin to a line-of-sight check to skip waypoints. Many game AI implementations do this post-processing on an A* path.

Another aspect of transitions is how bots orient themselves through doorways or tight spaces. TF2’s nav areas often tightly fit door frames, so going center-to-center is fine. But if a door is narrow, the nav generator might have placed a small area in the doorway. The bot will indeed go through that doorway area (cannot skip it because walls on each side block line-of-sight). The inherit visibility and area binds we discussed help the bot know that once in the doorway, it can see both connected rooms. The bot will treat the doorway like any other area, but might slow down or ensure alignment if needed (though there is no explicit “doorway slow” flag except STOP if manually set).

4. Handling Elevation Changes – Stairs, Slopes, Jumps, Ladders: Nav areas and the pathfinder handle vertical movement in specific ways:

  • Stairs and Ramps: These are usually built as a series of nav areas that increment in Z. However, the nav generator flags them as STAIRS (green line overlay) and generally keeps stairs as walkable continuous areas rather than a bunch of tiny steps. Bots don’t need to do anything special for stairs except just walk forward – the engine’s movement code will automatically climb up small step heights (Source engine characters can step up minor height changes up to a certain height without jumping). The nav areas on stairs often overlap multiple actual steps, creating a ramp in nav terms. The STAIRS flag is mostly informational (to prevent the generator from splitting them in weird ways, and to signal the bot that it doesn’t need to jump). When pathfinding, stairs areas are just nodes like others; the vertical difference is encoded in the distances. Since running up stairs might slow movement slightly due to gravity, the cost might be a tad higher than flat distance, but A* doesn’t explicitly account for that except via distance and maybe a small constant. Bots handle stairs by just moving forward; if the steps are unusually tall (above step height but below jump height), sometimes nav generation fails and such cases require marking the area with JUMP or tweaking the mesh.

  • Slopes: Sloped terrain is handled by nav areas with different corner heights, as mentioned. Bots treat it no differently than flat ground – the coordinates smoothly change in Z as they cross the area. Since slope traversal speed is the same as flat in TF2, no special cost or behavior is needed. The navmesh essentially “bakes in” the slope as part of the area geometry, so the bot’s path will just go through those sloped areas. One thing to watch is extremely steep slopes: navmesh might not generate areas on slopes beyond a certain angle (they’d be unwalkable), or they might require a JUMP connection. But moderate slopes (e.g. the ramps up to capture points) are just nav areas with one corner higher than the other. The bot’s movement code (NextBot locomotion) will try to move in a straight line toward the target point; the collision with world geometry will naturally elevate the bot along the slope.

  • Jumps (Gaps/Cliffs): If a bot must jump to get from one area to another (for example, a small gap or hopping down from a ledge), the nav mesh can represent this in a couple ways. For upward jumps: The higher area may be marked with the JUMP attribute on its lower neighbor side, meaning “to enter here, bot needs to jump”. The pathfinding algorithm itself can still consider that a connection (because nav generator usually still links the areas), but the bot’s Action for that transition will include a jump. TF2 bots are programmed to attempt a jump when traversing into a JUMP-flagged area. If multiple paths exist and one requires a jump, a designer might mark those areas as avoid or give them slightly higher cost so that bots prefer non-jump paths unless the jump route is significantly shorter. For downward drops: The navmesh may sometimes have a one-way connection (if the drop isn’t too high to injure the bot). In many cases, large drops are not linked in navmesh at all (to prevent bots from suicidally dropping). But if a drop is survivable and intended (e.g. dropping off a ledge to a lower floor in 2Fort), the nav mesh might connect the upper area’s south edge to the lower area’s north edge, but not vice versa. The engine can also mark such connections with a drop-down flag (there isn’t a specific nav flag for “drop” aside from one-way link, but “cliff” attribute exists to denote areas near a big drop). Bots deciding to go down might just see it as a neighbor and do it (the movement code will let them fall). If a safe drop requires a crouch or careful movement, usually it’s avoided or handled as a ladder or special case.

    • The pathfinding cost for a jump or drop may not be fully captured by center distance. There might be a constant penalty added for jumps (to discourage unless beneficial). Valve’s CS bots, for example, had logic to slightly prefer non-jump paths for smoothness. TF2 bots in combat might actually be more willing to drop down if chasing an enemy, but when simply pathing to a goal, they’d treat a long way around as possibly better than a dangerous drop.
  • Ladders: Ladders are a distinct nav element. When A* explores neighbors of an area, it must also consider ladder moves. For example, from bottom area X, the algorithm sees ladder L in X’s “ladder up” list; it will then consider the area at the top of L (let’s call it area Y) as a neighbor of X for pathfinding purposes. Similarly, from a top area that has a ladder down, it considers the bottom area neighbor. The cost to traverse a ladder can be calculated as the ladder’s length or vertical distance divided by climb speed (i.e., time to climb). Ladder climbing is slower than running, so effectively a ladder edge is “longer” than a horizontal edge of the same spatial distance. The nav file includes the ladder’s length (vertical extent), so the pathfinder can use that. Typically, ladder traversal speed is known (e.g. X units/sec), so cost = length (or some multiple). Bots will avoid ladder routes if a faster walk route exists due to this cost. However, in many maps ladders might be the only way up certain spots (e.g. the towers in Double Cross).

    • When a bot reaches a ladder during movement, the navigation code switches to a ladder climbing mode. Pathfinding only gives it the sequence: e.g. Area 10 (bottom) → via Ladder 3 → Area 11 (top). The bot then aligns with Ladder 3’s bottom point and climbs. The nav area at the ladder’s top often does not overlap the ladder; rather the ladder has specific “mount” points (top and bottom). Valve’s nav ladders have fields for the top exit connections – forward, left, right, behind area IDs – which indicate where a bot can go when it finishes climbing. For example, if a ladder top leads onto a ledge area 5, that would be topForwardArea=5 (assuming forward of ladder faces that ledge). The bot, upon climbing, will land into that area and continue on the path.
    • Ladders can also be blocked by design for one team (though usually not – most ladders are neutral). If needed, a ladder’s top or bottom areas might be marked as one-way doors or blocked if, say, it goes into an enemy spawn. That would be handled by the area flags, not ladder data itself.

5. Path Following and Adjustment: Once a path is found, TF2’s NextBot-based movement code takes over to follow it. The bot will iterate through the areas in the path. Typically it sets a current goal position somewhere in the next area (often the center or the entry point to that area). As the bot moves, it continuously checks and updates this goal:

  • If the bot can already see a farther area in the path (no obstacles between here and some later area’s center), it may skip ahead and set that farther area as the immediate goal. This is the “skip areas” optimization which makes the route more direct.
  • If the bot strays or is pushed (e.g. by explosions or collisions) off the planned path, the system can re-evaluate and either find a new path or guide the bot back to the nearest nav area of the current path.
  • If dynamic events occur (e.g. a door closes making a later area suddenly blocked, or a new shorter path opens), the bot’s AI can invalidate the current path and request a re-path to the target. Typically, TF2 bots periodically re-path or at least validate the next segment to avoid running into newly closed routes.

Centers vs Edges: The user’s question asks to clarify if bots pathfind using area centers, edges, or other subpoints. The answer is a bit nuanced:

  • *During pathfinding (A),** the algorithm uses area nodes; it doesn’t explicitly choose a crossing point on an edge. It considers an area “traversed” as a whole. The cost calculation often uses center distances as a convenient metric, so in that sense the *centers* are used for computing and comparing path lengths.
  • During path following (movement), the bot doesn’t rigidly go from center to center. Instead, it will move fluidly through the shared edge between areas. If a straight line from the bot’s current position to the center of the next area stays within navmesh boundaries, the bot will effectively cut the corner. This often happens in wide open areas: the navmesh might be a grid of rectangles, but a bot going diagonally will pass through the corner of one area into another without aiming for the center of the first. The NextBot locomotion can handle that because it’s doing continuous collision checks, not strictly waypoint following. Only in constrained spaces do bots essentially head for the middle of an area (to maximize clearance from boundaries).
  • There’s also the concept of a “approach point” when entering a new area. In some nav systems, you define a portal point on the edge. TF2’s navmesh doesn’t explicitly store portal points (the old approach spots were something else and removed), so the bot likely computes it on the fly: essentially, the approach point is “the point on the shared edge to which I should travel”. This could be the midpoint of the shared edge or the point on that edge closest to the bot’s current trajectory. Because areas are convex and mostly axis-aligned, a common approach is to move toward the center of the edge connecting to the next area.

In summary, bots plan their route per area, but move in a more granular way, effectively crossing at edges where appropriate. The area centers are used in the pathfinding algorithm’s heuristic and as a fallback movement target, but not as strict waypoints if not necessary. This gives bots a smoother path. If you enable nav debugging in TF2, you can sometimes see the purple and green lines drawn for bot paths – these lines often go from a bot to a point on the next area or next objective. Community analysis has shown that bots will create a path of areas, then attempt to follow it by continuously aiming at a point further down the path (not just the immediate next area’s exact center) as long as line-of-sight and precise rules allow.

6. Pathfinding Heuristics & AI Behavior:

  • *Heuristic (A):** As mentioned, TF2 uses A* with a distance heuristic. The heuristic is typically the straight-line distance on the XY plane (plus maybe a small factor for vertical). The code example from GMod confirms using direct distance between nav area centers as the heuristic cost estimate. This keeps the search optimal and fast.
  • Cost adjustments: TF2 bots can adjust path costs based on threats or other factors. For example, areas marked with high danger (like those visible by a sentry as indicated by TF_NAV_RED_SENTRY_DANGER) might on-the-fly be treated as higher cost for BLU bots, so they try to route around them if there is an alternate path. Similarly, if a Spy bot wants to avoid player sight lines, it could use the vis data (area binds) to choose a path that stays out of sight (though this would be more of a tactical decision layer on top of pure pathfinding).
  • Flood fill usage: Aside from A*, TF2’s AI uses flood-fill (BFS) for certain tasks like capturing zones or spread. For example, to compute the Earliest Occupy Times stored in the nav, they likely did a multi-source flood fill from team spawns. Bots themselves might use flood-fills in specific logic, e.g. “find all nav areas in this region” or “flood out from this point to mark an area as searched.” An example is if engineers search for a sentry build spot: they might flood out to evaluate a region. In general pathfinding though, they stick to A*.
  • Handling moving obstacles: TF2’s bots are primarily designed for static geometry. Dynamic obstacles like payload carts or player-built teleporters aren’t represented in navmesh (though teleporter exits could be special points to navigate to). If something blocks a nav area temporarily (e.g. a payload cart as obstacle – which usually it isn’t considered blocking in nav terms because bots can walk around it), the bot may perceive it via collision and either wait or repath around on the fly. The TRANSIENT flag was intended to mark areas that might be blocked, prompting periodic repath checks, but it’s noted as likely unimplemented. Instead, TF2 uses entities like func_nav_blocker tied to doors or gates – these directly toggle area blocked status when triggered, and bots respond by not considering those areas.

7. Non-trivial Cases – Examples:

  • Staircases: Suppose a bot needs to go up a spiral staircase. The navmesh on a spiral stair might be several small pie-slice areas flagged as stairs. The pathfinder will get a sequence through those slices. The bot will likely treat it as continuous movement, hugging the inner edge if possible. Because of the precise flag (often you’d mark narrow spiral stairs as precise to ensure the bot doesn’t try any fancy skipping), the bot will follow each slice closely, essentially going area to area with no skipping. If precise wasn’t set and the spiral is open, it might try to cut through, but the wall or rails typically enforce staying on the path.
  • Jump gaps: On maps like CTF_2Fort, bots may use the bridges. If a bot is on the battlements and wants to jump down to the bridge, the nav might allow a drop. The path might show going from battlement area → bridge area directly (if the drop isn’t lethal). The bot will reach the edge and then just fall, which the engine handles as a valid move. If the drop was too high, likely no nav connection exists and the bot would have to find stairs or a different route.
  • One-way doors: Consider a bot inside a spawn room during setup. The spawn exit area is flagged TF_NAV_SPAWN_ROOM_EXIT and also TF_NAV_RED_SETUP_GATE (if RED spawn during setup). That area is blocked to RED until setup ends (so RED bots won’t try to leave before the gate opens). BLU bots outside might have the same area flagged as one-way door (so BLU can’t enter). Pathfinding wise, before setup ends, any path for a RED bot that tries to go through that exit will see that area as blocked (either removed from adjacency or treated with infinite cost). So the bot won’t leave spawn (indeed we often see bots pacing until gates open). The moment setup ends, the game clears the block, and the bot’s pathfinder can now include that area. Typically, bots are signaled to recompute their paths at round start to now flood out.
  • Navigation with multiple routes (MvM): In Mann vs Machine, bots carrying the bomb have multiple possible lanes. Valve uses func_nav_prefer to reduce cost on one lane and func_nav_avoid to raise cost on others, to force bots to choose a particular path for a wave. Every time a wave ends, a logic in the map toggles which areas are preferred/avoided for the next wave, effectively “choosing” a different route randomly. The A* algorithm then naturally picks the low-cost (preferred) areas when plotting paths for the next wave of robots. If the robots stray or carry the bomb differently, they might still deviate, but generally the nav cost biases keep them on the intended route. This is a great example of how manipulating nav area metadata influences pathfinding without changing the underlying algorithm.

8. AI Decisions Beyond Pure Shortest Path: While A* finds the shortest (or cost-optimal) path, TF2 bots sometimes have layered decisions:

  • They may choose a longer path deliberately if it’s safer (this could be implemented by cost tuning, e.g. mark the dangerous area as avoid so path cost is higher).
  • When pursuing an enemy (combat), a bot might temporarily ignore the “ideal” nav path and cut corners (like a Soldier bot rocket jumping – they might even leave the navmesh temporarily in an arc).
  • If a bot’s target is moving, the goal area can keep changing, leading to continuous re-pathing. TF2 bots usually update their path periodically or when events happen.

Despite these complexities, at its core the pathfinding still uses the navmesh connectivity and A* as described. The navmesh’s role is to abstract the world into manageable chunks and provide all those annotated details so the bot knows what to do in each chunk (crouch, jump, etc.) and what to beware of (sentry here, sniper there, etc.).

In conclusion, bots in TF2 plan paths on the navmesh by treating nav areas as nodes and performing A* search to find a sequence of areas leading to the destination. They primarily consider area centers for distance calculations during pathfinding, but when executing the path they navigate through the shared edges between areas, not necessarily going through each center if it’s not needed. Transitions from one area to the next are computed by crossing the common boundary (portal) – the bot effectively “steps” from one polygon to the adjacent one. The nav area data provides all necessary metadata for these transitions: if a jump is required, the area is flagged and the bot will jump; if a ladder is present, the adjacency is understood and the bot will climb; if the area is blocked or one-way, that is accounted for so the bot doesn’t choose an invalid transition.

The use of heuristics like A* and precomputed info like “earliest occupy time” further enhance decision-making – for example, a bot might pick a patrol route through areas that its team can reach quickly, or designers can see that value for balancing. In summary, TF2’s bots leverage the navmesh not just as a static grid, but as a rich dataset: spatial nodes for routing, annotated with semantic info that influences path selection and movement style. This combination enables relatively intelligent navigation on the complex geometry of TF2 maps, handling everything from winding staircases and dropdowns to dynamically changing paths in events like Mann vs. Machine.

References

  • Valve Developer Community: NAV Mesh file format (structure of nav areas, connections, hides, encounters, etc.)
  • Valve Developer Community: Nav Mesh Editing (list of nav area attributes and usage in games)
  • Valve Developer Community: Team Fortress 2 Nav Mesh Attributes (TF2-specific nav area flags for spawn rooms, gates, points, etc.)
  • Kaitai Struct community: TF2 .nav format gist (reverse-engineered enumeration of nav area fields and TF2 flags)
  • Facepunch GMod Wiki: Simple Pathfinding (demonstration of A* on Source navmesh, using area centers for cost and heuristic)
  • AlliedModders forum: MvM bot path selection (explains usage of func_nav_prefer/avoid to bias A* costs for multiple routes)
  • Valve Dev Wiki: Non-attribute nav states (how areas get dynamically blocked by doors, spawn rooms, hazards at runtime)
  • Valve Dev Wiki: TF2 Navigation Mesh notes (hiding spot usage for spies, sniper hints, etc. in TF2)
  • Valve Dev Wiki: Nav Place names (how place IDs and names are stored and used in nav files).

About

lmaobox medbot lua

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages