Alerts, Recent Transmissions, And Minimap Pings
Contract
Warcraft III location-bearing notifications are local presentation layered over authoritative simulation events. OpenRealm keeps four concerns separate:
simulation event / JASS native
-> game calls generic gi.MinimapPing
-> reliable svc_minimap_ping packet
-> client/cl_minimap.c
-> temporary minimap indicator
-> eight-entry recent-alert history
-> Space recall through the normal camera-position path
The server does not move the camera when an alert is emitted, and a ping does not change fog or selection. Recent-alert state is client-local and is not added to playerState_t/entityState_t.
The wire contract is the existing generic svc_minimap_ping message: world position, lifetime, RGBA color, and behavior flags. The packet keeps its existing layout and uses the client's generic marker sizing. The authored alert model name is the server-owned CS_MINIMAP configstring, following Quake's CS_SKY pattern; client/cl_minimap.c loads it through the normal configstring lifecycle. No minimap state belongs to a game UI library.
Ordinary minimap unit/building contacts are not pings. Warcraft III assigns its contact classes to the generic three-bit EFX_GAME_VARIANT_* payload in entityState_t.effect_flags; shared/client code carries that payload opaquely and the WC3 renderer interprets it. See minimap markers. A ping is a transient attention event, analogous to svc_sound, and therefore does not use the automatic-contact variant or survive save/load.
Producers
G_SendMinimapPing() in game/g_minimap.c resolves MinimapIndicator from the recipient's active war3skins.txt section and publishes the model path through CS_MINIMAP before sending the generic ping packet. An empty model path leaves the client with its generic colored marker.
The following high-confidence gameplay completions also call G_SendOwnerMinimapAlert() and therefore enter recent-alert history:
| Event | Stored location |
|---|---|
| construction completes | completed building s.origin2 |
| unit training completes | completed unit s.origin2 |
| research completes | researching producer s.origin2 |
The completion path currently uses a one-second alert-ping lifetime. That is an implementation default, not a measured retail constant; do not treat it as proven Warcraft timing.
Automatic normal-weapon attack alerts are also produced by the WC3 game module. The resolved primary weapon-hit path calls G_WC3_AttackAlert() before applying the hit; direct spell, DoT, splash, and scripted T_Damage() callers do not automatically become attack alarms. A qualifying remote alert:
- resolves
AttackNotifyDelayandAttackNotifyRangefrom the active Misc data, with stock fallbacks of 30 seconds and 1250 world units; - resolves the stock
Units\\CommandStrings.txtadvisor text (Unitattack/Townattack) through the same archive/TRIGSTRstring path used elsewhere; retail comments out the[AdvisorStrings]header, so those keys are read from the active[Errors]section, with explicit[AdvisorStrings]overrides preferred; - resolves allied advisor text through
Allyunderattack/Allytownattackand substitutes the attacked player's current name for the stock%stoken; - presents advisor text through the ordinary transient gameplay-message layer, not the command-error HUD and not the persistent Message Log;
- uses the recipient's race/map skin
UnderAttackSoundorTownAttackSound; - uses
AllyUnderAttackSound/AllyTownUnderAttackSoundfor passive allies for whom the attacked owner hasALLIANCE_HELP_REQUEST; - emits a transient opaque-red minimap attention signal with
MINIMAP_PING_REMEMBER, using the recipient'sMinimapIndicatormodel, so only notifications which survive range/cooldown filtering enter the existing eight-entry Space history; - never changes the ordinary minimap contact, fog, selection, or camera position.
The following compatibility choices are deliberately marked in code with BZ_COMPAT_GUESS because the available retail-facing data names the feature but does not fully specify the runtime rule:
AttackNotifyRangeis currently interpreted as Euclidean world distance from the recipient's authoritative camera target position to the victim.- All attack-alert classes for a recipient share one
AttackNotifyDelaycooldown. - An authored building (
G_UnitIsBuilding) is classified as a town alert; other units use the forces alert. - The attacked signal currently uses opaque red and the recipient's normally authored
MinimapIndicator, with the existing one-second alert lifetime. Warcraft exposes a dedicated attacked-signal color, but the underlying raw Misc field and exact retail lifetime/animation have not yet been recovered in-tree. - The automatic alarm is currently produced on a resolved primary normal-weapon hit. This intentionally excludes direct spell/DoT/script damage, but direct retail capture is still needed to decide whether a missed/evaded attack should alarm earlier at swing or projectile-launch time.
- Advisor text uses the existing gameplay-message overlay's automatic duration rule (message length / 6 + 5 seconds) and no dedicated fade. The retail
[AdvisorStrings]keys/wording are data-backed; the exact retail advisor position, lifetime, and fade curve still need direct capture.
These guesses are confined to the attack-alert call site and g_minimap.c; replacing any of them after direct retail capture must not require changes to generic damage or ordinary minimap contact rendering. The generic ping packet intentionally retains its stable position/lifetime/RGBA/flags layout; matching a unit, building, or Hero alert to its WC3 contact footprint would require an approved wire-format change. Hero-revive and building-morph completion alerts remain separate work.
JASS Minimap Pings
PingMinimap(x, y, duration) sends the generic packet but does not add an automatic recent-alert entry. In a GetLocalPlayer() context OpenRealm targets that represented player; with no local-player context it sends the presentation to all connected game clients, matching a native invoked on every retail client.
PingMinimapEx(x, y, duration, red, green, blue, extraEffects) transports clamped RGB values and MINIMAP_PING_EXTRA_EFFECTS. The generic marker uses the color and adds an outer pulse for extra effects. An authored model draws its own materials and animation, so packet tint does not override that model.
Minimap Projection And Drawing
refExport_t.WorldToMinimap is the renderer-side inverse companion of TraceMinimap. R_WorldToMinimap() calls the same R_MinimapPointForWorld() helper used by minimap camera/click projection, so pings and clicks share one world/minimap transform instead of duplicating map-coordinate math in JASS or UI code.
For rectangular Warcraft maps, world-space minimap content must not be stretched across the whole square HUD frame. Warsmash computes a centred minimapFilledArea using max(worldWidth, worldHeight): the authored minimap texture still fills the complete frame, while fog, units, camera geometry, clicks, and alert pings use the aspect-preserving content rectangle. OpenRealm mirrors that contract through WC3_MinimapContentRect() and stores that rectangle in tr.minimapRect after WC3 draws the minimap. Without this inset, the alert's world coordinate is correct (so Spacebar recall is correct) but its visual ping is displaced relative to the authored map/fog on non-square maps.
client/cl_minimap.c stores up to 16 simultaneously active visual pings. Sixteen is an OpenRealm implementation cap, not a retail Warcraft constant. When all slots are occupied, the oldest active visual ping is replaced. Lifetime uses the normal advancing cl.time clock.
FT_MINIMAP invokes CL_LayoutDrawMinimap(), which first asks the game renderer to draw terrain, fog, entities, and camera bounds, then draws active attention markers. A nonzero model index uses the registered authored model. Model zero, or an unavailable model after a logged warning, uses the generic colored cross/pulse. The stable packet carries no marker-size field, so pings use the authored model or the generic fallback size.
MDLX_DrawSpriteTinted() temporarily replaces tr.viewDef. Because minimap pings are drawn after the world, it must restore the previous tr.viewDef after its sprite pass; otherwise a post-world sprite can corrupt renderer state expected by subsequent HUD/overlay work.
Camera outline and portrait view ownership
R_DrawMinimapCameraRect() projects the gameplay viewport corners onto the ground plane. It skips views marked
RDF_NOWORLDMODEL or RDF_NOFRUSTUMCULL. The selected-unit portrait can render before the minimap, so
R_RenderFrame() in renderer/r_view.c must restore the previous tr.viewDef after a no-world scene, including
the entity-camera early return. World renders retain their new view for later HUD consumers.
A bounded Human02 trace confirmed the missing outline was caused by the portrait leaving flags 0x2d and its small
viewport in tr.viewDef; the minimap returned before projecting any corners. Minimap clicks still moved the camera.
Restoring the world view produced flags zero and four projected corners, with the outline visible under both ROC and
TFT archives. git blame traces the entity-camera early return to b6349c392; GL viewport restoration alone did not
restore the renderer's camera state.
make test-renderer-view exercises the production frame function with mocked game/GPU passes: authored portrait
cameras, missing model cameras, empty entity-camera scenes, ordinary no-world scenes, and subsequent world frames.
Removing view restoration makes all four no-world cases fail. For visual verification, load Human02 with a bounded
frame limit, skip its intro via cinematic stop after connection with cheats enabled, and capture the HUD with a unit
selected. See renderer view ownership.
Recent Alert History And Space
The generic minimap client keeps the latest eight positions from packets carrying MINIMAP_PING_REMEMBER. New entries are inserted newest-first and evict the oldest when full.
Space behaviour is:
If a new alert arrives during traversal, the cursor resets so the next Space goes to that newest alert. SDL key-repeat is consumed without advancing the cursor, so holding Space does not race through the history.
CL_MinimapKeyEvent() consumes Space only while recent history exists, then calls CL_SetCameraPosition(), preserving local prediction, replicated camera-bounds clamping, and the normal clc_camera_position server update. It stores coordinates rather than entity pointers, so a source may die or be removed without invalidating history.
SetCameraQuickPosition
SetCameraQuickPosition remains non-moving server-side state in game/api/api_camera.h. It records client->camera.quick_position and does not move the current camera.
When recent history is empty, the minimap handler leaves Space unconsumed. WC3's normal SPACE "cmd quickcamera" binding then invokes CMD_QuickCamera, which reads the authoritative server-side quick position and applies it through G_ClientSetCameraPosition(). Automatic alert history therefore retains priority without a game-specific branch in client code.
CL_ClearState() calls CL_ClearMinimap() directly to clear active pings, history, and dragging across disconnect/map resets; scripted quick-position state remains server-owned.
Engine Boundary
Shared additions are intentionally content-neutral: game_import.MinimapPing sends svc_minimap_ping, CL_ParseMinimapPing owns transient state, and refExport_t.WorldToMinimap projects a world point through the renderer's current minimap. Warcraft-specific producers, skin lookup, and alert policy remain under games/warcraft-3/.
Known Gaps
- under-attack, town-under-attack and allied alert producers/throttling;
- hero-revive and building-morph completion alert policy;
- verified retail timing/animation choice and ping-relative MDX animation phase for automatic completion pings;
- exact retail precedence between automatic transmission history and
SetCameraQuickPosition.
Do not fill these gaps by guessing constants or emitting damage alerts every hit.
Verification
tests/test_net.c covers valid and truncated svc_minimap_ping packets. games/warcraft-3/game/tests/t_game.c covers owner targeting, duration/color/flags, optional model registration, and the disconnected-client inverse path.
Recommended runtime checks when testing manually:
- Complete a building off-screen, confirm the minimap indicator, then press Space and verify the camera centres on the building without changing selection.
- Complete more than eight location-bearing notifications and verify Space walks newest to oldest, retaining only the latest eight.
- Complete a trained unit and verify the remembered point is the spawned unit location.
- Complete research and verify the remembered point is the researching building.
- On a rectangular map, call
PingMinimapat several known world points and verify the indicator aligns with the authored minimap/fog reveal inside the centred aspect-preserving content area, then verify it expires independently of recent-alert history. - Call
SetCameraQuickPositionwith no automatic alert history and verify assignment does not move the camera, then Space recalls it. - Verify a ping in fog does not reveal terrain or units.
See also: Triggered Dialogue, Cinematics And Camera, Sound architecture, and Server-Selected Presentation Effects.
Interactive signal cursor
cmd signal (default Alt+G) pushes a transient signal overlay. A world/entity
or minimap click emits the existing ally ping; Esc/right click cancels without
losing the underlying target or item-drag command. Minimap clicks carry a
generic point-routing flag and do not recenter/drag the camera. Signal cursor
color follows the player's skin-resolved TeamColor image; see
cursor mode and color evidence.
This currently reuses the one-second white ping service. Retail ping lifetime, player tint on the ping model, and HUD signal-button wiring are not established by the cursor tests and remain separate presentation work.