Compare commits

..

10 commits

17 changed files with 386 additions and 183 deletions

3
.gitignore vendored
View file

@ -7,3 +7,6 @@ bin
# Ignore VSCode settings
.vscode
# ignore local releases dir for Codeberg releases
releases

View file

@ -4,14 +4,42 @@ Is the `server.properties` file too overwhelming? Here is a graphical applicatio
## About
ServerCraft is designed for the layman who has knowledge of Minecraft but has no idea how to create a simple server.
ServerCraft is designed for the layman who has knowledge of Minecraft but has no idea how to create a simple server. It provides a simple interface for running a Minecraft server and managing it's settings, all automated with simple clicks.
## Features
* Utilizes the Spigot Minecraft server for performance and extensibility
* Automatically downloads the correct Java version, no more confusing errors when trying to run the server
* Automatically downloads the correct Java version, no more confusing errors when trying to run a server
* Common settings available at a glance
* Separate section for common settings that are a little more advanced, such as:
* The amount of RAM to allocate to the server
* Render and simulation distances
*
* Enabling command blocks
## Screenshots
provide screenshots
## Building and Running
Pre-compiled binaries are available to download [here](https://codeberg.org/brysonsteck/ServerCraft/releases). Simply download the correct file for your OS. (`.exe` or `.msi` for Windows, `.dmg` or `.pkg` for macOS, etc.)
ServerCraft uses Gradle as the build system of choice. At the very least, you will need to install at least Java 11. ServerCraft is built on OpenJFX, of which requires Java 11 or later. The binaries I offer on the [releases page](https://codeberg.org/brysonsteck/ServerCraft/releases) are bundled with Java 17 and do not require installing Java to run.
However, installing Gradle is not necessary. You can replace `gradle` in the following instructions with `./gradlew` on Unix systems and `.\gradlew.bat` on Windows systems, as long as your current directory is the root of the repository. These scripts download and run a Gradle jar file.
To build ServerCraft from source, creating a fat jar, Java class files, etc., run:
```
gradle build
```
To run ServerCraft from source, run:
```
gradle run
```
To compile binaries for the current system, run:
```
gradle pack
```
**NOTE**: Building binaries on Windows requires the WiX toolset and Inno Setup to be installed and on the PATH. See this JavaPackager [GitHub page](https://github.com/fvarrui/JavaPackager/blob/master/docs/windows-tools-guide.md) for installation instructions.

View file

@ -32,8 +32,6 @@ plugins {
apply plugin: 'io.github.fvarrui.javapackager.plugin'
version = "1.0"
repositories {
// Use Maven Central for resolving dependencies.
mavenCentral()
@ -68,8 +66,6 @@ distTar {
}
jar {
// archiveFileName = 'ServerCraft.jar'
manifest {
attributes 'Main-Class': application.mainClass
}
@ -89,7 +85,7 @@ task pack(type: io.github.fvarrui.javapackager.gradle.PackageTask, dependsOn: bu
organizationUrl = "https://brysonsteck.xyz"
organizationEmail = "me@brysonsteck.xyz"
url = "https://codeberg.org/brysonsteck/ServerCraft"
additionalModules = [ "jdk.crypto.ec" ]
additionalModules = [ "java.management", "jdk.crypto.ec" ]
linuxConfig {
pngFile = file('src/main/resources/icon.png')
@ -114,6 +110,32 @@ task pack(type: io.github.fvarrui.javapackager.gradle.PackageTask, dependsOn: bu
}
}
task createProperties(dependsOn: processResources) {
doLast {
def stdout = new ByteArrayOutputStream()
if (project.hasProperty("release")) {
exec {
commandLine "git", "describe", "--tags", "--abbrev=0"
standardOutput = stdout
}
project.version = stdout.toString().trim().replaceAll("\"", "")
} else {
exec {
commandLine "git", "log", "-n", "1", "--pretty=format:\"%h\""
standardOutput = stdout
}
project.version = "git(" + stdout.toString().trim().replaceAll("\"", "") + ")"
}
new File("$buildDir/resources/main/xyz/brysonsteck/ServerCraft/info.properties").withWriter { w ->
Properties p = new Properties()
p['version'] = project.version.toString()
p.store w, null
}
}
}
build.doLast {
if (OperatingSystem.current().isLinux()) {
exec {
@ -127,6 +149,10 @@ build.doLast {
}
}
classes {
dependsOn createProperties
}
javafx {
version = "20"
modules = ['javafx.controls', 'javafx.fxml', 'javafx.graphics']

14
docs/features.md Normal file
View file

@ -0,0 +1,14 @@
# Features
Here are all the features currently implemented/planned for ServerCraft:
* [X] Change `server.properties` file
* [X] Adapt controls based on what properties are found
* [X] Save server-specific settings in subdirectory
* [X] Allow changing memory passed into JVM
* [ ] Manage port forwarding automatically using UPnP
* [ ] Create system to check for updates
* [ ] Add and remove Spigot server mods/plugins
* [ ] Create servers of any Minecraft version >= 1.8
* [ ] "Advanced mode" with all settings editable
* [ ] Recognize existing server jars and give option to run them instead

View file

@ -4,12 +4,18 @@ import javafx.fxml.FXML
import javafx.application.Platform
import javafx.scene.Node
import javafx.scene.control.Hyperlink
import javafx.scene.control.Label
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
class InfoController {
@FXML
lateinit private var version: Label
private val emails = mapOf(
"bryson" to "me@brysonsteck.xyz"
)
@ -19,6 +25,13 @@ class InfoController {
private val source = "https://codeberg.org/brysonsteck/ServerCraft"
private val license = "https://www.gnu.org/licenses/gpl-3.0.html"
@FXML
public fun initialize() {
val props = Properties()
props.load(App().javaClass.getResourceAsStream("info.properties"))
version.text += props.getProperty("version")
}
@FXML
private fun openHyperlink(e: ActionEvent) {
val link = e.source as Hyperlink
@ -52,7 +65,6 @@ class InfoController {
}
}
split[0].equals("license") -> {
println("license")
if (!os.contains("linux")) {
desktop.browse(URI(license))
} else {

View file

@ -57,6 +57,10 @@ 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.dialogs.Dialog
import xyz.brysonsteck.ServerCraft.dialogs.CommonDialog
import xyz.brysonsteck.ServerCraft.dialogs.KillDialog
import xyz.brysonsteck.ServerCraft.dialogs.EulaDialog
class PrimaryController {
@FXML
@ -129,16 +133,19 @@ class PrimaryController {
lateinit private var scrollPane: ScrollPane
lateinit private var server: Server
lateinit private var killDialog: Dialog
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)
if (console.text.length > 100000) {
console.text = console.text.drop(str.length + 10)
}
console.text = console.text + str
print(str)
}
@FXML
@ -231,6 +238,7 @@ class PrimaryController {
onPropChange("level-seed", new)
}
}
killDialog = KillDialog()
}
@FXML
@ -333,10 +341,10 @@ class PrimaryController {
}
prop == "difficulty" -> {
if (value == "Hardcore") {
server.setProp("hardcode", "true")
server.setProp("hardcore", "true")
server.setProp(prop, "hard")
} else {
server.setProp("hardcode", "false")
server.setProp("hardcore", "false")
server.setProp(prop, value.lowercase())
}
}
@ -365,7 +373,7 @@ class PrimaryController {
@FXML
private fun onDefaults() {
val res = createDialog("info", "Reset settings to defaults?\nThere is NO GOING BACK!")
val res = CommonDialog("info", "Reset all settings?", "Reset settings to defaults?\nThere is NO GOING BACK!").show()
if (res) {
server.loadProps()
applyProps()
@ -441,21 +449,21 @@ class PrimaryController {
withContext(Dispatchers.JavaFx){
statusBar.text = "Downloading ${it.key}..."
progressBar.progress = ProgressBar.INDETERMINATE_PROGRESS
log("Downloading ${it.key} from ${it.value}")
log("Downloading ${it.key} from ${it.value}\n")
}
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
withContext(Dispatchers.JavaFx) {log("Progress: ${prog * 100}%")}
withContext(Dispatchers.JavaFx) {log("Progress: ${prog * 100}%\n")}
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}")}
withContext(Dispatchers.JavaFx) {log("Download of ${it.key} complete with status: ${download.status}\n")}
}
// extract java archive
@ -463,7 +471,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"}")
log("Extracting Java archive to ${directory + "ServerCraft" + File.separator + "Java"}\n")
}
var stream = archiver.stream(File(directory + "ServerCraft" + File.separator + "Java" + File.separator + javaFile))
val dest = File(directory + "ServerCraft" + File.separator + "Java")
@ -477,7 +485,7 @@ class PrimaryController {
do {
withContext(Dispatchers.JavaFx) {
progressBar.progress = currentEntry/entries
log(entry.name)
log(entry.name + "\n")
}
entry.extract(dest)
entry = stream.getNextEntry()
@ -490,31 +498,35 @@ class PrimaryController {
progressBar.progress = ProgressBar.INDETERMINATE_PROGRESS
statusBar.text = "Building Minecraft Server..."
}
val builder = ProcessBuilder("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"))
val proc = builder.start()
val reader = InputStreamReader(proc.inputStream)
val br = BufferedReader(reader)
try {
var line = br.readLine()
var cbuf = CharArray(1000)
var currentline = 0.0
while (line != null) {
while (proc.isAlive) {
if (!building) {
proc.destroy()
}
withContext(Dispatchers.JavaFx) {log(line)}
line = br.readLine()
withContext(Dispatchers.JavaFx) {log(cbuf.joinToString(separator=""))}
cbuf = CharArray(1000)
reader.read(cbuf, 0, 1000)
currentline++
if (currentline > 15) {
withContext(Dispatchers.JavaFx) {progressBar.progress = if (spigotBuilt) {currentline/1100.0} else {currentline/14122.0} }
}
}
cbuf = CharArray(1000)
reader.read(cbuf, 0, 1000)
withContext(Dispatchers.JavaFx) {log(cbuf.joinToString(separator=""))}
} catch (e: IOException) {
withContext(Dispatchers.JavaFx) {log("Stream Closed")}
withContext(Dispatchers.JavaFx) {log("Stream Closed\n")}
}
}
progressBar.isVisible = false
findServerJar()
withContext(Dispatchers.JavaFx){
worldSettingsPane.isDisable = false
directoryPane.isDisable = false
@ -538,11 +550,11 @@ 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?", hold=false)
killDialog.show()
return;
}
if (!File(directory + "eula.txt").exists()) {
val res = eulaDialog()
val res = EulaDialog().show()
if (res) {
File(directory + "eula.txt").writeText("eula=true")
} else {
@ -559,30 +571,34 @@ class PrimaryController {
startButton.text = "Kill Server"
@Suppress("OPT_IN_USAGE")
GlobalScope.launch(Dispatchers.Default) {
val builder = ProcessBuilder("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))
val proc = builder.start()
val reader = InputStreamReader(proc.inputStream)
val br = BufferedReader(reader)
try {
var line = br.readLine()
while (line != null) {
if (asyncResult) {
var cbuf = CharArray(1000)
reader.read(cbuf, 0, 1000)
while (proc.isAlive) {
if (killDialog.result) {
withContext(Dispatchers.JavaFx) {
statusBar.text = "Killing Minecraft server..."
startButton.isDisable = true
}
proc.destroy()
}
withContext(Dispatchers.JavaFx) {log(line)}
line = br.readLine()
withContext(Dispatchers.JavaFx) {log(cbuf.joinToString(separator=""))}
cbuf = CharArray(1000)
reader.read(cbuf, 0, 1000)
}
cbuf = CharArray(1000)
reader.read(cbuf, 0, 1000)
withContext(Dispatchers.JavaFx) {log(cbuf.joinToString(separator=""))}
} catch (e: IOException) {
withContext(Dispatchers.JavaFx) {log("Stream Closed")}
withContext(Dispatchers.JavaFx) {log("Stream Closed\n")}
}
withContext(Dispatchers.JavaFx) {
statusBar.text = if (asyncResult) {
asyncResult = false
statusBar.text = if (killDialog.result) {
killDialog.result = false
"Server killed."
} else {
"Server stopped."
@ -599,132 +615,6 @@ class PrimaryController {
}
}
private fun eulaDialog(): Boolean {
var result = false
val resources = App().javaClass.getResource("icons/warning.png")
val dialog = Stage()
dialog.icons.add(Image(App().javaClass.getResourceAsStream("app.png")))
dialog.setResizable(false)
dialog.initModality(Modality.APPLICATION_MODAL);
dialog.title = directory
val scenePane = Pane()
val dialogScene = Scene(scenePane, 400.0, 150.0);
val imagePane = Pane()
val icon = Image("$resources")
imagePane.layoutX = 14.0
imagePane.layoutY = 14.0
imagePane.scaleX = 0.7
imagePane.scaleY = 0.7
imagePane.children.add(ImageView(icon))
val label = Label("Do you agree to the terms of the Minecraft End User License Agreement?")
label.isWrapText = true
label.layoutX = 115.0
label.layoutY = 40.0
val buttonBar = ButtonBar()
buttonBar.buttonOrder = "L+R"
buttonBar.padding = Insets(10.0, 10.0, 10.0, 10.0)
buttonBar.layoutX = 0.0
buttonBar.layoutY = 107.0
buttonBar.prefWidth = 400.0
val noButton = Button("I Disagree")
noButton.onAction = EventHandler<ActionEvent>() {
result = false
dialog.hide()
}
noButton.isDefaultButton = true
val yesButton = Button("I Agree")
yesButton.onAction = EventHandler<ActionEvent>() {
result = true
dialog.hide()
}
val eula = Button("View EULA")
eula.onAction = EventHandler<ActionEvent>() {
val desktop = Desktop.getDesktop()
if (desktop.isSupported(Desktop.Action.BROWSE)) {
// most likely running on Windows or macOS
try {
desktop.browse(URI("https://account.mojang.com/documents/minecraft_eula"))
} catch (e: Exception) {
println(e)
}
} else {
// assume running on linux
try {
Runtime.getRuntime().exec("xdg-open https://account.mojang.com/documents/minecraft_eula");
} catch (e: Exception) {
println(e)
}
}
}
ButtonBar.setButtonData(eula, ButtonBar.ButtonData.LEFT)
ButtonBar.setButtonData(noButton, ButtonBar.ButtonData.RIGHT)
ButtonBar.setButtonData(yesButton, ButtonBar.ButtonData.RIGHT)
buttonBar.buttons.add(eula)
buttonBar.buttons.add(noButton)
buttonBar.buttons.add(yesButton)
scenePane.children.addAll(imagePane, label, buttonBar)
dialog.setScene(dialogScene);
dialog.showAndWait();
return result
}
private fun createDialog(type: String, msg: String, yes: String = "Yes", no: String = "No", hold: Boolean = true): Boolean {
var result = false
val resources = App().javaClass.getResource("icons/$type.png")
val dialog = Stage()
dialog.icons.add(Image(App().javaClass.getResourceAsStream("app.png")))
dialog.setResizable(false)
dialog.initModality(Modality.APPLICATION_MODAL);
dialog.title = directory
val scenePane = Pane()
val dialogScene = Scene(scenePane, 400.0, 150.0);
val imagePane = Pane()
val icon = Image("$resources")
imagePane.layoutX = 14.0
imagePane.layoutY = 14.0
imagePane.scaleX = 0.7
imagePane.scaleY = 0.7
imagePane.children.add(ImageView(icon))
val label = Label(msg)
label.isWrapText = true
label.layoutX = 115.0
label.layoutY = if (type == "warning") {10.0} else {40.0}
val buttonBar = ButtonBar()
buttonBar.padding = Insets(10.0, 10.0, 10.0, 10.0)
buttonBar.layoutX = 0.0
buttonBar.layoutY = 107.0
buttonBar.prefWidth = 400.0
val noButton = Button(no)
noButton.onAction = EventHandler<ActionEvent>() {
if (hold) {
result = false
} else {
asyncResult = false
}
dialog.hide()
}
val yesButton = Button(yes)
yesButton.onAction = EventHandler<ActionEvent>() {
if (hold) {
result = true
} else {
asyncResult = true
}
dialog.hide()
}
yesButton.isDefaultButton = true
buttonBar.buttons.add(noButton)
buttonBar.buttons.add(yesButton)
scenePane.children.addAll(imagePane, label, buttonBar)
dialog.setScene(dialogScene);
if (hold) {
dialog.showAndWait();
} else {
dialog.show();
}
return result
}
private fun loadServerDir(dir: String): Boolean {
directory = dir
// exit if doesn't exist for whatever reason
@ -751,23 +641,27 @@ 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?")
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."
if (result) {
startButton.isDisable = false
startButton.isDisable = true
File(directory + "ServerCraft").mkdir()
buildButton.text = "Build Server"
statusBar.text = "Server converted. Build the server to start."
server.loadProps(dir, convert=true)
}
server.loadProps(dir)
return result
} else {
// assume clean directory
val result = createDialog("info", "There is no server in this directory.\nCreate one?")
val result = CommonDialog("info", directory, "There is no server in this directory.\nCreate one?").show()
if (result) {
File(directory + "ServerCraft").mkdir()
startButton.isDisable = true
buildButton.text = "Build Server"
server.dir = dir
server.loadProps()
}
statusBar.text = "Ready."
server.loadProps()
return result
}

View file

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

View file

@ -0,0 +1,77 @@
package xyz.brysonsteck.ServerCraft.dialogs
import javafx.scene.control.Button
import javafx.scene.control.ButtonBar
import javafx.scene.control.Label
import javafx.scene.image.Image
import javafx.scene.image.ImageView
import javafx.scene.layout.Pane
import javafx.scene.Scene
import javafx.stage.Modality
import javafx.stage.Stage
import javafx.geometry.Insets
import javafx.event.EventHandler
import javafx.event.ActionEvent
import xyz.brysonsteck.ServerCraft.App
abstract class Dialog {
public var result = false
abstract val type: String
abstract val title: String
abstract val msg: String
abstract val neutralButton: Button?
abstract val yesButton: Button?
abstract val noButton: Button?
protected var hold: Boolean = true
protected var labelOffset: Double = 40.0
protected val dialog = Stage()
public fun show(): Boolean {
val resources = App().javaClass.getResource("icons/$type.png")
val icon = Image("$resources")
dialog.icons.add(Image(App().javaClass.getResourceAsStream("app.png")))
dialog.setResizable(false)
dialog.initModality(Modality.APPLICATION_MODAL);
dialog.title = title
val imagePane = Pane()
imagePane.layoutX = 14.0
imagePane.layoutY = 14.0
imagePane.scaleX = 0.7
imagePane.scaleY = 0.7
imagePane.children.add(ImageView(icon))
val label = Label(msg)
label.isWrapText = true
label.layoutX = 115.0
label.layoutY = labelOffset
val buttonBar = ButtonBar()
buttonBar.padding = Insets(10.0)
buttonBar.layoutX = 0.0
buttonBar.layoutY = 107.0
buttonBar.prefWidth = 400.0
val scenePane = Pane()
scenePane.children.addAll(imagePane, label, buttonBar)
if (noButton != null) {
ButtonBar.setButtonData(noButton, ButtonBar.ButtonData.RIGHT)
buttonBar.buttons.add(noButton)
}
if (yesButton != null) {
ButtonBar.setButtonData(yesButton, ButtonBar.ButtonData.RIGHT)
buttonBar.buttons.add(yesButton)
}
if (neutralButton != null) {
ButtonBar.setButtonData(neutralButton, ButtonBar.ButtonData.LEFT)
buttonBar.buttons.add(neutralButton)
}
val dialogScene = Scene(scenePane, 400.0, 150.0);
dialog.setScene(dialogScene);
if (hold) {
dialog.showAndWait();
} else {
dialog.show();
}
return result
}
}

View file

@ -0,0 +1,55 @@
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.net.URI
class EulaDialog: Dialog {
override val type: String
override val title: String
override val msg: String
override val neutralButton: Button?
override val yesButton: Button?
override val noButton: Button?
constructor() {
type = "warning"
title = "Minecraft End User License Agreement (EULA)"
msg = "Do you agree to the terms of the Minecraft End User License Agreement?"
noButton = Button("I Disagree")
noButton.onAction = EventHandler<ActionEvent>() {
result = false
dialog.hide()
}
noButton.isDefaultButton = true
yesButton = Button("I Agree")
yesButton.onAction = EventHandler<ActionEvent>() {
result = true
dialog.hide()
}
neutralButton = Button("View EULA")
neutralButton.onAction = EventHandler<ActionEvent>() {
val desktop = Desktop.getDesktop()
if (desktop.isSupported(Desktop.Action.BROWSE)) {
// most likely running on Windows or macOS
try {
desktop.browse(URI("https://account.mojang.com/documents/minecraft_eula"))
} catch (e: Exception) {
println(e)
}
} else {
// assume running on linux
try {
Runtime.getRuntime().exec("xdg-open https://account.mojang.com/documents/minecraft_eula");
} catch (e: Exception) {
println(e)
}
}
}
}
}

View file

@ -0,0 +1,34 @@
package xyz.brysonsteck.ServerCraft.dialogs
import javafx.scene.control.Button
import javafx.event.EventHandler
import javafx.event.ActionEvent
class KillDialog: Dialog {
override val type: String
override val title: String
override val msg: String
override val neutralButton: Button?
override val yesButton: Button?
override val noButton: Button?
constructor() {
type = "warning"
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?"
hold = false
noButton = Button("No")
noButton.onAction = EventHandler<ActionEvent>() {
result = false
dialog.hide()
}
yesButton = Button("Yes")
yesButton.onAction = EventHandler<ActionEvent>() {
result = true
dialog.hide()
}
yesButton.isDefaultButton = true
neutralButton = null
}
}

View file

@ -54,14 +54,12 @@ 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
}

View file

@ -2,6 +2,7 @@ package xyz.brysonsteck.ServerCraft.server
import java.io.File
import java.io.InputStream
import java.io.FileNotFoundException
import java.util.Properties
public class Server {
@ -33,16 +34,39 @@ public class Server {
props.setProperty("level-seed", "")
props.setProperty("level-type", "minecraft:normal")
props.setProperty("motd", "A server for a dummy")
writeProps()
}
public fun loadProps(dir: String) {
val ins = File(dir + File.separator + "server.properties").inputStream()
public fun loadProps(dir: String, convert: Boolean = false) {
var ins = File(dir + File.separator + "server.properties").inputStream()
props.load(ins)
try {
ins = File(dir + File.separator + "ServerCraft" + File.separator + "ServerCraft.properties").inputStream()
props.load(ins)
} catch (e: FileNotFoundException) {
if (convert) {
// create the file in question, as this is an external server being converted into a ServerCraft managed one
File(dir + File.separator + "ServerCraft" + File.separator + "ServerCraft.properties").createNewFile()
// also apply app-specific properties
props.setProperty("jvm-ram", 1024.toString())
// then write to file
val temp = Properties()
val outs = File(dir + File.separator + "ServerCraft" + File.separator + "ServerCraft.properties").outputStream()
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")
} else {
println(e)
}
}
}
private fun writeProps() {
val 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")
val temp = Properties()
outs = File(dir + File.separator + "ServerCraft" + File.separator + "ServerCraft.properties").outputStream()
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")
}
public fun getProp(prop: String): String {

View file

@ -27,7 +27,7 @@
<Font name="System Bold" size="28.0" />
</font>
</Label>
<Label layoutX="146.0" layoutY="82.0" text="Version 1.0" />
<Label fx:id="version" layoutX="146.0" layoutY="82.0" text="Version " />
<ButtonBar layoutY="318.0" prefHeight="40.0" prefWidth="398.0">
<buttons>
<Button mnemonicParsing="false" onAction="#closeInfo" text="Close" />
@ -36,20 +36,24 @@
<Insets bottom="8.0" left="8.0" right="8.0" top="8.0" />
</padding>
</ButtonBar>
<Label layoutX="52.0" layoutY="135.0" text="A graphical interface for creating Minecraft servers" textAlignment="CENTER" />
<TabPane layoutY="158.0" prefHeight="161.0" prefWidth="398.0" stylesheets="@css/info-tabs.css" tabClosingPolicy="UNAVAILABLE">
<Label alignment="CENTER" layoutX="1.0" layoutY="135.0" prefHeight="15.0" prefWidth="398.0" text="A graphical interface for creating Minecraft servers" textAlignment="CENTER" />
<TabPane layoutY="158.0" prefHeight="166.0" prefWidth="398.0" stylesheets="@css/info-tabs.css" tabClosingPolicy="UNAVAILABLE">
<tabs>
<Tab text="About">
<content>
<Pane prefHeight="200.0" prefWidth="200.0">
<Pane prefHeight="200.0" prefWidth="398.0">
<children>
<Label alignment="TOP_LEFT" prefWidth="398.0" text="This program is for simple Minecraft servers, and expects that the user knows how to port forward.&#10;&#10;ServerCraft is free and open source under the GNU General Public License Version 3.0" wrapText="true">
<padding>
<Insets bottom="13.0" left="13.0" right="13.0" top="13.0" />
</padding>
</Label>
<Hyperlink fx:id="license" layoutX="127.0" layoutY="110.0" onAction="#openHyperlink" text="License" />
<Hyperlink fx:id="source" layoutX="189.0" layoutY="110.0" onAction="#openHyperlink" text="Source Code" />
<HBox alignment="CENTER" layoutY="106.0" prefHeight="21.0" prefWidth="398.0">
<children>
<Hyperlink fx:id="license" onAction="#openHyperlink" text="License" />
<Hyperlink fx:id="source" onAction="#openHyperlink" text="Source Code" />
</children>
</HBox>
</children>
<padding>
<Insets bottom="13.0" left="13.0" right="13.0" top="13.0" />
@ -61,7 +65,7 @@
<content>
<Pane prefHeight="200.0" prefWidth="200.0">
<children>
<Label layoutX="78.0" layoutY="109.0" text="Want to join the list? Contribute with a PR!" textAlignment="CENTER" />
<Label alignment="CENTER" layoutY="109.0" prefHeight="15.0" prefWidth="398.0" text="Want to join the list? Contribute with a PR!" textAlignment="CENTER" />
<VBox>
<children>
<Label text="Bryson Steck" VBox.vgrow="ALWAYS">