Files
Papay-Shooter/debug/summary_capture.gd
Nicholas ButzkeandClaude Opus 5 986179854d feat(modes): matches now end, and something is at stake when they do
There was a string — `current_gamemode`, always "Deathmatch" — an OptionButton
with one entry in it, and a five-minute timer that counted to zero, set
`match_active = false`, and did nothing else. No winner was declared, no summary
appeared, the clock froze at 00:00, and players carried on shooting each other
in a match that had stopped counting. Nothing was tracked beyond a running kill
count. A match did not end so much as stop mattering, and everything a player
does in the last minute only matters if there is a last minute.

globals/game_mode.gd answers the three questions a mode has to answer — how you
score, when it ends, who won — for three modes:

  Deathmatch        25 frags or 10 minutes.
  Team Deathmatch   two squads to 50, teams balanced by COUNT on join (a 4v4
                    that loses three from one side must refill the short side;
                    round-robin on join order leaves it 4v1 forever), friendly
                    fire off and enforced on the server before damage is even
                    broadcast — a mode where the damage lands but the kill does
                    not count is worse than either.
  Gun Game          every kill promotes you a rung and swaps your weapon. The
                    score IS the rung and the limit IS the ladder's length, so
                    finishing the ladder and reaching the score limit are the
                    same event and only one win condition exists.

`check_win` is a pure function of the stats and the clock, deliberately, because
a win condition that can only be exercised by playing a whole match is one
nobody tests — and the previous one never was. debug/game_mode_check.gd runs 30
cases over it: a tie is a DRAW rather than a win for whoever came first out of
the dictionary; a team match is decided on the TEAM's total, which a per-player
check never reaches; every ladder rung names a weapon that exists, or that rung
softlocks the mode; and promoting past the top clamps, because the winning kill
promotes before the match-end RPC lands.

Tracking now covers score, team, current streak, best streak and ladder rung.
Score and kills are separate numbers because in Gun Game they coincide and in
anything with an objective they would not. The scoreboard ranks by score, shows
the mode's own noun for it, and puts the limit on the top line — a win condition
players cannot see is one they cannot play toward.

ui/match_summary.gd gives the ending somewhere to happen: who won, WHY (time and
frag limit are different stories about the same scoreline), team totals, full
standings, and a way out that is not alt-F4. Play Again is host-only on a server.

Three bugs found on the way, all of which only became bugs once matches could
actually end: loading a level reset the clock but not the scores, so the second
match on a server would have ended on its first kill; `.rpc()` on an offline
peer does not call locally, so singleplayer had no killfeed and no stat sync at
all; and a rocket already in the air at the whistle could change the result
after the summary was on screen.

spawn smoke 0, game modes 30/30, movement 11/11.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-28 12:28:32 -04:00

72 lines
2.1 KiB
GDScript

extends SceneTree
## Photograph the end-of-match summary, for a free-for-all and for a team mode.
##
## godot --path . --windowed --resolution 1280x800 \
## -s res://debug/summary_capture.gd -- <out_dir>
##
## Fed a synthetic result rather than a played match, so the screen can be looked
## at without spending ten minutes reaching a frag limit — and so the awkward
## cases (a draw, a long name, a team scoreline) can be put on screen on purpose
## instead of waiting for them to happen.
var _out := "."
func _initialize() -> void:
var args := OS.get_cmdline_user_args()
if args.size() > 0:
_out = String(args[0])
_run()
func _run() -> void:
await process_frame
UITheme.apply_global(self)
var layer := CanvasLayer.new()
root.add_child(layer)
var back := ColorRect.new()
back.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
back.color = Color(0.30, 0.42, 0.62)
layer.add_child(back)
var summary := MatchSummary.new()
layer.add_child(summary)
await _shot(summary, "ffa", {
"reason": "score", "name": "Nicholas", "mode": GameMode.DEATHMATCH,
"standings": [1, 2, 3],
"stats": {
1: _p("Nicholas", 25, 25, 11, 4, 7),
2: _p("Papaya Enjoyer", 19, 19, 17, 2, 4),
3: _p("guest_4471", 8, 8, 24, 9, 2),
}})
await _shot(summary, "team", {
"reason": "time", "team": 2, "name": "MAGENTA TEAM",
"mode": GameMode.TEAM_DEATHMATCH,
"standings": [3, 1, 4, 2],
"stats": {
1: _p("Nicholas", 14, 14, 9, 3, 5, 1),
2: _p("Papaya Enjoyer", 6, 6, 15, 1, 2, 1),
3: _p("guest_4471", 17, 17, 8, 6, 6, 2),
4: _p("bananaboat", 12, 12, 10, 4, 3, 2),
}})
quit(0)
func _shot(summary: MatchSummary, tag: String, result: Dictionary) -> void:
summary.show_result(result)
for _i in 6:
await process_frame
root.get_texture().get_image().save_png("%s/summary_%s.png" % [_out, tag])
print("summary_capture: saved ", tag)
func _p(who: String, score: int, kills: int, deaths: int, assists: int,
streak: int, team: int = 0) -> Dictionary:
return {"username": who, "score": score, "kills": kills, "deaths": deaths,
"assists": assists, "best_streak": streak, "team": team}