window resizing works, need to refine further

This commit is contained in:
Bryson Steck 2025-02-17 14:37:14 -07:00
parent 9a16ada0f6
commit e674df1cd3
13 changed files with 721 additions and 710 deletions

View file

@ -1,25 +1,26 @@
package xyz.brysonsteck.ServerCraft package xyz.brysonsteck.ServerCraft
import javafx.application.Application; import javafx.application.Application
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader
import javafx.scene.Parent; import javafx.scene.Parent
import javafx.scene.Scene; import javafx.scene.Scene
import javafx.scene.image.Image import javafx.scene.image.Image
import javafx.stage.Stage; import javafx.stage.Stage
import java.awt.Desktop;
class App : Application() { class App : Application() {
override fun start(stage: Stage) { override fun start(stage: Stage) {
var scene = Scene(loadFXML("primary"), 963.0, 713.0) var scene = Scene(loadFXML("primary"), 963.0, 713.0)
// Application.setUserAgentStylesheet(CupertinoLight().getUserAgentStylesheet())
scene.stylesheets.add(this.javaClass.getResource("css/style.css").toExternalForm())
stage.icons.add(Image(this.javaClass.getResourceAsStream("app.png"))) stage.icons.add(Image(this.javaClass.getResourceAsStream("app.png")))
stage.setResizable(false) stage.setResizable(true)
stage.title = "ServerCraft" stage.title = "ServerCraft"
stage.scene = scene stage.scene = scene
stage.show() stage.show()
} }
public fun loadFXML(fxml: String) : Parent { public fun loadFXML(fxml: String): Parent {
val fxmlLoader = FXMLLoader(this.javaClass.getResource(fxml + ".fxml")) val fxmlLoader = FXMLLoader(this.javaClass.getResource(fxml + ".fxml"))
return fxmlLoader.load() return fxmlLoader.load()
} }
@ -27,5 +28,4 @@ class App : Application() {
public fun run() { public fun run() {
launch() launch()
} }
} }

View file

@ -2,4 +2,4 @@ package xyz.brysonsteck.ServerCraft
fun main() { fun main() {
App().run() App().run()
} }

View file

@ -1,27 +1,21 @@
package xyz.brysonsteck.ServerCraft.controllers package xyz.brysonsteck.ServerCraft.controllers
import java.awt.Desktop
import java.net.URI
import java.util.Properties
import javafx.event.ActionEvent
import javafx.fxml.FXML import javafx.fxml.FXML
import javafx.application.Platform
import javafx.scene.Node import javafx.scene.Node
import javafx.scene.control.Hyperlink import javafx.scene.control.Hyperlink
import javafx.scene.control.Label import javafx.scene.control.Label
import javafx.stage.Stage import javafx.stage.Stage
import javafx.event.ActionEvent
import java.awt.Desktop
import java.net.URI
import java.util.Properties
import xyz.brysonsteck.ServerCraft.App import xyz.brysonsteck.ServerCraft.App
class InfoController { class InfoController {
@FXML @FXML lateinit private var version: Label
lateinit private var version: Label
private val emails = mapOf( private val emails = mapOf("bryson" to "me@brysonsteck.xyz")
"bryson" to "me@brysonsteck.xyz" private val websites = mapOf("bryson" to "https://brysonsteck.xyz")
)
private val websites = mapOf(
"bryson" to "https://brysonsteck.xyz"
)
private val source = "https://codeberg.org/brysonsteck/ServerCraft" private val source = "https://codeberg.org/brysonsteck/ServerCraft"
private val license = "https://www.gnu.org/licenses/gpl-3.0.html" private val license = "https://www.gnu.org/licenses/gpl-3.0.html"
@ -79,8 +73,8 @@ class InfoController {
@FXML @FXML
private fun closeInfo(e: ActionEvent) { private fun closeInfo(e: ActionEvent) {
val source = e.getSource() as Node val source = e.getSource() as Node
val stage = source.getScene().getWindow() as Stage val stage = source.getScene().getWindow() as Stage
stage.close(); stage.close()
} }
} }

View file

@ -1,136 +1,76 @@
package xyz.brysonsteck.ServerCraft.controllers package xyz.brysonsteck.ServerCraft.controllers
import kotlinx.coroutines.*
import kotlinx.coroutines.javafx.JavaFx
import org.rauschig.jarchivelib.*
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.io.BufferedReader
import java.io.InputStreamReader import java.io.InputStreamReader
import java.awt.Checkbox
import java.awt.Desktop
import java.util.Properties
import java.net.URL 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.collections.FXCollections
import javafx.event.ActionEvent
import javafx.fxml.FXML import javafx.fxml.FXML
import javafx.fxml.FXMLLoader import javafx.fxml.FXMLLoader
import javafx.geometry.Insets import javafx.scene.Scene
import javafx.scene.control.Button 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.ButtonBar
import javafx.scene.control.CheckBox import javafx.scene.control.CheckBox
import javafx.scene.control.ChoiceBox
import javafx.scene.control.Label
import javafx.scene.control.ProgressBar import javafx.scene.control.ProgressBar
import javafx.scene.control.Hyperlink
import javafx.scene.control.ScrollPane import javafx.scene.control.ScrollPane
import javafx.scene.layout.Border import javafx.scene.control.Spinner
import javafx.scene.layout.BorderStroke import javafx.scene.control.TextField
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.Image
import javafx.scene.image.ImageView import javafx.scene.image.ImageView
import javafx.stage.FileChooser import javafx.scene.layout.HBox
import javafx.stage.FileChooser.ExtensionFilter import javafx.scene.layout.Pane
import javafx.stage.DirectoryChooser import javafx.stage.DirectoryChooser
import javafx.stage.Modality import javafx.stage.Modality
import javafx.stage.Stage import javafx.stage.Stage
import javafx.event.EventHandler import kotlinx.coroutines.*
import javafx.event.ActionEvent import kotlinx.coroutines.javafx.JavaFx
import org.rauschig.jarchivelib.* import org.rauschig.jarchivelib.*
import xyz.brysonsteck.ServerCraft.server.Server
import xyz.brysonsteck.ServerCraft.server.Download
import xyz.brysonsteck.ServerCraft.App import xyz.brysonsteck.ServerCraft.App
import xyz.brysonsteck.ServerCraft.dialogs.Dialog
import xyz.brysonsteck.ServerCraft.dialogs.CommonDialog import xyz.brysonsteck.ServerCraft.dialogs.CommonDialog
import xyz.brysonsteck.ServerCraft.dialogs.KillDialog import xyz.brysonsteck.ServerCraft.dialogs.Dialog
import xyz.brysonsteck.ServerCraft.dialogs.EulaDialog import xyz.brysonsteck.ServerCraft.dialogs.EulaDialog
import xyz.brysonsteck.ServerCraft.dialogs.KillDialog
import xyz.brysonsteck.ServerCraft.server.Download
import xyz.brysonsteck.ServerCraft.server.Server
class PrimaryController { class PrimaryController {
@FXML @FXML lateinit private var primary: Pane
lateinit private var primary: Pane @FXML lateinit private var currentDirectoryLabel: Label
@FXML @FXML lateinit private var worldNameField: TextField
lateinit private var currentDirectoryLabel: Label @FXML lateinit private var seedField: TextField
@FXML @FXML lateinit private var portSpinner: Spinner<kotlin.Int>
lateinit private var worldNameField: TextField @FXML lateinit private var difficultyBox: ChoiceBox<String>
@FXML @FXML lateinit private var gamemodeBox: ChoiceBox<String>
lateinit private var seedField: TextField @FXML lateinit private var worldTypeBox: ChoiceBox<String>
@FXML @FXML lateinit private var worldSettingsPane: HBox
lateinit private var portSpinner: Spinner<kotlin.Int> @FXML lateinit private var parentPane: Pane
@FXML @FXML lateinit private var directoryPane: Pane
lateinit private var difficultyBox: ChoiceBox<String> @FXML lateinit private var buttonBar: ButtonBar
@FXML @FXML lateinit private var flightCheckbox: CheckBox
lateinit private var gamemodeBox: ChoiceBox<String> @FXML lateinit private var netherCheckbox: CheckBox
@FXML @FXML lateinit private var structuresCheckbox: CheckBox
lateinit private var worldTypeBox: ChoiceBox<String> @FXML lateinit private var pvpCheckbox: CheckBox
@FXML @FXML lateinit private var whitelistCheckbox: CheckBox
lateinit private var worldSettingsPane: HBox @FXML lateinit private var cmdBlocksCheckbox: CheckBox
@FXML @FXML lateinit private var playerCountCheckbox: CheckBox
lateinit private var parentPane: Pane @FXML lateinit private var maxPlayerSpinner: Spinner<kotlin.Int>
@FXML @FXML lateinit private var maxSizeSpinner: Spinner<kotlin.Int>
lateinit private var directoryPane: Pane @FXML lateinit private var memorySpinner: Spinner<kotlin.Int>
@FXML @FXML lateinit private var spawnSpinner: Spinner<kotlin.Int>
lateinit private var buttonBar: ButtonBar @FXML lateinit private var simulationSpinner: Spinner<kotlin.Int>
@FXML @FXML lateinit private var renderSpinner: Spinner<kotlin.Int>
lateinit private var flightCheckbox: CheckBox @FXML lateinit private var maxTickSpinner: Spinner<kotlin.Int>
@FXML @FXML lateinit private var statusBar: Label
lateinit private var netherCheckbox: CheckBox @FXML lateinit private var progressBar: ProgressBar
@FXML @FXML lateinit private var startButton: Button
lateinit private var structuresCheckbox: CheckBox @FXML lateinit private var buildButton: Button
@FXML @FXML lateinit private var defaultsButton: Button
lateinit private var pvpCheckbox: CheckBox @FXML lateinit private var dropDownIcon: ImageView
@FXML @FXML lateinit private var console: Label
lateinit private var whitelistCheckbox: CheckBox @FXML lateinit private var scrollPane: ScrollPane
@FXML
lateinit private var cmdBlocksCheckbox: CheckBox
@FXML
lateinit private var playerCountCheckbox: CheckBox
@FXML
lateinit private var maxPlayerSpinner: 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
@FXML
lateinit private var dropDownIcon: ImageView
@FXML
lateinit private var console: Label
@FXML
lateinit private var scrollPane: ScrollPane
lateinit private var server: Server lateinit private var server: Server
lateinit private var killDialog: Dialog lateinit private var killDialog: Dialog
@ -150,97 +90,84 @@ class PrimaryController {
@FXML @FXML
public fun initialize() { public fun initialize() {
scrollPane.vvalueProperty().bind(console.heightProperty()); // scrollPane.vvalueProperty().bind(console.heightProperty());
difficultyBox.items = FXCollections.observableArrayList( difficultyBox.items =
"Peaceful", FXCollections.observableArrayList("Peaceful", "Easy", "Normal", "Hard", "Hardcore")
"Easy",
"Normal",
"Hard",
"Hardcore"
)
difficultyBox.value = "Normal" difficultyBox.value = "Normal"
difficultyBox.selectionModel.selectedIndexProperty().addListener { _, _, new -> difficultyBox.selectionModel.selectedIndexProperty().addListener { _, _, new ->
if (!loading) { if (!loading) {
onPropChange("difficulty", difficultyBox.items[new as Int]) onPropChange("difficulty", difficultyBox.items[new as Int])
} }
} }
gamemodeBox.items = FXCollections.observableArrayList( gamemodeBox.items =
"Survival", FXCollections.observableArrayList("Survival", "Creative", "Adventure", "Spectator")
"Creative",
"Adventure",
"Spectator"
)
gamemodeBox.value = "Survival" gamemodeBox.value = "Survival"
gamemodeBox.selectionModel.selectedIndexProperty().addListener { _, _, new -> gamemodeBox.selectionModel.selectedIndexProperty().addListener { _, _, new ->
if (!loading) { if (!loading) {
onPropChange("gamemode", gamemodeBox.items[new as Int]) onPropChange("gamemode", gamemodeBox.items[new as Int])
} }
} }
worldTypeBox.items = FXCollections.observableArrayList( worldTypeBox.items =
"Normal", FXCollections.observableArrayList("Normal", "Superflat", "Large Biomes", "Amplified")
"Superflat",
"Large Biomes",
"Amplified"
)
worldTypeBox.value = "Normal" worldTypeBox.value = "Normal"
worldTypeBox.selectionModel.selectedIndexProperty().addListener { _, _, new -> worldTypeBox.selectionModel.selectedIndexProperty().addListener { _, _, new ->
if (!loading) { if (!loading) {
onPropChange("level-type", worldTypeBox.items[new as Int]) onPropChange("level-type", worldTypeBox.items[new as Int])
} }
} }
maxPlayerSpinner.editor.textProperty().addListener { _, _, new -> maxPlayerSpinner.editor.textProperty().addListener { _, _, new ->
if (!loading) { if (!loading) {
onPropChange("max-players", new) onPropChange("max-players", new)
} }
} }
maxSizeSpinner.editor.textProperty().addListener { _, _, new -> maxSizeSpinner.editor.textProperty().addListener { _, _, new ->
if (!loading) { if (!loading) {
onPropChange("max-world-size", new) onPropChange("max-world-size", new)
} }
} }
portSpinner.editor.textProperty().addListener { _, _, new -> portSpinner.editor.textProperty().addListener { _, _, new ->
if (!loading) { if (!loading) {
onPropChange("server-port", new) onPropChange("server-port", new)
} }
} }
renderSpinner.editor.textProperty().addListener { _, _, new -> renderSpinner.editor.textProperty().addListener { _, _, new ->
if (!loading) { if (!loading) {
onPropChange("view-distance", new) onPropChange("view-distance", new)
} }
} }
memorySpinner.editor.textProperty().addListener { _, _, new -> memorySpinner.editor.textProperty().addListener { _, _, new ->
if (!loading) { if (!loading) {
onPropChange("jvm-ram", new) onPropChange("jvm-ram", new)
} }
} }
spawnSpinner.editor.textProperty().addListener { _, _, new -> spawnSpinner.editor.textProperty().addListener { _, _, new ->
if (!loading) { if (!loading) {
onPropChange("spawn-protection", new) onPropChange("spawn-protection", new)
} }
} }
simulationSpinner.editor.textProperty().addListener { _, _, new -> simulationSpinner.editor.textProperty().addListener { _, _, new ->
if (!loading) { if (!loading) {
onPropChange("simulation-distance", new) onPropChange("simulation-distance", new)
} }
} }
maxTickSpinner.editor.textProperty().addListener { _, _, new -> maxTickSpinner.editor.textProperty().addListener { _, _, new ->
if (!loading) { if (!loading) {
onPropChange("max-tick-time", new) onPropChange("max-tick-time", new)
} }
} }
worldNameField.textProperty().addListener { _, _, new -> worldNameField.textProperty().addListener { _, _, new ->
if (!loading) { if (!loading) {
onPropChange("level-name", new) onPropChange("level-name", new)
} }
} }
seedField.textProperty().addListener { _, _, new -> seedField.textProperty().addListener { _, _, new ->
if (!loading) { if (!loading) {
onPropChange("level-seed", new) onPropChange("level-seed", new)
} }
} }
killDialog = KillDialog() killDialog = KillDialog()
} }
@FXML @FXML
private fun onDirectoryButtonClick() { private fun onDirectoryButtonClick() {
val dirChooser = DirectoryChooser() val dirChooser = DirectoryChooser()
@ -293,14 +220,17 @@ class PrimaryController {
spawnSpinner.valueFactory.value = server.getProp("spawn-protection").toIntOrNull() spawnSpinner.valueFactory.value = server.getProp("spawn-protection").toIntOrNull()
simulationSpinner.valueFactory.value = server.getProp("simulation-distance").toIntOrNull() simulationSpinner.valueFactory.value = server.getProp("simulation-distance").toIntOrNull()
maxTickSpinner.valueFactory.value = server.getProp("max-tick-time").toIntOrNull() maxTickSpinner.valueFactory.value = server.getProp("max-tick-time").toIntOrNull()
difficultyBox.value = if (parseBool(server.getProp("hardcore"))) { difficultyBox.value =
"Hardcore" if (parseBool(server.getProp("hardcore"))) {
} else { "Hardcore"
server.getProp("difficulty").replaceFirstChar { it.uppercase() } } else {
} server.getProp("difficulty").replaceFirstChar { it.uppercase() }
}
gamemodeBox.value = server.getProp("gamemode").replaceFirstChar { it.uppercase() } gamemodeBox.value = server.getProp("gamemode").replaceFirstChar { it.uppercase() }
worldTypeBox.value = server.getProp("level-type").removePrefix("minecraft:") worldTypeBox.value =
.split('_').joinToString(" ") { it.replaceFirstChar(Char::uppercaseChar)} server.getProp("level-type").removePrefix("minecraft:").split('_').joinToString(" ") {
it.replaceFirstChar(Char::uppercaseChar)
}
worldNameField.text = server.getProp("level-name") worldNameField.text = server.getProp("level-name")
seedField.text = server.getProp("level-seed") seedField.text = server.getProp("level-seed")
loading = false loading = false
@ -373,7 +303,13 @@ class PrimaryController {
@FXML @FXML
private fun onDefaults() { private fun onDefaults() {
val res = CommonDialog("info", "Reset all settings?", "Reset settings to defaults?\nThere is NO GOING BACK!").show() val res =
CommonDialog(
"info",
"Reset all settings?",
"Reset settings to defaults?\nThere is NO GOING BACK!"
)
.show()
if (res) { if (res) {
server.loadProps() server.loadProps()
applyProps() applyProps()
@ -387,7 +323,7 @@ class PrimaryController {
val scene = Scene(FXMLLoader(App().javaClass.getResource("info.fxml")).load(), 398.0, 358.0) val scene = Scene(FXMLLoader(App().javaClass.getResource("info.fxml")).load(), 398.0, 358.0)
stage.icons.add(Image(App().javaClass.getResourceAsStream("app.png"))) stage.icons.add(Image(App().javaClass.getResourceAsStream("app.png")))
stage.setResizable(false) stage.setResizable(false)
stage.initModality(Modality.APPLICATION_MODAL); stage.initModality(Modality.APPLICATION_MODAL)
stage.title = "About ServerCraft" stage.title = "About ServerCraft"
stage.scene = scene stage.scene = scene
stage.show() stage.show()
@ -399,7 +335,7 @@ class PrimaryController {
building = false building = false
statusBar.text = "Cancelling..." statusBar.text = "Cancelling..."
buildButton.isDisable = true buildButton.isDisable = true
return; return
} }
building = true building = true
worldSettingsPane.isDisable = true worldSettingsPane.isDisable = true
@ -429,24 +365,28 @@ class PrimaryController {
} }
// download files // download files
val downloads = mapOf( val downloads =
"Java 20" to "https://download.java.net/java/GA/jdk20.0.1/b4887098932d415489976708ad6d1a4b/9/GPL/${javaFile}", mapOf(
"BuildTools" to "https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar", "Java 20" to
) "https://download.java.net/java/GA/jdk20.0.1/b4887098932d415489976708ad6d1a4b/9/GPL/${javaFile}",
val destinations = mapOf ( "BuildTools" to
"Java 20" to directory + "ServerCraft" + File.separator + "Java" + File.separator, "https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar",
"BuildTools" to directory + "ServerCraft" + File.separator + "Spigot" + File.separator )
) 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 spigotBuilt = File(destinations["BuildTools"]).exists()
val javaExtracted = File(destinations["Java 20"] + "jdk-20.0.1").exists() val javaExtracted = File(destinations["Java 20"] + "jdk-20.0.1").exists()
destinations.forEach { destinations.forEach { File(it.value).mkdir() }
File(it.value).mkdir()
}
downloads.forEach { downloads.forEach {
if (it.key == "Java 20" && javaExtracted) { if (it.key == "Java 20" && javaExtracted) {
return@forEach return@forEach
} }
withContext(Dispatchers.JavaFx){ withContext(Dispatchers.JavaFx) {
statusBar.text = "Downloading ${it.key}..." statusBar.text = "Downloading ${it.key}..."
progressBar.progress = ProgressBar.INDETERMINATE_PROGRESS progressBar.progress = ProgressBar.INDETERMINATE_PROGRESS
log("Downloading ${it.key} from ${it.value}\n") log("Downloading ${it.key} from ${it.value}\n")
@ -455,15 +395,18 @@ class PrimaryController {
download.start() download.start()
while (download.status == Download.Status.DOWNLOADING) { while (download.status == Download.Status.DOWNLOADING) {
var prog = (download.downloaded.toDouble() / download.contentLength.toDouble()) 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 // for whatever reason I need to print something to the screen in order for it to update
withContext(Dispatchers.JavaFx) {log("Progress: ${prog * 100}%\n")} // the progress bar
withContext(Dispatchers.JavaFx) { log("Progress: ${prog * 100}%\n") }
if (prog >= 0.01) { if (prog >= 0.01) {
withContext(Dispatchers.JavaFx) {progressBar.progress = prog} withContext(Dispatchers.JavaFx) { progressBar.progress = prog }
} }
if (!building) download.status = Download.Status.CANCELLED if (!building) download.status = Download.Status.CANCELLED
Thread.sleep(300) Thread.sleep(300)
} }
withContext(Dispatchers.JavaFx) {log("Download of ${it.key} complete with status: ${download.status}\n")} withContext(Dispatchers.JavaFx) {
log("Download of ${it.key} complete with status: ${download.status}\n")
}
} }
// extract java archive // extract java archive
@ -473,18 +416,38 @@ class PrimaryController {
statusBar.text = "Extracting Java archive..." statusBar.text = "Extracting Java archive..."
log("Extracting Java archive to ${directory + "ServerCraft" + File.separator + "Java"}\n") log("Extracting Java archive to ${directory + "ServerCraft" + File.separator + "Java"}\n")
} }
var stream = archiver.stream(File(directory + "ServerCraft" + File.separator + "Java" + File.separator + javaFile)) var stream =
archiver.stream(
File(
directory +
"ServerCraft" +
File.separator +
"Java" +
File.separator +
javaFile
)
)
val dest = File(directory + "ServerCraft" + File.separator + "Java") val dest = File(directory + "ServerCraft" + File.separator + "Java")
var entries = 0.0 var entries = 0.0
while(stream.getNextEntry() != null && building) { while (stream.getNextEntry() != null && building) {
entries++ entries++
} }
stream = archiver.stream(File(directory + "ServerCraft" + File.separator + "Java" + File.separator + javaFile)) stream =
archiver.stream(
File(
directory +
"ServerCraft" +
File.separator +
"Java" +
File.separator +
javaFile
)
)
var entry = stream.getNextEntry() var entry = stream.getNextEntry()
var currentEntry = 0.0 var currentEntry = 0.0
do { do {
withContext(Dispatchers.JavaFx) { withContext(Dispatchers.JavaFx) {
progressBar.progress = currentEntry/entries progressBar.progress = currentEntry / entries
log(entry.name + "\n") log(entry.name + "\n")
} }
entry.extract(dest) entry.extract(dest)
@ -498,7 +461,16 @@ class PrimaryController {
progressBar.progress = ProgressBar.INDETERMINATE_PROGRESS progressBar.progress = ProgressBar.INDETERMINATE_PROGRESS
statusBar.text = "Building Minecraft Server..." statusBar.text = "Building Minecraft Server..."
} }
val builder = ProcessBuilder("${directory}ServerCraft${File.separator}Java${File.separator}jdk-20.0.1${File.separator}bin${File.separator}java", "-jar", "BuildTools.jar", "--rev", "latest", "-o", ".." + File.separator + ".." + File.separator) val builder =
ProcessBuilder(
"${directory}ServerCraft${File.separator}Java${File.separator}jdk-20.0.1${File.separator}bin${File.separator}java",
"-jar",
"BuildTools.jar",
"--rev",
"latest",
"-o",
".." + File.separator + ".." + File.separator
)
builder.directory(File(directory + "ServerCraft" + File.separator + "Spigot")) builder.directory(File(directory + "ServerCraft" + File.separator + "Spigot"))
val proc = builder.start() val proc = builder.start()
val reader = InputStreamReader(proc.inputStream) val reader = InputStreamReader(proc.inputStream)
@ -509,40 +481,48 @@ class PrimaryController {
if (!building) { if (!building) {
proc.destroy() proc.destroy()
} }
withContext(Dispatchers.JavaFx) {log(cbuf.joinToString(separator=""))} withContext(Dispatchers.JavaFx) { log(cbuf.joinToString(separator = "")) }
cbuf = CharArray(1000) cbuf = CharArray(1000)
reader.read(cbuf, 0, 1000) reader.read(cbuf, 0, 1000)
currentline++ currentline++
if (currentline > 15) { if (currentline > 15) {
withContext(Dispatchers.JavaFx) {progressBar.progress = if (spigotBuilt) {currentline/1100.0} else {currentline/14122.0} } withContext(Dispatchers.JavaFx) {
progressBar.progress =
if (spigotBuilt) {
currentline / 1100.0
} else {
currentline / 14122.0
}
}
} }
} }
cbuf = CharArray(1000) cbuf = CharArray(1000)
reader.read(cbuf, 0, 1000) reader.read(cbuf, 0, 1000)
withContext(Dispatchers.JavaFx) {log(cbuf.joinToString(separator=""))} withContext(Dispatchers.JavaFx) { log(cbuf.joinToString(separator = "")) }
} catch (e: IOException) { } catch (e: IOException) {
withContext(Dispatchers.JavaFx) {log("Stream Closed\n")} withContext(Dispatchers.JavaFx) { log("Stream Closed\n") }
} }
} }
progressBar.isVisible = false progressBar.isVisible = false
findServerJar() findServerJar()
withContext(Dispatchers.JavaFx){ withContext(Dispatchers.JavaFx) {
worldSettingsPane.isDisable = false worldSettingsPane.isDisable = false
directoryPane.isDisable = false directoryPane.isDisable = false
parentPane.isDisable = false parentPane.isDisable = false
defaultsButton.isDisable = false defaultsButton.isDisable = false
buildButton.text = "Build Server" buildButton.text = "Build Server"
buildButton.isDisable = false buildButton.isDisable = false
statusBar.text = if (building) { statusBar.text =
findServerJar() if (building) {
buildButton.text = "Rebuild Server" findServerJar()
startButton.isDisable = false buildButton.text = "Rebuild Server"
"Ready." startButton.isDisable = false
} else { "Ready."
"Server build cancelled." } else {
} "Server build cancelled."
building = false; }
building = false
} }
} }
} }
@ -551,18 +531,19 @@ class PrimaryController {
private fun onStart() { private fun onStart() {
if (started) { if (started) {
killDialog.show() killDialog.show()
return; return
} }
if (!File(directory + "eula.txt").exists()) { if (!File(directory + "eula.txt").exists()) {
val res = EulaDialog().show() val res = EulaDialog().show()
if (res) { if (res) {
File(directory + "eula.txt").writeText("eula=true") File(directory + "eula.txt").writeText("eula=true")
} else { } else {
return; return
} }
} }
started = true started = true
statusBar.text = "The Minecraft Server is now running. Shutdown the server to unlock the settings." statusBar.text =
"The Minecraft Server is now running. Shutdown the server to unlock the settings."
worldSettingsPane.isDisable = true worldSettingsPane.isDisable = true
directoryPane.isDisable = true directoryPane.isDisable = true
parentPane.isDisable = true parentPane.isDisable = true
@ -571,7 +552,13 @@ class PrimaryController {
startButton.text = "Kill Server" startButton.text = "Kill Server"
@Suppress("OPT_IN_USAGE") @Suppress("OPT_IN_USAGE")
GlobalScope.launch(Dispatchers.Default) { GlobalScope.launch(Dispatchers.Default) {
val builder = ProcessBuilder("${directory}ServerCraft${File.separator}Java${File.separator}jdk-20.0.1${File.separator}bin${File.separator}java", "-Xmx${server.getProp("jvm-ram")}M", "-jar", "${server.jar}") val builder =
ProcessBuilder(
"${directory}ServerCraft${File.separator}Java${File.separator}jdk-20.0.1${File.separator}bin${File.separator}java",
"-Xmx${server.getProp("jvm-ram")}M",
"-jar",
"${server.jar}"
)
builder.directory(File(directory)) builder.directory(File(directory))
val proc = builder.start() val proc = builder.start()
val reader = InputStreamReader(proc.inputStream) val reader = InputStreamReader(proc.inputStream)
@ -586,23 +573,24 @@ class PrimaryController {
} }
proc.destroy() proc.destroy()
} }
withContext(Dispatchers.JavaFx) {log(cbuf.joinToString(separator=""))} withContext(Dispatchers.JavaFx) { log(cbuf.joinToString(separator = "")) }
cbuf = CharArray(1000) cbuf = CharArray(1000)
reader.read(cbuf, 0, 1000) reader.read(cbuf, 0, 1000)
} }
cbuf = CharArray(1000) cbuf = CharArray(1000)
reader.read(cbuf, 0, 1000) reader.read(cbuf, 0, 1000)
withContext(Dispatchers.JavaFx) {log(cbuf.joinToString(separator=""))} withContext(Dispatchers.JavaFx) { log(cbuf.joinToString(separator = "")) }
} catch (e: IOException) { } catch (e: IOException) {
withContext(Dispatchers.JavaFx) {log("Stream Closed\n")} withContext(Dispatchers.JavaFx) { log("Stream Closed\n") }
} }
withContext(Dispatchers.JavaFx) { withContext(Dispatchers.JavaFx) {
statusBar.text = if (killDialog.result) { statusBar.text =
killDialog.result = false if (killDialog.result) {
"Server killed." killDialog.result = false
} else { "Server killed."
"Server stopped." } else {
} "Server stopped."
}
worldSettingsPane.isDisable = false worldSettingsPane.isDisable = false
directoryPane.isDisable = false directoryPane.isDisable = false
parentPane.isDisable = false parentPane.isDisable = false
@ -612,19 +600,18 @@ class PrimaryController {
startButton.text = "Start Server" startButton.text = "Start Server"
started = false started = false
} }
} }
} }
private fun loadServerDir(dir: String): Boolean { private fun loadServerDir(dir: String): Boolean {
directory = dir directory = dir
// exit if doesn't exist for whatever reason // exit if doesn't exist for whatever reason
if (!File(directory).isDirectory) { if (!File(directory).isDirectory) {
return false; return false
} }
// add system dir separator for cleaner code // add system dir separator for cleaner code
if (directory[directory.length-1] != File.separatorChar) if (directory[directory.length - 1] != File.separatorChar) directory += File.separatorChar
directory += File.separatorChar
val hasDummy = File(directory + "ServerCraft").isDirectory val hasDummy = File(directory + "ServerCraft").isDirectory
val hasProperties = File(directory + File.separator + "server.properties").isFile val hasProperties = File(directory + File.separator + "server.properties").isFile
@ -641,19 +628,27 @@ class PrimaryController {
statusBar.text = "Server needs to be built before starting." statusBar.text = "Server needs to be built before starting."
} else if (!hasDummy && hasServer) { } else if (!hasDummy && hasServer) {
// server created externally // server created externally
val result = CommonDialog("warning", directory, "This server directory was not created by \nServerCraft. Errors may occur; copying\nthe world directories to a new folder may be\nsafer. Proceed anyway?").show() val result =
CommonDialog(
"warning",
directory,
"This server directory was not created by \nServerCraft. Errors may occur; copying\nthe world directories to a new folder may be\nsafer. Proceed anyway?"
)
.show()
statusBar.text = "Ready." statusBar.text = "Ready."
if (result) { if (result) {
startButton.isDisable = true startButton.isDisable = true
File(directory + "ServerCraft").mkdir() File(directory + "ServerCraft").mkdir()
buildButton.text = "Build Server" buildButton.text = "Build Server"
statusBar.text = "Server converted. Build the server to start." statusBar.text = "Server converted. Build the server to start."
server.loadProps(dir, convert=true) server.loadProps(dir, convert = true)
} }
return result return result
} else { } else {
// assume clean directory // assume clean directory
val result = CommonDialog("info", directory, "There is no server in this directory.\nCreate one?").show() val result =
CommonDialog("info", directory, "There is no server in this directory.\nCreate one?")
.show()
if (result) { if (result) {
File(directory + "ServerCraft").mkdir() File(directory + "ServerCraft").mkdir()
startButton.isDisable = true startButton.isDisable = true
@ -671,7 +666,7 @@ class PrimaryController {
server.loadProps() server.loadProps()
} }
return true; return true
} }
private fun findServerJar(): Boolean { private fun findServerJar(): Boolean {
@ -681,10 +676,7 @@ class PrimaryController {
// patch number // patch number
for (j in 15 downTo 0) { for (j in 15 downTo 0) {
var spigotFile: String = "" var spigotFile: String = ""
if (j == 0) if (j == 0) spigotFile += "spigot-1.$i.jar" else spigotFile += "spigot-1.$i.$j.jar"
spigotFile += "spigot-1.$i.jar"
else
spigotFile += "spigot-1.$i.$j.jar";
if (File(directory + spigotFile).isFile) { if (File(directory + spigotFile).isFile) {
server.jar = directory + spigotFile server.jar = directory + spigotFile
@ -701,4 +693,4 @@ class PrimaryController {
return false return false
} }
} }

View file

@ -1,10 +1,10 @@
package xyz.brysonsteck.ServerCraft.dialogs package xyz.brysonsteck.ServerCraft.dialogs
import javafx.scene.control.Button
import javafx.event.EventHandler
import javafx.event.ActionEvent import javafx.event.ActionEvent
import javafx.event.EventHandler
import javafx.scene.control.Button
class CommonDialog: Dialog { class CommonDialog : Dialog {
override var type: String override var type: String
override var title: String override var title: String
override var msg: String override var msg: String
@ -18,17 +18,18 @@ class CommonDialog: Dialog {
this.msg = msg this.msg = msg
noButton = Button("No") noButton = Button("No")
noButton.onAction = EventHandler<ActionEvent>() { noButton.onAction =
result = false EventHandler<ActionEvent>() {
dialog.hide() result = false
} dialog.hide()
}
yesButton = Button("Yes") yesButton = Button("Yes")
yesButton.onAction = EventHandler<ActionEvent>() { yesButton.onAction =
result = true EventHandler<ActionEvent>() {
dialog.hide() result = true
} dialog.hide()
}
yesButton.isDefaultButton = true yesButton.isDefaultButton = true
neutralButton = null neutralButton = null
} }
}
}

View file

@ -1,17 +1,15 @@
package xyz.brysonsteck.ServerCraft.dialogs package xyz.brysonsteck.ServerCraft.dialogs
import javafx.geometry.Insets
import javafx.scene.Scene
import javafx.scene.control.Button import javafx.scene.control.Button
import javafx.scene.control.ButtonBar import javafx.scene.control.ButtonBar
import javafx.scene.control.Label import javafx.scene.control.Label
import javafx.scene.image.Image import javafx.scene.image.Image
import javafx.scene.image.ImageView import javafx.scene.image.ImageView
import javafx.scene.layout.Pane import javafx.scene.layout.Pane
import javafx.scene.Scene
import javafx.stage.Modality import javafx.stage.Modality
import javafx.stage.Stage import javafx.stage.Stage
import javafx.geometry.Insets
import javafx.event.EventHandler
import javafx.event.ActionEvent
import xyz.brysonsteck.ServerCraft.App import xyz.brysonsteck.ServerCraft.App
abstract class Dialog { abstract class Dialog {
@ -33,7 +31,7 @@ abstract class Dialog {
val icon = Image("$resources") val icon = Image("$resources")
dialog.icons.add(Image(App().javaClass.getResourceAsStream("app.png"))) dialog.icons.add(Image(App().javaClass.getResourceAsStream("app.png")))
dialog.setResizable(false) dialog.setResizable(false)
dialog.initModality(Modality.APPLICATION_MODAL); dialog.initModality(Modality.APPLICATION_MODAL)
dialog.title = title dialog.title = title
val imagePane = Pane() val imagePane = Pane()
imagePane.layoutX = 14.0 imagePane.layoutX = 14.0
@ -64,14 +62,13 @@ abstract class Dialog {
ButtonBar.setButtonData(neutralButton, ButtonBar.ButtonData.LEFT) ButtonBar.setButtonData(neutralButton, ButtonBar.ButtonData.LEFT)
buttonBar.buttons.add(neutralButton) buttonBar.buttons.add(neutralButton)
} }
val dialogScene = Scene(scenePane, 400.0, 150.0); val dialogScene = Scene(scenePane, 400.0, 150.0)
dialog.setScene(dialogScene); dialog.setScene(dialogScene)
if (hold) { if (hold) {
dialog.showAndWait(); dialog.showAndWait()
} else { } else {
dialog.show(); dialog.show()
} }
return result return result
} }
}
}

View file

@ -1,13 +1,12 @@
package xyz.brysonsteck.ServerCraft.dialogs package xyz.brysonsteck.ServerCraft.dialogs
import xyz.brysonsteck.ServerCraft.dialogs.Dialog
import javafx.scene.control.Button
import javafx.event.EventHandler
import javafx.event.ActionEvent
import java.awt.Desktop import java.awt.Desktop
import java.net.URI import java.net.URI
import javafx.event.ActionEvent
import javafx.event.EventHandler
import javafx.scene.control.Button
class EulaDialog: Dialog { class EulaDialog : Dialog {
override val type: String override val type: String
override val title: String override val title: String
override val msg: String override val msg: String
@ -18,38 +17,40 @@ class EulaDialog: Dialog {
constructor() { constructor() {
type = "warning" type = "warning"
title = "Minecraft End User License Agreement (EULA)" title = "Minecraft End User License Agreement (EULA)"
msg = "Do you agree to the terms of the Minecraft End User License Agreement?" msg = "Do you agree to the terms of the Minecraft\nEnd User License Agreement?"
noButton = Button("I Disagree") noButton = Button("I Disagree")
noButton.onAction = EventHandler<ActionEvent>() { noButton.onAction =
result = false EventHandler<ActionEvent>() {
dialog.hide() result = false
} dialog.hide()
}
noButton.isDefaultButton = true noButton.isDefaultButton = true
yesButton = Button("I Agree") yesButton = Button("I Agree")
yesButton.onAction = EventHandler<ActionEvent>() { yesButton.onAction =
result = true EventHandler<ActionEvent>() {
dialog.hide() result = true
} dialog.hide()
}
neutralButton = Button("View EULA") neutralButton = Button("View EULA")
neutralButton.onAction = EventHandler<ActionEvent>() { neutralButton.onAction =
val desktop = Desktop.getDesktop() EventHandler<ActionEvent>() {
if (desktop.isSupported(Desktop.Action.BROWSE)) { val desktop = Desktop.getDesktop()
// most likely running on Windows or macOS if (desktop.isSupported(Desktop.Action.BROWSE)) {
try { // most likely running on Windows or macOS
desktop.browse(URI("https://account.mojang.com/documents/minecraft_eula")) try {
} catch (e: Exception) { desktop.browse(URI("https://www.minecraft.net/en-us/eula"))
println(e) } catch (e: Exception) {
} println(e)
} else { }
// assume running on linux } else {
try { // assume running on linux
Runtime.getRuntime().exec("xdg-open https://account.mojang.com/documents/minecraft_eula"); try {
} catch (e: Exception) { Runtime.getRuntime().exec("xdg-open https://www.minecraft.net/en-us/eula")
println(e) } catch (e: Exception) {
} println(e)
} }
} }
}
} }
}
}

View file

@ -1,10 +1,10 @@
package xyz.brysonsteck.ServerCraft.dialogs package xyz.brysonsteck.ServerCraft.dialogs
import javafx.scene.control.Button
import javafx.event.EventHandler
import javafx.event.ActionEvent import javafx.event.ActionEvent
import javafx.event.EventHandler
import javafx.scene.control.Button
class KillDialog: Dialog { class KillDialog : Dialog {
override val type: String override val type: String
override val title: String override val title: String
override val msg: String override val msg: String
@ -15,20 +15,23 @@ class KillDialog: Dialog {
constructor() { constructor() {
type = "warning" type = "warning"
title = "Server is still running!" title = "Server is still running!"
msg = "You should only kill the server if absolutely necessary, i.e. not being responsive after long periods of time. Data loss may occur. Continue anyway?" msg =
"You should only kill the server if absolutely necessary, i.e. not being responsive after long periods of time. Data loss may occur. Continue anyway?"
hold = false hold = false
noButton = Button("No") noButton = Button("No")
noButton.onAction = EventHandler<ActionEvent>() { noButton.onAction =
result = false EventHandler<ActionEvent>() {
dialog.hide() result = false
} dialog.hide()
}
yesButton = Button("Yes") yesButton = Button("Yes")
yesButton.onAction = EventHandler<ActionEvent>() { yesButton.onAction =
result = true EventHandler<ActionEvent>() {
dialog.hide() result = true
} dialog.hide()
}
yesButton.isDefaultButton = true yesButton.isDefaultButton = true
neutralButton = null neutralButton = null
} }
} }

View file

@ -1,13 +1,17 @@
package xyz.brysonsteck.ServerCraft.server package xyz.brysonsteck.ServerCraft.server
import java.io.*; import java.io.*
import java.net.*; import java.net.*
import java.util.*; import java.util.*
import javax.net.ssl.HttpsURLConnection import javax.net.ssl.HttpsURLConnection
class Download: Runnable { class Download : Runnable {
public enum class Status { public enum class Status {
DOWNLOADING, PAUSED, COMPLETE, CANCELLED, ERROR DOWNLOADING,
PAUSED,
COMPLETE,
CANCELLED,
ERROR
} }
public var size: Int public var size: Int
public var downloaded: Int public var downloaded: Int
@ -19,7 +23,7 @@ class Download: Runnable {
private var url: URL private var url: URL
private var dir: String private var dir: String
constructor (url: URL, dir: String) { constructor(url: URL, dir: String) {
this.url = url this.url = url
this.dir = dir this.dir = dir
size = -1 size = -1
@ -38,19 +42,19 @@ class Download: Runnable {
return filename.substring(filename.lastIndexOf('/') + 1) return filename.substring(filename.lastIndexOf('/') + 1)
} }
override fun run() { override fun run() {
var stream: InputStream? = null var stream: InputStream? = null
var file: RandomAccessFile? = null var file: RandomAccessFile? = null
try { try {
// Open connection to URL. // Open connection to URL.
var connection = url.openConnection() as HttpsURLConnection; var connection = url.openConnection() as HttpsURLConnection
// Specify what portion of file to download. // Specify what portion of file to download.
connection.setRequestProperty("Range", "bytes=" + downloaded + "-"); connection.setRequestProperty("Range", "bytes=" + downloaded + "-")
// Connect to server. // Connect to server.
connection.connect(); connection.connect()
// Make sure response code is in the 200 range. // Make sure response code is in the 200 range.
if (connection.responseCode / 100 != 2) { if (connection.responseCode / 100 != 2) {
@ -58,64 +62,63 @@ class Download: Runnable {
} }
// Check for valid content length. // Check for valid content length.
contentLength = connection.getContentLength(); contentLength = connection.getContentLength()
if (contentLength < 1) { if (contentLength < 1) {
status = Status.ERROR status = Status.ERROR
} }
/* Set the size for this download if it /* Set the size for this download if it
hasn't been already set. */ hasn't been already set. */
if (size == -1) { if (size == -1) {
size = contentLength; size = contentLength
} }
// Open file and seek to the end of it. // Open file and seek to the end of it.
file = RandomAccessFile(dir + getFilename(url), "rw"); file = RandomAccessFile(dir + getFilename(url), "rw")
file.seek(downloaded.toLong()); file.seek(downloaded.toLong())
stream = connection.getInputStream(); stream = connection.getInputStream()
while (status == Status.DOWNLOADING) { while (status == Status.DOWNLOADING) {
/* Size buffer according to how much of the /* Size buffer according to how much of the
file is left to download. */ file is left to download. */
val buffer: ByteArray; val buffer: ByteArray
if (size - downloaded > MAX_BUFFER_SIZE as Int) { if (size - downloaded > MAX_BUFFER_SIZE as Int) {
buffer = ByteArray(MAX_BUFFER_SIZE) buffer = ByteArray(MAX_BUFFER_SIZE)
} else { } else {
buffer = ByteArray(size - downloaded); buffer = ByteArray(size - downloaded)
} }
// Read from server into buffer. // Read from server into buffer.
val read = stream.read(buffer); val read = stream.read(buffer)
if (read == -1) if (read == -1) break
break;
// Write buffer to file. // Write buffer to file.
file.write(buffer, 0, read); file.write(buffer, 0, read)
downloaded += read; downloaded += read
} }
/* Change status to complete if this point was /* Change status to complete if this point was
reached because downloading has finished. */ reached because downloading has finished. */
if (status == Status.DOWNLOADING) { if (status == Status.DOWNLOADING) {
status = Status.COMPLETE; status = Status.COMPLETE
} }
} catch (e: Exception) { } catch (e: Exception) {
println(e) println(e)
status = Status.ERROR status = Status.ERROR
} finally { } finally {
// Close file. // Close file.
if (file != null) { if (file != null) {
try { try {
file.close(); file.close()
} catch (e: Exception) {} } catch (e: Exception) {}
} }
// Close connection to server. // Close connection to server.
if (stream != null) { if (stream != null) {
try { try {
stream.close(); stream.close()
} catch (e: Exception) {} } catch (e: Exception) {}
} }
} }
} }
} }

View file

@ -1,7 +1,6 @@
package xyz.brysonsteck.ServerCraft.server package xyz.brysonsteck.ServerCraft.server
import java.io.File import java.io.File
import java.io.InputStream
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.util.Properties import java.util.Properties
@ -41,19 +40,34 @@ public class Server {
var ins = File(dir + File.separator + "server.properties").inputStream() var ins = File(dir + File.separator + "server.properties").inputStream()
props.load(ins) props.load(ins)
try { try {
ins = File(dir + File.separator + "ServerCraft" + File.separator + "ServerCraft.properties").inputStream() ins =
File(dir + File.separator + "ServerCraft" + File.separator + "ServerCraft.properties")
.inputStream()
props.load(ins) props.load(ins)
} catch (e: FileNotFoundException) { } catch (e: FileNotFoundException) {
if (convert) { if (convert) {
// create the file in question, as this is an external server being converted into a ServerCraft managed one // create the file in question, as this is an external server being converted into a
File(dir + File.separator + "ServerCraft" + File.separator + "ServerCraft.properties").createNewFile() // ServerCraft managed one
File(dir + File.separator + "ServerCraft" + File.separator + "ServerCraft.properties")
.createNewFile()
// also apply app-specific properties // also apply app-specific properties
props.setProperty("jvm-ram", 1024.toString()) props.setProperty("jvm-ram", 1024.toString())
// then write to file // then write to file
val temp = Properties() val temp = Properties()
val outs = File(dir + File.separator + "ServerCraft" + File.separator + "ServerCraft.properties").outputStream() val outs =
File(
dir +
File.separator +
"ServerCraft" +
File.separator +
"ServerCraft.properties"
)
.outputStream()
temp.setProperty("jvm-ram", props.getProperty("jvm-ram")) temp.setProperty("jvm-ram", props.getProperty("jvm-ram"))
temp.store(outs, "ServerCraft settings backup\nSometimes, Minecraft will completely overwrite the server.properties file,\ncompletely destroying these app-specific settings. This file backs up these settings\njust in case. :)\nhttps://codeberg.org/brysonsteck/ServerCraft") temp.store(
outs,
"ServerCraft settings backup\nSometimes, Minecraft will completely overwrite the server.properties file,\ncompletely destroying these app-specific settings. This file backs up these settings\njust in case. :)\nhttps://codeberg.org/brysonsteck/ServerCraft"
)
} else { } else {
println(e) println(e)
} }
@ -62,11 +76,19 @@ public class Server {
private fun writeProps() { private fun writeProps() {
var outs = File(dir + File.separator + "server.properties").outputStream() var outs = File(dir + File.separator + "server.properties").outputStream()
props.store(outs, "Minecraft server properties\nCreated with ServerCraft: https://codeberg.org/brysonsteck/ServerCraft") props.store(
outs,
"Minecraft server properties\nCreated with ServerCraft: https://codeberg.org/brysonsteck/ServerCraft"
)
val temp = Properties() val temp = Properties()
outs = File(dir + File.separator + "ServerCraft" + File.separator + "ServerCraft.properties").outputStream() outs =
File(dir + File.separator + "ServerCraft" + File.separator + "ServerCraft.properties")
.outputStream()
temp.setProperty("jvm-ram", props.getProperty("jvm-ram")) temp.setProperty("jvm-ram", props.getProperty("jvm-ram"))
temp.store(outs, "ServerCraft settings backup\nSometimes, Minecraft will completely overwrite the server.properties file,\ncompletely destroying these app-specific settings. This file backs up these settings\njust in case. :)\nhttps://codeberg.org/brysonsteck/ServerCraft") temp.store(
outs,
"ServerCraft settings backup\nSometimes, Minecraft will completely overwrite the server.properties file,\ncompletely destroying these app-specific settings. This file backs up these settings\njust in case. :)\nhttps://codeberg.org/brysonsteck/ServerCraft"
)
} }
public fun getProp(prop: String): String { public fun getProp(prop: String): String {
@ -77,5 +99,4 @@ public class Server {
props.setProperty(key, value.toString()) props.setProperty(key, value.toString())
writeProps() writeProps()
} }
}
}

View file

@ -2,4 +2,4 @@
-fx-background-color: "#F4F4F4"; -fx-background-color: "#F4F4F4";
-fx-border-color: "#DCDCDC"; -fx-border-color: "#DCDCDC";
-fx-border-width: 0 0 1 0 -fx-border-width: 0 0 1 0
} }

View file

@ -0,0 +1,4 @@
.root{
-fx-font-size: 11pt;
-fx-font-family: "Inter";
}

View file

@ -8,379 +8,374 @@
<?import javafx.scene.control.ContextMenu?> <?import javafx.scene.control.ContextMenu?>
<?import javafx.scene.control.Label?> <?import javafx.scene.control.Label?>
<?import javafx.scene.control.MenuItem?> <?import javafx.scene.control.MenuItem?>
<?import javafx.scene.control.ProgressBar?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.Separator?> <?import javafx.scene.control.Separator?>
<?import javafx.scene.control.Spinner?> <?import javafx.scene.control.Spinner?>
<?import javafx.scene.control.SpinnerValueFactory.IntegerSpinnerValueFactory?> <?import javafx.scene.control.SpinnerValueFactory.IntegerSpinnerValueFactory?>
<?import javafx.scene.control.TextField?> <?import javafx.scene.control.TextField?>
<?import javafx.scene.control.TitledPane?> <?import javafx.scene.control.TitledPane?>
<?import javafx.scene.control.Tooltip?> <?import javafx.scene.control.Tooltip?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?> <?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?> <?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?> <?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?> <?import javafx.scene.text.Font?>
<Pane fx:id="primary" maxHeight="873.0" maxWidth="963.0" minHeight="713.0" minWidth="963.0" prefHeight="873.0" prefWidth="963.0" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1" fx:controller="xyz.brysonsteck.ServerCraft.controllers.PrimaryController">
<AnchorPane fx:id="primary" minHeight="200.0" minWidth="200.0" prefHeight="633.0" prefWidth="1054.0" xmlns="http://javafx.com/javafx/23.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="xyz.brysonsteck.ServerCraft.controllers.PrimaryController">
<children> <children>
<HBox fx:id="directoryPane" prefHeight="39.0" prefWidth="963.0"> <VBox AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children> <children>
<Button id="openFile" fx:id="chooseDirectoryButton" lineSpacing="10.0" mnemonicParsing="false" onAction="#onDirectoryButtonClick" text="Choose Directory..."> <HBox fx:id="directoryPane" minHeight="-Infinity" VBox.vgrow="NEVER">
<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="&lt;NONE&gt;">
<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" 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" 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" 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" onAction="#onCheckboxClick" text="Allow Flight" />
<CheckBox fx:id="netherCheckbox" layoutX="14.0" layoutY="42.0" mnemonicParsing="false" onAction="#onCheckboxClick" selected="true" text="Allow The Nether" />
<CheckBox fx:id="structuresCheckbox" alignment="TOP_LEFT" layoutX="14.0" layoutY="70.0" mnemonicParsing="false" onAction="#onCheckboxClick" selected="true" text="Generate Structures&#10;(such as villages and strongholds)" />
<CheckBox fx:id="pvpCheckbox" layoutX="14.0" layoutY="109.0" mnemonicParsing="false" onAction="#onCheckboxClick" selected="true" text="Allow PvP" />
<CheckBox fx:id="whitelistCheckbox" alignment="TOP_LEFT" layoutX="14.0" layoutY="138.0" mnemonicParsing="false" onAction="#onCheckboxClick" text="Enable Whitelist&#10;(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" onAction="#onCheckboxClick" text="Enable Command Blocks" />
<CheckBox fx:id="playerCountCheckbox" layoutX="14.0" layoutY="41.0" mnemonicParsing="false" onAction="#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.&#10;For simple servers, 1024 MB will be plenty.&#10;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. &#10;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.&#10;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.&#10;Any value higher on a client than what is set will be ignored.&#10;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. &quot;lags&quot;) for longer than this amount of time, the server will shutdown.&#10;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" onAction="#onInfo" text="About ServerCraft" ButtonBar.buttonData="LEFT" />
<Button fx:id="defaultsButton" disable="true" mnemonicParsing="false" onAction="#onDefaults" text="Reset to Defaults" ButtonBar.buttonData="LEFT" />
<Button fx:id="buildButton" disable="true" mnemonicParsing="false" onAction="#onBuild" text="Build Server" ButtonBar.buttonData="RIGHT" />
<Button fx:id="startButton" defaultButton="true" disable="true" mnemonicParsing="false" onAction="#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>
<Pane layoutY="680.0" prefHeight="196.0" prefWidth="963.0" style="-fx-background-color: ddd;">
<children>
<HBox prefWidth="963.0">
<children> <children>
<Label text="Status:"> <Button id="openFile" fx:id="chooseDirectoryButton" lineSpacing="10.0" mnemonicParsing="false" onAction="#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 maxHeight="1.7976931348623157E308" orientation="VERTICAL">
<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>
<Font name="System Bold" size="13.0" /> <Font name="System Bold" size="13.0" />
</font> </font>
</Label> </Label>
<Label fx:id="statusBar" text="Ready."> <Label id="currentFilename" fx:id="currentDirectoryLabel" text="&lt;NONE&gt;">
<HBox.margin> <HBox.margin>
<Insets left="5.0" /> <Insets bottom="5.0" right="5.0" top="5.0" />
</HBox.margin> </HBox.margin>
</Label> </Label>
<ProgressBar fx:id="progressBar" prefWidth="400.0" visible="false">
<HBox.margin>
<Insets left="10.0" />
</HBox.margin>
</ProgressBar>
</children> </children>
<padding>
<Insets bottom="9.0" left="9.0" right="9.0" top="9.0" />
</padding>
</HBox>
<ImageView fx:id="dropDownIcon" fitHeight="66.0" fitWidth="39.0" layoutX="923.0" layoutY="-3.0" onMouseClicked="#onToggleConsole" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@icons/arrow_down.png" />
</image>
</ImageView>
<ScrollPane fx:id="scrollPane" layoutY="34.0" prefHeight="162.0" prefWidth="963.0" vbarPolicy="ALWAYS">
<padding> <padding>
<Insets bottom="7.0" left="7.0" right="7.0" top="7.0" /> <Insets bottom="7.0" left="7.0" right="7.0" top="7.0" />
</padding> </padding>
<content> </HBox>
<Label fx:id="console" prefWidth="935.0" text="Console Output:&#10; " wrapText="true"> <HBox fx:id="worldSettingsPane" disable="true" minHeight="-Infinity" prefHeight="43.0" prefWidth="964.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>
<Font name="Monospaced Regular" size="13.0" /> <Font name="System Bold" size="13.0" />
</font> </font>
<HBox.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="6.0" />
</HBox.margin>
</Label> </Label>
</content> <TextField fx:id="worldNameField" text="world">
</ScrollPane> <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 prefHeight="0.0" 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" 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" 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>
<GridPane fx:id="parentPane" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" VBox.vgrow="ALWAYS">
<columnConstraints>
<ColumnConstraints halignment="CENTER" hgrow="NEVER" minWidth="10.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="317.0" minWidth="10.0" prefWidth="177.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<VBox prefHeight="505.0" prefWidth="584.0" GridPane.hgrow="ALWAYS">
<children>
<TitledPane fx:id="settingsPane" animated="false" collapsible="false" prefHeight="273.0" prefWidth="627.0" text="Server Settings">
<content>
<AnchorPane maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308">
<children>
<CheckBox fx:id="flightCheckbox" layoutX="14.0" layoutY="14.0" mnemonicParsing="false" onAction="#onCheckboxClick" text="Allow Flight" />
<CheckBox fx:id="netherCheckbox" layoutX="14.0" layoutY="42.0" mnemonicParsing="false" onAction="#onCheckboxClick" selected="true" text="Allow The Nether" />
<CheckBox fx:id="structuresCheckbox" alignment="TOP_LEFT" layoutX="14.0" layoutY="70.0" mnemonicParsing="false" onAction="#onCheckboxClick" selected="true" text="Generate Structures&#10;(such as villages and strongholds)" />
<CheckBox fx:id="pvpCheckbox" layoutX="14.0" layoutY="109.0" mnemonicParsing="false" onAction="#onCheckboxClick" selected="true" text="Allow PvP" />
<CheckBox fx:id="whitelistCheckbox" alignment="TOP_LEFT" layoutX="14.0" layoutY="138.0" mnemonicParsing="false" onAction="#onCheckboxClick" text="Enable Whitelist&#10;(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" 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" onAction="#onCheckboxClick" text="Enable Command Blocks" />
<CheckBox fx:id="playerCountCheckbox" layoutX="14.0" layoutY="41.0" mnemonicParsing="false" onAction="#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.&#10;For simple servers, 1024 MB will be plenty.&#10;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. &#10;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.&#10;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.&#10;Any value higher on a client than what is set will be ignored.&#10;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. &quot;lags&quot;) for longer than this amount of time, the server will shutdown.&#10;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>
<padding>
<Insets top="7.0" />
</padding>
</TitledPane>
</children>
<padding>
<Insets bottom="7.0" left="7.0" right="7.0" top="7.0" />
</padding>
</VBox>
<VBox GridPane.columnIndex="1" GridPane.hgrow="ALWAYS">
<children>
<TitledPane fx:id="difficultyPane" animated="false" collapsible="false" text="Difficulty" VBox.vgrow="NEVER">
<content>
<AnchorPane minHeight="0.0" minWidth="0.0">
<children>
<ChoiceBox fx:id="difficultyBox" layoutX="14.0" layoutY="14.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<contextMenu>
<ContextMenu>
<items>
<MenuItem mnemonicParsing="false" />
</items>
</ContextMenu>
</contextMenu>
</ChoiceBox>
</children>
</AnchorPane>
</content>
</TitledPane>
<TitledPane fx:id="gamemodePane" animated="false" collapsible="false" text="Gamemode" VBox.vgrow="NEVER">
<content>
<AnchorPane>
<children>
<ChoiceBox fx:id="gamemodeBox" layoutX="14.0" layoutY="14.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
</content>
<padding>
<Insets top="7.0" />
</padding>
</TitledPane>
<TitledPane fx:id="worldTypePane" animated="false" collapsible="false" text="World Type">
<content>
<AnchorPane minHeight="0.0" minWidth="0.0">
<children>
<ChoiceBox fx:id="worldTypeBox" layoutX="14.0" layoutY="14.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
</content>
<padding>
<Insets top="7.0" />
</padding>
</TitledPane>
</children>
<GridPane.margin>
<Insets bottom="7.0" left="7.0" right="7.0" top="7.0" />
</GridPane.margin>
</VBox>
</children>
</GridPane>
<HBox maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" VBox.vgrow="NEVER">
<children>
<ButtonBar fx:id="buttonBar" buttonMinWidth="75.0" buttonOrder="L+R" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" HBox.hgrow="ALWAYS">
<buttons>
<Button fx:id="infoButton" mnemonicParsing="false" onAction="#onInfo" text="About ServerCraft" ButtonBar.buttonData="LEFT" />
<Button fx:id="defaultsButton" disable="true" mnemonicParsing="false" onAction="#onDefaults" text="Reset to Defaults" ButtonBar.buttonData="LEFT" />
<Button fx:id="buildButton" disable="true" mnemonicParsing="false" onAction="#onBuild" text="Build Server" ButtonBar.buttonData="RIGHT" />
<Button fx:id="startButton" defaultButton="true" disable="true" mnemonicParsing="false" onAction="#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>
</children>
</HBox>
</children> </children>
</Pane> </VBox>
</children> </children>
</Pane> </AnchorPane>