Skip to content
Open
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: 2 additions & 0 deletions src/main/java/com/faforever/client/config/CacheConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import static com.faforever.client.config.CacheNames.CLAN;
import static com.faforever.client.config.CacheNames.COOP_LEADERBOARD;
import static com.faforever.client.config.CacheNames.COOP_MAPS;
import static com.faforever.client.config.CacheNames.COOP_SCENARIOS;
import static com.faforever.client.config.CacheNames.COTURN;
import static com.faforever.client.config.CacheNames.COUNTRY_FLAGS;
import static com.faforever.client.config.CacheNames.COUNTRY_NAMES;
Expand Down Expand Up @@ -91,6 +92,7 @@ public CacheManager cacheManager() {
newBuilder().maximumSize(1).expireAfterAccess(5, MINUTES).buildAsync(), true),
new CaffeineCache(AVAILABLE_AVATARS, newBuilder().expireAfterAccess(10, MINUTES).buildAsync(), true),
new CaffeineCache(COOP_MAPS, newBuilder().expireAfterAccess(10, MINUTES).buildAsync(), true),
new CaffeineCache(COOP_SCENARIOS, newBuilder().expireAfterAccess(10, MINUTES).buildAsync(), true),
new CaffeineCache(NEWS, newBuilder().expireAfterWrite(5, MINUTES).buildAsync(), true),
new CaffeineCache(RATING_HISTORY, newBuilder().expireAfterWrite(1, MINUTES).buildAsync(), true),
new CaffeineCache(COOP_LEADERBOARD, newBuilder().expireAfterWrite(1, MINUTES).buildAsync(), true),
Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/faforever/client/config/CacheNames.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public final class CacheNames {
public static final String IMAGES = "images";
public static final String MOD_THUMBNAIL = "modThumbnail";
public static final String COOP_MAPS = "coopMaps";
public static final String COOP_SCENARIOS = "coopScenarios";
public static final String AVAILABLE_AVATARS = "availableAvatars";
public static final String NEWS = "news";
public static final String RATING_HISTORY = "ratingHistory";
Expand Down
5 changes: 0 additions & 5 deletions src/main/java/com/faforever/client/coop/CoopCategory.java

This file was deleted.

93 changes: 77 additions & 16 deletions src/main/java/com/faforever/client/coop/CoopController.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.faforever.client.coop;

import com.faforever.client.domain.api.CoopCategory;
import com.faforever.client.domain.api.CoopMission;
import com.faforever.client.domain.api.CoopResult;
import com.faforever.client.domain.api.CoopScenario;
import com.faforever.client.domain.server.GameInfo;
import com.faforever.client.fx.ControllerTableCell;
import com.faforever.client.fx.FxApplicationThreadExecutor;
Expand All @@ -24,9 +26,11 @@
import com.faforever.client.theme.UiService;
import com.faforever.client.util.PopupUtil;
import com.faforever.client.util.TimeService;
import com.faforever.client.vault.search.SearchController.SortOrder;
import com.faforever.commons.lobby.GameStatus;
import com.faforever.commons.lobby.GameType;
import com.google.common.base.Strings;
import javafx.application.HostServices;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
Expand All @@ -47,14 +51,22 @@
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.web.WebView;
import javafx.util.StringConverter;
import jdk.jfr.Category;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import java.awt.Desktop;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
Expand Down Expand Up @@ -89,6 +101,7 @@ public class CoopController extends NodeController<Node> {
private final FilteredList<CoopResult> leaderboardFilteredList = new FilteredList<>(leaderboardUnFilteredList);

public GridPane coopRoot;
public ComboBox<CoopScenario> scenarioComboBox;
public ComboBox<CoopMission> missionComboBox;
public WebView descriptionWebView;
public Pane gameViewContainer;
Expand All @@ -111,9 +124,13 @@ public class CoopController extends NodeController<Node> {

@Override
protected void onInitialize() {
missionComboBox.setCellFactory(param -> missionListCell());
missionComboBox.setButtonCell(missionListCell());
scenarioComboBox.setCellFactory(param -> scenarioListCell());
scenarioComboBox.setButtonCell(scenarioListCell());
scenarioComboBox.getSelectionModel().selectedItemProperty().when(showing).subscribe(this::populateMissionList);

missionComboBox.getSelectionModel().selectedItemProperty().when(showing).subscribe(this::setSelectedMission);
missionComboBox.setButtonCell(missionListCell());
missionComboBox.setCellFactory(param -> missionListCell());

mapPreviewImageView.imageProperty()
.bind(missionComboBox.getSelectionModel().selectedItemProperty().map(CoopMission::mapFolderName)
Expand Down Expand Up @@ -148,10 +165,10 @@ protected void onInitialize() {
playerCountColumn.setCellFactory(param -> new StringCell<>(String::valueOf));

playerNamesColumn.setCellValueFactory(param -> ObservableConstant.valueOf(param.getValue().replay().teams().values()
.stream()
.flatMap(Collection::stream)
.collect(Collectors.joining(
i18n.get("textSeparator")))));
.stream()
.flatMap(Collection::stream)
.collect(Collectors.joining(
i18n.get("textSeparator")))));

playerNamesColumn.setCellFactory(param -> new StringCell<>(Function.identity()));

Expand Down Expand Up @@ -185,25 +202,31 @@ protected void onInitialize() {
FilteredList<GameInfo> filteredItems = new FilteredList<>(gameService.getGames());
filteredItems.setPredicate(OPEN_COOP_GAMES_PREDICATE);

coopService.getMissions()
coopService.getScenarios()
.collectList()
.map(FXCollections::observableList)
.publishOn(fxApplicationThreadExecutor.asScheduler())
.subscribe(coopMaps -> {
missionComboBox.setItems(coopMaps);
.subscribe(coopScenarios -> {
scenarioComboBox.getItems().addAll(coopScenarios);

List<CoopMission> coopMissions = new ArrayList<>();
for (CoopScenario coopScenario : coopScenarios) {
coopMissions.addAll(coopScenario.maps());
}

gamesTableController.initializeGameTable(filteredItems,
mapFolderName -> coopMissionFromFolderName(coopMaps,
mapFolderName -> coopMissionFromFolderName(coopMissions,
mapFolderName),
false);
gamesTableController.getMapPreviewColumn().setVisible(false);
gamesTableController.getRatingRangeColumn().setVisible(false);

SingleSelectionModel<CoopScenario> selectionModel = scenarioComboBox.getSelectionModel();

SingleSelectionModel<CoopMission> selectionModel = missionComboBox.getSelectionModel();
if (selectionModel.isEmpty()) {
selectionModel.selectFirst();
}

}, throwable -> notificationService.addPersistentErrorNotification("coop.couldNotLoad",
throwable.getLocalizedMessage()));
}
Expand Down Expand Up @@ -232,22 +255,28 @@ private ListCell<Integer> numberOfPlayersCell() {
}, fxApplicationThreadExecutor);
}

private ListCell<CoopMission> missionListCell() {
return new StringListCell<>(fxApplicationThreadExecutor, CoopMission::name, mission -> {
private ListCell<CoopScenario> scenarioListCell() {
return new StringListCell<>(fxApplicationThreadExecutor, CoopScenario::name, category -> {
Label label = new Label();
Region iconRegion = new Region();
label.setGraphic(iconRegion);
iconRegion.getStyleClass().add(ThemeService.CSS_CLASS_ICON);
switch (mission.category()) {
switch (category.faction()) {
case AEON -> iconRegion.getStyleClass().add(ThemeService.AEON_STYLE_CLASS);
case CYBRAN -> iconRegion.getStyleClass().add(ThemeService.CYBRAN_STYLE_CLASS);
case UEF -> iconRegion.getStyleClass().add(ThemeService.UEF_STYLE_CLASS);
case SERAPHIM -> iconRegion.getStyleClass().add(ThemeService.SERAPHIM_STYLE_CLASS);
case CUSTOM -> iconRegion.getStyleClass().add(ThemeService.WRENCH_STYLE_CLASS);
default -> {
return null;
}
}
return label;
}, Pos.CENTER_LEFT, "coop-mission");
}, Pos.CENTER_LEFT, "coop-category");
}

private ListCell<CoopMission> missionListCell() {
return new StringListCell<>(fxApplicationThreadExecutor, CoopMission::concatName, null, Pos.CENTER_LEFT, "coop-mission");
}

private void loadLeaderboard() {
Expand All @@ -274,11 +303,20 @@ private Set<String> getAllPlayerNamesFromTeams(CoopResult coopResult) {
.collect(Collectors.toUnmodifiableSet());
}


private CoopMission getSelectedMission() {
return missionComboBox.getSelectionModel().getSelectedItem();
}

private void populateMissionList(CoopScenario scenario) {
if (scenario == null) {
return;
}

missionComboBox.getItems().clear();
missionComboBox.getItems().addAll(scenario.maps());
missionComboBox.getSelectionModel().select(0);
}

private void setSelectedMission(CoopMission mission) {
if (mission == null) {
return;
Expand All @@ -292,11 +330,34 @@ private void setSelectedMission(CoopMission mission) {
loadLeaderboard();
}

private void desktopBrowserNavigateToURI(URI uri) {
if (!Desktop.isDesktopSupported() || !Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
log.warn("Desktop browsing is not supported on this platform.");
notificationService.addImmediateWarnNotification("coop.browser.notSupported");
return;
}

try {
Desktop.getDesktop().browse(uri);
} catch (IOException e) {
log.error("Could not open browser.", e);
notificationService.addImmediateErrorNotification(e, "coop.browser.error");
}
Comment on lines +334 to +345
Copy link
Member

Choose a reason for hiding this comment

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

Use the platformService.showDocument for opening a browser

}

public void onPlayButtonClicked() {
gameRunner.host(new NewGameInfo(titleTextField.getText(), Strings.emptyToNull(passwordTextField.getText()),
COOP.getTechnicalName(), getSelectedMission().mapFolderName(), Set.of()));
}

public void onWikiButtonClicked() {
desktopBrowserNavigateToURI(URI.create("https://wiki.faforever.com/en/Development/Missions/Mission-Scripting"));
}

public void onDiscordHyperLinkClicked() {
desktopBrowserNavigateToURI(URI.create("https://discord.gg/ayzAVr9JUV"));
}

public void onMapPreviewImageClicked() {
Optional.ofNullable(mapPreviewImageView.getImage()).ifPresent(PopupUtil::showImagePopup);
}
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/com/faforever/client/coop/CoopFaction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.faforever.client.coop;

public enum CoopFaction {
UEF, CYBRAN, AEON, SERAPHIM, CUSTOM
}
9 changes: 9 additions & 0 deletions src/main/java/com/faforever/client/coop/CoopService.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.faforever.client.config.CacheNames;
import com.faforever.client.domain.api.CoopMission;
import com.faforever.client.domain.api.CoopResult;
import com.faforever.client.domain.api.CoopScenario;
import com.faforever.client.mapstruct.CoopMapper;
import com.faforever.commons.api.dto.Game;
import com.faforever.commons.api.dto.GamePlayerStats;
Expand Down Expand Up @@ -39,6 +40,14 @@ public Flux<CoopMission> getMissions() {
return fafApiAccessor.getMany(navigator).map(coopMapper::map).cache();
}

@Cacheable(value = CacheNames.COOP_SCENARIOS, sync = true)
public Flux<CoopScenario> getScenarios() {
ElideNavigatorOnCollection<com.faforever.commons.api.dto.CoopScenario> navigator = ElideNavigator.of(
com.faforever.commons.api.dto.CoopScenario.class).collection().addInclude("maps").pageSize(1000);
Copy link
Member

Choose a reason for hiding this comment

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

The max page size in the api is now 100 so 1000 won't do anything.

Also instead of specifying the include here it should be specified in the type map in the fafApiAccessor. GetMany also handles the page size

return fafApiAccessor.getMany(navigator).map(coopMapper::map).cache();
}


@Cacheable(value = CacheNames.COOP_LEADERBOARD, sync = true)
public Flux<CoopResult> getLeaderboard(CoopMission mission, int numberOfPlayers) {
Condition<?> filterCondition = qBuilder().intNum("mission").eq(mission.id());
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/com/faforever/client/coop/CoopType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.faforever.client.coop;

public enum CoopType {
SC, SCFA, CUSTOM
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.faforever.client.domain.api;

import com.faforever.client.coop.CoopFaction;

public record CoopCategory(
String categoryName,
CoopFaction coopCategory
Copy link
Member

Choose a reason for hiding this comment

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

The variable naming here is a bit confusing. What exactly is the category?

) {}
10 changes: 6 additions & 4 deletions src/main/java/com/faforever/client/domain/api/CoopMission.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
package com.faforever.client.domain.api;

import com.faforever.client.coop.CoopCategory;

import com.faforever.client.coop.CoopFaction;
import java.net.URL;

public record CoopMission(
Integer id,
String name,
String description,
int version,
CoopCategory category,
URL downloadUrl,
URL thumbnailUrlSmall,
URL thumbnailUrlLarge,
String mapFolderName
) {}
) {
public String concatName() {
return name + " - V" + version;
}
Comment on lines +16 to +18
Copy link
Member

Choose a reason for hiding this comment

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

Can this method be moved to the controller?

}
19 changes: 19 additions & 0 deletions src/main/java/com/faforever/client/domain/api/CoopScenario.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.faforever.client.domain.api;

import com.faforever.client.coop.CoopFaction;
import com.faforever.client.coop.CoopType;
import java.util.List;

public record CoopScenario (
Integer id,
String name,
String description,
Integer order,
CoopFaction faction,
CoopType type,
List<CoopMission> maps
) {
public CoopScenario {
maps = maps == null ? List.of() : List.copyOf(maps);
}
Comment on lines +16 to +18
Copy link
Member

Choose a reason for hiding this comment

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

We should make sure to not pass in null for the maps

}
9 changes: 9 additions & 0 deletions src/main/java/com/faforever/client/mapstruct/CoopMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

import com.faforever.client.domain.api.CoopMission;
import com.faforever.client.domain.api.CoopResult;
import com.faforever.client.domain.api.CoopScenario;
import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

import java.util.List;

@Mapper(uses = {ReplayMapper.class}, config = MapperConfiguration.class)
public interface CoopMapper {

Expand All @@ -15,6 +18,12 @@ public interface CoopMapper {
@InheritInverseConfiguration
com.faforever.commons.api.dto.CoopMission map(CoopMission bean);

@Mapping(target = ".", source = "dto")
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this annotation is necessary since there is only one parameter

CoopScenario map(com.faforever.commons.api.dto.CoopScenario dto);

@InheritInverseConfiguration
com.faforever.commons.api.dto.CoopScenario map(CoopScenario bean);

@Mapping(target = "replay", source = "dto.game")
@Mapping(target = "ranking", source = "ranking")
@Mapping(target = ".", source = "dto")
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/faforever/client/theme/ThemeService.java
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ public class ThemeService implements InitializingBean, DisposableBean {
public static final String AEON_STYLE_CLASS = "aeon-icon";
public static final String CYBRAN_STYLE_CLASS = "cybran-icon";
public static final String SERAPHIM_STYLE_CLASS = "seraphim-icon";
public static final String MOD_ICON_STYLE_CLASS = "mod-icon";
public static final String UEF_STYLE_CLASS = "uef-icon";
public static final String WRENCH_STYLE_CLASS = "wrench-icon";
public static final String RANDOM_FACTION_IMAGE = "/images/factions/random.png";

public static Theme DEFAULT_THEME = new Theme("Default", "Downlord", 1, "1");
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/i18n/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ leaderboard.failedToLoad = Leaderboard could not be loaded
leaderboard.loading = Loading leaderboard…
coop.host.title = Host this Mission
coop.host.button = Host Game
coop.wiki.button = Visit Wiki
coop.discord.button = Join the FAF Campaign Discord Server
coop.leaderboard = Leaderboard
coop.openGames = Open Games
coop.leaderboard.rank = \#
Expand All @@ -56,6 +58,7 @@ coop.leaderboard.singlePlayer = 1 player
coop.leaderboard.numberOfPlayersFormat = {0,number,#} players
coop.leaderboard.couldNotLoad = Co-Op leaderboard could not be loaded.
coop.couldNotLoad = Co-Op missions could not be loaded\: {0}
coop.info.label = Check out the FAF wiki to get tips on how to add stronger AI to your missions, fix any issues you might be having and other things. For contributing and creating your own, check out the campaign Discord server.
ranked1v1.players = Players (with at least {0} games played)
ranked1v1.play = Play
ranked1v1.cancel = Cancel
Expand Down
4 changes: 4 additions & 0 deletions src/main/resources/theme/icons.css
Original file line number Diff line number Diff line change
Expand Up @@ -397,4 +397,8 @@

.veto-palm {
-fx-shape: "M 11.8068 1.8587 L 11.8067 2.4721 L 11.8058 9.8249 L 13.3528 8.6739 C 14.0631 8.1453 15.0404 8.1616 15.7326 8.7136 L 17.008 9.7306 L 17.4413 10.0761 L 17.1686 10.5586 L 14.2707 15.6856 L 13.7992 16.5846 C 12.4816 19.0965 9.7937 20.5816 6.9659 20.3599 C 3.245 20.0681 0.3874 16.9418 0.4302 13.2098 L 0.5402 3.6419 L 0.547 3.0517 L 1.1351 3.0017 L 3.831 2.7726 L 3.9211 0.9443 L 3.9506 0.347 L 4.5483 0.3267 L 7.8067 0.2164 L 8.4872 0.1933 L 8.4786 0.8742 L 8.4687 1.665 L 11.1944 1.8232 L 11.8068 1.8587 Z M 8.4523 2.9663 L 8.3681 9.6469 L 7.0682 9.6305 L 7.1609 2.2698 L 7.1702 1.5387 L 5.1901 1.6057 L 5.1017 3.3995 L 4.8833 9.6389 L 3.5841 9.5935 L 3.777 4.0819 L 1.8333 4.2471 L 1.7302 13.2247 C 1.6951 16.2725 4.0288 18.8256 7.0675 19.0639 C 9.3769 19.2449 11.572 18.0322 12.6479 15.9807 L 13.1242 15.0728 L 13.1289 15.0638 L 13.1339 15.0548 L 15.7641 10.4014 L 14.9221 9.73 C 14.6914 9.546 14.3656 9.5406 14.1289 9.7168 L 11.5437 11.6405 L 10.5057 11.1189 L 10.5067 3.0854 L 8.4523 2.9663 Z";
}

.wrench-icon {
-fx-shape: "M.102 2.223A3.004 3.004 0 0 0 3.78 5.897l6.341 6.252A3.003 3.003 0 0 0 13 16a3 3 0 1 0-.851-5.878L5.897 3.781A3.004 3.004 0 0 0 2.223.1l2.141 2.142L4 4l-1.757.364zm13.37 9.019.528.026.287.445.445.287.026.529L15 13l-.242.471-.026.529-.445.287-.287.445-.529.026L13 15l-.471-.242-.529-.026-.287-.445-.445-.287-.026-.529L11 13l.242-.471.026-.529.445-.287.287-.445.529-.026L13 11z"
}
Loading