package it.paspiz85.nanobot.ui; import it.paspiz85.nanobot.attack.Attack; import it.paspiz85.nanobot.game.Troop; import it.paspiz85.nanobot.platform.Platform; import it.paspiz85.nanobot.scripting.ScriptManager; import it.paspiz85.nanobot.util.BuildInfo; import it.paspiz85.nanobot.util.Settings; import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; import javafx.beans.property.StringProperty; import javafx.beans.value.ChangeListener; import javafx.concurrent.Task; import javafx.fxml.FXML; import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Button; import javafx.scene.control.ButtonType; import javafx.scene.control.CheckBox; import javafx.scene.control.ChoiceDialog; import javafx.scene.control.ComboBox; import javafx.scene.control.Hyperlink; import javafx.scene.control.Label; import javafx.scene.control.Slider; import javafx.scene.control.TextArea; import javafx.scene.control.TextField; import javafx.scene.control.TextInputDialog; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.GridPane; import javafx.scene.web.WebView; /** * Main GUI controller. * * @author paspiz85 * */ public class MainController implements ApplicationAwareController { private static int toInt(final double n) { return (int) Math.round(n); } private Application application; @FXML private ComboBox<String> autoAttackComboBox; @FXML private CheckBox collectResourcesCheckBox; @FXML private GridPane configGridPane; @FXML private AnchorPane controlPane; @FXML private TextField deField; @FXML private CheckBox detectEmptyCollectorsCheckBox; @FXML private Label donateLabel; @FXML private Hyperlink donateLink; @FXML private TextField elixirField; @FXML private CheckBox extraFuncCheckBox; @FXML private Hyperlink githubLink; @FXML private TextField goldField; @FXML private ImageView heartImage; @FXML private CheckBox isMatchAllConditionsCheckBox; protected final Logger logger = Logger.getLogger(getClass().getName()); @FXML private ComboBox<Level> logLevelComboBox; @FXML private TextField maxThField; private final Model model = Model.instance(); private final Platform platform = Platform.instance(); @FXML private ComboBox<Troop> rax1ComboBox; @FXML private ComboBox<Troop> rax2ComboBox; @FXML private ComboBox<Troop> rax3ComboBox; @FXML private ComboBox<Troop> rax4ComboBox; @FXML private ComboBox<Troop> rax5ComboBox; @FXML private ComboBox<Troop> rax6ComboBox; @FXML private Label trainTroopsSliderPreview; @FXML private Button settingsButton; @FXML private AnchorPane setupPane; @FXML private Button startButton; @FXML private Button scriptsButton; @FXML private Button stopButton; @FXML private TextArea textArea; @FXML private Slider trainTroopsSlider; @FXML private Label versionLabel; @FXML private WebView webView; @Override public void afterShow() { final boolean autostart = Boolean.parseBoolean(application.getParameters().getNamed().get("autostart")); if (autostart) { javafx.application.Platform.runLater(() -> { final Alert dialog = new Alert(AlertType.INFORMATION); dialog.initOwner(application.getPrimaryStage()); dialog.setHeaderText("Autostart in 10 seconds. Close this dialog to cancel"); dialog.show(); final Task<Void> sleeper = new Task<Void>() { @Override protected Void call() throws Exception { try { Thread.sleep(10000); } catch (final Exception e) { logger.log(Level.SEVERE, e.getMessage(), e); } return null; } }; sleeper.setOnSucceeded(event -> { if (dialog.isShowing()) { dialog.close(); startButton.fire(); } }); new Thread(sleeper, "autostartThread").start(); }); } } private void alert(final String str) { platformRunNow(() -> { final Alert dialog = new Alert(AlertType.INFORMATION); dialog.initOwner(application.getPrimaryStage()); dialog.setHeaderText(str); final Optional<ButtonType> result = dialog.showAndWait(); }); } private boolean autoAdjustResolution() { final boolean[] ret = new boolean[1]; try { platformRunNow(() -> { final Alert dialog = new Alert(AlertType.CONFIRMATION); dialog.initOwner(application.getPrimaryStage()); dialog.setTitle("Resolution"); dialog.setHeaderText("Confirm changing resolution"); dialog.setContentText(String.format("Game must run in resolution %s.\n" + "Click YES to change it automatically, NO to do it later.\n", platform.getExpectedSize() .toString())); final Optional<ButtonType> result = dialog.showAndWait(); ret[0] = result.isPresent() && result.get() == ButtonType.OK; }); } catch (final Exception e) { logger.log(Level.SEVERE, "Unable to setup resolution", e); } return ret[0]; } private boolean confirm(final String str) { final boolean[] toReturn = new boolean[1]; platformRunNow(() -> { final Alert dialog = new Alert(AlertType.CONFIRMATION); dialog.initOwner(application.getPrimaryStage()); dialog.setHeaderText(str); final Optional<ButtonType> result = dialog.showAndWait(); toReturn[0] = result.isPresent() && result.get() == ButtonType.OK; }); return toReturn[0]; } @FXML public void handleCancelButtonAction() { showSettings(false); } @FXML public void handleSaveButtonAction() { model.saveSettings((settings) -> { if (!goldField.getText().isEmpty()) { settings.setGoldThreshold(Integer.parseInt(goldField.getText())); } if (!elixirField.getText().isEmpty()) { settings.setElixirThreshold(Integer.parseInt(elixirField.getText())); } if (!deField.getText().isEmpty()) { settings.setDarkElixirThreshold(Integer.parseInt(deField.getText())); } if (!maxThField.getText().isEmpty()) { settings.setMaxThThreshold(Integer.parseInt(maxThField.getText())); } settings.setDetectEmptyCollectors(detectEmptyCollectorsCheckBox.isSelected()); settings.setMatchAllConditions(isMatchAllConditionsCheckBox.isSelected()); settings.setCollectResources(collectResourcesCheckBox.isSelected()); settings.setTrainMaxTroops(toInt(trainTroopsSlider.getValue())); settings.setLogLevel(logLevelComboBox.getValue()); settings.setAttackStrategy(autoAttackComboBox.getValue()); settings.getRaxInfo()[0] = rax1ComboBox.getValue(); settings.getRaxInfo()[1] = rax2ComboBox.getValue(); settings.getRaxInfo()[2] = rax3ComboBox.getValue(); settings.getRaxInfo()[3] = rax4ComboBox.getValue(); settings.getRaxInfo()[4] = rax5ComboBox.getValue(); settings.getRaxInfo()[5] = rax6ComboBox.getValue(); settings.setExtraFunctions(extraFuncCheckBox.isSelected()); }); showSettings(false); } @FXML public void handleScriptsButtonAction() { final ChoiceDialog<String> dialog = new ChoiceDialog<>(null, model.getScripts()); dialog.initOwner(application.getPrimaryStage()); dialog.setTitle("Scripts"); dialog.setHeaderText("Select script to run"); dialog.setContentText("Script:"); dialog.setSelectedItem(model.getLastRunnedScript()); final Optional<String> result = dialog.showAndWait(); if (result.isPresent()) { try { model.runScript(result.get()); } catch (final IllegalAccessException e) { logger.warning(e.getMessage()); } } } @FXML public void handleSettingsButtonAction() { showSettings(true); } @FXML public void handleStartButtonAction() { model.start(); } @FXML public void handleStopButtonAction() { model.stop(); startButton.requestFocus(); } private void initFooter() { webView.getEngine().load(BuildInfo.instance().getAdUrl()); webView.getEngine().locationProperty().addListener((observable, oldValue, newValue) -> { if (BuildInfo.instance().getAdUrl().equals(oldValue)) { application.getHostServices().showDocument(newValue); } }); final Thread reloadThread = new Thread(() -> { while (true) { try { Thread.sleep(60000); javafx.application.Platform.runLater(() -> { webView.getEngine().reload(); }); } catch (final Exception e) { logger.log(Level.FINE, "Refresh failed", e); } } }, "AdvReloader"); reloadThread.setDaemon(true); reloadThread.start(); } private void initHeader() { versionLabel.setText(BuildInfo.instance().getFullName()); githubLink.setText(BuildInfo.instance().getRepositoryUrl()); model.checkForUpdate(() -> { javafx.application.Platform.runLater(() -> { versionLabel.setText(BuildInfo.instance().getFullName() + " (UPDATE AVAILABLE!)"); githubLink.setText(BuildInfo.instance().getLatestVersionUrl()); }); }); githubLink.setOnAction(event -> { application.getHostServices().showDocument(githubLink.getText()); githubLink.setVisited(false); }); final Image heartIcon = new Image(getClass().getResourceAsStream("heart.png")); donateLink.setGraphic(new ImageView(heartIcon)); donateLink.setOnAction(event -> { application.getHostServices().showDocument(BuildInfo.instance().getDonateUrl()); donateLink.setVisited(false); }); } @FXML private void initialize() { LogHandler.initialize(textArea); ScriptManager.instance().setAlert((str) -> alert(str)); ScriptManager.instance().setConfirm((str) -> confirm(str)); ScriptManager.instance().setPrompt((str, defValue) -> prompt(str, defValue)); ScriptManager.instance().setSelect((str, options) -> select(str, options)); model.initialize(() -> autoAdjustResolution(), () -> updateUI()); initHeader(); initFooter(); initSettingsPane(); updateUI(); startButton.requestFocus(); } private void initSettingsPane() { trainTroopsSlider.setMin(0); trainTroopsSlider.setMax(Settings.MAX_TRAIN_TROOPS); trainTroopsSlider.setBlockIncrement(1); trainTroopsSlider.valueProperty().addListener((observable, oldValue, newValue) -> { trainTroopsSliderPreview.setText(String.format("%d", MainController.toInt(newValue.doubleValue()))); }); final Troop[] availableTroops = model.getAvailableTroops(); rax1ComboBox.getItems().addAll(availableTroops); rax2ComboBox.getItems().addAll(availableTroops); rax3ComboBox.getItems().addAll(availableTroops); rax4ComboBox.getItems().addAll(availableTroops); rax5ComboBox.getItems().addAll(availableTroops); rax6ComboBox.getItems().addAll(availableTroops); autoAttackComboBox.getItems().addAll(Attack.noStrategy()); autoAttackComboBox.getItems().addAll(Attack.getAvailableStrategies()); autoAttackComboBox.setValue(autoAttackComboBox.getItems().get(0)); final ChangeListener<String> intFieldListener = (observable, oldValue, newValue) -> { try { if (!newValue.isEmpty()) { Integer.parseInt(newValue); } } catch (final NumberFormatException e) { ((TextField) ((StringProperty) observable).getBean()).setText(oldValue); } }; goldField.textProperty().addListener(intFieldListener); elixirField.textProperty().addListener(intFieldListener); deField.textProperty().addListener(intFieldListener); maxThField.textProperty().addListener(intFieldListener); logLevelComboBox.getItems().addAll(Level.FINEST, Level.FINER, Level.FINE, Level.CONFIG, Level.INFO, Level.WARNING, Level.SEVERE); logLevelComboBox.setValue(logLevelComboBox.getItems().get(1)); updateUI(); } private void platformRunNow(final Runnable runnable) { final boolean[] done = new boolean[1]; final Runnable sync = new Runnable() { @Override public void run() { logger.log(Level.FINE, "platformRunNow run start"); runnable.run(); done[0] = true; logger.log(Level.FINE, "platformRunNow run complete"); synchronized (this) { this.notify(); } } }; javafx.application.Platform.runLater(sync); logger.log(Level.FINE, "platformRunNow wait start"); synchronized (sync) { while (!done[0]) { try { sync.wait(); } catch (final InterruptedException e) { logger.log(Level.WARNING, e.getMessage(), e); } } } logger.log(Level.FINE, "platformRunNow wait complete"); } private String prompt(final String msg, final String value) { final String[] toReturn = new String[1]; platformRunNow(() -> { final TextInputDialog dialog = new TextInputDialog(value == null ? "" : value); dialog.initOwner(application.getPrimaryStage()); dialog.setHeaderText(msg); final Optional<String> result = dialog.showAndWait(); toReturn[0] = result.isPresent() ? result.get() : null; }); return toReturn[0]; } private String select(final String str, final String[] options) { final String[] toReturn = new String[1]; platformRunNow(() -> { final ChoiceDialog<String> dialog = new ChoiceDialog<>(null, options); dialog.initOwner(application.getPrimaryStage()); dialog.setHeaderText(str); final Optional<String> result = dialog.showAndWait(); toReturn[0] = result.isPresent() ? result.get() : null; }); return toReturn[0]; } @Override public void setApplication(final Application application) { this.application = application; showSettings(false); } private void showSettings(final boolean value) { setupPane.setVisible(value); controlPane.setVisible(!value); } private void updateUI() { final boolean running = model.isRunning(); final boolean scriptRunning = model.isScriptRunning(); startButton.setDisable(running || scriptRunning); stopButton.setDisable(!running && !scriptRunning); scriptsButton.setDisable(running || scriptRunning); final Settings settings = model.loadSettings(); goldField.setText(settings.getGoldThreshold() + ""); elixirField.setText(settings.getElixirThreshold() + ""); deField.setText(settings.getDarkElixirThreshold() + ""); maxThField.setText(settings.getMaxThThreshold() + ""); detectEmptyCollectorsCheckBox.setSelected(settings.isDetectEmptyCollectors()); isMatchAllConditionsCheckBox.setSelected(settings.isMatchAllConditions()); collectResourcesCheckBox.setSelected(settings.isCollectResources()); trainTroopsSlider.setValue(settings.getTrainMaxTroops()); logLevelComboBox.getSelectionModel().select(settings.getLogLevel()); autoAttackComboBox.getSelectionModel().select(settings.getAttackStrategy()); rax1ComboBox.getSelectionModel().select(settings.getRaxInfo()[0]); rax2ComboBox.getSelectionModel().select(settings.getRaxInfo()[1]); rax3ComboBox.getSelectionModel().select(settings.getRaxInfo()[2]); rax4ComboBox.getSelectionModel().select(settings.getRaxInfo()[3]); rax5ComboBox.getSelectionModel().select(settings.getRaxInfo()[4]); rax6ComboBox.getSelectionModel().select(settings.getRaxInfo()[5]); extraFuncCheckBox.setSelected(settings.isExtraFunctions()); } }