change package

This commit is contained in:
Bryson Steck 2023-05-13 19:53:48 -06:00
parent 4388bc0415
commit efd5388d33

View file

@ -0,0 +1,618 @@
package xyz.brysonsteck.serverfordummies
import kotlinx.coroutines.*
import kotlinx.coroutines.javafx.JavaFx
import org.rauschig.jarchivelib.*
import java.io.File
import java.io.IOException
import java.io.BufferedReader
import java.io.InputStreamReader
import java.awt.Checkbox
import java.awt.Desktop
import java.util.Properties
import java.net.URL
import java.net.URI
import javafx.beans.value.ChangeListener
import javafx.beans.value.ObservableValue
import javafx.concurrent.Task
import javafx.beans.property.BooleanProperty
import javafx.collections.FXCollections
import javafx.fxml.FXML
import javafx.fxml.FXMLLoader
import javafx.geometry.Insets
import javafx.scene.control.Button
import javafx.scene.control.ChoiceBox
import javafx.scene.control.Label
import javafx.scene.control.TextField
import javafx.scene.control.Spinner
import javafx.scene.control.TitledPane
import javafx.scene.control.ButtonBar
import javafx.scene.control.CheckBox
import javafx.scene.control.ProgressBar
import javafx.scene.control.Hyperlink
import javafx.scene.layout.Border
import javafx.scene.layout.BorderStroke
import javafx.scene.layout.GridPane
import javafx.scene.layout.Pane
import javafx.scene.layout.HBox
import javafx.scene.layout.VBox
import javafx.scene.text.TextAlignment
import javafx.scene.text.Text
import javafx.scene.Scene
import javafx.scene.input.MouseEvent
import javafx.scene.image.Image
import javafx.scene.image.ImageView
import javafx.stage.FileChooser
import javafx.stage.FileChooser.ExtensionFilter
import javafx.stage.DirectoryChooser
import javafx.stage.Modality
import javafx.stage.Stage
import javafx.event.EventHandler
import org.rauschig.jarchivelib.*
import Download
import xyz.brysonsteck.serverfordummies.server.Server
import xyz.brysonsteck.serverfordummies.App
class PrimaryController {
@FXML
lateinit private var currentDirectoryLabel: Label
@FXML
lateinit private var worldNameField: TextField
@FXML
lateinit private var seedField: TextField
@FXML
lateinit private var portSpinner: Spinner<kotlin.Int>
@FXML
lateinit private var difficultyBox: ChoiceBox<String>
@FXML
lateinit private var gamemodeBox: ChoiceBox<String>
@FXML
lateinit private var worldTypeBox: ChoiceBox<String>
@FXML
lateinit private var worldSettingsPane: HBox
@FXML
lateinit private var parentPane: Pane
@FXML
lateinit private var directoryPane: Pane
@FXML
lateinit private var buttonBar: ButtonBar
@FXML
lateinit private var flightCheckbox: CheckBox
@FXML
lateinit private var netherCheckbox: CheckBox
@FXML
lateinit private var structuresCheckbox: CheckBox
@FXML
lateinit private var pvpCheckbox: CheckBox
@FXML
lateinit private var whitelistCheckbox: CheckBox
@FXML
lateinit private var cmdBlocksCheckbox: CheckBox
@FXML
lateinit private var playerCountCheckbox: CheckBox
@FXML
lateinit private var maxPlayersSpinner: Spinner<kotlin.Int>
@FXML
lateinit private var maxSizeSpinner: Spinner<kotlin.Int>
@FXML
lateinit private var memorySpinner: Spinner<kotlin.Int>
@FXML
lateinit private var spawnSpinner: Spinner<kotlin.Int>
@FXML
lateinit private var simulationSpinner: Spinner<kotlin.Int>
@FXML
lateinit private var renderSpinner: Spinner<kotlin.Int>
@FXML
lateinit private var maxTickSpinner: Spinner<kotlin.Int>
@FXML
lateinit private var statusBar: Label
@FXML
lateinit private var progressBar: ProgressBar
@FXML
lateinit private var startButton: Button
@FXML
lateinit private var buildButton: Button
@FXML
lateinit private var defaultsButton: Button
lateinit private var server: Server
private var building = false
private var directory = ""
private var asyncResult = false
private var started = false
@FXML
public fun initialize() {
difficultyBox.items = FXCollections.observableArrayList(
"Peaceful",
"Easy",
"Normal",
"Hard",
"Hardcore"
)
difficultyBox.value = "Normal"
difficultyBox.selectionModel.selectedIndexProperty().addListener { _, _, new ->
onChoiceBoxChange("difficulty", difficultyBox.items[new as Int])
}
gamemodeBox.items = FXCollections.observableArrayList(
"Survival",
"Creative",
"Adventure",
"Spectator"
)
gamemodeBox.value = "Survival"
gamemodeBox.selectionModel.selectedIndexProperty().addListener { _, _, new ->
onChoiceBoxChange("gamemode", gamemodeBox.items[new as Int])
}
worldTypeBox.items = FXCollections.observableArrayList(
"Normal",
"Superflat",
"Large Biomes",
"Amplified"
)
worldTypeBox.value = "Normal"
worldTypeBox.selectionModel.selectedIndexProperty().addListener { _, _, new ->
onChoiceBoxChange("world-type", worldTypeBox.items[new as Int])
}
}
@FXML
private fun onDirectoryButtonClick() {
val dirChooser = DirectoryChooser()
dirChooser.title = "Open a server directory"
dirChooser.initialDirectory = File(System.getProperty("user.home"))
val result = dirChooser.showDialog(null)
if (result != null) {
currentDirectoryLabel.text = result.absolutePath
server = Server()
val res = loadServerDir(result.absolutePath)
if (res) {
parentPane.isDisable = false
worldSettingsPane.isDisable = false
buildButton.isDisable = false
defaultsButton.isDisable = false
} else {
currentDirectoryLabel.text = "<NONE>"
parentPane.isDisable = true
worldSettingsPane.isDisable = true
startButton.isDisable = true
buildButton.isDisable = true
defaultsButton.isDisable = true
}
}
}
@FXML
private fun onWorldNameChange() {
}
@FXML
private fun onSeedChange() {
}
@FXML
private fun onPortChange() {
}
@FXML
private fun onCheckboxClick() {
}
@FXML
private fun onSpinnerChange() {
}
private fun onChoiceBoxChange(box: String, selection: String) {
}
@FXML
private fun onInfo() {
val stage = Stage()
val scene = Scene(FXMLLoader(App().javaClass.getResource("info.fxml")).load(), 398.0, 358.0)
stage.icons.add(Image(App().javaClass.getResourceAsStream("app-256x256.png")))
stage.setResizable(false)
stage.initModality(Modality.APPLICATION_MODAL);
stage.title = "About ServerCraft"
stage.scene = scene
stage.show()
}
@FXML
private fun onBuild() {
if (building) {
building = false
return;
}
building = true
worldSettingsPane.isDisable = true
directoryPane.isDisable = true
parentPane.isDisable = true
startButton.isDisable = true
defaultsButton.isDisable = true
buildButton.text = "Cancel Build"
@Suppress("OPT_IN_USAGE")
GlobalScope.launch(Dispatchers.Default) {
progressBar.isVisible = true
var javaFile = ""
var archiver = ArchiverFactory.createArchiver(ArchiveFormat.TAR, CompressionType.GZIP)
val os = System.getProperty("os.name").lowercase()
when {
os.contains("win") -> {
javaFile = "openjdk-20.0.1_windows-x64_bin.zip"
archiver = ArchiverFactory.createArchiver(ArchiveFormat.ZIP)
}
os.contains("linux") -> {
javaFile = "openjdk-20.0.1_linux-x64_bin.tar.gz"
}
os.contains("mac") -> {
javaFile = "openjdk-20.0.1_macos-x64_bin.tar.gz"
}
}
// download files
val downloads = mapOf(
"Java 20" to "https://download.java.net/java/GA/jdk20.0.1/b4887098932d415489976708ad6d1a4b/9/GPL/${javaFile}",
"BuildTools" to "https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar",
)
val destinations = mapOf (
"Java 20" to directory + "ServerForDummies" + File.separator + "Java" + File.separator,
"BuildTools" to directory + "ServerForDummies" + File.separator + "Spigot" + File.separator
)
val spigotBuilt = File(destinations["BuildTools"]).exists()
val javaExtracted = File(destinations["Java 20"] + "jdk-20.0.1").exists()
destinations.forEach {
File(it.value).mkdir()
}
downloads.forEach {
if (it.key == "Java 20" && javaExtracted) {
return@forEach
}
withContext(Dispatchers.JavaFx){
statusBar.text = "Downloading ${it.key}..."
progressBar.progress = ProgressBar.INDETERMINATE_PROGRESS
}
val download = Download(URL(it.value), destinations[it.key]!!)
download.start()
while (download.status == Download.Status.DOWNLOADING) {
var prog = (download.downloaded.toDouble() / download.contentLength.toDouble())
// for whatever reason I need to print something to the screen in order for it to update the progress bar
print("")
if (prog >= 0.01) {
withContext(Dispatchers.JavaFx) {progressBar.progress = prog}
}
if (!building) download.status = Download.Status.CANCELLED
Thread.sleep(300)
}
}
// extract java archive
if (building && !javaExtracted) {
withContext(Dispatchers.JavaFx) {
progressBar.progress = ProgressBar.INDETERMINATE_PROGRESS
statusBar.text = "Extracting Java archive..."
}
var stream = archiver.stream(File(directory + "ServerForDummies" + File.separator + "Java" + File.separator + javaFile))
val dest = File(directory + "ServerForDummies" + File.separator + "Java")
var entries = 0.0
while(stream.getNextEntry() != null && building) {
entries++
}
stream = archiver.stream(File(directory + "ServerForDummies" + File.separator + "Java" + File.separator + javaFile))
var entry = stream.getNextEntry()
var currentEntry = 0.0
do {
withContext(Dispatchers.JavaFx) {progressBar.progress = currentEntry/entries}
entry.extract(dest)
entry = stream.getNextEntry()
currentEntry++
} while (entry != null && building)
}
if (building) {
withContext(Dispatchers.JavaFx) {
progressBar.progress = ProgressBar.INDETERMINATE_PROGRESS
statusBar.text = "Building Minecraft Server..."
}
val builder = ProcessBuilder("java", "-jar", "BuildTools.jar", "--rev", "latest", "-o", ".." + File.separator + ".." + File.separator)
builder.directory(File(directory + "ServerForDummies" + File.separator + "Spigot"))
val proc = builder.start()
val reader = InputStreamReader(proc.inputStream)
val br = BufferedReader(reader)
try {
var line = br.readLine()
var currentline = 0.0
while (line != null) {
if (!building) {
proc.destroy()
}
println(line)
line = br.readLine()
currentline++
if (currentline > 15) {
withContext(Dispatchers.JavaFx) {progressBar.progress = if (spigotBuilt) {currentline/1100.0} else {currentline/14122.0} }
}
}
} catch (e: IOException) {
println("Stream closed")
}
}
progressBar.isVisible = false
withContext(Dispatchers.JavaFx){
worldSettingsPane.isDisable = false
directoryPane.isDisable = false
parentPane.isDisable = false
defaultsButton.isDisable = false
buildButton.text = "Build Server"
statusBar.text = if (building) {
findServerJar()
buildButton.text = "Rebuild Server"
startButton.isDisable = false
"Ready."
} else {
"Server build cancelled."
}
building = false;
}
}
}
@FXML
private fun onStart() {
if (started) {
createDialog("warning", "You should only kill the server if\nabsolutely necessary. Data loss may occur.\nContinue anyway?", "Yes", "No", false)
return;
}
if (!File(directory + "eula.txt").exists()) {
val res = eulaDialog()
if (res) {
File(directory + "eula.txt").writeText("eula=true")
} else {
return;
}
}
started = true
statusBar.text = "The Minecraft Server is now running. Shutdown the server to unlock the settings."
worldSettingsPane.isDisable = true
directoryPane.isDisable = true
parentPane.isDisable = true
buildButton.isDisable = true
defaultsButton.isDisable = true
startButton.text = "Kill Server"
@Suppress("OPT_IN_USAGE")
GlobalScope.launch(Dispatchers.Default) {
val builder = ProcessBuilder("java", "-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) {
proc.destroy()
}
println(line);
line = br.readLine()
}
} catch (e: IOException) {
println("Stream closed")
}
withContext(Dispatchers.JavaFx) {
statusBar.text = if (asyncResult) {
asyncResult = false
"Server killed."
} else {
"Server stopped."
}
worldSettingsPane.isDisable = false
directoryPane.isDisable = false
parentPane.isDisable = false
buildButton.isDisable = false
defaultsButton.isDisable = false
startButton.text = "Start Server"
started = false
}
}
}
private fun eulaDialog(): Boolean {
var result = false
val resources = App().javaClass.getResource("icons/warning.png")
val dialog = Stage()
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.onMouseClicked = EventHandler<MouseEvent>() {
result = false
dialog.hide()
}
noButton.isDefaultButton = true
val yesButton = Button("I Agree")
yesButton.onMouseClicked = EventHandler<MouseEvent>() {
result = true
dialog.hide()
}
val eula = Button("View EULA")
eula.onMouseClicked = EventHandler<MouseEvent>() {
val desktop = Desktop.getDesktop()
if (desktop.isSupported(Desktop.Action.BROWSE)) {
println("made it!")
try {
desktop.browse(URI("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, no: String, hold: Boolean): Boolean {
var result = false
val resources = App().javaClass.getResource("icons/$type.png")
val dialog = Stage()
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.onMouseClicked = EventHandler<MouseEvent>() {
if (hold) {
result = false
} else {
asyncResult = false
}
dialog.hide()
}
val yesButton = Button(yes)
yesButton.onMouseClicked = EventHandler<MouseEvent>() {
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
if (!File(directory).isDirectory) {
return false;
}
if (directory[directory.length-1] != File.separatorChar)
directory += File.separatorChar
val hasDummy = File(directory + "ServerForDummies").isDirectory
val hasProperties = File(directory + File.separator + "server.properties").isFile
val hasServer = findServerJar()
if (hasDummy && hasServer) {
// server complete, just read jproperties
statusBar.text = "Server found!"
startButton.isDisable = false
buildButton.text = "Rebuild Server"
} else if (hasDummy && !hasServer && hasProperties) {
// just needs to be built
startButton.isDisable = true
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 \nServerForDummies. Errors may occur; copying\nthe world directories to a new folder may be\nsafer. Proceed anyway?", "Yes", "No", true)
statusBar.text = "Ready."
if (result) {
startButton.isDisable = false
}
return result
} else {
// assume clean directory
val result = createDialog("info", "There is no server in this directory.\nCreate one?", "Yes", "No", true)
if (result) {
File(directory + "ServerForDummies").mkdir()
startButton.isDisable = true
buildButton.text = "Build Server"
}
statusBar.text = "Ready."
return result
}
return true;
}
private fun findServerJar(): Boolean {
// search for spigot jar
// major version
for (i in 25 downTo 8) {
// patch number
for (j in 15 downTo 0) {
var spigotFile: String = ""
if (j == 0)
spigotFile += "spigot-1.$i.jar"
else
spigotFile += "spigot-1.$i.$j.jar";
if (File(directory + spigotFile).isFile) {
server.jar = directory + spigotFile
return true
}
}
}
// try vanilla server if no spigot server
if (File(directory + "server.jar").isFile) {
server.jar = directory + "server.jar"
return true
}
return false
}
}