Skip to content

Unit Selection And Control

Contract

Warcraft III selection is server-authoritative and keeps three decisions separate:

visible/selectable -> relationship presentation -> control authority

games/warcraft-3/game/g_commands.c owns the shared policy:

  • G_UnitCanBeSelected(client, ent) accepts a live SVF_MONSTER that is in use, not hidden, not EF_NOT_SELECTABLE, and actively visible through G_FowPlayerCanHoverEntity.
  • G_SelectionRelation(viewer, ent) returns friend, neutral, or enemy independently of command authority.
  • G_UnitCanControl(client, ent) is a pure authority check: it accepts locally owned units and passive allies that grant basic or advanced shared control, independent of fog/hover/selectability state. Active selection/order paths separately reject dead, hidden, and unselectable entities. Reserved neutral owners use the same alliance table; their default state simply does not grant shared control.
  • G_UnitCanSpendResources additionally requires advanced sharing for allied production, research, upgrades, construction, revival and their cancellation. See Team Resources and Advanced Control for the covered command paths and known UI gaps.
  • Alliance state is directional, matching the JASS SetPlayerAlliance(source, other, type, value) contract. G_SetPlayerAlliance changes only level.alliances[source][other]; callers that want mutual alliance must set both directions. Friend/enemy tests use ALLIANCE_PASSIVE specifically—shared vision, shared XP, or shared-control bits by themselves do not make a hostile unit friendly.

G_InitPlayerAlliances() establishes Warcraft's map-start defaults before map script execution. Reserved PLAYER_NEUTRAL_PASSIVE (15) receives a bilateral ALLIANCE_PASSIVE relation with every player. Ordinary W3I slots below the four reserved neutral owners whose playerType == kPlayerTypeNeutral receive the same bilateral passive defaults. PLAYER_NEUTRAL_AGGRESSIVE (12), Neutral Victim (13), and Neutral Extra (14) are not made passive allies by that rule. These are initial values, not permanent owner-ID exceptions: later SetPlayerAlliance calls may revoke Neutral Passive friendship or ally Neutral Aggressive.

Relationship consumers use G_PlayerTreatsPlayerAsAlly(source, other). The arguments are deliberately directional: the acting/querying player is the source and the target unit's owner is the other player. JASS IsUnitAlly and IsUnitEnemy, Smart orders, Follow, spell relationship checks, selection-ring classification, and automatic acquisition use that same direction.

This means a visible foreign unit may be inspected without giving the viewer ownership or order authority.

Selection Flow

client/cl_input.c sends entity numbers through select. CMD_Select revalidates every candidate server-side; client picking is not authority.

Presentation-only JASS effect handles are deliberately outside this widget-selection contract. G_SpawnModelEffect() marks their edicts EF_NOT_SELECTABLE, which becomes RF_NOT_SELECTABLE client-side. Their MDX still renders, but R_TraceEntity() and rectangle selection ignore it so clicks pass through to real widgets or terrain. This matches the JASS type hierarchy (effect extends agent, not widget) and prevents tutorial waypoint/special-effect art from intercepting right-click movement.

Mouse hover ray picking

renderer/r_ents.c:R_TraceEntity() asks the active game renderer for the nearest model hit under the cursor. WC3 models with authored MDX collision shapes use those shapes. Models without collision shapes fall back to geoset triangles in games/warcraft-3/renderer/mdx/r_mdx_geoset.c:MDLX_TraceModel(). That fallback must filter geosets using the same authored selection/animation state as Warsmash: geosets with the MDX Unselectable flag (selectable & 4) are ignored, and geosets whose current geoset-animation alpha is zero are ignored. Construction/death/helper geometry can have extents far outside the visible Stand model, so tracing every stored triangle creates invisible hover regions.

The WC3 renderer returns the actual world-space ray intersection and R_TraceModel() converts it to distance from the ray origin. R_TraceEntity() therefore resolves overlapping projected models by the nearest hit rather than entity submission order. A zero distance for every model is invalid: buildings can overlap in screen space even when their world meshes are separate.

A Prologue02 investigation showed the Orc Great Hall (ogre) had no collision shapes and a fallback model radius over 1100 units; the cursor repeatedly hit a distant Great Hall geoset while moving toward the nearby Gold Mine. The mine itself had no collision shapes either. This established that the oversized hover was client MDX fallback geometry, not the mine pathing texture or simulation collision radius.

For rectangle selection, a controllable non-building unit has the existing WC3-style mobile-unit preference. If one is present, buildings and non-controllable foreign entries are dropped from that selection. If no controllable mobile unit is present, all selectable entries may be selected, including enemy and neutral units. The authoritative server selection is capped at 12 entries even though the generic client-side cache remains sized by MAX_SELECTED_ENTITIES.

Persistent Hero and idle-worker HUD shortcuts reuse this authority boundary but have a separate retained HUD/cycling lifecycle; see Persistent Hero And Idle-Worker Shortcuts. Shortcut-driven server selections are mirrored back into the client selection cache with the existing GameCommand transport.

Targeted ability callbacks (menu.on_entity_selected) are a separate path: a left click completes the pending target action instead of replacing the unit selection.

JASS selection events

CMD_Select publishes Warcraft selection events from the final authoritative selection delta. Newly added members publish EVENT_PLAYER_UNIT_SELECTED and EVENT_UNIT_SELECTED; removed members publish the corresponding deselection events. Re-sending an unchanged full selection publishes nothing.

Do not publish events while CMD_Select temporarily clears and rebuilds the selection list. The client sends complete membership, so doing so would create false deselect/select pairs for units that remain selected. This event path is required by campaign tutorial triggers such as Prologue02's G1_SelectPeon sequence before later Peon-training stages can become active.

wc3_quest_debug 1 additionally prints WC3_QUEST_SELECT lines for the authoritative selected/deselected transitions, including player, entity number, and unit rawcode.

Focused Unit In A Multi-Selection

Selection membership and focused-unit presentation are separate state. The server retains one focused entity from the current selection; G_GetMainSelectedUnit() returns that entity while it remains selected, then falls back to the first unit in canonical Warcraft selection order if the focus becomes invalid. G_SelectEntity() still establishes an initial focus while membership is being built, while the completed select command replaces that provisional choice with the first canonically ordered unit. G_FocusSelectedUnit() changes focus without changing any selection bits. Focus is transient UI/input state and is reset when map-player state is initialized rather than being added to the save format.

Multi-selection ordering

G_GetOrderedSelectedUnits() is the shared authoritative ordering path used by the status panel and default-focus fallback. It reproduces the comparator in Warsmash MeleeUI.selectWidgets():

  1. UnitData.slk prio / UnitData_t.priority, descending;
  2. UnitBalance.slk level / UnitBalance_t.level, descending;
  3. unit type rawcode, descending in canonical Warcraft War3ID byte order.

The third comparison needs an explicit conversion. OpenRealm's MAKEFOURCC stores the first character in the low byte ('hfoo' is little-endian in the native integer), while Warsmash War3ID stores the first character in the high byte before comparing its integer value. Comparing edict.class_id directly is therefore not equivalent; G_SelectionRawcodeValue() converts to canonical Warcraft byte order first.

The sort is stable for equal comparator keys. OpenRealm's authoritative selection membership is currently only an entity bit per player, so it does not retain the original click/rectangle insertion order. Equal unit types therefore preserve the server's deterministic edict scan order rather than Warsmash's original input-list order. Reproducing that final within-type detail would require retaining selection insertion order as additional transient state.

The completed select command uses the first sorted unit for both default focus and the initial selection acknowledgement sound, matching Warsmash's flow where selectUnits() chooses selectedUnits.get(0) after sorting. Clicking a later multiselect portrait may still explicitly focus that unit without reordering the panel.

FT_MULTISELECT is one packed frame, but each payload item carries its entity number and a focused-subgroup flag. client/cl_scrn.c hit-tests the authored icon grid using the frame rectangle plus uiMultiselect_t.offset, consumes both mouse edges over an icon, and sends focus <entity> on release. The server validates that the entity is still selected before accepting the focus change. It then rebuilds the full focused-selection presentation (info panel, inventory, portrait, and command card), so focus cannot change only the command card while leaving the status panel stale.

The focused subgroup follows Warsmash's RenderUnit.groupsWith() rule: selected units with the same unit type (class_id in OpenRealm) are marked focused together. The client draws the active skin's SelectedSubgroupHighlight behind those icons. Selection membership remains unchanged; the concrete clicked unit is still the focused unit used by focused-unit commands, inventory, and the persistent unit portrait. The portrait remains visible for multiselections and follows focus; its live HP/mana bindings follow the same focused entity.

Status changes on a singly selected unit invalidate its info-panel cache. The normal once-per-frame refresh then reserializes the status icons and timed-status eligibility; HP, mana, and countdown progress continue through live player-state bindings without forcing a full panel rebuild every frame. The invalidation uses an out-of-range entity cache key and is cleared when the panel payload is sent, preserving the serialized client layout. Multiselection has no status panel and does not trigger this refresh.

Multiselect portrait clicks also preserve Warsmash's exact-unit second-click behavior. Clicking a different icon changes the concrete focused unit while keeping the full selection, even when that icon belongs to the already-focused unit-type subgroup. Clicking that same exact focused icon again collapses the authoritative selection to only that unit. The collapse is a real membership change: the server publishes deselection events for the removed members, mirrors the one-unit selection through svc_set_selection, and uses the normal selection acknowledgement sound path. Active entity-target commands still take precedence, so their portrait clicks target the selected unit instead of changing focus or collapsing membership.

When an entity-target command is active, the same multiselect-icon click is routed to menu.on_entity_selected instead of changing focus. This preserves the Warsmash behavior where a selected-unit portrait can be used as the target of the active command. Point-only target modes do not change selection focus from such a click.

Focused-unit consumers include the command card, inventory use/drop commands and order-response selection. The complete selection remains authoritative for multi-unit Smart/Move/Attack-style orders. Inventory presentation follows the same focused-unit rule; see Inventory And World Items.

Tab cycles the focused subgroup in the same Warsmash-compatible order used by the multiselect panel. It advances to the first selected unit of the next unit type, wraps from the final subgroup to the first, and leaves selection membership unchanged. A selection containing only one unit type is unaffected. The command reuses the normal focus refresh path, so subgroup highlight, portrait, inventory, and command card stay synchronized, and a successful change plays the authored SubGroupSelectionChange UI sound.

OpenRealm still does not reproduce Warsmash's focused/unfocused icon scaling. That is a presentation gap, not a reason to merge inventory state across the group.

Same-type selection

WC3 enables cl_same_type_select in its game config. Ctrl+clicking a world unit, or clicking the same unit twice within 500 ms without Shift, expands selection to matching units whose entity origins are inside the current rendered world viewport. The shared client sends the clicked entity as the authoritative type anchor plus visible candidate entity numbers. It may use snapshot class_id as a bandwidth/candidate-budget pre-filter, but CMD_Select resolves the anchor's current class_id server-side, discards candidates of other types, then runs the ordinary selectability, relationship, ordering, 12-unit cap, event, sound, and svc_set_selection reconciliation paths.

The client deliberately does not treat render models or snapshot class_id as authoritative gameplay state. While a target command is armed, the existing select target path consumes only the clicked anchor; extra sametype tokens and candidates cannot turn a spell target click into a selection change. Same- type expansion is currently unshifted only because Shift-click selection toggle semantics remain a known gap; Shift-modified target clicks retain the existing queue path. A drag, ground click, minimap click, or gameplay-UI click breaks the double-click chain.

Relationship Presentation

G_CustomizeEntity converts G_SelectionRelation to recipient-relative entity flags:

Relationship Snapshot flag Ring family
friend / own / shared control neither green
passive ally without shared control EF_NEUTRAL yellow
non-passive relationship EF_HOSTILE red

The commonly visible reserved neutral player slots are PLAYER_NEUTRAL_AGGRESSIVE == 12 and PLAYER_NEUTRAL_PASSIVE == 15. Their usual red/yellow presentation comes from the default alliance matrix, not a hard-coded color exception, so map scripts can change the relationship at runtime.

renderer/r_ents.c uses the same flags for both hover and full selected circles. The selection-circle texture and radius remain the existing WC3 data/model choice; persistent selected/hover relationship colours are currently renderer constants rather than SelectionCircle/ColorFriend, ColorNeutral, and ColorEnemy skin data. Smart/right-click target acknowledgement is separate: it resolves the relationship through G_SelectionRelation, reads the exact SelectionCircle palette from UI\MiscData.txt, and sends that colour through the generic transient widget-indicator path. See Command feedback.

Control Boundary

Never use FOR_SELECTED_UNITS for a player-issued multi-unit order. Use:

FOR_CONTROLLABLE_SELECTED_UNITS(client, ent)

The controllable filter is used by Smart/SmartPoint, Move, Attack/Attack-move, Stop, Hold Position, Patrol, Repair, Harvest/Return Resources, and Rally target callbacks. CMD_Button, CMD_Research, inventory use/drop, cancellation, training, and Rally command entry also validate the focused unit before acting.

Entity Smart orders are resolved independently for every controllable selected unit. One unit rejecting a target must not abort the loop. For example, when a Footman and a Hero are selected and the player right-clicks a world item, the Footman may reject that widget while the Hero's inventory accepts it and starts pickup. The first/primary selected entity is used for focused HUD/response presentation, not as a capability gate for the rest of the selection. See Inventory And World Items for the ROC Hero AInv fallback and item lifecycle.

For a live allied unit target that is not consumed by a higher-priority Smart interaction such as Repair, Smart uses a persistent unit-target Move/follow order rather than copying the target's current coordinates. movement.follow_target remains the authoritative default movement goal. Follow stopping distance comes from Misc.FollowRange for units and Misc.StructureFollowRange for targets carrying EF_BUILDING. Buildings with an authored pathTex measure that range from the blocked pathing-footprint edge, with the follower collision radius as the hard no-overlap lower bound; buildings without a usable footprint retain the legacy centre-distance check with both collision radii. This also preserves the Warcraft/Warsmash default rally contract: a producer may remain its own rally target at the model centre, because a trained unit nudged outside the producer will satisfy follow range at the authored footprint instead of walking back into the blocked centre. These values are loaded through the normal Units\MiscData.txt / war3mapMisc.txt chain and are deliberately independent of the follower's attack AcquireRange. The follower may still auto-acquire nearby enemies using AcquireRange, and resumes following after that combat ends. Point Move, Attack-Move, Patrol, Stop, and Hold Position replace this persistent follow goal. Explicit target Attack is a combat detour and may return to the retained follow goal afterward, matching the Warsmash default-behavior split. This includes Neutral Passive units while their directional passive alliance remains enabled; revoking that alliance makes the same unit a Smart Attack candidate instead.

Get_Commands_f clears the command card for a selected unit that the local player cannot control. A foreign building may still use the ordinary inspection panel, but its production queue is not serialized to the viewer. Shared-control units retain command-card access.

Selection acknowledgement voices are queued only for controllable selections. Ordinary non-Neutral-Passive foreign selections use the InterfaceClick UI sound instead of playing the selected unit's authored What response. Neutral Passive critter-specific response rules remain unresolved, so that owner class is not forced through the generic click path.

Selection Lifetime

G_UpdateClientSelections runs immediately after G_FowUpdate each server frame. It scans the raw per-player selection bits (not G_IsEntitySelected, which intentionally hides already-invalid entities), removes any entry that no longer passes G_UnitCanBeSelected, then calls G_SyncClientSelection(). That helper mirrors the authoritative surviving entity list with svc_set_selection and refreshes portrait/info/inventory plus command presentation. The same helper is used after normal select filtering, server-driven Hero/idle-worker shortcuts, and immediate death removal, so the client-side current-selection cache cannot retain entities the server discarded.

Consequences:

  • an enemy that leaves active vision stops being selected;
  • a hidden or newly unselectable unit has its stale selection bit removed.

Death has a stronger immediate path in unit_die: it snapshots which players had the unit selected, sets health to zero, clears all selection bits, sets EF_NOT_SELECTABLE, releases any held animation frame, and starts the death animation. After death/event bookkeeping it synchronizes every connected affected client. This is necessary because clearing the raw bit before G_UpdateClientSelections() means the later revalidation pass cannot discover which client lost membership. The info-panel cache also retains the last non-single selection count so a multiselect that shrinks but remains a multiselect is reserialized instead of leaving the corpse icon behind. The order entry points reject dead units as well, so a corpse cannot be picked or receive a new movement/order that would replace its death animation. G_ReviveHero clears EF_NOT_SELECTABLE when the persistent Hero edict is revived.

Known Gaps

Numbered group assignment, Shift+number append, recall, and double-tap camera focus are documented separately in Control Groups. Shift order queuing is documented in Shift Order Queue; the remaining Shift-click item below is selection toggling, not command queuing.

The following are deliberately not inferred by the current implementation:

  • Ghost/Ghost Visible still need full invisibility/selectability integration; Shadow Meld, Apiv, Sorceress Invisibility, Wind Walk, and ward true-sight use the player-aware visibility path, including owner/shared-vision access and hostile detector-gated selection;
  • Shift-click toggle semantics (Shift-drag addition exists separately);
  • exact Warsmash within-identical-type insertion ordering (selection membership currently retains only per-player bits, so stable ties use edict scan order);
  • exact neutral-shop Aneu patron-selection button/indicator and persistent per-player patron state; the current deterministic nearby-patron purchase flow is documented in Neutral Shops And Mercenary Camps;
  • data-driven SelectionCircle relationship colours;
  • Neutral Passive critter-specific selection response rules;
  • advanced-control UI controls, abandoned-player transitions, and retail Team Resources board layout;
  • EVENT_PLAYER_UNIT_SELECTED/DESELECTED currently reuse the generic player-unit owner-matching dispatcher. This is sufficient for own-unit campaign/tutorial selections such as Prologue02 Peons, but selecting a foreign unit should ultimately bind/match the selecting player rather than the selected unit's owner.

Combat reaction remains a separate gap from relationship classification: OpenRealm does not yet apply Warsmash's PEON suppression/canFlee civilian reaction policy when a non-combat unit is damaged. Likewise, a hostile relationship does not yet imply correct per-weapon attackability for ordinary units. Destructables now decode UnitWeapons.targs1 target-name lists, use debris for Smart/right-click breakables, permit explicit Attack on trees even though stock weapon lists commonly omit tree, and enforce the mask for the remaining destructable classes; broader unit target-mask coverage remains incomplete. See Warcraft III Attack Damage.

Do not bypass these gaps by weakening G_UnitCanControl or by restoring owner checks inside selection itself. Selection and command authority must remain separate.

Verification

In-engine coverage is in games/warcraft-3/game/tests/t_api.c and t_unit.c for relationship classification, visible foreign selectability, shared-control authority, dead-unit non-selectability, selection removal, Hero revival restoring selectability, selection/deselection JASS event deltas, same-type server filtering, Warsmash priority/level/canonical-rawcode multiselect ordering, and exact-focused multiselect portrait second-click collapse. Shared client_input coverage drives the real click-release path for Ctrl-click and double-click same-type packet generation and viewport candidate filtering. t_items.c additionally covers mixed-selection Smart item pickup with a non-inventory unit first in the selection.

Useful targeted commands after building the test binary:

make test-wc3-engine WC3_PATTERN='wc3_api.*'
make test-wc3-engine WC3_PATTERN='wc3_unit.*'

Runtime verification should also cover an enemy walking from visible terrain into fog, Neutral Passive/Hostile circle colours, and attempting Smart/command-card orders while a foreign unit is selected. For multiselect ordering, drag-select a deliberately mixed group (for example a Hero plus several different unit types) and confirm the status-panel icons remain grouped/sorted by priority, level, then rawcode regardless of entity spawn order; the first sorted unit should own the initial command card and selection response.