Unit Selection And Control
Contract
Warcraft III selection is server-authoritative and keeps three decisions separate:
games/warcraft-3/game/g_commands.c owns the shared policy:
G_UnitCanBeSelected(client, ent)accepts a liveSVF_MONSTERthat is in use, not hidden, notEF_NOT_SELECTABLE, and actively visible throughG_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_UnitCanSpendResourcesadditionally 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_SetPlayerAlliancechanges onlylevel.alliances[source][other]; callers that want mutual alliance must set both directions. Friend/enemy tests useALLIANCE_PASSIVEspecifically—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():
UnitData.slkprio/UnitData_t.priority, descending;UnitBalance.slklevel/UnitBalance_t.level, descending;- unit type rawcode, descending in canonical Warcraft
War3IDbyte 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:
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
Aneupatron-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
SelectionCirclerelationship 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/DESELECTEDcurrently 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:
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.