Inventory, unlockables and statistics¶
Verified against: SLUS_212.15 (NTSC-U, SHA1 e9cb2cc49aa046b9e494313dce2f5038ed17b2f4), static analysis only
(Ghidra, and the disc's compiled scripts read with coney-tools' Lua walker); the pickup callback also at runtime
(PCSX2 2.9.94, 2026-10-06, scenario store_loot, Combat: breakables).
Purpose¶
What the game keeps about a player besides the human: the inventory (items, money, flashes), the unlockables that the story opens (levels, Rumble content, upgrades, hints) and the statistics that score a mission. The ids are listed in Inventory items, Unlockables and Statistics; this page says how the game uses them.
Original structure¶
W_UnlockManager.cpp holds the unlockables manager (Source map); the end-of-mission screen is
GUI/GameStats.cpp (the path is a string at 0x00559f70); its line items (GameStatsSubItem.cpp) run on past
0x002176b8 into the human code's range. Names are ours.
| Address | Name | Role | Evidence |
|---|---|---|---|
0x0041e250 |
Inventory_SetItem |
CfgInventoryItem's writer, for both players |
confirmed (code) |
0x0041e420 |
Inventory_Count(inv, player, item) |
an item's count; 0 outside players 0-1 and items 0-22 | confirmed (code) |
0x0041e5b0 |
Inventory_AddItem(inv, player, item, amount, notify) |
every count change, and the callbacks (below) | confirmed (code), runtime |
0x00423718 |
Unlocks_SetRecord |
UM_SetUnlockable's writer |
confirmed (code) |
0x00424130 |
Unlocks_IsDataUnlocked(mgr, type, data) |
the code's test, as UM_IsDataUnlocked |
confirmed (code) |
0x004211d8 |
Stats_CategoryPoints(stats, group) |
the sum of counts × points of a category | confirmed (code) |
0x00422a90 |
Stats_CategoryScore |
the same, harmony inverted | confirmed (code) |
0x00422b00 |
Stats_CategoryPercent |
a score as a percentage of its maximum, at most 100 | confirmed (code) |
0x002176b8 |
GameStatsSubItem_IsReady |
Stats-screen sub item (vtable 0x0053ef48): ready once its texture batch (+0x68, +0xc4) is resident and its child widget answers ready; caches the answer in +0x10. |
confirmed (code) |
0x00217730 |
GameStatsSubItem_GetScaledRect |
Stats-screen sub item: copies the item's rect (+0x30) with its height multiplied by the item's scale method (vtable +0xb4). |
confirmed (code) |
0x00217788 |
GameStatsSubItem_Layout |
Stats-screen sub item: lays out its label and value widgets inside its rect, centring by half sizes and using the font size. | confirmed (code) |
0x002179e8 |
GameStatsSubItem_Draw |
Stats-screen sub item: when visible and both child widgets exist, draws the two children (+0x68, +0x6c). |
confirmed (code) |
Data¶
The inventory¶
Two blocks of 0x3f4 bytes at game state +0x480, one per player; item i is the 0x2c-byte entry at block
+0x04 + i × 0x2c: the object name (31 characters), the count (+0x20), the pickup sound's hash (+0x24) and a
duration in ms (+0x28). There are 23 items, ids 0-22. Confirmed (code) at 0x0041e250 and 0x0041e420.
The code uses five of them by number: 1 the flash (a revive; d-pad right heals with it, or at full health with upgrade
(6, 8) fills and starts rage, Combat), 2 money, 3 spray-paint charges, 5 handcuffs and 6 handcuff
keys (a cuffed player with upgrade (6, 15) spends one with triangle, Combat), and 10 for stolen
loot (the pickup and drop code, 0x00232c60, 0x002334d0, 0x0023bf00; confirmed (code)). A human's pocket is one
item id (+0x250) and a count (+0x254), set by HuPutItemInPocket (0x00238190; confirmed (code)); that it holds
the same ids is inferred from the scripts' calls.
Adding an item and the callbacks¶
Inventory_AddItem(inv, player, item, amount, notify) (0x0041e5b0) does nothing outside players 0-1 and items
0-22; it adds amount to the count, floors it at 0 and keeps it within the item's limits (the table at
0x0058b300, with the flash's raised by upgrade (6, 7)). A positive amount of item 3 raises the spray-paint hint
flags. Then, in this order, synchronously, after the count has changed. Confirmed (code):
- Item 2 (money), whatever
notify: when inventory+0x1034names a Lua function that exists, it is called with (player, amount). notify= 1 only: theCfgInventoryCallbackfunction (+0xfd4), if it exists, is called with (item), one argument; then theCfgHuInventoryCallbackone (+0xff4) with (player, item). The sign ofamountdoes not matter; both players' changes call the same functions.
A world pick-up (Human_PickUpObject, 0x0023bf00) passes notify = 1 for the item it gives and 0 for the money
that comes with loot. A store's jewellery gives item 10 ×1 (notify) and then its value in money (no notify)
(Combat: breakables). Confirmed (runtime): at the store each of three pick-ups called
Inventory_AddItem(player 0, 10, 1, 1) then (0, 2, 7, 0).
Mission 1's store (level99_lesson1.lua): a player entering the box vInsideStore (message 3) runs
P2.InsideStore; while objectives.Stores is false it sends Vermin to fVerminCar, shows tutorial text TT_6, makes
a HUD counter (label LBL_1, value lootCount of 3) and sets objectives.Stores;
CfgInventoryCallback("P2.UpdateLootCount") was set before. P2.UpdateLootCount(item) counts only item 10: it adds 1
to lootCount and updates the counter; at 3 it releases the counter, clears the callback, the tutorial text, the box's
handlers (3 and 4) and the announcement, sets objectives.Stolen and schedules P2.SetupCars in 3,000 ms, which
teleports the player to fStoreFront for the car lesson. Confirmed (runtime): the teleport came 91 updates after the
third pick-up.
Walking over a power-up¶
Objects of class powerup_item (spray cans dyn_spraycan, flashes, keys, money) are taken by touching them, not
with triangle: the triangle search skips the class (Combat: breakables). Confirmed
(code) at 0x00219d50, 0x0023bf00:
- Contact. When a human's body touches an object (
Human_OnContact,0x00219d50), a class other thanpowerup_item(the name at0x0055a908) is a solid contact. A power-up is passed through and picked up, unless it isTYPE_REVIVAL(14) and the human is a player at full health while game state+0x56e5is 0, or it is out of sight (0x0021c570, the same ray test as the search). Two types come first, before the class test, and are never solid nor taken (both answer 0; confirmed (code) in0x00219d50): - 29
TYPE_MOVINGVEHICLE: a particle above the human,Human_DealDamagewith the damage at+0x144of the vehicle's record (+0xd4), the material pair (the human's material, 26), and the human's body flags 1 and 2 cleared. When a player holds the victim (Human_GetGrabOther), the player gets style 7 and rage, plus a stat by the victim's brain kind: 2 combat 7 (and a mission stat when its class+0x11bis 13), 1 crime 4, 4 or 5 crime 2. - 34
TYPE_CHATTERBOXTRAIN: damage of a third of the human's maximum health (the human as attacker) and the material pair 26. Its once-only test compares the human's+0x564with the human itself, not the train, so it hits on every contact; it then stores the train in+0x564. - The take (
Human_PickUpObject,0x0023bf00) needs flag0x8000(pickable) and a brain of kind 0, 2, 3 or 4, then goes by the object's type:
| Type | Who | Gives |
|---|---|---|
13 TYPE_KEY |
a player below the item's limit | item 6 ×1 |
14 TYPE_REVIVAL |
a player below the limit | item 1 ×1 (a flash) |
16 TYPE_SPRAYCAN |
a player below the limit (0x0041ded0(3)) |
item 3 ×1 (one charge; never past 9) |
28 TYPE_MONEY |
a player | item 2 × the object's value (+0x124) |
12 TYPE_SPECIAL |
the named mission items and loot (Combat: breakables) |
Each gift passes notify = 1 (above), plays the item's pick-up sound and removes the object
(0x0023be98); the first spray can, key or flash also queues that item's hint once (game state flags 0x2000,
0x40, 0x4000) when hints are on. Any other type, or a refused one, returns 0 and the object stays.
3. Back in the contact, a taken object's physics body is freed.
At runtime (slot 1, a dyn_spraycan spawned with Obj_Spawn 2.5 m ahead of the player, who walked at it with the
stick at 60%): Human_PickUpObject was called from Human_OnContact when the player's centre was about 1.1 m from
the can's (the two bodies touching), and Inventory_AddItem(player 0, 3, 1, 1) followed in the same update.
Confirmed (runtime). A spray can that is never touched stays; its update (0x003f2870) only spins it and shows it
within 40 m (30 m for its icon; inferred).
Unlockables¶
The manager (0x006fe998) holds up to 640 records of 12 bytes: +0 level, +1 group, +2 item, +3 type, +4
a 16-bit extra, +8 a 32-bit data id; a record is unlocked when its bit in the set at 0x006fe8f8 is clear.
global.lua fills the table from its Unlockables list. UM_Unlock(level, group, item) unlocks every record with
those bytes; completing a level unlocks its (level, 0, 0) records. The game tests a record by type and data, the
first match deciding. Confirmed (code) for the layout and the tests.
What each type's data is:
| Type | Data | Tested by | Evidence |
|---|---|---|---|
| 0 | the next story level | scripts (runNextMission) |
inferred |
| 1 / 2 / 3 / 4 | a Rumble mode / arena / gang / character type | the Rumble menus (0x001f8168, 0x001eaa6c, 0x001ec9bc, 0x001ec490) |
confirmed (code) |
| 5 | a clubhouse display: level95 switches object zones by it |
scripts | inferred |
| 6 | an upgrade or a hub feature (below) | the code and the hub scripts | confirmed (code) |
| 7 | a flashback mission's level | the hub's markers | inferred |
| 8 | a Coney Island shop's stock | level95_coney's shops |
inferred |
| 9 | an Armies of the Night level (60-64) | the hub | inferred |
| 10 | a workout move | level95_workout |
inferred |
| 11 | a hint, shown once its record is unlocked (0x001cea38, then 0x001ce1e8) |
the code | confirmed (code) |
Which records are new (not yet seen in a menu) is a separate bit set at 0x006fe948 (Unlockables_IsDirty /
Unlockables_ClearDirty). UM_GetUnlockablesByType fills the unused entries of its result with 65535 and does not
check the 32-entry bound (0x00423eb0). Confirmed (code). No engine code reads extra; only UM_GetRecordData
returns it. What the menus count is under The counts the menus show.
Type 6, by data id (confirmed (code) at the tests cited; ids 1, 2 and 16-28 are tested only by scripts, 3 and 4 by the hub, World flags):
| Id | Unlocks | Id | Unlocks |
|---|---|---|---|
| 5 | brass knuckles: punches deal +byte 0 % damage (0x0021b518) |
11 | the power meter's maximum × (1 + byte 2 %) (0x00223068) |
| 6 | steel toe caps: kicks deal +byte 1 % damage (0x0021b570) |
12 | the stamina maximum × (1 + byte 3 %) (0x00223188) |
| 7 | one more flash carried, 3 to 4, and sold by dealers (0x0041df10) |
13 | knocked-out cops drop handcuff keys (0x00233260) |
| 8 | a flash at full health fills rage and starts it (0x00284830) |
14 | knocked-out humans may drop power cuffs, 51 % (0x00232ed4) |
| 9 | paying a bum $1 marks every dealer on the radar (0x002acb60) |
15 | a cuffed player can spend a key to free himself (0x00284488) |
| 10 | after four $1 payments a bum takes $10 and joins the gang (0x002ac728; joining inferred) |
The bytes are CfgWarriorUpgrade's four (0x006b6650; 10, 20, 10 and 20 in config_preload2.lua).
The counts the menus show¶
Four functions count records for the statistics, mission-select and Rumble screens. Each walks every record in index
order; "unlocked" is the record's bit clear in 0x006fe8f8 (Unlockables_IsUnlocked, 0x00424098). Confirmed (code)
at the addresses given.
Unlocks_IsLevelComplete(level)(0x004241d8): lists the records with that level and group 0 (Unlockables_ListByLevelGroup,0x00423de0, at most 32) and answers with the first one whose item is 0: the level is complete when its(level, 0, 0)record is unlocked. The mission-select and statistics screens list only complete levels;Unlocks_IsAnyUnlocked(0x00424540, the fourth-difficulty test, Save) asks it for every level record whose mission byte is 1-23.Unlocks_CountUnlocked(&groupUnlocked, &groupTotal, &unlocked, &total)(0x004245d8), the statistics screen's summary (Front end):total/unlocked: the records of types 1, 2, 3, 4 and 6 (Rumble modes, arenas, gangs, character types, upgrades) whose level byte is neither 1 nor 4; the completion percentage isunlocked× 100 /total, truncated;groupTotal/groupUnlocked: among the records of those five types in group 1 (any level), the distinct (level, item) pairs; only the first record of each pair is tested forgroupUnlocked.
Unlockables_GetLevelTargets(level, &scoreTarget, &charUnlocked, &charTotal, &arenaUnlocked, &arenaTotal, &groupUnlocked, &groupTotal)(0x00424298), one mission's line on the statistics and mission-select screens. The records it looks at are the level's, where level 60 stands for 60-64 (the Armies of the Night stages) and 101 for 101-137; of those only groups 0 and 1 count:scoreTarget= the highest item among the group-0 records × 1000 (the score the screens compare the best score with);charTotal/charUnlocked: the type-4 records;arenaTotal/arenaUnlocked: the type-2 records;groupTotal/groupUnlocked: the distinct items of the group-1 records of types 1, 2, 3, 4 and 6.
Unlockables_CountCharacters(&unlocked, &total)(0x004247d0): every type-4 record; the Rumble soldier screen'sn/total(Front end).
On the NTSC-U disc (corroboration, counted from the 610 records of the Unlockables
list, inferred until checked at runtime): the completion count covers 469 records (365 type 4, 46 type 3, 29 type 2,
20 type 6, 9 type 1; it leaves out the eight type-6 records of levels 1 and 4), and there are 40 distinct group-1
(level, item) pairs. Per level, Unlockables_GetLevelTargets gives:
| Level | Score target | Type 4 | Type 2 | Group-1 items | Level | Score target | Type 4 | Type 2 | Group-1 items |
|---|---|---|---|---|---|---|---|---|---|
| 2 | 7,000 | 12 | 2 | 2 | 52 | 8,000 | 7 | 0 | 1 |
| 3 | 8,000 | 18 | 1 | 0 | 54 | 2,000 | 1 | 1 | 0 |
| 5 | 7,000 | 15 | 4 | 2 | 55 | 2,000 | 13 | 1 | 0 |
| 9 | 15,000 | 18 | 1 | 1 | 60 (60-64) | 0 | 12 | 3 | 0 |
| 11 | 11,000 | 28 | 1 | 1 | 80 | 2,000 | 10 | 0 | 0 |
| 14 | 15,000 | 29 | 1 | 3 | 81 | 2,000 | 2 | 1 | 0 |
| 20 | 9,000 | 29 | 2 | 1 | 82 | 9,000 | 18 | 1 | 0 |
| 31 | 9,000 | 10 | 3 | 1 | 83 | 7,000 | 13 | 1 | 2 |
| 34 | 12,000 | 24 | 1 | 2 | 84 | 2,000 | 20 | 0 | 0 |
| 51 | 7,000 | 18 | 1 | 2 | 86 | 1,000 | 14 | 1 | 0 |
| 87 | 17,000 | 22 | 0 | 3 | 92 | 6,000 | 2 | 1 | 1 |
| 93 | 8,000 | 4 | 1 | 1 | 95 | 0 | 2 | 0 | 17 |
| 99 | 5,000 | 1 | 0 | 0 |
That a group-0 record with item n > 0 is the reward for a best score of n × 1000 is inferred from the target alone.
Statistics¶
The stats object (0x006fe490) keeps, per player, a counter per event in six categories, which are StatAdd's
groups and CfgSetStatValue's tables: 0 mission, 1 bonus, 2 style, 3 combat, 4 crime, 5 harmony. A category's
score is the sum of each event's count times its points (0x004211d8); harmony's is its maximum minus that sum
(0x00422a90), so harmony events are penalties. The screen shows each score as a percentage of the category's
maximum, capped at 100 (0x00422b00). CfgSetStatTypeMax(combat, crime, harmony, style, mission, bonus) stores
the six maxima at 0x00715840 in category order. Confirmed (code).
The points tables (16-bit) are at 0x005971b8 (3 events), 0x005971c0 (4), 0x00715510 (13), 0x00715500 (8),
0x00715818 (12) and 0x00715830 (5). The engine adds through one function per category (0x004ed908,
0x004ed888, 0x004ed988, 0x004ed8c8, 0x004ed948, 0x004de250); 0x004f39e8 adds a crime event to every
player, which AI Warriors led by a player use. What each event counts is in
Statistics, read from those callers (confirmed (code)): combat events by the
anim id that lands, unblocked, on a human not down; crimes by victim (civilian, bum or dealer; cop; gang member);
harmony by Warriors cuffed or down (every 2 s, the number still cuffed or down, from 0x00166f54).
Game-state functions¶
Functions of the game-state module (0x00417af0-0x00424e50: inventory, statistics, flags, configuration
workers) that belong to this page, by address.
| Address | Name | What it does | Evidence |
|---|---|---|---|
0x0041d618 |
GameState_SetObjectValueMod |
CfgObjectValueMod: the money multiplier +0x380 for picked-up items |
confirmed (code) |
0x0041ded0 |
Inventory_GetLimit(item) |
the most of an item a player may carry: the table at 0x0058b304 (8 bytes per item, 23 items), one more flash (item 1) with upgrade (6, 7) |
confirmed (code) |
0x0041df40 |
Inventory_Construct |
both players' 23 entries get the default name 0x0058b2e8, its CRC-32 and 10,000 ms; the four callback names cleared; then the checkpoint copy |
confirmed (code) |
0x0041e040 |
Inventory_Reset(full) |
clears the four callback names (+0xfd4, +0xff4, +0x1014, +0x1034); with full, zeroes every count of both players and takes the checkpoint copy; GameState_ResetForLevel passes full below checkpoint 2 |
confirmed (code) |
0x0041e168 |
Inventory_RestoreCheckpoint |
copies the checkpoint blocks (+0x7ec) back over the live ones (+0x04); from GameState_ResetForLevel at checkpoint 2 or later and from MissionFailed_Toggle (a retry) |
confirmed (code) |
0x0041e218 / 0x0041ec60 |
Inventory_ClearPlayer / Inventory_ClearPlayerChecked |
zeroes one player's counts, live and checkpoint; the second checks the player is 0 or 1 (from Human_ReleasePlayer) |
confirmed (code) |
0x0041e320 / 0x0041e348 / 0x0041e370 / 0x0041e3c0 |
InventoryBlock_GetSound / InventoryBlock_GetDuration / InventoryBlock_GetCount / InventoryBlock_GetEntry |
an item's pickup sound hash, its duration, its count (0 above item 22), the entry's address; used by Human_PickUpObject, Human_DropCarried, the mugging and the mission-item icons |
confirmed (code) |
0x0041e3d8 |
Inv_HasItem(inv, player, item) |
count above 0, for players 0-1 and items 0-22; from the triangle action and ContextAction_Use |
confirmed (code) |
0x0041e460 / 0x0041e490 / 0x0041e4c0 / 0x0041e4f0 |
Inventory_SetCallback / Inventory_SetHuCallback / Inventory_SetHuCallback2 / Inventory_SetMoneyCallback |
the names at +0xfd4, +0xff4, +0x1014, +0x1034 (32 characters) |
confirmed (code) |
0x0041e520 |
Inventory_CallHuCallback2(arg) |
calls the +0x1014 function with one argument; from Human_PickUpObject and Human_HandleMessage |
confirmed (code) |
0x0041e9d8 |
Inventory_SetCount(inv, player, item, n) |
sets a count, clamped to the item's range in the table at 0x0058b300 (minimum, maximum; with upgrade (6, 7) the flash's maximum is 4); a positive count of spray paint (item 3) sets game-state hint bits (the first time 0x100000000, later 0x400200000 and the hint 17 withdrawn); from InvSetMoney and 0x0041f030 |
confirmed (code) |
0x0041eca0 / 0x0041ece8 / 0x0041ed10 / 0x0041ed38 / 0x0041ed60 |
Cfg_SetInventoryItem / Cfg_SetInventoryCallback / Cfg_SetHuInventoryCallback / Cfg_SetHuInventoryCallback2 / Cfg_SetMoneyCallback |
the CfgInventoryItem, CfgInventoryCallback, CfgHuInventoryCallback, CfgHuInventoryCallback2 and CfgMoneyCallback workers, on the game state's inventory (+0x480) |
confirmed (code) |
0x0041ed88 |
Inv_PlayerHasItem |
InvPlayerHasItem(item, player), the player 1-based |
confirmed (code) |
0x0041edb8 / 0x0041edf0 |
Inv_GiveRevive / Inv_NumberRevives |
InvGiveRevive(n, player) adds flashes (item 1) with notify (also from BossDiegoGoal_Start) / InvNumberRevives(player) |
confirmed (code) |
0x0041ee20 / 0x0041ee58 |
Inv_GiveSkeletonKey / Inv_NumberSkeletonKeys |
the same for handcuff keys (item 6) | confirmed (code) |
0x0041ee88 / 0x0041eeb8 |
Inv_GetMoney / Inv_SetMoney |
InvGetMoney(player) / InvSetMoney(n, player): clears the player's HUD money roll (+0xe20) and sets item 2 through Inventory_SetCount |
confirmed (code) |
0x0041ef00 / 0x0041ef60 |
GameState_GiveMoney / GameState_TakeMoney |
GiveMoney(n, player) / TakeMoney(n, player): item 2 ± n with notify |
confirmed (code) |
0x0041ef98 / 0x0041efd0 |
Inv_GiveItem / Inv_NumberOf |
InvGiveItem(item, n, player) with notify / InvNumberOf(item, player) |
confirmed (code) |
0x0041f000 / 0x0041f030 |
Inv_GetSpraycanCharges / Inv_SetSpraycanCharges |
InvGetSpraycanCharges(player) / InvSetSpraycanCharges(n, player) (item 3, negative read as 0, through Inventory_SetCount) |
confirmed (code) |
0x004209c0 |
PlayerStats_Construct |
zeroes six static buffers (0x005971b8, 0x00715510, 0x005971c0, 0x00715500, 0x00715818, 0x00715830), +0x04 and +0xbc, then PlayerStats_Reset; from 0x004227d8 |
confirmed (code) |
0x00420a70 |
PlayerStats_Reset |
clears one player's statistics record: score, every category's counts and the style flag +0xbc; the Armies score +0x04 survives unless the current level's id is 60 (the first Armies level) |
confirmed (code) |
0x00420b48 / 0x00420ba8 / 0x00420c08 |
PlayerStats_CategoryEventCount / PlayerStats_CategoryPointsTable / PlayerStats_CategoryCounts |
the three lookups behind a category id (0-5, jump tables at 0x0058b710, 0x0058b730, 0x0058b750): how many events it has (3, 4, 13, 8, 12, 5), its 16-bit points table, and the record's array of counts for it |
confirmed (code) |
0x00420c80 |
PlayerStats_GetScore |
a player's running score (+0x00), or the Armies score (+0x04) in an Armies of the Night level |
confirmed (code) |
0x00420cb8 |
PlayerStats_AddMission |
category 0 (mission, 3 events): adds n to the event's count (+0x08, capped at 0xffffff), adds n × points (0x005971b8) to the score (capped at 9,999,999) and, in Armies levels, to +0x04; returns the points added. Reached through 0x004ed908 |
confirmed (code) |
0x00420db0 |
PlayerStats_AddStyle |
category 2 (style, 13 events, counts +0x24, points 0x00715510); sets the style flag +0xbc. Reached through 0x004ed988 |
confirmed (code) |
0x00420eb0 |
PlayerStats_AddBonus |
category 1 (bonus, 4 events, counts +0x14, points 0x005971c0), as the mission adder; also sets the bonus flag +0xbd the HUD's score counter shows a label for. Reached through 0x004ed888 (lock picks, tags) |
confirmed (code) |
0x00420fb0 |
PlayerStats_AddCombat |
category 3 (combat, 8 events, counts +0x58, points 0x00715500). Reached through 0x004ed8c8 |
confirmed (code) |
0x004210a8 |
PlayerStats_AddCrime |
category 4 (crime, 12 events, counts +0x78, points 0x00715818). Reached through 0x004ed948 and 0x004f39e8 |
confirmed (code) |
0x004211a0 |
PlayerStats_AddHarmony |
category 5 (harmony, 5 counts at +0xa8): adds to the count only, capped at 0xffffff; its score is taken off later as a penalty. Reached through 0x004de250 (arrests, knock-outs, the 2 s cuffed-or-down tally) |
confirmed (code) |
0x004211d8 |
Stats_CategoryPoints |
(already named) the sum over a category of count × points | confirmed (code) |
0x00421280 / 0x004212a0 |
PlayerStats_TakeStyleFlag / PlayerStats_TakeBonusFlag |
return and clear the record's style (+0xbc) or bonus (+0xbd) changed flag |
confirmed (code) |
0x004223e8 |
Cfg_SetStatTypeMax |
CfgSetStatTypeMax(combat, crime, harmony, style, mission, bonus): reorders its six arguments into category order and stores them as the maxima (0x00422900) |
confirmed (code) |
0x00422430 |
Cfg_SetStatValue |
CfgSetStatValue(category, event, points): writes one 16-bit entry of the category's points table; an unknown category is ignored |
confirmed (code) |
0x004224d8 |
Script_StatAdd |
StatAdd(human, category, event, n): resolves the human and calls that category's adder (0 mission … 5 harmony); not a human, or a category over 5, does nothing |
confirmed (code) |
0x004225f0 / 0x00422630 / 0x00422670 |
Script_StatGetRank / Script_StatGetScore / Script_StatGetPreviousHiScore |
the StatGetRank, StatGetScore (the running score, 0x00422998) and StatGetPreviousHiScore (the current-index level's best record, 0x00423050) bindings |
confirmed (code) |
0x004226a0 |
Script_StatGetTotal |
StatGetTotal(human, category): the category's score (Stats_CategoryScore), 0 when not a human |
confirmed (code) |
0x004226f8 / 0x00422930 |
Stats_ResetAll / Stats_ResetPlayers |
PlayerStats_Reset on both live player records of the stats object |
confirmed (code) |
0x00422718 |
Script_StatResetPlayer |
StatResetPlayer(human): resets that player's record (0x00422970) |
confirmed (code) |
0x00422758 |
Script_StatGetHighScore |
StatGetHighScore(levelId): finds the level-table record with that id (+0x04 of each 0x84-byte record, +0x56d4 of them) and returns its best record; index −1 when absent |
confirmed (code) |
0x004227d8 |
Stats_Construct |
the stats object (0x006fe490): constructs two live (+0x000, +0x0c0) and two checkpoint (+0x180, +0x240) 0xc0-byte player records, clears the maxima (0x00715840) and the 30 best records (+0x300, 0x168 bytes), resets the players and takes a checkpoint copy |
confirmed (code) |
0x004228a0 |
Stats_GetPlayer |
a human's live record: stats + player index (human +0x380) × 0xc0 |
confirmed (code) |
0x004228b8 |
Stats_RankFromPercent |
the rank of an average percentage: 0 below 50, 1 below 80, else 2 | confirmed (code) |
0x00422900 |
Stats_SetMaxima |
copies six 16-bit maxima to 0x00715840 |
confirmed (code) |
0x00422970 |
Stats_ResetPlayer |
PlayerStats_Reset on a human's live record |
confirmed (code) |
0x00422998 |
Stats_GetScore |
a human's running score (PlayerStats_GetScore); this is what StatGetScore and the HUD's score counter show, not a computed category total |
confirmed (code) |
0x00422ae8 |
Stats_GetMaximum |
the category's maximum (16-bit, 0x00715840 + 2 × category) |
confirmed (code) |
0x00422bd8 |
Stats_GetRank |
a human's rank: the mean of the six category percentages, through Stats_RankFromPercent |
confirmed (code) |
0x00422d00 |
Stats_RestoreCheckpoint |
copies the checkpoint pair (+0x180, 0x180 bytes) back over the live pair; from the mission-failed retry and level init |
confirmed (code) |
0x00422da0 |
Stats_MergePlayer2 |
when player 2 drops out (Human_ReleasePlayer): re-adds every count of the second record to the first through the category adders (so its points are scored again at the current rates), clears the second's flags and resets it; then the same for the checkpoint pair |
confirmed (code) |
0x00423308 / 0x00423338 |
Stats_StaticInit / Stats_StaticInitStub |
static constructor: Stats_Construct(0x006fe490) on (1, 0xffff) |
confirmed (code) |
0x004236d0 |
Script_UMReset |
UM_Reset: frees the unlockable records (Unlockables_Reset) |
confirmed (code) |
0x004236f0 |
Script_UMSetNumUnlockables |
UM_SetNumUnlockables(n): stores the record count (0x006fe99c) while no records are allocated yet |
confirmed (code) |
0x00423768 |
Script_UMGetUnlockables |
UM_GetUnlockables(level, group, out): when records exist, lists the indices matching level and group (Unlockables_ListByLevelGroup) |
confirmed (code) |
0x004237a8 |
UM_GetUnlockablesByType |
the type form: Unlockables_ListByType when records exist |
confirmed (code) |
0x004237e8 |
Script_UMUnlock |
UM_Unlock(level, group, item): Unlockables_Unlock when records exist |
confirmed (code) |
0x00423828 / 0x00423868 / 0x004238a8 |
Script_UMIsUnlocked / Script_UMIsDataUnlocked / Script_UMIsLevelComplete |
UM_IsUnlocked(index), UM_IsDataUnlocked(type, data), UM_IsLevelComplete(level): 0 when no records exist |
confirmed (code) |
0x004238e8 |
UM_GetRecordField |
UM_GetRecordData(index, field): field 0-3 the level, group, item and type bytes, 4 the 16-bit extra, 5 the data id; 0 otherwise |
confirmed (code) |
0x00423988 |
Script_UMIsTypeDirty |
UM_IsTypeDirty(type, clear): Unlockables_IsTypeDirty |
confirmed (code) |
0x004239b0 |
UM_IsDataDirty |
UM_IsDataDirty(type, data, clear): Unlockables_TestDirtyByData |
confirmed (code) |
0x004239e0 |
Script_UMUnlockAll |
UM_UnlockAll(on): Unlockables_SetAll |
confirmed (code) |
0x00423a08 |
Unlockables_Construct |
empties the manager (records, count), frees any records and sets every record locked and none new | confirmed (code) |
0x00423a98 / 0x00423b88 |
Unlockables_Reset / Unlockables_FreeRecords |
free the record array (if any) back to the heap and clear the pointer | confirmed (code) |
0x00423ab8 |
Unlockables_AllocRecords |
frees any old array and allocates count × 12 bytes; done lazily by the first record write | confirmed (code) |
0x00423c30 |
Unlockables_SetRecordFields |
fills one record (level, group, item, type, extra, data), allocating the array first if needed; from Unlocks_SetRecord; returns whether the index was valid |
confirmed (code) |
0x00423cf0 |
Unlockables_GetRecord |
the record at an index, or null when out of range or not allocated | confirmed (code) |
0x00423d30 |
Unlockables_FindByData |
the first record of a type with a data id, or null; from the Rumble soldier-buying screen | confirmed (code) |
0x00423de0 |
Unlockables_ListByLevelGroup |
fills a 32-entry list with 65535, then writes the index of every record with that level and group | confirmed (code) |
0x00423f70 |
Unlockables_Unlock |
every record matching (level, group, item): marked new if it was locked, then unlocked | confirmed (code) |
0x00424098 / 0x004240c8 / 0x004240f8 |
Unlockables_IsUnlocked / Unlockables_IsDirty / Unlockables_ClearDirty |
test the locked set (0x006fe8f8, clear = unlocked), test or clear the new set (0x006fe948) |
confirmed (code) |
0x004247d0 |
Unlockables_CountCharacters |
how many type-4 (character type) records exist and how many are unlocked; from the Rumble soldier-swap screen | confirmed (code) |
0x00424890 |
Unlockables_IsTypeDirty |
whether any record of a type is new; optionally clears each one's new bit | confirmed (code) |
0x00424958 |
Unlockables_TestDirtyByData |
whether the first record of a type with a data id is new; optionally clears it | confirmed (code) |
0x00424d60 |
Unlockables_SetAll |
the unlock-all switch: player 1's money (inventory +0x480) set to 1,000,000; on, every record unlocked and new; off, every record locked and none new |
confirmed (code) |
0x00424e50 / 0x00424ec8 |
Unlockables_StaticInit / Unlockables_StaticInitStub |
static constructor: clears both bit sets and constructs the manager on (1, 0xffff) | confirmed (code) |
Coney's implementation¶
Written from this page, Scripts, AI: crimes and the binding pages
(2026-10-06). GameState::player (src/warriors/player_state.h) holds:
- The inventory (
src/warriors/inventory.h): two players × 23 slots;CfgInventoryItemconfigures both players' slots;GiveMoney,TakeMoney,InvSetMoney,InvGiveItem,InvGiveRevive,InvGiveSkeletonKeyandInvSetSpraycanChargeschange counts, clamped at 0; revives at 3, or 4 once the upgrade (6, 7) is unlocked.InvSetMoneybumps a per-player counter the HUD can watch; the first positive spray-paint amount raises the hint flag. Mission complete (mode 0xb) banks each player's money into the profile. - The statistics (
src/warriors/player_stats.h): the six points tables, the maxima, per-player counters, the category points, scores and percentages above.StatAdd,StatGetScoreandStatResetPlayertake a player's human (from the humans the scripts made); other handles do nothing. - The unlockables' records (
src/warriors/unlock_records.h):UM_SetNumUnlockables,UM_SetUnlockable,UM_Reset,UM_Unlock,UM_IsLevelComplete,UM_IsDataUnlocked,UM_IsTypeDirty,UM_IsDataDirtyandUM_GetRecordDataover the profile's locked and new bits (SavedProgress), so a save carries story progress. - The checkpoint copy:
SetCheckPointcopies the inventories and statistics;restoreCheckpoint()puts them back for a restart. - The walk-over (
LevelPickups::walkOver,src/gamemodes/level_pickups.h): each update player 1 takes everypowerup_itemhe touches that is shown, in an enabled zone and in sight (the search's ray test), by its kind as in the table above: money its value, a key item 6, a flash item 1 (passed by at full health whileCfgPowerupPickupis off; the level reset turns it on), a spray can item 3 (at most 9), aTYPE_SPECIALloot as triangle gives it; each gift notifies, plays its item'sCfgInventoryItempick-up sound without a position and removes the object for good; a refused one stays. A loot taken by triangle plays item 10's sound too.
Coney's choices, where the page is silent:
- Items other than revives have no upper limit (the table at
0x0058b300is not described). StatGetScoreis the five scoring categories' points less harmony's, not below 0. The original returns the running score (0x00422998, Game-state functions), so this differs.- An event index past its table is ignored;
StatGetTotalis not registered. Inventory_AddItemisscript::addInventoryItem(src/scripting/player_bindings.h), with the money,CfgInventoryCallbackandCfgHuInventoryCallbackcalls in the order above;CfgMoneyCallback(config) sets the money callback. The give bindings keep their own paths (GiveMoneycalls the money callback; the others call none), as their notify flags are not traced.SetMultiplayerCallback's function is kept (PlayerState::multiplayerCallback); Coney has one player, so the two-player sync that calls it never runs.- The walk-over's touch: Coney's power-ups have no collision bodies, so player 1 touches one within 1.1 m of his feet
in plan (where the runtime check's contact took the spray can) and from 0.5 m below his feet to 2 m above them.
Kinds 29 and 34's own contact cases, the first key's and flash's hints, the named mission items (every
TYPE_SPECIALis loot) and a second player are not built; a key's and a flash's limits are the inventory's.
Open questions¶
- What category 13 is, which the mission event 0/1 requires of the gang member knocked out.
- What world
+0x158is: it gates event 0/1 and upgrade (6, 14). - What a hat's byte
+0x10b(1 or 2) separates in style events 2/1 and 2/2. - How
global.lua'sSetupMissionPointsturns a level's mission and bonus values into maxima. - Whether scripts other than the four found set events 1/0-1/2 and 2/4.
- The item limits at
0x0058b300, and what each item's duration (+0x28) does. - Which hints the first spray can, key and flash queue (game state flags
0x2000,0x40,0x4000); whatHuman_OnContactdoes with object kinds 29 and 34; whichTYPE_SPECIALmodel hashes are the named mission items and what each gives. CfgPowerupPickupoff: whether a player walks past a flash only at full health (the walk-over above) or past every kind-14 object (the binding's page, config).- Which category
StatGetTotal's statistic id picks. - Which notify flag
InvGiveItem,InvGiveReviveand the other give bindings pass toInventory_AddItem. - What the money multiplier at game state
+0x380(1.0 in mission 1) is.