Compare commits
10 commits
633132c1c0
...
9a16ada0f6
Author | SHA1 | Date | |
---|---|---|---|
9a16ada0f6 | |||
b2ffd5fc3f | |||
d1bf5734bf | |||
88802ece04 | |||
443a153024 | |||
68ff0f0d78 | |||
01f6beb513 | |||
e57a3d6864 | |||
6405861558 | |||
d38d79e9b7 |
17 changed files with 386 additions and 183 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -7,3 +7,6 @@ bin
|
|||
|
||||
# Ignore VSCode settings
|
||||
.vscode
|
||||
|
||||
# ignore local releases dir for Codeberg releases
|
||||
releases
|
||||
|
|
34
README.md
34
README.md
|
@ -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.
|
36
build.gradle
36
build.gradle
|
@ -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
14
docs/features.md
Normal 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
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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. 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">
|
||||
|
|
Loading…
Add table
Reference in a new issue