Wardogs: Inventory Ammo Packing (curves, asset and magazine multipliers)
Source: every CurveFloat under Content/GameMechanics/Inventory/InventoryMagPacking, the shared CR_CombineAmmoCurve one folder up, the DA_InventoryMagPackingData asset, and the magazine items that carry the two multiplier fields. Every table below is generated by _tools/ammo_packing_tables.py; do not edit it by hand.
This is the data behind the inventory operations that move loose rounds into a magazine, out of one, or between two magazines. It is not the combat reload. The magazine multiplier fields and the native consumers (function RVAs, call sites) are recorded in the “Obfuscated magazine fields” note of Attachments and Platforms (limitations section); this page adds the curve data. Related: Weapons and Ammo.
What is data and what is inferred. Keys, curve references, tag entries and magazine values are read from the exports. The export stores no axis labels. Reading the input as the round count and the output as a time (probably seconds) is inference from the native code (the curves are evaluated at a round count and the result is multiplied by the magazine field) and from the key ranges; no unit is recovered. Every example below is derived, not observed in game.
1. Counts
| Family | Curves | |—|—| | Default combine curve | 1 | | Combine loose ammo | 13 | | Extract ammo | 12 | | Load ammo | 12 | | Total | 44 |
The folder holds 37 curve files in total (12 combine-loose, 12 extract, 12 load and one CR_CombineLooseAmmoCurve_Default), plus CR_CombineAmmoCurve in the parent folder and the data asset. Each caliber family has the same 12 calibers: 5.45 mm, 5.56 mm, 12 gauge, .308 Win, .45 ACP, .45 Colt, .50 AE, .50 Cal, 7.62x39 mm, 7.62x54 mm, 9 mm and arrow.
2. The asset: DA_InventoryMagPackingData
A UWDInventoryMagPackingData primary data asset (SDK UWDInventoryMagPackingData, size 0x2C8). The inventory component references it at +0xB20, and UWDCustomInventoryOperationRule_LoadAmmo holds its own reference at +0x28. Its properties are obfuscated; the offsets below come from the SDK class layout and the names are the export’s property names.
| Offset | Property (obfuscated) | Kind | Content | |—|—|—|—| | +0x30 | _aaabllnojcfhdgeommfma | default curve | CR_CombineAmmoCurve | | +0xB8 | _aaabimpbjloglbhgafela | default curve | CR_CombineAmmoCurve | | +0x140 | _aaacacabcoegcjlfdjelm | default curve | CR_CombineLooseAmmoCurve_Default | | +0x1C8 | _aaaboilmcjjfcpblfgjjf | per-caliber map | 12 caliber-tag entries, each pointing at a per-caliber curve (extract ammo family) | | +0x218 | _aaablhndpcjmcdfploclj | per-caliber map | 12 caliber-tag entries, each pointing at a per-caliber curve (load ammo family) | | +0x268 | _aaacdfgmjbjbcodlbnooi | per-caliber map | 12 caliber-tag entries, each pointing at a per-caliber curve (combine loose ammo family) | | +0x2C4 | _aaaamidkofoncinfnlfeg | int32 | 48 (the only scalar stored in the asset; the bool at +0x2B8, the int at +0x2BC and the float at +0x2C0 are at their defaults; meaning not traced) |
How the six slots are used, in the order the native code reads them (native notes from round 27; see the attachments page):
- Combine ammo (
UWDPlayerInventoryComponent::CombineAmmo):curveA(rounds) x field(+0x6C)of one magazine pluscurveB(rounds) x field(+0x68)of the other. Each curve is taken from a per-caliber map (+0x1C8for A,+0x218for B) searched up the gameplay-tag parents, or from the default curve (+0x30for A,+0xB8for B, bothCR_CombineAmmoCurve) when no tag matches. - Load ammo rule (
UWDCustomInventoryOperationRule_LoadAmmo, vtable slot 86): second-curve value (+0x218map, else+0xB8) times+0x68of the magazine. - Per-round interval (caller
0x754B640): first-curve value times+0x6C, divided by the round count. - Combine loose ammo (
+0x140defaultCR_CombineLooseAmmoCurve_Default,+0x268per-caliber map): the slots are in the asset, but no native consumer was traced for them in the earlier rounds; their use for combining loose rounds is inference from the names.
Inference, not traced: that the +0x1C8 map (extract curves) feeds curve A and the +0x218 map (load curves) feeds curve B follows from the property offsets in the native notes and the curve names; the source/target order of the two magazines in CombineAmmo is unconfirmed.
2.1 Per-caliber tag entries
Every map has the same 12 tag keys and points each one at its own curve asset. The tag Id.Item.762mm maps to the 7.62x39 curves.
| Caliber tag | Extract map (+0x1C8) | Load map (+0x218) | Combine-loose map (+0x268) | Inline keys (all three maps) | |—|—|—|—|—| | Id.Item.545mm | CR_ExtractAmmoCurve_545mm | CR_LoadAmmoCurve_545mm | CR_CombineLooseAmmoCurve_545mm | 0 -> 3, 100 -> 6 | | Id.Item.556mm | CR_ExtractAmmoCurve_556mm | CR_LoadAmmoCurve_556mm | CR_CombineLooseAmmoCurve_556mm | none | | Id.Item.12g | CR_ExtractAmmoCurve_12Guage | CR_LoadAmmoCurve_12Guage | CR_CombineLooseAmmoCurve_12Guage | none | | Id.Item.308Win | CR_ExtractAmmoCurve_308Win | CR_LoadAmmoCurve_308Win | CR_CombineLooseAmmoCurve_308Win | none | | Id.Item.45ACP | CR_ExtractAmmoCurve_45ACP | CR_LoadAmmoCurve_45ACP | CR_CombineLooseAmmoCurve_45ACP | none | | Id.Item.45Colt | CR_ExtractAmmoCurve_45Colt | CR_LoadAmmoCurve_45Colt | CR_CombineLooseAmmoCurve_45Colt | none | | Id.Item.50AE | CR_ExtractAmmoCurve_50AE | CR_LoadAmmoCurve_50AE | CR_CombineLooseAmmoCurve_50AE | none | | Id.Item.50Cal | CR_ExtractAmmoCurve_50Cal | CR_LoadAmmoCurve_50Cal | CR_CombineLooseAmmoCurve_50Cal | none | | Id.Item.762mm | CR_ExtractAmmoCurve_762x39mm | CR_LoadAmmoCurve_762x39mm | CR_CombineLooseAmmoCurve_762x39mm | none | | Id.Item.762x54mm | CR_ExtractAmmoCurve_762x54mm | CR_LoadAmmoCurve_762x54mm | CR_CombineLooseAmmoCurve_762x54mm | none | | Id.Item.9mm | CR_ExtractAmmoCurve_9mm | CR_LoadAmmoCurve_9mm | CR_CombineLooseAmmoCurve_9mm | none | | Id.Item.Arrow | CR_ExtractAmmoCurve_Arrow | CR_LoadAmmoCurve_Arrow | CR_CombineLooseAmmoCurve_Arrow | none |
Each map entry is a runtime float curve with an inline curve (EditorCurveData) and an external curve. Only the 5.45 mm entries have inline keys (0 -> 3, 100 -> 6, identical in all three maps) and every other inline curve is empty. It matters here that every entry also has an ExternalCurve: the engine’s runtime-curve type reads the external asset when one is set, so the per-caliber curves in section 3 are the effective ones and the 5.45 mm inline keys are inert (inference from standard engine behavior; the native evaluator 0x6C2F100 and its curve read were not re-traced this round). This corrects the earlier note, which treated the 11 empty entries as “no override”: all 12 calibers override the default curve through their external assets.
3. The curves
Keys are rounds -> time, all linear (Linear in the Interp column), which this page evaluates with constant extension past the last key (the assets list RCCE_Constant for the map curves; for the standalone curve assets the extrapolation mode is not stored in the export). The default-value column is the curve’s fallback value as stored; the 45 ACP extract and load curves store 0.3 and 0.4 where every other curve stores 1.0 (no consumer of the default value found).
3.1 Default combine curve
| Curve | Keys (rounds -> time) | Interp | Default value | |—|—|—|—| | CR_CombineAmmoCurve | 0 -> 1, 100 -> 6 | Linear | 1 |
3.2 Load ammo
| Curve | Keys (rounds -> time) | Interp | Default value | |—|—|—|—| | CR_LoadAmmoCurve_12Guage | 0 -> 0.5, 30 -> 20 | Linear | 1 | | CR_LoadAmmoCurve_308Win | 0 -> 0.5, 60 -> 30 | Linear | 1 | | CR_LoadAmmoCurve_45ACP | 0 -> 0.3, 100 -> 35 | Linear | 0.4 | | CR_LoadAmmoCurve_45Colt | 0 -> 0.5, 12 -> 10 | Linear | 1 | | CR_LoadAmmoCurve_50AE | 0 -> 0.4, 20 -> 15 | Linear | 1 | | CR_LoadAmmoCurve_50Cal | 0 -> 1.2, 20 -> 28 | Linear | 1 | | CR_LoadAmmoCurve_545mm | 0 -> 0.5, 200 -> 70 | Linear | 1 | | CR_LoadAmmoCurve_556mm | 0 -> 0.5, 200 -> 60 | Linear | 1 | | CR_LoadAmmoCurve_762x39mm | 0 -> 0.5, 40 -> 15 | Linear | 1 | | CR_LoadAmmoCurve_762x54mm | 0 -> 1, 100 -> 45 | Linear | 1 | | CR_LoadAmmoCurve_9mm | 0 -> 0.4, 100 -> 35 | Linear | 1 | | CR_LoadAmmoCurve_Arrow | 0 -> 0.5, 30 -> 8.5 | Linear | 1 |
3.3 Extract ammo
| Curve | Keys (rounds -> time) | Interp | Default value | |—|—|—|—| | CR_ExtractAmmoCurve_12Guage | 0 -> 0.3, 30 -> 10 | Linear | 1 | | CR_ExtractAmmoCurve_308Win | 0 -> 0.3, 60 -> 20 | Linear | 1 | | CR_ExtractAmmoCurve_45ACP | 0 -> 0.3, 100 -> 20 | Linear | 0.3 | | CR_ExtractAmmoCurve_45Colt | 0 -> 0.3, 12 -> 5.4 | Linear | 1 | | CR_ExtractAmmoCurve_50AE | 0 -> 0.3, 20 -> 10 | Linear | 1 | | CR_ExtractAmmoCurve_50Cal | 0 -> 0.8, 20 -> 15 | Linear | 1 | | CR_ExtractAmmoCurve_545mm | 0 -> 0.3, 200 -> 40 | Linear | 1 | | CR_ExtractAmmoCurve_556mm | 0 -> 0.3, 200 -> 40 | Linear | 1 | | CR_ExtractAmmoCurve_762x39mm | 0 -> 0.3, 40 -> 10 | Linear | 1 | | CR_ExtractAmmoCurve_762x54mm | 0 -> 0.7, 100 -> 30 | Linear | 1 | | CR_ExtractAmmoCurve_9mm | 0 -> 0.3, 100 -> 20 | Linear | 1 | | CR_ExtractAmmoCurve_Arrow | 0 -> 0.3, 30 -> 5 | Linear | 1 |
3.4 Combine loose ammo
| Curve | Keys (rounds -> time) | Interp | Default value | |—|—|—|—| | CR_CombineLooseAmmoCurve_12Guage | 0 -> 0.5, 24 -> 3.5, 48 -> 8 | Linear | 1 | | CR_CombineLooseAmmoCurve_308Win | 0 -> 0.5, 30 -> 3.25, 60 -> 6.5 | Linear | 1 | | CR_CombineLooseAmmoCurve_45ACP | 0 -> 0.4, 13 -> 1.9, 26 -> 3.8 | Linear | 1 | | CR_CombineLooseAmmoCurve_45Colt | 0 -> 0.5, 6 -> 2.5, 12 -> 5 | Linear | 1 | | CR_CombineLooseAmmoCurve_50AE | 0 -> 0.4, 10 -> 1.7, 20 -> 3.4 | Linear | 1 | | CR_CombineLooseAmmoCurve_50Cal | 0 -> 1.2, 10 -> 7, 20 -> 14 | Linear | 1 | | CR_CombineLooseAmmoCurve_545mm | 0 -> 0.5, 75 -> 5.25, 150 -> 10.5 | Linear | 1 | | CR_CombineLooseAmmoCurve_556mm | 0 -> 0.5, 200 -> 14.25 | Linear | 1 | | CR_CombineLooseAmmoCurve_762x39mm | 0 -> 0.5, 20 -> 3.5, 40 -> 7 | Linear | 1 | | CR_CombineLooseAmmoCurve_762x54mm | 0 -> 1, 100 -> 19 | Linear | 1 | | CR_CombineLooseAmmoCurve_9mm | 0 -> 0.4, 50 -> 2.65, 100 -> 5.3 | Linear | 1 | | CR_CombineLooseAmmoCurve_Arrow | 0 -> 0.5, 30 -> 4.25 | Linear | 1 | | CR_CombineLooseAmmoCurve_Default | 0 -> 0.5, 200 -> 14.25 | Linear | 1 |
4. Magazines with a multiplier
Both fields (FWDMagazineAttachmentData +0x68 and +0x6C) sit next to ReloadAnimationPlayRate on every magazine item. A value below 1.0 shortens the packing time of that magazine; 1.0 leaves the curve time unchanged. Only three magazines are below 1.0, all machine-gun magazines.
69 magazine items carry the two fields; 66 hold 1.0 in both and 3 hold a lower value (the two fields are equal on every item).
| Magazine | +0x68 | +0x6C | Ammo / item tags in the export | |—|—|—|—| | ID_M249_Mag_Standard | 0.6 | 0.6 | Id.Item.556mm.Tracer, Id.Item.M249Magazine | | ID_MAGZ_062 | 0.8 | 0.8 | Id.Item.556mm.Tracer, Id.Item.M249, Id.Item.MAGZ_062 | | ID_LMG_02_Magazine_Standard | 0.7 | 0.7 | Id.Item.762x54mm.Tracer, Id.Item.LMG_02Magazine |
5. Derived examples
Derived, using the curve keys above and the formula time = curve(rounds) x multiplier. The round counts are chosen for illustration: magazine capacity is not stored on the magazine item in this export, so the table does not claim what each magazine holds. Values interpolate linearly between keys. The load and extract columns are the 5.56 mm per-caliber curves; for a CombineAmmo operation the two terms of the formula add (curve A with one magazine’s +0x6C, curve B with the other’s +0x68), and which magazine is which is unconfirmed.
| Case (multiplier) | Rounds | Load curve | Extract curve | Load x mult | Extract x mult | |—|—|—|—|—|—| | 5.56 mm, ID_M249_Mag_Standard (0.6) | 100 | 30.25 | 20.15 | 18.15 | 12.09 | | 5.56 mm, ID_M249_Mag_Standard (0.6) | 200 | 60.00 | 40.00 | 36.00 | 24.00 | | 5.56 mm, ID_556STANAG_Mag_Standard (1.0) | 30 | 9.43 | 6.25 | 9.43 | 6.25 | | 5.56 mm, ID_MAGZ_062 (0.8) | 100 | 30.25 | 20.15 | 24.20 | 16.12 | | 7.62x54 mm, ID_LMG_02_Magazine_Standard (0.7) | 100 | 45.00 | 30.00 | 31.50 | 21.00 |
Fallback combine curve CR_CombineAmmoCurve (used only when no caliber entry matches): 30 rounds -> 2.50, 60 rounds -> 4.00, 100 rounds -> 6.00.
6. Native consumers (round 41, static disassembly, decompiler output unreviewed)
- Curve choice by caliber tag, with a parent search.
0x6C2F100looks the item’s caliber tag up in the map; when the tag is absent it asks for the tag’s direct parent and tries again, until a hit or no parent is left. SoId.Item.556mm.Tracerresolves to theId.Item.556mmentry. Only if the whole chain misses does the caller use the default curve. - Which map does what.
+0x1C8is the extract map (default+0x30),+0x218the load map (default+0xB8),+0x268the combine-loose map (default+0x140).0x6B5E500reads+0x1C8/+0x30and+0x218/+0xB8; the load rule (UWDCustomInventoryOperationRule_LoadAmmoslot 86,0x6C47420) reads+0x218/+0xB8for a load into a magazine and+0x268/+0x140for combining loose rounds of one caliber. - Input and output. The curve is evaluated at the round count (
0x6B5E566: the integer argument converted to float, then0x6C26830, the rich-curve read of the external curve). The result is multiplied by the magazine’s+0x6Cfield (extract term) or+0x68field (load term) and the two are added (0x6B5E500, called twice byCombineAmmo0x6B554B0). Whether the sum is seconds is still inferred from its use as an operation duration. - Defaults and the int
48. The scalars at+0x2B8,+0x2BC,+0x2C0are unset in the asset;+0x2C4is 48 (the asset’s own value). Their consumers were not found (the load rule does not read them).
Still Not Pulled / Limitations
- The unit of the output (seconds assumed) and the input (round count) are inferred, not recovered; no UI or timer consumer was read this round.
- Native consumer for the combine-loose map and default: found in round 41 (section 6). The stored int
48at+0x2C4and the three default-valued scalars are still unexplained. - The parent-tag search is found (section 6); which tags the shipped magazines carry beyond the 12 keys is not listed here.
- Standalone curve assets do not store an extrapolation mode in the export; clamping past the last key is assumed.
- No in-game timing was measured, so none of the example values are verified against play.