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
| Setting | Type | Default | Description |
|---|
ENABLED | Boolean | True | Enable/disable the radar system |
RADIUS | Integer | 9000 | Detection 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:
- On the same team (same IFF)
- Within radar range (distance ≤ RADIUS)
- Player’s own aircraft (always visible)
Aircraft IS hidden when:
- Enemy team (different IFF)
- 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)
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)
Suitable for:
- Close-quarters dogfighting
- Canyon/urban maps
- High-intensity tactical combat
Disabled (Full Visibility)
Or set a very large radius:
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.
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:
-
All enemies always visible
- Check
ENABLED = True
- Verify RADIUS isn’t too large
- Confirm players are on different IFFs
-
Friendly aircraft disappearing
- Verify IFF assignments are correct
- Check for position tracking initialization
-
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)