Weapons built from traits, fashion, Hourglass, About tab, and Ithaca

- Every attack is now assembled from traits on its definition instead
  of per-weapon code: an anchor and path (thrusts, out-and-back, arcs),
  shared flight physics (gravity, drag, acceleration, homing, and one
  wall response: end, burst, stick, crawl, bounce, stop, pass, return,
  latch), and flags like bouncy, drifting, tethered, orbiting, instant
  and arming. Melee are projectiles too.
- Shots have a lifetime and a fade (when damage bottoms out). Range is
  speed times lifetime. Ricochets and the Vacuum wind lifetime back.
- The Vacuum pulls anyone's shots, and its airblast flips them out
  along the aim, claiming enemies' (fading into your colour).
- New trinket: Hourglass, longer projectile lifetime and range. Ropes
  and chains lengthen with it and the Accelerator.
- Fashion: 23 items worn on the head, face or torso, chosen in Profile.
- Flamethrower flames hit continuously, the whip coils and sweeps,
  bouncing shots roll over ramps, and Ricochet relights grenade fuses.
- Profile tab, colour tooltips, option hints on every setting, an
  About tab with credits and links, and the Ithaca font.
- Loadouts lock once readied, tomatoes clear with the match, and the
  round-over pause is 4 seconds.
- CC0 license. Tools and CLAUDE.md are no longer tracked, and the web
  shell moves to web/.
This commit is contained in:
shweet committed 2026-11-10 15:27:12 -05:00
1 parent e55596b276
commit e9d8cac926
101 files changed
+5787 -7148

No files matched your search

+30 -7
View File
@@ -83,10 +83,30 @@ void Chat::clear()
void Chat::players_clear()
{
bubbles.clear();
pvps.clear();
typing.clear();
recent.clear();
}
// In a lobby's sandbox, tells everyone whenever a player turns PvP on or off, in their colour. Someone just arrived
// is only noted.
void Chat::pvps_announce(Game& game)
{
if (!game.session.isJoined || game.sim.match.state != MATCH_PRACTICE)
return;
for (int i = 0; i < game.sim.fighterCount; i++)
{
int owner = game.sim.match.owners[i];
bool isPvp = game.sim.fighters[i].isPvp;
auto known = pvps.find(owner);
bool isChanged = known != pvps.end() && known->second != isPvp;
pvps[owner] = isPvp;
if (isChanged)
system(game.session.name_get(game, owner) + (isPvp ? " turned PvP on." : " turned PvP off."),
game.color_get(i, TINT_PLAIN));
}
}
// Who a fighter's chat belongs to: its lobby member online; offline, this screen's own fighter is the one that talks
// (under this screen's member id, -1).
int Chat::member_get(Game& game, int fighter)
@@ -96,9 +116,9 @@ int Chat::member_get(Game& game, int fighter)
return fighter == game.local ? game.net.selfId : NO_MEMBER;
}
void Chat::system(const std::string& text)
void Chat::system(const std::string& text, Color color)
{
lines.push_back({.text = text, .color = WHITE, .time = seconds_get()});
lines.push_back({.text = text, .color = color, .time = seconds_get()});
if ((int)lines.size() > CHAT_LINES_MAX)
lines.erase(lines.begin());
}
@@ -118,7 +138,7 @@ void Chat::say(Game& game, int member, const std::string& text)
bool isOnline = game.session.isActive;
int fighter = isOnline ? game.session.fighter_get(game, member) : game.local;
Color color = fighter >= 0 ? game.color_get(fighter, true) : WHITE;
Color color = fighter >= 0 ? game.color_get(fighter, TINT_PLAIN) : WHITE;
std::string name = isOnline ? game.session.name_get(game, member) : std::string(game.settings.name);
lines.push_back({.text = name + ": " + text, .color = color, .time = now});
if ((int)lines.size() > CHAT_LINES_MAX)
@@ -135,6 +155,7 @@ void Chat::update(Game& game)
for (std::string& notice : game.net.notices)
system(notice);
game.net.notices.clear();
pvps_announce(game);
Input& input = game.input;
if (!isOpen && !game.menu.isOpen && input.is_key_control_pressed(CONTROL_CHAT))
open(game);
@@ -219,7 +240,7 @@ static std::vector<std::string> arguments_split(const std::string& text)
return words;
}
// /name <name> and /color <colour> change this player's (as Customize does), /kick "<name>" "<reason>" (the host
// /name <name> and /color <colour> change this player's (as Profile does), /kick "<name>" "<reason>" (the host
// only) puts a player out of the lobby, and /help lists them. Answers are system lines only this player sees.
void Chat::command(Game& game, const std::string& text)
{
@@ -248,7 +269,9 @@ void Chat::command(Game& game, const std::string& text)
// 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 (!game.session.isActive)
system(isPvp ? "PvP is now on." : "PvP is now off.");
return;
}
if (name == "/kick")
{
@@ -360,9 +383,9 @@ void Chat::overhead_draw(Game& game)
int member = member_get(game, i);
if (!fighter.isAlive || member == NO_MEMBER)
continue;
Color color = game.color_get(i, true);
Color color = game.color_get(i, TINT_PLAIN);
float centerX = fighter.body.box.x + fighter.body.box.w / 2;
float y = fighter.body.box.y - above;
float y = game.scene.top_get(fighter) - above;
bool isTyping = member == game.net.selfId ? isOpen : typing[member];
if (isTyping)
{