Tomatoes, match tabs, shared pings, and a round of fixes

- On the results, everyone but the winner splats tomatoes where they
  click (bots aim at the winner), with a new splat sound.
- During a match the menu has the item tabs (choosing when the
  countdown allows, and otherwise levelling the sandbox's levels for
  when you're back), Play (the game's rules) and Settings.
- Pings are shared through the server (protocol 4), so other players'
  connection bars show over their heads and on their windows.
- PvP comes into lobbies with your profile and is saved.
- The choice menu opens by itself however the countdown began.
- Knocked-out fighters keep their buttons current, so Ready on the
  results toggles once per press.
- The weapon wheel wraps both ways. Readying with empty slots plays
  the equip sound for your own fighter.
- Shock Absorber: up to 50% less weapon movement and 33% less
  knockback. Only the catalog previews the next level.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shweetandClaude Opus 5.5 committed 2026-11-08 18:11:06 -05:00
1 parent cdb0845da2
commit 9a41474069
29 files changed
+295 -79

No files matched your search

+32
View File
@@ -14,6 +14,12 @@ constexpr float FLASH_LIFE = 0.05f;
constexpr float FLASH_WIDTH = 6;
constexpr float RING_LIFE = 0.3f;
constexpr int RING_WIDTH = 2;
constexpr float SPLAT_CORE = 6;
constexpr float SPLAT_FADE = 0.6f;
constexpr float SPLAT_LIFE = 2;
constexpr float SPLAT_REACH = 10;
constexpr float SPLAT_SIZE_MAX = 3.5f;
constexpr float SPLAT_SIZE_MIN = 1.5f;
constexpr float AFTERIMAGE_LIFE = 0.12f;
constexpr float NUMBER_GAP = 8;
constexpr float POPUP_FADE = 0.4f;
@@ -127,6 +133,19 @@ void Effects::ring_spawn(Vec2 position, float radius, Color color)
effect_add(rings, Ring{color, position, RING_LIFE, radius});
}
// A big blob where it landed, with smaller ones flung round it.
void Effects::splat_spawn(Vec2 position, Color color)
{
Splat splat = {.color = color, .position = position, .life = SPLAT_LIFE};
splat.sizes[0] = SPLAT_CORE;
for (int i = 1; i < SPLAT_BLOBS; i++)
{
splat.blobs[i] = vec2_polar(random_get(0, 2 * PI), random_get(SPLAT_CORE, SPLAT_REACH));
splat.sizes[i] = random_get(SPLAT_SIZE_MIN, SPLAT_SIZE_MAX);
}
effect_add(splats, splat);
}
void Effects::dps_record(int amount)
{
hits.push_back({ticks * TICK, amount});
@@ -168,6 +187,10 @@ void Effects::update()
ring.life -= TICK;
std::erase_if(rings, [](Ring& ring) { return ring.life <= 0; });
for (Splat& splat : splats)
splat.life -= TICK;
std::erase_if(splats, [](Splat& splat) { return splat.life <= 0; });
for (Afterimage& afterimage : afterimages)
afterimage.life -= TICK;
std::erase_if(afterimages, [](Afterimage& afterimage) { return afterimage.life <= 0; });
@@ -209,6 +232,14 @@ void Effects::draw(Game& game)
renderer.line_draw(flash.position - across, flash.position + across, width * FLASH_CROSS, flash.color);
}
// Each splat holds, then fades out over its last moments.
for (Splat& splat : splats)
{
Color color = color_fade(splat.color, std::min(1.0f, splat.life / SPLAT_FADE));
for (int i = 0; i < SPLAT_BLOBS; i++)
renderer.circle_draw(splat.position + splat.blobs[i], splat.sizes[i], color);
}
// Each ring eases out to its full radius as it fades.
for (Ring& ring : rings)
{
@@ -259,5 +290,6 @@ void Effects::clear()
particles.clear();
popups.clear();
rings.clear();
splats.clear();
shake = 0;
}