Skip to content

aldmbmtl/flanker

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

10 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Flankers

A hybrid FPS / RTS game built in Godot 4. Play as a ground soldier in first-person while commanding waves of minions and placing towers from a top-down RTS view. Supports single-player and multiplayer (up to 10 players via ENet). Three procedurally generated lanes connect two team bases across a 200Γ—200 unit map.


About This Project

A hybrid FPS/RTS game combining ground-based combat with top-down base management. Features:

  • Single-player and multiplayer (up to 10 players via ENet)
  • Player roles: Fighter (FPS + RTS) or Supporter (RTS-only)
  • Dual-mode gameplay: first-person combat and RTS tower placement
  • Procedurally generated terrain with diverse features
  • Wave-based minion spawning with escalating difficulty
  • Physics-based bullet system with realistic gravity
  • Ballistic cannonball tower projectiles with splash damage
  • Tower defense mechanics with auto-attack AI
  • Team-based currency and resource management
  • Dynamic lighting with shootable street lamps
  • Fog of war shader overlay
  • Time-of-day system affecting lamp behavior and visibility
  • Skill system with Fighter (Guardian/DPS/Tank) and Supporter (Arsenal/Logistics/Defense) branches

System Requirements

Component Minimum Recommended
OS Linux (X11), Windows 10, macOS 12 Linux
Godot 4.4+ 4.6.2
Renderer Forward+ (Vulkan) Forward+ (Vulkan)
GPU Vulkan 1.1 capable Vulkan 1.3+
VRAM 2 GB 4 GB+
RAM 4 GB 8 GB
CPU Quad-core 2.5 GHz Any modern multi-core
Display 1280Γ—720 1920Γ—1080

Vulkan is required. The project uses the Forward+ rendering backend. OpenGL / Compatibility mode is not supported.

Installing Godot

Arch Linux (recommended)

sudo pacman -S godot

Ubuntu / Debian

# Godot 4.6+ is not in default apt repos β€” use Flatpak or download directly
flatpak install flathub org.godotengine.Godot
# or download from https://godotengine.org/download

macOS

brew install --cask godot

Windows Download the standard (non-Mono) 64-bit installer from godotengine.org/download.

Verify your install:

godot --version
# expected: 4.x.x.stable...

Running the Game

git clone <repo-url>
cd flanker
make        # stop any running instance, launch, show logs

Makefile targets

Command Description
make Stop + relaunch + show logs after 8s (default)
make run Launch + show logs
make stop Kill running instance
make logs Print /tmp/flankers.log

Logs are written to /tmp/flankers.log. On a successful launch you will see:

Terrain: verts=40401 seed=XXXXXXXXX plateaus=5 peaks=5 secret_paths=6 grass_left=true

Running without Make

DISPLAY=:0 godot --path /path/to/flanker > /tmp/flankers.log 2>&1 &

Downloading Pre-built Releases

Pre-built binaries are available from the build/ folder in the repository.

Downloading

  1. Download the appropriate zip for your OS:

  2. Extract the zip to a folder of your choice.

Running the Game

Linux

Open a terminal in the extracted folder and run:

./flanker-linux

Or double-click the file in your file manager. If prompted, mark as executable.

Windows

Double-click flanker-windows.exe to launch the game.

Note: Vulkan 1.1+ is required. Ensure your GPU drivers are up to date.


Controls

FPS Mode (Fighter role only)

Key / Button Action
W A S D Move
Mouse Look
LMB Shoot
RMB (hold) Zoom / ADS
Space Jump
Shift (hold) Sprint
Ctrl (hold) Crouch
Tab Switch to RTS mode
Esc Pause menu

RTS Mode

Key / Button Action
W A S D Pan camera
Scroll Zoom
LMB Place tower
Tab Switch to FPS mode (Fighter only)

Supporter role players start in RTS mode and cannot switch to FPS. Tab is locked for both roles β€” Fighters stay FPS-only unless they tab switch; Supporters are RTS-only permanently.


Gameplay

Overview

  • Two teams: Blue (south, z=+82) and Red (north, z=-82)
  • Three lanes connect the bases: Left, Mid, Right
  • Minions spawn in waves every 30 seconds, escalating in count each wave (cap 6 per lane per team)
  • Destroy the enemy base to win

Player Roles

At game start, each player picks a role:

  • Fighter β€” spawns as an FPS soldier. Can switch between first-person combat and RTS tower placement with Tab
  • Supporter β€” RTS-only. No FPS body. Permanently in top-down view, placing towers and managing resources

Only one Supporter per team is allowed. The server enforces this β€” if two players try to claim Supporter on the same team, the second is rejected and must pick Fighter.

Multiplayer

From the Start Menu, choose Host or Join:

  • Host β€” opens a lobby on the specified port (default 8910). Other players join by IP
  • Join β€” connects to a host by IP and port, enters the lobby
  • Local Play β€” single-player, skips networking entirely

In the Lobby, players are auto-assigned to teams (balanced). Everyone hits Ready; the host starts the game. The server broadcasts a seed so all clients generate the identical map.

Up to 10 players supported. Server is authoritative for shot damage, minion state, tower placement, and team points. Respawn timers escalate with each death (5s base + 5s per prior death).

Map Features

Each run is procedurally seeded β€” the map is different every game:

  • Lanes β€” flattened dirt paths along cubic BΓ©zier curves, lit by alternating-side hanging street lamps, lined with wooden fence panels
  • Mountain bands β€” rough terrain between |x| 15–80 on each side of the map
  • Secret paths β€” 6 narrow mountain trails (3 per side) cutting through the off-lane zones, good for flanking
  • Plateaus β€” 5 elevated flat areas (~6–7 units high) per run, placed in mountain bands. Good sniper nests with sightlines down onto lanes
  • Peaks β€” 5 impassable snow-capped spires (~22 units high) per run. Snow appears above ~13 units. Physically unreachable β€” jump height is 6 units
  • Biomes β€” one side of the map is grass, the other desert, randomly assigned each run based on seed
  • Cover objects β€” walls and crates scattered across 20 random clearings in off-lane areas

Bullets

  • All shots are physics-based projectiles with realistic gravity drop (18 m/sΒ²)
  • Player bullets travel at 280 m/s, minion bullets at 120 m/s
  • Tracer color: player = yellow-white, blue minions = blue, red minions = red
  • Friendly fire is disabled
  • Player has a 1.5s reload delay β€” a progress bar appears under the crosshair during reload and hides when ready

Cannonball Towers

Towers fire ballistic cannonballs at enemy minions:

  • Computed arc: x/z constant velocity, y component overcomes gravity over 2.5 second flight time
  • Direct hit damage applies to the first collider struck
  • Splash damage β€” 50% of direct damage, applied to all targets within 3 units of impact
  • Friendly fire disabled via the same _should_damage() pattern as bullets
  • Impact spawns a GPU particle burst

Minion AI

  • Minions detect enemies at 12 units, begin strafing approach
  • Stop and fire at 10 units; each has a unique strafe phase so crowds naturally spread out
  • Separation steering prevents minions from stacking
  • In darkness (no nearby lit lamp), detect range drops to 5 units and shots have a 60% miss chance
  • Shoot out a lamp while being chased β€” minions lose track and bullets go wide

Street Lamps and Darkness

Lanes are lined with hanging street lamps procedurally placed along each curve.

  • Time-of-day aware β€” lamps are on at sunrise, dusk, and night. At noon they stay off (daylight is sufficient)
  • Shootable β€” aim at the bulb and shoot to destroy the light. Only the bulb has a hitbox; shooting the pole does nothing
  • Flicker on shoot-out β€” the bulb flickers rapidly before going dark, simulating the filament dying
  • Auto-respawn β€” shot-out lamps flicker back on after 15 seconds
  • Tactical darkness β€” dark zones reduce minion detection range and accuracy. Use them to break pursuit or set up an ambush

Fence and Torches

Both edges of every lane are lined with procedural wooden fence panels:

  • 20% random gaps β€” natural breaks in the fence line
  • 15% of panels randomly sprout a torch: OmniLight3D + GPU particle flame effect
  • Torches are spaced at least 12 units apart (no torch clusters)
  • Fence panels are StaticBody3D on collision layer 2 β€” block movement but not bullets

Cover Objects

WallPlacer scatters cover across 20 randomly placed clearings in the mountain/off-lane areas:

  • Mix of walls (kenney_fantasy-town-kit) and crates (kenney_blaster-kit)
  • Clearings avoid lane edges, secret paths, and base zones
  • Each piece has a StaticBody3D collision shape on collision layer 2

Fog of War

A full-map shader overlay at y=25 clears visibility circles around allied units:

  • Up to 64 visibility sources: player position, allied minion positions, allied tower positions
  • Each source has a configurable radius β€” player sees farther than minions
  • Updated every frame via FogOverlay.update_sources(...)

Enemy Health Bars

  • Health bars appear above enemies when zoomed in (RMB hold)
  • Only visible within 75 units
  • Occluded by terrain and geometry β€” no health bars through hills or walls

Respawn

On death, the player switches to RTS camera and waits:

  • Base respawn time: 5 seconds
  • Each subsequent death adds 5 more seconds (escalating penalty)
  • In multiplayer, death counts are tracked server-side in LobbyManager.player_death_counts

Skill System

The game features a skill system for both player roles:

Fighter Skills:

  • Guardian Branch: Field Medic (heal allies), Rally Cry (speed boost), Revive Pulse (full heal)
  • DPS Branch: Dash (movement), Rapid Fire (weapon boost), Rocket Barrage (tower targeting)
  • Tank Branch: Adrenaline (heal self), Iron Skin (damage shield), Deploy MG (turret placement)

Supporter Skills:

  • Arsenal Branch: Build Discount (reduced costs), Turret Overdrive (tower speed boost), Advanced Launcher (unlock missile types)
  • Logistics Branch: Fast Respawn (faster return), Ammo Drop (reload allies), Build Anywhere (no setback), Rally (team speed boost)
  • Defense Branch: Tower HP (increased tower health), Repair (tower healing), Fortify (barrier durability), Point Surge (team points)

Each role has a unique skill tree with three branches. Skills are earned through leveling up and can be unlocked in the skill tree UI. Active abilities have cooldowns and can be assigned to hotbar slots for quick access.


Project Structure

flanker/
β”œβ”€β”€ Makefile
β”œβ”€β”€ project.godot          # input actions, autoload singletons, renderer config
β”œβ”€β”€ assets/
β”‚   β”œβ”€β”€ day_environment.tres
β”‚   β”œβ”€β”€ dusk_environment.tres
β”‚   β”œβ”€β”€ night_environment.tres
β”‚   β”œβ”€β”€ FogOfWar.gdshader
β”‚   β”œβ”€β”€ ui_theme.tres
β”‚   β”œβ”€β”€ weapons/           # weapon preset .tres files
β”‚   β”œβ”€β”€ kenney_blocky-characters/  # GLB character models
β”‚   β”œβ”€β”€ kenney_fantasy-town-kit/   # wall, fence GLB models
β”‚   β”œβ”€β”€ kenney_blaster-kit/        # crate GLB models
β”‚   β”œβ”€β”€ kenney_pirate-kit/         # 3D models (optional)
β”‚   β”œβ”€β”€ kenney_ui-audio/           # UI sound effects
β”‚   └── tower-defense-kit/         # tower models (optional)
β”œβ”€β”€ scenes/
β”‚   β”œβ”€β”€ Main.tscn          # root game scene β€” all nodes wired here
β”‚   β”œβ”€β”€ StartMenu.tscn     # host/join/local-play UI with cinematic camera
β”‚   β”œβ”€β”€ Lobby.tscn         # pre-game lobby with team lists and ready checks
β”‚   β”œβ”€β”€ LoadingScreen.tscn # progress bar overlay during scene setup
β”‚   β”œβ”€β”€ RoleSelectDialog.tscn  # Fighter / Supporter role picker
β”‚   β”œβ”€β”€ PauseMenu.tscn     # Resume / Leave game overlay
β”‚   β”œβ”€β”€ FPSPlayer.tscn
β”‚   β”œβ”€β”€ RemotePlayer.tscn  # ghost representation of a remote peer
β”‚   β”œβ”€β”€ Minion.tscn
β”‚   β”œβ”€β”€ Bullet.tscn
β”‚   β”œβ”€β”€ Cannonball.tscn    # ballistic tower projectile
β”‚   β”œβ”€β”€ Tower.tscn
β”‚   β”œβ”€β”€ Base.tscn
β”‚   └── WeaponPickup.tscn
└── scripts/
    β”œβ”€β”€ Main.gd              # game manager, mode switching, wave announcements
    β”œβ”€β”€ LaneData.gd          # autoload β€” BΓ©zier curves, waypoints
    β”œβ”€β”€ TeamData.gd          # autoload β€” team points tracking
    β”œβ”€β”€ NetworkManager.gd    # autoload β€” ENet peer management
    β”œβ”€β”€ LobbyManager.gd      # autoload β€” lobby state, RPCs, game start orchestration
    β”œβ”€β”€ GameSync.gd          # autoload β€” in-game player state (health, teams, respawn)
    β”œβ”€β”€ TerrainGenerator.gd  # procedural mesh, collision, biomes, peaks, plateaus
    β”œβ”€β”€ LaneVisualizer.gd    # dirt ribbon meshes along lanes
    β”œβ”€β”€ LampPlacer.gd        # hanging street lamps along lane edges
    β”œβ”€β”€ ShootableLamp.gd     # per-lamp flicker + restore logic
    β”œβ”€β”€ FencePlacer.gd       # fence panels + torches along lane edges
    β”œβ”€β”€ WallPlacer.gd        # walls + crates in random off-lane clearings
    β”œβ”€β”€ TreePlacer.gd        # procedural trees along lane edges
    β”œβ”€β”€ FogOverlay.gd        # fog of war mesh + shader source management
    β”œβ”€β”€ MenuCamera.gd        # cinematic orbiting camera for start menu
    β”œβ”€β”€ FPSController.gd     # player movement, shoot, sprint, crouch, zoom, reload
    β”œβ”€β”€ RTSController.gd     # top-down camera, tower placement
    β”œβ”€β”€ RemotePlayerManager.gd  # creates/removes RemotePlayerGhost nodes
    β”œβ”€β”€ RemotePlayerGhost.gd    # lerp position/rotation, drive walk/idle animation
    β”œβ”€β”€ MinionAI.gd          # pathfinding, strafing, ranged shooting, separation
    β”œβ”€β”€ MinionSpawner.gd     # wave timer, escalating spawn counts
    β”œβ”€β”€ Bullet.gd            # projectile physics, raycast collision, friendly fire
    β”œβ”€β”€ Cannonball.gd        # ballistic arc, splash damage, impact particles
    β”œβ”€β”€ TowerAI.gd           # auto-attack enemy minions in range
    β”œβ”€β”€ Tower.gd             # tower setup, state machine
    β”œβ”€β”€ BuildSystem.gd       # RTS tower placement logic
    β”œβ”€β”€ Base.gd              # base HP, damage, win condition
    β”œβ”€β”€ WeaponData.gd        # weapon definitions for pickups
    β”œβ”€β”€ WeaponPickup.gd      # weapon pickup interaction
    β”œβ”€β”€ EntityHUD.gd         # per-entity health bars (zoom + LOS gated)
    β”œβ”€β”€ RoleSelectDialog.gd  # Fighter / Supporter role picker UI
    β”œβ”€β”€ Lobby.gd             # lobby screen UI and ready/start logic
    β”œβ”€β”€ LoadingScreen.gd     # progress bar overlay
    β”œβ”€β”€ LoadingState.gd      # global loading step reporter
    β”œβ”€β”€ PauseMenu.gd         # resume / leave game
    β”œβ”€β”€ Minimap.gd           # RTS minimap rendering
    β”œβ”€β”€ SkillTree.gd         # autoload β€” skill tree state, RPC, cooldowns, passive bonus queries
    β”œβ”€β”€ SkillDefs.gd         # autoload β€” static skill definitions
    β”œβ”€β”€ FighterSkills.gd     # Fighter skill execution
    └── SupporterSkills.gd   # Supporter skill execution

Development Notes

  • All scene and resource edits are done by hand in .tscn / .tres files β€” there is no editor GUI workflow
  • Geometry is generated at runtime in _ready() β€” no pre-baked meshes in the repo
  • LaneData autoload is the single source of truth for all lane positions β€” never hardcode lane coordinates elsewhere
  • Multiplayer authority: server is authoritative for shot damage, minion state, tower placement, and team points. Clients send requests; server validates and broadcasts results
  • RPC sender ID: multiplayer.get_remote_sender_id() returns 0 when the server calls an RPC on itself. Use the _sender_id() helper in LobbyManager (maps 0 β†’ 1)
  • Seed sync: LobbyManager.start_game broadcasts a non-zero seed to all peers before scene change. Never allow seed=0 β€” TerrainGenerator falls back to randi() causing client/server map divergence
  • The project was developed against Godot 4.6.2 (system install). No .NET / Mono required
  • No external dependencies beyond Godot 4.6.2 engine

Testing

Run make test after every single code change β€” no exceptions.

make test

This runs the GUT headless suite. The suite must exit with no failing tests (the summary line will read ---- N pending/risky tests ---- with zero failures, or ---- All tests passed! ----). A change that introduces any new failure is not acceptable, even if the feature works at runtime.

Rules:

  • Fix the test if the code is correct β€” with written justification for why the test was wrong.
  • Fix the code if the test is correct β€” do not mark real regressions as pending().
  • Write new tests for every new feature or bug fix before the change is considered complete.
  • Never silence a failure by deleting the test or converting it to pending() without a documented known-bug justification.

Current baseline: 591 passing and 8 pending/risky (all intentional β€” documented known bugs or no-assert smoke tests). Any run dropping below 591 passing is a regression.

What the tests cover

The tests verify data plumbing across all major systems:

Area What is tested
TeamData Points add/spend/sync/guards
LevelSystem XP, level-up, carry-over, attribute spend, bonus stats
GameSync Health, damage, death, respawn, teams, ammo
LobbyManager Registration, team balance, role slots, death counts, respawn timers
TowerBase Setup, take_damage, friendly fire, death, XP, fire position, team detection
MinionBase Setup, damage, death, slow debuff, puppet state, waypoints
ProjectileBase Lifetime/expire, gravity, hit callbacks, friendly fire guard
BuildSystem Item costs, spacing formula, team-half guard, placement validation
Multiplayer RPCs Full RPC surface of LobbyManager β€” registration, shots, transforms, minion sync, tower sync, pings, level/XP
Position/Visibility Sync Player transform broadcast, ghost creation/update, fog overlay sources, minimap fog
ENet loopback Real connection, peer detection, seed broadcast, role accept/reject

What the tests do NOT cover

  • Visual rendering on a real second Godot client
  • GLB assets, animations, or particle effects displaying correctly
  • Smooth lerp/interpolation of remote player ghosts on screen
  • Audio playback

Known active bugs (tracked as pending() in tests)

These are confirmed broken in the current codebase. Tests document them but do not fail on them:

  1. _roles_pending never decrements on early disconnect β€” LobbyManager.gd:236-244
  2. Respawn ignores level bonus HP β€” LobbyManager.gd:452-455 broadcasts flat PLAYER_MAX_HP instead of PLAYER_MAX_HP + bonus
  3. request_destroy_tree is call_remote β€” host-fired bullets never destroy trees on the host side β€” LobbyManager.gd:694-705
  4. broadcast_player_transform double-emits on server β€” remote_player_updated fires twice on the host β€” LobbyManager.gd:370-371
  5. Remote players invisible on minimap β€” RemotePlayerGhost is in group "remote_players" but Minimap only queries group "player" β€” Minimap.gd
  6. Minimap fog ignores allied remote player positions β€” fog only clears around the local player β€” Minimap.gd

(End of file - total 478 lines)

About

Shamelessly Vibe Coded

Resources

Contributing

Stars

Watchers

Forks

Contributors

Languages