# 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: 1. Reads the extracted CSV data for every installed mod 2. Compares each mod's values against RHS as the baseline 3. Generates a `config.cpp` mod 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: 1. Looks up the entry's tier in the baseline table 2. If the tier exists in the baseline, compares every field 3. 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 velocities - `ACE_velocityBoundaries[]` — velocity thresholds for BC changes - `ACE_muzzleVelocities[]` — muzzle velocity per barrel length - `ACE_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 points - `passThrough` — 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") - `passThrough` values 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: ```cpp class CfgPatches { class armadump_afou { units[] = {}; weapons[] = {}; requiredAddons[] = {"afou"}; }; }; ``` This tells Arma to load our overrides after the original mod. ### CfgAmmo (Ammo Overrides) ```cpp 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) ```cpp 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: 1. **`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. 2. **`output/unmatched.csv`** — A spreadsheet of items that couldn't be matched. Review these to decide if they need manual adjustment. 3. **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: 1. **Classification**: Class name `B_afou_556x45_Ball` matches pattern `556x45` -> tier `rifle_556x45`. No "tracer", "ap", etc. in name -> subtype `ball`. 2. **Baseline lookup**: Tier `rifle_556x45` / `ball` maps to RHS's `B_556x45_Ball`: - hit = 9, caliber = 0.87, typicalSpeed = 920, airFriction = -0.001033, deflecting = 21 3. **Comparison**: AFou's values differ on airFriction, caliber, deflecting, hit, typicalSpeed. 5. **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.