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:
1 parent
e55596b276
commit
e9d8cac926
101 files changed
+5787
-7148
No files matched your search
+30
-7
@@ -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)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user