Skip to content

Commit

Permalink
Show division in replays when available (#3042)
Browse files Browse the repository at this point in the history
* Show league divisions in matchmaker replays

* Use records and new mapper

* Revert controllers

* Decouple rating and division displaying

* Fix mapper with dummy values

* Change team result calculations

* Create a display mode

* Fix and add tests

* Prevent early aborts in test runs

* Fix race condition

* Don't add controllers twice to UI

---------

Co-authored-by: Sheikah45 <Sheikah450@gmail.com>
  • Loading branch information
BlackYps and Sheikah45 committed Mar 22, 2024
1 parent 618ba92 commit 6823740
Show file tree
Hide file tree
Showing 18 changed files with 372 additions and 47 deletions.
3 changes: 3 additions & 0 deletions src/main/java/com/faforever/client/api/FafApiAccessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.faforever.commons.api.dto.GameReviewsSummary;
import com.faforever.commons.api.dto.LeaderboardEntry;
import com.faforever.commons.api.dto.LeaderboardRatingJournal;
import com.faforever.commons.api.dto.LeagueScoreJournal;
import com.faforever.commons.api.dto.LeagueSeason;
import com.faforever.commons.api.dto.LeagueSeasonDivisionSubdivision;
import com.faforever.commons.api.dto.LeagueSeasonScore;
Expand Down Expand Up @@ -97,6 +98,8 @@ public class FafApiAccessor implements InitializingBean {
List.of("leagueSeason", "leagueSeason.leaderboard", "leagueSeason.league",
"leagueSeasonDivisionSubdivision", "leagueSeasonDivisionSubdivision.leagueSeasonDivision")),
java.util.Map.entry(LeagueSeasonDivisionSubdivision.class, List.of("leagueSeasonDivision")),
java.util.Map.entry(LeagueScoreJournal.class, List.of("leagueSeason", "leagueSeasonDivisionSubdivisionBefore", "leagueSeasonDivisionSubdivisionAfter",
"leagueSeasonDivisionSubdivisionBefore.leagueSeasonDivision", "leagueSeasonDivisionSubdivisionAfter.leagueSeasonDivision")),
java.util.Map.entry(MapVersion.class, List.of("map", "map.reviewsSummary", "map.author")),
java.util.Map.entry(MapReviewsSummary.class, List.of("map.latestVersion", "map.author", "map.reviewsSummary")),
java.util.Map.entry(Map.class, List.of("latestVersion", "author", "reviewsSummary")),
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/com/faforever/client/domain/api/GameOutcome.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.faforever.client.domain.api;

public enum GameOutcome {
VICTORY,
DEFEAT,
DRAW,
MUTUAL_DRAW,
UNKNOWN,
CONFLICTING
}
12 changes: 10 additions & 2 deletions src/main/java/com/faforever/client/domain/api/GamePlayerStats.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,16 @@
import java.util.List;

public record GamePlayerStats(
PlayerInfo player, byte score, byte team,
Faction faction, OffsetDateTime scoreTime, List<LeaderboardRatingJournal> leaderboardRatingJournals
boolean ai,
Faction faction,
byte color,
byte team,
byte startSpot,
byte score,
OffsetDateTime scoreTime,
GameOutcome outcome,
PlayerInfo player,
List<LeaderboardRatingJournal> leaderboardRatingJournals
) {

public GamePlayerStats {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.faforever.client.domain.api;

public record LeagueScoreJournal(
int id,
int gameId,
int loginId,
int gameCount,
int scoreBefore,
int scoreAfter,
LeagueSeason season,
Subdivision divisionBefore,
Subdivision divisionAfter
) {}

53 changes: 49 additions & 4 deletions src/main/java/com/faforever/client/game/PlayerCardController.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.faforever.client.avatar.AvatarService;
import com.faforever.client.domain.api.GamePlayerStats;
import com.faforever.client.domain.api.LeaderboardRatingJournal;
import com.faforever.client.domain.api.Subdivision;
import com.faforever.client.domain.server.PlayerInfo;
import com.faforever.client.fx.JavaFxUtil;
import com.faforever.client.fx.NodeController;
Expand All @@ -21,8 +22,10 @@
import com.faforever.client.fx.contextmenu.ShowPlayerInfoMenuItem;
import com.faforever.client.fx.contextmenu.ViewReplaysMenuItem;
import com.faforever.client.i18n.I18n;
import com.faforever.client.leaderboard.LeaderboardService;
import com.faforever.client.player.CountryFlagService;
import com.faforever.client.player.SocialStatus;
import com.faforever.client.replay.DisplayType;
import com.faforever.client.theme.ThemeService;
import com.faforever.client.theme.UiService;
import com.faforever.client.util.RatingUtil;
Expand Down Expand Up @@ -60,6 +63,7 @@ public class PlayerCardController extends NodeController<Node> {
private final UiService uiService;
private final CountryFlagService countryFlagService;
private final AvatarService avatarService;
private final LeaderboardService leaderboardService;
private final ContextMenuBuilder contextMenuBuilder;
private final I18n i18n;

Expand All @@ -72,21 +76,30 @@ public class PlayerCardController extends NodeController<Node> {
public Label friendIconText;
public Region factionIcon;
public ImageView factionImage;
public ImageView divisionImageView;
public Label noteIcon;
public Label ratingLabel;
public Label ratingChange;

private final ObjectProperty<PlayerInfo> player = new SimpleObjectProperty<>();
private final ObjectProperty<GamePlayerStats> playerStats = new SimpleObjectProperty<>();
private final ObjectProperty<Integer> rating = new SimpleObjectProperty<>();
private final ObjectProperty<Subdivision> division = new SimpleObjectProperty<>();
private final ObjectProperty<Faction> faction = new SimpleObjectProperty<>();
private final ObjectProperty<DisplayType> displayType = new SimpleObjectProperty<>();
private final Tooltip noteTooltip = new Tooltip();
private final Tooltip avatarTooltip = new Tooltip();
private final Tooltip divisionTooltip = new Tooltip();

@Override
protected void onInitialize() {
JavaFxUtil.bindManagedToVisible(avatarStackPane, factionIcon, foeIconText, factionImage, friendIconText, countryImageView, noteIcon);
JavaFxUtil.bindManagedToVisible(avatarStackPane, factionIcon, foeIconText, factionImage, friendIconText,
countryImageView, divisionImageView, ratingLabel, ratingChange, noteIcon);
countryImageView.visibleProperty().bind(countryImageView.imageProperty().isNotNull());
avatarImageView.visibleProperty().bind(avatarImageView.imageProperty().isNotNull());
divisionImageView.visibleProperty().bind(divisionImageView.imageProperty().isNotNull());
ratingLabel.visibleProperty().bind(rating.isNotNull().and(displayType.isEqualTo(DisplayType.RATING)));
ratingChange.visibleProperty().bind(playerStats.isNotNull().and(displayType.isEqualTo(DisplayType.RATING)));

factionImage.setImage(uiService.getImage(ThemeService.RANDOM_FACTION_IMAGE));
factionImage.visibleProperty().bind(faction.map(value -> value == Faction.RANDOM));
Expand All @@ -97,10 +110,12 @@ protected void onInitialize() {
.when(showing));
avatarImageView.imageProperty()
.bind(player.flatMap(PlayerInfo::avatarProperty).map(avatarService::loadAvatar).when(showing));
divisionImageView.imageProperty()
.bind(division.map(Subdivision::smallImageUrl).map(leaderboardService::loadDivisionImage).when(showing));
playerInfo.textProperty().bind(player.flatMap(PlayerInfo::usernameProperty)
.flatMap(username -> rating.map(value -> i18n.get("userInfo.tooltipFormat.withRating", username, value))
.orElse(i18n.get("userInfo.tooltipFormat.noRating", username)))
.when(showing));
ratingLabel.textProperty().bind(rating.map(value -> i18n.get("game.tooltip.ratingFormat", value))
.when(showing));
foeIconText.visibleProperty().bind(player.flatMap(PlayerInfo::socialStatusProperty)
.map(socialStatus -> socialStatus == SocialStatus.FOE)
.when(showing));
Expand All @@ -111,7 +126,6 @@ protected void onInitialize() {
.when(showing)
.addListener((SimpleChangeListener<String>) this::onNoteChanged);

ratingChange.visibleProperty().bind(playerStats.isNotNull());
ObservableValue<Integer> ratingChangeObservable = playerStats.map(GamePlayerStats::leaderboardRatingJournals)
.map(
journals -> journals.isEmpty() ? null : journals.getFirst())
Expand All @@ -131,6 +145,13 @@ protected void onInitialize() {
avatarTooltip.setShowDelay(Duration.ZERO);
avatarTooltip.setShowDuration(Duration.seconds(30));
Tooltip.install(avatarImageView, avatarTooltip);

divisionTooltip.textProperty().bind(
division.map(value -> i18n.get("leaderboard.divisionName", i18n.get("leagues.divisionName.%s".formatted(value.division().nameKey())), value.nameKey()))
.when(showing));
divisionTooltip.setShowDelay(Duration.ZERO);
divisionTooltip.setShowDuration(Duration.seconds(30));
Tooltip.install(divisionImageView, divisionTooltip);
}

private void onNoteChanged(String newValue) {
Expand Down Expand Up @@ -241,6 +262,18 @@ public void setRating(Integer rating) {
this.rating.set(rating);
}

public Subdivision getDivision() {
return division.get();
}

public ObjectProperty<Subdivision> divisionProperty() {
return division;
}

public void setDivision(Subdivision subdivision) {
this.division.set(subdivision);
}

public Faction getFaction() {
return faction.get();
}
Expand All @@ -253,6 +286,18 @@ public void setFaction(Faction faction) {
this.faction.set(faction);
}

public DisplayType getDisplayType() {
return displayType.get();
}

public ObjectProperty<DisplayType> displayTypeProperty() {
return displayType;
}

public void setDisplayType(DisplayType displayType) {
this.displayType.set(displayType);
}

public GamePlayerStats getPlayerStats() {
return playerStats.get();
}
Expand Down
84 changes: 80 additions & 4 deletions src/main/java/com/faforever/client/game/TeamCardController.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@


import com.faforever.client.domain.api.GamePlayerStats;
import com.faforever.client.domain.api.Subdivision;
import com.faforever.client.domain.server.GameInfo;
import com.faforever.client.domain.api.GameOutcome;
import com.faforever.client.domain.server.PlayerInfo;
import com.faforever.client.fx.FxApplicationThreadExecutor;
import com.faforever.client.fx.JavaFxUtil;
import com.faforever.client.fx.NodeController;
import com.faforever.client.fx.SimpleChangeListener;
import com.faforever.client.i18n.I18n;
import com.faforever.client.player.PlayerService;
import com.faforever.client.replay.DisplayType;
import com.faforever.client.theme.UiService;
import com.faforever.client.util.RatingUtil;
import com.faforever.commons.api.dto.Faction;
Expand All @@ -18,6 +22,7 @@
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.css.PseudoClass;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
Expand All @@ -43,6 +48,12 @@
@Slf4j
@RequiredArgsConstructor
public class TeamCardController extends NodeController<Node> {

private static final PseudoClass VICTORY = PseudoClass.getPseudoClass("victory");
private static final PseudoClass DEFEAT = PseudoClass.getPseudoClass("defeat");
private static final PseudoClass DRAW = PseudoClass.getPseudoClass("draw");
private static final PseudoClass UNKNOWN = PseudoClass.getPseudoClass("unknown");

private final I18n i18n;
private final PlayerService playerService;
private final FxApplicationThreadExecutor fxApplicationThreadExecutor;
Expand All @@ -51,12 +62,17 @@ public class TeamCardController extends NodeController<Node> {
public Pane teamPaneRoot;
public VBox teamPane;
public Label teamNameLabel;
public Label teamRatingLabel;
public Label gameResultLabel;

private final ObjectProperty<List<Integer>> playerIds = new SimpleObjectProperty<>(List.of());
private final ObjectProperty<List<PlayerInfo>> players = new SimpleObjectProperty<>(List.of());
private final ObjectProperty<Function<PlayerInfo, Integer>> ratingProvider = new SimpleObjectProperty<>();
private final ObjectProperty<Function<PlayerInfo, Subdivision>> divisionProvider = new SimpleObjectProperty<>();
private final ObjectProperty<Function<PlayerInfo, Faction>> factionProvider = new SimpleObjectProperty<>();
private final ObjectProperty<RatingPrecision> ratingPrecision = new SimpleObjectProperty<>();
private final ObjectProperty<GameOutcome> teamOutcome = new SimpleObjectProperty<>();
private final ObjectProperty<DisplayType> displayType = new SimpleObjectProperty<>();
private final IntegerProperty teamId = new SimpleIntegerProperty();
private final SimpleChangeListener<List<PlayerInfo>> playersListener = this::populateTeamContainer;
private final ObservableValue<Integer> teamRating = ratingProvider.flatMap(provider -> ratingPrecision.flatMap(precision -> players.map(playerBeans -> playerBeans.stream()
Expand All @@ -69,19 +85,23 @@ public class TeamCardController extends NodeController<Node> {

@Override
protected void onInitialize() {
setDisplayType(DisplayType.RATING);
teamNameLabel.textProperty()
.bind(teamRating.flatMap(teamRating -> teamId.map(id -> switch (id.intValue()) {
.bind(teamId.map(id -> switch (id.intValue()) {
case 0, GameInfo.NO_TEAM -> i18n.get("game.tooltip.teamTitleNoTeam");
case GameInfo.OBSERVERS_TEAM -> i18n.get("game.tooltip.observers");
default -> {
try {
yield i18n.get("game.tooltip.teamTitle", id.intValue() - 1, teamRating);
yield i18n.get("game.tooltip.teamTitle", id.intValue() - 1);
} catch (NumberFormatException e) {
yield "";
}
}
})));

}));
JavaFxUtil.bindManagedToVisible(teamRatingLabel, gameResultLabel);
teamRatingLabel.textProperty().bind(teamRating.map(value -> i18n.get("game.tooltip.ratingFormat", value)));
teamRatingLabel.visibleProperty().bind(teamRating.flatMap(teamRating -> displayType.map(type -> type == DisplayType.RATING && teamRating != 0)));
gameResultLabel.visibleProperty().bind(gameResultLabel.textProperty().isEmpty().not());
players.addListener(playersListener);
}

Expand All @@ -99,27 +119,71 @@ private List<PlayerCardController> createPlayerCardControllers(List<PlayerInfo>
controller.ratingProperty()
.bind(ratingProvider.map(ratingFunction -> ratingFunction.apply(player))
.flatMap(rating -> ratingPrecision.map(precision -> precision == RatingPrecision.ROUNDED ? RatingUtil.getRoundedRating(rating) : rating)));
controller.divisionProperty()
.bind(divisionProvider.map(divisionFunction -> divisionFunction.apply(player)));
controller.factionProperty()
.bind(factionProvider.map(factionFunction -> factionFunction.apply(player)));
controller.setPlayer(player);
controller.displayTypeProperty().bind(displayType);

playerCardControllersMap.put(player, controller);

return controller;
}).toList();
}

public void showGameResult() {
switch (teamOutcome.get()) {
case VICTORY -> {
gameResultLabel.setText(i18n.get("game.resultVictory"));
gameResultLabel.pseudoClassStateChanged(VICTORY, true);
gameResultLabel.pseudoClassStateChanged(DEFEAT, false);
gameResultLabel.pseudoClassStateChanged(DRAW, false);
gameResultLabel.pseudoClassStateChanged(UNKNOWN, false);
}
case DEFEAT -> {
gameResultLabel.setText(i18n.get("game.resultDefeat"));
gameResultLabel.pseudoClassStateChanged(VICTORY, false);
gameResultLabel.pseudoClassStateChanged(DEFEAT, true);
gameResultLabel.pseudoClassStateChanged(DRAW, false);
gameResultLabel.pseudoClassStateChanged(UNKNOWN, false);
}
case DRAW, MUTUAL_DRAW -> {
gameResultLabel.setText(i18n.get("game.resultDraw"));
gameResultLabel.pseudoClassStateChanged(VICTORY, false);
gameResultLabel.pseudoClassStateChanged(DEFEAT, false);
gameResultLabel.pseudoClassStateChanged(DRAW, true);
gameResultLabel.pseudoClassStateChanged(UNKNOWN, false);
}
default -> {
gameResultLabel.setText(i18n.get("game.resultUnknown"));
gameResultLabel.pseudoClassStateChanged(VICTORY, false);
gameResultLabel.pseudoClassStateChanged(DEFEAT, false);
gameResultLabel.pseudoClassStateChanged(DRAW, false);
gameResultLabel.pseudoClassStateChanged(UNKNOWN, true);
}
}
}

public void bindPlayersToPlayerIds() {
players.bind(playerIds.map(ids -> ids.stream()
.map(playerService::getPlayerByIdIfOnline)
.flatMap(Optional::stream)
.collect(Collectors.toCollection(FXCollections::observableArrayList))));
}

public void setDisplayType(DisplayType type) {
this.displayType.set(type);
}

public void setRatingProvider(Function<PlayerInfo, Integer> ratingProvider) {
this.ratingProvider.set(ratingProvider);
}

public void setDivisionProvider(Function<PlayerInfo, Subdivision> divisionProvider) {
this.divisionProvider.set(divisionProvider);
}

public void setFactionProvider(Function<PlayerInfo, Faction> factionProvider) {
this.factionProvider.set(factionProvider);
}
Expand Down Expand Up @@ -156,6 +220,18 @@ public ObjectProperty<Function<PlayerInfo, Integer>> ratingProviderProperty() {
return ratingProvider;
}

public void setTeamOutcome(GameOutcome teamOutcome) {
this.teamOutcome.set(teamOutcome);
}

public GameOutcome getTeamOutcome() {
return teamOutcome.get();
}

public ObjectProperty<GameOutcome> teamResult() {
return teamOutcome;
}

public void setStats(List<GamePlayerStats> teamPlayerStats) {
for (GamePlayerStats playerStats : teamPlayerStats) {
PlayerCardController controller = playerCardControllersMap.get(playerStats.player());
Expand Down
Loading

0 comments on commit 6823740

Please sign in to comment.