Deal items on ready, /pvp, and calmer rival windows
- Readying in a countdown deals any empty slots at random right away, and the countdown reads "Starting..." once everyone is ready. - /pvp toggles PvP in the sandbox, offline or in a lobby. - Rival windows sit at half opacity (half again when knocked out, and when hovered), show no item tooltips, and drop to small icons past two full-size rows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6f112ab231
commit
5d6ebf2bba
5 files changed
+77
-68
No files matched your search
@@ -238,8 +238,18 @@ void Chat::command(Game& game, const std::string& text)
|
||||
system("/color <colour>: Changes your colour. Colours: Red, Blue, Yellow, Green, Orange, Purple, Cyan, Pink, "
|
||||
"Lime, Teal, Magenta, Sky, Peach, Lavender, Brown, Silver.");
|
||||
system("/kick \"<player>\" \"<reason>\": Removes a player from the lobby (host only).");
|
||||
system("/pvp: Turns PvP on or off in the sandbox.");
|
||||
return;
|
||||
}
|
||||
if (name == "/pvp")
|
||||
{
|
||||
if (game.sim.match.state != MATCH_PRACTICE)
|
||||
return system("PvP can only be changed in the sandbox.");
|
||||
// Sent through this screen's controls like the menu's toggle, so every machine sees it on the same tick.
|
||||
bool isPvp = !game.local_get().isPvp;
|
||||
game.input.edit_send(game.devices[game.local], edit_encode({.type = EDIT_PVP, .value = isPvp}));
|
||||
return system(isPvp ? "PvP is now on." : "PvP is now off.");
|
||||
}
|
||||
if (name == "/kick")
|
||||
{
|
||||
if (words.size() < 2)
|
||||
|
||||
+27
-13
@@ -140,14 +140,17 @@ void Match::update(Sim& sim)
|
||||
break;
|
||||
case MATCH_COUNTDOWN:
|
||||
{
|
||||
// Ready sticks once pressed, and bots always are. Once everyone is, the wait drops to its last second.
|
||||
// Fighters choosing items pick them through their controls.
|
||||
// Ready sticks once pressed, and bots always are. Readying deals any slots still empty at random. Once
|
||||
// everyone is, the wait drops to its last second. Fighters choosing items pick them through their controls.
|
||||
bool isEveryone = true;
|
||||
for (int i = 0; i < sim.fighterCount; i++)
|
||||
{
|
||||
Fighter& fighter = sim.fighters[i];
|
||||
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);
|
||||
isEveryone &= isReady[i];
|
||||
}
|
||||
if (isEveryone)
|
||||
@@ -421,20 +424,31 @@ void Match::mirror_copy(Sim& sim, bool isTrinket)
|
||||
}
|
||||
}
|
||||
|
||||
// The countdown is over: slots still empty after choosing are dealt at random (once, and shared, when mirrored), and
|
||||
// the menu closes.
|
||||
// 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)
|
||||
{
|
||||
Fighter& fighter = sim.fighters[index];
|
||||
for (int kind = 0; kind < 2; kind++)
|
||||
{
|
||||
bool isTrinket = kind == 1;
|
||||
ItemRules& items = isTrinket ? rules.trinkets : rules.weapons;
|
||||
if (items.isMirror && index > 0)
|
||||
continue;
|
||||
if (isTrinket)
|
||||
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);
|
||||
if (items.isMirror)
|
||||
mirror_copy(sim, isTrinket);
|
||||
}
|
||||
}
|
||||
|
||||
// The countdown is over: slots still empty after choosing (by anyone who never readied) are dealt at random, and the
|
||||
// menu closes.
|
||||
void Match::fight_start(Sim& sim)
|
||||
{
|
||||
for (int i = 0; i < sim.fighterCount; i++)
|
||||
{
|
||||
Fighter& fighter = sim.fighters[i];
|
||||
picks_deal(sim.rng, fighter.weaponLoadout, fighter.weaponSlots, WEAPON_COUNT, rules.weapons.isDuplicates);
|
||||
picks_deal(sim.rng, fighter.trinketLoadout, fighter.trinketSlots, TRINKET_COUNT, rules.trinkets.isDuplicates);
|
||||
if (i == 0 && rules.weapons.isMirror)
|
||||
mirror_copy(sim, false);
|
||||
if (i == 0 && rules.trinkets.isMirror)
|
||||
mirror_copy(sim, true);
|
||||
}
|
||||
loadout_deal(sim, i);
|
||||
if (isWeaponPick || isTrinketPick)
|
||||
sim.cues->choice_close();
|
||||
isWeaponPick = false;
|
||||
|
||||
@@ -248,6 +248,7 @@ private:
|
||||
void fight_start(Sim& sim);
|
||||
void edit_take(Sim& sim, Fighter& fighter);
|
||||
void mirror_copy(Sim& sim, bool isTrinket);
|
||||
void loadout_deal(Sim& sim, int fighter);
|
||||
void deathmatch_update(Sim& sim);
|
||||
void race_update(Sim& sim);
|
||||
void queues_update(Sim& sim);
|
||||
|
||||
+37
-53
@@ -14,7 +14,8 @@ constexpr float RIVAL_BAR_MIN = 16;
|
||||
constexpr float RIVAL_DIVIDER = 1;
|
||||
constexpr float RIVAL_GAP = 4;
|
||||
constexpr float RIVAL_ICON_GAP = 2;
|
||||
constexpr float RIVAL_OUT_ALPHA = 0.25f;
|
||||
constexpr float RIVAL_ALPHA = 0.5f;
|
||||
constexpr float RIVAL_OUT_SHARE = 0.5f;
|
||||
constexpr float SPECTATE_GAP = 4;
|
||||
constexpr Control SPECTATE_CONTROLS[] = {CONTROL_LEFT, CONTROL_RIGHT, CONTROL_ATTACK};
|
||||
constexpr float HUD_IDLE_ALPHA = 0.5f;
|
||||
@@ -25,6 +26,8 @@ constexpr int RIVAL_COLUMNS_MAX = 3;
|
||||
// What the other fighters' windows try, from largest to smallest, until they all fit in a couple of rows: shrinking
|
||||
// icons before the font, then leaving the icons out.
|
||||
constexpr int RIVAL_ROWS_MAX = 2;
|
||||
// The most rows of full-size icons the windows can stack up between them before the icons shrink.
|
||||
constexpr int RIVAL_FULL_ICON_ROWS_MAX = 2;
|
||||
constexpr int RIVAL_STYLES = 6;
|
||||
constexpr int RIVAL_ICON_STYLES = 4;
|
||||
constexpr Font RIVAL_FONTS[RIVAL_STYLES] = {FONT_SMALL, FONT_SMALL, FONT_TINY, FONT_TINY, FONT_SMALL, FONT_TINY};
|
||||
@@ -393,7 +396,7 @@ void Ui::match_draw(Game& game)
|
||||
if (game.menu.isOpen)
|
||||
break;
|
||||
|
||||
SDL_snprintf(text, sizeof(text), "%s %d", info.round, info.seconds);
|
||||
SDL_snprintf(text, sizeof(text), "%s %s", info.round, info.time);
|
||||
text_center_draw(game, text, y, ROUND_SCALE);
|
||||
y += game.text.line_height_get(FONT_SMALL, ROUND_SCALE) + READY_GAP;
|
||||
SDL_snprintf(text, sizeof(text), isLocalReady ? "%d/%d Ready" : "%d/%d Ready - Press %s to Ready Up",
|
||||
@@ -731,7 +734,8 @@ void Ui::loadout_draw(Game& game, float alpha, float top)
|
||||
}
|
||||
|
||||
// Text centred across the canvas with its top at y.
|
||||
// What the countdown shows: the round's name, whole seconds left, how many are ready, and the key that readies up.
|
||||
// What the countdown shows: the round's name, whole seconds left (or Starting... once everyone's ready), how many are
|
||||
// ready, and the key that readies up.
|
||||
static CountdownInfo countdown_info_get(Game& game, bool isPad)
|
||||
{
|
||||
Match& match = game.sim.match;
|
||||
@@ -742,9 +746,12 @@ static CountdownInfo countdown_info_get(Game& game, bool isPad)
|
||||
SDL_strlcpy(info.round, "Final Round", sizeof(info.round));
|
||||
else
|
||||
SDL_snprintf(info.round, sizeof(info.round), "Round %d", match.round + 1);
|
||||
info.seconds = (int)std::ceil(match.timer);
|
||||
for (int i = 0; i < game.sim.fighterCount; i++)
|
||||
info.ready += match.isReady[i];
|
||||
if (info.ready == game.sim.fighterCount)
|
||||
SDL_strlcpy(info.time, "Starting...", sizeof(info.time));
|
||||
else
|
||||
SDL_snprintf(info.time, sizeof(info.time), "%d", (int)std::ceil(match.timer));
|
||||
Binding binding = game.input.bindings[CONTROL_READY];
|
||||
game.input.binding_name_get(isPad ? binding.pad : binding.key, isPad, info.key, sizeof(info.key));
|
||||
return info;
|
||||
@@ -758,7 +765,6 @@ void Ui::countdown_aside_draw(Game& game, bool isPad)
|
||||
{
|
||||
CountdownInfo info = countdown_info_get(game, isPad);
|
||||
const char* round = info.round;
|
||||
int seconds = info.seconds;
|
||||
int ready = info.ready;
|
||||
const char* key = game.sim.match.isReady[game.local] ? nullptr : info.key;
|
||||
float margin = (CANVAS_WIDTH - MENU_WIDTH) / 2;
|
||||
@@ -771,16 +777,20 @@ void Ui::countdown_aside_draw(Game& game, bool isPad)
|
||||
return game.text.line_height_get(FONT_SMALL, scale);
|
||||
};
|
||||
|
||||
float labelScale = game.text.size_get(round, FONT_SMALL, ASIDE_LABEL_SCALE).x <= margin - ASIDE_PADDING * 2
|
||||
? ASIDE_LABEL_SCALE
|
||||
: 1;
|
||||
float height =
|
||||
game.text.line_height_get(FONT_SMALL, labelScale) + game.text.line_height_get(FONT_SMALL, ROUND_SCALE);
|
||||
// Each line at the largest size that fits the margin.
|
||||
auto scale_fit = [&](const char* line, float scale) {
|
||||
for (; scale > 1; scale--)
|
||||
if (game.text.size_get(line, FONT_SMALL, scale).x <= margin - ASIDE_PADDING * 2)
|
||||
break;
|
||||
return scale;
|
||||
};
|
||||
float labelScale = scale_fit(round, ASIDE_LABEL_SCALE);
|
||||
float timeScale = scale_fit(info.time, ROUND_SCALE);
|
||||
float height = game.text.line_height_get(FONT_SMALL, labelScale) + game.text.line_height_get(FONT_SMALL, timeScale);
|
||||
float y = middle - height / 2;
|
||||
y += line_draw(round, margin / 2, y, labelScale);
|
||||
line_draw(info.time, margin / 2, y, timeScale);
|
||||
char text[TOOLTIP_LENGTH];
|
||||
SDL_snprintf(text, sizeof(text), "%d", seconds);
|
||||
line_draw(text, margin / 2, y, ROUND_SCALE);
|
||||
|
||||
float rightX = CANVAS_WIDTH - margin / 2;
|
||||
y = middle - (key ? LINE_HEIGHT * 3 : LINE_HEIGHT) / 2;
|
||||
@@ -1177,12 +1187,12 @@ static void effects_preview_get(Game& game, const char* now, const char* next, c
|
||||
|
||||
// Name, numbers at a level and description of a weapon or trinket. Trinkets say how many copies are equipped, and
|
||||
// when they stack.
|
||||
Tooltip Ui::tooltip_build(Game& game, bool isTrinket, int item, int level, int owner)
|
||||
Tooltip Ui::tooltip_build(Game& game, bool isTrinket, int item, int level)
|
||||
{
|
||||
Tooltip tooltip;
|
||||
tooltip.isTrinket = isTrinket;
|
||||
tooltip.item = item;
|
||||
Fighter& fighter = owner >= 0 ? game.sim.fighters[owner] : game.local_get();
|
||||
Fighter& fighter = game.local_get();
|
||||
int* loadout = isTrinket ? fighter.trinketLoadout : fighter.weaponLoadout;
|
||||
int slots = isTrinket ? fighter.trinketSlots : fighter.weaponSlots;
|
||||
tooltip.slot = std::find(loadout, loadout + slots, item) - loadout;
|
||||
@@ -2271,10 +2281,11 @@ static float rival_ping_get(Game& game, int fighter)
|
||||
|
||||
// Lays out the other fighters' windows in the HUD's right column, from the bottom up to the readouts above, in no more
|
||||
// than two rows: as many to a row as fit across (the rows as even as they go). A window is as wide as its icons, name
|
||||
// or health (with its connection's bars, online). Without icons, the health number can go to make room. When they don't
|
||||
// all fit, the icons shrink to small, then the font to tiny, then the icons to tiny, then the icons are left out. Each
|
||||
// kind of icon wraps onto more rows past three across, and tiny icons wrap further if they still don't fit. If even
|
||||
// names and health alone don't fit, the windows split the column evenly and the names are cut short.
|
||||
// or health (with its connection's bars, online). Without icons, the health number can go to make room. Full-size
|
||||
// icons stack no more than two rows deep across the windows. When they don't all fit, the icons shrink to small, then
|
||||
// the font to tiny, then the icons to tiny, then the icons are left out. Each kind of icon wraps onto more rows past
|
||||
// three across, and tiny icons wrap further if they still don't fit. If even names and health alone don't fit, the
|
||||
// windows split the column evenly and the names are cut short.
|
||||
RivalLayout Ui::rivals_layout(Game& game)
|
||||
{
|
||||
RivalLayout layout;
|
||||
@@ -2336,7 +2347,8 @@ RivalLayout Ui::rivals_layout(Game& game)
|
||||
perRow = std::clamp((int)((HUD_COLUMN_WIDTH + RIVAL_GAP) / (width + RIVAL_GAP)), 1, layout.count);
|
||||
int cardRows = (layout.count + perRow - 1) / perRow;
|
||||
perRow = (layout.count + cardRows - 1) / cardRows;
|
||||
isFit = width <= HUD_COLUMN_WIDTH && cardRows <= RIVAL_ROWS_MAX &&
|
||||
bool isFullTall = layout.size == ICON_FULL && cardRows * layout.rows > RIVAL_FULL_ICON_ROWS_MAX;
|
||||
isFit = width <= HUD_COLUMN_WIDTH && cardRows <= RIVAL_ROWS_MAX && !isFullTall &&
|
||||
cardRows * (height + RIVAL_GAP) - RIVAL_GAP <= room;
|
||||
}
|
||||
}
|
||||
@@ -2359,9 +2371,8 @@ RivalLayout Ui::rivals_layout(Game& game)
|
||||
|
||||
// Every other fighter's window in a match, bordered in its colour: its name centred (cut short if it doesn't fit), its
|
||||
// health (online with its connection's bars after), then (with room) its weapons, a divider, and its trinkets, the
|
||||
// weapon in hand lit. Each is laid over the game as one piece,
|
||||
// see-through for a fighter out of the round, and fades while the mouse is over it. Hovering an icon shows the item's
|
||||
// tooltip, as that fighter has it, in its colour.
|
||||
// weapon in hand lit. Each is laid over the game as one piece, half see-through (half again for a fighter out of the
|
||||
// round), and fades by half again while the mouse is over it.
|
||||
void Ui::rivals_draw(Game& game)
|
||||
{
|
||||
Match& match = game.sim.match;
|
||||
@@ -2372,15 +2383,12 @@ void Ui::rivals_draw(Game& game)
|
||||
float inset = PANEL_BORDER + RIVAL_PADDING;
|
||||
float iconSize = ICON_PIXELS[layout.size];
|
||||
float cell = iconSize + RIVAL_ICON_GAP;
|
||||
int hoveredFighter = -1;
|
||||
int hoveredKind = 0;
|
||||
int hoveredSlot = 0;
|
||||
for (int k = 0; k < layout.count; k++)
|
||||
{
|
||||
int index = layout.fighters[k];
|
||||
Fighter& fighter = game.sim.fighters[index];
|
||||
Rect card = layout.cards[k];
|
||||
float base = fighter.isAlive || match.is_racing() || index == spectated ? 1 : RIVAL_OUT_ALPHA;
|
||||
float base = RIVAL_ALPHA * (fighter.isAlive || match.is_racing() || index == spectated ? 1 : RIVAL_OUT_SHARE);
|
||||
Color color = game.color_get(index);
|
||||
Color dim = color_shade(color, SECONDARY_SHADE);
|
||||
renderer.layer_begin();
|
||||
@@ -2446,21 +2454,13 @@ void Ui::rivals_draw(Game& game)
|
||||
int* loadout = isTrinket ? fighter.trinketLoadout : fighter.weaponLoadout;
|
||||
for (int slot = 0; slot < slots; slot++)
|
||||
{
|
||||
Rect box = {x + slot % layout.columns[kind] * cell, y + slot / layout.columns[kind] * cell, iconSize,
|
||||
iconSize};
|
||||
int item = loadout[slot];
|
||||
if (item < 0)
|
||||
continue;
|
||||
bool isLit = isTrinket || slot == fighter.slot;
|
||||
icon_draw(game, isTrinket, item,
|
||||
{.color = isLit ? color : dim, .position = {box.x + box.w / 2, box.y + box.h / 2}},
|
||||
layout.size);
|
||||
if (is_point_in_rect(game.input.mouse, box))
|
||||
{
|
||||
hoveredFighter = index;
|
||||
hoveredKind = kind;
|
||||
hoveredSlot = slot;
|
||||
}
|
||||
Vec2 centre = {x + slot % layout.columns[kind] * cell + iconSize / 2,
|
||||
y + slot / layout.columns[kind] * cell + iconSize / 2};
|
||||
icon_draw(game, isTrinket, item, {.color = isLit ? color : dim, .position = centre}, layout.size);
|
||||
}
|
||||
x += layout.columns[kind] * cell;
|
||||
if (kind == 0)
|
||||
@@ -2471,22 +2471,6 @@ void Ui::rivals_draw(Game& game)
|
||||
}
|
||||
renderer.layer_end(base * hover_alpha_get(rivalHovers[index]));
|
||||
}
|
||||
|
||||
if (hoveredFighter < 0 || game.menu.isOpen)
|
||||
return;
|
||||
Fighter& fighter = game.sim.fighters[hoveredFighter];
|
||||
bool isTrinket = hoveredKind == 1;
|
||||
int item = (isTrinket ? fighter.trinketLoadout : fighter.weaponLoadout)[hoveredSlot];
|
||||
int level = (isTrinket ? fighter.trinketSlotLevels : fighter.weaponSlotLevels)[hoveredSlot];
|
||||
Tooltip tooltip = tooltip_build(game, isTrinket, item, level, hoveredFighter);
|
||||
tooltip.slot = hoveredSlot;
|
||||
// The tooltip takes that fighter's colour for the moment, so it's clear whose item it is.
|
||||
Color colors[2] = {tint, shade};
|
||||
tint = game.color_get(hoveredFighter);
|
||||
shade = color_shade(tint, SECONDARY_SHADE);
|
||||
tooltip_draw(game, tooltip);
|
||||
tint = colors[0];
|
||||
shade = colors[1];
|
||||
}
|
||||
|
||||
// Choosing items with several players on this screen: a panel each below top (side by side for two, a 2x2 grid for
|
||||
|
||||
@@ -79,7 +79,7 @@ struct RivalLayout
|
||||
struct CountdownInfo
|
||||
{
|
||||
char round[64] = {};
|
||||
int seconds = 0;
|
||||
char time[16] = {};
|
||||
int ready = 0;
|
||||
char key[64] = {};
|
||||
};
|
||||
@@ -198,7 +198,7 @@ public:
|
||||
void draw(Game& game);
|
||||
void slot_draw(Game& game, Slot slot);
|
||||
void panel_draw(Renderer& renderer, Rect rect, Color border, float fillAlpha = -1, float stroke = PANEL_BORDER);
|
||||
Tooltip tooltip_build(Game& game, bool isTrinket, int item, int level, int owner = -1);
|
||||
Tooltip tooltip_build(Game& game, bool isTrinket, int item, int level);
|
||||
void stats_table_build(Fighter& fighter, int weapon, int level, bool isPreview, Tooltip& tooltip);
|
||||
void table_row_draw(Game& game, Tooltip& tooltip, int row, Vec2 corner, const float* columns, Font font,
|
||||
float alpha);
|
||||
|
||||
Reference in new issue
Block a user