ZoneAttackAlert

Version: v3.3.1

A DCS World Lua mission script that sends coalition-specific attack alerts when ground or ship units inside defined trigger zones are hit.

Overview

ZoneAttackAlert monitors the DCS World event stream for S_EVENT_HIT events. When a ground unit or ship inside a configured trigger zone is hit, the script broadcasts a location-aware alert message to the coalition that owns the actual unit being hit (read dynamically via target:getCoalition()). Blue coalition receives distances in nautical miles (NM) and English alert text; Red coalition receives distances in kilometers (KM) and German alert text. A per-zone cooldown timer prevents message spam.

Features

Logic Flow Diagrams

Initialization Flow

Script loadedReload guardZAA.loaded?Skip (return)Localize globals(capture hot-path refs)Build zone cache(geometry only: x, z, r_sq)Build airbase cache(nearest-airbase lookup)Init state(last_alert_time empty)Register event handler(S_EVENT_HIT only)Log startup message YesNo

Event Handler Flow

onEvent(event)pcall wrapperevent.id == S_EVENT_HIT?target exists?target:getDesc() exists?category is GROUND_UNIT or SHIP?target:getPosition() exists?target:getCoalition() exists?find_zone(px, pz)zone found & not on cooldown?send_alert(zone, target_coal)Return silently NoYesNo targetYesgetDesc nilYesNot ground/shipYesPosition nilYesCoalition nilYesYesNo / on cooldown

Airbase Distance Lookup

Alert triggeredfind_zone() returns zonereceive target_coalbuild_zone_loc(zone.x, zone.z, target_coal)Iterate airbase_cacheFind closest airbase(min squared distance)Compute distance (m)& cardinal bearingFormat string:Blue -> NM + English layoutRed -> KM + German layoutReturn loc_str target_coal == BLUEtarget_coal == RED

Cooldown Logic

find_zone(px, pz)For each zone in zone_cachePoint inside radius?On cooldown?Return zoneContinue scan(check overlapping zones)Return nil NoYesYes (on cooldown)No (ready)End loop

Message Construction Flow

send_alert(zone, target_coal)Update last_alert_time[zone.name]loc_str = build_zone_loc(zone.x, zone.z, target_coal)Choose alert template:Blue template:'ALERT: Friendly forces under attack!' + loc_strRed template:'ALARM: Freundliche Kräfte unter Beschuss!' + loc_stroutTextForCoalition(target_coal, msg, duration)Message displayed target_coal == BLUEtarget_coal == RED

Changes in v3.3

Robust Coalition Detection

Change Description
Remove prefix-based routing Zone names (RT* / BT*) no longer determine alert recipient; target:getCoalition() is used instead
Dynamic location strings build_zone_loc called at alert-time instead of pre-cached at startup, so units/NM vs KM match the actual recipient
German alert text for Red Blue receives English "Friendly forces under attack!"; Red receives German "Freundliche Kräfte unter Beschuss!"
Multi-coalition zone support Units from either coalition inside any zone now alert their actual owner

Configuration

ZAA.config = {
    zone_names = {
        "RT1", "RT2", "RT3", "RT4", "RT5",
        "BT1", "BT2", "BT3", "BT4", "BT5"
    },
    message_cooldown = 180,   -- seconds per zone
    message_duration   = 18,  -- seconds on screen
    debug              = false, -- enable DCS.log diagnostics
}

Zone prefixes:

Note: Prefixes are for organisational convenience only. Alert routing is determined by target:getCoalition() at runtime, so a Blue unit inside an RT* zone will alert Blue, and a Red unit inside a BT* zone will alert Red.

Debug Logging

Set debug = true in the config table to emit detailed diagnostics to the DCS log file. When enabled the script logs every step of event processing, including zone cache initialization, hit-event receipt, target category validation, distance checks against each zone, cooldown state, the detected target coalition, and the final alert dispatch. This produces zero runtime overhead when disabled.

Script Architecture

ZoneAttackAlert.lua
├── RELOAD GUARD (skip if ZAA.loaded already set)
├── ZAA CONFIGURATION (table, user-editable)
├── GLOBAL LOCALIZATION (captured locals for hot path)
├── STATIC DATA (cardinal_directions)
├── ZONE CACHE (built at load time; geometry only, no coalition)
├── AIRBASE CACHE (built at load time; nearest-airbase lookup)
├── build_zone_loc() (runtime location formatter, NM for Blue / KM for Red)
│   └── Compass bearing: atan2(East, North) using DCS axes (+x=N, +z=E)
├── STATE (last_alert_time table)
└── EVENT HANDLER (onEvent -> S_EVENT_HIT only, stored in ZAA.event_handler)

DCS Coordinate System Reference

ZoneAttackAlert uses the DCS world coordinate system for all bearing and distance calculations:

The compass bearing from North is computed with the standard formula:

local north_offset = px - airbase.x   -- positive when target is North
local east_offset  = pz - airbase.z   -- positive when target is East
local bearing_deg  = math.deg(math.atan2(east_offset, north_offset)) % 360

This follows the DCS scripting engine FAQ definition (atan2(East, North)) and requires no additional rotational offsets. The result is a clockwise bearing from North in the range [0, 360).

Installation

  1. Copy ZoneAttackAlert.lua to your DCS mission's trigger DO SCRIPT action or load it via require in your mission scripting
  2. Ensure trigger zones named RT1RT5 and BT1BT5 exist in your mission
  3. Run the mission; alerts appear automatically when criteria are met

Dependencies

Version History

Version Changes
v1.x Original: camelCase, runtime API calls, all unit types, no pcall, distance printed "0 NM/KM"
v2.0 Optimized caches, aircraft/ground-only filtering, snake_case, pcall guards, < 1 distance fix, coalition-split zones
v2.1 Removed redundant isExist() and getCategory() checks; namespaced config/state into ZAA table
v2.1.1 Heartbeat option added (heartbeatEnabled, heartbeatInterval)
v2.3 Per-coalition cooldown; pre-built zone callouts; removed dead get_location_string function
v2.4 Per-zone cooldown (replaces per-coalition); cooldown check moved into zone scan loop
v2.5 Overlapping zone scan fix; heartbeat timer drift fix; deduplicated zone cache build
v2.6 Reload guard; cardinal direction fix; getDesc() nil guard; removed dedup registry
v2.6.1 Simplified event chain; overlapping zone scan fix; slimmed handler
v2.7 S_EVENT_DEAD fallback; generic "enemy forces" alerts; unified find_zone/send_alert path
v3.0 Radical simplification: S_EVENT_HIT only, no friendly-fire filter, ground+ship targets, single zone cache, single callout per zone, no heartbeat, 50% fewer lines
v3.1 Defensive coding fix: getDesc() guarded against nil (event.target can be destroyed before getDesc completes); ensures S_EVENT_HIT handler never crashes on deleted/damaged units
v3.2 Debug diagnostics: togglable config.debug boolean; structured [ZAA] prefix logging to DCS.log for zone cache, hit events, target validation, zone distance checks, cooldown state, and alert dispatch; zero-cost when disabled
v3.3 Robust coalition detection: alert routing now uses target:getCoalition() at runtime; removed prefix-based .coal field from zone cache; German alert text + KM units for Red coalition; English + NM units for Blue; dynamic location string built per-alert
v3.3.1 Localized cardinal directions: translated cardinal directions into German for the Red coalition (e.g., "nordöstlich") to prevent mixed-language alert messages.