Skip to content

Warcraft III Research And Upgrades

OpenRealm models Warcraft III research as player-owned techtree state started by a producer building. A Blacksmith only supplies the research command and queue; a completed upgrade belongs to the player and survives the researching building.

Reference behavior

The implementation here was checked against a WarsmashModEngine main snapshot dated 2026-08-31. The relevant Warsmash paths are CAbilityQueue / CUnit / CPlayer for research ownership and queueing, CUpgradeData / CUpgradeType / CUpgradeEffectAttackDice / CUpgradeEffectDefenseUpgradeBonus / CUpgradeEffectAttackRange / CUpgradeEffectHitPoints / CUpgradeEffectSpellLevel for UpgradeData.slk and the implemented effects, and MeleeUI for attack/armor status-icon family selection.

The effect and status-icon behavior documented below is Warsmash-specific where stated; remaining retail-parity gaps stay listed under Intentionally unresolved rather than being filled with guessed behavior.

Data sources

The runtime reads upgrade metadata from Units\UpgradeData.slk:

  • maxlevel
  • goldbase / goldmod
  • lumberbase / lumbermod
  • timebase / timemod
  • class
  • inherit
  • the four effect, base, mod, and code slots (effect/code are stored as FOURCCs)

Buildings expose upgrades through the Researches (ures) field already parsed into UnitProfile_t.researches. Unit types opt into completed upgrade effects through UnitBalance.upgrades (upgrades / Upgrades Used).

The race *UpgradeFunc.txt and *UpgradeStrings.txt tables remain authoritative for research art, button positions, names, tooltips, hotkeys, and per-level requirements.

Research availability

For upgrade rawcode U, the next level is:

player.researched[U] + 1

The research command is hidden when:

  • the next level exceeds UpgradeData.maxlevel;
  • SetPlayerTechMaxAllowed restricts the player below that level; or
  • the same upgrade is already in progress for that player.

Per-level Requires and Requiresamount fields disable the command until the requirements are satisfied. Level 1 uses the unsuffixed fields; level 2 uses Requires1 / Requiresamount1, level 3 uses Requires2 / Requiresamount2, matching Warcraft object-data convention.

Costs are:

gold   = goldbase   + goldmod   * (level - 1)
lumber = lumberbase + lumbermod * (level - 1)
time   = timebase   + timemod   * (level - 1)

Gold and lumber are charged when the research is accepted. The command-card tooltip uses the same next-level calculation and the ordinary Warcraft tooltip resource row, so research buttons show gold/lumber icons and costs exactly like training buttons. Because the next level is player-owned state, command-bar serialization must set the current UI client before formatting those tooltips.

Production queue

Research shares the existing seven-entry producer queue with training and Hero revival. A queued research item is a lightweight hidden server entity carrying only the upgrade rawcode, level, exact charged resources, duration, and elapsed progress.

Only the queue head advances. Research does not reserve Food Used. The building uses the same production think path as unit training, so a building that can both train and research preserves the authored queue order instead of running a second independent timer.

The selected-building info panel shows:

RESEARCHING

for an active research head, uses the level-specific upgrade Art, and drives the existing progress bar from simulation progress.

Cancelling a queued research item:

  • removes it from the shared queue;
  • refunds the exact gold/lumber charged for that item;
  • clears the player's in-progress marker; and
  • activates the next queue head.

Producer destruction uses the same queue cancellation path, so unfinished research is cancelled/refunded while completed research remains on the player.

Completion and player state

Completion clears the in-progress marker and sets the player's researched level to the level carried by the queue item. The command card is invalidated so the next level (or no button at maximum level) becomes visible immediately.

The local player also receives the upgrade-complete text and the active race skin's ResearchComplete UI sound when those presentation resources are available.

SetPlayerTechResearched and AddPlayerTechResearched use the same stat-effect path as normal completion. Scripted tech changes therefore update live unit stats rather than only changing the integer queried by JASS/AI.

JASS research lifecycle events

Accepted research publishes both the player-unit and unit-scoped start events:

EVENT_PLAYER_UNIT_RESEARCH_START
EVENT_UNIT_RESEARCH_START

Cancelling a queued research item publishes the matching *_RESEARCH_CANCEL events, and completion publishes the matching *_RESEARCH_FINISH events. The triggering/researching unit is always the producer building, so GetTriggerUnit() and GetResearchingUnit() resolve to that building. GetResearched() returns the exact upgrade rawcode carried by the research queue item.

Do not recover GetResearched() from the producer's current queue head. Event delivery is deferred, and cancel/finish removes and frees that queue item before the JASS callback may run. GAMEEVENT.value therefore carries the rawcode as a scalar event payload into JASSCONTEXT.eventValue. This also makes the payload stable if another queue item becomes active before the callback executes.

The scalar payload is part of save/load state: unread game events serialize it, and sleeping JASS callbacks snapshot it with the coroutine context. Save format 14 pairs this with JASS snapshot format 3.

The Prologue02 tutorial depends on this contract at the War Mill lesson: its Trig_U2_ClickUpgrade_Done trigger registers EVENT_PLAYER_UNIT_RESEARCH_START and advances only when clicking an upgrade produces the research-start callback.

Runtime stat effects

This implementation intentionally enables only the upgrade effect codes whose current Warsmash behavior is sufficiently unambiguous. Effects remain driven by UpgradeData.slk plus each unit type's Upgrades Used list; Human Barracks research such as Long Rifles and Animal War Training does not use unit-rawcode special cases.

ratx — flat attack damage

For an affected runtime attack, each level's effect value is:

base + mod * (level - 1)

Changing level applies only the old/new delta to damageBase and records the same delta in permanentDamageBonus. The ledger keeps the research bonus intact when Hero primary-attribute recomputation rebuilds base attack damage.

ratd — attack dice

For an affected unit, each level's effect value is:

base + mod * (level - 1)

Changing level applies only the difference between the old and new value to runtime attack dice. This avoids stacking the full new level on top of the old one and preserves unrelated Hero/item damage modifiers.

rarm — defense upgrade bonus

Armor follows the Warcraft unit type's defUp / armorPerUpgrade value:

upgrade armor = armorPerUpgrade * researched level

Changing level applies the delta between old and new levels to both edict_t.permanent_armor_bonus and edict_t.armor_value. Hero Agility recomputation rebuilds base armor plus this persistent ledger, so researched armor is not lost when attributes change.

ratr — attack range

For each authored runtime attack, the level value is:

base + mod * (level - 1)

Changing level applies only the old/new delta to unitAttack_t.range. Both Attack 1 and Attack 2 receive the effect when that runtime attack exists. This matches Warsmash's CUpgradeEffectAttackRange contract and lets upgrades such as Human Long Rifles (Rhri) flow through the ordinary player-tech path. The combat behavior already queries the mutable runtime range, so existing and newly trained affected units use the researched distance without replacing the unit type.

rhpx — maximum hit points

The authored level value uses the same base + mod * (level - 1) rule and is added to maximum life. When the maximum changes, current life keeps the same percentage of maximum life, matching Warsmash's relative max-life adjustment.

edict_t.permanent_health_bonus records the research-owned contribution so a later Hero primary-attribute recomputation cannot discard it. Existing owned units and future units use the same generic upgrade application path. Human Animal War Training (Rhan) therefore works for every affected unit type that lists that upgrade in Upgrades Used, rather than through a Knight-only table.

rlev — ability/spell level

rlev uses the effect code as the affected ability rawcode. Matching Warsmash, researched level N sets that ability to level N + 1; removing the research returns it to level 1. The base and mod numeric columns are not used by this effect.

Some Warcraft units author a research-granted command in their normal ability list before the research completes. For those units, OpenRealm correlates the unit's Upgrades Used list with any rlev effect targeting that command. The command is hidden when its required upgrade level exceeds the player's maximum allowed level; if that level is allowed but unresearched, the command remains visible but disabled. Direct execution is rejected until the owning player has researched the matching upgrade. Human Footman Defend (Rhde targeting Adef) uses this path; custom unit/upgrade pairs using the same data contract inherit it without rawcode-specific code.

Some stock dependencies are authored as gate-only upgrade rows with no effect or target code. checkDep indicates that an ability checks dependencies, but it does not identify the governing upgrade. Comments are descriptive text, so OpenRealm does not infer links from overlapping words. These retail-only facts are held in stock_ability_requirements in g_building.c, separately from the generic rlev data path. This is a deliberate, narrow hardcoded compatibility exception: retail rows contain no structured link to parse, comments are not authoritative identifiers, and reqLevel has Hero-skill meaning. Undead Ghoul Cannibalize (Ruac / Acan) uses this table; it has the same command-card and direct-execution gate as an rlev dependency, but the ability does not gain a level from the research.

Retail Sorceress training (Rhst) is an explicit stock relation that the SLKs do not encode as an effect/code pair: hsor lists Rhst in UnitBalance and Aivs / Aply are marked checkDep, while the Rhst UpgradeData row only contains stat effects. Aivs, Aply, Adis, and Ainf all have AbilityData.reqLevel=0; checkDep=1 marks dependency checking but doesn't encode a training tier. Their levels=1 is the ability rank count, not a training tier. Rhst and Rhpt have maxlevel=2, but their effect codes (rmnx, rmnr, rhpx, ratd) don't name the unlocked spell.

reqLevel cannot safely supply this tier. Retail normal abilities such as Heal and Defend also have reqLevel=0; Hero skills such as Blizzard, Water Elemental, Flame Strike, Holy Light, and Channel have reqLevel=1 and levels=3. For Hero skills, reqLevel works with levelSkip to specify the Hero level at which each skill rank can be learned. It is unrelated to caster training, and map ability edits can change it through arlv without changing the meaning.

OpenRealm therefore uses one small stock compatibility table in g_building.c for the links that retail omits: Invisibility (Aivs) requires Rhst level 1, Polymorph (Aply) level 2, Dispel Magic (Adis) requires Rhpt level 1, Inner Fire (Ainf) level 2, and Cannibalize (Acan) is gated by Ruac. Custom dependencies use the parsed UpgradeData.effect / effectCode pair when it contains rlev; descriptive comments alone do not create a dependency. The generic rlev path doesn't recognize these stock spell rawcodes specially.

UnitBalance.upgrades links a unit to the upgrade; Upgrades Used alone doesn't name which ability each caster-training level unlocks. Human05's Sorceress still lists Rhst as its training upgrade and UnitAbilities still contains Aivs and Aply. Its script sets Player 1's maximum allowed Rhst level to 1, so Invisibility is permitted while Polymorph is hidden.

OpenRealm uses the resolved requirement for both command-card and direct activation checks. If the required level exceeds GetPlayerTechMaxAllowed, the command is hidden. If the level is allowed but not researched, the command stays visible and disabled. The ability is usable only while its required level is within the current maximum and has been researched; lowering the maximum after research hides the command and blocks direct activation. SetPlayerAbilityAvailable remains a separate per-player rawcode switch that can suppress an ability regardless of training. Unmapped owners do not use the fallback client returned by G_GetPlayerClientByNumber; their research state cannot be borrowed from a real player's slot.

Map object overrides can edit a unit's UnitBalance.upgrades and map ability data is parsed from war3map.w3a. The ability override path applies fields such as alev, arlv, and DataA-I, but arlv retains its Hero-level meaning. OpenRealm does not currently merge war3map.w3q upgrade-object overrides, so custom maps cannot yet supply a new rlev upgrade-to-ability relation through that file. Unit object edits can change which upgrades a unit uses, but cannot author the missing spell-to-upgrade tier. The stock table remains necessary until a supported authoritative source exposes those links and tiers. Do not infer them from text fields or repurpose reqLevel.

rmnx / rmnr — mana capacity and regeneration

rmnx changes maximum mana by the authored old/new level delta and moves current mana by that same absolute delta, clamped to the new maximum. rmnr records the authored regeneration delta in mana_regen_bonus.

For Heroes, G_RecomputeHeroStats reads the current rmnx total from the player's researched tech and the unit's Upgrades Used list. Research changes recompute that derived maximum instead of adding the delta again. This keeps the upgrade alongside Intelligence and item mana bonuses when Hero attributes change or a form is rebound, without another per-unit save field.

Existing owned units whose Upgrades Used list contains the upgrade are updated when player tech changes. Newly spawned/trained units run the same application path after their base/Hero stats are initialized, so they inherit research that was completed earlier.

The existing info panel resolves the attack/armor upgrade holder from the unit's Upgrades Used list and the upgrade's class; it therefore displays the same player researched level that drives these effects. The class lookup is shared with game-side upgrade data rather than re-parsed independently by the HUD.

Upgrade families remain distinct even when they share the same armor class. For the stock Human data, for example:

  • Rhar is the heavy-unit armor family used by units such as Footmen;
  • Rhla is the light-unit armor family used by Riflemen; and
  • Rhac is the building armor family used by Human structures.

Researching Rhar therefore must not make a Rifleman's Rhla holder or a building's Rhac holder display level 1. Each holder displays the researched level of the rawcode actually present in that selected unit type's Upgrades Used list. Units whose listed upgrades have no attack/armor class, such as the stock Peasant list, keep those level holders hidden.

Intentionally unresolved

This patch does not guess at the remaining Warcraft upgrade effect codes. Movement, attack speed, hit-point regeneration and other upgrade effect families still need clean data-driven implementations before they should mutate runtime units.

The following compatibility work also remains:

  • war3map.w3q custom upgrade-object overrides are not yet merged into the normalized runtime upgrade rows;
  • W3I upgrade-availability records are parsed but are not yet applied to the research state;
  • Attack 2 runtime stats are initialized and receive ratx/ratd, but combat order selection still attacks through Attack 1; full WC3 Attack 1/Attack 2 selection remains unresolved;
  • ownership-transfer semantics for upgrades whose inherit flag controls transfer are not changed here;
  • UpgradeData global semantics and any timing/game-speed adjustment beyond the authoritative SLK base/increment values are not introduced speculatively here.

These gaps should be completed from authoritative Warcraft/Warsmash behavior rather than by adding Blacksmith-specific rawcode tables.

See also Attack Damage for the runtime damage/armor formula and modifier ownership.