Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tournament games #2807

Draft
wants to merge 9 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ dependencies {
implementation("org.springframework:spring-web")
implementation("org.springframework:spring-websocket")

def commonsVersion = "b0a374fa17988e0a8249fcf9b6d3a511b476ef8d"
def commonsVersion = "aca27907ccd99917112e02901a6e80de0cc44684"

implementation("com.github.FAForever.faf-java-commons:faf-commons-data:${commonsVersion}") {
exclude module: 'guava'
Expand Down
33 changes: 25 additions & 8 deletions src/main/java/com/faforever/client/game/GameService.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import com.faforever.client.util.ConcurrentUtil;
import com.faforever.client.util.MaskPatternLayout;
import com.faforever.commons.lobby.GameInfo;
import com.faforever.commons.lobby.GameLaunchResponse;
import com.faforever.commons.lobby.GameStatus;
import com.faforever.commons.lobby.GameVisibility;
import com.google.common.annotations.VisibleForTesting;
Expand Down Expand Up @@ -514,18 +515,34 @@ public CompletableFuture<Void> startSearchMatchmaker() {
return matchmakerFuture;
}

matchmakerFuture = listenForServerInitiatedGame(FAF.getTechnicalName());
return matchmakerFuture;
}

public CompletableFuture<Void> startListeningForTournamentGame(String featuredModTechnicalName) {
if (isRunning()) {
log.info("Game is running, ignoring tournament search request");
notificationService.addImmediateWarnNotification("game.gameRunning");
return completedFuture(null);
}

return listenForServerInitiatedGame(featuredModTechnicalName);
}

private CompletableFuture<Void> listenForServerInitiatedGame(String featuredModTechnicalName) {
if (!preferencesService.isGamePathValid()) {
CompletableFuture<Path> gameDirectoryFuture = postGameDirectoryChooseEvent();
return gameDirectoryFuture.thenCompose(path -> startSearchMatchmaker());
}

log.info("Matchmaking search has been started");
log.info("Started listening for game launch message");

matchmakerFuture = modService.getFeaturedMod(FAF.getTechnicalName())
final CompletableFuture<GameLaunchResponse> gameLaunchMessageFuture = fafServerAccessor.getGameLaunchMessageFuture();
final CompletableFuture<Void> matchFuture = modService.getFeaturedMod(featuredModTechnicalName)
.thenAccept(featuredModBean -> updateGameIfNecessary(featuredModBean, Set.of()))
.thenCompose(aVoid -> fafServerAccessor.startSearchMatchmaker())
.thenCompose(aVoid -> gameLaunchMessageFuture)
.thenCompose(gameLaunchResponse -> downloadMapIfNecessary(gameLaunchResponse.getMapName())
.thenCompose(aVoid -> leaderboardService.getActiveLeagueEntryForPlayer(playerService.getCurrentPlayer(), gameLaunchResponse.getLeaderboard()))
.thenCompose(aVoid -> leaderboardService.getActiveLeagueEntryForPlayer(playerService.getCurrentPlayer(),gameLaunchResponse.getLeaderboard()))
.thenApply(leagueEntryOptional -> {
GameParameters parameters = gameMapper.map(gameLaunchResponse);
parameters.setDivision(leagueEntryOptional.map(bean -> bean.getSubdivision().getDivision().getNameKey())
Expand All @@ -536,25 +553,25 @@ public CompletableFuture<Void> startSearchMatchmaker() {
})
.thenCompose(this::startGame));

matchmakerFuture.whenComplete((aVoid, throwable) -> {
matchFuture.whenComplete((aVoid, throwable) -> {
if (throwable != null) {
throwable = ConcurrentUtil.unwrapIfCompletionException(throwable);
if (throwable instanceof CancellationException) {
log.info("Matchmaking search has been cancelled");
log.info("Listening to server made game has been cancelled");
if (isRunning()) {
notificationService.addServerNotification(new ImmediateNotification(i18n.get("matchmaker.cancelled.title"), i18n.get("matchmaker.cancelled"), Severity.INFO));
gameKilled = true;
process.destroy();
}
} else {
log.warn("Matchmade game could not be started", throwable);
log.warn("Game could not be started", throwable);
}
} else {
log.info("Matchmaker queue exited");
}
});

return matchmakerFuture;
return matchFuture;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.faforever.commons.api.dto.LeagueSeasonScore;
import com.faforever.commons.api.elide.ElideNavigator;
import com.faforever.commons.api.elide.ElideNavigatorOnCollection;
import com.google.common.base.Strings;
import javafx.scene.image.Image;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.Cacheable;
Expand Down Expand Up @@ -174,6 +175,9 @@ public CompletableFuture<Optional<LeagueEntryBean>> getHighestActiveLeagueEntryF

@Cacheable(value = CacheNames.LEAGUE_ENTRIES, sync = true)
public CompletableFuture<Optional<LeagueEntryBean>> getActiveLeagueEntryForPlayer(PlayerBean player, String leaderboardName) {
if (Strings.isNullOrEmpty(leaderboardName)) {
1-alex98 marked this conversation as resolved.
Show resolved Hide resolved
return CompletableFuture.completedFuture(Optional.empty());
}
ElideNavigatorOnCollection<LeagueSeasonScore> navigator = ElideNavigator.of(LeagueSeasonScore.class).collection()
.setFilter(qBuilder()
.intNum("loginId").eq(player.getId())
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/com/faforever/client/main/MainController.java
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ private void makePopUpAskingForPreferenceInStartTab(WindowPrefs mainWindow) {
});
ImmediateNotification notification =
new ImmediateNotification(i18n.get("startTab.title"), i18n.get("startTab.message"),
Severity.INFO, null, Collections.singletonList(saveAction), startTabChooseController.getRoot());
Severity.INFO, null, Collections.singletonList(saveAction), startTabChooseController.getRoot(), false);
notificationService.addNotification(notification);
}

Expand Down Expand Up @@ -594,9 +594,11 @@ private void displayImmediateNotification(ImmediateNotification notification) {
.setNotification(notification)
.setCloseListener(dialog::close);

dialog.setOverlayClose(notification.isOverlayClose());
dialog.setContent(controller.getDialogLayout());
dialog.setAnimation(AlertAnimation.TOP_ANIMATION);
dialog.show();

}

private void displayServerNotification(ImmediateNotification notification) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,37 @@ public class ImmediateNotification {
private final Throwable throwable;
private final List<Action> actions;
private final Parent customUI;
/**
* If false notification will not close by clicking beside it.
*/
private final boolean overlayClose;
private Runnable dismissAction;

public ImmediateNotification(String title, String text, Severity severity) {
this(title, text, severity, null);
}

public ImmediateNotification(String title, String text, Severity severity, List<Action> actions) {
this(title, text, severity, null, actions, null);
this(title, text, severity, null, actions, null, true);
}

public ImmediateNotification(String title, String text, Severity severity, Throwable throwable, List<Action> actions) {
this(title, text, severity, throwable, actions, null);
this(title, text, severity, throwable, actions, null, true);
}

public ImmediateNotification(String title, String text, Severity severity, List<Action> actions, Parent customUI) {
this(title, text, severity, null, actions, customUI);
this(title, text, severity, null, actions, customUI, true);
}

public ImmediateNotification(String title, String text, Severity severity, Throwable throwable, List<Action> actions, Parent customUI) {
this(title, text, severity, throwable, actions, customUI, true);
}

public void setCloseAction(Runnable dismissAction) {
this.dismissAction = dismissAction;
}

public void dismiss(){
dismissAction.run();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public ImmediateNotificationController setNotification(ImmediateNotification not
if (notification.getCustomUI() != null) {
immediateNotificationRoot.getChildren().add(notification.getCustomUI());
}
notification.setCloseAction(this::dismiss);
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ public void requestMatchmakerInfo() {
lobbyClient.requestMatchmakerInfo();
}

public CompletableFuture<GameLaunchResponse> startSearchMatchmaker() {
public CompletableFuture<GameLaunchResponse> getGameLaunchMessageFuture() {
return lobbyClient.getEvents()
.filter(event -> event instanceof GameLaunchResponse)
.next()
Expand All @@ -228,6 +228,10 @@ public void removeFriend(int playerId) {
lobbyClient.removeFriend(playerId);
}

public void sendIsReady(String requestId) {
lobbyClient.sendReady(requestId);
}

public void removeFoe(int playerId) {
lobbyClient.removeFoe(playerId);
}
Expand Down
13 changes: 12 additions & 1 deletion src/main/java/com/faforever/client/theme/UiService.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.faforever.client.fx.JavaFxUtil;
import com.faforever.client.i18n.I18n;
import com.faforever.client.preferences.PreferencesService;
import com.faforever.client.ui.StageHolder;
import com.faforever.client.ui.dialog.Dialog;
import com.faforever.client.ui.dialog.Dialog.DialogTransition;
import com.faforever.client.ui.dialog.DialogLayout;
Expand All @@ -30,6 +31,7 @@
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.IOUtils;
import org.springframework.beans.factory.DisposableBean;
Expand Down Expand Up @@ -390,13 +392,22 @@ private String[] getStylesheets() {
getThemeFile("theme/colors.css"),
getThemeFile("theme/icons.css"),
getSceneStyleSheet(),
getThemeFile("theme/style_extension.css")
getThemeFile("theme/style_extension.css"),
getThemeFile("theme/progress.css")
};
} catch (IOException e) {
throw new AssetLoadException("Could not retrieve stylesheets", e, "theme.stylesheets.couldNotGet");
}
}


public void bringMainStageToFront() {
Stage stage = StageHolder.getStage();
if ((!stage.isFocused() || !stage.isShowing())) {
JavaFxUtil.runLater(stage::toFront);
}
}

/**
* Registers a WebView against the theme service so it can be updated whenever the theme changes.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package com.faforever.client.tournament.game;

import com.faforever.client.fx.Controller;
import com.faforever.client.fx.JavaFxUtil;
import com.faforever.client.i18n.I18n;
import com.faforever.client.ui.progress.RingProgressIndicator;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.scene.Parent;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.util.StringConverter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.VisibleForTesting;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.time.OffsetDateTime;


@Slf4j
@Component
@RequiredArgsConstructor
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class IsReadyForGameController implements Controller<Parent> {
private final I18n i18n;
public VBox root;
public Label description;
public RingProgressIndicator progressIndicator;
public Button isReadyButton;
private int timeLeft;
@VisibleForTesting
Timeline queuePopTimeUpdater;
@Setter
private Runnable readyCallback;
@Setter
private Runnable dismissCallBack;
private boolean clickedReady = false;


@Override
public void initialize() {
progressIndicator.setProgressLabelStringConverter(new StringConverter<>() {
@Override
public String toString(Integer object) {
return i18n.number(timeLeft);
Comment on lines +51 to +52
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find it a little odd that this is not just using the variable passed in. Why pass timeLeft directly here instead of setting the value of the progessIndicator

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I know it is strange I know XD

The idea is the following:
The Controller calculates the timeLeft till timeout. It then calculates the progress in percent and sets the progress. The passed number is the progress in percent. Why calculate the time left in seconds back from the percentage if we already have it. Also might cause rounding errors 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess so but then it seems like it would be best for the widget to have two properties and components. The label and the progress bar. Seems a little odd they are combined to be the same

}

@Override
public Integer fromString(String string) {
throw new UnsupportedOperationException();
}
});
}


@Override
public Parent getRoot() {
return root;
}

public void setTimeout(int responseTimeSeconds) {
OffsetDateTime start = OffsetDateTime.now();

queuePopTimeUpdater = new Timeline(1, new KeyFrame(javafx.util.Duration.seconds(0), (ActionEvent event) -> {
updateTimer(responseTimeSeconds, start);
}), new KeyFrame(javafx.util.Duration.seconds(1)));
queuePopTimeUpdater.setCycleCount(Timeline.INDEFINITE);
queuePopTimeUpdater.play();
}

private void updateTimer(int responseTimeSeconds, OffsetDateTime start) {
OffsetDateTime now = OffsetDateTime.now();
Duration timeGone = Duration.between(start, now);
final var percent = timeGone.toSeconds() / (double) responseTimeSeconds;
this.timeLeft = (int) (responseTimeSeconds - timeGone.toSeconds());
progressIndicator.setProgress((int) (percent * 100));
if (timeLeft <= 0 && queuePopTimeUpdater != null) {
queuePopTimeUpdater.stop();
JavaFxUtil.runLater(this::end);
}
}

private void end() {
if (clickedReady) {
isReadyButton.setText(i18n.get("isReady.launching"));
} else {
dismissCallBack.run();
}
}

public void onReady() {
readyCallback.run();
isReadyButton.setDisable(true);
clickedReady = true;
isReadyButton.setText(i18n.get("isReady.waiting"));
}
}
Loading