aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/xyz/brysonsteck
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/kotlin/xyz/brysonsteck')
-rw-r--r--src/main/kotlin/xyz/brysonsteck/ServerCraft/controllers/PrimaryController.kt245
-rw-r--r--src/main/kotlin/xyz/brysonsteck/ServerCraft/server/Download.kt6
-rw-r--r--src/main/kotlin/xyz/brysonsteck/ServerCraft/server/Server.kt24
3 files changed, 237 insertions, 38 deletions
diff --git a/src/main/kotlin/xyz/brysonsteck/ServerCraft/controllers/PrimaryController.kt b/src/main/kotlin/xyz/brysonsteck/ServerCraft/controllers/PrimaryController.kt
index 99d1009..d445923 100644
--- a/src/main/kotlin/xyz/brysonsteck/ServerCraft/controllers/PrimaryController.kt
+++ b/src/main/kotlin/xyz/brysonsteck/ServerCraft/controllers/PrimaryController.kt
@@ -32,6 +32,7 @@ import javafx.scene.control.ButtonBar
import javafx.scene.control.CheckBox
import javafx.scene.control.ProgressBar
import javafx.scene.control.Hyperlink
+import javafx.scene.control.ScrollPane
import javafx.scene.layout.Border
import javafx.scene.layout.BorderStroke
import javafx.scene.layout.GridPane
@@ -50,6 +51,7 @@ import javafx.stage.DirectoryChooser
import javafx.stage.Modality
import javafx.stage.Stage
import javafx.event.EventHandler
+import javafx.event.ActionEvent
import org.rauschig.jarchivelib.*
import xyz.brysonsteck.ServerCraft.server.Server
@@ -58,6 +60,8 @@ import xyz.brysonsteck.ServerCraft.App
class PrimaryController {
@FXML
+ lateinit private var primary: Pane
+ @FXML
lateinit private var currentDirectoryLabel: Label
@FXML
lateinit private var worldNameField: TextField
@@ -94,7 +98,7 @@ class PrimaryController {
@FXML
lateinit private var playerCountCheckbox: CheckBox
@FXML
- lateinit private var maxPlayersSpinner: Spinner<kotlin.Int>
+ lateinit private var maxPlayerSpinner: Spinner<kotlin.Int>
@FXML
lateinit private var maxSizeSpinner: Spinner<kotlin.Int>
@FXML
@@ -117,15 +121,29 @@ class PrimaryController {
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
private var building = false
private var directory = ""
private var asyncResult = false
private var started = false
+ private var loading = false
+ private var showingConsole = false
+
+ private fun log(str: String) {
+ console.text = console.text + str + "\n"
+ println(str)
+ }
@FXML
public fun initialize() {
+ scrollPane.vvalueProperty().bind(console.heightProperty());
difficultyBox.items = FXCollections.observableArrayList(
"Peaceful",
"Easy",
@@ -135,7 +153,9 @@ class PrimaryController {
)
difficultyBox.value = "Normal"
difficultyBox.selectionModel.selectedIndexProperty().addListener { _, _, new ->
- onChoiceBoxChange("difficulty", difficultyBox.items[new as Int])
+ if (!loading) {
+ onPropChange("difficulty", difficultyBox.items[new as Int])
+ }
}
gamemodeBox.items = FXCollections.observableArrayList(
"Survival",
@@ -145,7 +165,9 @@ class PrimaryController {
)
gamemodeBox.value = "Survival"
gamemodeBox.selectionModel.selectedIndexProperty().addListener { _, _, new ->
- onChoiceBoxChange("gamemode", gamemodeBox.items[new as Int])
+ if (!loading) {
+ onPropChange("gamemode", gamemodeBox.items[new as Int])
+ }
}
worldTypeBox.items = FXCollections.observableArrayList(
"Normal",
@@ -155,7 +177,59 @@ class PrimaryController {
)
worldTypeBox.value = "Normal"
worldTypeBox.selectionModel.selectedIndexProperty().addListener { _, _, new ->
- onChoiceBoxChange("world-type", worldTypeBox.items[new as Int])
+ if (!loading) {
+ onPropChange("level-type", worldTypeBox.items[new as Int])
+ }
+ }
+ maxPlayerSpinner.editor.textProperty().addListener { _, _, new ->
+ if (!loading) {
+ onPropChange("max-players", new)
+ }
+ }
+ maxSizeSpinner.editor.textProperty().addListener { _, _, new ->
+ if (!loading) {
+ onPropChange("max-world-size", new)
+ }
+ }
+ portSpinner.editor.textProperty().addListener { _, _, new ->
+ if (!loading) {
+ onPropChange("server-port", new)
+ }
+ }
+ renderSpinner.editor.textProperty().addListener { _, _, new ->
+ if (!loading) {
+ onPropChange("view-distance", new)
+ }
+ }
+ memorySpinner.editor.textProperty().addListener { _, _, new ->
+ if (!loading) {
+ onPropChange("jvm-ram", new)
+ }
+ }
+ spawnSpinner.editor.textProperty().addListener { _, _, new ->
+ if (!loading) {
+ onPropChange("spawn-protection", new)
+ }
+ }
+ simulationSpinner.editor.textProperty().addListener { _, _, new ->
+ if (!loading) {
+ onPropChange("simulation-distance", new)
+ }
+ }
+ maxTickSpinner.editor.textProperty().addListener { _, _, new ->
+ if (!loading) {
+ onPropChange("max-tick-time", new)
+ }
+ }
+ worldNameField.textProperty().addListener { _, _, new ->
+ if (!loading) {
+ onPropChange("level-name", new)
+ }
+ }
+ seedField.textProperty().addListener { _, _, new ->
+ if (!loading) {
+ onPropChange("level-seed", new)
+ }
}
}
@@ -174,6 +248,8 @@ class PrimaryController {
worldSettingsPane.isDisable = false
buildButton.isDisable = false
defaultsButton.isDisable = false
+ applyProps()
+ server.dir = result.absolutePath
} else {
currentDirectoryLabel.text = "<NONE>"
parentPane.isDisable = true
@@ -185,33 +261,114 @@ class PrimaryController {
}
}
- @FXML
- private fun onWorldNameChange() {
-
+ private fun parseBool(bool: String): Boolean {
+ if (bool == "true") {
+ return true
+ }
+ return false
}
- @FXML
- private fun onSeedChange() {
-
+ private fun applyProps() {
+ loading = true
+ flightCheckbox.isSelected = parseBool(server.getProp("allow-flight"))
+ netherCheckbox.isSelected = parseBool(server.getProp("allow-nether"))
+ structuresCheckbox.isSelected = parseBool(server.getProp("generate-structures"))
+ pvpCheckbox.isSelected = parseBool(server.getProp("pvp"))
+ whitelistCheckbox.isSelected = parseBool(server.getProp("white-list"))
+ cmdBlocksCheckbox.isSelected = parseBool(server.getProp("enable-command-block"))
+ playerCountCheckbox.isSelected = parseBool(server.getProp("hide-online-players"))
+ maxPlayerSpinner.valueFactory.value = server.getProp("max-players").toIntOrNull()
+ maxSizeSpinner.valueFactory.value = server.getProp("max-world-size").toIntOrNull()
+ portSpinner.valueFactory.value = server.getProp("server-port").toIntOrNull()
+ renderSpinner.valueFactory.value = server.getProp("view-distance").toIntOrNull()
+ memorySpinner.valueFactory.value = server.getProp("jvm-ram").toIntOrNull()
+ spawnSpinner.valueFactory.value = server.getProp("spawn-protection").toIntOrNull()
+ simulationSpinner.valueFactory.value = server.getProp("simulation-distance").toIntOrNull()
+ maxTickSpinner.valueFactory.value = server.getProp("max-tick-time").toIntOrNull()
+ difficultyBox.value = if (parseBool(server.getProp("hardcore"))) {
+ "Hardcore"
+ } else {
+ server.getProp("difficulty").replaceFirstChar { it.uppercase() }
+ }
+ gamemodeBox.value = server.getProp("gamemode").replaceFirstChar { it.uppercase() }
+ worldTypeBox.value = server.getProp("level-type").removePrefix("minecraft:")
+ .split('_').joinToString(" ") { it.replaceFirstChar(Char::uppercaseChar)}
+ worldNameField.text = server.getProp("level-name")
+ seedField.text = server.getProp("level-seed")
+ loading = false
}
@FXML
- private fun onPortChange() {
-
+ private fun onCheckboxClick(e: ActionEvent) {
+ val box = e.target as CheckBox
+ when {
+ box == whitelistCheckbox -> {
+ server.setProp("white-list", whitelistCheckbox.isSelected)
+ }
+ box == pvpCheckbox -> {
+ server.setProp("pvp", pvpCheckbox.isSelected)
+ }
+ box == netherCheckbox -> {
+ server.setProp("allow-nether", netherCheckbox.isSelected)
+ }
+ box == cmdBlocksCheckbox -> {
+ server.setProp("enable-command-block", cmdBlocksCheckbox.isSelected)
+ }
+ box == flightCheckbox -> {
+ server.setProp("allow-flight", flightCheckbox.isSelected)
+ }
+ box == structuresCheckbox -> {
+ server.setProp("generate-structures", structuresCheckbox.isSelected)
+ }
+ box == playerCountCheckbox -> {
+ server.setProp("hide-online-players", playerCountCheckbox.isSelected)
+ }
+ }
}
- @FXML
- private fun onCheckboxClick() {
-
+ private fun onPropChange(prop: String, value: String) {
+ when {
+ prop == "gamemode" -> {
+ server.setProp(prop, value.lowercase())
+ }
+ prop == "difficulty" -> {
+ if (value == "Hardcore") {
+ server.setProp("hardcode", "true")
+ server.setProp(prop, "hard")
+ } else {
+ server.setProp("hardcode", "false")
+ server.setProp(prop, value.lowercase())
+ }
+ }
+ prop == "level-type" -> {
+ server.setProp(prop, "minecraft:" + value.lowercase().replace(" ", "_"))
+ }
+ else -> {
+ server.setProp(prop, value)
+ }
+ }
}
@FXML
- private fun onSpinnerChange() {
-
+ private fun onToggleConsole() {
+ if (showingConsole) {
+ primary.getScene().window.height = 743.0
+ dropDownIcon.image = Image(App().javaClass.getResourceAsStream("icons/arrow_down.png"))
+ } else {
+ primary.getScene().window.height = 905.0
+ dropDownIcon.image = Image(App().javaClass.getResourceAsStream("icons/arrow_up.png"))
+ }
+ showingConsole = !showingConsole
}
- private fun onChoiceBoxChange(box: String, selection: String) {
-
+ @FXML
+ private fun onDefaults() {
+ val res = createDialog("info", "Reset settings to defaults?\nThere is NO GOING BACK!")
+ if (res) {
+ server.loadProps()
+ applyProps()
+ statusBar.text = "Resetting settings to defaults successful."
+ }
}
@FXML
@@ -280,19 +437,21 @@ class PrimaryController {
withContext(Dispatchers.JavaFx){
statusBar.text = "Downloading ${it.key}..."
progressBar.progress = ProgressBar.INDETERMINATE_PROGRESS
+ log("Downloading ${it.key} from ${it.value}")
}
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("")
+ withContext(Dispatchers.JavaFx) {log("Progress: ${prog * 100}%")}
if (prog >= 0.01) {
withContext(Dispatchers.JavaFx) {progressBar.progress = prog}
}
if (!building) download.status = Download.Status.CANCELLED
Thread.sleep(300)
}
+ withContext(Dispatchers.JavaFx) {log("Download of ${it.key} complete with status: ${download.status}")}
}
// extract java archive
@@ -300,6 +459,7 @@ class PrimaryController {
withContext(Dispatchers.JavaFx) {
progressBar.progress = ProgressBar.INDETERMINATE_PROGRESS
statusBar.text = "Extracting Java archive..."
+ log("Extracting Java archive to ${directory + "ServerCraft" + File.separator + "Java"}")
}
var stream = archiver.stream(File(directory + "ServerCraft" + File.separator + "Java" + File.separator + javaFile))
val dest = File(directory + "ServerCraft" + File.separator + "Java")
@@ -311,7 +471,10 @@ class PrimaryController {
var entry = stream.getNextEntry()
var currentEntry = 0.0
do {
- withContext(Dispatchers.JavaFx) {progressBar.progress = currentEntry/entries}
+ withContext(Dispatchers.JavaFx) {
+ progressBar.progress = currentEntry/entries
+ log(entry.name)
+ }
entry.extract(dest)
entry = stream.getNextEntry()
currentEntry++
@@ -335,7 +498,7 @@ class PrimaryController {
if (!building) {
proc.destroy()
}
- println(line)
+ withContext(Dispatchers.JavaFx) {log(line)}
line = br.readLine()
currentline++
if (currentline > 15) {
@@ -343,7 +506,7 @@ class PrimaryController {
}
}
} catch (e: IOException) {
- println("Stream closed")
+ withContext(Dispatchers.JavaFx) {log("Stream Closed")}
}
}
@@ -370,7 +533,7 @@ class PrimaryController {
@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)
+ createDialog("warning", "You should only kill the server if\nabsolutely necessary. Data loss may occur.\nContinue anyway?", hold=false)
return;
}
if (!File(directory + "eula.txt").exists()) {
@@ -391,7 +554,7 @@ class PrimaryController {
startButton.text = "Kill Server"
@Suppress("OPT_IN_USAGE")
GlobalScope.launch(Dispatchers.Default) {
- val builder = ProcessBuilder("java", "-jar", "${server.jar}")
+ val builder = ProcessBuilder("java", "-Xmx${server.getProp("jvm-ram")}M", "-jar", "${server.jar}")
builder.directory(File(directory))
val proc = builder.start()
val reader = InputStreamReader(proc.inputStream)
@@ -406,11 +569,11 @@ class PrimaryController {
}
proc.destroy()
}
- println(line);
+ withContext(Dispatchers.JavaFx) {log(line)}
line = br.readLine()
}
} catch (e: IOException) {
- println("Stream closed")
+ withContext(Dispatchers.JavaFx) {log("Stream Closed")}
}
withContext(Dispatchers.JavaFx) {
statusBar.text = if (asyncResult) {
@@ -459,18 +622,18 @@ class PrimaryController {
buttonBar.layoutY = 107.0
buttonBar.prefWidth = 400.0
val noButton = Button("I Disagree")
- noButton.onMouseClicked = EventHandler<MouseEvent>() {
+ noButton.onAction = EventHandler<ActionEvent>() {
result = false
dialog.hide()
}
noButton.isDefaultButton = true
val yesButton = Button("I Agree")
- yesButton.onMouseClicked = EventHandler<MouseEvent>() {
+ yesButton.onAction = EventHandler<ActionEvent>() {
result = true
dialog.hide()
}
val eula = Button("View EULA")
- eula.onMouseClicked = EventHandler<MouseEvent>() {
+ eula.onAction = EventHandler<ActionEvent>() {
val desktop = Desktop.getDesktop()
if (desktop.isSupported(Desktop.Action.BROWSE)) {
// most likely running on Windows or macOS
@@ -500,7 +663,7 @@ class PrimaryController {
return result
}
- private fun createDialog(type: String, msg: String, yes: String, no: String, hold: Boolean): Boolean {
+ private fun createDialog(type: String, msg: String, yes: String = "Yes", no: String = "No", hold: Boolean = true): Boolean {
var result = false
val resources = App().javaClass.getResource("icons/$type.png")
val dialog = Stage()
@@ -527,7 +690,7 @@ class PrimaryController {
buttonBar.layoutY = 107.0
buttonBar.prefWidth = 400.0
val noButton = Button(no)
- noButton.onMouseClicked = EventHandler<MouseEvent>() {
+ noButton.onAction = EventHandler<ActionEvent>() {
if (hold) {
result = false
} else {
@@ -536,7 +699,7 @@ class PrimaryController {
dialog.hide()
}
val yesButton = Button(yes)
- yesButton.onMouseClicked = EventHandler<MouseEvent>() {
+ yesButton.onAction = EventHandler<ActionEvent>() {
if (hold) {
result = true
} else {
@@ -559,10 +722,12 @@ class PrimaryController {
private fun loadServerDir(dir: String): Boolean {
directory = dir
+ // exit if doesn't exist for whatever reason
if (!File(directory).isDirectory) {
return false;
}
+ // add system dir separator for cleaner code
if (directory[directory.length-1] != File.separatorChar)
directory += File.separatorChar
@@ -570,7 +735,7 @@ class PrimaryController {
val hasProperties = File(directory + File.separator + "server.properties").isFile
val hasServer = findServerJar()
- if (hasDummy && hasServer) {
+ if (hasDummy && hasServer && hasProperties) {
// server complete, just read jproperties
statusBar.text = "Server found!"
startButton.isDisable = false
@@ -581,24 +746,32 @@ class PrimaryController {
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)
+ 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?")
statusBar.text = "Ready."
if (result) {
startButton.isDisable = false
}
+ server.loadProps(dir)
return result
} else {
// assume clean directory
- val result = createDialog("info", "There is no server in this directory.\nCreate one?", "Yes", "No", true)
+ val result = createDialog("info", "There is no server in this directory.\nCreate one?")
if (result) {
File(directory + "ServerCraft").mkdir()
startButton.isDisable = true
buildButton.text = "Build Server"
}
statusBar.text = "Ready."
+ server.loadProps()
return result
}
+ if (hasProperties) {
+ server.loadProps(dir)
+ } else {
+ server.loadProps()
+ }
+
return true;
}
diff --git a/src/main/kotlin/xyz/brysonsteck/ServerCraft/server/Download.kt b/src/main/kotlin/xyz/brysonsteck/ServerCraft/server/Download.kt
index 757c32c..f538458 100644
--- a/src/main/kotlin/xyz/brysonsteck/ServerCraft/server/Download.kt
+++ b/src/main/kotlin/xyz/brysonsteck/ServerCraft/server/Download.kt
@@ -3,6 +3,7 @@ package xyz.brysonsteck.ServerCraft.server
import java.io.*;
import java.net.*;
import java.util.*;
+import javax.net.ssl.HttpsURLConnection
class Download: Runnable {
public enum class Status {
@@ -43,7 +44,7 @@ class Download: Runnable {
try {
// Open connection to URL.
- var connection = url.openConnection() as HttpURLConnection;
+ var connection = url.openConnection() as HttpsURLConnection;
// Specify what portion of file to download.
connection.setRequestProperty("Range", "bytes=" + downloaded + "-");
@@ -53,12 +54,14 @@ class Download: Runnable {
// Make sure response code is in the 200 range.
if (connection.responseCode / 100 != 2) {
+ println(connection.responseCode)
status = Status.ERROR
}
// Check for valid content length.
contentLength = connection.getContentLength();
if (contentLength < 1) {
+ println(connection.getContentLength())
status = Status.ERROR
}
@@ -99,6 +102,7 @@ class Download: Runnable {
status = Status.COMPLETE;
}
} catch (e: Exception) {
+ println(e)
status = Status.ERROR
} finally {
// Close file.
diff --git a/src/main/kotlin/xyz/brysonsteck/ServerCraft/server/Server.kt b/src/main/kotlin/xyz/brysonsteck/ServerCraft/server/Server.kt
index 746bc55..b591074 100644
--- a/src/main/kotlin/xyz/brysonsteck/ServerCraft/server/Server.kt
+++ b/src/main/kotlin/xyz/brysonsteck/ServerCraft/server/Server.kt
@@ -1,14 +1,16 @@
package xyz.brysonsteck.ServerCraft.server
import java.io.File
+import java.io.InputStream
import java.util.Properties
public class Server {
public var jar = ""
+ public var dir = ""
private val props = Properties()
- constructor() {
+ public fun loadProps() {
props.setProperty("allow-flight", false.toString())
props.setProperty("allow-nether", true.toString())
props.setProperty("generate-structures", true.toString())
@@ -32,4 +34,24 @@ public class Server {
props.setProperty("level-type", "minecraft:normal")
props.setProperty("motd", "A server for a dummy")
}
+
+ public fun loadProps(dir: String) {
+ val ins = File(dir + File.separator + "server.properties").inputStream()
+ props.load(ins)
+ }
+
+ private fun writeProps() {
+ val outs = File(dir + File.separator + "server.properties").outputStream()
+ props.store(outs, "Minecraft server properties\nCreated with ServerCraft: https://codeberg.org/brysonsteck/ServerCraft")
+ }
+
+ public fun getProp(prop: String): String {
+ return props.getProperty(prop)
+ }
+
+ public fun setProp(key: String, value: Any) {
+ props.setProperty(key, value.toString())
+ writeProps()
+ }
+
} \ No newline at end of file