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:
maxlevelgoldbase/goldmodlumberbase/lumbermodtimebase/timemodclassinherit- the four
effect,base,mod, andcodeslots (effect/codeare 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:
The research command is hidden when:
- the next level exceeds
UpgradeData.maxlevel; SetPlayerTechMaxAllowedrestricts 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:
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:
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:
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:
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:
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:
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:
Rharis the heavy-unit armor family used by units such as Footmen;Rhlais the light-unit armor family used by Riflemen; andRhacis 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.w3qcustom 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
inheritflag controls transfer are not changed here; - UpgradeData
globalsemantics 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.