Skip to main content
The Radar plugin implements a proximity-based visibility system similar to the original WW3 server, where enemy aircraft are only visible when within radar range.

Overview

In realistic combat scenarios, pilots cannot see all enemy aircraft at all times. The radar system simulates this by hiding enemy positions when they’re outside detection range, adding a tactical layer to gameplay.
This plugin recreates the fog-of-war effect from the original YSFlight WW3 server.

Configuration

Configure radar settings in plugins/radar.py:
ENABLED = True
RADIUS = 9000  # in meters, within this range planes with different IFF can see each other.

Settings Explained

SettingTypeDefaultDescription
ENABLEDBooleanTrueEnable/disable the radar system
RADIUSInteger9000Detection range in meters

How It Works

Visibility Logic

The radar system intercepts server-to-client flight data packets and applies these rules: Aircraft IS visible when:
  1. On the same team (same IFF)
  2. Within radar range (distance ≤ RADIUS)
  3. Player’s own aircraft (always visible)
Aircraft IS hidden when:
  1. Enemy team (different IFF)
  2. Outside radar range (distance > RADIUS)

Position Spoofing

When an enemy is outside radar range, their position is replaced with a distant coordinate:
else:
    try:
        # This is a really arbitrary position that's hard to reach, better would
        # be to randomize it for every time the server starts.
        position_data = struct.pack("3f", 10e8, 200, 10e8)
        if decode.packet_version == 4 or 5:
            updated_data = data[:14] + position_data + data[26:]
        else:
            updated_data = data[:16] + position_data + data[28:]
    except:
        traceback.print_exc()
    
    player.streamWriterObject.write(send(updated_data))
    return False
The position (10e8, 200, 10e8) is intentionally far outside normal map boundaries, effectively hiding the aircraft from view.

Technical Implementation

Player Tracking

The plugin maintains a dictionary of flying players:
flying_players = {}

# Structure:
# flying_players[aircraft_id] = [player_object, [x, y, z]]
This tracks:
  • Player object for IFF checking
  • Current 3D position for distance calculations

Distance Calculation

@staticmethod
def in_range(pos1, pos2):
    # pos1 and pos2 are lists of [x,y,z]
    dist = math.sqrt((pos1[0] - pos2[0])**2 + (pos1[1] - pos2[1])**2 + (pos1[2] - pos2[2])**2)
    return dist <= RADIUS
Standard 3D Euclidean distance formula:
dist = √[(x₂-x₁)² + (y₂-y₁)² + (z₂-z₁)²]

Hook Flow

The plugin uses three hooks:

1. Client Flight Data

def on_flight_data(self, data, player, message_to_client, message_to_server):
    try:
        flying_players[player.aircraft.id][1] = FSNETCMD_AIRPLANESTATE(data).position
    except KeyError:
        return True
    return True
Updates player’s position from their own flight data.

2. Server Flight Data

def on_flight_data_server(self, data, player, message_to_client, message_to_server):
    # Initialize player tracking if needed
    if player.aircraft.id not in flying_players and player.aircraft.id != -1:
        flying_players[player.aircraft.id] = [player, []]
        return True
    
    decode = FSNETCMD_AIRPLANESTATE(data)
    
    # Always show if:
    # - Same IFF
    # - Own aircraft
    # - Within range
    if flying_players[decode.player_id][0].iff == player.iff:
        return True
    elif self.in_range(flying_players[player.aircraft.id][1], decode.position):
        return True
    else:
        # Hide by spoofing position
        ...
Intercepts other players’ positions before sending to client.

3. Unjoin

def on_unjoin(self, data, player, message_to_client, message_to_server):
    if player.aircraft.id in flying_players:
        flying_players.pop(player.aircraft.id)
    return True
Cleans up tracking when players leave aircraft.

Example Configurations

Long Range (Visual Combat)

RADIUS = 15000  # 15km
Suitable for:
  • Large maps
  • High-altitude combat
  • Beyond-visual-range engagements

Medium Range (Balanced)

RADIUS = 9000  # 9km (default)
Suitable for:
  • Standard dogfighting
  • Mixed altitude combat
  • Tactical awareness gameplay

Short Range (Close Combat)

RADIUS = 5000  # 5km
Suitable for:
  • Close-quarters dogfighting
  • Canyon/urban maps
  • High-intensity tactical combat

Disabled (Full Visibility)

ENABLED = False
Or set a very large radius:
RADIUS = 999999
With radar disabled, all enemy positions are always visible regardless of distance or IFF.

Gameplay Impact

Tactical Considerations

For Attackers:
  • Approach from unexpected angles
  • Use terrain to mask approach
  • Coordinate surprise attacks
For Defenders:
  • Maintain high altitude for better visibility
  • Use scout aircraft to extend radar coverage
  • Establish radar picket lines

Team Communication

The radar system encourages:
  • Callouts of enemy positions
  • Coordinated formations
  • Tactical planning
  • Wingman cooperation
Consider combining radar limits with team communication tools for enhanced tactical gameplay.

Performance Considerations

The radar system processes every flight data packet:
# Per packet:
# 1. Distance calculation (√ operation)
# 2. Dictionary lookups
# 3. IFF comparisons
# 4. Potential packet rewriting
Optimization tips:
  • Radius checks use squared distance when possible
  • Dictionary operations are O(1)
  • Only out-of-range enemies require packet modification

Troubleshooting

Common Issues:
  1. All enemies always visible
    • Check ENABLED = True
    • Verify RADIUS isn’t too large
    • Confirm players are on different IFFs
  2. Friendly aircraft disappearing
    • Verify IFF assignments are correct
    • Check for position tracking initialization
  3. Jittery enemy positions
    • May occur at radar range boundary
    • Consider adding hysteresis (e.g., 95% for hide, 105% for show)

Advanced Modifications

Randomized Hidden Positions

The current implementation uses a fixed hiding position. To randomize:
import random

# In plugin __init__:
self.hide_position = (random.uniform(-10e8, 10e8), 200, random.uniform(-10e8, 10e8))

# Use self.hide_position instead of hardcoded values

IFF-Based Radar Range

Different teams could have different radar ranges:
RADAR_RANGES = {
    0: 9000,   # Blue team
    1: 12000,  # Red team - better radar
}

RADIUS = RADAR_RANGES.get(player.iff, 9000)

Altitude-Based Detection

Higher altitude = better radar range:
def effective_radius(altitude):
    base_radius = 9000
    altitude_bonus = altitude * 0.5  # +0.5m range per meter altitude
    return base_radius + altitude_bonus
  • Works independently of all other plugins
  • Compatible with anti-cheat and damage systems
  • No configuration in config.py (plugin-level only)

Build docs developers (and LLMs) love