Shared tomatoes, P1 sandbox spawns, and weapons left out

- Tomato splats are kept in the match, so everyone sees everyone's
  (others' arrive in replays, which play no effects), each splatting
  with juice and a sound once.
- The sandbox's first player (solo or hosting) spawns on the arena's
  P1 mark.
- A thrown flail ball or grapple stays out when switching weapons, and
  another weapon fires while a grapple hangs on. Mines, flail balls and
  grapples go once their slot no longer holds the weapon that put them
  out (mines fizzling).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shweetandClaude Opus 5.5 committed 2026-11-08 19:02:15 -05:00
1 parent e88fed1f4c
commit e55596b276
10 files changed
+84 -79

No files matched your search

+44 -11
View File
@@ -69,6 +69,16 @@ constexpr float WHIP_INNER = 2;
constexpr float WHIP_JITTER = 0.5f;
constexpr float WHIP_OUTER = 5;
constexpr float WISP_FRAME_STAGGER = 0.3f;
constexpr int TOMATO_BLOBS = 6;
constexpr float TOMATO_BLOB_MAX = 3.5f;
constexpr float TOMATO_BLOB_MIN = 1.5f;
constexpr float TOMATO_CORE = 6;
constexpr float TOMATO_FADE = 0.6f;
// How old a tomato can be when this screen first sees it and still splat with juice and a sound.
constexpr float TOMATO_FRESH = 0.5f;
constexpr float TOMATO_LIFE = 2;
constexpr Uint32 TOMATO_LOOK_STEP = 2654435761u;
constexpr float TOMATO_REACH = 10;
constexpr Burst TOMATO_BURST = {.gravity = 400, .life = 0.5f, .size = 2, .speed = 140, .spread = 1.2f, .count = 12};
// A random offset for an empowered attack or fighter to shake by, wider the more its colour fills in, like a damage
@@ -391,17 +401,6 @@ void Scene::match_start()
game.menu.isOpen = false;
}
// A tomato thrown on the results: a splat in the thrower's colour where it landed, spraying juice.
void Scene::tomato_splat(Vec2 position, int owner)
{
if (isMuted)
return;
Color color = game.color_get(owner, true);
game.effects.splat_spawn(position, color);
game.effects.burst(TOMATO_BURST, position, -PI / 2, color);
game.audio.play(SOUND_SPLAT);
}
// Readying with empty slots dealt them: this screen's own fighters hear the items go in.
void Scene::deal_play(int fighter)
{
@@ -425,6 +424,39 @@ void Scene::update()
{
for (int i = 0; i < game.sim.fighterCount; i++)
animator_update(game.sim.fighters[i], game.sprites.fighters[i]);
// Each new tomato (this screen's, or someone else's once their click arrives) splats with juice and a sound, once.
Match& match = game.sim.match;
tomatoesSeen = std::min(tomatoesSeen, match.tomatoCount);
for (; tomatoesSeen < match.tomatoCount; tomatoesSeen++)
{
Tomato& tomato = match.tomatoes[tomatoesSeen % TOMATO_MAX];
if (tomato.serial != tomatoesSeen || tomato.age > TOMATO_FRESH)
continue;
game.effects.burst(TOMATO_BURST, tomato.position, -PI / 2, game.color_get(tomato.owner, true));
game.audio.play(SOUND_SPLAT);
}
}
// The match's tomatoes as splats in their throwers' colours: a big blob where each landed with smaller ones flung round
// it (the same on every screen), holding and then fading out.
void Scene::tomatoes_draw()
{
for (Tomato& tomato : game.sim.match.tomatoes)
{
if (tomato.owner < 0 || tomato.age >= TOMATO_LIFE)
continue;
Color color =
color_fade(game.color_get(tomato.owner, true), std::min(1.0f, (TOMATO_LIFE - tomato.age) / TOMATO_FADE));
game.renderer.circle_draw(tomato.position, TOMATO_CORE, color);
Rng look = {.state = (Uint32)tomato.serial * TOMATO_LOOK_STEP + 1};
for (int i = 1; i < TOMATO_BLOBS; i++)
{
Vec2 offset = vec2_polar(look.random_get(0, 2 * PI), look.random_get(TOMATO_CORE, TOMATO_REACH));
game.renderer.circle_draw(tomato.position + offset, look.random_get(TOMATO_BLOB_MIN, TOMATO_BLOB_MAX),
color);
}
}
}
// The game between its last two ticks: the world, the bots' map (when debugging), afterimages, blasts under the
@@ -438,6 +470,7 @@ void Scene::draw()
for (int i = 0; i < game.sim.fighterCount; i++)
fighter_draw(game.sim.fighters[i]);
attacks_draw();
tomatoes_draw();
if (game.settings.isCollisionShown)
collision_draw();
}