- Document tracer direction fix root cause and solution in TOWER_DEMO.md - Select Hossin as the continent for tower arena demo - Update PROJECT.md with tracer direction fix completion - Update status checklists
5.3 KiB
Tower Attack Demo
Overview
A controlled demonstration of the bot combat system at a single tower location. By manually capturing coordinates, we create a bounded "arena" where bots behave correctly without needing full world geometry.
Why This Approach
- Wall shooting problem: Bots currently shoot through walls (no server-side collision)
- Full solution is massive: UBR mesh extraction + navmesh generation = huge project
- POC needs to be demonstrable: Bots shooting through 4 concrete walls isn't a demo, it's a bug showcase
- Scrappy but effective: Manual coordinate capture gives us a working showcase
Implementation Plan
Step 1: Choose a Tower
Pick a tower with good sight lines and clear boundaries:
- Select continent: Hossin (jungle continent with interesting terrain)
- Select specific tower location (user to provide)
- Capture tower coordinates via
!locrec <note>command in-game
Step 2: Define Combat Arena
Capture boundary coordinates by walking the perimeter:
/loc at each corner point:
- Northwest corner
- Northeast corner
- Southeast corner
- Southwest corner
- Height bounds (ground level, top platform)
Step 3: Implement Arena System
// Combat arena definition
case class CombatArena(
minX: Float, maxX: Float,
minY: Float, maxY: Float,
minZ: Float, maxZ: Float,
name: String = "unnamed"
)
// Check if position is within arena bounds
def isInArena(pos: Vector3, arena: CombatArena): Boolean = {
pos.x >= arena.minX && pos.x <= arena.maxX &&
pos.y >= arena.minY && pos.y <= arena.maxY &&
pos.z >= arena.minZ && pos.z <= arena.maxZ
}
// Only engage if BOTH bot AND target are in the arena
// This prevents shooting through walls into/out of the arena
def findTarget(botState: BotState): Option[Player] = {
val arena = currentArena // loaded from config
zone.LivePlayers.filter { p =>
isInArena(botState.player.Position, arena) &&
isInArena(p.Position, arena) &&
p.isAlive &&
p.Faction != botFaction &&
// ... other existing checks
}
}
Step 4: Optional - Interior Exclusion Zones
Define boxes for building interiors where bots won't engage:
case class ExclusionZone(
minX: Float, maxX: Float,
minY: Float, maxY: Float,
minZ: Float, maxZ: Float,
name: String = "interior"
)
// Skip targets in exclusion zones
def isInExclusionZone(pos: Vector3, zones: Seq[ExclusionZone]): Boolean = {
zones.exists { zone =>
pos.x >= zone.minX && pos.x <= zone.maxX &&
pos.y >= zone.minY && pos.y <= zone.maxY &&
pos.z >= zone.minZ && pos.z <= zone.maxZ
}
}
Demo Scenario
Setup
- Spawn 3-5 NC bots around tower perimeter
- Player approaches as TR/VS
- Bots engage when player enters arena bounds
- Combat feels natural - no wall shooting because everyone is in open area
What to Show
- Bots spot player and react (2-second delay)
- Bots track and engage target
- Accuracy/recoil system visible in hit patterns
- Bots die when killed, respawn after delay
- Weapon drawn, muzzle flash, sound effects
Known Limitations for Demo
No tracersRESOLVED - tracers working! (see notes below)- Bots don't navigate around obstacles (random wandering only)
- Single hardcoded arena location
Future: Scaling Beyond One Tower
Once the single-tower demo works, the approach can scale:
- Multiple arenas: Define several towers/bases as combat zones
- Config file: Load arena definitions from JSON/YAML
- Auto-generation: Eventually parse map data for structure bounds
- Full solution: UBR mesh extraction for complete world collision
Coordinate Capture Workflow
When ready to capture tower coordinates:
1. Log into game, go to chosen tower
2. Walk to each boundary corner, type /loc
3. Record coordinates in format:
Tower: [Name]
Continent: [home1/home2/etc]
Boundary Points:
- NW: (x, y, z)
- NE: (x, y, z)
- SE: (x, y, z)
- SW: (x, y, z)
Height Bounds:
- Ground: z = [value]
- Top: z = [value]
Optional Interior Exclusions:
- Room 1: (minX, minY, minZ) to (maxX, maxY, maxZ)
Tracer Investigation Notes
Status: RESOLVED ✓
Root Cause
The client caches player orientation when ChangeFireStateMessage_Start is received
and uses that cached orientation for tracer direction rendering. Our original code
sent the fire state message BEFORE updating/broadcasting the bot's orientation.
Solution
In startFiring():
- Calculate yaw to face target
- Update
player.Orientationto face target - Broadcast
PlayerStatewith correct orientation - THEN send
ChangeFireStateMessage_Start
Also fixed facingYawUpper in PlayerState to be 0 (relative to body) instead of
the absolute yaw value.
Key Learnings
- Tracers use LocalEvents like turrets (
LocalAction.SendResponse(ChangeFireStateMessage_Start)) - The firing flag (
ChangeFireStateMessage_Start) MUST be sent for tracers to render - Orientation MUST be correct BEFORE fire state is sent - client caches it
facingYawUpperis relative tofacingYaw, not absolute (0 = looking straight ahead)
Status
- Tracers working correctly
- Continent selected: Hossin
- Tower location selected
- Coordinates captured via !locrec
- Arena bounds implemented
- Exclusion zones defined (optional)
- Demo tested and working
- Video/screenshots captured