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:
1 parent
cdb0845da2
commit
9a41474069
29 files changed
+295
-79
No files matched your search
+36
-12
@@ -4,6 +4,7 @@
|
||||
#include <numeric>
|
||||
#include <cstring>
|
||||
|
||||
constexpr float TOMATO_COOLDOWN = 0.25f;
|
||||
constexpr Burst FINISH_BURST = {.gravity = 200, .life = 1, .size = 3, .speed = 240, .count = 40};
|
||||
constexpr float RACE_GRACE = 15;
|
||||
constexpr float ROUND_OVER_TIME = 2.5f;
|
||||
@@ -45,9 +46,11 @@ Rules rules_clean(Rules rules)
|
||||
return rules;
|
||||
}
|
||||
|
||||
// Fills a loadout's empty slots with random picks out of count items, none repeating what's already in it.
|
||||
static void picks_deal(Rng& rng, int* loadout, int slots, int count, bool isDuplicates)
|
||||
// Fills a loadout's empty slots with random picks out of count items, none repeating what's already in it. Returns
|
||||
// whether it filled any.
|
||||
static bool picks_deal(Rng& rng, int* loadout, int slots, int count, bool isDuplicates)
|
||||
{
|
||||
bool isDealt = false;
|
||||
for (int i = 0; i < slots; i++)
|
||||
{
|
||||
if (loadout[i] >= 0)
|
||||
@@ -56,7 +59,9 @@ static void picks_deal(Rng& rng, int* loadout, int slots, int count, bool isDupl
|
||||
while (!isDuplicates && std::find(loadout, loadout + slots, pick) != loadout + slots)
|
||||
pick = rng.random_get(0, count);
|
||||
loadout[i] = pick;
|
||||
isDealt = true;
|
||||
}
|
||||
return isDealt;
|
||||
}
|
||||
|
||||
// Whether a round hands out this kind of item: the first round of the match always, every round, or every period.
|
||||
@@ -93,6 +98,8 @@ void Match::update(Sim& sim)
|
||||
{
|
||||
case MATCH_PRACTICE:
|
||||
case MATCH_OVER:
|
||||
if (state == MATCH_OVER)
|
||||
tomatoes_update(sim);
|
||||
// Online, after a queued game, Ready says to play it again, and once every player still in it is set, it
|
||||
// counts up to the server starting afresh with them.
|
||||
if (state == MATCH_OVER && isOnline)
|
||||
@@ -149,8 +156,8 @@ void Match::update(Sim& sim)
|
||||
edit_take(sim, fighter);
|
||||
bool wasReady = isReady[i];
|
||||
isReady[i] |= fighter.isBot || fighter.is_down(CONTROL_READY);
|
||||
if (isReady[i] && !wasReady)
|
||||
loadout_deal(sim, i);
|
||||
if (isReady[i] && !wasReady && loadout_deal(sim, i))
|
||||
sim.cues->deal_play(i);
|
||||
isEveryone &= isReady[i];
|
||||
}
|
||||
if (isEveryone)
|
||||
@@ -316,8 +323,6 @@ void Match::round_start(Sim& sim, bool isFirst)
|
||||
mirror_copy(sim, true);
|
||||
isWeaponPick &= rules.weapons.isChoice;
|
||||
isTrinketPick &= rules.trinkets.isChoice;
|
||||
if (isWeaponPick || isTrinketPick)
|
||||
sim.cues->choice_open();
|
||||
|
||||
std::fill(std::begin(isReady), std::end(isReady), false);
|
||||
std::fill(std::begin(finishTimes), std::end(finishTimes), -1.0f);
|
||||
@@ -410,6 +415,20 @@ void Match::edit_take(Sim& sim, Fighter& fighter)
|
||||
}
|
||||
}
|
||||
|
||||
// On the results, everyone but the winner can splat tomatoes where they click, for show, a few a second at most.
|
||||
void Match::tomatoes_update(Sim& sim)
|
||||
{
|
||||
for (int i = 0; i < sim.fighterCount; i++)
|
||||
{
|
||||
Fighter& fighter = sim.fighters[i];
|
||||
tomatoCooldowns[i] -= TICK;
|
||||
if (i == winner || isGone[i] || tomatoCooldowns[i] > 0 || !fighter.is_pressed(CONTROL_ATTACK))
|
||||
continue;
|
||||
tomatoCooldowns[i] = TOMATO_COOLDOWN;
|
||||
sim.cues->tomato_splat(fighter.controls.cursor, i);
|
||||
}
|
||||
}
|
||||
|
||||
// Copies the first fighter's loadout of one kind of item to every other fighter.
|
||||
void Match::mirror_copy(Sim& sim, bool isTrinket)
|
||||
{
|
||||
@@ -424,10 +443,12 @@ void Match::mirror_copy(Sim& sim, bool isTrinket)
|
||||
}
|
||||
}
|
||||
|
||||
// Deals a fighter's empty slots at random. A mirrored kind is dealt only to the first fighter, and copied to everyone.
|
||||
void Match::loadout_deal(Sim& sim, int index)
|
||||
// Deals a fighter's empty slots at random, returning whether it filled any. A mirrored kind is dealt only to the
|
||||
// first fighter, and copied to everyone.
|
||||
bool Match::loadout_deal(Sim& sim, int index)
|
||||
{
|
||||
Fighter& fighter = sim.fighters[index];
|
||||
bool isDealt = false;
|
||||
for (int kind = 0; kind < 2; kind++)
|
||||
{
|
||||
bool isTrinket = kind == 1;
|
||||
@@ -435,12 +456,15 @@ void Match::loadout_deal(Sim& sim, int index)
|
||||
if (items.isMirror && index > 0)
|
||||
continue;
|
||||
if (isTrinket)
|
||||
picks_deal(sim.rng, fighter.trinketLoadout, fighter.trinketSlots, TRINKET_COUNT, items.isDuplicates);
|
||||
isDealt |=
|
||||
picks_deal(sim.rng, fighter.trinketLoadout, fighter.trinketSlots, TRINKET_COUNT, items.isDuplicates);
|
||||
else
|
||||
picks_deal(sim.rng, fighter.weaponLoadout, fighter.weaponSlots, WEAPON_COUNT, items.isDuplicates);
|
||||
isDealt |=
|
||||
picks_deal(sim.rng, fighter.weaponLoadout, fighter.weaponSlots, WEAPON_COUNT, items.isDuplicates);
|
||||
if (items.isMirror)
|
||||
mirror_copy(sim, isTrinket);
|
||||
}
|
||||
return isDealt;
|
||||
}
|
||||
|
||||
// The countdown is over: slots still empty after choosing (by anyone who never readied) are dealt at random, and the
|
||||
@@ -449,8 +473,6 @@ void Match::fight_start(Sim& sim)
|
||||
{
|
||||
for (int i = 0; i < sim.fighterCount; i++)
|
||||
loadout_deal(sim, i);
|
||||
if (isWeaponPick || isTrinketPick)
|
||||
sim.cues->choice_close();
|
||||
isWeaponPick = false;
|
||||
isTrinketPick = false;
|
||||
clock = 0;
|
||||
@@ -788,6 +810,7 @@ Profile profile_get(Fighter& fighter)
|
||||
std::copy(std::begin(fighter.trinketSlotLevels), std::end(fighter.trinketSlotLevels), profile.trinketSlotLevels);
|
||||
std::copy(std::begin(fighter.weaponItemLevels), std::end(fighter.weaponItemLevels), profile.weaponItemLevels);
|
||||
std::copy(std::begin(fighter.trinketItemLevels), std::end(fighter.trinketItemLevels), profile.trinketItemLevels);
|
||||
profile.isPvp = fighter.isPvp;
|
||||
return profile;
|
||||
}
|
||||
|
||||
@@ -808,6 +831,7 @@ int Match::member_join(Sim& sim, int member, const Profile& profile)
|
||||
std::copy(std::begin(profile.trinketSlotLevels), std::end(profile.trinketSlotLevels), fighter.trinketSlotLevels);
|
||||
std::copy(std::begin(profile.weaponItemLevels), std::end(profile.weaponItemLevels), fighter.weaponItemLevels);
|
||||
std::copy(std::begin(profile.trinketItemLevels), std::end(profile.trinketItemLevels), fighter.trinketItemLevels);
|
||||
fighter.isPvp = profile.isPvp;
|
||||
owners[index] = member;
|
||||
std::memcpy(names[index], profile.name, sizeof(names[index]));
|
||||
names[index][PLAYER_NAME_MAX] = '\0';
|
||||
|
||||
Reference in new issue
Block a user