Compare commits

..
Author SHA1 Message Date
D4rkr34lm 24a7dae6d8 Apply temporary fix for rendering of the cursor 2026-06-17 22:29:44 +02:00
D4rkr34lm 99f7610d3d . 2026-06-12 19:48:16 +02:00
D4rkr34lm 2c05f06e2b Merge branch 'main' into BauSystem/add-cannon-automation-tool
# Conflicts:
#	BauSystem/BauSystem_Main/build.gradle.kts
2026-06-12 17:21:08 +02:00
D4rkr34lm 9606c6bc0d . 2026-06-12 16:50:26 +02:00
D4rkr34lm 34e7c62768 First draft of initial user flow 2026-06-12 13:08:35 +02:00
80 changed files with 803 additions and 3321 deletions
-5
View File
@@ -15,11 +15,6 @@ bin/
.vscode
.settings
# Language Server
**/.project
**/.factorypath
**/.classpath
# Other
lib
/WebsiteBackend/data
+1 -2
View File
@@ -18,7 +18,7 @@
*/
plugins {
steamwar.java
steamwar.kotlin
widener
}
@@ -35,7 +35,6 @@ dependencies {
compileOnly(libs.classindex)
annotationProcessor(libs.classindex)
compileOnly(project(":SpigotCore", "default"))
compileOnly(project(":KotlinCore", "default"))
compileOnly(libs.axiom)
compileOnly(libs.authlib)
@@ -22,6 +22,7 @@ package de.steamwar.bausystem;
import de.steamwar.bausystem.config.BauServer;
import de.steamwar.bausystem.configplayer.Config;
import de.steamwar.bausystem.configplayer.ConfigConverter;
import de.steamwar.bausystem.features.cannonCore.CannonCoreRegistrationKt;
import de.steamwar.bausystem.features.gui.BauGUI;
import de.steamwar.bausystem.features.script.lua.SteamWarLuaPlugin;
import de.steamwar.bausystem.features.script.lua.libs.LuaLib;
@@ -135,6 +136,8 @@ public class BauSystem extends JavaPlugin implements Listener {
String identifier = BauServerInfo.getOwnerUser().getUUID().toString().replace("-", "");
WorldIdentifier.set("bau/" + Core.getVersion() + "/" + identifier);
CannonCoreRegistrationKt.register(this);
}
@EventHandler
@@ -17,10 +17,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.kotlin.ui
package de.steamwar.bausystem.features.cannonCore
interface RenderObject {
fun destroy()
import org.bukkit.Location
class CannonCore(val location: Location) {
companion object Manager {
var activeCores: Observable<List<CannonCore>> = Observable(ArrayList())
}
}
fun render()
}
@@ -17,18 +17,18 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Optional
package de.steamwar.bausystem.features.cannonCore
open class VelocityServer : DevServer() {
import de.steamwar.bausystem.SWUtils
import de.steamwar.command.SWCommand
import org.bukkit.entity.Player
@get:Input
@get:Optional
var packetDecodeLogging: Boolean? = false
object CannonCoreCommand : SWCommand("cannoncore") {
init {
doFirst {
if (packetDecodeLogging == true) dParams.put("velocity.packet-decode-logging", "true")
}
@Register
fun giveCannonCoreWand(player: Player) {
val wandItem = CannonCoreWand.getWandItem()
SWUtils.giveItemToPlayer(player, wandItem)
}
}
}
@@ -17,30 +17,30 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.kotlin.ui
package de.steamwar.bausystem.features.cannonCore
import de.steamwar.kotlin.ui.components.item
import de.steamwar.kotlin.util.count
import kotlinx.coroutines.flow.MutableStateFlow
import de.steamwar.entity.RBlockDisplay
import de.steamwar.entity.REntityAction
import de.steamwar.entity.REntityServer
import de.steamwar.entity.RInteraction
import org.bukkit.Location
import org.bukkit.Material
import org.bukkit.entity.Player
import org.bukkit.inventory.ItemStack
val Counter = MutableStateFlow(0)
class CannonCoreEntity: RBlockDisplay {
val entityMaterial = Material.COMMAND_BLOCK
class TestInv(player: Player): UIInventory(player) {
override fun view() {
inventory(3, "") {
item {
val amount by Counter.map { it + 1 }.listen()
item = ItemStack.of(Material.STONE)
.count(amount)
x = 0
y = 0
onClick {
Counter.value += 1
}
}
}
val hitbox: RInteraction
constructor(server: REntityServer, location: Location, onClick: (player: Player, clickAction: REntityAction) -> Unit) : super(server, location) {
setBlock(entityMaterial.createBlockData())
hitbox = RInteraction(server, location)
hitbox.setCallback(onClick)
}
}
override fun die() {
super.die()
hitbox.die()
}
}
@@ -17,7 +17,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.kotlin.ui
package de.steamwar.bausystem.features.cannonCore
@DslMarker
annotation class RenderMarker()
import org.bukkit.plugin.Plugin
fun register(plugin: Plugin) {
CannonCoreCommand.register()
val pluginManager = plugin.server.pluginManager
pluginManager.registerEvents(CannonCoreWand, plugin)
}
@@ -0,0 +1,76 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.cannonCore
import de.steamwar.bausystem.BauSystem
import de.steamwar.bausystem.utils.ItemUtils
import de.steamwar.core.SWPlayer
import de.steamwar.inventory.SWItem
import org.bukkit.Material
import org.bukkit.entity.Player
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.player.PlayerItemHeldEvent
import org.bukkit.inventory.ItemStack
import org.bukkit.plugin.java.JavaPlugin
object CannonCoreWand : Listener {
val wandId = "CANNON_CORE_WAND"
val wandMaterial = Material.BREEZE_ROD
fun getWandItem(): ItemStack {
val title = "§eCannon Core Wand"
val lore = listOf(
"§eRight Click §8- §7Create a new cannon core"
)
val wand = SWItem(wandMaterial, title, lore, false, null)
val item = wand.itemStack
return ItemUtils.setItem(item, wandId)
}
fun isWandItem(itemStack: ItemStack?): Boolean {
return ItemUtils.isItem(itemStack, wandId)
}
@EventHandler
fun onPlayerEquip(event: PlayerItemHeldEvent) {
val player = event.player
val swPlayer = SWPlayer.of(event.player)
val item = player.inventory.getItem(event.newSlot)
if (isWandItem(item)) {
scheduleDisplayUpdate(player)
} else {
swPlayer.removeComponent(EmptyCannonCoreWandDisplay::class.java)
}
}
private fun scheduleDisplayUpdate(player: Player) {
BauSystem.runTaskLater(JavaPlugin.getPlugin(BauSystem::class.java), Runnable { updateDisplay(player) }, 2)
}
private fun updateDisplay(player: Player) {
if (isWandItem(player.inventory.itemInMainHand)) {
EmptyCannonCoreWandDisplay(player)
}
}
}
@@ -17,27 +17,29 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.advancements;
package de.steamwar.bausystem.features.cannonCore
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import de.steamwar.core.SWPlayer
import de.steamwar.entity.REntityServer
import org.bukkit.entity.Player
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URI;
class CannonCoresDisplay: SWPlayer.Component {
val displayServer = REntityServer()
val unlisten: () -> Unit
public class Items {
constructor(owner: Player) {
unlisten = CannonCore.activeCores.observe( { cores ->
displayServer.entities.forEach { it.die() }
cores.forEach { CannonCoreEntity(displayServer, it.location) { _, _ -> println("Handler clicked at ${it.location}") } }
})
}
/**
* Loaded from https://github.com/retrooper/packetevents/blob/2.0/mappings/registries/item.json
*/
public static final JsonObject values;
override fun onMount(player: SWPlayer) {
displayServer.addPlayer(player.player)
}
static {
try {
values = new Gson().fromJson(new BufferedReader(new InputStreamReader(URI.create("https://raw.githubusercontent.com/retrooper/packetevents/refs/heads/2.0/mappings/registries/item.json").toURL().openConnection().getInputStream())), JsonObject.class);
} catch (Exception e) {
throw new IllegalStateException(e);
}
override fun onUnmount(player: SWPlayer?) {
unlisten()
displayServer.close()
}
}
@@ -0,0 +1,57 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.cannonCore
import de.steamwar.core.SWPlayer
import de.steamwar.cursor.Cursor
import org.bukkit.Material
import org.bukkit.entity.Player
class EmptyCannonCoreWandDisplay : SWPlayer.Component {
val coresDisplay: CannonCoresDisplay
var cursor: Cursor
constructor(owner: Player) {
coresDisplay = CannonCoresDisplay(owner)
SWPlayer.of(owner).setComponent(this)
cursor = Cursor(
coresDisplay.displayServer, owner, Material.GLASS, Material.COMMAND_BLOCK,
listOf(
Cursor.CursorMode.BLOCK_ALIGNED
),
) { location, hitEntity, action ->
print("Hello")
}
}
override fun onMount(player: SWPlayer) {
coresDisplay.onMount(player)
}
override fun onUnmount(player: SWPlayer) {
player.getComponent(Cursor::class.java)
.filter { it === cursor }
.ifPresent { player.removeComponent(Cursor::class.java) }
coresDisplay.onUnmount(player)
}
}
@@ -17,25 +17,31 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.kotlin.ui
package de.steamwar.bausystem.features.cannonCore
import kotlinx.coroutines.Job
typealias Observer<T> = (currentValue: T) -> Unit
abstract class StateFlowListener {
private val jobs = mutableMapOf<Any, Job>()
class Observable<T>(var value: T) {
private val observers = ArrayList<Observer<T>>()
internal fun track(key: Any, createJob: () -> Job) {
if (key in jobs) return
fun set(value: T) {
val oldValue = this.value
this.value = value
val job = createJob()
jobs[key] = job
job.invokeOnCompletion { jobs.remove(key) }
observers.forEach { it.invoke(value) }
}
abstract fun update()
open fun destroy() {
jobs.values.toList().forEach { it.cancel() }
jobs.clear()
fun get(): T {
return value
}
}
fun observe(observer: Observer<T>): () -> Unit {
observers.add(observer)
observer(value)
return {removeObserver(observer)}
}
fun removeObserver(observer: Observer<T>) {
observers.remove(observer)
}
}
@@ -290,7 +290,7 @@ public class KillcheckerVisualizer {
}
rEntities.get(point).die();
}
RBlockDisplay entity = new RBlockDisplay(outlinePoints.contains(point) ? outline : inner, point.toLocation(WORLD, 0, 0, 0));
RBlockDisplay entity = new RBlockDisplay(outlinePoints.contains(point) ? outline : inner, point.toLocation(WORLD, 0.5, 0, 0.5));
entity.setBlock(MATERIALS[Math.min(count - 1, MATERIALS.length) - 1].createBlockData());
rEntities.put(point, entity);
if (outlinePoints.contains(point)) outlinePointsCache.add(point);
@@ -185,16 +185,17 @@ public class TestblockCommand extends SWCommand {
return new TypeMapper<SchematicNode>() {
@Override
public List<String> tabCompletes(CommandSender commandSender, PreviousArguments previousArguments, String s) {
List<String> stringList = new ArrayList<>();
List<String> stringList = new ArrayList<>(SchematicNode.getNodeTabcomplete(SteamwarUser.get(((Player) commandSender).getUniqueId()), s));
stringList.addAll(SchematicNode.getNodeTabcomplete(SteamwarUser.byId(0), s));
stringList.addAll(SchematicNode.getNodeTabcomplete(SteamwarUser.get(((Player) commandSender).getUniqueId()), s));
return stringList;
}
@Override
public SchematicNode map(CommandSender commandSender, PreviousArguments previousArguments, String s) {
SchematicNode node = SchematicNode.getNodeFromPath(SteamwarUser.byId(0), s);
if (node == null) node = SchematicNode.getNodeFromPath(SteamwarUser.get(((Player) commandSender).getUniqueId()), s);
SchematicNode node = SchematicNode.getNodeFromPath(SteamwarUser.get(((Player) commandSender).getUniqueId()), s);
if (node == null) {
node = SchematicNode.getNodeFromPath(SteamwarUser.byId(0), s);
}
return node;
}
};
@@ -19,7 +19,6 @@
package de.steamwar.bausystem.features.simulator;
import com.destroystokyo.paper.event.server.ServerTickEndEvent;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.SWUtils;
@@ -78,16 +77,8 @@ public class SimulatorCursor implements Listener {
return isSimulatorItem(player.getInventory().getItemInMainHand()) || isSimulatorItem(player.getInventory().getItemInOffHand());
}
private static Set<Player> scheduledUpdates = new HashSet<>();
private static void scheduleCursorUpdate(Player player) {
scheduledUpdates.add(player);
}
@EventHandler
public void onServerTickEnd(ServerTickEndEvent event) {
scheduledUpdates.forEach(SimulatorCursor::calcCursor);
scheduledUpdates.clear();
BauSystem.runTaskLater(BauSystem.getInstance(), () -> calcCursor(player), 1);
}
@EventHandler
@@ -131,7 +122,6 @@ public class SimulatorCursor implements Listener {
public void onPlayerQuit(PlayerQuitEvent event) {
cursorType.remove(event.getPlayer());
removeCursor(event.getPlayer());
scheduledUpdates.remove(event.getPlayer());
}
private static final Map<Player, Long> LAST_SNEAKS = new HashMap<>();
@@ -165,7 +165,6 @@ public class TPSSystem implements Listener {
}
@Register(value = "default", description = "TPSLIMIT_DEFAULT_HELP")
@Register(value = "rate")
public void reset(@Validator Player player) {
TickManager.impl.setTickRate(20.0F);
sendTickRateChange();
@@ -188,18 +187,15 @@ public class TPSSystem implements Listener {
}
@Register(value = {"rate", "0"}, description = "TICK_FREEZE_HELP")
public void rate(@Validator Player player) {
@Register(value = "freeze", description = "TICK_FREEZE_HELP_2")
public void freeze(@Validator Player player) {
TickManager.impl.setFreeze(true);
sendTickRateChange();
}
@Register(value = "freeze", description = "TICK_FREEZE_HELP_2")
public void freezeToggle(@Validator Player player) {
if (TickManager.impl.isFrozen()) {
TickManager.impl.setTickRate(20.0F);
} else {
TickManager.impl.setFreeze(true);
}
@Register(value = "unfreeze", description = "TICK_UNFREEZE_HELP")
public void unfreeze(@Validator Player player) {
TickManager.impl.setTickRate(20.0F);
sendTickRateChange();
}
}
@@ -153,11 +153,7 @@ public class TraceCommand extends SWCommand {
@Register(value = "isolate", description = "TRACE_COMMAND_HELP_ISOLATE")
public void isolate(@Validator Player player, Trace trace, @ErrorMessage("TRACE_RECORD_ID_INVALID") TNTPoint... records) {
if (records.length == 0) {
TraceManager.instance.isolate(player, trace, trace.getRecords().toArray(TNTPoint[]::new));
} else {
TraceManager.instance.isolate(player, trace, records);
}
TraceManager.instance.isolate(player, records);
BauSystem.MESSAGE.send("TRACE_MESSAGE_ISOLATE", player);
}
@@ -347,10 +347,9 @@ public class TraceManager implements Listener {
* Toggles the isolated render for the given records and player
*
* @param player the player the trace is shown to
* @param ptrace the trace for whitch isolation is toggled
* @param records the records of the trace for which isolation is toggled
* @param records the record for which isolation is toggled
*/
public void isolate(Player player, Trace ptrace, TNTPoint... records) {
public void isolate(Player player, TNTPoint... records) {
unfollow(player);
Region region = Region.getRegion(player.getLocation());
@@ -375,20 +374,12 @@ public class TraceManager implements Listener {
isolateFlag.toggleId(record.getTntId());
}
if (isolateFlag.isEmpty() && playerTraceShowData.hasViewFlagOnly(IsolateFlag.class) && records.length != 0) {
playerTraceShowData.removeViewFlag(IsolateFlag.class);
}
PlayerTraceShowData finalPlayerTraceShowData = playerTraceShowData;
tracesByRegion.getOrDefault(region, Collections.emptyMap()).forEach((integer, trace) -> {
if (trace.getUuid() == ptrace.getUuid() || finalPlayerTraceShowData.hasNoViewFlags()) {
trace.render(player, finalPlayerTraceShowData);
followerMap.getOrDefault(player, Collections.emptySet()).forEach(follower -> {
trace.render(follower, finalPlayerTraceShowData);
});
} else {
trace.hide(player);
}
trace.render(player, finalPlayerTraceShowData);
followerMap.getOrDefault(player, Collections.emptySet()).forEach(follower -> {
trace.render(follower, finalPlayerTraceShowData);
});
});
}
@@ -135,13 +135,6 @@ public class TraceRecorder implements Listener {
Iterator<TNTPrimed> iter = trackedTNT.getOrDefault(region, Collections.emptyList()).iterator();
while (iter.hasNext()) {
TNTPrimed tnt = iter.next();
if (tnt.isDead()) {
iter.remove();
tntSpawnRegion.remove(tnt);
historyMap.remove(tnt);
tntSpawnRegion.remove(tnt);
continue;
}
if (tnt.getFuseTicks() == 80) continue;
TNTPoint record = record(tnt, wrappedTrace, Collections.emptyList());
if (record == null) {
@@ -95,8 +95,4 @@ public class PlayerTraceShowData {
public void addViewFlag(ViewFlag viewFlag) {
viewFlags.put(viewFlag.getClass(), viewFlag);
}
public <T extends ViewFlag> void removeViewFlag(Class<T> clazz) {
viewFlags.remove(clazz);
}
}
@@ -48,9 +48,9 @@ import static de.steamwar.bausystem.features.util.TNTClickListener.TNT_CLICK_DET
*/
public class TraceEntity extends RBlockDisplay {
public static final float TNT_VISUAL_SCALE = 0.98F;
public static final float TNT_VISUAL_OFFSET = -TNT_VISUAL_SCALE / 2.0F;
public static final Transformation TNT_VISUAL_TRANSFORM = new Transformation(
private static final float TNT_VISUAL_SCALE = 0.98F;
private static final float TNT_VISUAL_OFFSET = -TNT_VISUAL_SCALE / 2.0F;
private static final Transformation TNT_VISUAL_TRANSFORM = new Transformation(
new Vector3f(TNT_VISUAL_OFFSET, 0.0F, TNT_VISUAL_OFFSET),
new Quaternionf(0, 0, 0, 1),
new Vector3f(TNT_VISUAL_SCALE, TNT_VISUAL_SCALE, TNT_VISUAL_SCALE),
@@ -131,7 +131,6 @@ public abstract class ViewFlag {
if (yLocation.distanceSquared(representative.getLocation()) >= 1.0 / 256.0 && yLocation.distanceSquared(previous.getLocation()) >= 1.0 / 256.0) {
RBlockDisplay y = new RBlockDisplay(server, yLocation);
y.setBlock(Material.WHITE_STAINED_GLASS.createBlockData());
y.setTransform(TraceEntity.TNT_VISUAL_TRANSFORM);
}
Location secoundLocation;
@@ -144,7 +143,6 @@ public abstract class ViewFlag {
if (secoundLocation.distanceSquared(representative.getLocation()) >= 1.0 / 256.0 && secoundLocation.distanceSquared(previous.getLocation()) >= 1.0 / 256.0) {
RBlockDisplay second = new RBlockDisplay(server, secoundLocation);
second.setBlock(Material.WHITE_STAINED_GLASS.createBlockData());
second.setTransform(TraceEntity.TNT_VISUAL_TRANSFORM);
}
}
}
@@ -51,10 +51,6 @@ public class IsolateFlag extends ViewFlag {
}
}
public boolean isEmpty() {
return tntToIsolate.isEmpty();
}
@Override
public Stream<TNTPoint> filter(Stream<TNTPoint> records) {
if (tntToIsolate.isEmpty()) return records;
@@ -139,7 +139,6 @@ public class NoClipCommand extends SWCommand implements Listener {
@EventHandler(ignoreCancelled = true)
public void onBlock(BlockCanBuildEvent event) {
if (event.getPlayer() == null) return;
if (SWPlayer.of(event.getPlayer()).hasComponent(NoClipData.class)) {
event.setBuildable(true);
}
@@ -20,28 +20,30 @@
package de.steamwar.bausystem.utils;
import de.steamwar.bausystem.SWUtils;
import lombok.experimental.UtilityClass;
import org.bukkit.NamespacedKey;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
@UtilityClass
public class ItemUtils {
public final class ItemUtils {
private final NamespacedKey ITEM_KEY = SWUtils.getNamespaceKey("bau_item");
private static final NamespacedKey ITEM_KEY = SWUtils.getNamespaceKey("bau_item");
public boolean isItem(ItemStack itemStack, String tag) {
private ItemUtils() {
}
public static boolean isItem(ItemStack itemStack, String tag) {
String value = getTag(itemStack, ITEM_KEY);
return value != null && value.equals(tag);
}
public void setItem(ItemStack itemStack, String tag) {
public static ItemStack setItem(ItemStack itemStack, String tag) {
setTag(itemStack, ITEM_KEY, tag);
return itemStack;
}
public String getTag(ItemStack itemStack, NamespacedKey key) {
public static String getTag(ItemStack itemStack, NamespacedKey key) {
if (itemStack == null) {
return null;
}
@@ -56,7 +58,7 @@ public class ItemUtils {
return container.get(key, PersistentDataType.STRING);
}
public void setTag(ItemStack itemStack, NamespacedKey key, String value) {
public static void setTag(ItemStack itemStack, NamespacedKey key, String value) {
if (itemStack == null) {
return;
}
+1 -1
View File
@@ -37,6 +37,6 @@ tasks.register<DevServer>("DevBau21") {
dependsOn(":SpigotCore:shadowJar")
dependsOn(":BauSystem:shadowJar")
dependsOn(":SchematicSystem:shadowJar")
dependsOn(":KotlinCore:shadowJar")
template = "Bau21"
debug = true
}
+5 -18
View File
@@ -7,7 +7,6 @@ import com.github.ajalt.mordant.rendering.TextColors
import com.github.ajalt.mordant.rendering.TextStyles
import de.steamwar.db.Database
import de.steamwar.db.execute
import de.steamwar.db.executeScript
import de.steamwar.db.useDb
import java.io.File
@@ -23,25 +22,13 @@ class ResetCommand : CliktCommand() {
val schema = schemaFile.readText()
execute("SET FOREIGN_KEY_CHECKS=0;") { }
val databaseObjects = execute("SHOW FULL TABLES;") { it.getString(1) to it.getString(2) }
for (view in databaseObjects.filter { it.second == "VIEW" }.map { it.first }) {
execute("DROP VIEW IF EXISTS `${view.replace("`", "``")}`;") { }
}
for (table in databaseObjects.filter { it.second == "BASE TABLE" }.map { it.first }) {
execute("DROP TABLE IF EXISTS `${table.replace("`", "``")}`;") { }
val tables = execute("SHOW TABLES;") { it.getString(1) }
for (table in tables) {
execute("DROP TABLE IF EXISTS $table;") { }
}
executeScript(schema)
val seed = javaClass.getResource("/db/reset-seed.sql")
?: throw CliktError("Reset seed file not found!")
executeScript(seed.readText())
execute("SET FOREIGN_KEY_CHECKS=1;") { }
execute(schema) { }
echo(TextColors.brightGreen(TextStyles.bold("Database reset!")))
}
}
}
+5 -28
View File
@@ -108,10 +108,8 @@ class DevCommand : CliktCommand("dev") {
if (!templateFile.exists()) {
throw CliktError("Could not find world template: ${templateFile.absolutePath}")
}
worldFile.parentFile?.mkdirs()
templateFile.copyRecursively(worldFile)
}
val worldDir = worldFile.parentFile ?: workingDir
val devFile = File("/configs/DevServer/${System.getProperty("user.name")}.$port.$version")
if (System.getProperty("user.name") != "minecraft") {
@@ -122,11 +120,10 @@ class DevCommand : CliktCommand("dev") {
args, jvmArgs, listOf(
jarFile,
*(if (forceUpgrade) arrayOf("-forceUpgrade") else arrayOf()),
"--log-strip-color",
"--port", port.toString(),
"--level-name", worldFile.name,
"--world-dir", worldDir.absolutePath,
"nogui",
"--world-dir", workingDir.absolutePath,
"--nogui",
*(if (plugins != null) arrayOf("--plugins", plugins!!.absolutePathString()) else arrayOf())
), serverDir
)
@@ -159,16 +156,6 @@ class DevCommand : CliktCommand("dev") {
val jvmArgOverrides = arrayOf("--add-opens", "java.base/jdk.internal.misc=ALL-UNNAMED")
val jvmNonJava8Params = arrayOf(
*jvmArgOverrides,
"-XX:-CRIUSecProvider"
)
val extendedStartupParams = arrayOf(
"-Dpaper.disablePluginRemapping=true",
"-javaagent:/jars/AccessWidener.jar=start"
)
val supportedVersionJars = mapOf(
8 to "/jars/paper-1.8.8.jar",
9 to "/jars/spigot-1.9.4.jar",
@@ -241,23 +228,13 @@ class DevCommand : CliktCommand("dev") {
}
fun runServer(args: List<String>, jvmArgs: List<String>, cmd: List<String>, serverDir: File) {
val effectiveJvmArgs = mutableListOf<String>()
effectiveJvmArgs += jvmArgs
if (!isVelocity(server)) {
extendedStartupParams.forEach { arg ->
if (effectiveJvmArgs.none { it == arg }) {
effectiveJvmArgs += arg
}
}
}
val process = ProcessBuilder(
jvm?.absolutePath
?: if (isJava8(server)) "/usr/lib/jvm/openj9-8/bin/java" else "/usr/lib/jvm/openj9-21/bin/java",
*effectiveJvmArgs.toTypedArray(),
?: if (isJava8(server)) "/usr/lib/jvm/openj9-8/bin/java" else "java",
*jvmArgs.toTypedArray(),
*args.toTypedArray(),
*jvmDefaultParams,
*(if (isJava8(server)) arrayOf() else jvmNonJava8Params),
*(if (isJava8(server)) arrayOf() else jvmArgOverrides),
*(if (profile) arrayOf("-javaagent:/jars/LixfelsProfiler.jar=start") else arrayOf()),
"-Xshareclasses:nonfatal,name=$server",
"-jar",
+9 -120
View File
@@ -16,14 +16,15 @@ import java.sql.ResultSet
import java.util.*
object Database {
val host: String
val port: String
val database: String
val username: String
val password: String
lateinit var host: String
lateinit var port: String
lateinit var database: String
lateinit var db: Database
init {
fun ensureConnected() {
if (::db.isInitialized) {
return
}
val config = File(System.getProperty("user.home"), "mysql.properties")
if (!config.exists()) {
@@ -37,14 +38,9 @@ object Database {
host = props.getProperty("host")
port = props.getProperty("port")
database = props.getProperty("database")
username = props.getProperty("user")
password = props.getProperty("password")
}
fun ensureConnected() {
if (::db.isInitialized) {
return
}
val username = props.getProperty("user")
val password = props.getProperty("password")
val url = "jdbc:mariadb://$host:$port/$database"
@@ -83,113 +79,6 @@ fun <T> JdbcTransaction.executeSingle(sql: String, transform: (ResultSet) -> T):
}.single()
}
fun JdbcTransaction.executeScript(sql: String) {
for (statement in splitSqlScript(sql)) {
exec(statement) { }
}
}
private fun splitSqlScript(sql: String): List<String> {
val statements = mutableListOf<String>()
val current = StringBuilder()
var quote: Char? = null
var inLineComment = false
var inBlockComment = false
var index = 0
fun addStatement() {
val statement = current.toString().trim()
if (statement.isNotEmpty()) {
statements += statement
}
current.clear()
}
while (index < sql.length) {
val char = sql[index]
val next = sql.getOrNull(index + 1)
if (inLineComment) {
current.append(char)
if (char == '\n') {
inLineComment = false
}
index++
continue
}
if (inBlockComment) {
current.append(char)
if (char == '*' && next == '/') {
current.append(next)
inBlockComment = false
index += 2
} else {
index++
}
continue
}
if (quote != null) {
current.append(char)
if (char == '\\' && quote != '`' && next != null) {
current.append(next)
index += 2
continue
}
if (char == quote) {
if (next == quote) {
current.append(next)
index += 2
continue
}
quote = null
}
index++
continue
}
when {
char == '-' && next == '-' -> {
current.append(char).append(next)
inLineComment = true
index += 2
}
char == '#' -> {
current.append(char)
inLineComment = true
index++
}
char == '/' && next == '*' -> {
current.append(char).append(next)
inBlockComment = true
index += 2
}
char == '\'' || char == '"' || char == '`' -> {
current.append(char)
quote = char
index++
}
char == ';' -> {
addStatement()
index++
}
else -> {
current.append(char)
index++
}
}
}
addStatement()
return statements
}
fun useDb(statement: JdbcTransaction.() -> Unit) {
de.steamwar.db.Database.ensureConnected()
transaction(de.steamwar.db.Database.db, statement = statement)
File diff suppressed because one or more lines are too long
@@ -86,24 +86,6 @@ class CheckedSchematic(id: EntityID<CompositeID>) : CompositeEntity(id) {
useDb {
find { (CheckedSchematicTable.nodeOwner eq owner.id) and (CheckedSchematicTable.seen eq false) }.orderBy(CheckedSchematicTable.endTime to SortOrder.DESC).toList()
}
@JvmStatic
fun countAccepted(owner: SteamwarUser) =
useDb {
find { (CheckedSchematicTable.nodeOwner eq owner.id) and (CheckedSchematicTable.declineReason eq "freigegeben") }.count()
}
@JvmStatic
fun countAccepted(owner: SteamwarUser, type: String) =
useDb {
find { (CheckedSchematicTable.nodeOwner eq owner.id) and (CheckedSchematicTable.declineReason eq "freigegeben") and (CheckedSchematicTable.nodeType like "$type%") }.count()
}
@JvmStatic
fun countChecked(validator: SteamwarUser) =
useDb {
find { CheckedSchematicTable.validator eq validator.id }.count()
}
}
val node by CheckedSchematicTable.nodeId.transform({ it?.let { EntityID(it, SchematicNodeTable) } }, { it?.value })
@@ -130,43 +130,6 @@ class EventFight(id: EntityID<Int>) : IntEntity(id), Comparable<EventFight> {
}
)
}
@JvmStatic
fun countEventFights(fighter: SteamwarUser) =
useDb {
exec(
"SELECT COUNT(DISTINCT F.FightID) AS FightCount FROM FightPlayer INNER JOIN Fight F on FightPlayer.FightID = F.FightID INNER JOIN EventFight EF on F.FightID = EF.Fight WHERE UserID = ?",
args = listOf(IntegerColumnType() to fighter.id.value)
) {
if (it.next()) {
it.getLong("FightCount")
} else {
0
}
}
?: 0
}
@JvmStatic
fun countPlacement(fighter: SteamwarUser, placement: Int) =
useDb {
exec(
"""
SELECT COUNT(DISTINCT EventFight.EventID) AS PlacementCount FROM TeamTeilnahme
INNER JOIN EventFight ON EventFight.EventID = TeamTeilnahme.EventID
INNER JOIN FightPlayer ON FightPlayer.FightID = EventFight.Fight
WHERE (IF(FightPlayer.Team = 1, EventFight.TeamBlue, EventFight.TeamRed)) = TeamTeilnahme.TeamID AND UserID = ? AND Placement = ?
""".trimIndent(),
args = listOf(IntegerColumnType() to fighter.id.value, IntegerColumnType() to placement)
) {
if (it.next()) {
it.getInt("PlacementCount")
} else {
0
}
}
?: 0
}
}
val fightID by EventFightTable.id.transform({ EntityID(it, EventFightTable) }, { it.value })
@@ -237,7 +200,6 @@ class EventFight(id: EntityID<Int>) : IntEntity(id), Comparable<EventFight> {
override fun delete() =
useDb {
EventRelation.deleteRelations(this@EventFight)
super.delete()
}
}
}
@@ -159,13 +159,10 @@ class EventGroup(id: EntityID<Int>) : IntEntity(id) {
}
override fun delete() =
useDb {
EventRelation.deleteRelations(this@EventGroup)
super.delete()
}
useDb { super.delete() }
enum class EventGroupType {
GROUP_STAGE,
ELIMINATION_STAGE
}
}
}
@@ -24,10 +24,8 @@ import org.jetbrains.exposed.v1.core.and
import org.jetbrains.exposed.v1.core.dao.id.EntityID
import org.jetbrains.exposed.v1.core.dao.id.IntIdTable
import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.core.or
import org.jetbrains.exposed.v1.dao.IntEntity
import org.jetbrains.exposed.v1.dao.IntEntityClass
import org.jetbrains.exposed.v1.jdbc.deleteWhere
import org.jetbrains.exposed.v1.jdbc.select
object EventRelationTable : IntIdTable("EventRelation") {
@@ -61,23 +59,6 @@ class EventRelation(id: EntityID<Int>) : IntEntity(id) {
fun getGroupRelations(group: EventGroup) =
useDb { find { (EventRelationTable.fromId eq group.id.value) and (EventRelationTable.fromType eq FromType.GROUP) }.toList() }
@JvmStatic
fun deleteRelations(fight: EventFight) =
useDb {
EventRelationTable.deleteWhere {
(EventRelationTable.fightId eq fight.id) or
((EventRelationTable.fromId eq fight.id.value) and (EventRelationTable.fromType eq FromType.FIGHT))
}
}
@JvmStatic
fun deleteRelations(group: EventGroup) =
useDb {
EventRelationTable.deleteWhere {
(EventRelationTable.fromId eq group.id.value) and (EventRelationTable.fromType eq FromType.GROUP)
}
}
@JvmStatic
fun create(fight: EventFight, fightTeam: FightTeam, fromType: FromType, fromId: Int, fromPlace: Int) =
useDb {
@@ -178,4 +159,4 @@ class EventRelation(id: EntityID<Int>) : IntEntity(id) {
enum class FromType {
FIGHT, GROUP
}
}
}
@@ -20,12 +20,9 @@
package de.steamwar.sql
import de.steamwar.sql.internal.useDb
import org.jetbrains.exposed.v1.core.IntegerColumnType
import org.jetbrains.exposed.v1.core.VarCharColumnType
import org.jetbrains.exposed.v1.core.dao.id.CompositeID
import org.jetbrains.exposed.v1.core.dao.id.CompositeIdTable
import org.jetbrains.exposed.v1.core.dao.id.EntityID
import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.core.inList
import org.jetbrains.exposed.v1.dao.CompositeEntity
import org.jetbrains.exposed.v1.dao.CompositeEntityClass
@@ -72,28 +69,6 @@ class FightPlayer(id: EntityID<CompositeID>) : CompositeEntity(id) {
useDb {
find { FightPlayerTable.fightId inList fightIds.toList() }.toList()
}
@JvmStatic
fun countFights(userId: Int) =
useDb {
find { FightPlayerTable.userId eq userId }.count()
}
@JvmStatic
fun countFights(userId: Int, type: String) =
useDb {
exec(
"SELECT COUNT(*) AS FightCount FROM FightPlayer INNER JOIN Fight F on FightPlayer.FightID = F.FightID WHERE UserID = ? AND GameMode LIKE ?",
args = listOf(IntegerColumnType() to userId, VarCharColumnType() to "$type%")
) {
if (it.next()) {
it.getInt("FightCount")
} else {
0
}
}
?: 0
}
}
val fightID by FightPlayerTable.fightId.transform({ EntityID(it, FightTable) }, { it.value })
@@ -146,12 +146,12 @@ public final class GameModeConfig<M, W> {
public final List<String> CheckQuestions;
/**
* The allowed checkers to check this schematic type denoted by a list of SteamwarUser ids.
* The allowed checkers to check this schematic type denoted by a list of SteamWar ids.
* The people need the {@link UserPerm#CHECK} to be able to check though.
*
* @implSpec {@code []} by default -> denoting every person with {@link UserPerm#CHECK} can check it
*/
public final Set<Integer> Checkers;
public final List<Integer> Checkers;
/**
* Bundle for countdowns during the fight
@@ -246,7 +246,7 @@ public final class GameModeConfig<M, W> {
}
CheckQuestions = loader.getStringList("CheckQuestions");
Checkers = loader.getIntSet("Checkers");
Checkers = loader.getIntList("Checkers");
Times = new TimesConfig(loader.with("Times"));
// Arena would be here to be in config order but needs Schematic.Size and EnterStages loaded afterwards
Schematic = new SchematicConfig<>(loader.with("Schematic"));
@@ -488,27 +488,6 @@ public final class GameModeConfig<M, W> {
*/
public final boolean NoFloor;
/**
* Allows Wind Charges to cross the Middle.
*
* @implSpec {@code false} by default
*/
public final boolean WindchargesCanCrossMiddle;
/**
* Allows Wind Charge interaction with Blocks.
*
* @implSpec {@code true} by default
*/
public final boolean WindchargesInteractWithBlocks;
/**
* Allows Wind Charge to destroy Water.
*
* @implSpec {@code false} by default
*/
public final boolean WindchargesDestroyWater;
private ArenaConfig(YMLWrapper<M, W> loader, SchematicConfig.SizeConfig Size, List<Integer> EnterStages) {
loaded = loader.canLoad();
WaterDepth = loader.getInt("WaterDepth", 0);
@@ -526,9 +505,6 @@ public final class GameModeConfig<M, W> {
Leaveable = loader.getBoolean("Leaveable", false);
AllowMissiles = loader.getBoolean("AllowMissiles", !EnterStages.isEmpty());
NoFloor = loader.getBoolean("NoFloor", false);
WindchargesCanCrossMiddle = loader.getBoolean("WindchargesCanCrossMiddle", false);
WindchargesInteractWithBlocks = loader.getBoolean("WindchargesInteractWithBlocks", true);
WindchargesDestroyWater = loader.getBoolean("WindchargesDestroyWater", false);
}
@ToString
@@ -953,13 +929,6 @@ public final class GameModeConfig<M, W> {
*/
public final boolean PersonalKits;
/**
* Maximal blast resistance for the blocks in the kit
*
* @implSpec {@code 9.0} by default
*/
public final double MaxBlastResistance;
/**
* Items (Valid spigot material values) that are not allowed in the personal kit
*/
@@ -971,10 +940,7 @@ public final class GameModeConfig<M, W> {
MemberDefault = loader.getString("MemberDefault", "default");
LeaderDefault = loader.getString("LeaderDefault", "default");
PersonalKits = loader.getBoolean("PersonalKits", false);
MaxBlastResistance = loader.getDouble("MaxBlastResistance", 9.0);
List forbiddenItems = new ArrayList<>(loader.getMaterialList("ForbiddenItems"));
forbiddenItems.addAll(SQLWrapper.impl.getMaterialWithGreaterBlastResistance(MaxBlastResistance));
ForbiddenItems = Collections.unmodifiableList(forbiddenItems);
ForbiddenItems = loader.getMaterialList("ForbiddenItems");
}
}
@@ -139,12 +139,6 @@ final class YMLWrapper<M, W> {
return get(path, o -> (List<Integer>) o);
}
public Set<Integer> getIntSet(String path) {
List<Integer> list = get(path, o -> (List<Integer>) o);
if (list.isEmpty()) return Collections.emptySet();
return Collections.unmodifiableSet(new HashSet<>(list));
}
public List<SchematicType> getSchematicTypeList(String path) {
List<String> list = getStringList(path);
if (list.isEmpty()) {
@@ -64,10 +64,6 @@ Arena:
AllowMissiles: false # defaults to true if EnterStages are present otherwise 'false'
# Denotes that there is no floor for this GameMode
NoFloor: false # defaults to false if missing
# Allows Wind Charges to cross the Middle.
WindchargesCanCrossMiddle: false # defaults to false if missing
# Allows Wind Charge interaction with Blocks.
WindchargesInteractWithBlocks: false # defaults to false if missing
Schematic:
# The size of the schematics
@@ -25,7 +25,6 @@ import de.steamwar.fightsystem.FightSystem;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentListener;
import de.steamwar.linkage.Linked;
import de.steamwar.sql.GameModeConfig;
import de.steamwar.sql.SchematicNode;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.UserPerm;
@@ -45,24 +44,14 @@ public class Check implements Listener {
new StateDependentListener(ArenaMode.Check, FightState.All, this);
}
private boolean checkPermission(SteamwarUser user, SchematicNode schematic) {
GameModeConfig<Object, String> gameModeConfig = GameModeConfig.getAll().stream()
.filter(gmc -> gmc.Schematic.Type != null && gmc.Schematic.Type.equals(schematic.getSchemtype()))
.findFirst()
.orElse(null);
if (gameModeConfig == null) gameModeConfig = GameModeConfig.getDefaults();
if (user.hasPerm(UserPerm.ADMINISTRATION)) return true;
if (gameModeConfig.Checkers.isEmpty() && user.hasPerm(UserPerm.CHECK)) return true;
return gameModeConfig.Checkers.contains(user.getId());
}
@EventHandler
public void onJoin(PlayerJoinEvent e) {
Player player = e.getPlayer();
SteamwarUser user = SteamwarUser.get(player.getUniqueId());
if (user.hasPerm(UserPerm.CHECK)) return;
SchematicNode schem = SchematicNode.getSchematicNode(Config.CheckSchemID);
if (checkPermission(user, schem)) return;
if (user.getId() == schem.getOwner()) return;
FightSystem.getMessage().send("CHECK_JOIN_DENIED", player);
@@ -1,7 +1,7 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@@ -31,16 +31,17 @@ import de.steamwar.linkage.Linked;
import net.md_5.bungee.api.ChatMessageType;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.Tag;
import org.bukkit.block.Block;
import org.bukkit.block.data.type.Dispenser;
import org.bukkit.block.data.type.DriedGhast;
import org.bukkit.entity.Player;
import org.bukkit.entity.TNTPrimed;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.*;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockDispenseEvent;
import org.bukkit.event.block.BlockFromToEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.EntityExplodeEvent;
import org.bukkit.event.entity.FoodLevelChangeEvent;
import org.bukkit.event.entity.PlayerDeathEvent;
@@ -257,16 +258,4 @@ public class Permanent implements Listener {
event.setCancelled(true);
FightSystem.getMessage().sendPrefixless("NO_BLOCK_BREAK", event.getPlayer(), ChatMessageType.ACTION_BAR);
}
/**
* Prevents Dried Ghast from spawning and plants from growing
*/
@EventHandler
public void onBlockGrow(BlockGrowEvent event) {
var type = event.getBlock().getType();
if (event.getBlock().getBlockData() instanceof DriedGhast || Tag.CROPS.isTagged(type) || Tag.SAPLINGS.isTagged(type)) {
event.setCancelled(true);
}
}
}
@@ -72,9 +72,6 @@ public class WaterRemover implements Listener {
@EventHandler
public void handleEntityExplode(EntityExplodeEvent event) {
if (event.getEntityType() == EntityType.WIND_CHARGE && !Config.GameModeConfig.Arena.WindchargesDestroyWater) {
return;
}
event.setYield(0); //No drops (additionally to world config)
FightTeam spawn = tnt.remove(event.getEntity().getEntityId());
@@ -1,44 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.fightsystem.listener;
import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentListener;
import de.steamwar.linkage.Linked;
import org.bukkit.entity.EntityType;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityExplodeEvent;
@Linked
public class WindchargeInteractionDisabler implements Listener {
public WindchargeInteractionDisabler() {
new StateDependentListener(!Config.GameModeConfig.Arena.WindchargesInteractWithBlocks, FightState.Running, this);
}
@EventHandler(priority = EventPriority.HIGHEST)
public void handleEntityExplode(EntityExplodeEvent event) {
if (event.getEntityType() != EntityType.WIND_CHARGE) return;
event.blockList().clear();
}
}
@@ -23,14 +23,14 @@ import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentTask;
import de.steamwar.linkage.Linked;
import net.minecraft.world.entity.projectile.windcharge.WindCharge;
import org.bukkit.Location;
import org.bukkit.entity.WindCharge;
@Linked
public class WindchargeStopper {
public WindchargeStopper() {
new StateDependentTask(!Config.GameModeConfig.Arena.WindchargesCanCrossMiddle, FightState.Running, this::run, 1, 1);
new StateDependentTask(true, FightState.Running, this::run, 1, 1);
}
private static final int middleLine = Config.SpecSpawn.getBlockZ();
@@ -39,13 +39,13 @@ public class WindchargeStopper {
private void run() {
Recording.iterateOverEntities(windChargeClass::isInstance, entity -> {
Location nextlocation = entity.getLocation().add(entity.getVelocity());
Location location = entity.getLocation();
Location prevLocation = location.clone().subtract(entity.getVelocity());
boolean passedMiddle = nextlocation.getBlockZ() >= middleLine && location.getBlockZ() <= middleLine ||
nextlocation.getBlockZ() <= middleLine && location.getBlockZ() >= middleLine;
boolean passedMiddle = location.getBlockZ() > middleLine && prevLocation.getBlockZ() > middleLine ||
location.getBlockZ() < middleLine && prevLocation.getBlockZ() < middleLine;
if (passedMiddle) {
if (!passedMiddle) {
entity.remove();
}
});
-8
View File
@@ -1,5 +1,3 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
/*
* This file is a part of the SteamWar software.
*
@@ -36,7 +34,6 @@ dependencies {
compileOnly(libs.paperapi)
compileOnly(project(":SpigotCore"))
implementation(libs.coroutinesCore)
implementation(libs.exposedCore)
implementation(libs.exposedDao)
implementation(libs.exposedJdbc)
@@ -44,8 +41,3 @@ dependencies {
implementation(libs.mysql)
implementation("org.slf4j:slf4j-simple:2.0.17")
}
val compileKotlin: KotlinCompile by tasks
compileKotlin.compilerOptions {
freeCompilerArgs.set(listOf("-XXLanguage:+ContextParameters"))
}
@@ -1,90 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.kotlin.ui
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalForInheritanceCoroutinesApi
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlin.reflect.KProperty
private val uiStateScope = CoroutineScope(SupervisorJob() + Dispatchers.Unconfined)
context(render: StateFlowListener)
fun <T> StateFlow<T>.listen(): StateFlow<T> {
render.track(this) {
drop(1).onEach { render.update() }.launchIn(uiStateScope)
}
return this
}
context(render: StateFlowListener)
fun <T> MutableStateFlow<T>.listen(): MutableStateFlow<T> {
render.track(this) {
drop(1).onEach { render.update() }.launchIn(uiStateScope)
}
return this
}
fun <T> StateFlow<T>.listen(callback: (T) -> Unit): () -> Unit {
callback(value)
val job = drop(1).onEach { callback(it) }.launchIn(uiStateScope)
return { job.cancel() }
}
operator fun <T> StateFlow<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
operator fun <T> MutableStateFlow<T>.setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this.value = value
}
fun <T, R> StateFlow<T>.map(mapper: (T) -> R): StateFlow<R> = MappedStateFlow(this, mapper)
@OptIn(ExperimentalForInheritanceCoroutinesApi::class)
private class MappedStateFlow<T, R>(
private val parent: StateFlow<T>,
private val mapper: (T) -> R,
): StateFlow<R> {
override val replayCache: List<R>
get() = listOf(value)
override val value: R
get() = mapper(parent.value)
override suspend fun collect(collector: FlowCollector<R>): Nothing {
var initialized = false
var previous: Any? = null
parent.collect {
val mapped = mapper(it)
if (!initialized || previous != mapped) {
initialized = true
previous = mapped
collector.emit(mapped)
}
}
}
}
@@ -1,55 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.kotlin.ui
import de.steamwar.kotlin.ui.context.WindowContext
import org.bukkit.entity.Player
import org.bukkit.event.inventory.InventoryType
abstract class UIInventory(val player: Player): StateFlowListener() {
var window: UIWindow? = null
abstract fun view()
fun open() {
if (window == null) {
view()
assert(window != null) { "View method must create a inventory" }
}
window!!.open()
}
fun render() {
window?.onClose()
window = null
open()
}
override fun update() = render()
protected fun inventory(size: Int, title: String, init: WindowContext.() -> Unit) {
window = UIWindow(size, title, player, init)
}
protected fun inventory(type: InventoryType, title: String, init: WindowContext.() -> Unit) {
window = UIWindow(type, title, player, init)
}
}
@@ -1,91 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.kotlin.ui
import de.steamwar.kotlin.KotlinCore
import de.steamwar.kotlin.ui.context.WindowContext
import net.kyori.adventure.text.Component
import org.bukkit.Bukkit
import org.bukkit.entity.Player
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.inventory.ClickType
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.event.inventory.InventoryCloseEvent
import org.bukkit.event.inventory.InventoryType
import org.bukkit.inventory.Inventory
import org.bukkit.inventory.InventoryHolder
import org.bukkit.inventory.InventoryView
class UIWindow(val player: Player, val render: WindowContext.() -> Unit): InventoryHolder {
val onClicks = mutableMapOf<Int, (event: InventoryClickEvent) -> Unit>()
private var updating = false
lateinit var bukkitInv: Inventory
private set
val context by lazy { WindowContext(this) }
constructor(size: Int, title: String, player: Player, render: WindowContext.() -> Unit): this(player, render) {
bukkitInv = KotlinCore.plugin.server.createInventory(this, size * 9, Component.translatable(title))
}
constructor(type: InventoryType, title: String, player: Player, render: WindowContext.() -> Unit): this(player, render) {
assert(type != InventoryType.CHEST) { "Chest inventories should use the constructor with size" }
bukkitInv = KotlinCore.plugin.server.createInventory(this, type, Component.translatable(title))
}
fun open() {
render(context)
player.openInventory(bukkitInv)
}
fun onClose() {
if (updating) return
onClicks.clear()
context.destroy()
}
override fun getInventory() = bukkitInv
companion object : Listener {
init {
Bukkit.getPluginManager().registerEvents(this, KotlinCore.plugin)
}
@EventHandler
fun onInventoryClick(event: InventoryClickEvent) {
val window = event.inventory.holder
if (window is UIWindow) {
event.isCancelled = true
if (window.context.skipDoubleClick && event.click == ClickType.DOUBLE_CLICK) return
window.onClicks[event.slot]?.invoke(event)
}
}
@EventHandler
fun onInventoryClose(event: InventoryCloseEvent) {
val window = event.inventory.holder
if (window is UIWindow) {
window.onClose()
}
}
}
}
@@ -1,66 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.kotlin.ui.components
import de.steamwar.kotlin.ui.StateFlowListener
import de.steamwar.kotlin.ui.RenderMarker
import de.steamwar.kotlin.ui.RenderObject
import de.steamwar.kotlin.ui.context.GroupContext
import de.steamwar.kotlin.ui.context.RenderParent
import org.bukkit.Material
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.inventory.ItemStack
@RenderMarker
class ItemContext(val parent: RenderParent, val renderFunc: ItemContext.() -> Unit): RenderObject, StateFlowListener() {
override fun update() {
val oldX = x
val oldY = y
render()
if (oldX != x || oldY != y) {
parent.resetSlot(oldX, oldY)
}
}
override fun destroy() {
super.destroy()
parent.resetSlot(x, y)
}
override fun render() {
renderFunc(this)
parent.renderItem(x, y, item, onClick)
}
init {
render()
}
var item: ItemStack = ItemStack.of(Material.AIR)
var onClick: (event: InventoryClickEvent) -> Unit = {}
var x: Int = 0
var y: Int = 0
fun onClick(func: (event: InventoryClickEvent) -> Unit) {
onClick = func
}
}
fun GroupContext.item(init: ItemContext.() -> Unit) = children.add(ItemContext(this, init))
@@ -1,65 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.kotlin.ui.context
import de.steamwar.kotlin.ui.StateFlowListener
import de.steamwar.kotlin.ui.RenderMarker
import de.steamwar.kotlin.ui.RenderObject
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.inventory.ItemStack
@RenderMarker
open class GroupContext(val parent: RenderParent?, val init: GroupContext.() -> Unit): StateFlowListener(), RenderObject, RenderParent {
val children = mutableListOf<RenderObject>()
val updatedSlots = mutableSetOf<Pair<Int, Int>>()
override fun update() = render()
override fun destroy() {
children.forEach { it.destroy() }
super.destroy()
}
init {
init(this)
}
override fun renderItem(x: Int, y: Int, item: ItemStack, onClick: (event: InventoryClickEvent) -> Unit) {
updatedSlots.add(x to y)
parent?.renderItem(x, y, item, onClick)
}
override fun resetSlot(x: Int, y: Int) {
parent?.resetSlot(x, y)
}
fun group(init: GroupContext.() -> Unit) {
children.add(GroupContext(this, init))
}
override fun render() {
children.forEach { it.destroy() }
children.clear()
val oldUpdatedSlots = updatedSlots.toList()
updatedSlots.clear()
init(this)
(oldUpdatedSlots - updatedSlots).forEach { resetSlot(it.first, it.second) }
}
}
@@ -1,29 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.kotlin.ui.context
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.inventory.ItemStack
interface RenderParent {
fun renderItem(x: Int, y: Int, item: ItemStack, onClick: (event: InventoryClickEvent) -> Unit)
fun resetSlot(x: Int, y: Int)
}
@@ -1,54 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.kotlin.ui.context
import de.steamwar.kotlin.ui.RenderMarker
import de.steamwar.kotlin.ui.UIWindow
import org.bukkit.Material
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.event.inventory.InventoryType
import org.bukkit.inventory.ItemStack
@RenderMarker
class WindowContext(val window: UIWindow): GroupContext(null, {}) {
override fun renderItem(x: Int, y: Int, item: ItemStack, onClick: (event: InventoryClickEvent) -> Unit) {
val slot = calculateSlot(x, y)
window.bukkitInv.setItem(slot, item)
window.onClicks[slot] = onClick
}
override fun resetSlot(x: Int, y: Int) {
val slot = calculateSlot(x, y)
window.bukkitInv.setItem(slot, ItemStack.of(Material.AIR))
window.onClicks.remove(slot)
}
private fun calculateSlot(x: Int, y: Int) = when (window.inventory.type) {
InventoryType.DROPPER, InventoryType.DISPENSER -> x + y * 3
InventoryType.HOPPER -> x
else -> x + y * 9
}
fun outsideClick(click: (event: InventoryClickEvent) -> Unit) {
window.onClicks[-999] = click
}
var skipDoubleClick = true
}
@@ -1,50 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.kotlin.util
import net.kyori.adventure.text.Component
import org.bukkit.Bukkit
import org.bukkit.Material
import org.bukkit.enchantments.Enchantment
import org.bukkit.inventory.ItemFlag
import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.meta.SkullMeta
import org.bukkit.inventory.meta.components.CustomModelDataComponent
fun ItemStack.count(count: Int) = apply { amount = count }
fun ItemStack.name(name: Component) = apply { itemMeta = itemMeta.also { it.displayName(name) } }
fun ItemStack.lored(lore: List<Component>) = apply { itemMeta = itemMeta.also { it.lore(lore) } }
fun String.asMaterial(): Material = Material.matchMaterial(this) ?: Material.BARRIER
fun skull(owner: String): ItemStack = ItemStack(Material.PLAYER_HEAD)
.also {
it.editMeta(SkullMeta::class.java) {
it.playerProfile = Bukkit.getOfflinePlayer(owner.trimStart('.')).playerProfile.also { pp -> pp.complete() }
}
}
fun ItemStack.hideAttributes() = apply { itemMeta = itemMeta.also { it.addItemFlags(*ItemFlag.entries.toTypedArray()) } }
fun ItemStack.enchanted() = apply { itemMeta = itemMeta.also { it.addEnchant(Enchantment.UNBREAKING, 10, true) } }
fun ItemStack.customModelData(model: CustomModelDataComponent) = apply { itemMeta = itemMeta.also { it.setCustomModelDataComponent(model) } }
-1
View File
@@ -42,7 +42,6 @@ tasks.register<DevServer>("DevLobby") {
group = "run"
description = "Run a Dev Lobby"
dependsOn(":SpigotCore:shadowJar")
dependsOn(":KotlinCore:shadowJar")
dependsOn(":LobbySystem:jar")
template = "Lobby21"
worldName = "Lobby"
@@ -20,6 +20,7 @@
package de.steamwar.lobby.boatrace;
import de.steamwar.entity.REntity;
import de.steamwar.entity.REntityAction;
import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RInteraction;
import de.steamwar.lobby.LobbySystem;
@@ -35,7 +36,6 @@ import org.bukkit.entity.Boat;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.entity.boat.*;
import org.bukkit.event.EventHandler;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
@@ -44,7 +44,6 @@ import org.bukkit.event.vehicle.VehicleMoveEvent;
import org.bukkit.scheduler.BukkitTask;
import java.util.EventListener;
import java.util.Random;
import static de.steamwar.lobby.util.LeaderboardManager.renderTime;
@@ -62,11 +61,11 @@ public class BoatRace implements EventListener, Listener {
static {
boatNpcServer = new REntityServer();
new REntity(boatNpcServer, EntityType.VILLAGER, BoatRacePositions.NPC);
RInteraction interaction = new RInteraction(boatNpcServer, BoatRacePositions.NPC.clone());
RInteraction interaction = new RInteraction(boatNpcServer, BoatRacePositions.NPC.clone().subtract(0.5, 0, 0.5));
interaction.setInteractionHeight(1.95f);
interaction.setCallback((player, entity, action) -> {
Bukkit.getWorlds().get(0).getEntities().stream().filter(e -> e.getType() == EntityType.END_CRYSTAL).forEach(Entity::remove);
if (!oneNotStarted) {
if (action == REntityAction.INTERACT && !oneNotStarted) {
oneNotStarted = true;
new BoatRace(player);
}
@@ -154,24 +153,10 @@ public class BoatRace implements EventListener, Listener {
oneNotStarted = false;
}
private final Class<? extends Boat>[] boatClasses = new Class[]{
AcaciaBoat.class,
BambooRaft.class,
BirchBoat.class,
CherryBoat.class,
DarkOakBoat.class,
JungleBoat.class,
MangroveBoat.class,
OakBoat.class,
PaleOakBoat.class,
SpruceBoat.class,
};
private final Random random = new Random();
public BoatRace(Player player) {
this.player = player;
boat = Bukkit.getWorlds().get(0).spawn(BoatRacePositions.START, boatClasses[random.nextInt(boatClasses.length)]);
boat = Bukkit.getWorlds().get(0).spawn(BoatRacePositions.START, Boat.class);
// boat.setBoatType(Boat.Type.values()[new Random().nextInt(Boat.Type.values().length)]);
boat.addPassenger(player);
bossBar = Bukkit.createBossBar("", BarColor.BLUE, BarStyle.SOLID);
task = Bukkit.getScheduler().runTaskTimer(LobbySystem.getInstance(), () -> {
@@ -42,10 +42,7 @@ import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.Month;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
public class CustomMap implements Listener {
@@ -59,7 +56,7 @@ public class CustomMap implements Listener {
new Vector(2346, 45, 1297), new Vector(2345, 45, 1297), new Vector(2344, 45, 1297), new Vector(2343, 45, 1297), new Vector(2342, 45, 1297), new Vector(2341, 45, 1297), new Vector(2340, 45, 1297)
);
private static final CustomMap RIGHT = new CustomMap(new File(System.getProperty("user.home") + "/lobbyBanner/right/"),
private static final CustomMap RIGHT = new CustomMap(new File(System.getProperty("user.home") + "/lobbyBanner/right.png"),
new Vector(2330, 48, 1297), new Vector(2329, 48, 1297), new Vector(2328, 48, 1297), new Vector(2327, 48, 1297), new Vector(2326, 48, 1297), new Vector(2325, 48, 1297), new Vector(2324, 48, 1297),
new Vector(2330, 47, 1297), new Vector(2329, 47, 1297), new Vector(2328, 47, 1297), new Vector(2327, 47, 1297), new Vector(2326, 47, 1297), new Vector(2325, 47, 1297), new Vector(2324, 47, 1297),
new Vector(2330, 46, 1297), new Vector(2329, 46, 1297), new Vector(2328, 46, 1297), new Vector(2327, 46, 1297), new Vector(2326, 46, 1297), new Vector(2325, 46, 1297), new Vector(2324, 46, 1297),
@@ -69,49 +66,30 @@ public class CustomMap implements Listener {
private File mapFile;
private Map<Vector, Integer> itemFrameIndex = new HashMap<>();
private ItemFrame[] itemFrames;
private boolean update = true;
private long lastModified = Long.MAX_VALUE;
public CustomMap(File mapFileOrDirectory, Vector... itemFrames) {
this.mapFile = mapFileOrDirectory;
public CustomMap(File mapFile, Vector... itemFrames) {
this.mapFile = mapFile;
this.itemFrames = new ItemFrame[itemFrames.length];
for (int i = 0; i < itemFrames.length; i++) {
itemFrameIndex.put(itemFrames[i], i);
}
if (mapFileOrDirectory.isDirectory()) {
AtomicReference<Month> lastMonth = new AtomicReference<>(LocalDateTime.now().getMonth());
Bukkit.getScheduler().runTaskTimer(LobbySystem.getInstance(), () -> {
Month current = LocalDateTime.now().getMonth();
if (!current.equals(lastMonth.get()) || update) {
lastMonth.set(current);
update = false;
this.mapFile = new File(mapFileOrDirectory, current.getValue() + ".png");
update();
}
}, 200L, 1200L);
} else {
AtomicReference<Long> lastModified = new AtomicReference<>(Long.MAX_VALUE);
Bukkit.getScheduler().runTaskTimer(LobbySystem.getInstance(), () -> {
long modified = mapFileOrDirectory.lastModified();
if (modified > lastModified.get() || update) {
lastModified.set(modified);
update = false;
update();
}
}, 200L, 200L);
}
Bukkit.getPluginManager().registerEvents(this, LobbySystem.getInstance());
}
private void update() {
System.out.println("Updating Banner: " + mapFile.getName());
Bukkit.getScheduler().runTaskAsynchronously(LobbySystem.getInstance(), () -> {
try {
run();
} catch (IOException e) {
// Ignore
Bukkit.getScheduler().runTaskTimer(LobbySystem.getInstance(), () -> {
long modified = mapFile.lastModified();
if (modified > lastModified) {
lastModified = modified;
System.out.println("Updating Banner: " + mapFile.getName());
Bukkit.getScheduler().runTaskAsynchronously(LobbySystem.getInstance(), () -> {
try {
run();
} catch (IOException e) {
// Ignore
}
});
}
});
}, 200L, 200L);
Bukkit.getPluginManager().registerEvents(this, LobbySystem.getInstance());
}
@EventHandler
@@ -123,7 +101,7 @@ public class CustomMap implements Listener {
if (itemFrameIndex.containsKey(vector)) {
if (itemFrames[itemFrameIndex.get(vector)] != null) continue;
itemFrames[itemFrameIndex.get(vector)] = itemFrame;
update = true;
lastModified = 0;
ItemStack itemStack = new ItemStack(Material.FILLED_MAP, 1);
MapMeta mapMeta = (MapMeta) itemStack.getItemMeta();
+1 -11
View File
@@ -29,15 +29,5 @@ dependencies {
compileOnly(libs.paperapi)
compileOnly(libs.nms)
compileOnly(libs.fawe)
}
tasks.register<FightServer>("MissileWars21") {
group = "run"
description = "Run a 1.21 Dev MissileWars"
dependsOn(":SpigotCore:shadowJar")
dependsOn(":MissileWars:jar")
template = "MissileWars"
worldName = "Great_Wall"
jar = "/jars/paper-1.21.6.jar"
compileOnly(libs.worldedit)
}
@@ -30,8 +30,6 @@ import com.sk89q.worldedit.function.operation.Operations;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.math.transform.AffineTransform;
import com.sk89q.worldedit.session.ClipboardHolder;
import com.sk89q.worldedit.util.SideEffect;
import com.sk89q.worldedit.util.SideEffectSet;
import com.sk89q.worldedit.world.World;
import com.sk89q.worldedit.world.block.BlockTypes;
import de.steamwar.misslewars.MissileWars;
@@ -111,17 +109,11 @@ public class Missile extends SpecialItem {
v = aT.apply(v.toVector3()).toBlockPoint();
v = v.add(location.getBlockX(), location.getBlockY(), location.getBlockZ());
EditSession e = WorldEdit.getInstance().getEditSessionFactory()
.getEditSession(world, -1);
e.setSideEffectApplier(SideEffectSet.defaults()
.with(SideEffect.NEIGHBORS, SideEffect.State.ON)
.with(SideEffect.LIGHTING, SideEffect.State.ON)
.with(SideEffect.UPDATE, SideEffect.State.ON));
EditSession e = WorldEdit.getInstance().getEditSessionFactory().getEditSession(world, -1);
ClipboardHolder ch = new ClipboardHolder(clipboard);
ch.setTransform(aT);
Operations.completeBlindly(ch.createPaste(e).to(v).ignoreAirBlocks(true).build());
e.flushSession();
return true;
}
@@ -37,27 +37,17 @@ public class AutoChecker {
public static final AutoChecker impl = new AutoChecker();
public AutoCheckerResult check(Clipboard clipboard, GameModeConfig<Material, String> type) {
return AutoCheckerResult.builder()
.type(type)
.height(clipboard.getDimensions().y())
.width(clipboard.getDimensions().x())
.depth(clipboard.getDimensions().z())
.blockScanResult(scan(clipboard, type))
.entities(
clipboard.getEntities().stream()
.map(Entity::getLocation)
.map(blockVector3 -> new BlockPos(blockVector3.getBlockX(), blockVector3.getBlockY(), blockVector3.getBlockZ()))
.collect(Collectors.toList()))
return AutoCheckerResult.builder().type(type).height(clipboard.getDimensions().x()).width(clipboard.getDimensions().x())
.depth(clipboard.getDimensions().z()).blockScanResult(scan(clipboard, type))
.entities(clipboard.getEntities().stream().map(Entity::getLocation)
.map(blockVector3 -> new BlockPos(blockVector3.getBlockX(), blockVector3.getBlockY(), blockVector3.getBlockZ()))
.collect(Collectors.toList()))
.build();
}
public AutoCheckerResult sizeCheck(Clipboard clipboard, GameModeConfig<Material, String> type) {
return AutoCheckerResult.builder()
.type(type)
.height(clipboard.getDimensions().y())
.width(clipboard.getDimensions().x())
.depth(clipboard.getDimensions().z())
.build();
return AutoCheckerResult.builder().type(type).height(clipboard.getDimensions().y()).width(clipboard.getDimensions().x())
.depth(clipboard.getDimensions().z()).build();
}
public AutoChecker.BlockScanResult scan(Clipboard clipboard, GameModeConfig<Material, String> type) {
@@ -73,19 +63,14 @@ public class AutoChecker {
continue;
}
BlockPos pos = new BlockPos(x, y, z);
result.getBlockCounts().merge(material, 1, Integer::sum);
if (AutoCheckerItems.impl.getInventoryMaterials().contains(material)) {
checkInventory(result, block, material, pos, type);
if (result.getDispenserItems().getOrDefault(pos, 0) > 0) {
result.getBlockCounts().merge(material, 1, Integer::sum);
}
} else {
result.getBlockCounts().merge(material, 1, Integer::sum);
checkInventory(result, block, material, new BlockPos(x, y, z), type);
}
if (x == min.x() || x == max.x() || y == max.y() || z == min.z() || z == max.z()) {
result.getDesignBlocks().computeIfAbsent(material, m -> new ArrayList<>()).add(pos);
result.getDesignBlocks().computeIfAbsent(material, m -> new ArrayList<>()).add(new BlockPos(x, y, z));
}
}
}
@@ -151,7 +151,7 @@ public class TechHider {
ClientboundSetHeldSlotPacket.class, // 7.1.104 Set Held Item (Player owning the channel)
ClientboundSetObjectivePacket.class, // 7.1.105 Update Objectives
ClientboundSetPlayerInventoryPacket.class, // 7.1.107 Set Player Inventory Slot (Player owning the channel)
ClientboundSetPlayerTeamPacket.class, // 7.1.108 Update Teams
// ClientboundSetPlayerTeamPacket.class, // 7.1.108 Update Teams
ClientboundSetScorePacket.class, // 7.1.109 Update Score
ClientboundSetSimulationDistancePacket.class, // 7.1.110 Set Simulation Distance
ClientboundSetSubtitleTextPacket.class, // 7.1.111 Set Subtitle Text
@@ -80,7 +80,7 @@ public class Subserver {
static void shutdown() {
while (!serverList.isEmpty()) {
Subserver server = serverList.get(0);
server.sleep();
server.stop();
}
}
@@ -138,7 +138,7 @@ public class Subserver {
writer.println(command);
}
public void sleep() {
public void stop() {
try {
long pid = process.pid();
if (checkpoint) {
@@ -152,32 +152,9 @@ public class Subserver {
try {
if (!process.waitFor(1, TimeUnit.MINUTES)) {
forceStop();
logger.log(Level.SEVERE, () -> serverName + " did not stop correctly, forcibly stopping!");
process.destroyForcibly();
}
} catch (InterruptedException e) {
logger.log(Level.SEVERE, "Subserver stop interrupted!", e);
Thread.currentThread().interrupt();
}
}
public void stop() {
try {
process.destroy();
if (!process.waitFor(1, TimeUnit.MINUTES)) {
forceStop();
} else if (thread.isAlive()) {
thread.join();
}
} catch (InterruptedException e) {
logger.log(Level.SEVERE, "Subserver stop interrupted!", e);
Thread.currentThread().interrupt();
}
}
public void forceStop() {
try {
logger.log(Level.SEVERE, () -> serverName + " did not stop correctly, forcibly stopping!");
process.destroyForcibly();
if (thread.isAlive()) thread.join();
} catch (InterruptedException e) {
@@ -219,7 +196,7 @@ public class Subserver {
protected void register() {
if (Persistent.getInstance().getProxy().getServer(serverName).isPresent()) {
SecurityException e = new SecurityException("Server already registered: " + serverName);
sleep();
stop();
failureCallback.accept(e);
throw e;
}
@@ -20,7 +20,6 @@
package de.steamwar.command;
import com.velocitypowered.api.command.SimpleCommand;
import com.velocitypowered.api.command.SimpleCommand.Invocation;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.Message;
import de.steamwar.sql.UserPerm;
@@ -92,15 +91,11 @@ public class SWCommand extends AbstractSWCommand<Chatter> {
@Override
public boolean hasPermission(Invocation invocation) {
return SWCommand.this.hasPermission(invocation);
return permission == null || Chatter.of(invocation.source()).user().perms().contains(permission);
}
};
}
protected boolean hasPermission(Invocation invocation) {
return permission == null || Chatter.of(invocation.source()).user().perms().contains(permission);
}
@Override
public void unregister() {
if (command == null) return;
@@ -271,8 +271,7 @@ CHALLENGE_ACCEPT_HOVER = §aAccept challenge
#EventCommand
EVENT_TIME_FORMAT = HH:mm
EVENT_DATE_FORMAT = dd.MM.
EVENT_TEAM_USAGE = §8/§7event team §8[§eTeam§8] - §7To teleport to a fight
EVENT_SHOW_FIGHTS_USAGE = §8/§7event showfights §8[§eEvent§8] - §7To show the fights of that Event
EVENT_USAGE = §8/§7event §8[§eTeam§8] - §7To teleport to a fight
EVENT_NO_TEAM = §cThis team does not exist
EVENT_NO_FIGHT_TEAM = §cThis team has no current fight
EVENT_NO_CURRENT = §cThere is no event taking place currently
@@ -283,13 +282,9 @@ EVENT_COMING_SCHEM_DEADLINE = §7 Submission deadline§8: §7{0}
EVENT_COMING_TEAMS = §7 With§8: {0}
EVENT_COMING_TEAM = §{0}{1}
EVENT_CURRENT_EVENT = §e§l{0}
EVENT_CURRENT_FIGHT_1 = §7{0} §{1}{2}
EVENT_CURRENT_FIGHT_VS = §8 vs
EVENT_CURRENT_FIGHT_2 = §{1}{2}
EVENT_CURRENT_FIGHT = §7{0} §{1}{2}§8 vs §{3}{4}
EVENT_CURRENT_FIGHT_WIN = §8: §7Victory §{0}{1}
EVENT_CURRENT_FIGHT_DRAW = §8: §7Draw
EVENT_TEAM_TABLE = §{0}{1}{2} §8 with §e{3} §7points
EVENT_SHOW_TABLE_USAGE = §8/§7event showtable §8[§eEvent§8] - §7To show the table of that Event
#EventRescheduleCommand
EVENTRESCHEDULE_USAGE = §8/§7eventreschedule §8[§eTeam1§8] [§eTeam2§8]
@@ -756,9 +751,3 @@ DC_SCHEMUPLOAD_IGNORED = Skipping `{0}`, not a schematic file.
DC_SCHEMUPLOAD_INVCHAR = `{0}` has invalid characters in its name.
DC_SCHEMUPLOAD_SUCCESS = `{0}` was uploaded successfully.
DC_SCHEMUPLOAD_ERROR = An error has occured during the upload of `{0}`. For more information ask a Developer.
UTIL_LIST_BACK_ARROW = §8««
UTIL_LIST_BACK_ARROW_HOVER = §ePrevious page
UTIL_LIST_PAGE = §e Page §7({0}/{1})
UTIL_LIST_NEXT = §8 »»
UTIL_LIST_NEXT_HOVER = §eNext page
@@ -253,8 +253,7 @@ CHALLENGE_ACCEPT_HOVER = §aHerausforderung annehmen
#EventCommand
EVENT_TIME_FORMAT = HH:mm
EVENT_DATE_FORMAT = dd.MM.
EVENT_TEAM_USAGE=§8/§7event team §8[§eTeam§8] - §7Um dich zum Kampf zu teleportieren
EVENT_SHOW_FIGHTS_USAGE = §8/§7event showfights §8[§eEvent§8] - §7Um die fights des Events zu zeigen
EVENT_USAGE = §8/§7event §8[§eTeam§8] - §7Um dich zum Kampf zu teleportieren
EVENT_NO_TEAM = §cDieses Team gibt es nicht
EVENT_NO_FIGHT_TEAM = §cDas Team kämpft derzeit nicht
EVENT_NO_CURRENT = §cDerzeit findet kein Event statt
@@ -265,10 +264,9 @@ EVENT_COMING_SCHEM_DEADLINE = §7 Einsendeschluss§8: §7{0}
EVENT_COMING_TEAMS = §7 Mit§8: {0}
EVENT_COMING_TEAM = §{0}{1}
EVENT_CURRENT_EVENT = §e§l{0}
EVENT_CURRENT_FIGHT = §7{0} §{1}{2}§8 vs §{3}{4}
EVENT_CURRENT_FIGHT_WIN = §8: §7Sieg §{0}{1}
EVENT_CURRENT_FIGHT_DRAW = §8: §7Unentschieden
EVENT_TEAM_TABLE = §{0}{1}{2} §8 mit §e{3} §7Punkten
EVENT_SHOW_TABLE_USAGE = §8/§7event showtable §8[§eEvent§8] - §7Um die Tabelle des Events zu zeigen.
#EventRescheduleCommand
EVENTRESCHEDULE_USAGE = §8/§7eventreschedule §8[§eTeam1§8] [§eTeam2§8]
@@ -55,7 +55,7 @@ public class ServerStarter {
public static final String WORLDS_BASE_PATH = WORLDS_FOLDER + "/userworlds";
public static final String BUILDER_BASE_PATH = WORLDS_FOLDER + "/builder";
public static final String WORLDS_STORAGE_BASE_PATH = "/mnt/storage/worlds/userworlds";
private static final String WORLDS_STORAGE_BASE_PATH = "/mnt/storage/worlds/userworlds";
private File directory = null;
private String worldDir = null;
@@ -201,11 +201,8 @@ public class ServerStarter {
private void tempWorld(String template) {
worldDir = TEMP_WORLD_PATH;
worldSetup = () -> {
mkdirWorldDir(node, worldDir);
copyWorld(node, template, new File(worldDir, worldName).getPath());
};
worldCleanup = () -> SubserverSystem.deleteFolder(node, new File(worldDir, worldName).getPath());
worldSetup = () -> copyWorld(node, template, worldDir + worldName);
worldCleanup = () -> SubserverSystem.deleteFolder(node, worldDir + worldName);
}
private void buildWithTemp(Player owner) {
@@ -216,7 +213,7 @@ public class ServerStarter {
if (startingBau(owner)) return false;
Bauserver subserver = Bauserver.get(owner.getUniqueId());
if (subserver != null && subserver.isStarted()) subserver.sleep();
if (subserver != null && subserver.isStarted()) subserver.stop();
return !startingBau(owner);
};
@@ -357,10 +354,6 @@ public class ServerStarter {
return serverName.replace(' ', '_').replace("[", "").replace("]", "").replace(".", "");
}
public static void mkdirWorldDir(Node node, String targetDir) {
node.execute("mkdir", "p", targetDir);
}
public static void copyWorld(Node node, String template, String target) {
node.execute("cp", "-r", template, target);
}
@@ -403,4 +396,4 @@ public class ServerStarter {
}
}
}
}
@@ -1,340 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.advancements;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
import de.steamwar.messages.Chatter;
import de.steamwar.sql.SteamwarUser;
import io.netty.buffer.ByteBuf;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import net.kyori.adventure.text.Component;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
@RequiredArgsConstructor
public class Advancement {
protected static final Map<SteamwarUser, Map<Advancement.Value.Key, Advancement.Value>> values = new HashMap<>();
{
Advancements.all.add(this);
}
protected final Map<SteamwarUser, Advancement.Data> data = new HashMap<>();
protected final Map<SteamwarUser, Advancement.Value> value = new HashMap<>();
public Advancement.Data get(SteamwarUser user) {
return get(user, Data::new);
}
public Advancement.Data get(SteamwarUser user, BiFunction<Advancement, SteamwarUser, Data> function) {
if (data.containsKey(user)) return data.get(user);
return function.apply(this, user);
}
private final String identifier;
private final Optional<Advancement> parent;
private final Display display;
private final HidePolicy hidePolicy;
private final int total;
private final Function<SteamwarUser, Integer> progressCalculator;
@Override
public String toString() {
StringBuilder st = new StringBuilder();
st.append("Advancement(");
parent.ifPresent(advancement -> st.append(advancement.identifier).append("<-"));
st.append(identifier);
st.append(", total=").append(total);
st.append(")");
return st.toString();
}
@RequiredArgsConstructor
@AllArgsConstructor
public static class Display {
private final Component title;
private final Component description;
private final String item;
private final FrameType frameType;
private Optional<String> background = Optional.empty();
private final float xCoord;
private final float yCoord;
public enum FrameType {
TASK,
CHALLENGE,
GOAL
}
}
public enum HidePolicy {
NEVER {
@Override
public boolean hidden(Data data) {
return false;
}
},
NO_PROGRESS {
@Override
public boolean hidden(Data data) {
return data.progress == 0;
}
},
PREVIOUS_UNFINISHED {
@Override
public boolean hidden(Data data) {
if (data.advancement.parent.isPresent()) {
Advancement parent = data.advancement.parent.get();
Advancement.Data parentData = parent.get(data.user);
return parentData.progress != parentData.advancement.total;
} else {
return false;
}
}
},
WITH_PREVIOUS {
@Override
public boolean hidden(Data data) {
if (data.advancement.parent.isPresent()) {
Advancement parent = data.advancement.parent.get();
Advancement.Data parentData = parent.get(data.user);
return parentData.hidden;
} else {
return false;
}
}
},
;
public abstract boolean hidden(Data data);
}
public static class Value<T extends Number> {
@AllArgsConstructor
public static class Key<T extends Number> {
public static final List<Key> keys = new ArrayList<>();
{
keys.add(this);
}
private final Function<SteamwarUser, T> valueFunction;
private Advancement.Value get(SteamwarUser user) {
Key self = this;
return values.computeIfAbsent(user, __ -> new HashMap<>()).computeIfAbsent(self, __ -> {
Value data = new Advancement.Value();
data.update(user, self);
return data;
});
}
public Function<SteamwarUser, Integer> max(int neededValue) {
return user -> {
double value = get(user).value.doubleValue();
if (value > neededValue) return Math.min(neededValue, 100);
return (int) (value / Math.max(neededValue / 100.0, 1));
};
}
public Function<SteamwarUser, Integer> reached(int neededValue) {
return user -> {
double value = get(user).value.doubleValue();
return value >= neededValue ? 1 : 0;
};
}
}
@Getter
private T value;
public void update(SteamwarUser user, Key<T> key) {
this.value = key.valueFunction.apply(user);
}
}
@ToString
public static class Data {
private final Advancement advancement;
private final SteamwarUser user;
private int progress;
private boolean showToast = true;
private boolean hidden = false;
public Data(Advancement advancement, SteamwarUser user) {
this.advancement = advancement;
advancement.data.put(user, this);
this.user = user;
this.progress = advancement.progressCalculator.apply(user);
checkHidden();
checkFinished();
new Packet(this, showToast).send();
}
public Data(Advancement advancement, SteamwarUser user, int progress) {
this.advancement = advancement;
advancement.data.put(user, this);
this.user = user;
this.progress = progress;
checkHidden();
checkFinished();
new Packet(this, showToast).send();
}
public void update() {
this.progress = advancement.progressCalculator.apply(user);
checkHidden();
new Packet(this, showToast).send();
// Update Advancements that have this as parent
Advancements.getAll()
.stream()
.filter(advancement -> advancement.parent.filter(value -> value == this.advancement).isPresent())
.map(advancement -> advancement.get(user))
.forEach(Advancement.Data::update);
checkFinished();
}
private void checkHidden() {
hidden = advancement.hidePolicy.hidden(this);
}
private void checkFinished() {
if (progress == advancement.total) {
showToast = false;
}
}
private void encodeAdvancement(ByteBuf byteBuf, ProtocolVersion protocolVersion, boolean showToast) {
ProtocolUtils.writeString(byteBuf, advancement.identifier);
if (advancement.parent.isPresent()) {
byteBuf.writeBoolean(true);
ProtocolUtils.writeString(byteBuf, advancement.parent.get().identifier);
} else {
byteBuf.writeBoolean(false);
}
{ // Display
byteBuf.writeBoolean(true);
new ComponentHolder(protocolVersion, advancement.display.title).write(byteBuf);
new ComponentHolder(protocolVersion, advancement.display.description).write(byteBuf);
{ // Slot
ProtocolUtils.writeVarInt(byteBuf, 1);
int itemId = Items.values
.get(protocolVersion.name().replace("MINECRAFT_", "V_"))
.getAsJsonObject()
.get(advancement.display.item)
.getAsInt();
ProtocolUtils.writeVarInt(byteBuf, itemId);
ProtocolUtils.writeVarInt(byteBuf, 0);
ProtocolUtils.writeVarInt(byteBuf, 0);
}
ProtocolUtils.writeVarInt(byteBuf, advancement.display.frameType.ordinal());
if (advancement.display.background.isPresent()) {
byteBuf.writeInt(0x01 | (showToast ? 0x02 : 0x00) | (hidden ? 0x04 : 0x00));
ProtocolUtils.writeString(byteBuf, advancement.display.background.get());
} else {
byteBuf.writeInt((showToast ? 0x02 : 0x00) | (hidden ? 0x04 : 0x00));
}
byteBuf.writeFloat(advancement.display.xCoord);
byteBuf.writeFloat(advancement.display.yCoord);
}
ProtocolUtils.writeVarInt(byteBuf, advancement.total);
for (int i = 0; i < advancement.total; i++) {
ProtocolUtils.writeVarInt(byteBuf, 1);
ProtocolUtils.writeString(byteBuf, advancement.identifier + "_" + i);
}
byteBuf.writeBoolean(false); // No Telemetry
}
private void encodeProgress(ByteBuf byteBuf) {
ProtocolUtils.writeString(byteBuf, this.advancement.identifier);
ProtocolUtils.writeVarInt(byteBuf, advancement.total);
for (int i = 0; i < advancement.total; i++) {
ProtocolUtils.writeString(byteBuf, advancement.identifier + "_" + i);
if (i == advancement.total - 1 && advancement.total == progress) {
byteBuf.writeBoolean(true);
byteBuf.writeLong(new Date().getTime());
} else if (i < progress) {
byteBuf.writeBoolean(true);
byteBuf.writeLong(0);
} else {
byteBuf.writeBoolean(false);
}
}
}
}
protected record Packet(Data data, boolean showToast) implements MinecraftPacket {
public void send() {
Player player = Chatter.of(data.user).getPlayer();
((ConnectedPlayer) player).getConnection().write(this);
}
@Override
public void decode(ByteBuf byteBuf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
throw new UnsupportedOperationException();
}
@Override
public void encode(ByteBuf byteBuf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
byteBuf.writeBoolean(false); // Clear
if (!data.hidden) {
ProtocolUtils.writeVarInt(byteBuf, 1);
data.encodeAdvancement(byteBuf, protocolVersion, showToast);
ProtocolUtils.writeVarInt(byteBuf, 0); // No Advancements to remove
ProtocolUtils.writeVarInt(byteBuf, 1);
data.encodeProgress(byteBuf);
} else {
ProtocolUtils.writeVarInt(byteBuf, 0); // No Advancements to update
ProtocolUtils.writeVarInt(byteBuf, 1);
ProtocolUtils.writeString(byteBuf, data.advancement.identifier);
ProtocolUtils.writeVarInt(byteBuf, 0); // No Advancements Progress to update
}
byteBuf.writeBoolean(true); // Show Advancements
}
@Override
public boolean handle(MinecraftSessionHandler minecraftSessionHandler) {
return false;
}
}
}
@@ -1,315 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.advancements;
import de.steamwar.messages.Chatter;
import de.steamwar.persistent.Storage;
import de.steamwar.sql.CheckedSchematic;
import de.steamwar.sql.EventFight;
import de.steamwar.sql.FightPlayer;
import lombok.Getter;
import lombok.experimental.UtilityClass;
import net.kyori.adventure.text.Component;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@UtilityClass
public class Advancements {
@Getter
static final List<Advancement> all = new ArrayList<>();
@Getter
private static final List<Advancement> playtime = new ArrayList<>();
public static final Advancement.Value.Key<Double> PLAY_TIME_KEY = new Advancement.Value.Key<>(user -> {
double playtime = user.getOnlinetime();
playtime += Instant.now().getEpochSecond() - Storage.sessions.get(Chatter.of(user).getPlayer()).toInstant().getEpochSecond();
playtime /= 60d * 60d;
return playtime;
});
public static final Advancement.Value.Key<Long> FIGHT_COUNT = new Advancement.Value.Key<>(user -> {
return FightPlayer.countFights(user.getId());
});
public static final Advancement.Value.Key<Integer> FIGHT_COUNT_WAR_GEAR = new Advancement.Value.Key<>(user -> {
return FightPlayer.countFights(user.getId(), "WarGear");
});
public static final Advancement.Value.Key<Integer> FIGHT_COUNT_MINI_WAR_GEAR = new Advancement.Value.Key<>(user -> {
return FightPlayer.countFights(user.getId(), "MiniWarGear");
});
public static final Advancement.Value.Key<Integer> FIGHT_COUNT_WAR_SHIP = new Advancement.Value.Key<>(user -> {
return FightPlayer.countFights(user.getId(), "WarShip");
});
public static final Advancement.Value.Key<Long> EVENT_FIGHT_COUNT = new Advancement.Value.Key<>(user -> {
return EventFight.countEventFights(user);
});
public static final Advancement.Value.Key<Integer> EVENT_FIGHT_FIRST_PLACE_COUNT = new Advancement.Value.Key<>(user -> {
return EventFight.countPlacement(user, 1);
});
public static final Advancement.Value.Key<Integer> EVENT_FIGHT_SECOND_PLACE_COUNT = new Advancement.Value.Key<>(user -> {
return EventFight.countPlacement(user, 2);
});
public static final Advancement.Value.Key<Integer> EVENT_FIGHT_THIRDPLACE_COUNT = new Advancement.Value.Key<>(user -> {
return EventFight.countPlacement(user, 3);
});
public static final Advancement.Value.Key<Long> CHECKED_SCHEMATIC_COUNT = new Advancement.Value.Key<>(user -> {
return CheckedSchematic.countChecked(user);
});
public static final Advancement.Value.Key<Long> ACCEPTED_SCHEMATIC_COUNT = new Advancement.Value.Key<>(user -> {
return CheckedSchematic.countAccepted(user);
});
public static final Advancement.Value.Key<Long> ACCEPTED_SCHEMATIC_COUNT_WAR_GEAR = new Advancement.Value.Key<>(user -> {
return CheckedSchematic.countAccepted(user, "WarGear");
});
public static final Advancement.Value.Key<Long> ACCEPTED_SCHEMATIC_COUNT_MINI_WAR_GEAR = new Advancement.Value.Key<>(user -> {
return CheckedSchematic.countAccepted(user, "MiniWarGear");
});
public static final Advancement.Value.Key<Long> ACCEPTED_SCHEMATIC_COUNT_WAR_SHIP = new Advancement.Value.Key<>(user -> {
return CheckedSchematic.countAccepted(user, "WarShip");
});
public static final Advancement ROOT = new Advancement(
"steamwar:advancements/root",
Optional.empty(),
new Advancement.Display(
Component.text("SteamWar"),
Component.text("Join SteamWar for the first time!"),
"cactus_flower",
Advancement.Display.FrameType.CHALLENGE,
Optional.of("minecraft:gui/advancements/backgrounds/adventure"),
0f,
3f
),
Advancement.HidePolicy.NEVER,
1,
user -> 1
);
static {
Advancement previous = ROOT;
int[] playTimes = new int[]{1, 10, 100, 500, 1000, 2500, 5000, 7500, 10000, 15000, 20000};
for (int i = 0; i < playTimes.length; i++) {
int neededPlayTime = playTimes[i];
previous = new Advancement(
"steamwar:advancements/playtime_" + neededPlayTime + "_hour",
Optional.of(previous),
new Advancement.Display(
Component.text("Play " + neededPlayTime + " Hour" + (neededPlayTime > 1 ? "s" : "")),
Component.text("Play " + neededPlayTime + " hour" + (neededPlayTime > 1 ? "s" : "") + " on SteamWar"),
"clock",
Advancement.Display.FrameType.TASK,
i + 1f,
3f
),
Advancement.HidePolicy.PREVIOUS_UNFINISHED,
Math.min(neededPlayTime, 100),
PLAY_TIME_KEY.max(neededPlayTime)
);
playtime.add(previous);
}
}
static {
Advancement previous = ROOT;
int[] fightCounts = new int[]{1, 10, 50, 100, 200, 500, 1000, 2500, 5000, 7500, 10000, 15000, 20000};
for (int i = 0; i < fightCounts.length; i++) {
int fightCount = fightCounts[i];
previous = new Advancement(
"steamwar:advancements/fights_" + fightCount,
Optional.of(previous),
new Advancement.Display(
Component.text(fightCount + " Fight" + (fightCount > 1 ? "s" : "")),
Component.text(fightCount + " Fight" + (fightCount > 1 ? "s" : "")),
"iron_sword",
Advancement.Display.FrameType.TASK,
i + 1f,
4f
),
Advancement.HidePolicy.PREVIOUS_UNFINISHED,
Math.min(fightCount, 100),
FIGHT_COUNT.max(fightCount)
);
if (i == 0) {
fightsPerType(previous, 5f, "WarGear", FIGHT_COUNT_WAR_GEAR, "stone_bricks");
fightsPerType(previous, 6f, "MiniWarGear", FIGHT_COUNT_MINI_WAR_GEAR, "stone_brick_slab");
fightsPerType(previous, 7f, "WarShip", FIGHT_COUNT_WAR_SHIP, "dark_oak_boat");
}
}
}
private static void fightsPerType(Advancement previous, float yCoord, String type, Advancement.Value.Key<Integer> typeKey, String item) {
int[] fightCounts = new int[]{1, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000};
for (int i = 0; i < fightCounts.length; i++) {
int fightCount = fightCounts[i];
previous = new Advancement(
"steamwar:advancements/fights_" + type + "_" + fightCount,
Optional.of(previous),
new Advancement.Display(
Component.text(type + " " + fightCount + " Fight" + (fightCount > 1 ? "s" : "")),
Component.text(type + " " + fightCount + " Fight" + (fightCount > 1 ? "s" : "")),
item,
Advancement.Display.FrameType.TASK,
i + 2f,
yCoord
),
i == 0 ? Advancement.HidePolicy.WITH_PREVIOUS : Advancement.HidePolicy.PREVIOUS_UNFINISHED,
Math.min(fightCount, 100),
typeKey.max(fightCount)
);
}
}
static {
Advancement previous = ROOT;
int[] eventFightCounts = new int[]{1, 5, 10, 15, 25, 50, 100, 150, 200, 250};
for (int i = 0; i < eventFightCounts.length; i++) {
int eventFightCount = eventFightCounts[i];
previous = new Advancement(
"steamwar:advancements/event_fights_" + eventFightCount,
Optional.of(previous),
new Advancement.Display(
Component.text(eventFightCount + " Event-Fight" + (eventFightCount > 1 ? "s" : "")),
Component.text(eventFightCount + " Event-Fight" + (eventFightCount > 1 ? "s" : "")),
"golden_sword",
Advancement.Display.FrameType.TASK,
i + 1f,
8f
),
Advancement.HidePolicy.PREVIOUS_UNFINISHED,
Math.min(eventFightCount, 100),
EVENT_FIGHT_COUNT.max(eventFightCount)
);
if (i == 0) {
placementsCounts(previous, 9f, 1, "gold_block", EVENT_FIGHT_FIRST_PLACE_COUNT, Advancement.Display.FrameType.CHALLENGE);
placementsCounts(previous, 10f, 2, "iron_block", EVENT_FIGHT_SECOND_PLACE_COUNT, Advancement.Display.FrameType.GOAL);
placementsCounts(previous, 11f, 3, "copper_block", EVENT_FIGHT_THIRDPLACE_COUNT, Advancement.Display.FrameType.TASK);
}
}
}
private static void placementsCounts(Advancement previous, float yCoord, int placement, String item, Advancement.Value.Key<Integer> typeKey, Advancement.Display.FrameType frameType) {
for (int placementCount = 1; placementCount <= 10; placementCount++) {
int finalPlacementCount = placementCount;
previous = new Advancement(
"steamwar:advancements/event_placement_" + placement + "_" + placementCount,
Optional.of(previous),
new Advancement.Display(
Component.text(placementCount + "x " + placement + ". Place in Event"),
Component.text(""),
item,
frameType,
2f + (placementCount - 1f),
yCoord
),
placementCount == 1 ? Advancement.HidePolicy.WITH_PREVIOUS : Advancement.HidePolicy.PREVIOUS_UNFINISHED,
1,
typeKey.reached(placementCount)
);
}
}
static {
Advancement previous = ROOT;
int[] checkedCounts = new int[]{1, 10, 100, 250, 500, 750, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000};
for (int i = 0; i < checkedCounts.length; i++) {
int checkedCount = checkedCounts[i];
previous = new Advancement(
"steamwar:advancements/checked_" + checkedCount,
Optional.of(previous),
new Advancement.Display(
Component.text(checkedCount + " Check Session" + (checkedCount > 1 ? "s" : "")),
Component.text(checkedCount + " Check Session" + (checkedCount > 1 ? "s" : "")),
"paper",
Advancement.Display.FrameType.TASK,
i + 1f,
0f
),
i == 0 ? Advancement.HidePolicy.NO_PROGRESS : Advancement.HidePolicy.PREVIOUS_UNFINISHED,
Math.min(checkedCount, 100),
CHECKED_SCHEMATIC_COUNT.max(checkedCount)
);
}
}
static {
Advancement previous = ROOT;
int[] acceptedCounts = new int[]{1, 5, 10, 15, 25, 50, 100, 150, 200, 250, 500, 750, 1000};
for (int i = 0; i < acceptedCounts.length; i++) {
int acceptedCount = acceptedCounts[i];
previous = new Advancement(
"steamwar:advancements/accepted_" + acceptedCount,
Optional.of(previous),
new Advancement.Display(
Component.text(acceptedCount + " Accepted Schematic" + (acceptedCount > 1 ? "s" : "")),
Component.text(acceptedCount + " Accepted Schematic" + (acceptedCount > 1 ? "s" : "")),
"cauldron",
Advancement.Display.FrameType.TASK,
i + 1f,
2f
),
Advancement.HidePolicy.PREVIOUS_UNFINISHED,
Math.min(acceptedCount, 100),
ACCEPTED_SCHEMATIC_COUNT.max(acceptedCount)
);
if (i == 0) {
acceptedPerType(previous, 2f, "WarGear", ACCEPTED_SCHEMATIC_COUNT_WAR_GEAR, "end_stone_bricks");
acceptedPerType(previous, 3f, "MiniWarGear", ACCEPTED_SCHEMATIC_COUNT_MINI_WAR_GEAR, "end_stone_brick_slab");
acceptedPerType(previous, 4f, "WarShip", ACCEPTED_SCHEMATIC_COUNT_WAR_SHIP, "oak_boat");
}
}
}
private static void acceptedPerType(Advancement previous, float xCoord, String type, Advancement.Value.Key<Long> typeKey, String item) {
new Advancement(
"steamwar:advancements/accepted_" + type,
Optional.of(previous),
new Advancement.Display(
Component.text(type + " Accepted"),
Component.text(""),
item,
Advancement.Display.FrameType.GOAL,
xCoord,
1f
),
Advancement.HidePolicy.WITH_PREVIOUS,
1,
typeKey.reached(1)
);
}
}
@@ -1,99 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.advancements;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.DisconnectEvent;
import com.velocitypowered.api.event.connection.PostLoginEvent;
import com.velocitypowered.api.event.player.ServerPostConnectEvent;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry;
import de.steamwar.linkage.Linked;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.velocitycore.listeners.BasicListener;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import java.lang.reflect.Field;
import java.util.Optional;
// @Linked
public class AdvancementsManager extends BasicListener {
private static SelectAdvancementTabPacket selectAdvancementTabPacket;
static {
selectAdvancementTabPacket = new SelectAdvancementTabPacket(Optional.of("steamwar:advancements/root"));
registerPacketId(ProtocolVersion.MINECRAFT_1_21_9, 0x53, 0x80);
registerPacketId(ProtocolVersion.MINECRAFT_1_21_7, 0x4E, 0x7B);
registerPacketId(ProtocolVersion.MINECRAFT_1_21_6, 0x4E, 0x7B);
registerPacketId(ProtocolVersion.MINECRAFT_1_21_5, 0x4E, 0x7B);
registerPacketId(ProtocolVersion.MINECRAFT_1_21_4, 0x4F, 0x7B);
}
private static void registerPacketId(ProtocolVersion version, int selectAdvancementTabPacket, int advancementPacket) {
try {
StateRegistry.PacketRegistry.ProtocolRegistry registry = StateRegistry.PLAY.getProtocolRegistry(ProtocolUtils.Direction.CLIENTBOUND, version);
Field field = StateRegistry.PacketRegistry.ProtocolRegistry.class.getDeclaredField("packetClassToId");
field.setAccessible(true);
Object2IntMap<Class<? extends MinecraftPacket>> map = (Object2IntMap) field.get(registry);
map.put(SelectAdvancementTabPacket.class, selectAdvancementTabPacket);
map.put(Advancement.Packet.class, advancementPacket);
} catch (Exception e) {
// Ignore
}
}
@Subscribe(priority = -1000)
public void onPostLogin(PostLoginEvent event) {
sendAdvancements(event.getPlayer());
}
@Subscribe(priority = -1000)
public void onServerPostConnect(ServerPostConnectEvent event) {
sendAdvancements(event.getPlayer());
}
private void sendAdvancements(Player player) {
// Only enable for 1.21.4 or higher
if (player.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_21_4)) {
return;
}
((ConnectedPlayer) player).getConnection().write(selectAdvancementTabPacket);
SteamwarUser user = SteamwarUser.get(player.getUniqueId());
for (Advancement advancement : Advancements.getAll()) {
advancement.get(user).update();
}
}
@Subscribe
public void onDisconnect(DisconnectEvent event) {
SteamwarUser user = SteamwarUser.get(event.getPlayer().getUniqueId());
for (Advancement advancement : Advancements.getAll()) {
advancement.data.remove(user);
}
Advancement.values.remove(user);
}
}
@@ -1,55 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.velocitycore.advancements;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import lombok.AllArgsConstructor;
import java.util.Optional;
@AllArgsConstructor
public class SelectAdvancementTabPacket implements MinecraftPacket {
private Optional<String> identifier;
@Override
public void decode(ByteBuf byteBuf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
throw new UnsupportedOperationException("Packet is not implemented");
}
@Override
public void encode(ByteBuf byteBuf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
if (this.identifier.isPresent()) {
byteBuf.writeBoolean(true);
ProtocolUtils.writeString(byteBuf, this.identifier.get());
} else {
byteBuf.writeBoolean(false);
}
}
@Override
public boolean handle(MinecraftSessionHandler minecraftSessionHandler) {
return false;
}
}
@@ -44,7 +44,6 @@ import de.steamwar.velocitycore.inventory.SWItem;
import de.steamwar.velocitycore.network.NetworkSender;
import de.steamwar.velocitycore.util.BauLock;
import java.io.File;
import java.util.Collection;
import java.util.function.Consumer;
@@ -188,35 +187,11 @@ public class BauCommand extends SWCommand {
bauserver.getRegisteredServer().getPlayersConnected().stream().findAny().ifPresent(player -> NetworkSender.send(player, new BaumemberUpdatePacket()));
}
try {
deletePlayerData(owner, user);
} catch (Exception e) {
// Ignore this silently since it does not matter in any way if the data is not deleted properly.
}
member.system("BAU_DELMEMBER_DELETED_TARGET", owner);
owner.system("BAU_DELMEMBER_DELETED");
});
}
private void deletePlayerData(Chatter owner, SteamwarUser user) {
String worldName = String.valueOf(owner.user().getId());
String targetDat = user.getUUID().toString().toLowerCase() + ".dat";
String targetDatOld = user.getUUID().toString().toLowerCase() + ".dat_old";
for (ServerVersion version : ServerVersion.values()) {
File playerData = new File(new File(version.getWorldFolder(ServerStarter.WORLDS_BASE_PATH), worldName), "playerdata");
if (playerData.exists()) {
new File(playerData, targetDat).delete();
new File(playerData, targetDatOld).delete();
}
File playerDataStorage = new File(new File(version.getWorldFolder(ServerStarter.WORLDS_STORAGE_BASE_PATH), worldName), "playerdata");
if (playerDataStorage.exists()) {
new File(playerDataStorage, targetDat).delete();
new File(playerDataStorage, targetDatOld).delete();
}
}
}
@Mapper(value = "addedUsers", local = true)
public TypeMapper<SteamwarUser> addedUsers() {
return new TypeMapper<SteamwarUser>() {
@@ -19,7 +19,6 @@
package de.steamwar.velocitycore.commands;
import com.velocitypowered.api.command.SimpleCommand;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection;
import de.steamwar.command.SWCommand;
@@ -63,88 +62,61 @@ public class CheckCommand extends SWCommand {
public static Message getWaitTime(SchematicNode schematic) {
long waitedMillis = Timestamp.from(Instant.now()).getTime() - schematic.getLastUpdate().getTime();
String color;
if (waitedMillis > 48L * 60 * 60 * 1000) color = "4";
else if (waitedMillis > 24L * 60 * 60 * 1000) color = "c";
else if (waitedMillis > 12L * 60 * 60 * 1000) color = "6";
else if (waitedMillis > 4L * 60 * 60 * 1000) color = "e";
else color = "a";
String ce = waitedMillis > 86400000 ? "c" : "e";
String color = waitedMillis > 14400000 ? ce : "a";
long hours = waitedMillis / 3600000;
long minutes = (waitedMillis - hours * 3600000) / 60000;
return new Message("CHECK_LIST_WAIT", color, hours, (minutes < 10) ? "0" + minutes : minutes);
}
public CheckCommand() {
super("check");
VelocityCore.schedule(() -> Chatter.allStream().forEach(CheckCommand::sendReminder)).delay(10, TimeUnit.MINUTES).repeat(10, TimeUnit.MINUTES).schedule();
}
super("check", UserPerm.CHECK);
@Override
protected boolean hasPermission(SimpleCommand.Invocation invocation) {
SteamwarUser user = Chatter.of(invocation.source()).user();
if (user.perms().contains(UserPerm.CHECK)) return true;
return GameModeConfig.getAll()
.stream()
.anyMatch(gameMode -> gameMode.Checkers.contains(user.getId()));
}
private static Map<SchematicNode, SteamwarUser> getSchematics(SteamwarUser user) {
Map<SchematicNode, SteamwarUser> map = new HashMap<>();
for (SchematicNode schematicNode : getSchemsToCheck()) {
if (!mayCheck(user, schematicNode)) continue;
CheckSession checkSession = currentSchems.get(schematicNode.getId());
map.put(schematicNode, checkSession == null ? null : checkSession.checker.user());
}
return map;
}
private static boolean mayCheck(SteamwarUser user, SchematicNode schematic) {
GameModeConfig<String, String> gameModeConfig = ArenaMode.getBySchemType(schematic.getSchemtype());
if (gameModeConfig == null) gameModeConfig = GameModeConfig.getDefaults();
if (user.hasPerm(UserPerm.ADMINISTRATION)) return true;
if (gameModeConfig.Checkers.isEmpty() && user.hasPerm(UserPerm.CHECK)) return true;
return gameModeConfig.Checkers.contains(user.getId());
VelocityCore.schedule(() -> sendReminder(Chatter.serverteam())).repeat(10, TimeUnit.MINUTES).schedule();
}
public static void sendReminder(Chatter chatter) {
Map<SchematicNode, SteamwarUser> schematics = getSchematics(chatter.user());
if (schematics.isEmpty()) return;
long needsChecking = schematics.entrySet().stream().filter(entry -> entry.getValue() == null).count();
if (needsChecking == 0) return;
chatter.system("CHECK_REMINDER", new Message("CHECK_REMINDER_HOVER"), ClickEvent.runCommand("/check list"), needsChecking);
List<SchematicNode> schematics = getSchemsToCheck();
if (schematics.size() == currentCheckers.size()) return;
chatter.system("CHECK_REMINDER", new Message("CHECK_REMINDER_HOVER"), ClickEvent.runCommand("/check list"), schematics.size() - currentCheckers.size());
}
@Register(value = "list", description = "CHECK_HELP_LIST")
public void list(Chatter sender) {
Map<SchematicNode, SteamwarUser> schematics = getSchematics(sender.user());
List<SchematicNode> schematicList = getSchemsToCheck();
sender.system("CHECK_LIST_HEADER", schematics.size());
sender.system("CHECK_LIST_HEADER", schematicList.size());
for (Map.Entry<SchematicNode, SteamwarUser> entry : schematics.entrySet()) {
String message;
ClickEvent clickEvent;
Message hoverMessage;
String checker;
if (entry.getValue() == null) {
message = "CHECK_LIST_TO_CHECK";
clickEvent = ClickEvent.runCommand("/check schematic " + entry.getKey().getId());
hoverMessage = new Message("CHECK_LIST_TO_CHECK_HOVER");
checker = "";
} else {
message = "CHECK_LIST_CHECKING";
clickEvent = ClickEvent.runCommand("/join " + entry.getValue().getUserName());
hoverMessage = new Message("CHECK_LIST_CHECKING_HOVER");
checker = entry.getValue().getUserName();
for (SchematicNode schematic : schematicList) {
GameModeConfig<String, String> gameModeConfig = ArenaMode.getBySchemType(schematic.getSchemtype());
if (gameModeConfig == null) gameModeConfig = GameModeConfig.getDefaults();
CheckSession current = currentSchems.get(schematic.getId());
ClickEvent clickEvent = null;
Message hoverMessage = null;
if (gameModeConfig.Checkers.isEmpty() || gameModeConfig.Checkers.contains(sender.user().getId())) {
if (current == null) {
clickEvent = ClickEvent.runCommand("/check schematic " + schematic.getId());
hoverMessage = new Message("CHECK_LIST_TO_CHECK_HOVER");
} else {
clickEvent = ClickEvent.runCommand("/join " + current.checker.user().getUserName());
hoverMessage = new Message("CHECK_LIST_CHECKING_HOVER");
}
}
sender.prefixless(message,
hoverMessage,
clickEvent,
getWaitTime(entry.getKey()),
entry.getKey().getSchemtype().getKuerzel(),
SteamwarUser.byId(entry.getKey().getOwner()).getUserName(),
entry.getKey().getName(),
checker);
if (current == null) {
sender.prefixless("CHECK_LIST_TO_CHECK",
hoverMessage,
clickEvent,
getWaitTime(schematic),
schematic.getSchemtype().getKuerzel(), SteamwarUser.byId(schematic.getOwner()).getUserName(), schematic.getName());
} else {
sender.prefixless("CHECK_LIST_CHECKING",
hoverMessage,
clickEvent,
getWaitTime(schematic),
schematic.getSchemtype().getKuerzel(), SteamwarUser.byId(schematic.getOwner()).getUserName(), schematic.getName(), current.checker.user().getUserName());
}
}
}
@@ -170,7 +142,8 @@ public class CheckCommand extends SWCommand {
}
int playerTeam = sender.user().hasPerm(UserPerm.MODERATION) ? 0 : sender.user().getTeam();
if (playerTeam != 0 && SteamwarUser.byId(schem.getOwner()).getTeam() == playerTeam) {
// Ignore 795 SteamWar Team
if (playerTeam != 0 && playerTeam != 795 && SteamwarUser.byId(schem.getOwner()).getTeam() == playerTeam) {
sender.system("CHECK_SCHEMATIC_OWN_TEAM");
return;
}
@@ -236,6 +209,11 @@ public class CheckCommand extends SWCommand {
return schematicList;
}
public static String getChecker(SchematicNode schematic) {
if (currentSchems.get(schematic.getId()) == null) return null;
return currentSchems.get(schematic.getId()).checker.user().getUserName();
}
private static boolean notChecking(Player player) {
if (!isChecking(player)) {
Chatter.of(player).system("CHECK_NOT_CHECKING");
@@ -19,7 +19,6 @@
package de.steamwar.velocitycore.commands;
import com.google.protobuf.MapEntry;
import de.steamwar.command.PreviousArguments;
import de.steamwar.command.SWCommand;
import de.steamwar.command.TypeMapper;
@@ -29,18 +28,13 @@ import de.steamwar.messages.Chatter;
import de.steamwar.messages.PlayerChatter;
import de.steamwar.persistent.Subserver;
import de.steamwar.sql.*;
import de.steamwar.sql.Event;
import de.steamwar.velocitycore.EventStarter;
import de.steamwar.velocitycore.SubserverSystem;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import java.awt.*;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -88,84 +82,35 @@ public class EventCommand extends SWCommand {
@Register
public void eventOverview(@Validator(value = "noEvent", invert = true) Chatter sender) {
sender.system("EVENT_TEAM_USAGE");
sender.system("EVENT_USAGE");
Event currentEvent = Event.get();
eventShowFightsPage(sender, currentEvent, 1);
}
sender.system("EVENT_CURRENT_EVENT", currentEvent.getEventName());
@Register(value = "showtable", description = "EVENT_SHOW_TABLE_USAGE")
public void eventtable(Chatter sender, Event event) {
if (!Instant.now().isBefore(event.getEnd().toInstant())) return;
DateTimeFormatter format = DateTimeFormatter.ofPattern(sender.parseToPlain("EVENT_TIME_FORMAT"));
for (EventFight fight : EventFight.getEvent(currentEvent.getEventID())) {
Team blue = Team.byId(fight.getTeamBlue());
Team red = Team.byId(fight.getTeamRed());
StringBuilder fline = new StringBuilder(sender.parseToLegacy("EVENT_CURRENT_FIGHT", fight.getStartTime().toLocalDateTime().format(format), blue.getTeamColor(), blue.getTeamKuerzel(), red.getTeamColor(), red.getTeamKuerzel()));
Map<Integer, Integer> Teampoints = new HashMap<>();
EventFight.getEvent(event.getEventID()).forEach(eventFight -> {
if (eventFight.hasFinished() && eventFight.getGroup().isPresent()) {
switch (eventFight.getErgebnis()) {
if (fight.hasFinished()) {
switch (fight.getErgebnis()) {
case 1:
//win blue
Teampoints.put(eventFight.getTeamBlue(), Teampoints.getOrDefault(eventFight.getTeamBlue(), 0) + eventFight.getGroup().get().getPointsPerWin());
Teampoints.put(eventFight.getTeamRed(), Teampoints.getOrDefault(eventFight.getTeamRed(),0) + eventFight.getGroup().get().getPointsPerLoss());
fline.append(sender.parseToLegacy("EVENT_CURRENT_FIGHT_WIN", blue.getTeamColor(), blue.getTeamKuerzel()));
break;
case 2:
//win red
Teampoints.put(eventFight.getTeamBlue(), Teampoints.getOrDefault(eventFight.getTeamBlue(),0) + eventFight.getGroup().get().getPointsPerLoss());
Teampoints.put(eventFight.getTeamRed(), Teampoints.getOrDefault(eventFight.getTeamRed(),0) + eventFight.getGroup().get().getPointsPerWin());
fline.append(sender.parseToLegacy("EVENT_CURRENT_FIGHT_WIN", red.getTeamColor(), red.getTeamKuerzel()));
break;
default:
//draw
Teampoints.put(eventFight.getTeamBlue(), Teampoints.getOrDefault(eventFight.getTeamBlue(),0) + eventFight.getGroup().get().getPointsPerDraw());
Teampoints.put(eventFight.getTeamRed(), Teampoints.getOrDefault(eventFight.getTeamRed(),0) + eventFight.getGroup().get().getPointsPerDraw());
fline.append(sender.parseToLegacy("EVENT_CURRENT_FIGHT_DRAW"));
}
}
});
Map<Integer, Integer> Teampointssortet = Teampoints.entrySet()
.stream()
.sorted(Map.Entry.comparingByValue())
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(a , b) -> a,
LinkedHashMap::new
));
for (Map.Entry<Integer, Integer> entry : Teampointssortet.entrySet()) {
Component finalmessage = sender.parse("EVENT_TEAM_TABLE",Team.byId(entry.getKey()).getTeamColor(), Team.byId(entry.getKey()).getTeamKuerzel(), Team.byId(entry.getKey()).getTeamName(), entry.getValue());
finalmessage = finalmessage.clickEvent(ClickEvent.runCommand("/team info " + Team.byId(entry.getKey()).getTeamName()));
sender.sendMessage(finalmessage);
sender.prefixless("PLAIN_STRING", fline.toString());
}
}
@Register(value = "showfights", description = "EVENT_SHOW_FIGHTS_USAGE")
public void eventShowFightsPage(Chatter sender, Event event, @AllowNull @OptionalValue("1") int page) {
if (!Instant.now().isBefore(event.getEnd().toInstant())) return;
int pagecount = sendEventPage(sender, event, page);
Integer nextpage = page + 1;
Integer prevpage = page - 1;
Component finalmessage;
Component pagenumber = sender.parse("UTIL_LIST_PAGE", page, pagecount);
if (pagecount != 0 && page != 1) {
finalmessage = sender.parse("UTIL_LIST_BACK_ARROW");
finalmessage = finalmessage.clickEvent(ClickEvent.runCommand("/event showfights " + event.getEventName() + " " + prevpage));
finalmessage = finalmessage.append(pagenumber);
} else {
finalmessage = pagenumber;
}
if (pagecount != 0 && pagecount > page) {
Component nextbutton = sender.parse("UTIL_LIST_NEXT");
nextbutton = nextbutton.clickEvent(ClickEvent.runCommand("/event showfights " + event.getEventName() + " " + nextpage));
finalmessage = finalmessage.append(nextbutton);
}
sender.sendMessage(finalmessage);
}
@Register(value = "team", description = "EVENT_TEAM_USAGE")
@Register
public void eventWithTeam(@Validator(value = "noEvent", invert = true) PlayerChatter sender, @ErrorMessage("EVENT_NO_TEAM") Team team) {
Subserver eventArena = EventStarter.getEventServer().get(team.getTeamId());
if (eventArena == null || !Subserver.getServerList().contains(eventArena)) {
@@ -177,69 +122,6 @@ public class EventCommand extends SWCommand {
}
}
private int sendEventPage(Chatter sender, Event event, int page) {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(sender.parseToPlain("EVENT_DATE_FORMAT"));
Map<String, List<EventFight>> dates = EventFight.getEvent(event.getEventID()).stream().collect(Collectors.groupingBy(p -> p.getStartTime().toLocalDateTime().format(dateTimeFormatter)));
if (dates.size() >= page) {
sender.system("EVENT_CURRENT_EVENT", event.getEventName());
DateTimeFormatter format = DateTimeFormatter.ofPattern(sender.parseToPlain("EVENT_TIME_FORMAT"));
for (EventFight eventFight : dates.get(dates.keySet().toArray()[page - 1])) {
Team blue = Team.byId(eventFight.getTeamBlue());
Team red = Team.byId(eventFight.getTeamRed());
Component finalfightline = sender.parse("EVENT_CURRENT_FIGHT_1", eventFight.getStartTime().toLocalDateTime().format(format), blue.getTeamColor(), blue.getTeamKuerzel());
finalfightline = finalfightline.clickEvent(ClickEvent.runCommand("/team info " + blue.getTeamName()));
Component vs = sender.parse("EVENT_CURRENT_FIGHT_VS");
finalfightline = finalfightline.append(vs);
Component redteam = sender.parse("EVENT_CURRENT_FIGHT_2", red.getTeamColor(), red.getTeamKuerzel());
redteam = redteam.clickEvent(ClickEvent.runCommand("/team info " + red.getTeamName()));
finalfightline = finalfightline.append(redteam);
if (eventFight.hasFinished()) {
Component ergebnis;
switch (eventFight.getErgebnis()) {
case 1:
ergebnis = sender.parse("EVENT_CURRENT_FIGHT_WIN", blue.getTeamColor(), blue.getTeamKuerzel());
break;
case 2:
ergebnis = sender.parse("EVENT_CURRENT_FIGHT_WIN", red.getTeamColor(), red.getTeamKuerzel());
break;
default:
ergebnis = sender.parse("EVENT_CURRENT_FIGHT_DRAW");
}
finalfightline = finalfightline.append(ergebnis);
}
sender.sendMessage(finalfightline);
}
}
return dates.size();
}
protected static TypeMapper<Event> eventTypeMapper() {
return new TypeMapper<>() {
@Override
public Event map(Chatter sender, PreviousArguments previousArguments, String s) {
return Event.get(s);
}
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
Set<String> events = new HashSet<>();
List<Event> allevents = Event.getAll();
for (Event event : allevents) {
if (!Instant.now().isAfter(event.getEnd().toInstant())) {
events.add(event.getEventName());
}
}
return events;
}
};
}
@ClassMapper(value = Event.class, local = true)
public TypeMapper<Event> eventMapper() {
return eventTypeMapper();
}
protected static TypeMapper<Team> eventTeam(Function<EventFight, List<Integer>> teamMapper) {
return new TypeMapper<>() {
@Override
@@ -19,7 +19,6 @@
package de.steamwar.velocitycore.commands;
import com.velocitypowered.api.command.SimpleCommand;
import de.steamwar.command.SWCommand;
import de.steamwar.linkage.EventMode;
import de.steamwar.linkage.Linked;
@@ -41,13 +40,7 @@ public class StreamingCommand extends SWCommand {
}
public StreamingCommand() {
super("streaming");
}
@Override
protected boolean hasPermission(SimpleCommand.Invocation invocation) {
SteamwarUser user = Chatter.of(invocation.source()).user();
return user.hasPerm(UserPerm.TEAM) || user.hasPerm(UserPerm.PREFIX_YOUTUBER);
super("streaming", UserPerm.TEAM);
}
@Register
@@ -35,9 +35,6 @@ import de.steamwar.sql.CheckedSchematic;
import de.steamwar.sql.SchematicType;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.UserPerm;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.advancements.Advancement;
import de.steamwar.velocitycore.advancements.Advancements;
import de.steamwar.velocitycore.commands.*;
import de.steamwar.velocitycore.discord.DiscordBot;
import de.steamwar.velocitycore.discord.util.DiscordRanks;
@@ -48,7 +45,6 @@ import net.kyori.adventure.text.event.ClickEvent;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Linked
public class ConnectionListener extends BasicListener {
@@ -84,7 +80,8 @@ public class ConnectionListener extends BasicListener {
Player player = event.getPlayer();
SteamwarUser user = SteamwarUser.get(player.getUniqueId());
Chatter chatter = Chatter.of(player);
CheckCommand.sendReminder(chatter);
if (user.hasPerm(UserPerm.CHECK)) CheckCommand.sendReminder(chatter);
for (Subserver subserver : Subserver.getServerList()) {
if (Subserver.isArena(subserver)) {
@@ -105,12 +102,8 @@ public class ConnectionListener extends BasicListener {
}
if (newPlayers.contains(player.getUniqueId())) {
Advancements.ROOT.get(user, (advancement, __) -> new Advancement.Data(advancement, user, 0));
Chatter.broadcast().system("JOIN_FIRST", player);
newPlayers.remove(player.getUniqueId());
VelocityCore.schedule(() -> {
Advancements.ROOT.get(user).update();
}).delay(1, TimeUnit.SECONDS).schedule();
}
if (!StreamingCommand.isNotStreaming(user)) {
@@ -39,28 +39,22 @@ public class TexturePackSystem extends BasicListener {
private static final File PACKS_DIR = new File("/var/www/packs");
private static final String BASE_ULR = "https://packs.steamwar.de/";
private TreeMap<Integer, String> protocolVersionToPackVersion = new TreeMap<>();
private TreeMap<Integer, Integer> protocolVersionToPackVersion = new TreeMap<>();
public TexturePackSystem() {
// https://minecraft.wiki/w/Pack_format#List_of_resource_pack_formats
// https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol_version_numbers
protocolVersionToPackVersion.put(759, "9");
protocolVersionToPackVersion.put(761, "12");
protocolVersionToPackVersion.put(762, "13");
protocolVersionToPackVersion.put(763, "15");
protocolVersionToPackVersion.put(764, "18");
protocolVersionToPackVersion.put(765, "22");
protocolVersionToPackVersion.put(766, "32");
protocolVersionToPackVersion.put(767, "34");
protocolVersionToPackVersion.put(768, "42");
protocolVersionToPackVersion.put(769, "46");
protocolVersionToPackVersion.put(770, "55");
protocolVersionToPackVersion.put(771, "63");
protocolVersionToPackVersion.put(772, "64");
protocolVersionToPackVersion.put(773, "69.0");
protocolVersionToPackVersion.put(774, "75.0");
protocolVersionToPackVersion.put(775, "84.0");
protocolVersionToPackVersion.put(776, "88.0");
protocolVersionToPackVersion.put(759, 9);
protocolVersionToPackVersion.put(761, 12);
protocolVersionToPackVersion.put(762, 13);
protocolVersionToPackVersion.put(763, 15);
protocolVersionToPackVersion.put(764, 18);
protocolVersionToPackVersion.put(765, 22);
protocolVersionToPackVersion.put(766, 32);
protocolVersionToPackVersion.put(767, 34);
protocolVersionToPackVersion.put(768, 42);
protocolVersionToPackVersion.put(769, 46);
protocolVersionToPackVersion.put(770, 55);
}
@Subscribe
@@ -69,19 +63,20 @@ public class TexturePackSystem extends BasicListener {
return;
}
VelocityCore.schedule(() -> {
TreeMap<String, File> fileTreeMap = new TreeMap<>();
TreeMap<Integer, File> fileTreeMap = new TreeMap<>();
for (File fileEntry : PACKS_DIR.listFiles()) {
try {
fileTreeMap.put(fileEntry.getName().split("_")[0], fileEntry);
int packVersion = Integer.parseInt(fileEntry.getName().split("_")[0]);
fileTreeMap.put(packVersion, fileEntry);
} catch (NumberFormatException e) {
// Ignore
}
}
int playerVersion = event.getPlayer().getProtocolVersion().getProtocol();
Map.Entry<Integer, String> packVersionEntry = protocolVersionToPackVersion.floorEntry(playerVersion);
Map.Entry<Integer, Integer> packVersionEntry = protocolVersionToPackVersion.floorEntry(playerVersion);
if (packVersionEntry == null) return;
Map.Entry<String, File> selectedPackEntry = fileTreeMap.floorEntry(packVersionEntry.getValue());
Map.Entry<Integer, File> selectedPackEntry = fileTreeMap.floorEntry(packVersionEntry.getValue());
if (selectedPackEntry == null) return;
File selectedPack = selectedPackEntry.getValue();
+8
View File
@@ -19,6 +19,7 @@
plugins {
`kotlin-dsl`
`groovy-gradle-plugin`
}
repositories {
@@ -26,6 +27,13 @@ repositories {
gradlePluginPortal()
}
sourceSets {
main {
groovy.srcDirs("src/main/groovy")
kotlin.srcDirs("src/main/kotlin")
}
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.21")
}
@@ -0,0 +1,348 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import java.security.MessageDigest
import java.util.stream.Collectors
plugins {
}
class DevServer extends DefaultTask {
@Input
boolean debug = false
@Input
String template = null
@Input
@Optional
String plugins = null
@Input
@Optional
Integer port = null
@Input
@Optional
String jar = null
@Input
@Optional
Map<String, String> dParams = new HashMap<>()
@Input
@Optional
String jvmArgs = null
@Input
@Optional
String checkpointFolder = null
@Input
@Optional
Boolean profile = null
@Input
@Optional
Boolean forceUpgrade = null
@Input
@Optional
String worldName = null
DevServer() {
super()
doFirst {
if (checkpointFolder != null) dParams.put("checkpoint", checkpointFolder)
List<Project> projects = []
projects.add(project)
while (projects.first.parent != null) {
projects.add(0, projects.first.parent)
}
def properties = new Properties()
projects.forEach {
def file = new File(it.projectDir, "steamwar.properties")
if (file.exists()) {
properties.load(new FileInputStream(file))
}
}
if (template.startsWith("Bau")) {
if (properties.containsKey("worldName")) {
worldName = properties.get("worldName")
} else {
throw new GradleException("Please supply the 'worldName' in a 'steamwar.properties' files either in this project dir or any parent project!")
}
}
host = properties.get("host")
debugPort = new Random().nextInt(5001, 10000)
if (host == null) {
throw new GradleException("Please supply the 'host' in a 'steamwar.properties' files either in this project dir or any parent project!")
}
}
doLast {
setupTemplate(template)
uploadDependencies()
if (debug) startDebugPort()
startDevServer()
}
finalizedBy(new Finalizer())
}
@Internal
BufferedWriter processInput
@Internal
String host
@Internal
int debugPort
@Internal
Boolean running = true
class Finalizer extends DefaultTask {
Finalizer() {
super()
doLast {
if (processInput != null) {
processInput.write(template.endsWith("Velocity") ? "end\n" : "stop\n")
processInput.flush()
}
running = false
}
}
}
private Process run(String... args) {
List<String> arguments = new ArrayList<>();
arguments.add("ssh")
arguments.add(host)
arguments.add("-T")
arguments.addAll(Arrays.asList(args))
def process = new ProcessBuilder(arguments).start()
process.waitFor()
return process
}
private boolean checkFileOnRemote(String path) {
def process = run("[ -e \"$path\" ] && echo \"true\"")
process.errorStream.close()
process.outputStream.close()
try (def reader = new BufferedReader(new InputStreamReader(process.inputStream))) {
return reader.lines().count() > 0
}
}
private static void closeProcess(Process process) {
process.outputStream.close()
process.inputStream.close()
process.errorStream.close()
}
void setupTemplate(String template) {
if (checkFileOnRemote("$template")) return
if (checkFileOnRemote("/configs/GameModes/${template}.yml")) {
println("GameMode Config exists")
def process = run("cat /configs/GameModes/${template}.yml | grep \"Folder: \"")
String serverTemplateName = new BufferedReader(new InputStreamReader(process.inputStream)).lines().collect(Collectors.joining("\n"))
.trim()
.substring("Folder: ".length())
DevServer.closeProcess(process)
setupTemplate(serverTemplateName)
run("ln -s $serverTemplateName $template")
return
}
if (!checkFileOnRemote("/servers/$template")) {
throw new GradleException("Used template ($template) is not in /servers/ directory of the given host $host")
}
DevServer.closeProcess(run("cp -r /servers/$template $template"))
DevServer.closeProcess(run("chmod u+w $template"))
DevServer.closeProcess(run("rm -r $template/plugins/*WorldEdit/"))
DevServer.closeProcess(run("rm $template/log4j2.xml"))
}
void uploadDependencies() {
def base = plugins == null ? "$template/plugins" : plugins
println("Uploading to ~/$base")
this.dependsOn.forEach {
Project resolved
AbstractArchiveTask archiveTask
if (it instanceof String) {
resolved = project.findProject(it.substring(0, it.lastIndexOf(':')))
archiveTask = (AbstractArchiveTask) resolved.tasks.findByName(it.substring(it.lastIndexOf(':') + 1))
} else {
throw new GradleException("Illegal argument for uploading dependencies")
}
def archive = archiveTask.archiveFile.get().asFile
Process process = new ProcessBuilder("ssh", host, "-T", "sha1sum $base/${archive.name.replace("-all", "")}").start()
byte[] bytes = MessageDigest.getInstance("sha1").digest(archive.bytes)
StringBuilder sb = new StringBuilder()
for (byte b : bytes) {
sb.append(String.format("%02X", b))
}
boolean same = false
process.inputStream.readLines().forEach {
same |= it.startsWith(sb.toString().toLowerCase())
}
DevServer.closeProcess(process)
if (same) {
println("Skipping $archive")
return
}
println("Uploading $archive")
process = new ProcessBuilder("ssh", host, "-T", "rm $base/${archive.name.replace("-all", "")}").start()
process.waitFor()
DevServer.closeProcess(process)
process = new ProcessBuilder("scp", archive.absolutePath, "$host:~/$base/${archive.name.replace("-all", "")}").start();
process.waitFor()
DevServer.closeProcess(process)
println("Uploaded $archive")
}
}
void startDebugPort() {
def process = new ProcessBuilder("ssh", host, "-L", "5005:localhost:$debugPort").start()
def processOutput = new BufferedReader(new InputStreamReader(process.inputStream))
new Thread({
while (running) {
}
processOutput.close()
process.errorStream.close()
}).start()
}
void startDevServer() {
def devPy = new StringBuilder().append("dev.py")
if (port != null) devPy.append(" --port $port")
if (worldName != null) devPy.append(" -w $template/$worldName")
if (plugins != null) devPy.append(" -p $plugins")
if (profile != null) devPy.append(" --profile")
if (forceUpgrade != null) devPy.append(" --forceUpgrade")
if (jar != null) devPy.append(" --jar $jar")
for (Map.Entry<String, String> dParam : dParams.entrySet()) {
devPy.append(" -D${dParam.key}=${dParam.value}")
}
devPy.append(" -Dpaper.disablePluginRemapping=true")
devPy.append(" $template")
if (debug) devPy.append(" -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:$debugPort")
devPy.append(" -javaagent:/jars/AccessWidener.jar=start")
if (jvmArgs != null) devPy.append(" $jvmArgs")
println("Starting $template with command ${devPy.toString()}")
def process = new ProcessBuilder("ssh", host, "-T", devPy.toString()).start()
def processOutput = new BufferedReader(new InputStreamReader(process.inputStream))
new Thread({
while (running) {
if (processOutput.ready()) {
println(processOutput.readLine())
}
}
processOutput.close()
process.errorStream.close()
}).start()
processInput = new BufferedWriter(new OutputStreamWriter(process.outputStream))
def input = new BufferedReader(new InputStreamReader(System.in))
new Thread({
while (running) {
def text = input.readLine()
if (text == null) break
processInput.write(text)
processInput.newLine()
processInput.flush()
}
}).start()
process.waitFor()
if (processInput != null) {
processInput.close()
}
processInput = null
running = false
}
}
class VelocityServer extends DevServer {
@Input
@Optional
Boolean packetDecodeLogging = false
VelocityServer() {
super()
doFirst {
if (packetDecodeLogging) dParams.put("velocity.packet-decode-logging", "true")
}
}
}
class FightServer extends DevServer {
@Input
@Optional
Integer checkSchemID = 0
@Input
@Optional
Integer prepareSchemID = 0
@Input
@Optional
Integer replay = 0
@Input
@Optional
String config = null
@Input
@Optional
// Property: fightID
Integer eventKampfID = 0
@Input
@Optional
UUID blueLeader = null
@Input
@Optional
UUID redLeader = null
FightServer() {
super()
doFirst {
if (checkSchemID != 0) dParams.put("checkSchemID", "$checkSchemID")
if (prepareSchemID != 0) dParams.put("prepareSchemID", "$prepareSchemID")
if (replay != 0) dParams.put("replay", "$replay")
if (eventKampfID != 0) dParams.put("fightID", "$eventKampfID")
if (blueLeader != null) dParams.put("blueLeader", blueLeader.toString())
if (redLeader != null) dParams.put("redLeader", redLeader.toString())
if (config != null) dParams.put("config", config)
}
}
}
@@ -18,18 +18,16 @@
*/
plugins {
`java-library`
id 'java-library'
}
val libs = the<VersionCatalogsExtension>().named("libs")
java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
tasks.compileJava {
options.encoding = "UTF-8"
options.encoding "UTF-8"
}
sourceSets {
@@ -56,9 +54,8 @@ sourceSets {
}
dependencies {
val lombok = libs.findLibrary("lombok").get()
annotationProcessor(lombok)
compileOnly(lombok)
testCompileOnly(lombok)
testAnnotationProcessor(lombok)
}
annotationProcessor libs.lombok
compileOnly libs.lombok
testCompileOnly libs.lombok
testAnnotationProcessor libs.lombok
}
@@ -1,7 +1,7 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@@ -18,12 +18,10 @@
*/
plugins {
`java-library`
id("org.jetbrains.kotlin.jvm")
id 'java-library'
id "org.jetbrains.kotlin.jvm"
}
val libs = the<VersionCatalogsExtension>().named("libs")
kotlin {
jvmToolchain(21)
}
@@ -34,7 +32,7 @@ java {
}
tasks.compileJava {
options.encoding = "UTF-8"
options.encoding "UTF-8"
}
sourceSets {
@@ -69,9 +67,8 @@ sourceSets {
}
dependencies {
val lombok = libs.findLibrary("lombok").get()
annotationProcessor(lombok)
compileOnly(lombok)
testCompileOnly(lombok)
testAnnotationProcessor(lombok)
}
annotationProcessor libs.lombok
compileOnly libs.lombok
testCompileOnly libs.lombok
testAnnotationProcessor libs.lombok
}
-290
View File
@@ -1,290 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.bundling.AbstractArchiveTask
import java.io.BufferedReader
import java.io.BufferedWriter
import java.io.File
import java.io.FileInputStream
import java.io.InputStreamReader
import java.io.OutputStreamWriter
import java.security.MessageDigest
import java.util.Properties
import java.util.Random
import java.util.stream.Collectors
open class DevServer : DefaultTask() {
@get:Input
var debug: Boolean = false
@get:Input
var template: String? = null
@get:Input
@get:Optional
var plugins: String? = null
@get:Input
@get:Optional
var port: Int? = null
@get:Input
@get:Optional
var jar: String? = null
@get:Input
@get:Optional
var dParams: MutableMap<String, String> = HashMap()
@get:Input
@get:Optional
var jvmArgs: String? = null
@get:Input
@get:Optional
var checkpointFolder: String? = null
@get:Input
@get:Optional
var profile: Boolean? = null
@get:Input
@get:Optional
var forceUpgrade: Boolean? = null
@get:Input
@get:Optional
var worldName: String? = null
@get:Internal
var processInput: BufferedWriter? = null
@get:Internal
var host: String? = null
@get:Internal
var debugPort: Int = 0
@get:Internal
var running: Boolean = true
init {
doFirst {
checkpointFolder?.let { dParams.put("checkpoint", it) }
val projects = mutableListOf<Project>()
projects.add(project)
while (projects[0].parent != null) {
projects.add(0, projects[0].parent!!)
}
val properties = Properties()
projects.forEach {
val file = File(it.projectDir, "steamwar.properties")
if (file.exists()) {
FileInputStream(file).use { input -> properties.load(input) }
}
}
if (template!!.startsWith("Bau")) {
if (properties.containsKey("worldName")) {
worldName = properties.getProperty("worldName")
} else {
throw GradleException("Please supply the 'worldName' in a 'steamwar.properties' files either in this project dir or any parent project!")
}
}
host = properties.getProperty("host")
debugPort = Random().nextInt(5001, 10000)
if (host == null) {
throw GradleException("Please supply the 'host' in a 'steamwar.properties' files either in this project dir or any parent project!")
}
}
doLast {
setupTemplate(template!!)
uploadDependencies()
if (debug) startDebugPort()
startDevServer()
}
finalizedBy(Finalizer())
}
inner class Finalizer : DefaultTask() {
init {
doLast {
processInput?.let {
it.write(if (template!!.endsWith("Velocity")) "end\n" else "stop\n")
it.flush()
}
running = false
}
}
}
private fun run(vararg args: String): Process {
val arguments = mutableListOf("ssh", host, "-T")
arguments.addAll(args)
val process = ProcessBuilder(arguments).start()
process.waitFor()
return process
}
private fun checkFileOnRemote(path: String): Boolean {
val process = run("[ -e \"$path\" ] && echo \"true\"")
process.errorStream.close()
process.outputStream.close()
return BufferedReader(InputStreamReader(process.inputStream)).use { reader ->
reader.lines().count() > 0
}
}
private fun closeProcess(process: Process) {
process.outputStream.close()
process.inputStream.close()
process.errorStream.close()
}
fun setupTemplate(template: String) {
if (checkFileOnRemote(template)) return
if (checkFileOnRemote("/configs/GameModes/$template.yml")) {
println("GameMode Config exists")
val process = run("cat /configs/GameModes/$template.yml | grep \"Folder: \"")
val serverTemplateName = BufferedReader(InputStreamReader(process.inputStream)).lines()
.collect(Collectors.joining("\n"))
.trim()
.substring("Folder: ".length)
closeProcess(process)
setupTemplate(serverTemplateName)
run("ln -s $serverTemplateName $template")
return
}
if (!checkFileOnRemote("/servers/$template")) {
throw GradleException("Used template ($template) is not in /servers/ directory of the given host $host")
}
closeProcess(run("cp -r /servers/$template $template"))
closeProcess(run("chmod u+w $template"))
closeProcess(run("rm -r $template/plugins/*WorldEdit/"))
closeProcess(run("rm $template/log4j2.xml"))
}
fun uploadDependencies() {
val base = if (plugins == null) "$template/plugins" else plugins
println("Uploading to ~/$base")
this.dependsOn.forEach {
if (it !is String) {
throw GradleException("Illegal argument for uploading dependencies")
}
val resolved = project.findProject(it.substring(0, it.lastIndexOf(':')))!!
val archiveTask = resolved.tasks.findByName(it.substring(it.lastIndexOf(':') + 1)) as AbstractArchiveTask
val archive = archiveTask.archiveFile.get().asFile
var process = ProcessBuilder("ssh", host, "-T", "sha1sum $base/${archive.name.replace("-all", "")}").start()
val bytes = MessageDigest.getInstance("sha1").digest(archive.readBytes())
val sb = StringBuilder()
for (b in bytes) {
sb.append(String.format("%02X", b))
}
var same = false
process.inputStream.bufferedReader().readLines().forEach { line ->
same = same || line.startsWith(sb.toString().lowercase())
}
closeProcess(process)
if (same) {
println("Skipping $archive")
return@forEach
}
println("Uploading $archive")
process = ProcessBuilder("ssh", host, "-T", "rm $base/${archive.name.replace("-all", "")}").start()
process.waitFor()
closeProcess(process)
process = ProcessBuilder("scp", archive.absolutePath, "$host:~/$base/${archive.name.replace("-all", "")}").start()
process.waitFor()
closeProcess(process)
println("Uploaded $archive")
}
}
fun startDebugPort() {
val process = ProcessBuilder("ssh", host, "-L", "5005:localhost:$debugPort").start()
val processOutput = BufferedReader(InputStreamReader(process.inputStream))
Thread {
while (running) {
}
processOutput.close()
process.errorStream.close()
}.start()
}
fun startDevServer() {
val devPy = StringBuilder().append("sw dev")
if (port != null) devPy.append(" --port $port")
if (worldName != null) devPy.append(" -w $template/$worldName")
if (plugins != null) devPy.append(" -p $plugins")
if (profile != null) devPy.append(" --profile")
if (forceUpgrade != null) devPy.append(" --forceUpgrade")
if (jar != null) devPy.append(" --jar $jar")
devPy.append(" $template")
for ((key, value) in dParams) {
devPy.append(" -D$key=$value")
}
devPy.append(" -Dpaper.disablePluginRemapping=true")
if (debug) devPy.append(" -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:$debugPort")
devPy.append(" -javaagent:/jars/AccessWidener.jar=start")
if (jvmArgs != null) devPy.append(" $jvmArgs")
println("Starting $template with command $devPy")
val process = ProcessBuilder("ssh", host, "-T", devPy.toString()).start()
val processOutput = BufferedReader(InputStreamReader(process.inputStream))
Thread {
while (running) {
if (processOutput.ready()) {
println(processOutput.readLine())
}
}
processOutput.close()
process.errorStream.close()
}.start()
processInput = BufferedWriter(OutputStreamWriter(process.outputStream))
val input = BufferedReader(InputStreamReader(System.`in`))
Thread {
while (running) {
val text = input.readLine() ?: break
processInput?.write(text)
processInput?.newLine()
processInput?.flush()
}
}.start()
process.waitFor()
processInput?.close()
processInput = null
running = false
}
}
-66
View File
@@ -1,66 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Optional
import java.util.UUID
open class FightServer : DevServer() {
@get:Input
@get:Optional
var checkSchemID: Int? = 0
@get:Input
@get:Optional
var prepareSchemID: Int? = 0
@get:Input
@get:Optional
var replay: Int? = 0
@get:Input
@get:Optional
var config: String? = null
// Property: fightID
@get:Input
@get:Optional
var eventKampfID: Int? = 0
@get:Input
@get:Optional
var blueLeader: UUID? = null
@get:Input
@get:Optional
var redLeader: UUID? = null
init {
doFirst {
if (checkSchemID != 0) dParams.put("checkSchemID", "$checkSchemID")
if (prepareSchemID != 0) dParams.put("prepareSchemID", "$prepareSchemID")
if (replay != 0) dParams.put("replay", "$replay")
if (eventKampfID != 0) dParams.put("fightID", "$eventKampfID")
blueLeader?.let { dParams.put("blueLeader", it.toString()) }
redLeader?.let { dParams.put("redLeader", it.toString()) }
config?.let { dParams.put("config", it) }
}
}
}
+1 -1
View File
@@ -118,6 +118,7 @@ dependencyResolutionManagement {
library("nms", "de.steamwar:spigot:1.21.6")
library("axiom", "de.steamwar:axiompaper:RELEASE")
library("worldedit", "com.sk89q.worldedit:worldedit-bukkit:7.3.16")
library("fawe", "de.steamwar:fastasyncworldedit:1.21")
library("velocity", "de.steamwar:velocity:RELEASE")
@@ -128,7 +129,6 @@ dependencyResolutionManagement {
library("msgpack", "org.msgpack:msgpack-core:0.9.8")
library("logback", "ch.qos.logback:logback-classic:1.5.6")
library("coroutinesCore", "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
val ktorVersion = "2.3.12"