diff --git a/src/arena/MatchHost.java b/src/arena/MatchHost.java index 3c02928..5ad446d 100644 --- a/src/arena/MatchHost.java +++ b/src/arena/MatchHost.java @@ -21,6 +21,7 @@ import megamek.client.bot.princess.Princess; import megamek.common.Player; import megamek.common.compute.Compute; import megamek.common.enums.GamePhase; +import megamek.common.game.Game; import megamek.common.game.IGame; import megamek.common.icons.FileCamouflage; import megamek.common.loaders.MekSummaryCache; @@ -531,6 +532,27 @@ public final class MatchHost { System.out.println("\n================================================="); System.out.println(" GAME OVER (round " + live.getCurrentRound() + ")"); + // Who won, as MegaMek recorded it rather than as the numbers below + // imply. A forced victory - one side declaring it and the rest using + // /defeat - leaves the loser's units standing, so "the side with + // something left" is not the winner, and a match report inferring it + // that way would tell a player they won a fight they conceded. + // + // Two fields because MegaMek keeps two: a team win sets victoryTeam + // and leaves victoryPlayerId at PLAYER_NONE, a single-player win does + // the opposite, and a draw leaves both at none. The accessors are on + // Game rather than IGame, which is what the cast is for; anything else + // records no winner rather than failing the result, since the counts + // are still worth having. + int victoryTeam = Player.TEAM_NONE; + int victoryPlayerId = Player.PLAYER_NONE; + if (live instanceof Game g) { + victoryTeam = g.getVictoryTeam(); + victoryPlayerId = g.getVictoryPlayerId(); + } else { + System.out.println("[host] game is not a Game; the result carries no winner"); + } + // `name` is what the seat was called on screen, which the renames // made a display name. `slot` is the join key identity.json and the // manifest speak, which is what attribution needs - a player not in @@ -539,7 +561,10 @@ public final class MatchHost { names.forEach((slot, display) -> slotsByName.put(display, slot)); StringBuilder json = new StringBuilder(); - json.append("{\n \"round\": ").append(live.getCurrentRound()).append(",\n \"players\": [\n"); + json.append("{\n \"round\": ").append(live.getCurrentRound()) + .append(",\n \"victoryTeam\": ").append(victoryTeam) + .append(",\n \"victoryPlayerId\": ").append(victoryPlayerId) + .append(",\n \"players\": [\n"); List players = live.getPlayersList(); for (int i = 0; i < players.size(); i++) { Player p = players.get(i); @@ -553,7 +578,12 @@ public final class MatchHost { int bvLeft = p.getBV(); System.out.printf(" %-16s units remaining: %d/%d BV: %d/%d%n", p.getName(), remaining, p.getInitialEntityCount(), bvLeft, bvStart); - json.append(" {\"name\": ").append(quote(p.getName())) + // The id is here for victoryPlayerId to point at. Nothing else + // in this document uses it, and nothing outside the match may: + // MegaMek assigns it per connection, so it means nothing once the + // container is gone. + json.append(" {\"id\": ").append(p.getId()) + .append(", \"name\": ").append(quote(p.getName())) .append(", \"slot\": ").append(quote(slotsByName.getOrDefault(p.getName(), p.getName()))) .append(", \"observer\": ").append(p.isObserver()) .append(", \"team\": ").append(p.getTeam()) @@ -564,6 +594,14 @@ public final class MatchHost { json.append(i + 1 < players.size() ? ",\n" : "\n"); } json.append(" ]\n}\n"); + if (victoryTeam != Player.TEAM_NONE) { + System.out.println(" winner: team " + victoryTeam); + } else if (victoryPlayerId != Player.PLAYER_NONE) { + Player victor = live.getPlayer(victoryPlayerId); + System.out.println(" winner: " + (victor == null ? victoryPlayerId : victor.getName())); + } else { + System.out.println(" no winner recorded: a draw, or a game that ended without one"); + } System.out.println("================================================="); writeState(a.stateDir, "result.json", json.toString());