# EasyRed2 Killstreak Announcer

**An Unreal-Tournament / Quake-style killstreak announcer for Easy Red 2.**
by **StixsworldHD (StixsmasterHD4k)**

Version 1.1.0 · for Easy Red 2 **v2.0.6** (+ all DLC) · BepInEx 6 (IL2CPP, x64)

---

## What's new in v1.1.0 (much bigger sound library)

The announcer now drives a **far larger voice library — 701 clips across 50 event categories** (up from 317 across 48). Every event has more variety, and two brand-new callouts were added:

- **Vehicle kills now have their own announcement.** When you destroy a vehicle (and its crew), you get a dedicated road/vehicle callout (Road Kill, Road Rampage, Vehicular Manslaughter, Pancake, Top Gun, and more) *in addition* to the crew counting toward your multi-kills and sprees.
- **A 4-minute match-clock warning** was added alongside the existing 5/3/2/1-minute and 30/20-second warnings (optional clock, still off by default).

Beyond that, the classic announcer events were stocked with all the extra takes and variant spellings from the new packs (e.g. `double_kill2/3`, `ultrakill2`, multiple `You_have_won_the_match` reads), and the impressive UT reward medals (Goliath, Iron Guard, Iron Skull, Juggernauts, Hellions, ThunderCrash, FireStorm, Sun Blade, Vampire, Eagle Eye, DareDevil, Rage, Ownage, Whirlwind, Retribution…) are folded into the top multi-kill and spree tiers as epic-moment variety.

**How the library was selected.** The source packs contain a lot of audio that has **no matching Easy Red 2 event** — Unreal-Tournament map mission briefings (Convoy / Glacier / Onslaught / Mothership / Robot Factory / Junkyard / Fallen City intros), Assault objective scripts ("defend the bunker", "destroy the command center", power-node/power-core/shield/door callouts), CTF flag chatter, medal *description* voice-overs, and similar. Those can't be triggered by anything the mod detects, so including them would only mean dead files that never play. This download therefore contains **only the 701 files the mod actually uses** — every clip in the `KSounds` folder maps to a real event, so the in-game log reports `0 unmapped`.

**Nothing was removed and the control surface is unchanged.** No new config keys were added — vehicle-kill callouts use the existing `EnableVehicleKills` toggle and the 4-minute warning uses `EnableTimeWarnings`. The plugin GUID is the same, so your existing config still applies. (If you keep an older, larger `KSounds` folder around, that's fine too — unmapped files are simply ignored.)

<details>
<summary>Earlier fix history (v1.0.1 – v1.0.3)</summary>

**v1.0.3 — runtime crash on every kill.** Once a battle started, the log flooded with `During invoking native->managed trampoline` / `NullReferenceException` at `DamageFeedback::OnSoldierHit`. That hook takes a `Nullable<Vector3>` parameter; when Harmony patches an IL2CPP method, Il2CppInterop's trampoline must marshal every original parameter and boxes value types via `Il2CppClassPointerStore.GetNativeClassPointer`. For a **generic value-type instantiation** like `Nullable<Vector3>` that pointer resolves to zero, so the box throws on every call. Fixed by moving the kill hook to the parameterless `GameStatistics.OnPlayerGetKill()` (the game already calls it for every direct player kill; explosion kills use a separate path, so no double-counting), and moving win/loss off the `EndBattleGUI.EndBattle(BattleResults)` struct onto the clean `BattleManager.OnWin(string, bool)`.

**v1.0.2 — load crash.** v1.0.0/v1.0.1 crashed during IL2CPP type registration (`ClassInjector.ConvertMethodInfo` → `NullReferenceException`) because the injected `MonoBehaviour` exposed methods taking the mod's own `EventCategory` enum. Il2CppInterop converts **all** instance methods (public *and* non-public) of an injected type, and its type-support check waves every value type (including enums) through, so it tried to resolve a non-existent IL2CPP class for the enum and threw. v1.0.1's attempt to fix this by making the methods `internal` didn't work (non-public methods are converted too). v1.0.2 fixed it for good by splitting into a paper-thin injected shell (`AnnouncerController`, only `Awake`/`Update`) plus a separate non-injected logic class (`AnnouncerCore`) that the engine never sees.
</details>

---

Every kill, multi-kill, spree, headshot, capture, lead change, and match result fires a punchy voice callout pulled at **random** from a library of **701** announcer clips, so it never feels repetitive. Pure flavor, zero gameplay changes — it only *listens* to the game and plays sounds.

Everything is driven by **verified hooks** into the game's own kill/score/battle events (reverse-engineered from the v2.0.6 decompile), so callouts fire exactly when the matching thing actually happens to **you** — not your squadmates, not the enemy.

---

## Requirements

- **Easy Red 2 v2.0.6** (the build this was reversed against) with all DLC.
- **BepInEx 6 (IL2CPP, x64)** already installed and working — i.e. you have launched the game once with BepInEx and a `BepInEx` folder exists in your game root. This mod does **not** include BepInEx.
  - If you don't have it yet, install the **BepInEx 6 Bleeding Edge (BE) Unity IL2CPP x64** build into your Easy Red 2 folder first, launch once, then close.

> Not sure where your game root is? In Steam: right-click **Easy Red 2 → Manage → Browse local files**. That folder (the one containing `Easy Red 2.exe` and the `BepInEx` folder) is your **game root**.

---

## Installation

This package mirrors the exact folder layout of your game. **Extract the ZIP straight into your Easy Red 2 game root and let the folders merge.**

After extracting, you should have:

```
<Easy Red 2 game root>/
├─ Easy Red 2.exe
├─ BepInEx/
│  ├─ plugins/
│  │  └─ StixsworldHD-KillstreakAnnouncer/
│  └─────── EasyRed2.KillstreakAnnouncer.dll      ← the mod
└─ KSounds/                                        ← the voice library (sits NEXT TO BepInEx)
   ├─ Sounds1/
   ├─ Sounds2/
   ├─ Sounds3/
   ├─ Sounds4/
   └─ Sounds5/
```

**Two things must be true:**
1. `EasyRed2.KillstreakAnnouncer.dll` is inside `BepInEx/plugins/` (a subfolder is fine).
2. The **`KSounds` folder lives in the game root, right next to the `BepInEx` folder** — *not* inside it. This is where the mod looks for its voice clips.

Launch the game. On the BepInEx console/log you'll see the banner and a line like `Indexed 701 sound files across 50 event categories (0 unmapped)`. Frag away.

> **Uninstall:** delete the `StixsworldHD-KillstreakAnnouncer` plugin folder (and optionally the `KSounds` folder and the `.cfg`).

---

## What triggers what

The mod reacts to real in-game events for the **locally controlled player**:

| You do this in-game | You hear |
|---|---|
| First enemy kill of the battle | **First Blood** |
| 2–9+ kills in quick succession (within the multi-kill window) | **Double → Triple → Mega → Ultra → Monster → Ludicrous → Wicked Sick → Holy Shit** |
| Reach a kill milestone without dying | **Killing Spree → Rampage → Dominating → Unstoppable → GodLike → Berzerk** |
| Lethal helmet headshot | **Headshot** (and **Headhunter** every N headshots) |
| Rack up grenade/explosive kills | **Explosive spree** (Flak Monkey / Combo) |
| Get a kill while sprinting | **Adrenaline / Speed** (rare, on a cooldown) |
| Get a kill while injured / low HP | **Clutch / Last Man Standing** |
| Destroy a vehicle + its crew | **Vehicle kill callout** (Road Kill, Road Rampage, Pancake…) — and the crew also feeds your multi-kills & sprees |
| Capture an objective / collect a dog tag / revive an ally | **Capture / Nice Catch / Assist** |
| Your faction takes or loses the lead | **Taking the Lead / Lost the Lead** |
| Die repeatedly with no kills | **Losing Badly** |
| Battle starts | **Prepare / Begin** |
| Battle ends (win) | **Victory** — or **Flawless Victory** if you never died |
| Battle ends (loss) | **Defeat** |
| *(optional)* Match-clock time warnings & final 10-second countdown | **5/4/3/2/1-min, 30/20-sec, 10…1** |

When several things happen at once, a small **priority queue** makes sure the big moment (e.g. a Monster Kill) plays first and minor chatter is dropped rather than overlapping.

---

## Full sound → event mapping

All matching is **case-insensitive**. File-name prefixes (`(All).`, `Generic.`), `_alt` / `_F` suffixes, and numeric variant suffixes (e.g. `double_kill2`) are all normalized away, so every one of the **701 bundled files** maps onto one of these events. Each event picks a clip **at random** from its pool, and the table below lists how many clips back each one.

**Combat** — kills, multi-kills & flavors
| Event | Clips | Sound files used (representative) |
|---|---|---|
| First Blood | 12 | `firstblood`, `first_blood`, `first_blood_mutates` |
| Double Kill (2) | 10 | `Double_Kill`, `doublekill` (+ numeric variants) |
| Triple Kill (3) | 20 | `triplekill`, `MultiKill`, `Hattrick` |
| Mega Kill (4) | 12 | `MegaKill`, `SKAARJannihilation` |
| Ultra Kill (5) | 14 | `UltraKill`, `SKAARJbloodbath` |
| Monster Kill (6) | 17 | `MonsterKill`, `SKAARJerradication` |
| Ludicrous Kill (7) | 12 | `LudicrousKill`, `SKAARJextermination` |
| Wicked Sick (8) | 11 | `WhickedSick`, `SKAARJslaughter` |
| Holy Shit (9+) | 16 | `HolyShit`, `SKAARJtermination`, `unreal`, `Juggernauts`, `Hellions` |
| Headshot | 8 | `Headshot` |
| Headhunter (headshot spree) | 6 | `Headhunter` |
| Explosive spree | 12 | `FlackMonkey`, `Combowhore` |
| Adrenaline (sprint kill) | 42 | `adrenalin`, `Booster`, `Speed`, `Invisible`, `Invulnerable`, `Camouflaged`, `LowGrav`, `Super_jump`, `Holograph`, `Loaded`, `LockandLoad`, `Instagib`, `Silencer` |
| Clutch (injured kill) | 31 | `last_man_standing`, `players_left`, `Averted`, `Narrowly_Averted`, `Last_Second_Save` |
| **Vehicle kill** (NEW) | 35 | `Road_Kill`, `RoadRampage`, `Road_Rage`, `Roadwarrior`, `Vehicular_manslaughter`, `Vehicle_destroyed`, `Pancake`, `Hit_and_run`, `Spin_Out`, `Pile_up`, `Car_jacked`, `Hijacked`, `Totalled`, `Wrecker`, `Fender_Bender`, `Burn_out`, `Top_Gun` |

**Sprees (kills without dying)** — epic reward medals folded into the top tiers
| Event | Clips | Sound files used (representative) |
|---|---|---|
| Killing Spree | 9 | `killingspree`, `Killing_Spree` |
| Rampage | 13 | `Rampage`, `Rage`, `Vampire` |
| Dominating | 13 | `Dominating`, `Ownage`, `DareDevil`, `BloodFist` |
| Unstoppable | 13 | `Unstoppable`, `Whirlwind`, `EagleEye` |
| GodLike | 14 | `GodLike`, `Retribution`, `Goliath`, `IronGuard`, `IronSkull`, `SunBlade` |
| Berzerk | 12 | `Berzerk`, `Berserk`, `ThunderCrash`, `FireStorm`, `BlackLegion`, `TheCorrupt` |

**Objective & teamplay**
| Event | Clips | Sound files used (representative) |
|---|---|---|
| Capture / score | 34 | `capture`, `proceed`, `blue/red_team_scores`, `Score`, `Goal`, `Interception`, `objective_accomplished`, `objective_reached` |
| Nice Catch (dog tag) | 2 | `nicecatch` |
| Assist (revive) | 2 | `assist` |

**Lead status**
| Event | Clips | Sound files used (representative) |
|---|---|---|
| Taking the Lead | 66 | `takenlead`, `blue/red_team_takes_the_lead`, `blue/red_team_increases_their_lead`, `blue/red_team_dominating`, `blue/red_team_on_offence`, `red_team_destroying`, `…_is_on_a_roll` |
| Lost the Lead | 23 | `lostlead`, `blue/red_pass_fumbled`, `…_is_falling_behind`, `…_is_outmatched` |
| Losing Badly | 7 | `lastplace`, `bottom_feeder` |

**Match flow & result**
| Event | Clips | Sound files used (representative) |
|---|---|---|
| Match Start | 26 | `prepare`, `Play`, `invasion_begun`, `invasion_has_begun`, `BallReset`, `new_round_in`, `new_assault_in` |
| Overtime | 9 | `overtime`, `sudden_death` |
| Victory | 42 | `Winner`, `You_have_won_the_match`, `blue/red_team_is_the_winner`, `blue_team_wins`, `blue/red_team_wins_the_round` |
| Flawless Victory | 6 | `Flawless_victory` |
| Defeat | 18 | `lostmatch`, `You_have_lost_the_match`, `failed`, `Humiliating_Defeat`, `Crushing_Defeat` |

**Optional match clock** (off by default — see config)
| Event | Clips | Sound files used (representative) |
|---|---|---|
| 5 / 4 / 3 / 2 / 1-min remaining | 30 | `5_minute_warning`, `4_minutes_remain`, `3_minutes_remain`, `2_minutes_remain`, `1_minute_remains` (+ `cd*min`) |
| 30-sec / 20-sec remaining | 16 | `30_seconds_remain`/`cd30sec`, `20_seconds` |
| Final countdown 10…1 | 80 | `ten`/`cd10` … `one`/`cd1` (8 clips each) |
| Generic seconds | 8 | `Second`, `Seconds`, `Time`, `Time_is_up` |

---

## Configuration

All settings live in:

```
BepInEx/config/com.stixsworldhd.easyred2.killstreakannouncer.cfg
```

It's a plain text file — edit it with Notepad (or anything) **while the game is closed**, then relaunch. Every entry in the file has a built-in description, its default, and an allowed min/max. There is **no in-game menu and no console commands** by design — the config file is the whole control surface.

A pre-filled copy is included so you can read every option before you even launch. If you ever delete the file, it is regenerated with defaults on next launch.

Highlights you might want to tweak:

- **`01 - General → MasterVolume`** (0–1): how loud the announcer is. Default `0.85`.
- **`01 - General → MasterEnabled`**: master mute for everything.
- **`02 - Multi-Kills → MultiKillWindowSeconds`** (1–10): how forgiving multi-kill chains are. Bigger = easier chains.
- **`03 - Sprees → KillingSpreeAt … BerzerkAt`**: the kill counts for each spree tier. Keep them in increasing order.
- **`04 - Headshots → HeadshotCooldownSeconds`**: throttle so a headshot streak doesn't spam the line.
- **`13 - Match Clock (optional)`**: **off by default.** Easy Red 2 doesn't expose a universal match timer, so this feature is best-effort — you tell it your match/phase length in seconds and it fires time warnings + a final countdown from time-elapsed. Turn it on only for timed modes where you know the length.

Each category also has its own `Enable…` toggle, so you can keep, say, multi-kills and victory lines but silence lead changes and adrenaline.

---

## Performance & behavior notes

- Sounds play through a dedicated **2D** audio source (straight to your ears, not positioned in the world) that **ignores the listener pause**, so callouts still play cleanly.
- By default, each clip is **decoded the first time it's needed and then cached** — no upfront cost. If you'd rather pay it all at battle start (and never risk a first-play micro-stutter), flip `PreloadAllSounds = true`.
- The kill hook fires for every soldier in the battle, so it does the cheapest possible **pointer check against the cached local player first** and bails instantly for everyone else — negligible overhead.
- If something ever looks off, set `VerboseLogging = true` to see exactly which files mapped and which events fired in the BepInEx log.

---

## Troubleshooting

- **No sounds at all** — confirm the `KSounds` folder is in the **game root next to `BepInEx`**, not inside it. Check the log for `KSounds folder not found`; it prints the exact path it looked at.
- **Mod not loading** — make sure you're on **BepInEx 6 IL2CPP (x64)** and that the DLL is under `BepInEx/plugins/`. The log should show the `EasyRed2 Killstreak Announcer v1.0.3` banner.
- **A callout feels too frequent / too rare** — adjust that category's cooldown or `…At`/`…Every` threshold in the config.
- **Wrong win/loss called** — the result is read from the game's end-of-battle data, with a fallback to your last known lead state; enable verbose logging if you want to see what it detected.

---

## Credits

- **Mod by StixsworldHD (StixsmasterHD4k).**
- Announcer voice clips are the classic UT/Quake-style set bundled in `KSounds`.
- Easy Red 2 © Corvostudio. This is an unofficial, fan-made mod and is not affiliated with or endorsed by Corvostudio.

Enjoy, and go get a Monster Kill.
— **StixsworldHD (StixsmasterHD4k)**
