- SQF extraction scripts (ammo, armor, weapons, magazines) with ACE3 properties - Python pipeline: extract_csvs, split_by_mod, generate_patches - Generates CfgPatches config.cpp normalizing non-RHS values to RHS baseline - Documentation: REFERENCE.md, GENERATE_PATCHES_EXPLAINED.md, AGENTS.md
12 KiB
Generate Patches — How It Works
This document explains the logic behind generate_patches.py, the script that produces the rebalance config.cpp.
Purpose
Many Arma 3 mods define ammo and armor with values that don't match RHS standards. This script:
- Reads the extracted CSV data for every installed mod
- Compares each mod's values against RHS as the baseline
- Generates a
config.cppmod that overrides non-RHS values to match RHS equivalents
The result is a single drop-in mod that normalizes your entire mod list to RHS balance.
Overview of the Process
Per-mod CSV files (data/)
|
v
+-------------------+
| Load baseline | Reads RHS + vanilla + RHS sub-mods
| (RHS / vanilla) | as the "correct" reference values
+-------------------+
|
v
+-------------------+
| Classify every | Each ammo/armor item is sorted into a
| entry into tiers | "tier" group based on caliber or protection level
+-------------------+
|
v
+-------------------+
| Build baseline | For each tier, pick one representative RHS entry
| lookup table | to use as the reference values
+-------------------+
|
v
+-------------------+
| Compare each mod | For every non-RHS entry, look up its tier's
| against baseline | RHS baseline and record any differences
+-------------------+
|
v
+-------------------+
| Generate | Write a config.cpp that overrides every
| config.cpp | differing value to match the RHS baseline
+-------------------+
|
v
output/config.cpp (the rebalance mod)
output/unmatched.csv (items with no RHS equivalent)
Step 1: Loading Baseline Data
The script loads CSVs from these folders as the baseline (the "correct" values):
| Folder | Source |
|---|---|
_rhs |
RHS AFRF (Russian equipment) |
_rhsusf |
RHS USMC (American equipment) |
_rhsgref |
RHS GREF (generic/other factions) |
_rhssaf |
RHS SAF (Serbian equipment) |
_vanilla |
Arma 3 base game (BI) |
_ace |
ACE3 mod (ballistic overrides) |
All entries from these mods are pooled together. When two entries exist in the same tier, RHS entries are preferred over vanilla.
Step 2: Classification — Putting Items Into Tiers
Every ammo and armor item is classified into a tier. Items in the same tier are considered equivalent — a 5.56mm round from mod A should have similar stats to a 5.56mm round from RHS.
Ammo Classification
Ammo is classified by two properties: a caliber tier (the type of round) and a subtype (ball, AP, tracer, etc.).
How Caliber Tier Is Determined
The script checks the ammo class name against a list of patterns. The first pattern that matches wins. This is done by name, not by the numeric caliber value, because Arma's caliber property is a penetration coefficient, not the actual bullet caliber.
| Pattern in class name | Tier | Typical RHS values (hit / caliber / speed) |
|---|---|---|
9x19, 9mm, 9x18 |
pistol_9mm |
5 / 0.4 / 370 |
45acp, .45 |
pistol_45acp |
6 / 0.5 / 260 |
57x28, 5.7 |
smg_57x28 |
4 / 0.3 / 390 |
46x30, 4.6 |
smg_46x30 |
4 / 0.3 / 375 |
545x39, 5.45 |
rifle_545x39 |
9 / 0.65 / 900 |
556x45, 5.56 |
rifle_556x45 |
9 / 0.87 / 920 |
762x39 |
rifle_762x39 |
11 / 1.2 / 730 |
762x51, .308 |
rifle_762x51 |
11.6 / 1.6 / 800 |
762x54 |
rifle_762x54 |
11.6 / 1.8 / 830 |
300blk |
rifle_300blk |
(no RHS baseline) |
338, 338lapua |
sniper_338 |
(no RHS baseline) |
408, cheytac |
sniper_408 |
(no RHS baseline) |
127x99, 50bmg, 12.7 |
hmg_50 |
22 / 2.8 / 900 |
145x114, 14.5 |
hmg_145 |
28 / 3.5 / 950 |
20mm |
cannon_20 |
30 / 1.2 / 1000 |
23mm |
cannon_23 |
40 / 1.5 / 950 |
25mm |
cannon_25 |
60 / 2.0 / 900 |
30mm |
cannon_30 |
80 / 2.5 / 850 |
35mm |
cannon_35 |
85 / 2.5 / 850 |
40mm, mk19 |
cannon_40 |
100 / 2.5 / 800 |
mortar, 82mm, 81mm |
mortar_82 |
varies |
100mm and above |
gun_heavy |
varies |
12gauge, buckshot |
shotgun_12g |
varies |
rocket, pg7, rpg |
rocket |
varies |
missile, titan, hellfire |
missile |
varies |
grenade, 40mm_he |
grenade_40mm |
varies |
mine, ied, satchel |
explosive |
varies |
penetrator, heat |
penetrator |
varies |
If no name pattern matches, the script falls back to the simulation property:
| Simulation | Tier |
|---|---|
shotBullet (speed < 350) |
bullet_subsonic |
shotBullet (other) |
bullet_unknown |
shotShell + explosive > 0.5 |
shell_he |
shotShell (other) |
shell_ap |
shotMissile |
missile |
shotRocket |
rocket |
shotGrenade |
grenade_40mm |
shotIlluminating, shotSmokeX |
utility |
How Subtype Is Determined
Within each caliber tier, items are further sorted into subtypes by checking the class name for keywords:
| Condition | Subtype |
|---|---|
| Speed > 0 and < 350 m/s | subsonic |
| Name contains "ap" | ap |
| Name contains "tracer" | ball_tracer |
| Name contains "match", "sniper", "otm", "mk262", "mk316" | match |
| Name contains "incendiary" or "incen" | incendiary |
| Name contains "hedp" | hedp |
| Name contains "he" (but not "hedp") | he |
| Everything else | ball |
Armor Classification
Armor is classified by item type (Vest or HeadGear) and a protection tier.
Vest Tiers
Based on the armor_chest value (front plate protection):
| Chest Armor Value | Tier | Examples |
|---|---|---|
| 0 (no plate) | soft |
Cloth vests, chest rigs |
| 1 - 16 | light |
Plate carrier lite, PASGT vest |
| 17 - 31 | medium |
IOTV, SPC, 6B13 |
| 32 - 50 | heavy |
Full battle carrier |
| 50+ | eod |
EOD suits, GL carriers |
Helmet Tiers
Based on the armor_head value:
| Head Armor Value | Tier | Examples |
|---|---|---|
| 0 (no armor) | cap |
Berets, caps |
| 1 - 6 | light |
Cloth helmets, bump helmets |
| 7 - 15 | medium |
PASGT, MICH |
| 16 - 25 | heavy |
ACH, ECH, 6B47 |
| 25+ | special |
Specialized heavy helmets |
Step 3: Building the Baseline Lookup Table
After all entries are loaded and classified, the script builds a lookup table. For each tier, it picks one representative RHS entry to use as the reference.
Example: For the rifle_556x45 / ball tier, the baseline might be RHS's B_556x45_Ball with:
- hit = 9
- caliber = 0.87
- typicalSpeed = 920
- airFriction = -0.001033
- deflecting = 21
Any 5.56mm ball round from any other mod will be compared against these values.
Step 4: Finding Overrides
For each non-RHS mod entry, the script:
- Looks up the entry's tier in the baseline table
- If the tier exists in the baseline, compares every field
- If a field differs and the mod's value is not zero, records a change to the baseline value
What Gets Compared
Ammo fields (14 values):
| Field | Arma Config Name | What It Controls |
|---|---|---|
hit |
hit |
Direct damage on impact |
indirect_hit |
indirectHit |
Splash damage |
indirect_hit_range |
indirectHitRange |
Splash radius (meters) |
caliber |
caliber |
Penetration coefficient |
typical_speed |
typicalSpeed |
Muzzle velocity (m/s) |
air_friction |
airFriction |
Drag coefficient |
deflecting |
deflecting |
Min ricochet angle (degrees) |
explosive |
explosive |
0 = kinetic, 1 = explosive |
ace_caliber |
ACE_caliber |
Actual bullet diameter (mm) |
ace_bullet_length |
ACE_bulletLength |
Bullet length (mm) |
ace_bullet_mass |
ACE_bulletMass |
Bullet mass (grams) |
ace_drag_model |
ACE_dragModel |
Drag curve (1=G1, 7=G7, etc.) |
ace_transonic |
ACE_transonicStabilityCoef |
Transonic stability (0-1) |
ace_mv_var |
ACE_muzzleVelocityVariationSD |
Velocity spread (%) |
Plus 4 ACE array fields compared as text:
ACE_ballisticCoefficients[]— drag coefficient at different velocitiesACE_velocityBoundaries[]— velocity thresholds for BC changesACE_muzzleVelocities[]— muzzle velocity per barrel lengthACE_barrelLengths[]— barrel lengths for velocity interpolation
Armor fields (16 values):
For each body part (Head, Neck, Chest, Diaphragm, Abdomen, Body, Arms, Legs):
armor— additional armor pointspassThrough— 0 = full block, 1 = no protection
What Does NOT Get Overridden
- Fields where the mod's value is zero (treated as "not defined, keep it")
passThroughvalues where both the mod and RHS baseline are 1.0 (no change for unarmored areas)
Step 5: Generating config.cpp
The output is a standard Arma 3 config.cpp with three sections:
CfgPatches (Dependency Declaration)
Each mod that has overrides gets a dependency entry:
class CfgPatches {
class armadump_afou {
units[] = {};
weapons[] = {};
requiredAddons[] = {"afou"};
};
};
This tells Arma to load our overrides after the original mod.
CfgAmmo (Ammo Overrides)
class CfgAmmo {
class B_afou_556x45_Ball {
airFriction = -0.001033;
caliber = 0.87;
deflecting = 21;
hit = 9;
typicalSpeed = 920;
};
};
Only fields that differ from RHS are listed. Fields not listed keep their original mod values.
CfgWeapons (Armor Overrides)
class CfgWeapons {
class MyMod_Vest {
class ItemInfo {
class HitpointsProtectionInfo {
class Chest {
armor = 24;
passThrough = 0.5;
};
class Abdomen {
armor = 16;
passThrough = 0.6;
};
};
};
};
};
Only body parts with differing armor values are listed.
Step 6: Unmatched Items
Items that are classified into a tier with no RHS baseline equivalent are written to output/unmatched.csv for manual review.
Common reasons an item is unmatched:
| Reason | Example |
|---|---|
| Caliber has no RHS equivalent | .300 Blackout, .57x28mm, .338 Lapua |
| Subtype has no RHS equivalent | Tracer variants when only ball exists in RHS |
| Simulation-based fallback tier | Unknown simulation type |
These items are not included in the generated config.cpp. They retain their original mod values.
What You End Up With
After running the script:
-
output/config.cpp— Drop this into an@mod/addons/folder and load it in Arma. It silently overrides every non-RHS ammo and armor value to match RHS balance. -
output/unmatched.csv— A spreadsheet of items that couldn't be matched. Review these to decide if they need manual adjustment. -
No changes to original mods — The script only reads data. Original mod files are untouched.
Example: Tracing One Entry
Taking the AFou mod's 5.56mm ball round as an example:
-
Classification: Class name
B_afou_556x45_Ballmatches pattern556x45-> tierrifle_556x45. No "tracer", "ap", etc. in name -> subtypeball. -
Baseline lookup: Tier
rifle_556x45/ballmaps to RHS'sB_556x45_Ball:- hit = 9, caliber = 0.87, typicalSpeed = 920, airFriction = -0.001033, deflecting = 21
-
Comparison: AFou's values differ on airFriction, caliber, deflecting, hit, typicalSpeed.
-
Override generated: The config.cpp sets those 5 fields to RHS values. All other fields (ACE properties, cost, etc.) keep AFou's original values.
Limitations
- Tier-based, not per-class matching: All 5.56mm ball rounds are normalized to the same RHS reference. If a mod intentionally makes a round weaker or stronger (e.g., training ammo), it will be overwritten.
- No threshold filtering: Even a difference of 0.001 generates an override. There is no minimum difference filter.
- First RHS entry wins: If multiple RHS entries exist in the same tier, the first one encountered is used. This is usually fine since RHS values within a tier are consistent.
- Unmatched items skipped: Calibers without an RHS equivalent (like .300 BLK) are left untouched.