SROCodex
ironsro

Potions

What a potion gives back on this site, how often you may reach for one, and why the six universal pills are not the same item — each answer marked as the game's, ours, or missing.

verified: partialSR_GameServer.exe — the item loader and the use handler, at the addresses in the appendixsro_client.exe — the cooldown a confirmed use arms, NetProcessInInner.cpp @87a8ba…@87ac0f (the pace)IRONSRO_SHARD._RefObjItem — the columns every number here comes from

What this site does when you drink something, and where that is the real game. The marks are the same on every page in this section: Game — read out of SR_GameServer.exe or the shard's own tables; Ours — our decision, because the game's answer is not in anything we can read; Missing — not modelled.

What a potion gives back · Game

A potion is an ordinary item whose first four columns say what it restores: a flat amount of health, a share of your maximum health, a flat amount of mana, a share of your maximum mana. A row is one kind or the other — the server asks the flat columns first, and only a row with neither reads the shares.

A flat potion grows with the drinker (the use handler, 0x49aa70):

health = flat × (1 + STR / 416) × 1.02 ^ (level − 1)
mana   = flat × (1 + INT / 416) × 1.02 ^ (level − 1)

truncated. So the same herb is worth about ten times more at level 110 than at level 1, and more to a strong body than to a clever one — which is what keeps a "Recovery Herb" of 120 abreast of a bar that grew to eight thousand. It arrives a fifth at once and four fifths over the next four seconds, a fifth a second (the body's recovery queue, 0x49a5b0), and a potion drunk while what the queue still owes would already fill the bar is swallowed and lost.

A share potion — the grains — is that share of your maximum, at once, and one that would overfill the bar also empties the queue behind it. Both are clamped to your maximum, so overhealing is quietly thrown away.

(Until 2026-09-18 this page said flat plus share, unscaled and at once; that was read off the columns, not the handler.)

How often you may drink · the client's

Not one potion carries a cooldown of its own. Items can — summon statues and transport scrolls do, twenty of them on this shard, from a minute to an hour — and no health, mana or vigour potion is among them. The server refuses nothing.

What paces you instead is the client: the moment the server confirms a mouthful, the client arms a clock for that kind of item, and the numbers are in its code (the appendix has the addresses):

Gap between two of the same kind
Flat potion, Chinese character 1 s
Flat potion, European character 15 s
Percentage grain, either race 4 s
An HP potion while you are panicking, an MP one while combusting + 4 s
Universal pill 1 s
Purification pill 20 s

Flat or percentage is read off the item, not off its name. Each kind keeps its own clock, so a mouthful of mana never holds up the health potion beside it. This site's fight keeps the same clocks, and adds the four seconds where the status is on you.

One consequence catches European characters out here. Every grain on this shard has no level requirement while the flat potions climb 1, 15, 30, 45, 60, 121 — and "whatever is in the belt" takes the highest requirement your level allows. So the default hands a European a fifteen-second potion at every level. The four-second grain is there; it has to be picked by name.

Pills, and why the grade matters · Game, then measured

The two pills answer different halves of what can be stuck to you, and the split is the server's own — it stamps each of its thirty-two status slots with a cure class as it builds them.

Universal pill Purification pill
Answers frozen, frostbitten, shocked, burnt, poisoned, zombie asleep, rooted, slowed, afraid, short-sighted, bleeding, darkness, disease and eight more
How many at once all of them one, drawn from those it names
How it decides a strength, weighed against the status a roll — whole up to the pill's level (3, 6, 9 or 12 by grade), and divided by how far the status's level stands above it

Neither touches stun, and that is the game's rule rather than an omission: petrify, stun, confusion and combustion run out on their own.

The universal pill's strength is what makes its six grades six different items. Measured against the 8,382 elemental status effects this shard's skills declare:

Pill Strength Statuses it ends outright
Universal Pill (small) 36 4.6 %
Universal Pill (medium) 68 14.7 %
Universal Pill (large) 108 28.3 %
Special Universal Pill (small) 172 51.2 %
Special Universal Pill (medium) 228 73.1 %
Special Universal Pill (large) 268 88.3 %

Strong enough and the status comes off. Not strong enough and the pill takes its strength off the status's remaining time instead — so a small pill against a long burn is neither wasted nor an answer, and the icon stays on your strip with a shorter clock.

Read to the end (2026-09-18): the applier that spends the number is 0x4a56c0, the same one the cure skills go through — the magnitude becomes milliseconds on the status's own per-level scale and comes off its start time (@4a58f7), and if what is left has run out the status ends. The appendix has the whole chain.

What this site does not do

Missing Why
An inventory Nothing is consumed here, so nothing runs out and no stack is tracked. A fight on this site is about the numbers, not about shopping
The player's own pace setting In the game, how often the auto-potion window fires is a number you pick, in tenths of a second. This site uses the game's pace rather than yours; the belt cannot carry a setting yet
Recovery over time It exists here, but only while standing still — so inside a fight the belt is still what keeps you alive

Reading the real numbers

Every potion's own columns are in this pack: the item page prints the raw block, and the consumables list is /items/consumables.

For anyone opening the binary

Everything below is the research the page stands on — where an item's cooldown really lives, what was checked and found absent, and the exact groups the pills and the cure skills share. It is written for somebody verifying us against the real server, not for somebody playing.

What the use handler does with the four columns

The potion branch of the item-use handler (@49b8a2…@49b94b) asks 0x49aa70 for the two amounts off the item's row — Param1 at [row+0x2a0], Param2 at [+0x2c0], Param3 at [+0x2e0], Param4 at [+0x300]. With a flat column set (@49aa95…@49aaa9) the flats are taken and scaled (@49ab63…@49ac2b): 1.02 (0xb45ea0) to the power of the level less one, off GetStat(0), and 1 + GetStat(1) / 416 (0xb45e98) on the health, 1 + GetStat(2) / 416 on the mana, truncated (0x9fbb40). With neither flat set the shares are taken of GetMaxHP ([vt+0x110]) and GetMaxMP ([vt+0x114]) (@49aac9…@49ab3b). Both nought is refused with error 2 (@49b8fb). The amounts then go to the body's recovery queue, 0x49a5b0 on [body+0x1d58], through 0x4ef450 with the mode the kind decided: a share potion in mode 0 is put on the bars at once (0x4a86a0) and, where it would overfill one, empties that bar's queue (@49a636, 0x695450); a flat potion in mode 1 becomes an entry of trunc(amount / 5) a tick for four ticks (0x49a0c0, clamped 1…1 000 000), queued only while the bar plus what its queue still owes (0x49a110) is not past the maximum (@49a75b…@49a76a), with one fifth put on the bar at once (@49a80f…@49a82a). The player's tick (0x52aa90 @52acd0) runs the queue (0x49a510): every entry's fifth onto its bar, a tick counted off, the entry dropped at nought. The tick is a second — the player's 1.0 s timer, registered as the body is made (@4a702b) and counted in the seconds the main loop hands every object (@4029bf) — so a flat potion is five seconds of drinking (lib/battle/potion.ts SIP_TICK_MS).

Where an item's cooldown really lives

An item can carry one, and the place is not a column — it is a Param/Desc pair, which is why nobody finds it by reading the schema.

_RefObjItem has twenty of those pairs. The loader walks them, and for each one whose DescN_128 is not the "xxx" placeholder it looks for the word cooltime in the text, takes everything after the : — hexadecimal when it starts 0x, decimal otherwise — and keeps it as the item's cooldown group mask, with the matching ParamN as the duration in milliseconds:

DescN_128 = "COOLTIME:0x000000CA"     ParamN = 3600000

read at @6a48fb…@6a4a97 in SR_GameServer.exe (the ReferenceData.cpp item loader), stored as [RefObjItem + 0x520] (mask) and [+0x524] (duration). A bad number after the colon is refused by name — that is the invalid parameter has been assigned for item cooltime: %s, %s line in the binary.

The mask is four one-byte group ids, not a number: the check walks it a byte at a time (shr 0, 8, 16, 24) and looks each up in a keeper the character carries at +0x1f40, which holds {duration, startedAt} per group and refuses while now - startedAt < duration (FUN_0064c040). Using the item writes the groups back (FUN_0064c320). So two different items can share one cooldown by naming the same group, and one item can sit in four groups at once.

Twenty distinct (mask, duration) pairs exist on this shard, from a minute to an hour: guard summons, transport scrolls, the siege manuals, the event experience scroll.

No cooldown on a potion, and no stack limit

Not one HP, MP or vigour potion declares a COOLTIME. Every one of them carries Param5 = -1 and no cooltime text in any of its twenty description slots — 1 661 rows on IronSro and 1 434 on vanilla, every Param/Desc pair of each — so the server refuses nothing. Nor does anything else in the handler: the whole of 0x704C (@0x510980…@0x5112c1) reads a clock in exactly one place, and that place is the cooldown-group keeper above (@0x510d78 → FUN_0064c040, written back at @0x5112bc).

Nor does the shard treat a Chinese and a European character differently. The potions carry no race (ReqLearn_Race is unset on all of them), the client's auto-potion panel is the same three slots for both — AUTO_POTION_COUNT is 3, HP, MP and vigour, each a threshold in per cent of the maximum and a chosen inventory slot (AutoPotion.cpp, @82ccd0…@82ce63) — and the server's use handler (0x704C, @0x510980) reads the character's race nowhere along the path.

MaxContain = -1 likewise means the stack is unbounded in the reference row; the practical limit comes from the inventory slot.

The client's own pace, which is a setting

CAutoPotion carries a delay of its own at [this+0x38]. Its constructor writes 500 ms into it (@0x82d034, mov [ebx+0x38], 0x1F4), and the saved settings overwrite it as

delay = (value & 0x7F) × 100 ms      @0x82cff7…@0x82cfff

— tenths of a second, from nothing up to 12.7 s, with bit 7 of the same byte kept beside it as a flag ([this+0x34]). So how often a player's macro drinks is a number that player chose, and 500 ms is what a client that has never been told otherwise uses. It replaces FORMULAE.md §6's interpreted second: the second was a reading of what a player experiences, this is what the client ships.

The important part is not the number, though — it is that there are three of them. The panel runs one timer per slot: HP at @0x82d0e0 (timer 0xe), MP at @0x82d160 (0xf), vigour at @0x82d1d0 (0x10), each armed from that same delay and each answering on its own. Nothing anywhere holds the belt down as one thing.

How often a potion may actually be drunk — the client's own table

Everyone who has played knows the rule: a Chinese character drinks about once a second, a European one about once every fifteen, and the percentage grain both of them fall back on is about four. It is the race-balance lever the European classes were built around — the reason an EU party wants a Cleric.

It is not in the shard's data or its server, and each of the three places it is usually said to be was checked:

Where it is said to be What is there
15000 hardcoded in SR_GameServer.exe's potion function The whole binary holds six 0x3a98. Five are add reg,0x3a98 — structure offsets. The sixth is cmp eax,0x3a98 at @4e694a, inside a threshold ladder at @4e6930 returning 5 / 30 / 50 / 100 across 0, 4 000, 15 000 and 30 000. Not a delay, and IronSro's own SR_GameServer.exe carries the same six at the same addresses. The potion's own use path (0x4d57b8 → 0x49b710 → 0x49aa70) reads no clock at all
A _RefCooltime table in the shard No such table: nothing matching %ool% or %Delay% among SRO_VT_SHARD's 210 or IRONSRO_SHARD's 211 beyond _ItemPool and Tab_RefSpawnToolVersion
refcooltime.txt in Media.pk2 Not in either client. Walking all 34 580 and 22 072 entries, the only "cool" files are effect/icon/cool_time_0..9.ddj — the skill cooldown sweep

Where it is is the client, and it was read there (2026-09-18): sro_client.exe's handler of the server's answer to a use (NetProcessInInner.cpp, @87a8ba…@87ac0f) arms a cooldown per kind the moment the mouthful is confirmed:

Item Delay Where
a flat potion (3/3/1, Param1/Param3), Chinese character 1 000 ms @87a96f, by m_byCountry ([player+0x9c], @87a922)
the same potion, European character 15 000 ms @87a968 — any other country is an assertion, "not a Chinese nor a European country"
a grain (Param2 or Param4 set) 4 000 ms, whatever the race @87a97e…@87a990
an HP potion under panic (cshp, status bit 21) + 4 000 ms @87a9b6, [player+0x2c4] & 0x200000
an MP potion under combustion (csmp, bit 22) + 4 000 ms @87a9d7, & 0x400000
a vigour potion the same delay; none at all while [player+0x540] is set @87a9fa (the flag is unread)
the universal pill (3/3/2/6), the pet's (/7) 1 000 ms @87abd3, @87abf9
the purification pill (3/3/2/1) 20 000 ms @87ac02

Each kind on its own timer — the same five clocks RSBot's UsePotion (external/RSBot/Library/RSBot.Core/Objects/Player.cs) keeps from the other side of the wire, and where its 1050, 15050, 4050, 20050 came from: the round number is the client's, the fifty a bot's margin against a clock it cannot see.

Two things follow, and the fight does both (lib/battle/pace.ts).

Potion or grain is read off the item, not off its name. The client's branch is the item's own columns, and they are the four this pack already carries: Param1/Param3 are the flat recovery, Param2/Param4 the percentage. ITEM_ETC_HP_POTION_01 is "HP Recovery Herb" and flat; ITEM_ETC_HP_SPOTION_01 is "HP Recovery Grain (Small)" and percentage.

Which settles what a European's belt actually feels like, and the answer is: slow. Every grain row carries req_level = 0 while the flat potions climb 1, 15, 30, 45, 60, 121, and "whatever is in the belt" takes the highest requirement the level allows — so the default rule hands out a flat potion at every level, and a European on the default belt really does wait fifteen seconds. The four-second grain is there, but it has to be named: a rule may carry an item of its own, and choosing one is the single decision on that panel that changes the pace of a fight.

And the status adds its four seconds. The fight keeps the same clocks — 1 000, 15 000, 4 000, 1 000 and 20 000 — and, as it drinks, adds the four under panic or combustion (SIP_STATUS_EXTRA_MS).

Vigour fills two bars, so here it has two shares

The client's panel gives it one. All three of its slots are the same shape — a threshold in per cent of the maximum and a chosen inventory slot (AutoPotion.cpp, @82ccd0…@82ce63) — and for a potion that fills health and mana that single number can only ever mean "either of them fell this far". Which is not a rule anybody sets: health halved is one moment, mana nearly out is another, and a player who wants the grain for mana does not want it every time a monster lands a hit.

RSBot's protection panel asks each bar on its own, and has all along: checkUseVigorHP with numPlayerHPVigorPotionMin beside checkUseVigorMP with numPlayerMPVigorPotionMin, its handler testing mana against its own minimum first and health against its own second (external/RSBot/Plugins/RSBot.Protection/Components/Player/VigorRecoveryHandler.cs). An unticked box there is a bar it does not watch at all.

That is the shape this site's panel takes: two shares for vigour, one per bar, with off as the first choice on each — the bot's unticked box. The room fires the mouthful when either watched bar is at or below its own share (lib/character/potions.ts barShares, game/battle.ts sip). It is the bot's answer rather than the client's, and it is said out loud here for that reason; a rule saved before the split carries one number, and both bars keep reading it, because that is exactly what it said.

What a pill is, inside the server

A pill is not a special item type to the server either — it is one of the same tagged parameter groups a skill uses to clean a status off, written into an item's columns instead of a skill's. The cure skills say so plainly: `Force Cure

  • Poison` at level 1 carries
curt [63]        [36]
curl [25145280]  [100]  [1]
rcur [2]

and the two pills are those first two groups, field for field — curl's mask, chance and level are the purification pill's Param1, Param3 and Param2 (the server hands the applier {Param1, Param3, Param2} and a count of one, @49b829…@49b855), and curt's magnitude is what the universal pill repeats across six columns, one per elemental status. The magnitudes even meet: the level-1 cure skill's curt is 36, and so is the smallest universal pill's.

The curl half is a roll, not a certainty: the applier (0x4a56c0, @4a5801…@4a5810) takes the chance whole while the status's own level is at the group's third word or one above it, and divides it by the gap past that — Force Cure rank 1 ([…, 100, 1]) clears a sleep written for level 8 at 100 ÷ 7 = 14 %, the X-Large purification pill (level 12) clears it outright and a level-14 one at half. A lost roll leaves the status as it was, and the magnitude half does not weigh it either. Which status the pill's one draw falls on is the applier's shuffle.

Which statuses each group may touch is the server's own, not the item's. Every character carries thirty-two status slots (keeper + 0xc + id × 0x70, built at @4a40c0), and as each is built the server stamps it (@5aa450) with its id, its client bit, and a cure class:

Class Test on the status's bit Meaning
1 in 0x3F the six elemental — what a universal pill answers
2 in 0x17FEFC0 what a purification pill answers
0 neither nothing cures it

Two details worth having written down. The server's class-2 mask 0x17FEFC0 includes stun; the purification item's own mask, 25145280, does not — they differ by exactly one bit. And taking a status off is a primitive of its own (@4a5660): deactivate the slot, call its handler with 2, rebuild the character's mask and announce it.

What the magnitude is measured in was settled against the shard's own rows rather than the binary. An elemental status has a level — its strength after immunity and resistance — and lasts that many multiples of its per-level constant. Across the 8,382 elemental effects these 35,427 skill rows declare, that strength runs min 1, p25 100, median 170, p75 234, p90 274, and the pill ladder 36 / 68 / 108 / 172 / 228 / 268 ends 4.6 %, 14.7 %, 28.3 %, 51.2 %, 73.1 % and 88.3 % of them outright — a median status of 170 against a middle pill of 172. curt on the cure skills runs the same scale in the same steps (36, 42, 48, 54, 60, 66, 68, 76 … 300) with a lone 9999, which is only ever "always enough".

So the magnitude is a strength on the status's own level scale, and this site's fight uses it: strong enough ends the status, not strong enough takes the magnitude off its remaining time (lib/battle/cure.ts, This site's fight). And the server code that consumes it was found (2026-09-18): the one applier 0x4a56c0 — the pill's six columns in its first argument (@49b7d8), a skill's curl/curt/rcur in the next three (@594a89) — turns the magnitude into milliseconds through the same FUN_00410b40 table a status's length comes from (@4a5868) and takes them off the status's start time (@4a58f7); the purification half is a roll instead, Param3 whole up to the Param2 level and divided by the gap above it (@4a5801), one status a pill (@49b855).

Reading it live

Every potion's actual numbers are in this pack — the item page shows the raw Param block, and the consumables list is /items/consumables. Nothing on this page needs to be kept in sync by hand, because the numbers are not on this page: the mechanism is, and the mechanism has not changed since 2013.