Files
tactical-shooter/client/characters
shawn 194aad8f83 t_p2_bomb: Bomb/defuse objective system for search & destroy rounds
Adds:
- bomb_objective.gd: server-authoritative bomb state machine (IDLE→PLANTED→EXPLODED/DEFUSED)
  - plant_bomb(player_id, pos) - T-side only, LIVE phase, inside bombsite
  - defuse_bomb(player_id) - CT-side only, near bomb, 5s timer
  - cancel_defuse(player_id) - if defuser moves or is killed
  - bomb_explode() - configurable radius damage (10m lethal, 15m half)
  - 40s bomb timer (configurable)
  - Full query API: is_bomb_planted(), get_bomb_position(), get_bomb_timer()
  - Signals: bomb_planted, bomb_defused, bomb_exploded, defuse_started, defuse_cancelled
  - Connected to RoundManager via GameServer for round-end outcomes

- bomb_carrier.gd: client-side bomb carrier UI
  - Bomb status/defuse progress UI updates
  - Direction indicator to planted bomb
  - "Hold E to defuse" prompt for CT near bomb
  - "Hold E to plant" prompt for T with bomb in bombsite

- test_range.tscn: BombsiteA (south-west, -20,0,-5) and BombsiteB (north-east, 15,0,20)
  - 8x4m Area3D zones with BoxShape3D collision
  - PlantPosition Marker3D children
  - Group 'bomb_sites' for server discovery

- game_server.gd: BombObjective integration in _ready()
  - Creates and wires BombObjective to RoundManager, TeamManager, DamageProcessor
  - Registers bomb sites from scene tree
  - Connects bomb_exploded/bomb_defused → RoundManager.end_round()
  - Connects round_ended → bomb.reset()
2026-07-01 20:39:17 -04:00
..

FPS Character Controller — Tactical Shooter

First-person character controller for the tactical shooter GDExtension simulation core.

Architecture

The character controller is a GDScript bridge between Godot's input/physics system and the C++ SimulationServer (built in task t_aca0f251).

Godot Input
  │
  ├─► FPSCharacterController._physics_process()
  │      ├─ capture input (WASD, mouse, jump, sprint, crouch, shoot)
  │      ├─ build EntityInput Dictionary
  │      ├─ SimulationServer.apply_input(entity_id, input_dict)
  │      ├─ SimulationServer.fire_weapon(entity_id)  [if shoot]
  │      ├─ read entity position from server → apply to CharacterBody3D
  │      └─ apply mouse look (yaw on body, pitch on camera)
  │
  ├─► FpsCamera._process()
  │      ├─ view bobbing (head bob from movement)
  │      ├─ weapon sway
  │      └─ sprint FOV kick
  │
  └─► SimulationServer (C++ GDExtension)
         ├─ process_tick() @ 128Hz
         ├─ MovementComponent.update()
         ├─ HitDetection.process_shot()
         └─→ returns serialized snapshot

Files

File Purpose
character/fps_character_controller.gd Main controller (CharacterBody3D). Input capture, simulation bridge, walk toggle, crouch, jump.
character/fps_camera.gd First-person camera (Camera3D). Mouse look pitch, view bobbing, weapon sway, FOV kick.
input/input_handler.gd Documentation of expected input map bindings.

Scene Setup

  1. Create a CharacterBody3D scene:

    FPSCharacterController (CharacterBody3D)    — script: fps_character_controller.gd
    ├── FpsCamera (Camera3D)                     — script: fps_camera.gd
    ├── CollisionShape3D (CapsuleShape3D)        — radius: 0.4, height: 1.8
    └── Weapon (Node3D, optional)                — child of camera, rest transform tracked
    
  2. Configure exports on the root node:

    • mouse_sensitivity: 0.10.2 for typical tactical feel
    • walk_toggle: true = tap Shift to toggle sprint (CS-style)
    • crouch_toggle: false = hold Ctrl to crouch (tactical-style)
    • eye_height_stand / eye_height_crouch: match your capsule height
  3. Set up Input Map in Project Settings:

    • move_forward → W
    • move_backward → S
    • move_left → A
    • move_right → D
    • jump → Space
    • sprint → Shift
    • crouch → Ctrl
    • shoot → Mouse LMB
    • aim → Mouse RMB
    • ui_cancel → Esc (built-in, releases mouse capture)
  4. Integration with SimulationServer:

    # In your game manager (autoload or scene root):
    var server = SimulationServer.new()
    server.set_tick_rate(128)
    server.start()
    var player_id = server.spawn_entity(Vector3(0, 0, 0))
    
    # When player scene is ready:
    $Player/FPSCharacterController.set_entity_id(player_id)
    
    # In _process(delta):
    while server.can_tick(delta):
        var snapshot = server.tick()
        # send snapshot to network layer
    

Walk Toggle Behavior

  • walk_toggle = true (default): Press Shift to toggle between walk and sprint. Press again to toggle off. Same feel as Counter-Strike's walk/run toggle.
  • walk_toggle = false: Hold Shift for temporary sprint. Release to decelerate to walk speed.

Crouch Behavior

  • crouch_toggle = false (default): Hold Ctrl to crouch. Release to stand up.
  • crouch_toggle = true: Press Ctrl once to crouch, press again to stand.

Crouch transitions are smooth lerps over crouch_transition_time seconds. The collision capsule height and camera eye height both lerp simultaneously.

Standalone Mode

When no SimulationServer singleton is available, the controller falls back to built-in CharacterBody3D movement (_move_local()). This is useful for:

  • Testing the character controller in isolation
  • Prototyping without the GDExtension compiled
  • Single-player scenarios without the full simulation stack

Configure local_walk_speed, local_sprint_speed, local_crouch_speed, etc. for standalone tuning.

Dependencies

  • Godot 4.2+ (for Input.get_vector, move_toward, StringName literals)
  • SimulationServer GDExtension (from task t_aca0f251) for server-authoritative mode
  • Input map actions listed above

See Also

  • Scaffold: t_aca0f251src/movement_component.h (C++ Quake/CS movement physics)
  • Greybox map: t_p1_greybox — test environment for this controller
  • Hitscan weapon: t_p1_hitscan — weapon firing + lag compensation
  • Client prediction: t_p1_pred — client-side prediction + server reconciliation
  • Round system: t_p1_round — win/loss/sudden death on top of character controller