Compare commits

..

No commits in common. "9a16ada0f604eb409258efc1d8f2881d09634f4f" and "633132c1c058a9e7bd994b350b775e1b9aa779ab" have entirely different histories.

17 changed files with 183 additions and 386 deletions

3
.gitignore vendored
View file

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

View file

@ -4,42 +4,14 @@ Is the `server.properties` file too overwhelming? Here is a graphical applicatio
## About ## About
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. ServerCraft is designed for the layman who has knowledge of Minecraft but has no idea how to create a simple server.
## Features ## Features
* Utilizes the Spigot Minecraft server for performance and extensibility * Utilizes the Spigot Minecraft server for performance and extensibility
* Automatically downloads the correct Java version, no more confusing errors when trying to run a server * Automatically downloads the correct Java version, no more confusing errors when trying to run the server
* Common settings available at a glance * Common settings available at a glance
* Separate section for common settings that are a little more advanced, such as: * Separate section for common settings that are a little more advanced, such as:
* The amount of RAM to allocate to the server * The amount of RAM to allocate to the server
* Render and simulation distances * 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,6 +32,8 @@ plugins {
apply plugin: 'io.github.fvarrui.javapackager.plugin' apply plugin: 'io.github.fvarrui.javapackager.plugin'
version = "1.0"
repositories { repositories {
// Use Maven Central for resolving dependencies. // Use Maven Central for resolving dependencies.
mavenCentral() mavenCentral()
@ -66,6 +68,8 @@ distTar {
} }
jar { jar {
// archiveFileName = 'ServerCraft.jar'
manifest { manifest {
attributes 'Main-Class': application.mainClass attributes 'Main-Class': application.mainClass
} }
@ -85,7 +89,7 @@ task pack(type: io.github.fvarrui.javapackager.gradle.PackageTask, dependsOn: bu
organizationUrl = "https://brysonsteck.xyz" organizationUrl = "https://brysonsteck.xyz"
organizationEmail = "me@brysonsteck.xyz" organizationEmail = "me@brysonsteck.xyz"
url = "https://codeberg.org/brysonsteck/ServerCraft" url = "https://codeberg.org/brysonsteck/ServerCraft"
additionalModules = [ "java.management", "jdk.crypto.ec" ] additionalModules = [ "jdk.crypto.ec" ]
linuxConfig { linuxConfig {
pngFile = file('src/main/resources/icon.png') pngFile = file('src/main/resources/icon.png')
@ -110,32 +114,6 @@ 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 { build.doLast {
if (OperatingSystem.current().isLinux()) { if (OperatingSystem.current().isLinux()) {
exec { exec {
@ -149,10 +127,6 @@ build.doLast {
} }
} }
classes {
dependsOn createProperties
}
javafx { javafx {
version = "20" version = "20"
modules = ['javafx.controls', 'javafx.fxml', 'javafx.graphics'] modules = ['javafx.controls', 'javafx.fxml', 'javafx.graphics']

View file

@ -1,14 +0,0 @@
# 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,18 +4,12 @@ import javafx.fxml.FXML
import javafx.application.Platform 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.stage.Stage import javafx.stage.Stage
import javafx.event.ActionEvent import javafx.event.ActionEvent
import java.awt.Desktop import java.awt.Desktop
import java.net.URI import java.net.URI
import java.util.Properties
import xyz.brysonsteck.ServerCraft.App
class InfoController { class InfoController {
@FXML
lateinit private var version: Label
private val emails = mapOf( private val emails = mapOf(
"bryson" to "me@brysonsteck.xyz" "bryson" to "me@brysonsteck.xyz"
) )
@ -25,13 +19,6 @@ class InfoController {
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"
@FXML
public fun initialize() {
val props = Properties()
props.load(App().javaClass.getResourceAsStream("info.properties"))
version.text += props.getProperty("version")
}
@FXML @FXML
private fun openHyperlink(e: ActionEvent) { private fun openHyperlink(e: ActionEvent) {
val link = e.source as Hyperlink val link = e.source as Hyperlink
@ -65,6 +52,7 @@ class InfoController {
} }
} }
split[0].equals("license") -> { split[0].equals("license") -> {
println("license")
if (!os.contains("linux")) { if (!os.contains("linux")) {
desktop.browse(URI(license)) desktop.browse(URI(license))
} else { } else {

View file

@ -57,10 +57,6 @@ import org.rauschig.jarchivelib.*
import xyz.brysonsteck.ServerCraft.server.Server import xyz.brysonsteck.ServerCraft.server.Server
import xyz.brysonsteck.ServerCraft.server.Download 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.KillDialog
import xyz.brysonsteck.ServerCraft.dialogs.EulaDialog
class PrimaryController { class PrimaryController {
@FXML @FXML
@ -133,19 +129,16 @@ class PrimaryController {
lateinit private var scrollPane: ScrollPane lateinit private var scrollPane: ScrollPane
lateinit private var server: Server lateinit private var server: Server
lateinit private var killDialog: Dialog
private var building = false private var building = false
private var directory = "" private var directory = ""
private var asyncResult = false
private var started = false private var started = false
private var loading = false private var loading = false
private var showingConsole = false private var showingConsole = false
private fun log(str: String) { private fun log(str: String) {
if (console.text.length > 100000) { console.text = console.text + str + "\n"
console.text = console.text.drop(str.length + 10) println(str)
}
console.text = console.text + str
print(str)
} }
@FXML @FXML
@ -238,7 +231,6 @@ class PrimaryController {
onPropChange("level-seed", new) onPropChange("level-seed", new)
} }
} }
killDialog = KillDialog()
} }
@FXML @FXML
@ -341,10 +333,10 @@ class PrimaryController {
} }
prop == "difficulty" -> { prop == "difficulty" -> {
if (value == "Hardcore") { if (value == "Hardcore") {
server.setProp("hardcore", "true") server.setProp("hardcode", "true")
server.setProp(prop, "hard") server.setProp(prop, "hard")
} else { } else {
server.setProp("hardcore", "false") server.setProp("hardcode", "false")
server.setProp(prop, value.lowercase()) server.setProp(prop, value.lowercase())
} }
} }
@ -373,7 +365,7 @@ 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 = createDialog("info", "Reset settings to defaults?\nThere is NO GOING BACK!")
if (res) { if (res) {
server.loadProps() server.loadProps()
applyProps() applyProps()
@ -449,21 +441,21 @@ class PrimaryController {
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}")
} }
val download = Download(URL(it.value), destinations[it.key]!!) val download = Download(URL(it.value), destinations[it.key]!!)
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 the progress bar
withContext(Dispatchers.JavaFx) {log("Progress: ${prog * 100}%\n")} withContext(Dispatchers.JavaFx) {log("Progress: ${prog * 100}%")}
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}")}
} }
// extract java archive // extract java archive
@ -471,7 +463,7 @@ class PrimaryController {
withContext(Dispatchers.JavaFx) { withContext(Dispatchers.JavaFx) {
progressBar.progress = ProgressBar.INDETERMINATE_PROGRESS progressBar.progress = ProgressBar.INDETERMINATE_PROGRESS
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"}")
} }
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")
@ -485,7 +477,7 @@ class PrimaryController {
do { do {
withContext(Dispatchers.JavaFx) { withContext(Dispatchers.JavaFx) {
progressBar.progress = currentEntry/entries progressBar.progress = currentEntry/entries
log(entry.name + "\n") log(entry.name)
} }
entry.extract(dest) entry.extract(dest)
entry = stream.getNextEntry() entry = stream.getNextEntry()
@ -498,35 +490,31 @@ 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("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)
val br = BufferedReader(reader)
try { try {
var cbuf = CharArray(1000) var line = br.readLine()
var currentline = 0.0 var currentline = 0.0
while (proc.isAlive) { while (line != null) {
if (!building) { if (!building) {
proc.destroy() proc.destroy()
} }
withContext(Dispatchers.JavaFx) {log(cbuf.joinToString(separator=""))} withContext(Dispatchers.JavaFx) {log(line)}
cbuf = CharArray(1000) line = br.readLine()
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)
reader.read(cbuf, 0, 1000)
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")}
} }
} }
progressBar.isVisible = false progressBar.isVisible = false
findServerJar()
withContext(Dispatchers.JavaFx){ withContext(Dispatchers.JavaFx){
worldSettingsPane.isDisable = false worldSettingsPane.isDisable = false
directoryPane.isDisable = false directoryPane.isDisable = false
@ -550,11 +538,11 @@ class PrimaryController {
@FXML @FXML
private fun onStart() { private fun onStart() {
if (started) { if (started) {
killDialog.show() createDialog("warning", "You should only kill the server if\nabsolutely necessary. Data loss may occur.\nContinue anyway?", hold=false)
return; return;
} }
if (!File(directory + "eula.txt").exists()) { if (!File(directory + "eula.txt").exists()) {
val res = EulaDialog().show() val res = eulaDialog()
if (res) { if (res) {
File(directory + "eula.txt").writeText("eula=true") File(directory + "eula.txt").writeText("eula=true")
} else { } else {
@ -571,34 +559,30 @@ 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("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)
val br = BufferedReader(reader)
try { try {
var cbuf = CharArray(1000) var line = br.readLine()
reader.read(cbuf, 0, 1000) while (line != null) {
while (proc.isAlive) { if (asyncResult) {
if (killDialog.result) {
withContext(Dispatchers.JavaFx) { withContext(Dispatchers.JavaFx) {
statusBar.text = "Killing Minecraft server..." statusBar.text = "Killing Minecraft server..."
startButton.isDisable = true startButton.isDisable = true
} }
proc.destroy() proc.destroy()
} }
withContext(Dispatchers.JavaFx) {log(cbuf.joinToString(separator=""))} withContext(Dispatchers.JavaFx) {log(line)}
cbuf = CharArray(1000) line = br.readLine()
reader.read(cbuf, 0, 1000)
} }
cbuf = CharArray(1000)
reader.read(cbuf, 0, 1000)
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")}
} }
withContext(Dispatchers.JavaFx) { withContext(Dispatchers.JavaFx) {
statusBar.text = if (killDialog.result) { statusBar.text = if (asyncResult) {
killDialog.result = false asyncResult = false
"Server killed." "Server killed."
} else { } else {
"Server stopped." "Server stopped."
@ -615,6 +599,132 @@ 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 { 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
@ -641,27 +751,23 @@ 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 = 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." statusBar.text = "Ready."
if (result) { if (result) {
startButton.isDisable = true startButton.isDisable = false
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 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 = createDialog("info", "There is no server in this directory.\nCreate one?")
if (result) { if (result) {
File(directory + "ServerCraft").mkdir() File(directory + "ServerCraft").mkdir()
startButton.isDisable = true startButton.isDisable = true
buildButton.text = "Build Server" buildButton.text = "Build Server"
server.dir = dir
server.loadProps()
} }
statusBar.text = "Ready." statusBar.text = "Ready."
server.loadProps()
return result return result
} }

View file

@ -1,34 +0,0 @@
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

@ -1,77 +0,0 @@
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

@ -1,55 +0,0 @@
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

@ -1,34 +0,0 @@
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,12 +54,14 @@ class Download: Runnable {
// 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) {
println(connection.responseCode)
status = Status.ERROR status = Status.ERROR
} }
// Check for valid content length. // Check for valid content length.
contentLength = connection.getContentLength(); contentLength = connection.getContentLength();
if (contentLength < 1) { if (contentLength < 1) {
println(connection.getContentLength())
status = Status.ERROR status = Status.ERROR
} }

View file

@ -2,7 +2,6 @@ package xyz.brysonsteck.ServerCraft.server
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import java.io.FileNotFoundException
import java.util.Properties import java.util.Properties
public class Server { public class Server {
@ -34,39 +33,16 @@ public class Server {
props.setProperty("level-seed", "") props.setProperty("level-seed", "")
props.setProperty("level-type", "minecraft:normal") props.setProperty("level-type", "minecraft:normal")
props.setProperty("motd", "A server for a dummy") props.setProperty("motd", "A server for a dummy")
writeProps()
} }
public fun loadProps(dir: String, convert: Boolean = false) { public fun loadProps(dir: String) {
var ins = File(dir + File.separator + "server.properties").inputStream() val ins = File(dir + File.separator + "server.properties").inputStream()
props.load(ins) 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() { private fun writeProps() {
var outs = File(dir + File.separator + "server.properties").outputStream() val 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()
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 { public fun getProp(prop: String): String {

View file

@ -27,7 +27,7 @@
<Font name="System Bold" size="28.0" /> <Font name="System Bold" size="28.0" />
</font> </font>
</Label> </Label>
<Label fx:id="version" layoutX="146.0" layoutY="82.0" text="Version " /> <Label layoutX="146.0" layoutY="82.0" text="Version 1.0" />
<ButtonBar layoutY="318.0" prefHeight="40.0" prefWidth="398.0"> <ButtonBar layoutY="318.0" prefHeight="40.0" prefWidth="398.0">
<buttons> <buttons>
<Button mnemonicParsing="false" onAction="#closeInfo" text="Close" /> <Button mnemonicParsing="false" onAction="#closeInfo" text="Close" />
@ -36,24 +36,20 @@
<Insets bottom="8.0" left="8.0" right="8.0" top="8.0" /> <Insets bottom="8.0" left="8.0" right="8.0" top="8.0" />
</padding> </padding>
</ButtonBar> </ButtonBar>
<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" /> <Label layoutX="52.0" layoutY="135.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"> <TabPane layoutY="158.0" prefHeight="161.0" prefWidth="398.0" stylesheets="@css/info-tabs.css" tabClosingPolicy="UNAVAILABLE">
<tabs> <tabs>
<Tab text="About"> <Tab text="About">
<content> <content>
<Pane prefHeight="200.0" prefWidth="398.0"> <Pane prefHeight="200.0" prefWidth="200.0">
<children> <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"> <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> <padding>
<Insets bottom="13.0" left="13.0" right="13.0" top="13.0" /> <Insets bottom="13.0" left="13.0" right="13.0" top="13.0" />
</padding> </padding>
</Label> </Label>
<HBox alignment="CENTER" layoutY="106.0" prefHeight="21.0" prefWidth="398.0"> <Hyperlink fx:id="license" layoutX="127.0" layoutY="110.0" onAction="#openHyperlink" text="License" />
<children> <Hyperlink fx:id="source" layoutX="189.0" layoutY="110.0" onAction="#openHyperlink" text="Source Code" />
<Hyperlink fx:id="license" onAction="#openHyperlink" text="License" />
<Hyperlink fx:id="source" onAction="#openHyperlink" text="Source Code" />
</children>
</HBox>
</children> </children>
<padding> <padding>
<Insets bottom="13.0" left="13.0" right="13.0" top="13.0" /> <Insets bottom="13.0" left="13.0" right="13.0" top="13.0" />
@ -65,7 +61,7 @@
<content> <content>
<Pane prefHeight="200.0" prefWidth="200.0"> <Pane prefHeight="200.0" prefWidth="200.0">
<children> <children>
<Label alignment="CENTER" layoutY="109.0" prefHeight="15.0" prefWidth="398.0" text="Want to join the list? Contribute with a PR!" textAlignment="CENTER" /> <Label layoutX="78.0" layoutY="109.0" text="Want to join the list? Contribute with a PR!" textAlignment="CENTER" />
<VBox> <VBox>
<children> <children>
<Label text="Bryson Steck" VBox.vgrow="ALWAYS"> <Label text="Bryson Steck" VBox.vgrow="ALWAYS">