PSF-BotServer/bot-docs/TOWER_DEMO.md
Claude 95a579a3e6
docs: Update tracer fix status and select Hossin for tower demo
- 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
2025-11-23 08:35:12 +00:00

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

  1. Spawn 3-5 NC bots around tower perimeter
  2. Player approaches as TR/VS
  3. Bots engage when player enters arena bounds
  4. 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 tracers RESOLVED - 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:

  1. Multiple arenas: Define several towers/bases as combat zones
  2. Config file: Load arena definitions from JSON/YAML
  3. Auto-generation: Eventually parse map data for structure bounds
  4. 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():

  1. Calculate yaw to face target
  2. Update player.Orientation to face target
  3. Broadcast PlayerState with correct orientation
  4. 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
  • facingYawUpper is relative to facingYaw, 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