HUD pass, weapon and trinket rework, and font arrows

HUD:
- Hotbars fit a quarter of the screen, dropping to small then tiny slots
  (thinner outlines when tiny), with cooldown bars matching their size.
- HUD elements are drawn on their own layer and composited at one
  opacity: hotbar slots are see-through until hovered, the rest fade
  while hovered.
- Rival windows fit the right column in at most two rows, shrinking
  icons then font, then dropping icons for name, health and ping bars.
- Tooltips number equipped items' slots and say Recoil for weapons that
  only push back. Previews use arrows, spread uses degrees.
- Crosshair fills in and shakes with charge, and weapons shake after
  firing until their cooldown ends. DPS and speed meters off by default.

Gameplay:
- Dual Wield stacks: each copy fires another recently held weapon from
  a fanned off-hand, at its own level's cut.
- Flamethrower: no slowing, smaller fireball with some recoil, and an
  airblast that knocks others away.
- Iron Weights slam on any landing faster than a plain fall (platforms
  too), stacking radius and damage, and speeding fast falls.
- Stuck arrows are one-way ledges until they fade, matching their
  (slightly larger) hit size. Attacks now collide with slopes.
- Halved Tesla Coil, Recoil Coil, Diazepam, Shock Absorber, Feather and
  screenshake values.

Art and tools:
- Weapon and trinket icons merged into one sheet each (normal, small,
  tiny), cells in catalog order. Grimoire wisps stand upright.
- Fonts gain the degree sign and four arrows, decoded from UTF-8.
- Menu fighter preview walks at quarter speed.
- Unequip from either catalog tab by right click or gamepad.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shweetandClaude Opus 5.5 committed 2026-11-08 16:43:45 -05:00
1 parent 6003175173
commit 6f112ab231
54 files changed
+1150 -3046

No files matched your search

+24 -3
View File
@@ -12,6 +12,8 @@ constexpr float FLASH_CROSS = 0.5f;
constexpr float FLASH_LENGTH = 16;
constexpr float FLASH_LIFE = 0.05f;
constexpr float FLASH_WIDTH = 6;
constexpr float RING_LIFE = 0.3f;
constexpr int RING_WIDTH = 2;
constexpr float AFTERIMAGE_LIFE = 0.12f;
constexpr float NUMBER_GAP = 8;
constexpr float POPUP_FADE = 0.4f;
@@ -28,9 +30,9 @@ constexpr float POPUP_SPREAD = 40;
constexpr float PARTICLE_OUTLINE = 1;
constexpr float PARTICLE_RANDOM_MIN = 0.4f;
constexpr float SHAKE_DECAY = 2.5f;
constexpr float SHAKE_LIMIT = 12;
constexpr float SHAKE_MAX = 10;
constexpr float SHAKE_TRAUMA_MAX = 3;
constexpr float SHAKE_LIMIT = 6;
constexpr float SHAKE_MAX = 5;
constexpr float SHAKE_TRAUMA_MAX = 1.5f;
// Adds to one of the effect lists, dropping its oldest once it holds EFFECTS_MAX, so a busy fight can't flood the
// screen (or the renderer) with leftovers.
@@ -120,6 +122,11 @@ void Effects::flash_spawn(Vec2 position, float angle, float size, Color color)
effect_add(flashes, Flash{color, position, angle, FLASH_LIFE, size});
}
void Effects::ring_spawn(Vec2 position, float radius, Color color)
{
effect_add(rings, Ring{color, position, RING_LIFE, radius});
}
void Effects::dps_record(int amount)
{
hits.push_back({ticks * TICK, amount});
@@ -157,6 +164,10 @@ void Effects::update()
flash.life -= TICK;
std::erase_if(flashes, [](Flash& flash) { return flash.life <= 0; });
for (Ring& ring : rings)
ring.life -= TICK;
std::erase_if(rings, [](Ring& ring) { return ring.life <= 0; });
for (Afterimage& afterimage : afterimages)
afterimage.life -= TICK;
std::erase_if(afterimages, [](Afterimage& afterimage) { return afterimage.life <= 0; });
@@ -198,6 +209,15 @@ void Effects::draw(Game& game)
renderer.line_draw(flash.position - across, flash.position + across, width * FLASH_CROSS, flash.color);
}
// Each ring eases out to its full radius as it fades.
for (Ring& ring : rings)
{
float fade = ring.life / RING_LIFE;
float radius = ring.radius * (1 - fade * fade);
for (int i = 0; i < RING_WIDTH; i++)
renderer.ring_draw(ring.position, std::max(1.0f, radius - i), color_fade(ring.color, fade));
}
// Each particle shrinks and fades out. The outline is four strips so the fading core never shows it through.
for (Particle& particle : particles)
{
@@ -238,5 +258,6 @@ void Effects::clear()
hits.clear();
particles.clear();
popups.clear();
rings.clear();
shake = 0;
}