Special balls come from two lists of BallModifier (effect, chance, pitch ids, optional alternate): a park’s
BallparkDefinition.balls and a pitcher’s own PitcherDefinition.balls. Each pitch rolls both on their own, and the
two results stack. Using an existing effect is one line in DefaultContent. A new effect needs a BallEffect value,
its label and colour, its flight, its HUD, sound, intro and index text, and a co-op protocol bump.
A: Put an existing ball on a park or a boss
In Assets/_Project/Scripts/Data/DefaultContent.cs, add Ball(effect, chance, params pitchIds):
moon.balls.Add(Ball(BallEffect.AntiGravity, 1f / 3f, "curveball", "sinker", "changeup")); // a park's ballbenny.balls.Add(Ball(BallEffect.Wiffle, 1f / 4f)); // a boss's own ball
Pitch ids are PitchTypeDefinition.id values (fastball, sinker, changeup, curveball, slider,
knuckleball). No ids = every pitch.
Optional: one of two effects. Set alternate: when the roll wins, it is the alternate half the time. The Gale:
var gust = Ball(BallEffect.Headwind, 1f / 8f);gust.alternate = BallEffect.Tailwind;gale.balls.Add(gust);
That’s all. The map card, briefing and HUD describe it (BallModifier.Describe, e.g. “Anti-Gravity balls:
Curveballs, Sinkers, Changeups (1 in 3)”, “Headwind / Tailwind balls: any pitch (1 in 8)”); a boss’s lines come from
PitcherDefinition.DescribeBalls as ”• His own …“. The INDEX’s SPECIAL BALLS entry lists every park and pitcher
that throws it (Index Screen).
B: A brand-new kind of ball
Enum value. Add it at the end of enum BallEffect in Assets/_Project/Scripts/Data/BallEffect.cs.
Label and colour. Add cases to BallEffects.Label (shown in caps on the pitch pill) and BallEffects.Tint
(trail, glow). Put tuning constants in BallEffects too, like SuperFastSpeed, GhostFadeOut or GustAt.
The flight. Change the path in Pitching/PitchData.cs so the bat, timing ring, replay and spectators all
agree. Always test with Has(BallEffect.X), which checks both slots (map and boss):
Progress for pace along the path (Sticky, Headwind, Tailwind; BallEffects.LateSpeedProgress keeps both ends
fixed so effects stack),
Break for the shape of the break (Anti-Gravity),
PathAt for an extra offset (Wiffle’s WiffleOffset),
OpacityAt for fading (Ghost).
A speed change goes in PitcherAI.CreatePitch, as SuperFast does:
A reward on a home run goes in AtBatController (see how swing.pitch.Has(BallEffect.Money) pays MoneyBallCoins).
HUD colour. Add a case to HudView.PillColor (UI/HudView.cs).
Sound. Add a pitch for its windup cue in GameAudio.SpecialBallPitch (Audio/GameAudio.cs); otherwise it
plays at pitch 1.
Match intro line. Add a case to MatchIntroCard.EffectText (Intro/MatchIntroCard.cs, a short caps line like
“SWERVES HARD NEAR THE PLATE”); otherwise the intro card shows no effect text for it. See Cutscenes.
Index text. Add a case to IndexCatalog.BallDescription (Data/IndexCatalog.cs); without one it reads “A
special ball.” The entry itself appears automatically. If it was a planned ball (Heavy, Ice and Fire are listed as
“Coming soon” in IndexCatalog.AddPlanned), delete its Planned(...) line.
Put it on a park or a boss as in part A.
Bump the co-op protocol. Both slots travel as bytes in AtBatPitch (SpectateMessages.Pitch: effect,
bossEffect), so an older build would read an unknown value. Change NetSession.Protocol (today HRD-COOP-7; see
Add a Net Message).
Numbers
What
Value
Where to tune
Chance at Medium / Hard
x1.5 / x2 the listed chance (park and boss balls, and decoys)
Difficulties.BallChance
alternate split
50 / 50
PitcherAI.Roll
Pitch lean onto a ball’s pitch types
80% of draws (only for balls with a pitch list)
PitcherAI.FavouredShare
Super Fast speed
x1.15
BallEffects.SuperFastSpeed
$$$ home run bonus
3 coins
BallEffects.MoneyBallCoins
Ghost fade
out 0.2 to 0.38 of the flight, 12% opacity until 0.62, back in by 0.82
up to 0.6 m x 0.65 by 0.65 of the flight, then back to 0 at the plate
WiffleSwerve, WiffleAt
Headwind / Tailwind
from 0.7 of the flight, 0.45x / 1.9x pace
GustAt, HeadwindSpeed, TailwindSpeed
Check it works
Tools → Home Run Derby → Run Ball Effect Test: checks each flight (anti-gravity, sticky, ghost, wiffle, gusts),
that boss and park balls stack, the pitch lean, decoys, that only named pitches get the ball, that ball rolls never
change which pitches come, the chances, the co-op pitch message, Describe / DescribeBalls, and that every park has
its own boss. Add a check for a new effect’s flight here.
Press Play and F1 → FORCE A SPECIAL BALL to get it on every pitch (runs and the batting cage). The dev
menu lists every BallEffect value automatically. It forces the map slot only.
Gotchas
PitcherAI.Roll takes the first ball in a list whose roll wins, so list order matters when one list has several
balls on the same pitch. The park list and the pitcher list are separate slots, so one of each can land together.
The pitcher’s ball wins the look: colour, glow, sound and pill colour come from PitchData.Primary. The pill text
names both (“WIFFLE GHOST FASTBALL”).
The rolls use their own seeded stream (BallStream), so a ball’s roll never changes the pitch sequence. But a ball
limited to some pitch types makes pitchers lean on those types, and that lean draws from the pitch stream: adding or
changing such a ball does change which pitches come (and the daily seed’s pitches).
Append enum values, never insert: the value is a number in exported assets and in the net message.