diff options
3 files changed, 191 insertions, 30 deletions
diff --git a/app/src/main/kotlin/xyz/brysonsteck/serverfordummies/Download.kt b/app/src/main/kotlin/xyz/brysonsteck/serverfordummies/Download.kt index 07a6eca..b98b511 100644 --- a/app/src/main/kotlin/xyz/brysonsteck/serverfordummies/Download.kt +++ b/app/src/main/kotlin/xyz/brysonsteck/serverfordummies/Download.kt @@ -14,9 +14,11 @@ class Download: Runnable { private final val MAX_BUFFER_SIZE: Number = 1024 private var url: URL + private var dir: String - constructor (url: URL) { + constructor (url: URL, dir: String) { this.url = url + this.dir = dir size = -1 downloaded = 0 status = Status.DOWNLOADING @@ -65,7 +67,7 @@ class Download: Runnable { } // Open file and seek to the end of it. - file = RandomAccessFile(getFilename(url), "rw"); + file = RandomAccessFile(dir + getFilename(url), "rw"); file.seek(downloaded.toLong()); stream = connection.getInputStream(); diff --git a/app/src/main/kotlin/xyz/brysonsteck/serverfordummies/PrimaryController.kt b/app/src/main/kotlin/xyz/brysonsteck/serverfordummies/PrimaryController.kt index a73745f..48b6326 100644 --- a/app/src/main/kotlin/xyz/brysonsteck/serverfordummies/PrimaryController.kt +++ b/app/src/main/kotlin/xyz/brysonsteck/serverfordummies/PrimaryController.kt @@ -46,6 +46,7 @@ import javafx.stage.DirectoryChooser import javafx.stage.Modality import javafx.stage.Stage import javafx.event.EventHandler +import org.rauschig.jarchivelib.* import Download @@ -108,11 +109,48 @@ class PrimaryController { lateinit private var startButton: Button @FXML lateinit private var buildButton: Button + @FXML + lateinit private var defaultsButton: Button private var building = false private var directory = "" private var asyncResult = false private var started = false + + @FXML + public fun initialize() { + difficultyBox.items = FXCollections.observableArrayList( + "Peaceful", + "Easy", + "Normal", + "Hard", + "Hardcore" + ) + difficultyBox.value = "Normal" + difficultyBox.selectionModel.selectedIndexProperty().addListener { _, _, new -> + onChoiceBoxChange("difficulty", difficultyBox.items[new as Int]) + } + gamemodeBox.items = FXCollections.observableArrayList( + "Survival", + "Creative", + "Adventure", + "Spectator" + ) + gamemodeBox.value = "Survival" + gamemodeBox.selectionModel.selectedIndexProperty().addListener { _, _, new -> + onChoiceBoxChange("gamemode", gamemodeBox.items[new as Int]) + } + worldTypeBox.items = FXCollections.observableArrayList( + "Normal", + "Superflat", + "Large Biomes", + "Amplified" + ) + worldTypeBox.value = "Normal" + worldTypeBox.selectionModel.selectedIndexProperty().addListener { _, _, new -> + onChoiceBoxChange("world-type", worldTypeBox.items[new as Int]) + } + } @FXML private fun onDirectoryButtonClick() { @@ -161,6 +199,10 @@ class PrimaryController { } + private fun onChoiceBoxChange(box: String, selection: String) { + + } + @FXML private fun onBuild() { if (building) { @@ -172,17 +214,51 @@ class PrimaryController { directoryPane.isDisable = true parentPane.isDisable = true startButton.isDisable = true + defaultsButton.isDisable = true buildButton.text = "Cancel Build" @Suppress("OPT_IN_USAGE") GlobalScope.launch(Dispatchers.Default) { progressBar.isVisible = true - var downloads = mapOf( + var javaFile = "" + var archiver = ArchiverFactory.createArchiver(ArchiveFormat.TAR, CompressionType.GZIP) + val os = System.getProperty("os.name").lowercase() + when { + os.contains("win") -> { + javaFile = "openjdk-20.0.1_windows-x64_bin.zip" + archiver = ArchiverFactory.createArchiver(ArchiveFormat.ZIP) + } + os.contains("linux") -> { + javaFile = "openjdk-20.0.1_linux-x64_bin.tar.gz" + } + os.contains("mac") -> { + javaFile = "openjdk-20.0.1_macos-x64_bin.tar.gz" + } + } + + // download files + val downloads = mapOf( + "Java 20" to "https://download.java.net/java/GA/jdk20.0.1/b4887098932d415489976708ad6d1a4b/9/GPL/${javaFile}", "BuildTools" to "https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar", ) + val destinations = mapOf ( + "Java 20" to directory + "ServerForDummies" + File.separator + "Java" + File.separator, + "BuildTools" to directory + "ServerForDummies" + File.separator + "Spigot" + File.separator + ) + val spigotBuilt = File(destinations["BuildTools"]).exists() + val javaExtracted = File(destinations["Java 20"]).exists() + destinations.forEach { + File(it.value).mkdir() + } downloads.forEach { - withContext(Dispatchers.JavaFx){statusBar.text = "Downloading ${it.key}..."} - val download = Download(URL(it.value)) + if (it.key == "Java 20" && javaExtracted) { + return@forEach + } + withContext(Dispatchers.JavaFx){ + statusBar.text = "Downloading ${it.key}..." + progressBar.progress = ProgressBar.INDETERMINATE_PROGRESS + } + val download = Download(URL(it.value), destinations[it.key]!!) download.start() while (download.status == Download.Status.DOWNLOADING) { var prog = (download.downloaded.toDouble() / download.contentLength.toDouble()) @@ -195,12 +271,66 @@ class PrimaryController { Thread.sleep(300) } } + + // extract java archive + if (building && !javaExtracted) { + withContext(Dispatchers.JavaFx) { + progressBar.progress = ProgressBar.INDETERMINATE_PROGRESS + statusBar.text = "Extracting Java archive..." + } + var stream = archiver.stream(File(directory + "ServerForDummies" + File.separator + "Java" + File.separator + javaFile)) + val dest = File(directory + "ServerForDummies" + File.separator + "Java") + var entries = 0.0 + while(stream.getNextEntry() != null && building) { + entries++ + } + stream = archiver.stream(File(directory + "ServerForDummies" + File.separator + "Java" + File.separator + javaFile)) + var entry = stream.getNextEntry() + var currentEntry = 0.0 + do { + withContext(Dispatchers.JavaFx) {progressBar.progress = currentEntry/entries} + entry.extract(dest) + entry = stream.getNextEntry() + currentEntry++ + } while (entry != null && building) + } + + if (building) { + withContext(Dispatchers.JavaFx) { + progressBar.progress = ProgressBar.INDETERMINATE_PROGRESS + statusBar.text = "Building Minecraft Server..." + } + val builder = ProcessBuilder("java", "-jar", "BuildTools.jar", "--rev", "latest", "-o", ".." + File.separator + ".." + File.separator) + builder.directory(File(directory + "ServerForDummies" + File.separator + "Spigot")) + val proc = builder.start() + val reader = InputStreamReader(proc.inputStream) + val br = BufferedReader(reader) + try { + var line = br.readLine() + var currentline = 0.0 + while (line != null) { + if (!building) { + proc.destroy() + } + println(line) + line = br.readLine() + currentline++ + if (currentline > 15) { + withContext(Dispatchers.JavaFx) {progressBar.progress = if (spigotBuilt) {currentline/1100.0} else {currentline/14122.0} } + } + } + } catch (e: IOException) { + println("Stream closed") + } + } + progressBar.isVisible = false withContext(Dispatchers.JavaFx){ worldSettingsPane.isDisable = false directoryPane.isDisable = false parentPane.isDisable = false startButton.isDisable = false + defaultsButton.isDisable = false buildButton.text = "Build Server" statusBar.text = if (building) {"Ready."} else {"Server build cancelled."} building = false; @@ -220,6 +350,7 @@ class PrimaryController { directoryPane.isDisable = true parentPane.isDisable = true buildButton.isDisable = true + defaultsButton.isDisable = true startButton.text = "Kill Server" @Suppress("OPT_IN_USAGE") GlobalScope.launch(Dispatchers.Default) { @@ -251,6 +382,7 @@ class PrimaryController { directoryPane.isDisable = false parentPane.isDisable = false buildButton.isDisable = false + defaultsButton.isDisable = false startButton.text = "Start Server" started = false } @@ -337,6 +469,8 @@ class PrimaryController { if (hasServer) break; } + if (hasServer) + break; } val hasProperties = File(directory + File.separator + "server.properties").isFile @@ -360,6 +494,7 @@ class PrimaryController { } else { // assume clean directory val result = createDialog("info", "There is no server in this directory.\nCreate one?", "Yes", "No", true) + File(directory + "ServerForDummies").mkdir() statusBar.text = "Ready." return result } diff --git a/app/src/main/resources/xyz/brysonsteck/serverfordummies/primary.fxml b/app/src/main/resources/xyz/brysonsteck/serverfordummies/primary.fxml index f09356b..a8c885b 100644 --- a/app/src/main/resources/xyz/brysonsteck/serverfordummies/primary.fxml +++ b/app/src/main/resources/xyz/brysonsteck/serverfordummies/primary.fxml @@ -11,6 +11,7 @@ <?import javafx.scene.control.ProgressBar?> <?import javafx.scene.control.Separator?> <?import javafx.scene.control.Spinner?> +<?import javafx.scene.control.SpinnerValueFactory.IntegerSpinnerValueFactory?> <?import javafx.scene.control.TextField?> <?import javafx.scene.control.TitledPane?> <?import javafx.scene.control.Tooltip?> @@ -67,7 +68,7 @@ <Insets bottom="5.0" left="5.0" right="5.0" top="6.0" /> </HBox.margin> </Label> - <TextField fx:id="worldNameField" onInputMethodTextChanged="#onWorldNameChange"> + <TextField fx:id="worldNameField" onInputMethodTextChanged="#onWorldNameChange" text="world"> <HBox.margin> <Insets top="2.0" /> </HBox.margin> @@ -85,7 +86,7 @@ <Insets bottom="5.0" left="5.0" right="5.0" top="6.0" /> </HBox.margin> </Label> - <TextField fx:id="seedField" onInputMethodTextChanged="#onSeedChange" prefHeight="23.0" prefWidth="448.0"> + <TextField fx:id="seedField" onInputMethodTextChanged="#onSeedChange" prefHeight="23.0" prefWidth="448.0" promptText="Leave empty for random seed"> <HBox.margin> <Insets top="2.0" /> </HBox.margin> @@ -107,6 +108,9 @@ <HBox.margin> <Insets top="2.0" /> </HBox.margin> + <valueFactory> + <SpinnerValueFactory.IntegerSpinnerValueFactory amountToStepBy="1" initialValue="25565" max="60000" min="20000" /> + </valueFactory> </Spinner> </children> <opaqueInsets> @@ -120,43 +124,67 @@ <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="311.0" prefWidth="625.0"> <children> <CheckBox fx:id="flightCheckbox" layoutX="14.0" layoutY="14.0" mnemonicParsing="false" onMouseClicked="#onCheckboxClick" text="Allow Flight" /> - <CheckBox fx:id="netherCheckbox" layoutX="14.0" layoutY="42.0" mnemonicParsing="false" onMouseClicked="#onCheckboxClick" text="Allow The Nether" /> - <CheckBox fx:id="structuresCheckbox" alignment="TOP_LEFT" layoutX="14.0" layoutY="70.0" mnemonicParsing="false" onMouseClicked="#onCheckboxClick" prefHeight="45.0" prefWidth="231.0" text="Generate Structures (such as villages and strongholds)" /> - <CheckBox fx:id="pvpCheckbox" layoutX="14.0" layoutY="109.0" mnemonicParsing="false" onMouseClicked="#onCheckboxClick" text="Allow PvP" /> + <CheckBox fx:id="netherCheckbox" layoutX="14.0" layoutY="42.0" mnemonicParsing="false" onMouseClicked="#onCheckboxClick" selected="true" text="Allow The Nether" /> + <CheckBox fx:id="structuresCheckbox" alignment="TOP_LEFT" layoutX="14.0" layoutY="70.0" mnemonicParsing="false" onMouseClicked="#onCheckboxClick" prefHeight="45.0" prefWidth="231.0" selected="true" text="Generate Structures (such as villages and strongholds)" /> + <CheckBox fx:id="pvpCheckbox" layoutX="14.0" layoutY="109.0" mnemonicParsing="false" onMouseClicked="#onCheckboxClick" selected="true" text="Allow PvP" /> <CheckBox fx:id="whitelistCheckbox" alignment="TOP_LEFT" layoutX="14.0" layoutY="138.0" mnemonicParsing="false" onMouseClicked="#onCheckboxClick" prefHeight="45.0" prefWidth="231.0" text="Enable Whitelist (Only users you specify can join)" /> - <Spinner fx:id="maxPlayerSpinner" layoutX="130.0" layoutY="179.0" prefHeight="23.0" prefWidth="99.0" /> + <Spinner fx:id="maxPlayerSpinner" layoutX="130.0" layoutY="179.0" prefHeight="23.0" prefWidth="99.0"> + <valueFactory> + <SpinnerValueFactory.IntegerSpinnerValueFactory amountToStepBy="1" initialValue="20" max="1000" min="0" /> + </valueFactory> + </Spinner> <Label layoutX="14.0" layoutY="183.0" text="Maximum Players:" /> - <Spinner fx:id="maxSizeSpinner" layoutX="214.0" layoutY="212.0" prefHeight="23.0" prefWidth="155.0" /> + <Spinner fx:id="maxSizeSpinner" layoutX="214.0" layoutY="212.0" prefHeight="23.0" prefWidth="155.0"> + <valueFactory> + <SpinnerValueFactory.IntegerSpinnerValueFactory amountToStepBy="1" initialValue="29999984" max="29999984" min="1" /> + </valueFactory> + </Spinner> <Label layoutX="14.0" layoutY="216.0" text="Maximum World Size (in blocks):" /> </children> </AnchorPane> </content> </TitledPane> - <TitledPane fx:id="advancedPane" animated="false" layoutX="10.0" layoutY="289.0" prefHeight="259.0" prefWidth="627.0" text="Advanced Server Settings"> + <TitledPane fx:id="advancedPane" expanded="false" layoutX="10.0" layoutY="289.0" prefHeight="259.0" prefWidth="627.0" text="Advanced Server Settings"> <content> <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="269.0" prefWidth="625.0"> <children> <CheckBox fx:id="cmdBlocksCheckbox" layoutX="14.0" layoutY="14.0" mnemonicParsing="false" onMouseClicked="#onCheckboxClick" text="Enable Command Blocks" /> <CheckBox fx:id="playerCountCheckbox" layoutX="14.0" layoutY="41.0" mnemonicParsing="false" onMouseClicked="#onCheckboxClick" text="Hide Online Player Count" /> - <Spinner fx:id="memorySpinner" layoutX="154.0" layoutY="69.0" prefHeight="23.0" prefWidth="99.0" /> + <Spinner fx:id="memorySpinner" layoutX="154.0" layoutY="69.0" prefHeight="23.0" prefWidth="99.0"> + <valueFactory> + <SpinnerValueFactory.IntegerSpinnerValueFactory amountToStepBy="1" initialValue="1024" max="65536" min="512" /> + </valueFactory> + </Spinner> <Label ellipsisString="" layoutX="14.0" layoutY="73.0" text="Server Memory in MB:" textOverrun="CLIP"> <tooltip> <Tooltip text="This is the amount of RAM that will get passed to Minecraft/the JVM. For simple servers, 1024 MB will be plenty. If you typically have more than 5 concurrent players, consider allocating more." /> </tooltip> </Label> - <Spinner fx:id="spawnSpinner" layoutX="172.0" layoutY="100.0" prefHeight="23.0" prefWidth="99.0" /> + <Spinner fx:id="spawnSpinner" layoutX="172.0" layoutY="100.0" prefHeight="23.0" prefWidth="99.0"> + <valueFactory> + <SpinnerValueFactory.IntegerSpinnerValueFactory amountToStepBy="1" initialValue="16" max="29999984" min="0" /> + </valueFactory> + </Spinner> <Label layoutX="14.0" layoutY="104.0" text="Spawn Protection Radius:"> <tooltip> <Tooltip text="All blocks in a radius from 0,~,0 will be unbreakable. If you want to break blocks within spawn, change this value." /> </tooltip> </Label> - <Spinner fx:id="simulationSpinner" layoutX="147.0" layoutY="132.0" prefHeight="23.0" prefWidth="99.0" /> + <Spinner fx:id="simulationSpinner" layoutX="147.0" layoutY="132.0" prefHeight="23.0" prefWidth="99.0"> + <valueFactory> + <SpinnerValueFactory.IntegerSpinnerValueFactory amountToStepBy="1" initialValue="10" max="40" min="0" /> + </valueFactory> + </Spinner> <Label layoutX="14.0" layoutY="136.0" text="Simulation Distance:"> <tooltip> <Tooltip text="The radius of chunks for each player where ticks will be updated. In other words, anything outside these circles, such as furnaces, mobs, etc, will not be updated or simulated." /> </tooltip> </Label> - <Spinner fx:id="renderSpinner" layoutX="124.0" layoutY="165.0" prefHeight="23.0" prefWidth="99.0" /> + <Spinner fx:id="renderSpinner" layoutX="124.0" layoutY="165.0" prefHeight="23.0" prefWidth="99.0"> + <valueFactory> + <SpinnerValueFactory.IntegerSpinnerValueFactory amountToStepBy="1" initialValue="10" max="40" min="2" /> + </valueFactory> + </Spinner> <Label layoutX="14.0" layoutY="169.0" text="Render Distance:"> <tooltip> <Tooltip text="The radius of chunks where the server will render the view distance. Any value higher on a client than what is set will be ignored. Higher values will be more demanding on the server." /> @@ -167,7 +195,11 @@ <Tooltip text="If the server cannot update ticks (i.e. "lags") for longer than this amount of time, the server will shutdown. 60000 ms (60 seconds) is the default." /> </tooltip> </Label> - <Spinner fx:id="maxTickSpinner" layoutX="246.0" layoutY="199.0" prefHeight="23.0" prefWidth="99.0" /> + <Spinner fx:id="maxTickSpinner" layoutX="246.0" layoutY="199.0" prefHeight="23.0" prefWidth="99.0"> + <valueFactory> + <SpinnerValueFactory.IntegerSpinnerValueFactory amountToStepBy="1" initialValue="60000" max="180000" min="10000" /> + </valueFactory> + </Spinner> </children> </AnchorPane> </content> @@ -207,21 +239,13 @@ </AnchorPane> </content> </TitledPane> - <TitledPane fx:id="worldTypePane1" animated="false" collapsible="false" layoutX="649.0" layoutY="265.0" prefHeight="283.0" prefWidth="305.0" text="Whitelist"> - <content> - <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="94.0" prefWidth="303.0"> - <children> - <ChoiceBox layoutX="14.0" layoutY="14.0" prefHeight="23.0" prefWidth="276.0" /> - </children> - </AnchorPane> - </content> - </TitledPane> </children> </Pane> - <ButtonBar fx:id="buttonBar" disable="true" layoutY="635.0" prefHeight="40.0" prefWidth="963.0"> + <ButtonBar fx:id="buttonBar" buttonOrder="L+R" disable="true" layoutY="635.0" prefHeight="40.0" prefWidth="963.0"> <buttons> - <Button fx:id="buildButton" mnemonicParsing="false" onMouseClicked="#onBuild" text="Build Server" /> - <Button fx:id="startButton" defaultButton="true" mnemonicParsing="false" prefWidth="120.0" onMouseClicked="#onStart" text="Start Server" /> + <Button fx:id="defaultsButton" mnemonicParsing="false" onMouseClicked="#onBuild" text="Reset to Defaults" ButtonBar.buttonData="LEFT" /> + <Button fx:id="buildButton" mnemonicParsing="false" onMouseClicked="#onBuild" text="Build Server" ButtonBar.buttonData="RIGHT" /> + <Button fx:id="startButton" defaultButton="true" mnemonicParsing="false" onMouseClicked="#onStart" prefWidth="120.0" text="Start Server" ButtonBar.buttonData="RIGHT" /> </buttons> <padding> <Insets bottom="8.0" left="8.0" right="8.0" top="8.0" /> |