Add a Pitcher or Boss

In short

A pitcher is a PitcherDefinition: a weighted pitch mix, speed, strike rate, pace, the earliest inning it may pitch (minRound) and, for bosses, isBoss = true plus any of three tricks: batterModifiers that handicap the batter, his own special balls, or decoys. Add a Pitcher(...) line in DefaultContent. New pitches are PitchTypeDefinitions made with Pitch(...).

Steps

  1. (Optional) A new pitch type. In Assets/_Project/Scripts/Data/DefaultContent.cs, block // ---------- Pitch types ----------: Pitch(id, name, mph, horizontalBreak, verticalBreak, wobble, color), set spinRpm, its hit power by contact zone, its arc, and add it to db.pitchTypes:
    var curve = Pitch("curveball", "Curveball", 78f, 0.14f, -0.48f, 0f, new Color(0.75f, 0.6f, 1f));
    curve.spinRpm = 2600f;
    HitPower(curve, 1f, 0.97f, 0.9f);   // x exit velocity off the perfect / nice / sour part of the bat
    curve.arc = 0.6f;                   // metres the path loops above a straight line, midway
    curve.arcSideways = true;           // the loop can bend left, up or right, picked per pitch
    Breaks are metres of movement by the plate, seen from the catcher (+x = glove side of a righty, negative vertical = drop). wobble is the knuckleball flutter. Unset, hit power is 1 in every zone and the arc is 0. See Pitch Types.
  2. A regular pitcher. Block // ---------- Pitchers ----------. Pitcher(id, name, description, boss, minRound, speedMult, strikeRate, timeBetween, color, params (pitch, weight)[]):
    db.pitchers.Add(Pitcher("flamethrower", "Flamethrower", "Pure heat with a nasty slider.",
        false, 2, 1.05f, 0.7f, 1.1f, new Color(0.9f, 0.3f, 0.2f), (fastball, 3f), (slider, 2f), (sinker, 1f)));
    • minRound is a 0-based inning index: 2 means inning 3 onward.
    • strikeRate is the chance a pitch is aimed inside the zone.
    • timeBetween is seconds between pitches (plus RunConfig.extraPauseBetweenPitches).
  3. A boss. Pass true for boss, prefix the name with BOSS: like the others, and add its handicap:
    var ace = Pitcher("ace", "BOSS: The Ace", "Throws everything and paints the corners. Shrinks your timing window.",
        true, 0, 1.1f, 0.65f, 1.0f, new Color(0.9f, 0.75f, 0.1f),
        (fastball, 3f), (sinker, 2f), (slider, 2f), (curve, 2f), (changeup, 1f), (knuckle, 0.5f));
    ace.batterModifiers.Add(StatModifier.Percent(StatType.TimingWindow, -0.15f));
    db.pitchers.Add(ace);
    batterModifiers are added to the batter’s stats only while facing this pitcher (RunManager.PlayCurrentRound, CoopRun). The map screen lists them under the boss.
  4. (Optional) His own special ball (#41). Same Ball(...) helper as a park’s; it is rolled on its own and stacks with the park’s ball, and his sets the ball’s look. See Add a Special Ball:
    benny.balls.Add(Ball(BallEffect.Wiffle, 1f / 4f));
  5. (Optional) Decoys (#41). Fake balls that fly with the real one and split off at 30% of the flight; only the real one casts a shadow:
    phantom.decoys = 2;
    phantom.decoyChance = 1f / 4f;   // share of pitches that get them (x the difficulty's special-ball scale)
  6. Smarter behaviour (not just numbers) goes in PitcherAI.CreatePitch / ChoosePitchType.

Nothing else needs registering: the map briefing, HUD, match intro card and the INDEX’s PITCHERS / BOSSES tabs read the definition (name, description, colour, mix, modifiers, DescribeBalls). See Index Screen.

How the run picks pitchers

RunMapGenerator.Generate builds the 9 innings from the run seed:

  • Every RunConfig.bossEvery-th inning (3, 6, 9) is a boss inning. Innings 3 and 6 pick a random boss with minRound <= that inning, not used yet this run, and not any park’s own boss (finalBossId of any park). Inning 9 is the park’s own boss (Add a Final Boss); every run map has one today.
  • So a new roaming boss (not set as anyone’s finalBossId) joins the pool for innings 3 and 6. Today that pool is only The Ace (minRound 0) and Knuckle Sandwich (minRound 4), which makes inning 3 always The Ace and inning 6 always Knuckle Sandwich.
  • Other innings pick a random non-boss with minRound <= the inning.
  • If a filter leaves nobody, it falls back to looser lists (any boss, any pitcher), so the run never breaks.

Check it works

  • Tools → Home Run Derby → Run Run Structure Test: checks 9 innings, bosses in 3, 6 and 9, the same seed giving the same innings, and the park boss in inning 9.
  • Tools → Home Run Derby → Run Ball Effect Test: boss balls, decoys, DescribeBalls and that every park’s boss stays home.
  • Press Play, F1 → JUMP TO INNING to reach the inning where it can appear. F1 → FORCE A PITCH TYPE throws a new pitch every time (runs and the batting cage).

Gotchas

  • Adding a pitcher changes the innings of every seed: the generator picks from the list, so a saved run resumed on a newer build (rebuilt from its seed, see Seeds and Determinism) and the daily challenge can face different pitchers than before.
  • Never pitch the same type more than twice in a row (PitcherAI.MaxSamePitchInARow = 2), or three times for a pitch a special ball rides on (MaxFavouredInARow = 3); a one-pitch mix is allowed (the Knuckle Sandwich boss).
  • At a park whose ball rides only some pitch types, the pitcher draws from those types 80% of the time (PitcherAI.FavouredShare). Give a new pitcher at least one of them if he should feel the park’s ball.
  • PitcherDefinition.color is used by the INDEX card and the match intro’s pitcher card.
  • minRound means nothing for a park boss: he is placed by finalBossId, not drawn.
  • Co-op spectators look up a pitch by id (SpectateMessages.ReadPitch) and fall back to the first pitch type if it is unknown, so players need the same content. A rule change that affects co-op needs a protocol bump (Protocol Versions).