diff options
Diffstat (limited to 'src')
17 files changed, 1388 insertions, 0 deletions
diff --git a/src/main/kotlin/xyz/brysonsteck/ServerCraft/App.kt b/src/main/kotlin/xyz/brysonsteck/ServerCraft/App.kt new file mode 100644 index 0000000..5b5ddac --- /dev/null +++ b/src/main/kotlin/xyz/brysonsteck/ServerCraft/App.kt @@ -0,0 +1,34 @@ +/* + * This Kotlin source file was generated by the Gradle 'init' task. + */ +package xyz.brysonsteck.ServerCraft + +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.image.Image +import javafx.stage.Stage; +import java.awt.Desktop; + +class App : Application() { + + override fun start(stage: Stage) { + var scene = Scene(loadFXML("primary"), 963.0, 713.0) + stage.icons.add(Image(this.javaClass.getResourceAsStream("app.png"))) + stage.setResizable(false) + stage.title = "ServerCraft" + stage.scene = scene + stage.show() + } + + public fun loadFXML(fxml: String) : Parent { + val fxmlLoader = FXMLLoader(this.javaClass.getResource(fxml + ".fxml")) + return fxmlLoader.load() + } + + public fun run() { + launch() + } + +} diff --git a/src/main/kotlin/xyz/brysonsteck/ServerCraft/Main.kt b/src/main/kotlin/xyz/brysonsteck/ServerCraft/Main.kt new file mode 100644 index 0000000..fbb61e4 --- /dev/null +++ b/src/main/kotlin/xyz/brysonsteck/ServerCraft/Main.kt @@ -0,0 +1,5 @@ +package xyz.brysonsteck.ServerCraft + +fun main() { + App().run() +}
\ No newline at end of file diff --git a/src/main/kotlin/xyz/brysonsteck/ServerCraft/controllers/InfoController.kt b/src/main/kotlin/xyz/brysonsteck/ServerCraft/controllers/InfoController.kt new file mode 100644 index 0000000..dcf77ba --- /dev/null +++ b/src/main/kotlin/xyz/brysonsteck/ServerCraft/controllers/InfoController.kt @@ -0,0 +1,74 @@ +package xyz.brysonsteck.ServerCraft.controllers + +import javafx.fxml.FXML +import javafx.application.Platform +import javafx.scene.Node +import javafx.scene.control.Hyperlink +import javafx.stage.Stage +import javafx.event.ActionEvent +import java.awt.Desktop +import java.net.URI + +class InfoController { + private val emails = mapOf( + "bryson" to "me@brysonsteck.xyz" + ) + private val websites = mapOf( + "bryson" to "https://brysonsteck.xyz" + ) + private val source = "https://codeberg.org/brysonsteck/ServerCraft" + private val license = "https://www.gnu.org/licenses/gpl-3.0.html" + + @FXML + private fun openHyperlink(e: ActionEvent) { + val link = e.source as Hyperlink + link.isVisited = false + val split = link.id.split('_').toMutableList() + split.add("") + val os = System.getProperty("os.name").lowercase() + + val desktop = Desktop.getDesktop() + try { + when { + split[1].equals("email") -> { + if (!os.contains("linux")) { + desktop.browse(URI("mailto:" + emails[split[0]])) + } else { + Runtime.getRuntime().exec("xdg-open mailto:" + emails[split[0]]) + } + } + split[1].equals("website") -> { + if (!os.contains("linux")) { + desktop.browse(URI(websites[split[0]])) + } else { + Runtime.getRuntime().exec("xdg-open " + websites[split[0]]) + } + } + split[0].equals("source") -> { + if (!os.contains("linux")) { + desktop.browse(URI(source)) + } else { + Runtime.getRuntime().exec("xdg-open " + source) + } + } + split[0].equals("license") -> { + println("license") + if (!os.contains("linux")) { + desktop.browse(URI(license)) + } else { + Runtime.getRuntime().exec("xdg-open " + license) + } + } + } + } catch (e: Exception) { + println(e) + } + } + + @FXML + private fun closeInfo(e: ActionEvent) { + val source = e.getSource() as Node + val stage = source.getScene().getWindow() as Stage + stage.close(); + } +}
\ No newline at end of file diff --git a/src/main/kotlin/xyz/brysonsteck/ServerCraft/controllers/PrimaryController.kt b/src/main/kotlin/xyz/brysonsteck/ServerCraft/controllers/PrimaryController.kt new file mode 100644 index 0000000..99d1009 --- /dev/null +++ b/src/main/kotlin/xyz/brysonsteck/ServerCraft/controllers/PrimaryController.kt @@ -0,0 +1,632 @@ +package xyz.brysonsteck.ServerCraft.controllers + +import kotlinx.coroutines.* +import kotlinx.coroutines.javafx.JavaFx +import org.rauschig.jarchivelib.* + +import java.io.File +import java.io.IOException +import java.io.BufferedReader +import java.io.InputStreamReader +import java.awt.Checkbox +import java.awt.Desktop +import java.util.Properties +import java.net.URL +import java.net.URI + +import javafx.beans.value.ChangeListener +import javafx.beans.value.ObservableValue +import javafx.concurrent.Task +import javafx.beans.property.BooleanProperty +import javafx.collections.FXCollections +import javafx.fxml.FXML +import javafx.fxml.FXMLLoader +import javafx.geometry.Insets +import javafx.scene.control.Button +import javafx.scene.control.ChoiceBox +import javafx.scene.control.Label +import javafx.scene.control.TextField +import javafx.scene.control.Spinner +import javafx.scene.control.TitledPane +import javafx.scene.control.ButtonBar +import javafx.scene.control.CheckBox +import javafx.scene.control.ProgressBar +import javafx.scene.control.Hyperlink +import javafx.scene.layout.Border +import javafx.scene.layout.BorderStroke +import javafx.scene.layout.GridPane +import javafx.scene.layout.Pane +import javafx.scene.layout.HBox +import javafx.scene.layout.VBox +import javafx.scene.text.TextAlignment +import javafx.scene.text.Text +import javafx.scene.Scene +import javafx.scene.input.MouseEvent +import javafx.scene.image.Image +import javafx.scene.image.ImageView +import javafx.stage.FileChooser +import javafx.stage.FileChooser.ExtensionFilter +import javafx.stage.DirectoryChooser +import javafx.stage.Modality +import javafx.stage.Stage +import javafx.event.EventHandler +import org.rauschig.jarchivelib.* + +import xyz.brysonsteck.ServerCraft.server.Server +import xyz.brysonsteck.ServerCraft.server.Download +import xyz.brysonsteck.ServerCraft.App + +class PrimaryController { + @FXML + lateinit private var currentDirectoryLabel: Label + @FXML + lateinit private var worldNameField: TextField + @FXML + lateinit private var seedField: TextField + @FXML + lateinit private var portSpinner: Spinner<kotlin.Int> + @FXML + lateinit private var difficultyBox: ChoiceBox<String> + @FXML + lateinit private var gamemodeBox: ChoiceBox<String> + @FXML + lateinit private var worldTypeBox: ChoiceBox<String> + @FXML + lateinit private var worldSettingsPane: HBox + @FXML + lateinit private var parentPane: Pane + @FXML + lateinit private var directoryPane: Pane + @FXML + lateinit private var buttonBar: ButtonBar + @FXML + lateinit private var flightCheckbox: CheckBox + @FXML + lateinit private var netherCheckbox: CheckBox + @FXML + lateinit private var structuresCheckbox: CheckBox + @FXML + lateinit private var pvpCheckbox: CheckBox + @FXML + lateinit private var whitelistCheckbox: CheckBox + @FXML + lateinit private var cmdBlocksCheckbox: CheckBox + @FXML + lateinit private var playerCountCheckbox: CheckBox + @FXML + lateinit private var maxPlayersSpinner: Spinner<kotlin.Int> + @FXML + lateinit private var maxSizeSpinner: Spinner<kotlin.Int> + @FXML + lateinit private var memorySpinner: Spinner<kotlin.Int> + @FXML + lateinit private var spawnSpinner: Spinner<kotlin.Int> + @FXML + lateinit private var simulationSpinner: Spinner<kotlin.Int> + @FXML + lateinit private var renderSpinner: Spinner<kotlin.Int> + @FXML + lateinit private var maxTickSpinner: Spinner<kotlin.Int> + @FXML + lateinit private var statusBar: Label + @FXML + lateinit private var progressBar: ProgressBar + @FXML + lateinit private var startButton: Button + @FXML + lateinit private var buildButton: Button + @FXML + lateinit private var defaultsButton: Button + + lateinit private var server: Server + 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() { + val dirChooser = DirectoryChooser() + dirChooser.title = "Open a server directory" + dirChooser.initialDirectory = File(System.getProperty("user.home")) + val result = dirChooser.showDialog(null) + if (result != null) { + currentDirectoryLabel.text = result.absolutePath + server = Server() + val res = loadServerDir(result.absolutePath) + if (res) { + parentPane.isDisable = false + worldSettingsPane.isDisable = false + buildButton.isDisable = false + defaultsButton.isDisable = false + } else { + currentDirectoryLabel.text = "<NONE>" + parentPane.isDisable = true + worldSettingsPane.isDisable = true + startButton.isDisable = true + buildButton.isDisable = true + defaultsButton.isDisable = true + } + } + } + + @FXML + private fun onWorldNameChange() { + + } + + @FXML + private fun onSeedChange() { + + } + + @FXML + private fun onPortChange() { + + } + + @FXML + private fun onCheckboxClick() { + + } + + @FXML + private fun onSpinnerChange() { + + } + + private fun onChoiceBoxChange(box: String, selection: String) { + + } + + @FXML + private fun onInfo() { + val stage = Stage() + val scene = Scene(FXMLLoader(App().javaClass.getResource("info.fxml")).load(), 398.0, 358.0) + stage.icons.add(Image(App().javaClass.getResourceAsStream("app.png"))) + stage.setResizable(false) + stage.initModality(Modality.APPLICATION_MODAL); + stage.title = "About ServerCraft" + stage.scene = scene + stage.show() + } + + @FXML + private fun onBuild() { + if (building) { + building = false + return; + } + building = true + worldSettingsPane.isDisable = true + 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 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 + "ServerCraft" + File.separator + "Java" + File.separator, + "BuildTools" to directory + "ServerCraft" + File.separator + "Spigot" + File.separator + ) + val spigotBuilt = File(destinations["BuildTools"]).exists() + val javaExtracted = File(destinations["Java 20"] + "jdk-20.0.1").exists() + destinations.forEach { + File(it.value).mkdir() + } + downloads.forEach { + 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()) + // for whatever reason I need to print something to the screen in order for it to update the progress bar + print("") + if (prog >= 0.01) { + withContext(Dispatchers.JavaFx) {progressBar.progress = prog} + } + if (!building) download.status = Download.Status.CANCELLED + 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 + "ServerCraft" + File.separator + "Java" + File.separator + javaFile)) + val dest = File(directory + "ServerCraft" + File.separator + "Java") + var entries = 0.0 + while(stream.getNextEntry() != null && building) { + entries++ + } + stream = archiver.stream(File(directory + "ServerCraft" + 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 + "ServerCraft" + 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 + defaultsButton.isDisable = false + buildButton.text = "Build Server" + statusBar.text = if (building) { + findServerJar() + buildButton.text = "Rebuild Server" + startButton.isDisable = false + "Ready." + } else { + "Server build cancelled." + } + building = false; + } + } + } + + @FXML + private fun onStart() { + if (started) { + createDialog("warning", "You should only kill the server if\nabsolutely necessary. Data loss may occur.\nContinue anyway?", "Yes", "No", false) + return; + } + if (!File(directory + "eula.txt").exists()) { + val res = eulaDialog() + if (res) { + File(directory + "eula.txt").writeText("eula=true") + } else { + return; + } + } + started = true + statusBar.text = "The Minecraft Server is now running. Shutdown the server to unlock the settings." + worldSettingsPane.isDisable = true + directoryPane.isDisable = true + parentPane.isDisable = true + buildButton.isDisable = true + defaultsButton.isDisable = true + startButton.text = "Kill Server" + @Suppress("OPT_IN_USAGE") + GlobalScope.launch(Dispatchers.Default) { + val builder = ProcessBuilder("java", "-jar", "${server.jar}") + builder.directory(File(directory)) + val proc = builder.start() + val reader = InputStreamReader(proc.inputStream) + val br = BufferedReader(reader) + try { + var line = br.readLine() + while (line != null) { + if (asyncResult) { + withContext(Dispatchers.JavaFx) { + statusBar.text = "Killing Minecraft server..." + startButton.isDisable = true + } + proc.destroy() + } + println(line); + line = br.readLine() + } + } catch (e: IOException) { + println("Stream closed") + } + withContext(Dispatchers.JavaFx) { + statusBar.text = if (asyncResult) { + asyncResult = false + "Server killed." + } else { + "Server stopped." + } + worldSettingsPane.isDisable = false + directoryPane.isDisable = false + parentPane.isDisable = false + buildButton.isDisable = false + defaultsButton.isDisable = false + startButton.isDisable = false + startButton.text = "Start Server" + started = false + } + } + } + + private fun eulaDialog(): Boolean { + var result = false + val resources = App().javaClass.getResource("icons/warning.png") + val dialog = Stage() + dialog.icons.add(Image(App().javaClass.getResourceAsStream("app.png"))) + dialog.setResizable(false) + dialog.initModality(Modality.APPLICATION_MODAL); + dialog.title = directory + val scenePane = Pane() + val dialogScene = Scene(scenePane, 400.0, 150.0); + val imagePane = Pane() + val icon = Image("$resources") + imagePane.layoutX = 14.0 + imagePane.layoutY = 14.0 + imagePane.scaleX = 0.7 + imagePane.scaleY = 0.7 + imagePane.children.add(ImageView(icon)) + val label = Label("Do you agree to the terms of the Minecraft End User License Agreement?") + label.isWrapText = true + label.layoutX = 115.0 + label.layoutY = 40.0 + val buttonBar = ButtonBar() + buttonBar.buttonOrder = "L+R" + buttonBar.padding = Insets(10.0, 10.0, 10.0, 10.0) + buttonBar.layoutX = 0.0 + buttonBar.layoutY = 107.0 + buttonBar.prefWidth = 400.0 + val noButton = Button("I Disagree") + noButton.onMouseClicked = EventHandler<MouseEvent>() { + result = false + dialog.hide() + } + noButton.isDefaultButton = true + val yesButton = Button("I Agree") + yesButton.onMouseClicked = EventHandler<MouseEvent>() { + result = true + dialog.hide() + } + val eula = Button("View EULA") + eula.onMouseClicked = EventHandler<MouseEvent>() { + val desktop = Desktop.getDesktop() + if (desktop.isSupported(Desktop.Action.BROWSE)) { + // most likely running on Windows or macOS + try { + desktop.browse(URI("https://account.mojang.com/documents/minecraft_eula")) + } catch (e: Exception) { + println(e) + } + } else { + // assume running on linux + try { + Runtime.getRuntime().exec("xdg-open https://account.mojang.com/documents/minecraft_eula"); + } catch (e: Exception) { + println(e) + } + } + } + ButtonBar.setButtonData(eula, ButtonBar.ButtonData.LEFT) + ButtonBar.setButtonData(noButton, ButtonBar.ButtonData.RIGHT) + ButtonBar.setButtonData(yesButton, ButtonBar.ButtonData.RIGHT) + buttonBar.buttons.add(eula) + buttonBar.buttons.add(noButton) + buttonBar.buttons.add(yesButton) + scenePane.children.addAll(imagePane, label, buttonBar) + dialog.setScene(dialogScene); + dialog.showAndWait(); + return result + } + + private fun createDialog(type: String, msg: String, yes: String, no: String, hold: Boolean): Boolean { + var result = false + val resources = App().javaClass.getResource("icons/$type.png") + val dialog = Stage() + dialog.icons.add(Image(App().javaClass.getResourceAsStream("app.png"))) + dialog.setResizable(false) + dialog.initModality(Modality.APPLICATION_MODAL); + dialog.title = directory + val scenePane = Pane() + val dialogScene = Scene(scenePane, 400.0, 150.0); + val imagePane = Pane() + val icon = Image("$resources") + imagePane.layoutX = 14.0 + imagePane.layoutY = 14.0 + imagePane.scaleX = 0.7 + imagePane.scaleY = 0.7 + imagePane.children.add(ImageView(icon)) + val label = Label(msg) + label.isWrapText = true + label.layoutX = 115.0 + label.layoutY = if (type == "warning") {10.0} else {40.0} + val buttonBar = ButtonBar() + buttonBar.padding = Insets(10.0, 10.0, 10.0, 10.0) + buttonBar.layoutX = 0.0 + buttonBar.layoutY = 107.0 + buttonBar.prefWidth = 400.0 + val noButton = Button(no) + noButton.onMouseClicked = EventHandler<MouseEvent>() { + if (hold) { + result = false + } else { + asyncResult = false + } + dialog.hide() + } + val yesButton = Button(yes) + yesButton.onMouseClicked = EventHandler<MouseEvent>() { + if (hold) { + result = true + } else { + asyncResult = true + } + dialog.hide() + } + yesButton.isDefaultButton = true + buttonBar.buttons.add(noButton) + buttonBar.buttons.add(yesButton) + scenePane.children.addAll(imagePane, label, buttonBar) + dialog.setScene(dialogScene); + if (hold) { + dialog.showAndWait(); + } else { + dialog.show(); + } + return result + } + + private fun loadServerDir(dir: String): Boolean { + directory = dir + if (!File(directory).isDirectory) { + return false; + } + + if (directory[directory.length-1] != File.separatorChar) + directory += File.separatorChar + + val hasDummy = File(directory + "ServerCraft").isDirectory + val hasProperties = File(directory + File.separator + "server.properties").isFile + val hasServer = findServerJar() + + if (hasDummy && hasServer) { + // server complete, just read jproperties + statusBar.text = "Server found!" + startButton.isDisable = false + buildButton.text = "Rebuild Server" + } else if (hasDummy && !hasServer && hasProperties) { + // just needs to be built + startButton.isDisable = true + statusBar.text = "Server needs to be built before starting." + } else if (!hasDummy && hasServer) { + // server created externally + val result = createDialog("warning", "This server directory was not created by \nServerCraft. Errors may occur; copying\nthe world directories to a new folder may be\nsafer. Proceed anyway?", "Yes", "No", true) + statusBar.text = "Ready." + if (result) { + startButton.isDisable = false + } + return result + } else { + // assume clean directory + val result = createDialog("info", "There is no server in this directory.\nCreate one?", "Yes", "No", true) + if (result) { + File(directory + "ServerCraft").mkdir() + startButton.isDisable = true + buildButton.text = "Build Server" + } + statusBar.text = "Ready." + return result + } + + return true; + } + + private fun findServerJar(): Boolean { + // search for spigot jar + // major version + for (i in 25 downTo 8) { + // patch number + for (j in 15 downTo 0) { + var spigotFile: String = "" + if (j == 0) + spigotFile += "spigot-1.$i.jar" + else + spigotFile += "spigot-1.$i.$j.jar"; + + if (File(directory + spigotFile).isFile) { + server.jar = directory + spigotFile + return true + } + } + } + + // try vanilla server if no spigot server + if (File(directory + "server.jar").isFile) { + server.jar = directory + "server.jar" + return true + } + + return false + } +}
\ No newline at end of file diff --git a/src/main/kotlin/xyz/brysonsteck/ServerCraft/server/Download.kt b/src/main/kotlin/xyz/brysonsteck/ServerCraft/server/Download.kt new file mode 100644 index 0000000..757c32c --- /dev/null +++ b/src/main/kotlin/xyz/brysonsteck/ServerCraft/server/Download.kt @@ -0,0 +1,119 @@ +package xyz.brysonsteck.ServerCraft.server + +import java.io.*; +import java.net.*; +import java.util.*; + +class Download: Runnable { + public enum class Status { + DOWNLOADING, PAUSED, COMPLETE, CANCELLED, ERROR + } + public var size: Int + public var downloaded: Int + public var contentLength: Int + public var status: Status + + private final val MAX_BUFFER_SIZE: Number = 1024 + + private var url: URL + private var dir: String + + constructor (url: URL, dir: String) { + this.url = url + this.dir = dir + size = -1 + downloaded = 0 + status = Status.DOWNLOADING + contentLength = 1 + } + + public fun start() { + val thread = Thread(this) + thread.start() + } + + private fun getFilename(url: URL): String { + val filename = url.getFile() + return filename.substring(filename.lastIndexOf('/') + 1) + } + + override fun run() { + var stream: InputStream? = null + var file: RandomAccessFile? = null + + try { + // Open connection to URL. + var connection = url.openConnection() as HttpURLConnection; + + // Specify what portion of file to download. + connection.setRequestProperty("Range", "bytes=" + downloaded + "-"); + + // Connect to server. + connection.connect(); + + // Make sure response code is in the 200 range. + if (connection.responseCode / 100 != 2) { + status = Status.ERROR + } + + // Check for valid content length. + contentLength = connection.getContentLength(); + if (contentLength < 1) { + status = Status.ERROR + } + + /* Set the size for this download if it + hasn't been already set. */ + if (size == -1) { + size = contentLength; + } + + // Open file and seek to the end of it. + file = RandomAccessFile(dir + getFilename(url), "rw"); + file.seek(downloaded.toLong()); + + stream = connection.getInputStream(); + while (status == Status.DOWNLOADING) { + /* Size buffer according to how much of the + file is left to download. */ + val buffer: ByteArray; + if (size - downloaded > MAX_BUFFER_SIZE as Int) { + buffer = ByteArray(MAX_BUFFER_SIZE) + } else { + buffer = ByteArray(size - downloaded); + } + + // Read from server into buffer. + val read = stream.read(buffer); + if (read == -1) + break; + + // Write buffer to file. + file.write(buffer, 0, read); + downloaded += read; + } + + /* Change status to complete if this point was + reached because downloading has finished. */ + if (status == Status.DOWNLOADING) { + status = Status.COMPLETE; + } + } catch (e: Exception) { + status = Status.ERROR + } finally { + // Close file. + if (file != null) { + try { + file.close(); + } catch (e: Exception) {} + } + + // Close connection to server. + if (stream != null) { + try { + stream.close(); + } catch (e: Exception) {} + } + } + } +} diff --git a/src/main/kotlin/xyz/brysonsteck/ServerCraft/server/Server.kt b/src/main/kotlin/xyz/brysonsteck/ServerCraft/server/Server.kt new file mode 100644 index 0000000..746bc55 --- /dev/null +++ b/src/main/kotlin/xyz/brysonsteck/ServerCraft/server/Server.kt @@ -0,0 +1,35 @@ +package xyz.brysonsteck.ServerCraft.server + +import java.io.File +import java.util.Properties + +public class Server { + public var jar = "" + + private val props = Properties() + + constructor() { + props.setProperty("allow-flight", false.toString()) + props.setProperty("allow-nether", true.toString()) + props.setProperty("generate-structures", true.toString()) + props.setProperty("hardcore", false.toString()) + props.setProperty("pvp", true.toString()) + props.setProperty("white-list", false.toString()) + props.setProperty("enable-command-block", false.toString()) + props.setProperty("hide-online-players", false.toString()) + props.setProperty("max-players", 20.toString()) + props.setProperty("max-world-size", 29999984.toString()) + props.setProperty("server-port", 25565.toString()) + props.setProperty("view-distance", 10.toString()) + props.setProperty("jvm-ram", 1024.toString()) + props.setProperty("spawn-protection", 16.toString()) + props.setProperty("simulation-distance", 10.toString()) + props.setProperty("max-tick-time", 60000.toString()) + props.setProperty("difficulty", "normal") + props.setProperty("gamemode", "survival") + props.setProperty("level-name", "world") + props.setProperty("level-seed", "") + props.setProperty("level-type", "minecraft:normal") + props.setProperty("motd", "A server for a dummy") + } +}
\ No newline at end of file diff --git a/src/main/resources/dmg.png b/src/main/resources/dmg.png Binary files differnew file mode 100644 index 0000000..5c33572 --- /dev/null +++ b/src/main/resources/dmg.png diff --git a/src/main/resources/icon.icns b/src/main/resources/icon.icns Binary files differnew file mode 100644 index 0000000..fe1d98a --- /dev/null +++ b/src/main/resources/icon.icns diff --git a/src/main/resources/icon.ico b/src/main/resources/icon.ico Binary files differnew file mode 100644 index 0000000..bcfb4b0 --- /dev/null +++ b/src/main/resources/icon.ico diff --git a/src/main/resources/icon.png b/src/main/resources/icon.png Binary files differnew file mode 100644 index 0000000..a24c362 --- /dev/null +++ b/src/main/resources/icon.png diff --git a/src/main/resources/xyz/brysonsteck/ServerCraft/app.png b/src/main/resources/xyz/brysonsteck/ServerCraft/app.png Binary files differnew file mode 100644 index 0000000..ceb633a --- /dev/null +++ b/src/main/resources/xyz/brysonsteck/ServerCraft/app.png diff --git a/src/main/resources/xyz/brysonsteck/ServerCraft/css/info-tabs.css b/src/main/resources/xyz/brysonsteck/ServerCraft/css/info-tabs.css new file mode 100644 index 0000000..023b0c1 --- /dev/null +++ b/src/main/resources/xyz/brysonsteck/ServerCraft/css/info-tabs.css @@ -0,0 +1,5 @@ +.tab-pane>*.tab-header-area>*.tab-header-background { + -fx-background-color: "#F4F4F4"; + -fx-border-color: "#DCDCDC"; + -fx-border-width: 0 0 1 0 +}
\ No newline at end of file diff --git a/src/main/resources/xyz/brysonsteck/ServerCraft/icons/info.png b/src/main/resources/xyz/brysonsteck/ServerCraft/icons/info.png Binary files differnew file mode 100644 index 0000000..c1951a7 --- /dev/null +++ b/src/main/resources/xyz/brysonsteck/ServerCraft/icons/info.png diff --git a/src/main/resources/xyz/brysonsteck/ServerCraft/icons/warning.png b/src/main/resources/xyz/brysonsteck/ServerCraft/icons/warning.png Binary files differnew file mode 100644 index 0000000..4d66729 --- /dev/null +++ b/src/main/resources/xyz/brysonsteck/ServerCraft/icons/warning.png diff --git a/src/main/resources/xyz/brysonsteck/ServerCraft/info.fxml b/src/main/resources/xyz/brysonsteck/ServerCraft/info.fxml new file mode 100644 index 0000000..f0b73f3 --- /dev/null +++ b/src/main/resources/xyz/brysonsteck/ServerCraft/info.fxml @@ -0,0 +1,108 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Button?> +<?import javafx.scene.control.ButtonBar?> +<?import javafx.scene.control.Hyperlink?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.control.Separator?> +<?import javafx.scene.control.Tab?> +<?import javafx.scene.control.TabPane?> +<?import javafx.scene.image.Image?> +<?import javafx.scene.image.ImageView?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.layout.Pane?> +<?import javafx.scene.layout.VBox?> +<?import javafx.scene.text.Font?> + +<Pane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="358.0" prefWidth="398.0" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1" fx:controller="xyz.brysonsteck.ServerCraft.controllers.InfoController"> + <children> + <ImageView fitHeight="115.0" fitWidth="92.0" layoutX="30.0" layoutY="30.0" pickOnBounds="true" preserveRatio="true"> + <image> + <Image url="@app.png" /> + </image> + </ImageView> + <Label layoutX="146.0" layoutY="42.0" text="ServerCraft"> + <font> + <Font name="System Bold" size="28.0" /> + </font> + </Label> + <Label layoutX="146.0" layoutY="82.0" text="Version 1.0" /> + <ButtonBar layoutY="318.0" prefHeight="40.0" prefWidth="398.0"> + <buttons> + <Button mnemonicParsing="false" onAction="#closeInfo" text="Close" /> + </buttons> + <padding> + <Insets bottom="8.0" left="8.0" right="8.0" top="8.0" /> + </padding> + </ButtonBar> + <Label layoutX="52.0" layoutY="135.0" text="A graphical interface for creating Minecraft servers" textAlignment="CENTER" /> + <TabPane layoutY="158.0" prefHeight="161.0" prefWidth="398.0" stylesheets="@css/info-tabs.css" tabClosingPolicy="UNAVAILABLE"> + <tabs> + <Tab text="About"> + <content> + <Pane prefHeight="200.0" prefWidth="200.0"> + <children> + <Label alignment="TOP_LEFT" prefWidth="398.0" text="This program is for simple Minecraft servers, and expects that the user knows how to port forward. ServerCraft is free and open source under the GNU General Public License Version 3.0" wrapText="true"> + <padding> + <Insets bottom="13.0" left="13.0" right="13.0" top="13.0" /> + </padding> + </Label> + <Hyperlink fx:id="license" layoutX="127.0" layoutY="110.0" onAction="#openHyperlink" text="License" /> + <Hyperlink fx:id="source" layoutX="189.0" layoutY="110.0" onAction="#openHyperlink" text="Source Code" /> + </children> + <padding> + <Insets bottom="13.0" left="13.0" right="13.0" top="13.0" /> + </padding> + </Pane> + </content> + </Tab> + <Tab text="Authors"> + <content> + <Pane prefHeight="200.0" prefWidth="200.0"> + <children> + <Label layoutX="78.0" layoutY="109.0" text="Want to join the list? Contribute with a PR!" textAlignment="CENTER" /> + <VBox> + <children> + <Label text="Bryson Steck" VBox.vgrow="ALWAYS"> + <font> + <Font name="System Bold" size="16.0" /> + </font> + </Label> + <Label text="Creator / Maintainer"> + <padding> + <Insets top="4.0" /> + </padding> + </Label> + <HBox> + <children> + <Hyperlink fx:id="bryson_website" onAction="#openHyperlink" text="Website"> + <HBox.margin> + <Insets left="-3.0" top="2.0" /> + </HBox.margin> + </Hyperlink> + <Separator orientation="VERTICAL"> + <padding> + <Insets bottom="2.0" top="2.0" /> + </padding> + </Separator> + <Hyperlink fx:id="bryson_email" onAction="#openHyperlink" text="Email"> + <HBox.margin> + <Insets top="2.0" /> + </HBox.margin> + </Hyperlink> + </children> + </HBox> + </children> + <padding> + <Insets bottom="13.0" left="13.0" right="13.0" top="13.0" /> + </padding> + </VBox> + </children> + </Pane> + </content> + </Tab> + </tabs> + </TabPane> + </children> +</Pane> diff --git a/src/main/resources/xyz/brysonsteck/ServerCraft/primary.fxml b/src/main/resources/xyz/brysonsteck/ServerCraft/primary.fxml new file mode 100644 index 0000000..6b34102 --- /dev/null +++ b/src/main/resources/xyz/brysonsteck/ServerCraft/primary.fxml @@ -0,0 +1,362 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Button?> +<?import javafx.scene.control.ButtonBar?> +<?import javafx.scene.control.CheckBox?> +<?import javafx.scene.control.ChoiceBox?> +<?import javafx.scene.control.ContextMenu?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.control.MenuItem?> +<?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?> +<?import javafx.scene.layout.AnchorPane?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.layout.Pane?> +<?import javafx.scene.text.Font?> + +<Pane fx:id="primary" maxHeight="713.0" maxWidth="963.0" minHeight="713.0" minWidth="963.0" prefHeight="713.0" prefWidth="963.0" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1" fx:controller="xyz.brysonsteck.ServerCraft.controllers.PrimaryController"> + <children> + <HBox fx:id="directoryPane" prefHeight="39.0" prefWidth="963.0"> + <children> + <Button id="openFile" fx:id="chooseDirectoryButton" lineSpacing="10.0" mnemonicParsing="false" onMouseClicked="#onDirectoryButtonClick" text="Choose Directory..."> + <opaqueInsets> + <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> + </opaqueInsets> + <HBox.margin> + <Insets /> + </HBox.margin> + </Button> + <Separator orientation="VERTICAL" prefHeight="200.0"> + <HBox.margin> + <Insets left="5.0" /> + </HBox.margin> + </Separator> + <Label text="Server Directory:"> + <HBox.margin> + <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> + </HBox.margin> + <font> + <Font name="System Bold" size="13.0" /> + </font> + </Label> + <Label id="currentFilename" fx:id="currentDirectoryLabel" text="<NONE>"> + <HBox.margin> + <Insets bottom="5.0" right="5.0" top="5.0" /> + </HBox.margin> + </Label> + </children> + <padding> + <Insets bottom="7.0" left="7.0" right="7.0" top="7.0" /> + </padding> + </HBox> + <HBox fx:id="worldSettingsPane" disable="true" layoutY="39.0" prefHeight="41.0" prefWidth="963.0"> + <padding> + <Insets bottom="7.0" left="7.0" right="7.0" top="7.0" /> + </padding> + <children> + <Label text="World Name:" HBox.hgrow="ALWAYS"> + <font> + <Font name="System Bold" size="13.0" /> + </font> + <HBox.margin> + <Insets bottom="5.0" left="5.0" right="5.0" top="6.0" /> + </HBox.margin> + </Label> + <TextField fx:id="worldNameField" onInputMethodTextChanged="#onWorldNameChange" text="world"> + <HBox.margin> + <Insets top="2.0" /> + </HBox.margin> + </TextField> + <Separator orientation="VERTICAL" prefHeight="200.0"> + <HBox.margin> + <Insets left="5.0" /> + </HBox.margin> + </Separator> + <Label text="Seed:"> + <font> + <Font name="System Bold" size="13.0" /> + </font> + <HBox.margin> + <Insets bottom="5.0" left="5.0" right="5.0" top="6.0" /> + </HBox.margin> + </Label> + <TextField fx:id="seedField" onInputMethodTextChanged="#onSeedChange" promptText="Leave empty for random seed" HBox.hgrow="ALWAYS"> + <HBox.margin> + <Insets top="2.0" /> + </HBox.margin> + </TextField> + <Separator orientation="VERTICAL" prefHeight="200.0"> + <HBox.margin> + <Insets left="5.0" /> + </HBox.margin> + </Separator> + <Label text="Server Port:" HBox.hgrow="ALWAYS"> + <font> + <Font name="System Bold" size="13.0" /> + </font> + <HBox.margin> + <Insets bottom="5.0" left="5.0" right="5.0" top="6.0" /> + </HBox.margin> + </Label> + <Spinner fx:id="portSpinner" editable="true" onInputMethodTextChanged="#onPortChange" prefWidth="95.0"> + <HBox.margin> + <Insets top="2.0" /> + </HBox.margin> + <valueFactory> + <SpinnerValueFactory.IntegerSpinnerValueFactory amountToStepBy="1" initialValue="25565" max="60000" min="20000" /> + </valueFactory> + </Spinner> + </children> + <opaqueInsets> + <Insets bottom="3.0" /> + </opaqueInsets> + </HBox> + <Pane fx:id="parentPane" disable="true" layoutY="78.0" prefHeight="555.0" prefWidth="970.0"> + <children> + <TitledPane fx:id="settingsPane" animated="false" collapsible="false" layoutX="10.0" layoutY="7.0" prefHeight="273.0" prefWidth="627.0" text="Server Settings"> + <content> + <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" selected="true" text="Allow The Nether" /> + <CheckBox fx:id="structuresCheckbox" alignment="TOP_LEFT" layoutX="14.0" layoutY="70.0" mnemonicParsing="false" onMouseClicked="#onCheckboxClick" 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" text="Enable Whitelist (Only users you specify can join)" /> + <HBox layoutX="6.0" layoutY="174.0"> + <children> + <Label text="Maximum Players:" HBox.hgrow="ALWAYS"> + <padding> + <Insets bottom="8.0" left="8.0" right="8.0" top="8.0" /> + </padding> + </Label> + <Spinner fx:id="maxPlayerSpinner" editable="true" prefHeight="23.0" prefWidth="99.0"> + <valueFactory> + <SpinnerValueFactory.IntegerSpinnerValueFactory amountToStepBy="1" initialValue="20" max="1000" min="0" /> + </valueFactory> + <HBox.margin> + <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> + </HBox.margin> + </Spinner> + </children> + </HBox> + <HBox layoutX="6.0" layoutY="207.0"> + <children> + <Label text="Maximum World Size (in blocks):" HBox.hgrow="ALWAYS"> + <HBox.margin> + <Insets /> + </HBox.margin> + <padding> + <Insets bottom="8.0" left="8.0" right="8.0" top="8.0" /> + </padding> + </Label> + <Spinner fx:id="maxSizeSpinner" editable="true" prefHeight="23.0" prefWidth="155.0"> + <valueFactory> + <SpinnerValueFactory.IntegerSpinnerValueFactory amountToStepBy="1" initialValue="29999984" max="29999984" min="1" /> + </valueFactory> + <HBox.margin> + <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> + </HBox.margin> + </Spinner> + </children> + </HBox> + </children> + </AnchorPane> + </content> + </TitledPane> + <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" /> + <HBox layoutX="7.0" layoutY="65.0"> + <children> + <Label ellipsisString="" text="Server Memory in MB:" textOverrun="CLIP" HBox.hgrow="ALWAYS"> + <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> + <padding> + <Insets bottom="8.0" left="8.0" right="8.0" top="8.0" /> + </padding> + </Label> + <Spinner fx:id="memorySpinner" editable="true" prefHeight="23.0" prefWidth="99.0"> + <valueFactory> + <SpinnerValueFactory.IntegerSpinnerValueFactory amountToStepBy="1" initialValue="1024" max="65536" min="512" /> + </valueFactory> + <HBox.margin> + <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> + </HBox.margin> + </Spinner> + </children> + </HBox> + <HBox layoutX="7.0" layoutY="96.0"> + <children> + <Label text="Spawn Protection Radius:" HBox.hgrow="ALWAYS"> + <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> + <HBox.margin> + <Insets /> + </HBox.margin> + <padding> + <Insets bottom="8.0" left="8.0" right="8.0" top="8.0" /> + </padding> + </Label> + <Spinner fx:id="spawnSpinner" editable="true" prefHeight="23.0" prefWidth="99.0"> + <valueFactory> + <SpinnerValueFactory.IntegerSpinnerValueFactory amountToStepBy="1" initialValue="16" max="29999984" min="0" /> + </valueFactory> + <HBox.margin> + <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> + </HBox.margin> + </Spinner> + </children> + </HBox> + <HBox layoutX="7.0" layoutY="127.0"> + <children> + <Label text="Simulation Distance:" HBox.hgrow="ALWAYS"> + <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> + <HBox.margin> + <Insets /> + </HBox.margin> + <padding> + <Insets bottom="8.0" left="8.0" right="8.0" top="8.0" /> + </padding> + </Label> + <Spinner fx:id="simulationSpinner" editable="true" prefWidth="80.0"> + <valueFactory> + <SpinnerValueFactory.IntegerSpinnerValueFactory amountToStepBy="1" initialValue="10" max="40" min="0" /> + </valueFactory> + <HBox.margin> + <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> + </HBox.margin> + </Spinner> + </children> + </HBox> + <HBox layoutX="7.0" layoutY="160.0"> + <children> + <Label text="Render Distance:" HBox.hgrow="ALWAYS"> + <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." /> + </tooltip> + <HBox.margin> + <Insets /> + </HBox.margin> + <padding> + <Insets bottom="8.0" left="8.0" right="8.0" top="8.0" /> + </padding> + </Label> + <Spinner fx:id="renderSpinner" editable="true" prefWidth="80.0"> + <valueFactory> + <SpinnerValueFactory.IntegerSpinnerValueFactory amountToStepBy="1" initialValue="10" max="40" min="2" /> + </valueFactory> + <HBox.margin> + <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> + </HBox.margin> + </Spinner> + </children> + </HBox> + <HBox layoutX="7.0" layoutY="192.0"> + <children> + <Label text="Maximum Tick Time (in milliseconds):" HBox.hgrow="ALWAYS"> + <tooltip> + <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> + <padding> + <Insets bottom="8.0" left="8.0" right="8.0" top="8.0" /> + </padding> + </Label> + <Spinner fx:id="maxTickSpinner" editable="true"> + <valueFactory> + <SpinnerValueFactory.IntegerSpinnerValueFactory amountToStepBy="1" initialValue="60000" max="180000" min="10000" /> + </valueFactory> + <HBox.margin> + <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> + </HBox.margin> + </Spinner> + </children> + </HBox> + </children> + </AnchorPane> + </content> + </TitledPane> + <TitledPane fx:id="difficultyPane" animated="false" collapsible="false" layoutX="649.0" layoutY="7.0" prefHeight="77.0" prefWidth="305.0" text="Difficulty"> + <content> + <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="94.0" prefWidth="303.0"> + <children> + <ChoiceBox fx:id="difficultyBox" layoutX="14.0" layoutY="14.0" prefHeight="23.0" prefWidth="276.0"> + <contextMenu> + <ContextMenu> + <items> + <MenuItem mnemonicParsing="false" /> + </items> + </ContextMenu> + </contextMenu> + </ChoiceBox> + </children> + </AnchorPane> + </content> + </TitledPane> + <TitledPane fx:id="gamemodePane" animated="false" collapsible="false" layoutX="649.0" layoutY="92.0" prefHeight="77.0" prefWidth="305.0" text="Gamemode"> + <content> + <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="94.0" prefWidth="303.0"> + <children> + <ChoiceBox fx:id="gamemodeBox" layoutX="14.0" layoutY="14.0" prefHeight="23.0" prefWidth="276.0" /> + </children> + </AnchorPane> + </content> + </TitledPane> + <TitledPane fx:id="worldTypePane" animated="false" collapsible="false" layoutX="649.0" layoutY="178.0" prefHeight="77.0" prefWidth="305.0" text="World Type"> + <content> + <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="94.0" prefWidth="303.0"> + <children> + <ChoiceBox fx:id="worldTypeBox" layoutX="14.0" layoutY="14.0" prefHeight="23.0" prefWidth="276.0" /> + </children> + </AnchorPane> + </content> + </TitledPane> + </children> + </Pane> + <ButtonBar fx:id="buttonBar" buttonOrder="L+R" layoutY="635.0" prefHeight="40.0" prefWidth="963.0"> + <buttons> + <Button fx:id="infoButton" mnemonicParsing="false" onMouseClicked="#onInfo" text="About ServerCraft" ButtonBar.buttonData="LEFT" /> + <Button fx:id="defaultsButton" disable="true" mnemonicParsing="false" onMouseClicked="#onBuild" text="Reset to Defaults" ButtonBar.buttonData="LEFT" /> + <Button fx:id="buildButton" disable="true" mnemonicParsing="false" onMouseClicked="#onBuild" text="Build Server" ButtonBar.buttonData="RIGHT" /> + <Button fx:id="startButton" defaultButton="true" disable="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" /> + </padding> + </ButtonBar> + <HBox layoutY="680.0" prefHeight="33.0" prefWidth="963.0" style="-fx-background-color: ddd;"> + <children> + <Label text="Status:"> + <font> + <Font name="System Bold" size="13.0" /> + </font> + </Label> + <Label fx:id="statusBar" text="Ready."> + <HBox.margin> + <Insets left="5.0" /> + </HBox.margin> + </Label> + <ProgressBar fx:id="progressBar" prefWidth="400.0" visible="false"> + <HBox.margin> + <Insets left="10.0" /> + </HBox.margin> + </ProgressBar> + </children> + <padding> + <Insets bottom="9.0" left="9.0" right="9.0" top="9.0" /> + </padding> + </HBox> + </children> +</Pane> diff --git a/src/test/kotlin/xyz/brysonsteck/serverfordummies/AppTest.kt b/src/test/kotlin/xyz/brysonsteck/serverfordummies/AppTest.kt new file mode 100644 index 0000000..aca9105 --- /dev/null +++ b/src/test/kotlin/xyz/brysonsteck/serverfordummies/AppTest.kt @@ -0,0 +1,14 @@ +/* + * This Kotlin source file was generated by the Gradle 'init' task. + */ +package xyz.brysonsteck.ServerCraft + +import kotlin.test.Test +import kotlin.test.assertNotNull + +class AppTest { + // @Test fun appHasAGreeting() { + // val classUnderTest = App() + // assertNotNull(classUnderTest.greeting, "app should have a greeting") + // } +} |