builderFiltersActivated = new ArrayList<>(KeyBindType.values().length);
48 |
49 | @CommentSection({"Debug Settings", "------"})
50 | @Comment({"Whether or not CS2 needs to be focused."})
51 | @Key("cs2.focus")
52 | private boolean cs2Focus = true;
53 |
54 | public void setConfigPath(String configPath) {
55 | this.configPath = configPath;
56 | configPathProperty.set(configPath);
57 | }
58 |
59 | @PostInject
60 | private void updateProperties() {
61 | configPathProperty.set(configPath);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/java/de/bsommerfeld/randomizer/ui/RandomizerApplication.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.randomizer.ui;
2 |
3 | import com.github.kwhat.jnativehook.GlobalScreen;
4 | import com.github.kwhat.jnativehook.NativeHookException;
5 | import de.bsommerfeld.model.action.spi.ActionSequenceDispatcher;
6 | import de.bsommerfeld.model.action.spi.ActionSequenceExecutor;
7 | import de.bsommerfeld.randomizer.Main;
8 | import de.bsommerfeld.randomizer.ui.view.ViewProvider;
9 | import de.bsommerfeld.randomizer.ui.view.controller.RandomizerWindowController;
10 | import javafx.application.Application;
11 | import javafx.application.Platform;
12 | import javafx.scene.Parent;
13 | import javafx.scene.Scene;
14 | import javafx.scene.image.Image;
15 | import javafx.stage.Stage;
16 | import lombok.extern.slf4j.Slf4j;
17 |
18 | @Slf4j
19 | public class RandomizerApplication extends Application {
20 |
21 | private static final int MIN_WIDTH = 778;
22 | private static final int MIN_HEIGHT = 536;
23 | private static final int MAX_WIDTH = 1280;
24 | private static final int MAX_HEIGHT = 720;
25 |
26 | @Override
27 | public void start(Stage stage) {
28 | log.debug("Starting Randomizer...");
29 | try {
30 | Thread.setDefaultUncaughtExceptionHandler(new UIUncaughtExceptionHandler());
31 | buildAndShowApplication(stage);
32 | log.debug("Main window displayed");
33 | } catch (Exception e) {
34 | log.error("An error occurred while starting the application", e);
35 | }
36 | }
37 |
38 | private void buildAndShowApplication(Stage stage) {
39 | ViewProvider viewProvider = Main.getInjector().getInstance(ViewProvider.class);
40 | ActionSequenceDispatcher actionSequenceDispatcher =
41 | Main.getInjector().getInstance(ActionSequenceDispatcher.class);
42 | ActionSequenceExecutor actionSequenceExecutor =
43 | Main.getInjector().getInstance(ActionSequenceExecutor.class);
44 | log.debug("Loading main window...");
45 | buildApplication(stage, viewProvider, actionSequenceDispatcher, actionSequenceExecutor);
46 | }
47 |
48 | private void buildApplication(
49 | Stage stage,
50 | ViewProvider viewProvider,
51 | ActionSequenceDispatcher actionSequenceDispatcher,
52 | ActionSequenceExecutor actionSequenceExecutor) {
53 | Parent root = viewProvider.requestView(RandomizerWindowController.class).parent();
54 | Scene scene = new Scene(root);
55 | setupStage(stage, scene);
56 | stage.setOnCloseRequest(
57 | _ -> {
58 | try {
59 | log.info("Unregistering native hook...");
60 | GlobalScreen.unregisterNativeHook();
61 | } catch (NativeHookException e) {
62 | log.error("Failed to unregister native hook", e);
63 | // We don't want to throw anything on close request
64 | } finally {
65 | log.info("Closing application, stopping executor and discarding running actions...");
66 | actionSequenceDispatcher.discardAllRunningActions();
67 | actionSequenceExecutor.stop();
68 | Platform.exit();
69 | }
70 | });
71 | stage.show();
72 | }
73 |
74 | private void setupStage(Stage stage, Scene scene) {
75 | stage.setTitle("Randomizer " + Main.getRandomizerVersion());
76 | stage.getIcons().add(new Image("de/bsommerfeld/randomizer/images/randomizer.png"));
77 | scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
78 | stage.setMinWidth(MIN_WIDTH);
79 | stage.setMinHeight(MIN_HEIGHT);
80 | stage.setWidth(MIN_WIDTH);
81 | stage.setHeight(MIN_HEIGHT);
82 | stage.setMaxWidth(MAX_WIDTH);
83 | stage.setMaxHeight(MAX_HEIGHT);
84 | stage.setScene(scene);
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/java/de/bsommerfeld/randomizer/ui/UIUncaughtExceptionHandler.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.randomizer.ui;
2 |
3 | import de.bsommerfeld.model.ApplicationContext;
4 | import java.awt.Desktop;
5 | import java.io.File;
6 | import java.io.IOException;
7 | import java.net.URI;
8 | import java.net.URISyntaxException;
9 | import javafx.application.Platform;
10 | import javafx.scene.control.Alert;
11 | import javafx.scene.control.Hyperlink;
12 | import javafx.scene.control.Label;
13 | import javafx.scene.layout.VBox;
14 | import lombok.extern.slf4j.Slf4j;
15 |
16 | @Slf4j
17 | public class UIUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
18 |
19 | private static final long DIALOG_COOLDOWN_MS = 5000; // 5 seconds
20 |
21 | private static final String GITHUB_ISSUES_URL =
22 | "https://github.com/bsommerfeld/randomizer-cs2/issues";
23 |
24 | private long lastDialogShownTime = 0;
25 |
26 | @Override
27 | public void uncaughtException(Thread t, Throwable e) {
28 | log.error("Unexpected error", e);
29 |
30 | long currentTime = System.currentTimeMillis();
31 | if (currentTime - lastDialogShownTime > DIALOG_COOLDOWN_MS) {
32 | lastDialogShownTime = currentTime;
33 |
34 | try {
35 | Platform.runLater(
36 | () -> {
37 | Alert alert = createStyledAlert();
38 | VBox content = createContent();
39 | alert.getDialogPane().setContent(content);
40 | alert.showAndWait();
41 | });
42 | } catch (Exception ex) {
43 | log.error(
44 | "An unexpected error occurred, and the error dialog could not be displayed. Secondary exception: ",
45 | ex);
46 | }
47 | } else {
48 | log.error("Suppressed error dialog due to cooldown.", e);
49 | }
50 | }
51 |
52 | private Alert createStyledAlert() {
53 | Alert alert = new Alert(Alert.AlertType.ERROR);
54 | alert
55 | .getDialogPane()
56 | .getStylesheets()
57 | .add(getClass().getResource("alert-style.css").toExternalForm());
58 | alert.getDialogPane().getStyleClass().add("error");
59 | alert.setTitle("Unexpected Error");
60 | alert.setHeaderText("An error occurred");
61 |
62 | alert.getDialogPane().getStyleClass().add("fade-in");
63 |
64 | return alert;
65 | }
66 |
67 | private VBox createContent() {
68 | Hyperlink githubLink =
69 | createHyperlink("📋 GitHub Issues - Report Bug", () -> openUrl(GITHUB_ISSUES_URL));
70 |
71 | Hyperlink logsLink = createHyperlink("📁 Open Log Folder", this::openLogsFolder);
72 |
73 | VBox content = new VBox();
74 | content.getStyleClass().add("vbox");
75 | content
76 | .getChildren()
77 | .addAll(
78 | new Label("Please report this error to GitHub."),
79 | new Label("The following information can help to debug the issue:"),
80 | githubLink,
81 | logsLink);
82 |
83 | return content;
84 | }
85 |
86 | private Hyperlink createHyperlink(String text, Runnable action) {
87 | Hyperlink hyperlink = new Hyperlink(text);
88 | hyperlink.setOnAction(_ -> action.run());
89 | return hyperlink;
90 | }
91 |
92 | private void openUrl(String url) {
93 | try {
94 | Desktop.getDesktop().browse(new URI(url));
95 | } catch (IOException | URISyntaxException e) {
96 | log.error("Could not open URL: {}", url, e);
97 | throw new IllegalStateException(e);
98 | }
99 | }
100 |
101 | private void openLogsFolder() {
102 | try {
103 | File logsFolder = ApplicationContext.getAppdataLogsFolder();
104 | if (logsFolder.exists() && logsFolder.isDirectory()) {
105 | Desktop.getDesktop().open(logsFolder);
106 | } else {
107 | log.error("Log folder does not exist: {}", logsFolder.getAbsolutePath());
108 | throw new IllegalStateException(
109 | "Log folder does not exist: " + logsFolder.getAbsolutePath());
110 | }
111 | } catch (IOException e) {
112 | log.error("Could not open logs folder", e);
113 | throw new IllegalStateException(e);
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/java/de/bsommerfeld/randomizer/ui/view/View.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.randomizer.ui.view;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * Marks a class as a view.
10 | *
11 | * A View is not a Frame, but could also be a component etc.
12 | */
13 | @Retention(RetentionPolicy.RUNTIME)
14 | @Target(ElementType.TYPE)
15 | public @interface View {
16 | }
17 |
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/java/de/bsommerfeld/randomizer/ui/view/ViewLoader.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.randomizer.ui.view;
2 |
3 | import de.bsommerfeld.randomizer.Main;
4 | import java.io.IOException;
5 | import java.net.URL;
6 | import java.text.MessageFormat;
7 | import javafx.fxml.FXMLLoader;
8 | import javafx.scene.Parent;
9 |
10 | public class ViewLoader {
11 |
12 | /**
13 | * Loads a view and its controller from an FXML file associated with the specified class.
14 | *
15 | * @param the type of the controller
16 | * @param clazz the class of the controller for the corresponding FXML file
17 | * @return a {@link ViewWrapper} containing the loaded view and its controller
18 | * @throws IllegalStateException if the FXML file could not be found or loaded
19 | */
20 | public ViewWrapper loadView(Class clazz) {
21 | FXMLLoader fxmlLoader = new FXMLLoader();
22 |
23 | String name = clazz.getSimpleName().replace("Controller", "");
24 | URL fxmlLocation = clazz.getResource(name + ".fxml");
25 | if (fxmlLocation == null) {
26 | throw new IllegalStateException(
27 | MessageFormat.format("FXML Datei konnte nicht gefunden werden für Klasse: {0}", clazz));
28 | }
29 |
30 | fxmlLoader.setLocation(fxmlLocation);
31 | fxmlLoader.setControllerFactory(param -> Main.getInjector().getInstance(param));
32 |
33 | try {
34 | Parent parent = fxmlLoader.load();
35 | T controller = fxmlLoader.getController();
36 | return new ViewWrapper<>(parent, controller);
37 | } catch (IOException e) {
38 | throw new IllegalStateException(
39 | MessageFormat.format("FXML konnte nicht geladen werden für die Klasse: {0}", clazz), e);
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/java/de/bsommerfeld/randomizer/ui/view/ViewProvider.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.randomizer.ui.view;
2 |
3 | import java.util.Map;
4 | import java.util.concurrent.ConcurrentHashMap;
5 | import java.util.function.Consumer;
6 |
7 | public class ViewProvider {
8 |
9 | private final Map, ViewWrapper>> viewMap = new ConcurrentHashMap<>();
10 | private final Map, Consumer>> listenerMap = new ConcurrentHashMap<>();
11 | private final ViewLoader viewLoader = new ViewLoader();
12 |
13 | /**
14 | * Registers a listener that responds to changes in the specified view class.
15 | *
16 | * @param the type of the view class
17 | * @param viewClass the class of the view to listen for changes on
18 | * @param listener the consumer that performs actions when the view changes
19 | */
20 | public void registerViewChangeListener(Class viewClass, Consumer listener) {
21 | listenerMap.put(viewClass, listener);
22 | }
23 |
24 | /**
25 | * Requests a view of the specified class, loading it if necessary.
26 | *
27 | * @param viewClass the class of the view to request
28 | * @return a {@link ViewWrapper} containing the requested view and its controller
29 | * @throws IllegalStateException if the view could not be instantiated
30 | */
31 | @SuppressWarnings("unchecked")
32 | public ViewWrapper requestView(Class viewClass) {
33 | checkForViewAnnotation(viewClass);
34 | // Versuche zunächst, die View aus der Map zu holen
35 | ViewWrapper viewWrapper = (ViewWrapper) viewMap.get(viewClass);
36 | if (viewWrapper == null) {
37 | // Lade die View in einer separaten Methode, ohne seitliche Effekte in computeIfAbsent
38 | viewWrapper = loadViewSafely(viewClass);
39 | // Füge sie nur ein, wenn noch nicht vorhanden
40 | ViewWrapper previousValue = (ViewWrapper) viewMap.putIfAbsent(viewClass, viewWrapper);
41 | if (previousValue != null) {
42 | viewWrapper = previousValue;
43 | }
44 | }
45 | return viewWrapper;
46 | }
47 |
48 | private ViewWrapper loadViewSafely(Class viewClass) {
49 | try {
50 | return viewLoader.loadView(viewClass);
51 | } catch (Exception e) {
52 | throw new IllegalStateException("Could not instantiate view: " + viewClass.getName(), e);
53 | }
54 | }
55 |
56 | /**
57 | * Triggers a view change by notifying the registered listener for the specified view class.
58 | *
59 | * If the view is not already loaded, it will be loaded using {@link #requestView(Class)}.
60 | * After the view is loaded, the registered listener (if any) will be executed.
61 | *
62 | * @param viewClass the class of the view whose change listener should be triggered
63 | * @param the type of the view class
64 | */
65 | public void triggerViewChange(Class viewClass) {
66 | triggerViewChange(viewClass, null);
67 | }
68 |
69 | /**
70 | * Triggers a view change by notifying the registered listener for the specified view class and
71 | * optionally configuring the view controller.
72 | *
73 | * If the view is not already loaded, it will be loaded using {@link #requestView(Class)}.
74 | * After the view is loaded, the registered listener (if any) will be executed, and the provided
75 | * configurator will be applied to the view's controller.
76 | *
77 | * @param viewClass the class of the view whose change listener should be triggered
78 | * @param viewConfigurator an optional {@link Consumer} to configure the view controller before or
79 | * after the listener is triggered (can be {@code null})
80 | * @param the type of the view class
81 | */
82 | @SuppressWarnings("unchecked")
83 | public void triggerViewChange(Class viewClass, Consumer viewConfigurator) {
84 | // Check if the view is already loaded
85 | ViewWrapper viewWrapper = (ViewWrapper) viewMap.get(viewClass);
86 | if (viewWrapper == null) {
87 | // Load the view if it is not already loaded
88 | viewWrapper = requestView(viewClass);
89 | }
90 |
91 | // Get the controller from the ViewWrapper
92 | T controller = viewWrapper.controller();
93 |
94 | // Apply the registered listener, if any is registered
95 | Consumer listener = (Consumer) listenerMap.get(viewClass);
96 | if (listener != null) {
97 | listener.accept(controller);
98 | }
99 |
100 | // Apply the view configurator, if provided
101 | if (viewConfigurator != null) {
102 | viewConfigurator.accept(controller);
103 | }
104 | }
105 |
106 | private void checkForViewAnnotation(Class> viewClass) {
107 | if (!viewClass.isAnnotationPresent(View.class)) {
108 | throw new IllegalArgumentException("Class " + viewClass.getName() + " is not a view.");
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/java/de/bsommerfeld/randomizer/ui/view/ViewWrapper.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.randomizer.ui.view;
2 |
3 | import javafx.scene.Parent;
4 |
5 | /**
6 | * A record that bundles a JavaFX parent node and its associated controller of type .
7 | *
8 | * @param parent the root node of the FXML scene graph
9 | * @param controller the controller responsible for handling the events and interactions of the FXML view
10 | * @param the type of the controller
11 | */
12 | public record ViewWrapper(Parent parent, T controller) {
13 | }
14 |
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/java/de/bsommerfeld/randomizer/ui/view/controller/NavigationBarController.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.randomizer.ui.view.controller;
2 |
3 | import com.google.inject.Inject;
4 | import de.bsommerfeld.randomizer.ui.view.View;
5 | import de.bsommerfeld.randomizer.ui.view.ViewProvider;
6 | import de.bsommerfeld.randomizer.ui.view.controller.builder.BuilderViewController;
7 | import de.bsommerfeld.randomizer.ui.view.viewmodel.NavigationBarViewModel;
8 | import java.util.Arrays;
9 | import java.util.HashMap;
10 | import java.util.List;
11 | import java.util.Map;
12 | import java.util.function.Consumer;
13 | import javafx.application.Platform;
14 | import javafx.fxml.FXML;
15 | import javafx.scene.control.ToggleButton;
16 | import javafx.scene.control.Tooltip;
17 | import lombok.extern.slf4j.Slf4j;
18 |
19 | @View
20 | @Slf4j
21 | public class NavigationBarController {
22 |
23 | private final NavigationBarViewModel navigationBarViewModel;
24 | private final ViewProvider viewProvider;
25 | private final Map, Consumer>> viewInitializers = new HashMap<>();
26 | @FXML private ToggleButton homeButton;
27 | @FXML private ToggleButton randomizerButton;
28 | @FXML private ToggleButton builderButton;
29 | @FXML private ToggleButton settingsButton;
30 | private List navButtons;
31 |
32 | @Inject
33 | public NavigationBarController(
34 | NavigationBarViewModel navigationBarViewModel, ViewProvider viewProvider) {
35 | this.navigationBarViewModel = navigationBarViewModel;
36 | this.viewProvider = viewProvider;
37 | }
38 |
39 | @FXML
40 | private void initialize() {
41 | navButtons = Arrays.asList(homeButton, randomizerButton, builderButton, settingsButton);
42 |
43 | setupNavigationButton(homeButton, "Home", HomeViewController.class);
44 | setupNavigationButton(randomizerButton, "Randomizer", RandomizerViewController.class);
45 | setupNavigationButton(builderButton, "Builder", BuilderViewController.class);
46 | setupNavigationButton(settingsButton, "Settings", SettingsViewController.class);
47 |
48 | navigationBarViewModel
49 | .getSelectedView()
50 | .addListener((obs, oldView, newView) -> triggerViewChange(newView));
51 |
52 | if (navigationBarViewModel.getSelectedView().get() == null) {
53 | Platform.runLater(() -> selectButton(homeButton, HomeViewController.class));
54 | } else {
55 | Class> currentView = navigationBarViewModel.getSelectedView().get();
56 | navButtons.stream()
57 | .filter(btn -> getViewClassForButton(btn).equals(currentView))
58 | .findFirst()
59 | .ifPresent(btn -> Platform.runLater(() -> btn.setSelected(true)));
60 | }
61 | }
62 |
63 | private void setupNavigationButton(ToggleButton button, String tooltipText, Class> viewClass) {
64 | button.setUserData(viewClass);
65 | Tooltip tooltip = new Tooltip(tooltipText);
66 | tooltip.getStyleClass().add("tooltip-user-options");
67 | Tooltip.install(button, tooltip);
68 |
69 | button
70 | .selectedProperty()
71 | .addListener(
72 | (obs, wasSelected, isSelected) -> {
73 | if (isSelected) {
74 | selectButton(button, viewClass);
75 | } else {
76 | ensureAtLeastOneButtonSelected(button);
77 | }
78 | });
79 | }
80 |
81 | private void selectButton(ToggleButton selectedButton, Class> viewClass) {
82 | if (navigationBarViewModel.getSelectedView().get() != viewClass) {
83 | navigationBarViewModel.setSelectedView(viewClass);
84 | }
85 | for (ToggleButton btn : navButtons) {
86 | if (btn != selectedButton && btn.isSelected()) {
87 | btn.setSelected(false);
88 | }
89 | }
90 | if (!selectedButton.isSelected()) {
91 | selectedButton.setSelected(true);
92 | }
93 | }
94 |
95 | private void ensureAtLeastOneButtonSelected(ToggleButton deselectedButton) {
96 | boolean anotherButtonIsSelected =
97 | navButtons.stream().anyMatch(btn -> btn != deselectedButton && btn.isSelected());
98 | if (!anotherButtonIsSelected) {
99 | Platform.runLater(() -> deselectedButton.setSelected(true));
100 | }
101 | }
102 |
103 | @SuppressWarnings("unchecked")
104 | private void triggerViewChange(Class newViewClass) {
105 | if (newViewClass == null) {
106 | return;
107 | }
108 |
109 | Consumer initializer = (Consumer) viewInitializers.get(newViewClass);
110 |
111 | if (initializer != null) {
112 | viewProvider.triggerViewChange(newViewClass, initializer);
113 | } else {
114 | viewProvider.triggerViewChange(newViewClass);
115 | }
116 | }
117 |
118 | private Class> getViewClassForButton(ToggleButton button) {
119 | return (Class>) button.getUserData();
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/java/de/bsommerfeld/randomizer/ui/view/controller/RandomizerWindowController.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.randomizer.ui.view.controller;
2 |
3 | import com.google.inject.Inject;
4 | import de.bsommerfeld.randomizer.config.RandomizerConfig;
5 | import de.bsommerfeld.randomizer.ui.util.GifDecoder;
6 | import de.bsommerfeld.randomizer.ui.view.View;
7 | import de.bsommerfeld.randomizer.ui.view.ViewProvider;
8 | import de.bsommerfeld.randomizer.ui.view.controller.builder.BuilderViewController;
9 | import java.io.IOException;
10 | import java.net.URL;
11 | import java.util.ResourceBundle;
12 | import javafx.animation.KeyFrame;
13 | import javafx.animation.Timeline;
14 | import javafx.fxml.FXML;
15 | import javafx.fxml.Initializable;
16 | import javafx.scene.Node;
17 | import javafx.scene.Parent;
18 | import javafx.scene.image.Image;
19 | import javafx.scene.image.ImageView;
20 | import javafx.scene.input.MouseEvent;
21 | import javafx.scene.layout.BorderPane;
22 | import javafx.scene.layout.GridPane;
23 | import javafx.scene.layout.VBox;
24 | import javafx.util.Duration;
25 |
26 | @View
27 | public class RandomizerWindowController implements Initializable {
28 |
29 | private static final String GIF_RESOURCE_PATH = "de/bsommerfeld/randomizer/gif/its-time.gif";
30 |
31 | private final ViewProvider viewProvider;
32 | private final RandomizerConfig randomizerConfig;
33 |
34 | @FXML private BorderPane root;
35 | @FXML private GridPane contentPane;
36 | @FXML private VBox navigationBarHolder;
37 |
38 | @Inject
39 | public RandomizerWindowController(ViewProvider viewProvider, RandomizerConfig randomizerConfig) {
40 | this.viewProvider = viewProvider;
41 | this.randomizerConfig = randomizerConfig;
42 | }
43 |
44 | @Override
45 | public void initialize(URL url, ResourceBundle resourceBundle) {
46 | loadNavigationBar();
47 |
48 | registerViewListener();
49 | setupControlBarClickTransparency();
50 |
51 | if (randomizerConfig.isShowIntro()) addPreloadingGif();
52 |
53 | loadHomeView();
54 | }
55 |
56 | private void addPreloadingGif() {
57 | try {
58 | GifDecoder gifDecoder = new GifDecoder(GIF_RESOURCE_PATH);
59 | ImageView e = new ImageView(new Image(GIF_RESOURCE_PATH));
60 |
61 | e.fitHeightProperty().bind(root.heightProperty());
62 | e.fitWidthProperty().bind(root.widthProperty());
63 |
64 | root.getChildren().add(e);
65 | executeAfterDelay(gifDecoder.getTotalDuration(), () -> root.getChildren().remove(e));
66 | } catch (IOException e) {
67 | throw new RuntimeException(e);
68 | }
69 | }
70 |
71 | private void executeAfterDelay(int millis, Runnable action) {
72 | Timeline delay = new Timeline(new KeyFrame(Duration.millis(millis), _ -> action.run()));
73 | delay.setCycleCount(1);
74 | delay.play();
75 | }
76 |
77 | // note: for this pickOnBounds have to be false
78 | private void setupControlBarClickTransparency() {
79 | navigationBarHolder.addEventFilter(
80 | MouseEvent.MOUSE_CLICKED,
81 | event -> {
82 | if (event.getTarget() == navigationBarHolder) {
83 | event.consume();
84 | }
85 | });
86 | }
87 |
88 | private void registerViewListener() {
89 | viewProvider.registerViewChangeListener(HomeViewController.class, _ -> loadHomeView());
90 | viewProvider.registerViewChangeListener(BuilderViewController.class, _ -> loadBuilderView());
91 | viewProvider.registerViewChangeListener(
92 | RandomizerViewController.class, _ -> loadRandomizerView());
93 | viewProvider.registerViewChangeListener(SettingsViewController.class, _ -> loadSettingsView());
94 | }
95 |
96 | private void loadSettingsView() {
97 | Parent settingsViewParent = viewProvider.requestView(SettingsViewController.class).parent();
98 | setContent(settingsViewParent);
99 | }
100 |
101 | private void loadNavigationBar() {
102 | Parent controlBarParent = viewProvider.requestView(NavigationBarController.class).parent();
103 | navigationBarHolder.getChildren().add(controlBarParent);
104 | }
105 |
106 | private void loadHomeView() {
107 | Parent homeViewParent = viewProvider.requestView(HomeViewController.class).parent();
108 | setContent(homeViewParent);
109 | }
110 |
111 | private void loadRandomizerView() {
112 | Parent randomizerViewParent = viewProvider.requestView(RandomizerViewController.class).parent();
113 | setContent(randomizerViewParent);
114 | }
115 |
116 | private void loadBuilderView() {
117 | Parent builderViewParent = viewProvider.requestView(BuilderViewController.class).parent();
118 | setContent(builderViewParent);
119 | }
120 |
121 | private void setContent(Node node) {
122 | clearContent();
123 | if (node != null) {
124 | contentPane.getChildren().add(node);
125 | }
126 | }
127 |
128 | private void clearContent() {
129 | contentPane.getChildren().clear();
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/java/de/bsommerfeld/randomizer/ui/view/controller/SettingsViewController.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.randomizer.ui.view.controller;
2 |
3 | import com.google.inject.Inject;
4 | import de.bsommerfeld.randomizer.ui.view.View;
5 | import de.bsommerfeld.randomizer.ui.view.ViewProvider;
6 | import de.bsommerfeld.randomizer.ui.view.controller.settings.GeneralSettingsController;
7 | import de.bsommerfeld.randomizer.ui.view.controller.settings.MachineLearningSettingsController;
8 | import javafx.fxml.FXML;
9 | import javafx.scene.control.ToggleButton;
10 | import javafx.scene.layout.GridPane;
11 | import javafx.event.ActionEvent;
12 |
13 | @View
14 | public class SettingsViewController {
15 |
16 | private final ViewProvider viewProvider;
17 |
18 | @FXML private ToggleButton generalToggleButton;
19 | @FXML private ToggleButton machineLearningToggleButton;
20 | @FXML private GridPane contentPane;
21 |
22 | @Inject
23 | public SettingsViewController(ViewProvider viewProvider) {
24 | this.viewProvider = viewProvider;
25 | }
26 |
27 | @FXML
28 | private void initialize() {
29 | generalToggleButton.setOnAction(this::onGeneralToggleButtonAction);
30 | machineLearningToggleButton.setOnAction(this::onMachineLearningToggleButtonAction);
31 |
32 | generalToggleButton.setSelected(true);
33 | loadGeneralSettingsView();
34 | }
35 |
36 | @FXML
37 | private void onGeneralToggleButtonAction(ActionEvent event) {
38 | if (generalToggleButton.isSelected()) {
39 | machineLearningToggleButton.setSelected(false);
40 | loadGeneralSettingsView();
41 | } else {
42 | if (!machineLearningToggleButton.isSelected()) {
43 | generalToggleButton.setSelected(true);
44 | }
45 | }
46 | }
47 |
48 | @FXML
49 | private void onMachineLearningToggleButtonAction(ActionEvent event) {
50 | if (machineLearningToggleButton.isSelected()) {
51 | generalToggleButton.setSelected(false);
52 | loadMachineLearningView();
53 | } else {
54 | if (!generalToggleButton.isSelected()) {
55 | machineLearningToggleButton.setSelected(true);
56 | }
57 | }
58 | }
59 |
60 | private void loadGeneralSettingsView() {
61 | contentPane
62 | .getChildren()
63 | .setAll(viewProvider.requestView(GeneralSettingsController.class).parent());
64 | }
65 |
66 | private void loadMachineLearningView() {
67 | contentPane
68 | .getChildren()
69 | .setAll(viewProvider.requestView(MachineLearningSettingsController.class).parent());
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/java/de/bsommerfeld/randomizer/ui/view/controller/builder/filler/BuilderFillerViewController.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.randomizer.ui.view.controller.builder.filler;
2 |
3 | import com.google.inject.Inject;
4 | import de.bsommerfeld.randomizer.config.RandomizerConfig;
5 | import de.bsommerfeld.randomizer.ui.view.View;
6 | import de.bsommerfeld.randomizer.ui.view.ViewProvider;
7 | import de.bsommerfeld.randomizer.ui.view.controller.SettingsViewController;
8 | import javafx.beans.binding.Bindings;
9 | import javafx.event.ActionEvent;
10 | import javafx.fxml.FXML;
11 | import javafx.scene.layout.HBox;
12 |
13 | @View
14 | public class BuilderFillerViewController {
15 |
16 | private final ViewProvider viewProvider;
17 | private final RandomizerConfig randomizerConfig;
18 | @FXML private HBox configIndicatorHBox;
19 |
20 | @Inject
21 | public BuilderFillerViewController(ViewProvider viewProvider, RandomizerConfig randomizerConfig) {
22 | this.viewProvider = viewProvider;
23 | this.randomizerConfig = randomizerConfig;
24 | }
25 |
26 | @FXML
27 | private void initialize() {
28 | configIndicatorHBox
29 | .visibleProperty()
30 | .bind(
31 | Bindings.createBooleanBinding(
32 | () ->
33 | randomizerConfig.getConfigPath() == null
34 | || randomizerConfig.getConfigPath().isEmpty(),
35 | randomizerConfig.getConfigPathProperty()));
36 | }
37 |
38 | @FXML
39 | public void onHyperlink(ActionEvent event) {
40 | viewProvider.triggerViewChange(SettingsViewController.class);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/java/de/bsommerfeld/randomizer/ui/view/controller/settings/ActionSettingsController.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.randomizer.ui.view.controller.settings;
2 |
3 | import com.google.inject.Inject;
4 | import de.bsommerfeld.model.action.Action;
5 | import de.bsommerfeld.randomizer.ui.view.View;
6 | import de.bsommerfeld.randomizer.ui.view.component.MinMaxSlider;
7 | import de.bsommerfeld.randomizer.ui.view.viewmodel.settings.ActionSettingsViewModel;
8 | import java.util.function.Consumer;
9 | import javafx.application.Platform;
10 | import javafx.event.ActionEvent;
11 | import javafx.fxml.FXML;
12 | import javafx.scene.control.Label;
13 | import javafx.scene.layout.VBox;
14 |
15 | @View
16 | public class ActionSettingsController {
17 |
18 | private final ActionSettingsViewModel actionSettingsViewModel;
19 |
20 | @FXML private VBox actionSettingsVBox;
21 | @FXML private Label actionInFocusLabel;
22 | @FXML private MinMaxSlider minMaxSlider;
23 |
24 | @Inject
25 | public ActionSettingsController(ActionSettingsViewModel actionSettingsViewModel) {
26 | this.actionSettingsViewModel = actionSettingsViewModel;
27 | }
28 |
29 | @FXML
30 | void onClear(ActionEvent event) {
31 | minMaxSlider.setMinMaxValue(0, 1);
32 | }
33 |
34 | @FXML
35 | private void initialize() {
36 | initializeMinMaxSlider();
37 | setupBindings();
38 | }
39 |
40 | private void initializeMinMaxSlider() {
41 | minMaxSlider.setTimeUnit(MinMaxSlider.TimeUnit.MILLISECONDS);
42 | minMaxSlider.setMinLowerValue(0);
43 | minMaxSlider.setMaxHigherValue(9999);
44 | minMaxSlider.showLabels(false);
45 | minMaxSlider
46 | .getMinProperty()
47 | .bindBidirectional(actionSettingsViewModel.getMinIntervalProperty());
48 | minMaxSlider
49 | .getMaxProperty()
50 | .bindBidirectional(actionSettingsViewModel.getMaxIntervalProperty());
51 | }
52 |
53 | private void setupBindings() {
54 | // this is important, since if there is no action to adjust,
55 | // we don't need this view
56 | actionSettingsVBox
57 | .visibleProperty()
58 | .bind(actionSettingsViewModel.getActionInFocusProperty().isNotNull());
59 |
60 | actionSettingsViewModel
61 | .getActionInFocusProperty()
62 | .addListener(
63 | (_, _, newValue) -> {
64 | if (newValue == null) return;
65 | actionInFocusLabel.setText(newValue.getName());
66 |
67 | /*
68 | * even if it seems nonsensly to set this manually, since we already bound it bidirectional
69 | * to each other, this is still needed, because otherwise the values are not correctly set whyever.
70 | */
71 | Platform.runLater(
72 | () -> {
73 | minMaxSlider.setMinMaxValue(
74 | actionSettingsViewModel.getMinIntervalProperty().get(),
75 | actionSettingsViewModel.getMaxIntervalProperty().get());
76 | });
77 | });
78 | }
79 |
80 | public void bindOnVisibleProperty(Consumer consumer) {
81 | actionSettingsVBox.visibleProperty().addListener((_, _, newValue) -> consumer.accept(newValue));
82 | }
83 |
84 | public void onChange(Runnable callback) {
85 | minMaxSlider.getMinProperty().addListener((_, _, newNumber) -> callback.run());
86 | minMaxSlider.getMaxProperty().addListener((_, _, newNumber) -> callback.run());
87 | }
88 |
89 | public void setAction(Action action) {
90 | actionSettingsViewModel.getActionInFocusProperty().set(action);
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/java/de/bsommerfeld/randomizer/ui/view/controller/settings/MachineLearningSettingsController.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.randomizer.ui.view.controller.settings;
2 |
3 | import com.google.inject.Inject;
4 | import de.bsommerfeld.randomizer.ui.view.View;
5 | import javafx.fxml.FXML;
6 |
7 | @View
8 | public class MachineLearningSettingsController {
9 |
10 | @Inject
11 | public MachineLearningSettingsController() {
12 | // Empty constructor
13 | }
14 |
15 | @FXML
16 | private void initialize() {
17 | // Initialize the controller
18 | }
19 | }
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/java/de/bsommerfeld/randomizer/ui/view/controller/settings/TitleDescriptionSettingsController.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.randomizer.ui.view.controller.settings;
2 |
3 | import de.bsommerfeld.randomizer.ui.view.View;
4 | import javafx.application.Platform;
5 | import javafx.beans.property.ReadOnlyBooleanProperty;
6 | import javafx.beans.property.StringProperty;
7 | import javafx.fxml.FXML;
8 | import javafx.scene.control.TextArea;
9 | import javafx.scene.control.TextField;
10 | import javafx.scene.layout.VBox;
11 |
12 | @View
13 | public class TitleDescriptionSettingsController {
14 |
15 | @FXML private VBox root;
16 | @FXML private TextArea textArea;
17 | @FXML private TextField textField;
18 |
19 | public TitleDescriptionSettingsController() {
20 | Platform.runLater(this::initialize);
21 | }
22 |
23 | private void initialize() {
24 | textField
25 | .textProperty()
26 | .addListener(
27 | (_, oldValue, newValue) -> {
28 | if (newValue.length() > 50) {
29 | textField.setText(oldValue);
30 | }
31 | });
32 | }
33 |
34 | public StringProperty titleProperty() {
35 | return textField.textProperty();
36 | }
37 |
38 | public StringProperty descriptionProperty() {
39 | return textArea.textProperty();
40 | }
41 |
42 | public ReadOnlyBooleanProperty visibleProperty() {
43 | return root.visibleProperty();
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/java/de/bsommerfeld/randomizer/ui/view/viewmodel/HomeViewModel.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.randomizer.ui.view.viewmodel;
2 |
3 | import com.google.inject.Inject;
4 | import de.bsommerfeld.github.config.GitHubConfig;
5 | import de.bsommerfeld.github.model.GitHubRelease;
6 | import de.bsommerfeld.github.model.GitHubReleaseAsset;
7 | import de.bsommerfeld.github.service.GitHubService;
8 | import java.awt.*;
9 | import java.io.IOException;
10 | import java.net.URI;
11 | import java.util.List;
12 | import java.util.concurrent.CompletableFuture;
13 | import java.util.concurrent.CompletionStage;
14 |
15 | import javafx.application.Platform;
16 | import javafx.beans.property.IntegerProperty;
17 | import javafx.beans.property.SimpleIntegerProperty;
18 | import javafx.beans.property.SimpleStringProperty;
19 | import javafx.beans.property.StringProperty;
20 | import javafx.collections.FXCollections;
21 | import javafx.collections.ObservableList;
22 | import lombok.Getter;
23 | import lombok.extern.slf4j.Slf4j;
24 |
25 | @Slf4j
26 | public class HomeViewModel {
27 |
28 | private static final String GITHUB_LINK = "https://github.com/bsommerfeld/randomizer-cs2";
29 | private static final String DISCORD_LINK = "https://discord.gg/782s5ExhFy";
30 |
31 | @Getter
32 | private final ObservableList releasesList = FXCollections.observableArrayList();
33 |
34 | @Getter private final StringProperty currentChangelogProperty = new SimpleStringProperty();
35 | @Getter private final IntegerProperty starsProperty = new SimpleIntegerProperty();
36 | @Getter private final IntegerProperty forksProperty = new SimpleIntegerProperty();
37 |
38 | private final GitHubConfig gitHubConfig;
39 | private final GitHubService gitHubService;
40 |
41 | @Inject
42 | public HomeViewModel(GitHubConfig gitHubConfig, GitHubService gitHubService) {
43 | this.gitHubConfig = gitHubConfig;
44 | this.gitHubService = gitHubService;
45 | }
46 |
47 | public void openGitHub() throws IOException {
48 | Desktop.getDesktop().browse(URI.create(GITHUB_LINK));
49 | }
50 |
51 | public void openDiscord() throws IOException {
52 | Desktop.getDesktop().browse(URI.create(DISCORD_LINK));
53 | }
54 |
55 | public CompletionStage fetchChangelog(GitHubRelease gitHubRelease) {
56 | return CompletableFuture.supplyAsync(() -> {
57 | try {
58 | GitHubReleaseAsset changelogAsset = gitHubRelease.getChangelogAsset();
59 | if (changelogAsset == null) {
60 | return "Kein Changelog verfügbar für diese Version";
61 | }
62 | return gitHubService.downloadAssetContent(changelogAsset);
63 | } catch (IOException e) {
64 | log.error("Error downloading changelog for {}: {}", gitHubRelease.tag(), e.getMessage());
65 | throw new RuntimeException("Fehler beim Laden des Changelogs: " + e.getMessage(), e);
66 | }
67 | });
68 | }
69 |
70 | public void updateReleases() {
71 | log.info("Updating releases...");
72 | releasesList.clear();
73 | CompletableFuture.supplyAsync(
74 | () -> {
75 | try {
76 | List repositoryReleasesWithChangelog = gitHubService.getRepositoryReleasesWithChangelog(
77 | gitHubConfig.getAuthor(), gitHubConfig.getRepository());
78 | log.info("Fetched {} releases with an CHANGELOG.md", repositoryReleasesWithChangelog.size());
79 | return repositoryReleasesWithChangelog;
80 | } catch (IOException | InterruptedException e) {
81 | log.error("Error updating releases: {}", e.getMessage(), e);
82 | throw new RuntimeException(e);
83 | }
84 | })
85 | .thenAcceptAsync(releasesList::addAll, Platform::runLater);
86 | }
87 |
88 | public void updateRepositoryDetails() {
89 | log.info("Updating repository details..");
90 | CompletableFuture.supplyAsync(
91 | () -> {
92 | try {
93 | return gitHubService.getRepositoryDetails(
94 | gitHubConfig.getAuthor(), gitHubConfig.getRepository());
95 | } catch (IOException | InterruptedException e) {
96 | log.error("Error updating repository details: {} ", e.getMessage());
97 | throw new RuntimeException(e);
98 | }
99 | })
100 | .thenAcceptAsync(
101 | gitHubRepositoryDetails -> {
102 | log.info("Gathered repository details successfully - updating view..");
103 |
104 | final int stars = gitHubRepositoryDetails.stargazersCount();
105 | final int forks = gitHubRepositoryDetails.forksCount();
106 |
107 | starsProperty.set(stars);
108 | forksProperty.set(forks);
109 |
110 | log.info("View successfully updated with {} stars and {} forks", stars, forks);
111 | },
112 | Platform::runLater);
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/java/de/bsommerfeld/randomizer/ui/view/viewmodel/NavigationBarViewModel.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.randomizer.ui.view.viewmodel;
2 |
3 | import javafx.beans.property.ObjectProperty;
4 | import javafx.beans.property.SimpleObjectProperty;
5 | import lombok.Getter;
6 | import lombok.extern.slf4j.Slf4j;
7 |
8 | @Getter
9 | @Slf4j
10 | public class NavigationBarViewModel {
11 |
12 |
13 | private final ObjectProperty> selectedView = new SimpleObjectProperty<>();
14 |
15 | public void setSelectedView(Class> viewClass) {
16 | this.selectedView.set(viewClass);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/java/de/bsommerfeld/randomizer/ui/view/viewmodel/RandomizerViewModel.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.randomizer.ui.view.viewmodel;
2 |
3 | import com.google.inject.Inject;
4 | import de.bsommerfeld.model.ApplicationContext;
5 | import de.bsommerfeld.model.ApplicationState;
6 | import de.bsommerfeld.model.action.Action;
7 | import de.bsommerfeld.model.action.sequence.ActionSequence;
8 | import de.bsommerfeld.model.action.spi.ActionSequenceDispatcher;
9 | import java.util.function.Consumer;
10 | import javafx.beans.property.ObjectProperty;
11 | import javafx.beans.property.SimpleObjectProperty;
12 | import lombok.Getter;
13 | import lombok.extern.slf4j.Slf4j;
14 |
15 | @Slf4j
16 | public class RandomizerViewModel {
17 |
18 | private final ActionSequenceDispatcher actionSequenceDispatcher;
19 | private final ApplicationContext applicationContext;
20 |
21 | @Getter
22 | private final ObjectProperty currentActionSequenceProperty =
23 | new SimpleObjectProperty<>();
24 |
25 | @Getter private final ObjectProperty currentActionProperty = new SimpleObjectProperty<>();
26 |
27 | @Inject
28 | public RandomizerViewModel(
29 | ActionSequenceDispatcher actionSequenceDispatcher, ApplicationContext applicationContext) {
30 | this.actionSequenceDispatcher = actionSequenceDispatcher;
31 | this.applicationContext = applicationContext;
32 | setupInternalHandler();
33 | }
34 |
35 | public void setApplicationStateToRunning() {
36 | applicationContext.setApplicationState(ApplicationState.RUNNING);
37 | }
38 |
39 | public void setApplicationStateToStopped() {
40 | applicationContext.setApplicationState(ApplicationState.IDLING);
41 | }
42 |
43 | public void onStateChange(Consumer consumer) {
44 | applicationContext.registerApplicationStateChangeListener(consumer);
45 | }
46 |
47 | public void onActionSequenceFinished(Consumer consumer) {
48 | actionSequenceDispatcher.registerSequenceFinishHandler(consumer);
49 | }
50 |
51 | public void onActionFinished(Consumer consumer) {
52 | actionSequenceDispatcher.registerActionFinishHandler(consumer);
53 | }
54 |
55 | public void onActionStart(Consumer consumer) {
56 | actionSequenceDispatcher.registerActionHandler(consumer);
57 | }
58 |
59 | private void setupInternalHandler() {
60 | actionSequenceDispatcher.registerSequenceHandler(currentActionSequenceProperty::set);
61 | actionSequenceDispatcher.registerActionHandler(currentActionProperty::set);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/java/de/bsommerfeld/randomizer/ui/view/viewmodel/builder/BuilderActionsViewModel.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.randomizer.ui.view.viewmodel.builder;
2 |
3 | public class BuilderActionsViewModel {}
4 |
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/java/de/bsommerfeld/randomizer/ui/view/viewmodel/builder/BuilderEditorViewModel.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.randomizer.ui.view.viewmodel.builder;
2 |
3 | import com.google.inject.Inject;
4 | import de.bsommerfeld.model.action.Action;
5 | import de.bsommerfeld.model.action.spi.ActionRepository;
6 | import de.bsommerfeld.model.action.spi.ActionSequenceRepository;
7 | import de.bsommerfeld.model.action.sequence.ActionSequence;
8 | import java.util.ArrayList;
9 | import java.util.List;
10 | import java.util.concurrent.ThreadLocalRandom;
11 | import javafx.beans.property.ListProperty;
12 | import javafx.beans.property.ObjectProperty;
13 | import javafx.beans.property.SimpleListProperty;
14 | import javafx.beans.property.SimpleObjectProperty;
15 | import javafx.beans.property.SimpleStringProperty;
16 | import javafx.beans.property.StringProperty;
17 | import javafx.collections.FXCollections;
18 | import lombok.Getter;
19 |
20 | public class BuilderEditorViewModel {
21 |
22 | @Getter
23 | private final ObjectProperty currentActionSequenceProperty =
24 | new SimpleObjectProperty<>();
25 |
26 | @Getter
27 | private final ListProperty currentActionsProperty =
28 | new SimpleListProperty<>(FXCollections.observableArrayList());
29 |
30 | @Getter private final StringProperty sequenceNameProperty = new SimpleStringProperty();
31 | @Getter private final StringProperty sequenceDescriptionProperty = new SimpleStringProperty();
32 |
33 | private final ActionRepository actionRepository;
34 | private final ActionSequenceRepository actionSequenceRepository;
35 |
36 | @Inject
37 | public BuilderEditorViewModel(
38 | ActionRepository actionRepository, ActionSequenceRepository actionSequenceRepository) {
39 | this.actionRepository = actionRepository;
40 | this.actionSequenceRepository = actionSequenceRepository;
41 | }
42 |
43 | public void saveActionSequence() {
44 | deleteOldActionSequenceIfNeeded();
45 | ActionSequence actionSequence = craftActionSequence();
46 | actionSequenceRepository.saveActionSequence(actionSequence);
47 | }
48 |
49 | /** Deletes the old action sequence, when a rename happened. */
50 | private void deleteOldActionSequenceIfNeeded() {
51 | ActionSequence actionSequence = currentActionSequenceProperty.get();
52 | if (actionSequence.getName().equals(sequenceNameProperty.get())) {
53 | return;
54 | }
55 | actionSequenceRepository.deleteActionSequence(actionSequence);
56 | }
57 |
58 | public void addRandomActions(int count) {
59 | List allActions = new ArrayList<>(actionRepository.getActions().keySet());
60 | int randomCount = ThreadLocalRandom.current().nextInt(1, count);
61 | for (int i = 0; i < randomCount; i++) {
62 | int randomIndex = (int) (Math.random() * allActions.size());
63 | Action randomAction = allActions.get(randomIndex);
64 | currentActionsProperty.add(randomAction);
65 | }
66 | }
67 |
68 | public void setActions(List actions) {
69 | currentActionsProperty.setAll(actions);
70 | }
71 |
72 | private ActionSequence craftActionSequence() {
73 | ActionSequence actionSequence = new ActionSequence(sequenceNameProperty.get());
74 | actionSequence.setActions(new ArrayList<>(currentActionsProperty.get()));
75 | actionSequence.setDescription(sequenceDescriptionProperty.get());
76 | return actionSequence;
77 | }
78 |
79 | public List getActionSequences() {
80 | return actionSequenceRepository.getActionSequences();
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/java/de/bsommerfeld/randomizer/ui/view/viewmodel/settings/ActionSettingsViewModel.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.randomizer.ui.view.viewmodel.settings;
2 |
3 | import de.bsommerfeld.model.action.Action;
4 | import de.bsommerfeld.model.action.value.Interval;
5 | import javafx.beans.property.IntegerProperty;
6 | import javafx.beans.property.ObjectProperty;
7 | import javafx.beans.property.SimpleIntegerProperty;
8 | import javafx.beans.property.SimpleObjectProperty;
9 | import javafx.beans.value.ChangeListener;
10 | import lombok.Getter;
11 |
12 | @Getter
13 | public class ActionSettingsViewModel {
14 |
15 | private final ObjectProperty actionInFocusProperty = new SimpleObjectProperty<>();
16 | private final IntegerProperty minIntervalProperty = new SimpleIntegerProperty();
17 | private final IntegerProperty maxIntervalProperty = new SimpleIntegerProperty();
18 |
19 | private final ChangeListener minIntervalUpdateListener = (_, _, _) -> applyInterval();
20 | private final ChangeListener maxIntervalUpdateListener = (_, _, _) -> applyInterval();
21 |
22 | public ActionSettingsViewModel() {
23 | setupActionInFocusListener();
24 | }
25 |
26 | private void applyInterval() {
27 | Action currentAction = actionInFocusProperty.get();
28 | if (currentAction != null) {
29 | Interval interval = currentAction.getInterval();
30 | int newMin = minIntervalProperty.get();
31 | int newMax = maxIntervalProperty.get();
32 |
33 | if (newMin > newMax) {
34 | newMax = newMin + 1;
35 | }
36 |
37 | interval.setMin(newMin);
38 | interval.setMax(newMax);
39 | }
40 | }
41 |
42 | private void addIntervalUpdateListeners() {
43 | minIntervalProperty.addListener(minIntervalUpdateListener);
44 | maxIntervalProperty.addListener(maxIntervalUpdateListener);
45 | }
46 |
47 | private void removeIntervalUpdateListeners() {
48 | minIntervalProperty.removeListener(minIntervalUpdateListener);
49 | maxIntervalProperty.removeListener(maxIntervalUpdateListener);
50 | }
51 |
52 | private void setupActionInFocusListener() {
53 | actionInFocusProperty.addListener(
54 | (obs, oldAction, newAction) -> {
55 | removeIntervalUpdateListeners();
56 |
57 | if (newAction != null) {
58 | minIntervalProperty.set(newAction.getInterval().getMin());
59 | maxIntervalProperty.set(newAction.getInterval().getMax());
60 |
61 | addIntervalUpdateListeners();
62 | }
63 | });
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/gif/its-time-old.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/gif/its-time-old.gif
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/gif/its-time.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/gif/its-time.gif
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/actionSettings/resetIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/actionSettings/resetIcon.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/actionSettings/resetIconSelected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/actionSettings/resetIconSelected.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/builderEditorView/randomizeIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/builderEditorView/randomizeIcon.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/builderEditorView/randomizeIconSelected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/builderEditorView/randomizeIconSelected.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/builderEditorView/saveIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/builderEditorView/saveIcon.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/builderEditorView/saveIconSelected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/builderEditorView/saveIconSelected.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/builderEditorView/sequenceIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/builderEditorView/sequenceIcon.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/builderView/addIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/builderView/addIcon.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/builderView/addIconSelected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/builderView/addIconSelected.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/builderView/deleteIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/builderView/deleteIcon.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/builderView/deleteIconSelected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/builderView/deleteIconSelected.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/builderView/folderIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/builderView/folderIcon.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/builderView/folderIconSelected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/builderView/folderIconSelected.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/homeView/discordIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/homeView/discordIcon.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/homeView/discordIconSelected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/homeView/discordIconSelected.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/homeView/githubForksIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/homeView/githubForksIcon.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/homeView/githubForksIconSelected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/homeView/githubForksIconSelected.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/homeView/githubIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/homeView/githubIcon.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/homeView/githubIconSelected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/homeView/githubIconSelected.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/homeView/githubStarsIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/homeView/githubStarsIcon.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/homeView/githubStarsIconSelected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/homeView/githubStarsIconSelected.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/homeView/randomizerLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/homeView/randomizerLogo.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/navigationbar/builderIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/navigationbar/builderIcon.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/navigationbar/builderIconSelected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/navigationbar/builderIconSelected.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/navigationbar/homeIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/navigationbar/homeIcon.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/navigationbar/homeIconSelected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/navigationbar/homeIconSelected.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/navigationbar/logbookIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/navigationbar/logbookIcon.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/navigationbar/logbookIconSelected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/navigationbar/logbookIconSelected.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/navigationbar/settingsIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/navigationbar/settingsIcon.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/navigationbar/settingsIconSelected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/navigationbar/settingsIconSelected.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/randomizer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/randomizer.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/randomizerView/arrowDownIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/randomizerView/arrowDownIcon.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/randomizerView/arrowDownIconSelected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/randomizerView/arrowDownIconSelected.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/randomizerView/arrowUpIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/randomizerView/arrowUpIcon.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/randomizerView/arrowUpIconSelected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/randomizerView/arrowUpIconSelected.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/randomizerView/finalActionIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/randomizerView/finalActionIcon.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/randomizerView/finalActionIconActive.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/randomizerView/finalActionIconActive.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/randomizerView/middleActionIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/randomizerView/middleActionIcon.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/randomizerView/middleActionIconActive.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/randomizerView/middleActionIconActive.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/randomizerView/notFocusedIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/randomizerView/notFocusedIcon.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/randomizerView/pauseIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/randomizerView/pauseIcon.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/randomizerView/playIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/randomizerView/playIcon.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/randomizerView/sequenceIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/randomizerView/sequenceIcon.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/randomizerView/startingActionIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/randomizerView/startingActionIcon.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/randomizerView/startingActionIconActive.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/randomizerView/startingActionIconActive.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/settingsView/checkIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/settingsView/checkIcon.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/settingsView/configFolderIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/settingsView/configFolderIcon.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/settingsView/configFolderIconPressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/settingsView/configFolderIconPressed.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/settingsView/syncFailedIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/settingsView/syncFailedIcon.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/settingsView/syncFailedIconPressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/settingsView/syncFailedIconPressed.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/settingsView/syncPathIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/settingsView/syncPathIcon.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/settingsView/syncPathIconPressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/settingsView/syncPathIconPressed.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/settingsView/syncSuccesIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/settingsView/syncSuccesIcon.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/settingsView/syncSuccesIconPressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/settingsView/syncSuccesIconPressed.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/settingsView/uncheckIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/images/settingsView/uncheckIcon.png
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/ui/nav-bar.css:
--------------------------------------------------------------------------------
1 | .navigation-bar {
2 | -fx-min-width: 48;
3 | -fx-pref-height: 504;
4 | -fx-padding: 12 0;
5 | -fx-spacing: 12;
6 | -fx-background-color: #FBFBFE;
7 | }
8 |
9 | .navigation-bar-home, .navigation-bar-builder, .navigation-bar-randomizer, .navigation-bar-settings {
10 | -fx-pref-width: 38;
11 | -fx-pref-height: 28;
12 | -fx-background-color: transparent;
13 | -fx-background-repeat: no-repeat;
14 | -fx-background-position: right;
15 | -fx-cursor: hand;
16 | }
17 |
18 | .navigation-bar-home {
19 | -fx-background-image: url("../images/navigationbar/homeIcon.png");
20 | }
21 |
22 | .navigation-bar-home:hover, .navigation-bar-home:selected {
23 | -fx-background-image: url("../images/navigationbar/homeIconSelected.png");
24 | }
25 |
26 | .navigation-bar-builder {
27 | -fx-background-image: url("../images/navigationbar/builderIcon.png");
28 | }
29 |
30 | .navigation-bar-builder:hover, .navigation-bar-builder:selected {
31 | -fx-background-image: url("../images/navigationbar/builderIconSelected.png");
32 | }
33 |
34 | .navigation-bar-randomizer {
35 | -fx-background-image: url("../images/navigationbar/logbookIcon.png");
36 | }
37 |
38 | .navigation-bar-randomizer:hover, .navigation-bar-randomizer:selected {
39 | -fx-background-image: url("../images/navigationbar/logbookIconSelected.png");
40 | }
41 |
42 | .navigation-bar-settings {
43 | -fx-background-image: url("../images/navigationbar/settingsIcon.png");
44 | }
45 |
46 | .navigation-bar-settings:hover, .navigation-bar-settings:selected {
47 | -fx-background-image: url("../images/navigationbar/settingsIconSelected.png");
48 | }
49 |
50 | .navigation-bar-separator, .navigation-bar-separator .line {
51 | -fx-max-width: 27;
52 | -fx-pref-height: 2;
53 | -fx-translate-x: 7;
54 | -fx-border-color: null;
55 | -fx-background-color: #443DFF;
56 | }
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/ui/view/controller/NavigationBar.fxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/ui/view/controller/RandomizerWindow.fxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/ui/view/controller/SettingsView.fxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/ui/view/controller/builder/BuilderActionsView.fxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
14 |
16 |
18 |
20 |
22 |
23 |
24 |
25 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/ui/view/controller/builder/BuilderEditorView.fxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
18 |
19 |
21 |
22 |
23 |
24 |
25 |
27 |
29 |
30 |
31 |
32 |
33 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
51 |
52 |
53 |
54 |
55 |
57 |
58 |
60 |
62 |
63 |
64 |
65 |
66 |
69 |
70 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/ui/view/controller/builder/BuilderView.fxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
16 |
17 |
19 |
21 |
22 |
23 |
24 |
25 |
27 |
28 |
30 |
33 |
34 |
35 |
36 |
37 |
38 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/ui/view/controller/builder/filler/BuilderFillerView.fxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/ui/view/controller/settings/ActionSettings.fxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
11 |
12 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/ui/view/controller/settings/GeneralSettings.fxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
72 |
73 |
74 |
75 |
76 |
77 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/ui/view/controller/settings/MachineLearningSettings.fxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
20 |
21 |
22 |
23 |
24 |
25 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/de/bsommerfeld/randomizer/ui/view/controller/settings/TitleDescriptionSettings.fxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/randomizer-desktop/randomizer/src/main/resources/randomizer.properties:
--------------------------------------------------------------------------------
1 | randomizer.version=${revision}
--------------------------------------------------------------------------------
/randomizer-model/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | randomizer-cs2
7 | de.bsommerfeld
8 | ${revision}
9 |
10 | 4.0.0
11 |
12 | randomizer-model
13 | ${revision}
14 |
15 |
16 |
17 | com.google.code.gson
18 | gson
19 | 2.9.1
20 |
21 |
22 | commons-io
23 | commons-io
24 | 2.14.0
25 |
26 |
27 | org.projectlombok
28 | lombok
29 | 1.18.38
30 |
31 |
32 | org.slf4j
33 | slf4j-api
34 | 2.0.16
35 |
36 |
37 | ch.qos.logback
38 | logback-classic
39 | 1.4.12
40 |
41 |
42 | net.java.dev.jna
43 | jna
44 | 5.14.0
45 |
46 |
47 | net.java.dev.jna
48 | jna-platform
49 | 5.14.0
50 |
51 |
52 | com.fasterxml.jackson.core
53 | jackson-databind
54 | 2.13.4.2
55 |
56 |
57 | com.github.kwhat
58 | jnativehook
59 | 2.2.1
60 | compile
61 |
62 |
63 | com.google.inject
64 | guice
65 | 7.0.0
66 |
67 |
68 | junit
69 | junit
70 | 4.13.2
71 | test
72 |
73 |
74 | org.junit.jupiter
75 | junit-jupiter-api
76 | 5.9.0
77 | test
78 |
79 |
80 | org.mockito
81 | mockito-core
82 | 4.7.0
83 | test
84 |
85 |
86 |
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/ApplicationContext.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.model;
2 |
3 | import java.io.File;
4 | import java.util.ArrayList;
5 | import java.util.List;
6 | import java.util.function.Consumer;
7 | import lombok.Getter;
8 | import lombok.Setter;
9 |
10 | /**
11 | * The {@code ApplicationContext} class manages and notifies state changes within an application,
12 | * providing a centralized context for maintaining and accessing application state.
13 | */
14 | @Getter
15 | public class ApplicationContext {
16 |
17 | private static final File APPDATA_FOLDER =
18 | new File(System.getenv("APPDATA") + File.separator + "randomizer");
19 |
20 | private static final File APPDATA_LIBS_FOLDER = new File(APPDATA_FOLDER, "libs");
21 | private static final File APPDATA_LOGS_FOLDER = new File(APPDATA_FOLDER, "logs");
22 |
23 | private final List> changeListener = new ArrayList<>();
24 |
25 | private volatile ApplicationState applicationState = ApplicationState.IDLING;
26 | @Setter private volatile boolean checkForCS2Focus = true;
27 |
28 | public static File getAppdataFolder() {
29 | return APPDATA_FOLDER;
30 | }
31 |
32 | public static File getAppdataLibsFolder() {
33 | return APPDATA_LIBS_FOLDER;
34 | }
35 |
36 | public static File getAppdataLogsFolder() {
37 | return APPDATA_LOGS_FOLDER;
38 | }
39 |
40 | /**
41 | * Registers a listener to be invoked whenever the application state changes.
42 | *
43 | * @param listener A consumer that processes the new application state when it changes.
44 | */
45 | public void registerApplicationStateChangeListener(Consumer listener) {
46 | changeListener.add(listener);
47 | }
48 |
49 | /**
50 | * Sets the current state of the application and notifies all registered listeners of the change.
51 | *
52 | * @param applicationState the new state of the application to be set
53 | */
54 | public void setApplicationState(ApplicationState applicationState) {
55 | this.applicationState = applicationState;
56 | changeListener.forEach(changeListener -> changeListener.accept(applicationState));
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/ApplicationState.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.model;
2 |
3 | /**
4 | * The {@code ApplicationState} enum represents various states that an application can be in.
5 | */
6 | public enum ApplicationState {
7 |
8 | /**
9 | * Represents the state where the application is actively running and operational.
10 | */
11 | RUNNING,
12 | /**
13 | * The {@code AWAITING} state indicates that the application is in a waiting phase. This could be due to various
14 | * reasons such as waiting for user input, an external event, or a specific condition to be met before proceeding to
15 | * the next state.
16 | */
17 | AWAITING,
18 | /**
19 | * The {@code CORRUPTED} state indicates that the application has encountered an error or unexpected condition
20 | * rendering it unusable or unstable.
21 | */
22 | CORRUPTED,
23 | /**
24 | * Represents the state where the application is not currently executing tasks but is still active and ready to
25 | * perform actions upon request.
26 | */
27 | IDLING
28 | }
29 |
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/ModelModule.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.model;
2 |
3 | import com.google.gson.Gson;
4 | import com.google.inject.AbstractModule;
5 | import de.bsommerfeld.model.action.config.ActionConfig;
6 | import de.bsommerfeld.model.action.impl.DefaultActionExecutor;
7 | import de.bsommerfeld.model.action.impl.DefaultFocusManager;
8 | import de.bsommerfeld.model.action.repository.DefaultActionRepository;
9 | import de.bsommerfeld.model.action.repository.DefaultActionSequenceRepository;
10 | import de.bsommerfeld.model.action.sequence.DefaultActionSequenceDispatcher;
11 | import de.bsommerfeld.model.action.sequence.DefaultActionSequenceExecutor;
12 | import de.bsommerfeld.model.action.spi.ActionExecutor;
13 | import de.bsommerfeld.model.action.spi.ActionRepository;
14 | import de.bsommerfeld.model.action.spi.ActionSequenceDispatcher;
15 | import de.bsommerfeld.model.action.spi.ActionSequenceExecutor;
16 | import de.bsommerfeld.model.action.spi.ActionSequenceRepository;
17 | import de.bsommerfeld.model.action.spi.FocusManager;
18 | import de.bsommerfeld.model.config.keybind.KeyBindNameTypeMapper;
19 | import de.bsommerfeld.model.config.keybind.KeyBindRepository;
20 | import de.bsommerfeld.model.persistence.GsonProvider;
21 | import de.bsommerfeld.model.persistence.JsonUtil;
22 | import de.bsommerfeld.model.persistence.dao.ActionSequenceDao;
23 | import de.bsommerfeld.model.persistence.de_serializer.ActionJsonDeSerializer;
24 | import de.bsommerfeld.model.persistence.de_serializer.ActionSequenceJsonDeSerializer;
25 |
26 | public class ModelModule extends AbstractModule {
27 |
28 | @Override
29 | protected void configure() {
30 | // Configuration
31 | bind(ActionConfig.class).asEagerSingleton();
32 |
33 | // Core application components
34 | bind(ApplicationContext.class).asEagerSingleton();
35 | bind(JsonUtil.class).asEagerSingleton();
36 |
37 | // Action components
38 | bind(ActionRepository.class).to(DefaultActionRepository.class).asEagerSingleton();
39 | bind(ActionSequenceRepository.class).to(DefaultActionSequenceRepository.class).asEagerSingleton();
40 | bind(ActionSequenceDispatcher.class).to(DefaultActionSequenceDispatcher.class).asEagerSingleton();
41 | bind(ActionSequenceExecutor.class).to(DefaultActionSequenceExecutor.class);
42 | bind(ActionExecutor.class).to(DefaultActionExecutor.class).asEagerSingleton();
43 | bind(FocusManager.class).to(DefaultFocusManager.class).asEagerSingleton();
44 |
45 | // KeyBind components
46 | bind(KeyBindRepository.class).asEagerSingleton();
47 | bind(KeyBindNameTypeMapper.class).asEagerSingleton();
48 |
49 | // Persistence components
50 | bind(ActionSequenceDao.class).asEagerSingleton();
51 | bind(ActionJsonDeSerializer.class);
52 | bind(ActionSequenceJsonDeSerializer.class);
53 |
54 | // Gson provider
55 | bind(Gson.class).toProvider(GsonProvider.class);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/action/ActionKey.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.model.action;
2 |
3 | import lombok.Value;
4 |
5 | /**
6 | * ActionKey represents a key binding used in various actions.
7 | *
8 | * Instances of ActionKey are created using a static factory method {@link #of(String)}. This
9 | * class is used to associate a specific key binding with an action.
10 | *
11 | *
The key is stored as a string and can represent any key or a special key binding like
12 | * "".
13 | */
14 | @Value(staticConstructor = "of")
15 | public class ActionKey {
16 |
17 | String key;
18 | }
19 |
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/action/ActionType.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.model.action;
2 |
3 | /**
4 | * ActionType is an enumeration representing different types of Actions that can fired by the system.
5 | */
6 | public enum ActionType {
7 | MOUSE,
8 | MOUSE_WHEEL,
9 | KEYBOARD,
10 | CUSTOM,
11 | }
12 |
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/action/config/ActionConfig.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.model.action.config;
2 |
3 | import com.google.inject.Singleton;
4 | import lombok.Getter;
5 | import lombok.Setter;
6 |
7 | /**
8 | * Configuration parameters for the action package.
9 | * This class centralizes all configuration parameters used in the action package,
10 | * making it easier to modify and maintain them.
11 | */
12 | @Getter
13 | @Setter
14 | @Singleton
15 | public class ActionConfig {
16 |
17 | /**
18 | * The interval in milliseconds for checking if an action should be interrupted.
19 | */
20 | private int interruptCheckInterval = 50;
21 |
22 | /**
23 | * The number of steps to use when performing a smooth mouse movement.
24 | */
25 | private int mouseMoveSteps = 50;
26 |
27 | /**
28 | * The maximum distance in pixels that a mouse can move in a single action.
29 | */
30 | private int maxMouseMoveDistance = 5000;
31 |
32 | /**
33 | * The interval in milliseconds for checking if the application window is in focus.
34 | */
35 | private int focusCheckInterval = 500;
36 |
37 | /**
38 | * The minimum wait time in milliseconds between action sequence executions.
39 | */
40 | private int minWaitTime = 30 * 1000;
41 |
42 | /**
43 | * The maximum wait time in milliseconds between action sequence executions.
44 | */
45 | private int maxWaitTime = 120 * 1000;
46 |
47 | /**
48 | * The delay in milliseconds between mouse move steps.
49 | */
50 | private int mouseMoveSmoothDelay = 10;
51 | }
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/action/impl/BaseAction.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.model.action.impl;
2 |
3 | import de.bsommerfeld.model.action.Action;
4 | import de.bsommerfeld.model.action.ActionKey;
5 | import lombok.extern.slf4j.Slf4j;
6 |
7 | /**
8 | * BaseAction is a specific implementation of the Action class designed to handle different types of
9 | * user actions. Depending on the action type, it can simulate mouse presses, mouse wheel movements,
10 | * and key presses.
11 | */
12 | @Slf4j
13 | public class BaseAction extends Action {
14 |
15 | public BaseAction(String name, ActionKey actionKey) {
16 | super(name, actionKey);
17 | }
18 |
19 | @Override
20 | protected void performActionStart(int keyCode) {
21 | if (actionExecutor != null) {
22 | actionExecutor.executeActionStart(keyCode, getActionType());
23 | }
24 | }
25 |
26 | @Override
27 | protected void performActionEnd(int keyCode) {
28 | if (actionExecutor != null) {
29 | actionExecutor.executeActionEnd(keyCode, getActionType());
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/action/impl/DefaultActionExecutor.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.model.action.impl;
2 |
3 | import com.google.inject.Singleton;
4 | import de.bsommerfeld.model.action.ActionType;
5 | import de.bsommerfeld.model.action.spi.ActionExecutor;
6 | import java.awt.*;
7 | import lombok.extern.slf4j.Slf4j;
8 |
9 | /**
10 | * Default implementation of the ActionExecutor interface. This class uses a Robot to execute
11 | * keyboard and mouse actions.
12 | */
13 | @Slf4j
14 | @Singleton
15 | public class DefaultActionExecutor implements ActionExecutor {
16 |
17 | private final Robot robot;
18 |
19 | /**
20 | * Creates a new DefaultActionExecutor with a Robot instance.
21 | *
22 | * @throws RuntimeException if the Robot cannot be created
23 | */
24 | public DefaultActionExecutor() {
25 | try {
26 | this.robot = new Robot();
27 | } catch (AWTException e) {
28 | log.error("Failed to create Robot instance", e);
29 | throw new RuntimeException("Failed to create Robot instance", e);
30 | }
31 | }
32 |
33 | @Override
34 | public void executeActionStart(int keyCode, ActionType actionType) {
35 | switch (actionType) {
36 | case MOUSE -> robot.mousePress(keyCode);
37 | case MOUSE_WHEEL -> robot.mouseWheel(keyCode);
38 | case KEYBOARD -> robot.keyPress(keyCode);
39 | default -> log.debug("No action to execute for key code: {}", keyCode);
40 | }
41 | }
42 |
43 | @Override
44 | public void executeActionEnd(int keyCode, ActionType actionType) {
45 | switch (actionType) {
46 | case MOUSE -> robot.mouseRelease(keyCode);
47 | case KEYBOARD -> robot.keyRelease(keyCode);
48 | default -> log.debug("No action to end for key code: {}", keyCode);
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/action/impl/DefaultFocusManager.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.model.action.impl;
2 |
3 | import com.google.inject.Singleton;
4 | import com.sun.jna.Native;
5 | import com.sun.jna.platform.win32.User32;
6 | import com.sun.jna.platform.win32.WinDef.HWND;
7 | import de.bsommerfeld.model.action.spi.FocusManager;
8 | import lombok.extern.slf4j.Slf4j;
9 |
10 | /**
11 | * Default implementation of the FocusManager interface.
12 | * This class checks if the Counter-Strike 2 window is currently in focus.
13 | */
14 | @Slf4j
15 | @Singleton
16 | public class DefaultFocusManager implements FocusManager {
17 |
18 | /**
19 | * Checks if the window currently in focus belongs to the "Counter-Strike 2" game.
20 | *
21 | * @return true if the window in focus is "Counter-Strike 2", false otherwise
22 | */
23 | @Override
24 | public boolean isApplicationWindowInFocus() {
25 | try {
26 | User32 user32 = User32.INSTANCE;
27 | HWND hwnd = user32.GetForegroundWindow();
28 |
29 | if (hwnd == null) {
30 | return false;
31 | }
32 |
33 | char[] windowText = new char[512];
34 | user32.GetWindowText(hwnd, windowText, 512);
35 | String wText = Native.toString(windowText);
36 |
37 | return wText.contains("Counter-Strike 2");
38 | } catch (UnsatisfiedLinkError e) {
39 | log.error("JNA is not properly set up", e);
40 | return false;
41 | } catch (Exception e) {
42 | log.error("Error while checking for CS2 focus", e);
43 | return false;
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/action/impl/MouseMoveAction.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.model.action.impl;
2 |
3 | import de.bsommerfeld.model.action.Action;
4 | import de.bsommerfeld.model.action.ActionKey;
5 | import de.bsommerfeld.model.config.keybind.KeyBind;
6 | import java.awt.*;
7 | import java.util.concurrent.ThreadLocalRandom;
8 | import lombok.extern.slf4j.Slf4j;
9 |
10 | @Slf4j
11 | public class MouseMoveAction extends Action {
12 |
13 | public MouseMoveAction() {
14 | super("Mouse move", ActionKey.of(KeyBind.EMPTY_KEY_BIND.getKey()));
15 | }
16 |
17 | @Override
18 | protected void performActionStart(int keycode) {
19 | try {
20 | Point startPosition = MouseInfo.getPointerInfo().getLocation();
21 | int startX = startPosition.x;
22 | int startY = startPosition.y;
23 |
24 | int maxDistance = actionConfig != null ? actionConfig.getMaxMouseMoveDistance() : 5000;
25 | int deltaX = ThreadLocalRandom.current().nextInt(-maxDistance, maxDistance + 1);
26 | int deltaY = ThreadLocalRandom.current().nextInt(-maxDistance, maxDistance + 1);
27 |
28 | int endX = startX + deltaX;
29 | int endY = startY + deltaY;
30 |
31 | Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
32 | endX = Math.max(0, Math.min(endX, screenSize.width - 1));
33 | endY = Math.max(0, Math.min(endY, screenSize.height - 1));
34 |
35 | log.debug("Moving mouse smoothly from ({}, {}) to ({}, {})", startX, startY, endX, endY);
36 | smoothMove(startX, startY, endX, endY);
37 |
38 | } catch (Exception e) {
39 | log.error("Error during smooth mouse move", e);
40 | }
41 | }
42 |
43 | @Override
44 | protected void performActionEnd(int keycode) {
45 | // No action required
46 | }
47 |
48 | private void smoothMove(int startX, int startY, int endX, int endY) {
49 | int steps = actionConfig != null ? actionConfig.getMouseMoveSteps() : 50;
50 | int delay = actionConfig != null ? actionConfig.getMouseMoveSmoothDelay() : 10;
51 |
52 | double dx = (endX - startX) / (double) steps;
53 | double dy = (endY - startY) / (double) steps;
54 |
55 | for (int step = 1; step <= steps; step++) {
56 | int x = (int) Math.round(startX + dx * step);
57 | int y = (int) Math.round(startY + dy * step);
58 |
59 | try {
60 | java.awt.Robot robot = new java.awt.Robot();
61 | robot.mouseMove(x, y);
62 | robot.delay(delay);
63 | } catch (Exception e) {
64 | log.error("Error during mouse move", e);
65 | }
66 | }
67 |
68 | try {
69 | java.awt.Robot robot = new java.awt.Robot();
70 | robot.mouseMove(endX, endY);
71 | } catch (Exception e) {
72 | log.error("Error during final mouse move", e);
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/action/impl/PauseAction.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.model.action.impl;
2 |
3 | import de.bsommerfeld.model.action.Action;
4 | import de.bsommerfeld.model.action.ActionKey;
5 | import de.bsommerfeld.model.config.keybind.KeyBind;
6 |
7 | /**
8 | * The PauseAction class represents an action that pauses execution for a random duration within a
9 | * specified interval.
10 | *
11 | * This class extends the Action class and leverages the functionality of performing
12 | * interruptible delays. No specific action is performed at the end of the pause duration.
13 | */
14 | public class PauseAction extends Action {
15 |
16 | public PauseAction() {
17 | super("Pause", ActionKey.of(KeyBind.EMPTY_KEY_BIND.getKey()));
18 | }
19 |
20 | @Override
21 | protected void performActionStart(int keycode) {
22 | // No action needed at start - the delay is handled by executeWithDelay()
23 | // Removing the duplicate performInterruptibleDelay call here
24 | }
25 |
26 | @Override
27 | protected void performActionEnd(int keycode) {
28 | // Since no specific action needs to be executed at the end of a pause,
29 | // this method remains empty.
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/action/repository/DefaultActionRepository.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.model.action.repository;
2 |
3 | import com.google.inject.Singleton;
4 | import de.bsommerfeld.model.action.Action;
5 | import de.bsommerfeld.model.action.spi.ActionRepository;
6 | import lombok.extern.slf4j.Slf4j;
7 |
8 | import java.util.LinkedHashMap;
9 | import java.util.Map;
10 |
11 | /**
12 | * Default implementation of the ActionRepository interface.
13 | * This class manages actions and their enabled/disabled state.
14 | */
15 | @Slf4j
16 | @Singleton
17 | public class DefaultActionRepository implements ActionRepository {
18 |
19 | private final Map actions = new LinkedHashMap<>();
20 |
21 | /**
22 | * Registers a new action in the repository by adding it to the internal map of actions.
23 | *
24 | * @param action The action to be registered and enabled.
25 | */
26 | @Override
27 | public void register(Action action) {
28 | actions.put(action, true);
29 | }
30 |
31 | /**
32 | * Checks if an action with the specified name exists in the repository.
33 | *
34 | * @param name The name of the action to check.
35 | * @return true if an action with the specified name exists, false otherwise.
36 | */
37 | @Override
38 | public boolean hasActionWithName(String name) {
39 | return actions.keySet().stream().anyMatch(action -> action.getName().equals(name));
40 | }
41 |
42 | /**
43 | * Unregisters the given action, removing it from the repository.
44 | *
45 | * @param action The action to be unregistered.
46 | */
47 | @Override
48 | public void unregister(Action action) {
49 | actions.remove(action);
50 | }
51 |
52 | /**
53 | * Enables the specified action.
54 | *
55 | * @param action The action to be enabled.
56 | */
57 | @Override
58 | public void enable(Action action) {
59 | actions.put(action, true);
60 | }
61 |
62 | /**
63 | * Disables the specified action.
64 | *
65 | * @param action The action to be disabled.
66 | */
67 | @Override
68 | public void disable(Action action) {
69 | actions.put(action, false);
70 | }
71 |
72 | /**
73 | * Checks if the specified action is currently enabled.
74 | *
75 | * @param action The action to check for its enabled state.
76 | * @return true if the action is enabled or if it is not found in the repository, false otherwise.
77 | */
78 | @Override
79 | public boolean isEnabled(Action action) {
80 | return actions.getOrDefault(action, true);
81 | }
82 |
83 | /**
84 | * Retrieves a map of registered actions along with their enabled/disabled state.
85 | *
86 | * @return a Map containing {@link Action} objects as keys and their corresponding Boolean state
87 | * indicating whether the action is enabled (true) or disabled (false).
88 | * @throws RuntimeException if cloning an action fails.
89 | */
90 | @Override
91 | public Map getActions() {
92 | Map actionsCopy = new LinkedHashMap<>();
93 | this.actions.forEach(
94 | (action, enabled) -> {
95 | try {
96 | actionsCopy.put(action.clone(), enabled);
97 | } catch (CloneNotSupportedException e) {
98 | throw new RuntimeException("Failed to clone action", e);
99 | }
100 | });
101 | return actionsCopy;
102 | }
103 |
104 | /**
105 | * Retrieves an Action by its name and returns a cloned copy of it.
106 | *
107 | * @param actionName the name of the action to retrieve
108 | * @return a cloned copy of the Action with the specified name
109 | * @throws IllegalArgumentException if no action with the specified name is found
110 | * @throws RuntimeException if the action cannot be cloned
111 | */
112 | @Override
113 | public Action getByName(String actionName) {
114 | Action originalAction =
115 | actions.keySet().stream()
116 | .filter(action -> action.getName().equals(actionName))
117 | .findFirst()
118 | .orElseThrow(() -> new IllegalArgumentException("No action found with name: " + actionName));
119 |
120 | try {
121 | return originalAction.clone();
122 | } catch (CloneNotSupportedException e) {
123 | throw new RuntimeException("Failed to clone action: " + actionName, e);
124 | }
125 | }
126 | }
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/action/sequence/ActionSequence.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.model.action.sequence;
2 |
3 | import de.bsommerfeld.model.action.Action;
4 | import java.util.ArrayList;
5 | import java.util.List;
6 | import java.util.stream.Collectors;
7 | import lombok.EqualsAndHashCode;
8 | import lombok.Getter;
9 | import lombok.Setter;
10 | import lombok.extern.slf4j.Slf4j;
11 |
12 | /**
13 | * The ActionSequence class represents a sequence of actions to be executed. It includes
14 | * functionalities to manage the actions, track the sequence's state, and provide additional context
15 | * about the sequence.
16 | */
17 | @Getter
18 | @Setter
19 | @Slf4j
20 | @EqualsAndHashCode(onlyExplicitlyIncluded = true)
21 | public class ActionSequence {
22 |
23 | /**
24 | * The name of the ActionSequence instance. This field is included in the equals and hashCode
25 | * methods for comparison.
26 | */
27 | @EqualsAndHashCode.Include private final String name;
28 |
29 | /**
30 | * A list that holds the actions to be executed within the action sequence. This list is final and
31 | * cannot be modified directly, ensuring the integrity of the action sequence. Actions should be
32 | * set or modified using the relevant methods provided in the {@link ActionSequence} class.
33 | */
34 | private final List actions = new ArrayList<>();
35 |
36 | /**
37 | * Indicates whether the ActionSequence is currently active.
38 | *
39 | * If true, the sequence of actions defined in the ActionSequence can be executed; otherwise,
40 | * it will not be executed.
41 | */
42 | boolean active = true;
43 |
44 | /**
45 | * Indicates whether the ActionSequence has been interrupted.
46 | *
47 | *
If true, the sequence execution should be stopped as soon as possible. -- GETTER -- Checks
48 | * if this ActionSequence is interrupted.
49 | *
50 | * @return true if the sequence is interrupted, false otherwise
51 | */
52 | private volatile boolean interrupted = false;
53 |
54 | /**
55 | * Provides a textual description of the ActionSequence. This is intended to offer additional
56 | * context or details about the sequence of actions.
57 | */
58 | private String description = "No description provided";
59 |
60 | public ActionSequence(String name) {
61 | this.name = name;
62 | }
63 |
64 | /**
65 | * Sets the actions for the ActionSequence. Clears any existing actions and adds all the actions
66 | * from the provided list.
67 | *
68 | * @param actions the list of actions to set
69 | */
70 | public void setActions(List actions) {
71 | this.actions.clear();
72 | this.actions.addAll(actions);
73 | }
74 |
75 | /**
76 | * Interrupts this ActionSequence and all its actions. This will stop the execution of the
77 | * sequence as soon as possible.
78 | */
79 | public void interrupt() {
80 | this.interrupted = true;
81 | actions.forEach(Action::interrupt);
82 | log.info("ActionSequence '{}' has been interrupted", name);
83 | }
84 |
85 | /**
86 | * Interrupts this ActionSequence and all its actions immediately. This will immediately stop all
87 | * actions in the sequence.
88 | */
89 | public void instantInterrupt() {
90 | this.interrupted = true;
91 | actions.forEach(Action::instantInterrupt);
92 | log.info("ActionSequence '{}' has been immediately interrupted", name);
93 | }
94 |
95 | /** Resets the interrupted state of this ActionSequence and all its actions. */
96 | public void resetInterrupted() {
97 | this.interrupted = false;
98 | actions.forEach(Action::normalize);
99 | log.info("ActionSequence '{}' interrupted state has been reset", name);
100 | }
101 |
102 | @Override
103 | public String toString() {
104 | String eventsString = actions.stream().map(Action::toString).collect(Collectors.joining(", "));
105 | return "ActionSequence{name='" + name + "', actions=[" + eventsString + "]}";
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/action/spi/ActionExecutor.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.model.action.spi;
2 |
3 | import de.bsommerfeld.model.action.ActionType;
4 |
5 | /**
6 | * Interface for executing actions. This interface defines the contract for classes that can execute
7 | * actions like keyboard, mouse, or custom actions.
8 | */
9 | public interface ActionExecutor {
10 |
11 | /**
12 | * Executes the start of an action with the specified key code and action type.
13 | *
14 | * @param keyCode the code of the key to be pressed or the identifier for the action
15 | * @param actionType the type of the action to be executed, represented by an {@link ActionType}
16 | */
17 | void executeActionStart(int keyCode, ActionType actionType);
18 |
19 | /**
20 | * Executes the end of an action with the specified key code.
21 | *
22 | * @param keyCode the code of the key to be released or action to be ended
23 | * @param actionType the type of the action to be ended, represented by an {@link ActionType}
24 | */
25 | void executeActionEnd(int keyCode, ActionType actionType);
26 | }
27 |
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/action/spi/ActionRepository.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.model.action.spi;
2 |
3 | import de.bsommerfeld.model.action.Action;
4 |
5 | import java.util.Map;
6 |
7 | /**
8 | * Interface for repositories that manage actions.
9 | * This interface defines the contract for classes that store and retrieve actions.
10 | */
11 | public interface ActionRepository {
12 |
13 | /**
14 | * Registers a new action in the repository.
15 | *
16 | * @param action The action to be registered and enabled.
17 | */
18 | void register(Action action);
19 |
20 | /**
21 | * Checks if an action with the specified name exists in the repository.
22 | *
23 | * @param name The name of the action to check.
24 | * @return true if an action with the specified name exists, false otherwise.
25 | */
26 | boolean hasActionWithName(String name);
27 |
28 | /**
29 | * Unregisters the given action, removing it from the repository.
30 | *
31 | * @param action The action to be unregistered.
32 | */
33 | void unregister(Action action);
34 |
35 | /**
36 | * Enables the specified action.
37 | *
38 | * @param action The action to be enabled.
39 | */
40 | void enable(Action action);
41 |
42 | /**
43 | * Disables the specified action.
44 | *
45 | * @param action The action to be disabled.
46 | */
47 | void disable(Action action);
48 |
49 | /**
50 | * Checks if the specified action is currently enabled.
51 | *
52 | * @param action The action to check for its enabled state.
53 | * @return true if the action is enabled or if it is not found in the repository, false otherwise.
54 | */
55 | boolean isEnabled(Action action);
56 |
57 | /**
58 | * Retrieves a map of registered actions along with their enabled/disabled state.
59 | *
60 | * @return a Map containing {@link Action} objects as keys and their corresponding Boolean state
61 | * indicating whether the action is enabled (true) or disabled (false).
62 | */
63 | Map getActions();
64 |
65 | /**
66 | * Retrieves an Action by its name and returns a cloned copy of it.
67 | *
68 | * @param actionName the name of the action to retrieve
69 | * @return a cloned copy of the Action with the specified name
70 | * @throws IllegalArgumentException if no action with the specified name is found
71 | */
72 | Action getByName(String actionName);
73 | }
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/action/spi/ActionSequenceDispatcher.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.model.action.spi;
2 |
3 | import de.bsommerfeld.model.action.Action;
4 | import de.bsommerfeld.model.action.sequence.ActionSequence;
5 |
6 | import java.util.function.Consumer;
7 |
8 | /**
9 | * Interface for dispatching actions and action sequences.
10 | * This interface defines the contract for classes that dispatch actions and action sequences to handlers.
11 | */
12 | public interface ActionSequenceDispatcher {
13 |
14 | /**
15 | * Redispatches an action with a specified delay.
16 | *
17 | * @param action the action to be redispatched
18 | * @param remainingTime the delay in milliseconds before the action is processed
19 | */
20 | void redispatch(Action action, long remainingTime);
21 |
22 | /**
23 | * Dispatches an ActionSequence to registered handlers and processes each contained Action.
24 | *
25 | * @param actionSequence the ActionSequence to be dispatched
26 | */
27 | void dispatchSequence(ActionSequence actionSequence);
28 |
29 | /**
30 | * Discards all running actions.
31 | */
32 | void discardAllRunningActions();
33 |
34 | /**
35 | * Registers a handler to process any finished action.
36 | *
37 | * @param handler the Consumer to handle finished actions
38 | */
39 | void registerActionFinishHandler(Consumer handler);
40 |
41 | /**
42 | * Registers a handler to process any finished action sequence.
43 | *
44 | * @param handler the Consumer to handle finished action sequences
45 | */
46 | void registerSequenceFinishHandler(Consumer handler);
47 |
48 | /**
49 | * Registers a generic handler for Action events.
50 | *
51 | * @param handler the Consumer to process Action events
52 | */
53 | void registerActionHandler(Consumer handler);
54 |
55 | /**
56 | * Registers a generic handler for ActionSequence events.
57 | *
58 | * @param handler the Consumer to process ActionSequence events
59 | */
60 | void registerSequenceHandler(Consumer handler);
61 | }
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/action/spi/ActionSequenceExecutor.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.model.action.spi;
2 |
3 | /**
4 | * Interface for executing action sequences.
5 | * This interface defines the contract for classes that execute action sequences.
6 | */
7 | public interface ActionSequenceExecutor extends Runnable {
8 |
9 | /**
10 | * Sets the minimum wait time between action sequence executions.
11 | *
12 | * @param minWaitTime the minimum wait time in seconds
13 | */
14 | void setMinWaitTime(int minWaitTime);
15 |
16 | /**
17 | * Sets the maximum wait time between action sequence executions.
18 | *
19 | * @param maxWaitTime the maximum wait time in seconds
20 | */
21 | void setMaxWaitTime(int maxWaitTime);
22 |
23 | /**
24 | * Starts the executor in a new thread.
25 | *
26 | * @return the thread in which the executor is running
27 | */
28 | Thread start();
29 |
30 | /**
31 | * Stops the executor.
32 | */
33 | void stop();
34 | }
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/action/spi/ActionSequenceRepository.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.model.action.spi;
2 |
3 | import de.bsommerfeld.model.action.sequence.ActionSequence;
4 |
5 | import java.util.List;
6 | import java.util.Optional;
7 |
8 | /**
9 | * Interface for repositories that manage action sequences.
10 | * This interface defines the contract for classes that store and retrieve action sequences.
11 | */
12 | public interface ActionSequenceRepository {
13 |
14 | /**
15 | * Saves an action sequence to the storage and updates the cache.
16 | *
17 | * @param actionSequence The action sequence to be saved. It must not be null.
18 | * @throws NullPointerException if the action sequence is null
19 | */
20 | void saveActionSequence(ActionSequence actionSequence);
21 |
22 | /**
23 | * Deletes the given action sequence from the system.
24 | *
25 | * @param actionSequence the action sequence to be deleted; must not be null
26 | * @throws NullPointerException if the action sequence is null
27 | */
28 | void deleteActionSequence(ActionSequence actionSequence);
29 |
30 | /**
31 | * Adds an ActionSequence to the internal storage, ensuring no duplicate entries.
32 | *
33 | * @param actionSequence the ActionSequence to be added; must not be null
34 | * @throws NullPointerException if the action sequence is null
35 | */
36 | void addActionSequence(ActionSequence actionSequence);
37 |
38 | /**
39 | * Removes an action sequence from the storage map by its name.
40 | *
41 | * @param name the name of the action sequence to be removed
42 | */
43 | void removeActionSequence(String name);
44 |
45 | /**
46 | * Updates the action sequences cache if it is not already updated.
47 | */
48 | void updateActionSequencesCache();
49 |
50 | /**
51 | * Retrieves the action sequence associated with the given name.
52 | *
53 | * @param name the name of the action sequence to retrieve
54 | * @return an Optional containing the ActionSequence if found, otherwise an empty Optional
55 | */
56 | Optional getActionSequence(String name);
57 |
58 | /**
59 | * Retrieves a list of action sequences from the internal map.
60 | *
61 | * @return A list containing all action sequences from the map.
62 | */
63 | List getActionSequences();
64 | }
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/action/spi/FocusManager.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.model.action.spi;
2 |
3 | /**
4 | * Interface for managing application focus.
5 | * This interface defines the contract for classes that check if the application window is in focus.
6 | */
7 | public interface FocusManager {
8 |
9 | /**
10 | * Checks if the application window is in focus.
11 | *
12 | * @return true if the application window is in focus, false otherwise
13 | */
14 | boolean isApplicationWindowInFocus();
15 | }
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/action/spi/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Service Provider Interfaces (SPIs) for the action execution framework.
3 | *
4 | * This package defines the core abstractions and contracts for action management, execution, and
5 | * persistence within the randomizer application. These SPIs enable modular architecture and allow
6 | * for different implementations to be plugged in at runtime.
7 | *
8 | *
The primary SPIs include:
9 | *
10 | *
11 | * {@link de.bsommerfeld.model.action.spi.ActionExecutor} - Executes individual actions
12 | * {@link de.bsommerfeld.model.action.spi.ActionSequenceExecutor} - Handles execution of
13 | * action sequences
14 | * {@link de.bsommerfeld.model.action.spi.ActionSequenceDispatcher} - Manages and dispatches
15 | * action sequences
16 | * {@link de.bsommerfeld.model.action.spi.ActionRepository} - Provides CRUD operations for
17 | * actions
18 | * {@link de.bsommerfeld.model.action.spi.ActionSequenceRepository} - Manages action sequence
19 | * persistence
20 | * {@link de.bsommerfeld.model.action.spi.FocusManager} - Handles application focus and window
21 | * management
22 | *
23 | *
24 | * These interfaces follow the SPI pattern to enable loose coupling between the core domain logic
25 | * and specific implementations. Implementations can be swapped or extended without modifying the
26 | * core business logic.
27 | *
28 | *
Design Benefits:
29 | *
30 | *
31 | * Testability through mock implementations
32 | * Extensibility for new action types
33 | * Platform-specific implementations
34 | * Clear separation of concerns
35 | *
36 | *
37 | * @since 1.0
38 | * @author BSommerfeld
39 | */
40 | package de.bsommerfeld.model.action.spi;
41 |
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/action/value/Interval.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.model.action.value;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Getter;
5 | import lombok.Setter;
6 | import lombok.ToString;
7 |
8 | /** The Interval class represents a range between a minimum and maximum value. */
9 | @Getter
10 | @Setter
11 | @AllArgsConstructor(staticName = "of")
12 | @ToString
13 | public class Interval {
14 |
15 | private int min;
16 | private int max;
17 |
18 | /**
19 | * Checks if the interval is empty, i.e., both minimum and maximum values are zero.
20 | *
21 | * @return true if the interval is empty, false otherwise
22 | */
23 | public boolean isEmpty() {
24 | return min == 0 && max == 0;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/config/ConfigLoader.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.model.config;
2 |
3 | import de.bsommerfeld.model.config.keybind.KeyBindRepository;
4 | import java.io.File;
5 | import java.io.FileNotFoundException;
6 | import lombok.extern.slf4j.Slf4j;
7 |
8 | @Slf4j
9 | public class ConfigLoader {
10 |
11 | private static final String[] STEAM_PATHS = {
12 | "C:/Program Files (x86)/Steam",
13 | "C:/Program Files/Steam",
14 | "C:/Steam",
15 | "D:/SteamLibrary",
16 | "D:/Program Files (x86)/Steam",
17 | "D:/Steam",
18 | "D:/Games/Steam"
19 | };
20 |
21 | public static void loadDefaultKeyBinds(String configPath, KeyBindRepository keyBindRepository)
22 | throws FileNotFoundException {
23 | File defaultConfigFile = new File(configPath);
24 | if (!defaultConfigFile.exists()) {
25 | throw new FileNotFoundException("Standardkonfigurationsdatei nicht gefunden.");
26 | }
27 | if (configPath != null) {
28 | log.info("Lade Standard-Key-Bindings von: {}", configPath);
29 | keyBindRepository.initDefaults(configPath);
30 | } else {
31 | throw new FileNotFoundException("Standardkonfigurationsdatei nicht gefunden.");
32 | }
33 | }
34 |
35 | public static void loadUserKeyBindings(String configPath, KeyBindRepository keyBindRepository) {
36 | File userConfigFile = new File(configPath);
37 | if (!userConfigFile.exists()) {
38 | log.info("Keine Benutzer-Key-Bindings gefunden.");
39 | return;
40 | }
41 | if (configPath != null) {
42 | log.info("Lade Benutzer-Key-Bindings von: {}", configPath);
43 | keyBindRepository.initModifiedKeyBinds(configPath);
44 | } else {
45 | log.info("Keine Benutzer-Key-Bindings gefunden.");
46 | }
47 | }
48 |
49 | public static String findDefaultConfigFile() {
50 | for (String steamPath : STEAM_PATHS) {
51 | File defaultConfig =
52 | new File(
53 | steamPath
54 | + "/steamapps/common/Counter-Strike Global Offensive/game/csgo/cfg/user_keys_default.vcfg");
55 | if (defaultConfig.exists()) {
56 | return defaultConfig.getAbsolutePath();
57 | }
58 | }
59 | throw new IllegalStateException("Standardkonfigurationsdatei nicht gefunden.");
60 | }
61 |
62 | public static String findUserConfigFile() {
63 | for (String steamPath : STEAM_PATHS) {
64 | File userdataFolder = new File(steamPath + "/userdata");
65 | if (userdataFolder.exists()) {
66 | File[] userDirs = listUserDirectories(userdataFolder);
67 | if (userDirs != null) {
68 | String configFilePath = searchInUserDirectories(userDirs);
69 | if (configFilePath != null) {
70 | return configFilePath;
71 | }
72 | }
73 | }
74 | }
75 | throw new IllegalStateException("Keine Benutzerkonfigurationsdatei gefunden.");
76 | }
77 |
78 | private static File[] listUserDirectories(File userdataFolder) {
79 | return userdataFolder.listFiles(File::isDirectory);
80 | }
81 |
82 | private static String searchInUserDirectories(File[] userDirs) {
83 | for (File userDir : userDirs) {
84 | File gameFolder = new File(userDir, "730/remote/cs2_user_keys.vcfg");
85 | if (gameFolder.exists()) {
86 | return gameFolder.getAbsolutePath();
87 | }
88 | }
89 | return null;
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/config/keybind/KeyBind.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.model.config.keybind;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.EqualsAndHashCode;
5 | import lombok.Getter;
6 | import lombok.Setter;
7 | import lombok.ToString;
8 |
9 | @AllArgsConstructor
10 | @Setter
11 | @Getter
12 | @EqualsAndHashCode(of = "key")
13 | @ToString
14 | public class KeyBind {
15 |
16 | /**
17 | * A KeyBind object representing an unbound key action.
18 | *
19 | * This constant is used to signify that no key is assigned to a specific action. The key is
20 | * set to "" and the action is set to "none".
21 | */
22 | public static final KeyBind EMPTY_KEY_BIND = new KeyBind("", "none");
23 |
24 | /**
25 | * The key associated with a specific action in the KeyBind configuration. This string represents the actual
26 | * keybinding that triggers the corresponding action.
27 | */
28 | private String key;
29 | /**
30 | * Represents the action associated with a specific key binding. This variable holds the action name or description
31 | * that is triggered when the corresponding key is pressed.
32 | */
33 | private String action;
34 | }
35 |
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/config/keybind/KeyBindNameTypeMapper.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.model.config.keybind;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 |
5 | import java.util.HashMap;
6 | import java.util.Map;
7 |
8 | @Slf4j
9 | public class KeyBindNameTypeMapper {
10 |
11 | private final Map descriptorToNameMap = new HashMap<>();
12 |
13 | public KeyBindNameTypeMapper() {
14 | initializeDescriptorMappings();
15 | }
16 |
17 | /**
18 | * Checks if the given descriptor exists in the descriptor-to-name map.
19 | *
20 | * @param descriptor the key to be checked in the map
21 | *
22 | * @return true if the map contains the key; false otherwise
23 | */
24 | public boolean hasKey(String descriptor) {
25 | return descriptorToNameMap.containsKey(descriptor);
26 | }
27 |
28 | /**
29 | * Retrieves the key name associated with the given descriptor. If the descriptor does not have a predefined key
30 | * name, the descriptor itself is returned.
31 | *
32 | * @param descriptor The descriptor whose key name is to be retrieved.
33 | *
34 | * @return The key name associated with the given descriptor, or the descriptor itself if no key name is found.
35 | */
36 | public String getKeyName(String descriptor) {
37 | if (!hasKey(descriptor)) {
38 | return descriptor;
39 | }
40 | NameType nameType = descriptorToNameMap.get(descriptor);
41 | return nameType.name();
42 | }
43 |
44 | /**
45 | * Retrieves the {@link KeyBindType} associated with the given descriptor.
46 | *
47 | * @param descriptor the key used to retrieve the associated Type
48 | *
49 | * @return the Type associated with the given descriptor
50 | *
51 | * @throws IllegalStateException if the given descriptor does not have an associated Type
52 | */
53 | public KeyBindType getType(String descriptor) {
54 | if (!hasKey(descriptor)) {
55 | return KeyBindType.MISCELLANEOUS;
56 | }
57 | NameType nameType = descriptorToNameMap.get(descriptor);
58 | return nameType.type();
59 | }
60 |
61 | /**
62 | * Retrieves the KeyBindType by its name.
63 | *
64 | * @param name the name of the KeyBindType to be retrieved
65 | *
66 | * @return the KeyBindType corresponding to the provided name, or KeyBindType.MISCELLANEOUS if no matching type is
67 | * found
68 | */
69 | public KeyBindType getTypeByName(String name) {
70 | for (NameType nameType : descriptorToNameMap.values()) {
71 | if (nameType.name().equals(name)) {
72 | return nameType.type();
73 | }
74 | }
75 | return KeyBindType.MISCELLANEOUS;
76 | }
77 |
78 | /**
79 | * Retrieves an immutable copy of the descriptor-to-name map.
80 | *
81 | * @return an unmodifiable map where keys are descriptors and values are name types.
82 | */
83 | public Map getDescriptorToNameMap() {
84 | return Map.copyOf(descriptorToNameMap);
85 | }
86 |
87 | private void initializeDescriptorMappings() {
88 | descriptorToNameMap.put("+jump", new NameType("Jump", KeyBindType.MOVEMENT));
89 | descriptorToNameMap.put("+left", new NameType("Move Left", KeyBindType.MOVEMENT));
90 | descriptorToNameMap.put("+right", new NameType("Move Right", KeyBindType.MOVEMENT));
91 | descriptorToNameMap.put("+back", new NameType("Move Backward", KeyBindType.MOVEMENT));
92 | descriptorToNameMap.put("+forward", new NameType("Move Forward", KeyBindType.MOVEMENT));
93 | descriptorToNameMap.put("+duck", new NameType("Crouch", KeyBindType.MOVEMENT));
94 | descriptorToNameMap.put("+sprint", new NameType("Sprint", KeyBindType.MOVEMENT));
95 |
96 | descriptorToNameMap.put("slot1", new NameType("Select Slot 1", KeyBindType.INVENTORY));
97 | descriptorToNameMap.put("slot2", new NameType("Select Slot 2", KeyBindType.INVENTORY));
98 | descriptorToNameMap.put("slot3", new NameType("Select Slot 3", KeyBindType.INVENTORY));
99 | descriptorToNameMap.put("slot4", new NameType("Select Slot 4", KeyBindType.INVENTORY));
100 |
101 | descriptorToNameMap.put("drop", new NameType("Drop Item", KeyBindType.WEAPON));
102 | descriptorToNameMap.put("+reload", new NameType("Reload", KeyBindType.WEAPON));
103 | descriptorToNameMap.put("+attack", new NameType("Primary Attack", KeyBindType.WEAPON));
104 | descriptorToNameMap.put("+attack2", new NameType("Secondary Attack", KeyBindType.WEAPON));
105 |
106 | descriptorToNameMap.put("player_ping", new NameType("Player Ping", KeyBindType.MISCELLANEOUS));
107 | descriptorToNameMap.put("+showscores", new NameType("Scoreboard", KeyBindType.MISCELLANEOUS));
108 |
109 | log.info("{} Deskriptoren initialisiert.", descriptorToNameMap.size());
110 | }
111 |
112 | public record NameType(String name, KeyBindType type) {
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/config/keybind/KeyBindRepository.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.model.config.keybind;
2 |
3 | import com.google.inject.Inject;
4 | import lombok.extern.slf4j.Slf4j;
5 |
6 | import java.io.File;
7 | import java.io.FileNotFoundException;
8 | import java.util.ArrayList;
9 | import java.util.List;
10 | import java.util.Optional;
11 | import java.util.Scanner;
12 |
13 | @Slf4j
14 | public class KeyBindRepository {
15 |
16 | private static final String UNBOUND = "";
17 | private final List keyBinds = new ArrayList<>();
18 |
19 | private final KeyBindNameTypeMapper keyBindNameTypeMapper;
20 |
21 | private String defaultFilePath;
22 | private String userConfigFilePath;
23 |
24 | @Inject
25 | public KeyBindRepository(KeyBindNameTypeMapper keyBindNameTypeMapper) {
26 | this.keyBindNameTypeMapper = keyBindNameTypeMapper;
27 | }
28 |
29 | /**
30 | * Retrieves a KeyBind associated with the given key.
31 | *
32 | * @param key The key to search for in the key bindings.
33 | *
34 | * @return An Optional containing the KeyBind if found, otherwise an empty Optional.
35 | */
36 | public Optional getKeyBind(String key) {
37 | return keyBinds.stream().filter(bind -> bind.getKey().equals(key)).findFirst();
38 | }
39 |
40 | /**
41 | * Determines whether there are any key bindings configured.
42 | *
43 | * @return true if there are one or more key bindings present; false otherwise.
44 | */
45 | public boolean hasAnyKeyBinds() {
46 | return !keyBinds.isEmpty();
47 | }
48 |
49 | /**
50 | * Initializes the default key bindings from a specified configuration file.
51 | *
52 | * @param filePath the path to the configuration file containing the default key bindings
53 | */
54 | public void initDefaults(String filePath) {
55 | this.defaultFilePath = filePath;
56 | log.info("Initialisiere Standard-KeyBinds von Datei: {}", filePath);
57 | loadKeyBinds(filePath);
58 | }
59 |
60 | /**
61 | * Initializes modified key bindings from a specified configuration file.
62 | *
63 | * @param filePath the path to the configuration file containing the modified key bindings
64 | */
65 | public void initModifiedKeyBinds(String filePath) {
66 | this.userConfigFilePath = filePath;
67 | log.info("Initialisiere modifizierte KeyBinds von Datei: {}", filePath);
68 | loadKeyBinds(filePath);
69 | }
70 |
71 | private void loadKeyBinds(String filePath) {
72 | try (Scanner scanner = new Scanner(new File(filePath))) {
73 | while (scanner.hasNextLine()) {
74 | String line = scanner.nextLine().trim();
75 | processLine(line);
76 | }
77 | log.info("Erfolgreich {} KeyBinds geladen", keyBinds.size());
78 | } catch (FileNotFoundException e) {
79 | log.error("Konfigurationsdatei nicht gefunden: {}", filePath, e);
80 | throw new RuntimeException(e);
81 | }
82 | }
83 |
84 | private void processLine(String line) {
85 | if (line.isEmpty() || line.startsWith("//")) {
86 | return;
87 | }
88 | if (line.contains("\"")) {
89 | String[] parts = line.split("\"");
90 | if (parts.length >= 4) {
91 | handleKeyBind(parts[1], parts[3]);
92 | }
93 | }
94 | }
95 |
96 | private void handleKeyBind(String key, String descriptor) {
97 | if (UNBOUND.equals(descriptor)) {
98 | keyBinds.removeIf(bind -> bind.getKey().equals(key));
99 | } else if (keyBindNameTypeMapper.hasKey(descriptor)) {
100 | String keyName = keyBindNameTypeMapper.getKeyName(descriptor);
101 | addOrUpdateKeyBind(key, keyName);
102 | }
103 | }
104 |
105 | private void addOrUpdateKeyBind(String key, String actionName) {
106 | KeyBind newKeyBind = new KeyBind(key, actionName);
107 | keyBinds.removeIf(keyBind -> keyBind.getKey().equals(key));
108 | keyBinds.add(newKeyBind);
109 | }
110 |
111 | /**
112 | * Reloads the key bindings by clearing the current set and reinitializing them from default and user configuration
113 | * files. If the default file path has not been set, an IllegalStateException will be thrown.
114 | *
115 | * @throws IllegalStateException if the key binding repository has not been initialized with a default file path.
116 | */
117 | public void reloadBinds() {
118 | keyBinds.clear();
119 | if (defaultFilePath == null) {
120 | throw new IllegalStateException("KeyBindRepository wurde nicht initialisiert");
121 | }
122 | initDefaults(defaultFilePath);
123 | if (userConfigFilePath != null) {
124 | initModifiedKeyBinds(userConfigFilePath);
125 | }
126 | log.info("KeyBinds wurden erfolgreich neu geladen");
127 | }
128 |
129 | public List getKeyBinds() {
130 | return new ArrayList<>(keyBinds);
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/config/keybind/KeyBindType.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.model.config.keybind;
2 |
3 | /** KeyBindType is an enumeration that categorizes different types of key bindings. */
4 | public enum KeyBindType {
5 | MOVEMENT,
6 | WEAPON,
7 | INVENTORY,
8 | MISCELLANEOUS
9 | }
10 |
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/exception/UncaughtExceptionLogger.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.model.exception;
2 |
3 | import de.bsommerfeld.model.ApplicationContext;
4 | import lombok.extern.slf4j.Slf4j;
5 |
6 | import java.io.File;
7 |
8 | @Slf4j
9 | public class UncaughtExceptionLogger implements Thread.UncaughtExceptionHandler {
10 |
11 | public static final UncaughtExceptionLogger DEFAULT_UNCAUGHT_EXCEPTION_LOGGER =
12 | new UncaughtExceptionLogger();
13 |
14 | private static final File LOG_FOLDER =
15 | new File(ApplicationContext.getAppdataFolder() + File.separator + "logs");
16 |
17 | static {
18 | LOG_FOLDER.mkdirs();
19 | }
20 |
21 | @Override
22 | public void uncaughtException(Thread thread, Throwable throwable) {
23 | String exceptionMessage = buildExceptionMessage(thread, throwable);
24 | log.error("Ein Fehler ist aufgetreten: {}", exceptionMessage);
25 | }
26 |
27 | private String buildExceptionMessage(Thread thread, Throwable throwable) {
28 | StringBuilder exceptionMessageBuilder =
29 | new StringBuilder()
30 | .append("Uncaught exception in thread \"")
31 | .append(thread.getName())
32 | .append("\": ")
33 | .append(throwable.getClass().getSimpleName())
34 | .append(" - ")
35 | .append(throwable.getMessage())
36 | .append("\n");
37 |
38 | for (StackTraceElement element : throwable.getStackTrace()) {
39 | exceptionMessageBuilder.append("\tat ").append(element.toString()).append("\n");
40 | }
41 |
42 | if (throwable.getCause() != null) {
43 | exceptionMessageBuilder.append("Caused by: ").append(throwable.getCause()).append("\n");
44 | }
45 | return exceptionMessageBuilder.toString();
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/messages/Messages.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.model.messages;
2 |
3 | import lombok.experimental.UtilityClass;
4 | import lombok.extern.slf4j.Slf4j;
5 |
6 | import java.io.BufferedReader;
7 | import java.io.IOException;
8 | import java.io.InputStream;
9 | import java.io.InputStreamReader;
10 | import java.nio.charset.StandardCharsets;
11 | import java.util.HashMap;
12 | import java.util.Map;
13 |
14 | @UtilityClass
15 | @Slf4j
16 | public final class Messages {
17 |
18 | private static final Map MESSAGES_MAP = new HashMap<>();
19 |
20 | public static void cache() {
21 | try (InputStream inputStream =
22 | Messages.class.getClassLoader().getResourceAsStream("messages.properties")) {
23 |
24 | if (inputStream == null)
25 | throw new IllegalStateException("Corrupted jar - messages.properties is missing");
26 |
27 | BufferedReader bufferedReader =
28 | new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
29 | bufferedReader
30 | .lines()
31 | .forEach(
32 | line -> {
33 | String[] lineSplit = line.split("=", 2);
34 | MESSAGES_MAP.put(lineSplit[0], lineSplit[1]);
35 | });
36 | } catch (IOException e) {
37 | log.error("Failed to cache messages", e);
38 | }
39 | }
40 |
41 | public static String getMessage(String key) {
42 | return MESSAGES_MAP.get(key);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/persistence/GsonProvider.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.model.persistence;
2 |
3 | import com.google.gson.Gson;
4 | import com.google.gson.GsonBuilder;
5 | import com.google.inject.Inject;
6 | import com.google.inject.Provider;
7 | import de.bsommerfeld.model.action.Action;
8 | import de.bsommerfeld.model.action.sequence.ActionSequence;
9 | import de.bsommerfeld.model.persistence.de_serializer.ActionJsonDeSerializer;
10 | import de.bsommerfeld.model.persistence.de_serializer.ActionSequenceJsonDeSerializer;
11 |
12 | public class GsonProvider implements Provider {
13 |
14 | private final ActionJsonDeSerializer actionJsonDeSerializer;
15 | private final ActionSequenceJsonDeSerializer actionSequenceJsonDeSerializer;
16 |
17 | @Inject
18 | public GsonProvider(
19 | ActionJsonDeSerializer actionJsonDeSerializer,
20 | ActionSequenceJsonDeSerializer actionSequenceJsonDeSerializer) {
21 | this.actionJsonDeSerializer = actionJsonDeSerializer;
22 | this.actionSequenceJsonDeSerializer = actionSequenceJsonDeSerializer;
23 | }
24 |
25 | @Override
26 | public Gson get() {
27 | return new GsonBuilder()
28 | .registerTypeAdapter(Action.class, actionJsonDeSerializer)
29 | .registerTypeAdapter(ActionSequence.class, actionSequenceJsonDeSerializer)
30 | .create();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/persistence/JsonUtil.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.model.persistence;
2 |
3 | import com.google.gson.Gson;
4 | import com.google.inject.Inject;
5 | import de.bsommerfeld.model.action.Action;
6 | import de.bsommerfeld.model.action.sequence.ActionSequence;
7 |
8 | public class JsonUtil {
9 |
10 | private final Gson gson;
11 |
12 | @Inject
13 | public JsonUtil(Gson gson) {
14 | this.gson = gson;
15 | }
16 |
17 | /**
18 | * Serializes the given ActionSequence object into its JSON representation.
19 | *
20 | * @param actionSequence the ActionSequence object to be serialized
21 | *
22 | * @return the JSON representation of the given ActionSequence object
23 | */
24 | public String serialize(Object actionSequence) {
25 | return gson.toJson(actionSequence);
26 | }
27 |
28 | /**
29 | * Deserializes a JSON string into an {@link ActionSequence} object.
30 | *
31 | * @param json The JSON string to be deserialized.
32 | *
33 | * @return The {@link ActionSequence} object represented by the JSON string.
34 | */
35 | public ActionSequence deserializeActionSequence(String json) {
36 | return gson.fromJson(json, ActionSequence.class);
37 | }
38 |
39 | /**
40 | * Deserializes a JSON string into an {@link Action} object.
41 | *
42 | * @param json The JSON string to be deserialized.
43 | *
44 | * @return The {@link Action} object represented by the JSON string.
45 | */
46 | public Action deserializeAction(String json) {
47 | return gson.fromJson(json, Action.class);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/persistence/de_serializer/ActionJsonDeSerializer.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.model.persistence.de_serializer;
2 |
3 | import com.google.gson.JsonDeserializationContext;
4 | import com.google.gson.JsonDeserializer;
5 | import com.google.gson.JsonElement;
6 | import com.google.gson.JsonObject;
7 | import com.google.gson.JsonParseException;
8 | import com.google.gson.JsonSerializationContext;
9 | import com.google.gson.JsonSerializer;
10 | import com.google.inject.Inject;
11 | import de.bsommerfeld.model.action.Action;
12 | import de.bsommerfeld.model.action.spi.ActionRepository;
13 | import de.bsommerfeld.model.action.value.Interval;
14 | import java.lang.reflect.Type;
15 | import lombok.extern.slf4j.Slf4j;
16 |
17 | @Slf4j
18 | public class ActionJsonDeSerializer implements JsonSerializer, JsonDeserializer {
19 |
20 | private static final String NAME_KEY = "name";
21 | private static final String INTERVAL_KEY = "interval";
22 |
23 | private final ActionRepository actionRepository;
24 |
25 | @Inject
26 | public ActionJsonDeSerializer(ActionRepository actionRepository) {
27 | this.actionRepository = actionRepository;
28 | }
29 |
30 | private static void validateAction(String actionName, Action action) {
31 | if (action == null) {
32 | throw new JsonParseException("No Action found with the name: " + actionName);
33 | }
34 | }
35 |
36 | @Override
37 | public JsonElement serialize(Action action, Type type, JsonSerializationContext context) {
38 | JsonObject jsonObject = new JsonObject();
39 | jsonObject.addProperty(NAME_KEY, action.getName());
40 | jsonObject.add(INTERVAL_KEY, context.serialize(action.getInterval()));
41 | return jsonObject;
42 | }
43 |
44 | @Override
45 | public Action deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext context)
46 | throws JsonParseException {
47 | JsonObject jsonObject = jsonElement.getAsJsonObject();
48 | String actionName = jsonObject.get(NAME_KEY).getAsString();
49 |
50 | try {
51 | return assembleAction(jsonObject, actionName, context);
52 | } catch (Exception exception) {
53 | log.error(
54 | "Failed to deserialize Action: {}. Diese spezielle Aktion wird übersprungen.",
55 | actionName,
56 | exception);
57 | // Rückgabe von null, damit der fehlerhafte Eintrag übersprungen werden kann
58 | return null;
59 | }
60 | }
61 |
62 | private Action assembleAction(
63 | JsonObject jsonObject, String actionName, JsonDeserializationContext context) {
64 | Action templateAction = actionRepository.getByName(actionName);
65 | validateAction(actionName, templateAction);
66 |
67 | try {
68 | // Clone the action to avoid sharing the same instance
69 | Action action = templateAction.clone();
70 | Interval interval = context.deserialize(jsonObject.get(INTERVAL_KEY), Interval.class);
71 | action.setInterval(interval);
72 | return action;
73 | } catch (CloneNotSupportedException e) {
74 | throw new RuntimeException("Failed to clone action: " + actionName, e);
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/persistence/de_serializer/ActionSequenceJsonDeSerializer.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.model.persistence.de_serializer;
2 |
3 | import com.google.gson.JsonDeserializationContext;
4 | import com.google.gson.JsonDeserializer;
5 | import com.google.gson.JsonElement;
6 | import com.google.gson.JsonObject;
7 | import com.google.gson.JsonParseException;
8 | import com.google.gson.JsonSerializationContext;
9 | import com.google.gson.JsonSerializer;
10 | import com.google.gson.reflect.TypeToken;
11 | import de.bsommerfeld.model.action.Action;
12 | import de.bsommerfeld.model.action.sequence.ActionSequence;
13 | import lombok.extern.slf4j.Slf4j;
14 |
15 | import java.lang.reflect.Type;
16 | import java.util.Collections;
17 | import java.util.List;
18 |
19 | @Slf4j
20 | public class ActionSequenceJsonDeSerializer
21 | implements JsonSerializer, JsonDeserializer {
22 |
23 | private static final String NAME_KEY = "name";
24 | private static final String DESCRIPTION_KEY = "description";
25 | private static final String ACTIVE_KEY = "active";
26 | private static final String ACTIONS_KEY = "actions";
27 |
28 | @Override
29 | public JsonElement serialize(
30 | ActionSequence actionSequence, Type type, JsonSerializationContext context) {
31 | JsonObject jsonObject = new JsonObject();
32 |
33 | jsonObject.addProperty(NAME_KEY, actionSequence.getName());
34 | jsonObject.addProperty(DESCRIPTION_KEY, actionSequence.getDescription());
35 | jsonObject.addProperty(ACTIVE_KEY, actionSequence.isActive());
36 | jsonObject.add(ACTIONS_KEY, context.serialize(actionSequence.getActions()));
37 |
38 | return jsonObject;
39 | }
40 |
41 | @Override
42 | public ActionSequence deserialize(
43 | JsonElement jsonElement, Type type, JsonDeserializationContext context)
44 | throws JsonParseException {
45 |
46 | JsonObject jsonObject = jsonElement.getAsJsonObject();
47 | warnOnUnknownKeys(jsonObject);
48 |
49 | String name = extractStringValue(jsonObject, NAME_KEY, "");
50 | String description = extractStringValue(jsonObject, DESCRIPTION_KEY, "");
51 | List actions = extractActions(jsonObject, context);
52 | boolean active = extractBooleanValue(jsonObject, ACTIVE_KEY);
53 |
54 | ActionSequence actionSequence = new ActionSequence(name);
55 | actionSequence.setDescription(description);
56 | actionSequence.setActive(active);
57 | actionSequence.setActions(actions);
58 | return actionSequence;
59 | }
60 |
61 | private void warnOnUnknownKeys(JsonObject jsonObject) {
62 | for (String key : jsonObject.keySet()) {
63 | if (!isValidKey(key)) {
64 | log.warn("Unbekannter Key gefunden: {}", key);
65 | }
66 | }
67 | }
68 |
69 | private boolean isValidKey(String key) {
70 | return NAME_KEY.equals(key)
71 | || DESCRIPTION_KEY.equals(key)
72 | || ACTIONS_KEY.equals(key)
73 | || ACTIVE_KEY.equals(key);
74 | }
75 |
76 | private String extractStringValue(JsonObject jsonObject, String key, String defaultValue) {
77 | return jsonObject.has(key) && !jsonObject.get(key).isJsonNull()
78 | ? jsonObject.get(key).getAsString()
79 | : defaultValue;
80 | }
81 |
82 | private List extractActions(JsonObject jsonObject, JsonDeserializationContext context) {
83 | return jsonObject.has(ACTIONS_KEY) && !jsonObject.get(ACTIONS_KEY).isJsonNull()
84 | ? context.deserialize(
85 | jsonObject.get(ACTIONS_KEY), new TypeToken>() {
86 | }.getType())
87 | : Collections.emptyList();
88 | }
89 |
90 | private boolean extractBooleanValue(JsonObject jsonObject, String key) {
91 | return jsonObject.has(key)
92 | && !jsonObject.get(key).isJsonNull()
93 | && jsonObject.get(key).getAsBoolean();
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/randomizer-model/src/main/java/de/bsommerfeld/model/watcher/FileSystemWatcher.java:
--------------------------------------------------------------------------------
1 | package de.bsommerfeld.model.watcher;
2 |
3 | import de.bsommerfeld.model.persistence.dao.ActionSequenceDao;
4 | import lombok.extern.slf4j.Slf4j;
5 |
6 | import java.io.IOException;
7 | import java.nio.file.FileSystems;
8 | import java.nio.file.Path;
9 | import java.nio.file.StandardWatchEventKinds;
10 | import java.nio.file.WatchEvent;
11 | import java.nio.file.WatchKey;
12 | import java.nio.file.WatchService;
13 | import java.util.List;
14 | import java.util.Map;
15 | import java.util.concurrent.ConcurrentHashMap;
16 | import java.util.concurrent.CopyOnWriteArrayList;
17 | import java.util.function.Consumer;
18 |
19 | @Slf4j
20 | public class FileSystemWatcher implements Runnable {
21 |
22 | private static final long DEBOUNCE_TIME_MS = 50;
23 |
24 | private final List> fileChangeListeners = new CopyOnWriteArrayList<>();
25 | private final Map lastModifiedTimes = new ConcurrentHashMap<>();
26 |
27 | public void addFileChangeListener(Consumer fileChangeListener) {
28 | fileChangeListeners.add(fileChangeListener);
29 | }
30 |
31 | @Override
32 | public void run() {
33 | try (WatchService watcher = FileSystems.getDefault().newWatchService()) {
34 |
35 | Path clusterDirectory = ActionSequenceDao.ACTION_SEQUENCE_FOLDER.toPath();
36 | clusterDirectory.register(
37 | watcher,
38 | StandardWatchEventKinds.ENTRY_CREATE,
39 | StandardWatchEventKinds.ENTRY_DELETE,
40 | StandardWatchEventKinds.ENTRY_MODIFY);
41 |
42 | log.debug("Started watching the directory: {}", clusterDirectory);
43 |
44 | while (!Thread.currentThread().isInterrupted()) {
45 | WatchKey key = watcher.take();
46 | for (WatchEvent> event : key.pollEvents()) {
47 |
48 | WatchEvent.Kind> kind = event.kind();
49 | if (kind == StandardWatchEventKinds.OVERFLOW) {
50 | log.warn("Event overflow occurred. Some file system events might have been missed.");
51 | continue;
52 | }
53 |
54 | WatchEvent ev = (WatchEvent) event;
55 | Path filename = ev.context();
56 |
57 | if (filename.toString().endsWith(".sequence")) {
58 | processEvent(filename, kind);
59 | }
60 | }
61 |
62 | boolean valid = key.reset();
63 | if (!valid) {
64 | log.warn("WatchKey no longer valid. Stopping watcher.");
65 | break;
66 | }
67 | }
68 |
69 | } catch (IOException e) {
70 | log.error("IOException occurred while watching directory: ", e);
71 | } catch (InterruptedException e) {
72 | log.warn("FileSystemWatcher was interrupted: ", e);
73 | } finally {
74 | Thread.currentThread().interrupt();
75 | }
76 | }
77 |
78 | private void processEvent(Path filename, WatchEvent.Kind> kind) {
79 | synchronized (lastModifiedTimes) {
80 | Long lastModifiedTime = lastModifiedTimes.getOrDefault(filename, 0L);
81 | long currentTime = System.currentTimeMillis();
82 | if (currentTime - lastModifiedTime > DEBOUNCE_TIME_MS) {
83 | lastModifiedTimes.put(filename, currentTime);
84 | log.debug("Detected {} on file: {}", kind.name(), filename);
85 | for (Consumer listener : fileChangeListeners) {
86 | try {
87 | listener.accept(filename);
88 | } catch (Exception e) {
89 | log.error("Error executing file change listener: ", e);
90 | }
91 | }
92 | }
93 | }
94 | }
95 | }
--------------------------------------------------------------------------------
/randomizer-model/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n
5 |
6 |
7 |
8 |
9 | ${user.home}/AppData/Roaming/randomizer/logs/randomizer.log
10 | true
11 |
12 | ${user.home}/AppData/Roaming/randomizer/logs/randomizer-%d{yyyy-MM-dd}.log
13 |
14 | 14
15 |
16 |
17 | %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/randomizer-model/src/main/resources/messages.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsommerfeld/randomizer-cs2/f90dd559debad21d07f4630b2982021779895537/randomizer-model/src/main/resources/messages.properties
--------------------------------------------------------------------------------