Compare commits

...
Author SHA1 Message Date
Chaoscaot 829d8a6cdf Initial ChaosSchematicReader
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-01-21 23:33:45 +01:00
YoyoNow 361c698323 Check why StaticMessageChannel does not work? 2026-01-09 10:07:26 +01:00
YoyoNow db4ea2d69d Check why StaticMessageChannel does not work? 2026-01-09 10:05:17 +01:00
YoyoNow 3cecc58bce Check why StaticMessageChannel does not work? 2026-01-09 10:03:14 +01:00
YoyoNow ce3d50fcb7 Check why StaticMessageChannel does not work? 2026-01-09 10:01:36 +01:00
YoyoNow 61bd28150b Check why StaticMessageChannel does not work? 2026-01-09 09:59:22 +01:00
YoyoNow bb9caa28a3 Check why StaticMessageChannel does not work? 2026-01-09 09:57:18 +01:00
YoyoNow 4b2970d243 Check why StaticMessageChannel does not work? 2026-01-09 09:53:34 +01:00
Chaoscaot 834767edbe Fix API
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-01-04 12:35:11 +01:00
Chaoscaot 4bea077d36 Fix Kits
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-01-03 02:25:52 +01:00
Chaoscaot 74d6ccc24f Fix Kits
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-01-03 02:22:55 +01:00
Chaoscaot d9f905d957 Fix Kits
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-01-02 14:24:49 +01:00
Chaoscaot 663a745d8f Fix Kits
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-01-02 14:08:49 +01:00
Chaoscaot 1f64c3383d Fix HotbarKitListener
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-01-01 17:04:09 +01:00
Lixfel e4676d5eba Test hotpatch kit 2026-01-01 15:08:37 +01:00
Chaoscaot ebb2ec817d Fix Windcharge Damage on Arena
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-12-27 19:18:31 +01:00
Chaoscaot b6445ce2e9 Merge pull request 'Change Bug Button to Link' (#263) from update-bug-button into main
Reviewed-on: SteamWar/SteamWar#263
Reviewed-by: YoyoNow <yoyonow@noreply.localhost>
2025-12-25 21:09:37 +01:00
Chaoscaot a454da6da8 Change Bug Button to Link
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-12-25 21:07:53 +01:00
YoyoNow a87cc94700 Pot fix for WinconditionBasePercent 2025-12-23 12:27:48 +01:00
YoyoNow 31dac93698 Remove UnrankCommand 2025-12-23 10:40:53 +01:00
YoyoNow 1f568f3d8b Remove UnrankCommand 2025-12-22 21:25:40 +01:00
YoyoNow b8b8dd1ba0 Update UserPerm.kt 2025-12-21 13:25:30 +01:00
YoyoNow 99f864d889 Hotfix SteamwarUser and ServerStarter 2025-12-21 12:27:55 +01:00
YoyoNow 711a21b634 Merge pull request 'Remove SchemElo and UserElo' (#256) from RemoveElo into main
Reviewed-on: SteamWar/SteamWar#256
2025-12-21 12:04:27 +01:00
YoyoNow 9a85e8b442 Merge branch 'main' into RemoveElo 2025-12-21 12:03:33 +01:00
Chaoscaot 8358203cd4 Fix kits frfrfrfrfrfr
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-12-20 22:15:30 +01:00
Chaoscaot 6a619c2fd1 Fix kits frfrfrfrfr?
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-12-20 22:12:48 +01:00
Chaoscaot d348e4a480 Fix kits frfrfrfr?
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-12-20 22:10:25 +01:00
Chaoscaot 1269e4d971 Fix kits frfrfr
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-12-20 22:03:09 +01:00
Chaoscaot feac17d732 Fix kits frfr
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-12-20 21:58:44 +01:00
Chaoscaot 975b2bb8e6 Fix Kits and Maybe? Locale
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-12-20 21:35:34 +01:00
YoyoNow a1add4f997 Remove SchemElo and UserElo 2025-12-20 21:28:00 +01:00
YoyoNow b517fe3ad0 Remove SchemElo and UserElo 2025-12-20 21:26:42 +01:00
YoyoNow ac5dda58a1 Remove SchemElo and UserElo 2025-12-20 21:19:20 +01:00
YoyoNow 9efe625603 Pot fix for too many open files 2025-12-20 13:14:35 +01:00
YoyoNow 146ed598c8 Improve message on non ManualCheck submitted schematics 2025-12-20 12:20:36 +01:00
YoyoNow 50e86fbf89 Add 1.19 impl for SteamwarGameProfileRepository 2025-12-20 11:53:28 +01:00
YoyoNow 7216806a1c Fix SWPlayer on 1.15 2025-12-20 11:45:36 +01:00
YoyoNow 6cda79f7e1 Fix AbstractLinker 2025-12-20 11:41:36 +01:00
YoyoNow 87cc43a348 Fix FAWE stuff in 1.15 2025-12-20 11:35:08 +01:00
YoyoNow f9509c19d1 Trigger rebuild 2025-12-19 21:20:32 +01:00
YoyoNow e7bd5a9e74 Fix translation 2025-12-19 13:37:29 +01:00
YoyoNow aa74e0b887 Fix SQLWrapperImpl 2025-12-18 18:18:56 +01:00
Chaoscaot fe8d37c966 Maybe? Fix kits
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-12-17 23:29:00 +01:00
YoyoNow ccb79737db Fix Core.setServerName 2025-12-17 21:33:19 +01:00
Chaoscaot 1eea792e23 Synchronize team cache access to ensure thread safety
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-12-17 21:29:14 +01:00
YoyoNow 19c6ad0965 Merge pull request 'Add WorldIdentifier' (#249) from SpigotCore/WorldIdentifier into main
Reviewed-on: SteamWar/SteamWar#249
Reviewed-by: D4rkr34lm <dark@steamwar.de>
2025-12-17 21:28:41 +01:00
YoyoNow 2c5306bfd1 Merge pull request 'Add WaterDestroy feature with command and listener' (#248) from waterblocker into main
Reviewed-on: SteamWar/SteamWar#248
Reviewed-by: YoyoNow <yoyonow@noreply.localhost>
2025-12-17 21:13:45 +01:00
YoyoNow 1982b5e42c Update WaterDestroyCommand 2025-12-17 21:13:31 +01:00
Chaoscaot 3ad4081add Merge pull request 'Add AuditLog to Backend' (#244) from Backend/auditlog into main
Reviewed-on: SteamWar/SteamWar#244
Reviewed-by: YoyoNow <yoyonow@noreply.localhost>
2025-12-17 21:03:04 +01:00
Chaoscaot 33a7961979 Add WaterDestroy feature with command and listener
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-12-17 20:49:23 +01:00
YoyoNow 4e994813eb Add WorldIdentifier 2025-12-17 20:31:21 +01:00
YoyoNow 367a72141a Undo Improve TNTDistributor 2025-12-17 11:32:11 +01:00
YoyoNow c29788f1eb Improve TNTDistributor 2025-12-17 11:15:47 +01:00
YoyoNow 9d32a331ca Improve TNTDistributor 2025-12-16 14:50:56 +01:00
YoyoNow c55494aeba Revert WinconditionPercent for QuickGear 2025-12-16 14:15:04 +01:00
YoyoNow 61dcee6f8e Improve WinconditionPercent for QuickGear 2025-12-16 13:20:55 +01:00
Chaoscaot 8b2b7e011a Add WaterDestroy feature with command and listener
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-12-11 00:34:29 +01:00
Chaoscaot 09d2ed5384 Include permissions in /me route response
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-12-02 22:24:24 +01:00
Chaoscaot b576c36c2d Reapply "Improve Performance"
This reverts commit 0a7a753c48.

Signed-off-by: Chaoscaot <max@maxsp.de>

# Conflicts:
#	WebsiteBackend/src/de/steamwar/routes/AuditLog.kt
2025-12-02 21:59:34 +01:00
Chaoscaot a089d42d9a Improve Performance
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-12-02 21:34:35 +01:00
Chaoscaot 0a7a753c48 Revert "Improve Performance"
This reverts commit 86bfde90b8.
2025-12-02 21:14:00 +01:00
Chaoscaot 86bfde90b8 Improve Performance
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-12-02 21:13:43 +01:00
Chaoscaot 7c5a927d0f Add AuditLog to Backend
Signed-off-by: Chaoscaot <max@maxsp.de>
2025-12-01 18:36:36 +01:00
81 changed files with 1373 additions and 965 deletions
+2 -1
View File
@@ -20,4 +20,5 @@ lib
/WebsiteBackend/data
/WebsiteBackend/logs
/WebsiteBackend/skins
/WebsiteBackend/config.json
/WebsiteBackend/config.json
/WebsiteBackend/sessions
@@ -52,6 +52,9 @@ FLAG_ITEMS=Items
FLAG_NO_GRAVITY = No Gravity
FLAG_TESTBLOCK=Testblock
FLAG_CHANGED=Changed
FLAG_WATER_DESTROY=Water Destroy
FLAG_WATER_DESTROY_ALLOW=§coff
FLAG_WATER_DESTROY_DENY=§aon
FLAG_FIRE_ALLOW=§con
FLAG_FIRE_DENY=§aoff
FLAG_FREEZE_ACTIVE=§aon
@@ -140,6 +143,7 @@ BAU_INFO_ITEM_LORE_ITEMS=§7Items§8: §e{0}
BAU_INFO_ITEM_LORE_NO_GRAVITY = §7NoGravity§8: §e{0}
BAU_INFO_ITEM_LORE_TESTBLOCK=§7Testblock§8: §e{0}
BAU_INFO_ITEM_LORE_CHANGED=§7Changed§8: §e{0}
BAU_INFO_ITEM_LORE_WATER_DESTROY=§7Water Destroy§8: §e{0}
BAU_INFO_COMMAND_HELP=§8/§ebauinfo §8- §7Information regarding this build server
BAU_INFO_COMMAND_OWNER=§7Owner§8: §e{0}
BAU_INFO_COMMAND_MEMBER=§7{0} §8[§7{1}§8]§8: §e{2}
@@ -756,6 +760,9 @@ REGION_FIRE_DISABLED=§aFire damage activated in this region
REGION_FREEZE_HELP=§8/§efreeze §8- §7Toggle Freeze
REGION_FREEZE_ENABLED=§cRegion frozen
REGION_FREEZE_DISABLED=§aRegion thawed
REGION_WATER_HELP=§8/§ewaterdestroy §8- §7Toggle water damage
REGION_WATER_ENABLED=§aWater damage deactivated in this region
REGION_WATER_DISABLED=§cWater damage activated in this region
REGION_ITEMS_HELP=§8/§eitems §8- §7Toggle Items
REGION_ITEMS_ENABLED=§aItems enabled in this region
REGION_ITEMS_DISABLED=§cItems disabled in this region
@@ -54,6 +54,9 @@ FLAG_FREEZE_ACTIVE=§aan
FLAG_FREEZE_INACTIVE=§caus
FLAG_PROTECT_ACTIVE=§aan
FLAG_PROTECT_INACTIVE=§caus
FLAG_WATER_DESTROY=Wasserschaden
FLAG_WATER_DESTROY_ALLOW=§cerlaubt
FLAG_WATER_DESTROY_DENY=§aaus
FLAG_TNT_ALLOW=§aan
FLAG_TNT_DENY=§caus
FLAG_TNT_ONLY_TB=§7Kein §eBaurahmen
@@ -122,6 +125,7 @@ BAU_INFO_ITEM_NAME=§eBau-Management
BAU_INFO_ITEM_LORE_FIRE=§7Feuer§8: §e{0}
BAU_INFO_ITEM_LORE_COLOR=§7Farbe§8: §e{0}
BAU_INFO_ITEM_LORE_CHANGED=§7Verändert§8: §e{0}
BAU_INFO_ITEM_LORE_WATER_DESTROY=§7Wasserschaden§8: §e{0}
BAU_INFO_COMMAND_HELP=§8/§ebauinfo §8- §7Gibt Informationen über den Bau
BAU_INFO_COMMAND_OWNER=§7Besitzer§8: §e{0}
BAU_INFO_COMMAND_MEMBER=§7{0} §8[§7{1}§8]§8: §e{2}
@@ -704,6 +708,9 @@ REGION_PROTECT_FALSE_REGION=§cDu befindest dich derzeit in keiner (M)WG-Region
REGION_NO_GRAVITY_HELP = §8/§enogravity §8- §7Toggle NoGravity
REGION_NO_GRAVITY_ENABLED = §aNoGravity aktiviert in dieser Region
REGION_NO_GRAVITY_DISABLED = §cNoGravity deaktiviert in dieser Region
REGION_WATER_HELP=§8/§ewaterblock §8- §7Wasserschaden umschalten
REGION_WATER_ENABLED=§aWasserschaden deaktiviert
REGION_WATER_DISABLED=§cWasserschaden aktiviert
REGION_REGION_HELP_UNDO=§8/§eregion undo §8- §7Mache die letzten 20 /testblock oder /reset rückgängig
REGION_REGION_HELP_REDO=§8/§eregion redo §8- §7Wiederhole die letzten 20 §8/§7rg undo
REGION_REGION_HELP_RESTORE=§8/§eregion restore §8- §7Setzte die Region zurück, ohne das Gebaute zu löschen
@@ -40,10 +40,13 @@ import de.steamwar.bausystem.worlddata.WorldData;
import de.steamwar.command.AbstractValidator;
import de.steamwar.command.SWCommandUtils;
import de.steamwar.core.CRIUSleepEvent;
import de.steamwar.core.Core;
import de.steamwar.core.WorldEditRendererCUIEditor;
import de.steamwar.core.WorldIdentifier;
import de.steamwar.linkage.AbstractLinker;
import de.steamwar.linkage.SpigotLinker;
import de.steamwar.message.Message;
import de.steamwar.providers.BauServerInfo;
import lombok.Getter;
import org.bukkit.Bukkit;
import org.bukkit.GameRule;
@@ -121,6 +124,7 @@ public class BauSystem extends JavaPlugin implements Listener {
} catch (AbstractLinker.LinkException e) {
getLogger().log(Level.SEVERE, "Could not link a class.", e);
Bukkit.shutdown();
return;
}
TickListener.impl.init();
@@ -131,6 +135,9 @@ public class BauSystem extends JavaPlugin implements Listener {
new WorldEditRendererCUIEditor();
Bukkit.getWorlds().get(0).setGameRule(GameRule.SEND_COMMAND_FEEDBACK, false);
String identifier = BauServerInfo.getOwnerUser().getUUID().toString().replace("-", "");
WorldIdentifier.set("bau/" + Core.getVersion() + "/" + identifier);
}
@EventHandler
@@ -46,8 +46,8 @@ public class ObserverTracerListener implements Listener {
public ObserverTracerListener() {
Bukkit.getScheduler().runTaskTimer(BauSystem.getInstance(), () -> {
SWPlayer.allWithSingleComponent(ObserverTracer.class).forEach(pair -> {
if (pair.getKey().getGameMode() != GameMode.SPECTATOR) return;
pair.getValue().show();
if (pair.getPlayer().getGameMode() != GameMode.SPECTATOR) return;
pair.getComponent().show();
});
}, 15L, 15L);
}
@@ -64,7 +64,7 @@ public class ObserverTracerListener implements Listener {
createNew(event);
}
SWPlayer.allWithSingleComponent(ObserverTracer.class).forEach(pair -> {
pair.getValue().trace();
pair.getComponent().trace();
});
}, 1L);
} else {
@@ -0,0 +1,64 @@
/*
* This file is a part of the SteamWar software.
*
* 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
* 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.region;
import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.region.RegionUtils;
import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.region.flags.WaterDestroyMode;
import de.steamwar.command.SWCommand;
import de.steamwar.linkage.Linked;
import org.bukkit.entity.Player;
@Linked
public class WaterDestroyCommand extends SWCommand {
public WaterDestroyCommand() {
super("waterdestroy");
}
private String getEnableMessage(){
return "REGION_WATER_ENABLED";
}
private String getDisableMessage(){
return "REGION_WATER_DISABLED";
}
@Register(description = "REGION_WATER_HELP")
public void toggleCommand(@Validator Player p) {
Region region = Region.getRegion(p.getLocation());
if (toggle(region)) {
RegionUtils.actionBar(region, getEnableMessage());
} else {
RegionUtils.actionBar(region, getDisableMessage());
}
}
private boolean toggle(Region region) {
if (region.getRegionData().get(Flag.WATER_DESTROY).isWithDefault(WaterDestroyMode.DENY)) {
region.getRegionData().set(Flag.WATER_DESTROY, WaterDestroyMode.ALLOW);
return false;
} else {
region.getRegionData().set(Flag.WATER_DESTROY, WaterDestroyMode.DENY);
return true;
}
}
}
@@ -0,0 +1,57 @@
/*
* This file is a part of the SteamWar software.
*
* 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
* 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.region;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.region.flags.WaterDestroyMode;
import de.steamwar.bausystem.utils.ScoreboardElement;
import de.steamwar.linkage.Linked;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockFromToEvent;
@Linked
public class WaterDestroyListener implements Listener, ScoreboardElement {
@EventHandler
public void onBlockFromTo(BlockFromToEvent event) {
if (event.getBlock().getType() == Material.WATER && event.getToBlock().getType() != Material.AIR && Region.getRegion(event.getBlock().getLocation()).getRegionData().get(Flag.WATER_DESTROY).isWithDefault(WaterDestroyMode.DENY)) event.setCancelled(true);
}
@Override
public ScoreboardGroup getGroup() {
return ScoreboardGroup.REGION;
}
@Override
public int order() {
return 5;
}
@Override
public String get(Region region, Player p) {
if (region.getRegionData().get(Flag.WATER_DESTROY).isWithDefault(WaterDestroyMode.ALLOW)) return null;
return "§e" + BauSystem.MESSAGE.parse(Flag.WATER_DESTROY.getChatValue(), p) + "§8: " + BauSystem.MESSAGE.parse(region.getRegionData().get(Flag.WATER_DESTROY).getWithDefault().getChatValue(), p);
}
}
@@ -51,8 +51,8 @@ public class EventListener implements Listener {
Bukkit.getScheduler().runTaskTimer(BauSystem.getInstance(), () -> {
long millis = System.currentTimeMillis();
SWPlayer.allWithSingleComponent(ScriptRunner.ScriptData.class)
.filter(pair -> millis - pair.getValue().getLastF() > 200)
.forEach(pair -> pair.getValue().setLastF(Long.MAX_VALUE));
.filter(pair -> millis - pair.getComponent().getLastF() > 200)
.forEach(pair -> pair.getComponent().setLastF(Long.MAX_VALUE));
}, 1, 1);
}
@@ -45,8 +45,6 @@ public class TraceManager implements Listener {
instance = this;
}
public void init() {
if (!tracesFolder.exists())
tracesFolder.mkdir();
@@ -26,6 +26,7 @@ import com.sk89q.worldedit.extension.input.ParserContext;
import com.sk89q.worldedit.function.mask.Mask;
import de.steamwar.bausystem.features.worldedit.utils.FAWEMaskParser;
import de.steamwar.linkage.Linked;
import de.steamwar.linkage.MinVersion;
import de.steamwar.linkage.PluginCheck;
import javax.annotation.Nonnull;
@@ -33,6 +34,7 @@ import java.util.stream.Stream;
@Linked
@PluginCheck("FastAsyncWorldEdit")
@MinVersion(19)
public class FAWEAboveMaskParser extends FAWEMaskParser {
public FAWEAboveMaskParser() {
@@ -26,6 +26,7 @@ import com.sk89q.worldedit.extension.input.ParserContext;
import com.sk89q.worldedit.function.mask.Mask;
import de.steamwar.bausystem.features.worldedit.utils.FAWEMaskParser;
import de.steamwar.linkage.Linked;
import de.steamwar.linkage.MinVersion;
import de.steamwar.linkage.PluginCheck;
import javax.annotation.Nonnull;
@@ -33,6 +34,7 @@ import java.util.stream.Stream;
@Linked
@PluginCheck("FastAsyncWorldEdit")
@MinVersion(19)
public class FAWEBelowMaskParser extends FAWEMaskParser {
public FAWEBelowMaskParser() {
@@ -25,6 +25,7 @@ import com.sk89q.worldedit.extension.input.ParserContext;
import com.sk89q.worldedit.function.mask.Mask;
import de.steamwar.bausystem.features.worldedit.utils.FAWEMaskParser;
import de.steamwar.linkage.Linked;
import de.steamwar.linkage.MinVersion;
import de.steamwar.linkage.PluginCheck;
import javax.annotation.Nonnull;
@@ -32,6 +33,7 @@ import java.util.stream.Stream;
@Linked
@PluginCheck("FastAsyncWorldEdit")
@MinVersion(19)
public class FAWECheckerboard3DMaskParser extends FAWEMaskParser {
public FAWECheckerboard3DMaskParser() {
@@ -25,6 +25,7 @@ import com.sk89q.worldedit.extension.input.ParserContext;
import com.sk89q.worldedit.function.mask.Mask;
import de.steamwar.bausystem.features.worldedit.utils.FAWEMaskParser;
import de.steamwar.linkage.Linked;
import de.steamwar.linkage.MinVersion;
import de.steamwar.linkage.PluginCheck;
import javax.annotation.Nonnull;
@@ -32,6 +33,7 @@ import java.util.stream.Stream;
@Linked
@PluginCheck("FastAsyncWorldEdit")
@MinVersion(19)
public class FAWECheckerboardMaskParser extends FAWEMaskParser {
public FAWECheckerboardMaskParser() {
@@ -25,6 +25,7 @@ import com.sk89q.worldedit.extension.input.ParserContext;
import com.sk89q.worldedit.function.mask.Mask;
import de.steamwar.bausystem.features.worldedit.utils.FAWEMaskParser;
import de.steamwar.linkage.Linked;
import de.steamwar.linkage.MinVersion;
import de.steamwar.linkage.PluginCheck;
import javax.annotation.Nonnull;
@@ -32,6 +33,7 @@ import java.util.stream.Stream;
@Linked
@PluginCheck("FastAsyncWorldEdit")
@MinVersion(19)
public class FAWEGridMaskParser extends FAWEMaskParser {
public FAWEGridMaskParser() {
@@ -27,6 +27,7 @@ import com.sk89q.worldedit.regions.Region;
import de.steamwar.bausystem.features.worldedit.utils.FAWEPatternParser;
import de.steamwar.bausystem.utils.WorldEditUtils;
import de.steamwar.linkage.Linked;
import de.steamwar.linkage.MinVersion;
import de.steamwar.linkage.PluginCheck;
import org.bukkit.Axis;
@@ -35,6 +36,7 @@ import java.util.stream.Stream;
@Linked
@PluginCheck("FastAsyncWorldEdit")
@MinVersion(19)
public class FAWEGradientPatternParser extends FAWEPatternParser {
public FAWEGradientPatternParser() {
@@ -43,6 +43,7 @@ public final class Flag<T extends Enum<T> & Flag.Value<T>> implements EnumDispla
public static final Flag<NoGravityMode> NO_GRAVITY = new Flag<>("NO_GRAVITY", "FLAG_NO_GRAVITY", NoGravityMode.class, NoGravityMode.INACTIVE);
public static final Flag<TestblockMode> TESTBLOCK = new Flag<>("TESTBLOCK", "FLAG_TESTBLOCK", TestblockMode.class, TestblockMode.NO_VALUE);
public static final Flag<ChangedMode> CHANGED = new Flag<>("CHANGED", "FLAG_CHANGED", ChangedMode.class, ChangedMode.NO_CHANGE);
public static final Flag<WaterDestroyMode> WATER_DESTROY = new Flag<>("WATER_DESTROY", "FLAG_WATER_DESTROY", WaterDestroyMode.class, WaterDestroyMode.ALLOW);
private String name;
private int ordinal;
@@ -0,0 +1,56 @@
/*
* This file is a part of the SteamWar software.
*
* 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
* 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.region.flags;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum WaterDestroyMode implements Flag.Value<WaterDestroyMode> {
ALLOW("FLAG_WATER_DESTROY_ALLOW"),
DENY("FLAG_WATER_DESTROY_DENY");
private static WaterDestroyMode[] values;
private final String chatValue;
@Override
public WaterDestroyMode[] getValues() {
if (WaterDestroyMode.values == null) {
WaterDestroyMode.values = WaterDestroyMode.values();
}
return WaterDestroyMode.values;
}
@Override
public WaterDestroyMode getValue() {
return this;
}
@Override
public WaterDestroyMode getValueOf(final String name) {
try {
return WaterDestroyMode.valueOf(name.toUpperCase());
} catch (IllegalArgumentException e) {
return ALLOW;
}
}
}
@@ -34,7 +34,7 @@ public class FixedRegionData extends RegionData {
@Override
public @NonNull <T extends Enum<T> & Flag.Value<T>> RegionFlagPolicy has(@NonNull Flag<T> flag) {
if (flag.oneOf(Flag.COLOR, Flag.TNT, Flag.FIRE, Flag.FREEZE, Flag.PROTECT, Flag.NO_GRAVITY, Flag.CHANGED)) {
if (flag.oneOf(Flag.COLOR, Flag.TNT, Flag.FIRE, Flag.FREEZE, Flag.PROTECT, Flag.NO_GRAVITY, Flag.CHANGED, Flag.WATER_DESTROY)) {
return RegionFlagPolicy.WRITABLE;
}
if (flag.oneOf(Flag.ITEMS) && Core.getVersion() >= 20) {
@@ -56,7 +56,9 @@ public abstract class AbstractLinker<T> {
.map(s -> {
try {
return Class.forName(s, false, plugin.getClass().getClassLoader());
} catch (ClassNotFoundException | NoClassDefFoundError e) {
} catch (NoClassDefFoundError error) {
return null;
} catch (ClassNotFoundException e) {
throw new SecurityException(e.getMessage(), e);
}
})
@@ -33,8 +33,8 @@ object PersonalKitTable: CompositeIdTable("PersonalKit") {
val userId = reference("UserId", SteamwarUserTable)
val gamemode = varchar("Gamemode", 64).entityId()
val kitName = varchar("Name", 64).entityId()
val inventory = text("Inventory")
val armor = text("Armor")
val inventory = text("Inventory", eagerLoading = true)
val armor = text("Armor", eagerLoading = true)
val inUse = bool("InUse")
override val primaryKey = PrimaryKey(userId, gamemode, kitName)
@@ -48,14 +48,17 @@ class InternalKit(id: EntityID<CompositeID>): CompositeEntity(id) {
companion object: CompositeEntityClass<InternalKit>(PersonalKitTable) {
@JvmStatic
fun get(userId: Int, gamemode: String) = useDb {
find { PersonalKitTable.userId eq userId and (PersonalKitTable.gamemode eq gamemode) and (PersonalKitTable.inUse eq true) }
find { PersonalKitTable.userId eq userId and (PersonalKitTable.gamemode eq gamemode) }
.toList()
}
@JvmStatic
fun get(userId: Int, gamemode: String, kitName: String) = useDb {
find { PersonalKitTable.userId eq userId and (PersonalKitTable.gamemode eq gamemode) and (PersonalKitTable.kitName eq kitName) and (PersonalKitTable.inUse eq true) }
.firstOrNull()
findById(CompositeID {
it[PersonalKitTable.userId] = EntityID(userId, SteamwarUserTable)
it[PersonalKitTable.gamemode] = gamemode
it[PersonalKitTable.kitName] = kitName
})
}
@JvmStatic
@@ -67,10 +70,10 @@ class InternalKit(id: EntityID<CompositeID>): CompositeEntity(id) {
it[PersonalKitTable.kitName] = kitName
}
) {
this.inventory = rawInventory
this.armor = rawArmor
this.inUse = true
}
this.rawInventory = rawInventory
this.rawArmor = rawArmor
this.inUse = false
}.also { it.setDefault() }
}
@JvmStatic
@@ -107,6 +110,7 @@ class InternalKit(id: EntityID<CompositeID>): CompositeEntity(id) {
fun setDefault() = useDb {
find { PersonalKitTable.userId eq userID and (PersonalKitTable.gamemode eq gameMode) and (PersonalKitTable.inUse eq true) }
.filter { it.id.value != this@InternalKit.id.value }
.forEach { it.inUse = false }
inUse = true
}
@@ -1,74 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* 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
* 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.sql
import de.steamwar.sql.internal.useDb
import org.jetbrains.exposed.v1.core.and
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.dao.CompositeEntity
import org.jetbrains.exposed.v1.dao.CompositeEntityClass
import org.jetbrains.exposed.v1.jdbc.insertIgnore
object SchemEloTable: CompositeIdTable("SchemElo") {
val schemId = reference("SchemId", SchematicNodeTable)
val season = integer("Season").entityId()
val elo = integer("Elo")
override val primaryKey = PrimaryKey(schemId, season)
init {
addIdColumn(schemId)
}
}
class SchemElo(id: EntityID<CompositeID>): CompositeEntity(id) {
companion object: CompositeEntityClass<SchemElo>(SchemEloTable) {
@JvmStatic
fun getElo(node: SchematicNode, season: Int, defaultElo: Int = 0) = useDb {
find { (SchemEloTable.schemId eq node.id) and (SchemEloTable.season eq season) }.firstOrNull()?.elo ?: defaultElo
}
@JvmStatic
fun getCurrentElo(schemId: Int) = getElo(SchematicNode.byId(schemId)!!, Season.getSeason())
@JvmStatic
fun setElo(node: Int, elo: Int) = useDb {
findByIdAndUpdate(CompositeID {
it[SchemEloTable.schemId] = node
it[SchemEloTable.season] = Season.getSeason()
}) {
it.elo = elo
} ?: SchemEloTable.insertIgnore {
it[SchemEloTable.schemId] = node
it[SchemEloTable.season] = Season.getSeason()
it[SchemEloTable.elo] = elo
}
return@useDb
}
}
var node by SchemEloTable.schemId
var season by SchemEloTable.season
var elo by SchemEloTable.elo
}
@@ -116,7 +116,7 @@ class SchematicNode(id: EntityID<Int>) : IntEntity(id) {
@JvmStatic
fun schematicAccessibleForUser(user: SteamwarUser, schematicId: Int?) = fromSql(
"WITH RECURSIVE Nodes AS (SELECT NodeId, ParentId as ParentNode FROM NodeMember WHERE UserId = ? UNION SELECT NodeId, ParentNode FROM SchematicNode WHERE NodeOwner = ?), RSN AS ( SELECT NodeId, ParentNode FROM Nodes UNION SELECT SN.NodeId, SN.ParentNode FROM SchematicNode SN, RSN WHERE SN.ParentNode = RSN.NodeId ) SELECT SN.*, ? AS EffectiveOwner FROM RSN INNER JOIN SchematicNode SN ON RSN.NodeId = SN.NodeId WHERE NodeId = ?",
"WITH RECURSIVE Nodes AS (SELECT NodeId, ParentId as ParentNode FROM NodeMember WHERE UserId = ? UNION SELECT NodeId, ParentNode FROM SchematicNode WHERE NodeOwner = ?), RSN AS ( SELECT NodeId, ParentNode FROM Nodes UNION SELECT SN.NodeId, SN.ParentNode FROM SchematicNode SN, RSN WHERE SN.ParentNode = RSN.NodeId ) SELECT SN.*, ? AS EffectiveOwner FROM RSN INNER JOIN SchematicNode SN ON RSN.NodeId = SN.NodeId WHERE SN.NodeId = ?",
listOf(
IntegerColumnType() to user.getId(),
IntegerColumnType() to user.getId(),
@@ -365,8 +365,6 @@ class SchematicNode(id: EntityID<Int>) : IntEntity(id) {
}
}
fun getElo(season: Int) = SchemElo.getElo(this, season)
override fun delete() = useDb {
super.delete()
}
@@ -1,61 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* 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
* 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.sql;
import java.util.Calendar;
public class Season {
private Season() {}
public static int getSeason() {
Calendar calendar = Calendar.getInstance();
int yearIndex = calendar.get(Calendar.MONTH) / 4;
return (calendar.get(Calendar.YEAR) * 3 + yearIndex);
}
public static String getSeasonStart() {
Calendar calendar = Calendar.getInstance();
int month = calendar.get(Calendar.MONTH);
if (month <= 3) {
return calendar.get(Calendar.YEAR) + "-1-1";
} else if (month <= 7) {
return calendar.get(Calendar.YEAR) + "-5-1";
} else {
return calendar.get(Calendar.YEAR) + "-9-1";
}
}
public static String convertSeasonToString(int season){
if (season == -1) return "";
int yearSeason = season % 3;
int year = (season - yearSeason) / 3;
return String.format("%d-%d", year, yearSeason + 1);
}
public static int convertSeasonToNumber(String season){
if (season.isEmpty()) return -1;
String[] split = season.split("-");
try {
return Integer.parseInt(split[0]) * 3 + Integer.parseInt(split[1]) - 1;
} catch (NumberFormatException e) {
return -1;
}
}
}
@@ -170,7 +170,7 @@ class SteamwarUser(id: EntityID<Int>): IntEntity(id) {
fun isLeader() = leader
var locale: Locale by SteamwarUserTable.locale
.transform({ it.toLanguageTag() }, { it?.let { Locale.forLanguageTag(it) } ?: Locale.getDefault()})
.transform({ it.toLanguageTag() }, { it?.let { Locale.forLanguageTag(it) } ?: Locale.ENGLISH })
var manualLocale by SteamwarUserTable.manualLocale
var bedrock by SteamwarUserTable.bedrock
private var passwordInternal by SteamwarUserTable.password
+2 -2
View File
@@ -41,10 +41,10 @@ class Team(id: EntityID<Int>) : IntEntity(id) {
private val teamCache = mutableMapOf<Int, Team>()
@JvmStatic
fun clear() = teamCache.clear()
fun clear() = synchronized(teamCache) { teamCache.clear() }
@JvmStatic
fun byId(id: Int) = teamCache.computeIfAbsent(id) { useDb { Team[id] } }
fun byId(id: Int) = synchronized(teamCache) { teamCache.computeIfAbsent(id) { useDb { Team[id] } } }
@JvmStatic
fun get(name: String) = useDb { find { (TeamTable.name.lowerCase() eq name.lowercase() or (TeamTable.kuerzel.lowerCase() eq name.lowercase())) and not(TeamTable.deleted) }.firstOrNull() }
@@ -1,170 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* 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
* 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.sql
import de.steamwar.sql.internal.useDb
import org.jetbrains.exposed.v1.core.*
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.dao.CompositeEntity
import org.jetbrains.exposed.v1.dao.CompositeEntityClass
import org.jetbrains.exposed.v1.jdbc.insertIgnore
import org.jetbrains.exposed.v1.jdbc.select
import java.util.concurrent.ConcurrentHashMap
object UserEloTable : CompositeIdTable("UserElo") {
val season = integer("Season").entityId()
val gameMode = varchar("GameMode", 16).entityId()
val userId = reference("UserID", SteamwarUserTable)
val elo = integer("Elo")
override val primaryKey = PrimaryKey(season, gameMode, userId)
init {
addIdColumn(season)
addIdColumn(gameMode)
}
}
class UserElo(id: EntityID<CompositeID>) : CompositeEntity(id) {
companion object : CompositeEntityClass<UserElo>(UserEloTable) {
private const val ELO_DEFAULT = 0
private val gameModeUserEloCache: MutableMap<String, MutableMap<Int, Int?>> = ConcurrentHashMap()
private val emblemCache: MutableMap<Int, String> = ConcurrentHashMap()
@JvmStatic
fun clear() {
gameModeUserEloCache.clear()
emblemCache.clear()
}
@JvmStatic
fun getEloOrDefault(userId: Int, gameMode: String) =
getElo(userId, gameMode) ?: ELO_DEFAULT
@JvmStatic
fun getElo(userId: Int, gameMode: String) =
gameModeUserEloCache.getOrPut(gameMode) { mutableMapOf() }
.getOrPut(userId) { getEloFromDb(userId, gameMode)?.elo }
private fun getEloFromDb(userId: Int, gameMode: String) = useDb {
find { (UserEloTable.userId eq userId) and (UserEloTable.gameMode eq gameMode) and (UserEloTable.season eq Season.getSeason()) }.firstOrNull()
}
@JvmStatic
fun getFightsOfSeason(userId: Int, gamemode: String) = useDb {
exec(
"SELECT COUNT(*) AS Fights FROM FightPlayer INNER JOIN Fight F on FightPlayer.FightID = F.FightID WHERE UserID = ? AND GameMode = ? AND UNIX_TIMESTAMP(StartTime) + Duration >= UNIX_TIMESTAMP(?)",
args = listOf(
IntegerColumnType() to userId,
VarCharColumnType() to gamemode,
VarCharColumnType() to Season.getSeasonStart()
)
) {
return@exec if (it.next()) {
it.getInt("Fights")
} else {
0
}
} ?: 0
}
@JvmStatic
fun setElo(userId: Int, gameMode: String, elo: Int) {
emblemCache.remove(userId)
gameModeUserEloCache.getOrDefault(gameMode, mutableMapOf()).remove(userId)
useDb {
findByIdAndUpdate(CompositeID {
it[UserEloTable.userId] = userId
it[UserEloTable.gameMode] = gameMode
it[UserEloTable.season] = Season.getSeason()
}) {
it.elo = elo
} ?: UserEloTable.insertIgnore {
it[UserEloTable.userId] = userId
it[UserEloTable.gameMode] = gameMode
it[UserEloTable.season] = Season.getSeason()
it[UserEloTable.elo] = elo
}
}
}
@JvmStatic
fun getPlacement(elo: Int, gamemode: String) = useDb {
UserEloTable.select(UserEloTable.userId.count()).where {
(UserEloTable.gameMode eq gamemode) and (UserEloTable.elo greater elo) and (UserEloTable.season eq Season.getSeason())
}.firstOrNull()?.get(UserEloTable.userId.count())?.let { it + 1 }?.toInt() ?: -1
}
@JvmStatic
fun getEmblem(user: SteamwarUser, rankedModes: List<String>) =
emblemCache.getOrPut(user.id.value) {
var emblemProgression = -1
for (mode in rankedModes) {
if (getFightsOfSeason(user.id.value, mode) == 0) continue
val progression = getProgression(user.id.value, mode)
if (progression > emblemProgression) {
emblemProgression = progression
}
}
return toEmblem(emblemProgression)
}
@JvmStatic
fun getEmblemProgression(gameMode: String, userId: Int): String =
when (getProgression(userId, gameMode)) {
-1 -> "§8❱❱❱❱ ❂"
0 -> "§e❱§8❱❱❱ ❂"
1 -> "§e❱❱§8❱❱ ❂"
2 -> "§e❱❱❱§8❱ ❂"
3 -> "§e❱❱❱❱§8 ❂"
4 -> "§8❱❱❱❱ §5❂"
else -> throw SecurityException("Progression is not in range")
}
@JvmStatic
fun getProgression(userId: Int, gameMode: String) = useDb { getElo(userId, gameMode) ?: -1 }.let {
when {
it < 0 -> -1
it < 150 -> 0
it < 350 -> 1
it < 600 -> 2
it < 900 -> 3
else -> 4
}
}
@JvmStatic
fun toEmblem(progression: Int) = when (progression) {
-1 -> ""
0 -> "§e❱ "
1 -> "§e❱❱ "
2 -> "§e❱❱❱ "
3 -> "§e❱❱❱❱ "
4 -> "§5❂ "
else -> throw SecurityException("Progression out of range")
}
}
var elo by UserEloTable.elo
}
@@ -60,7 +60,7 @@ enum class UserPerm {
@JvmField
val prefixes = mapOf(
PREFIX_NONE to emptyPrefix,
PREFIX_YOUTUBER to Prefix("§7", "YT"),
PREFIX_YOUTUBER to Prefix("§x§8§A§2§B§E§5", "CC"), // 8A2BE5
PREFIX_GUIDE to Prefix("§x§e§7§6§2§e§d", "Guide"), // E762ED
PREFIX_SUPPORTER to Prefix("§x§6§0§9§5§F§B", "Sup"), // 6095FB
PREFIX_MODERATOR to Prefix("§x§F§F§A§2§5§C", "Mod"), // FFA25C
@@ -23,6 +23,7 @@ import org.intellij.lang.annotations.Language
import org.jetbrains.exposed.v1.core.ColumnType
import org.jetbrains.exposed.v1.core.Expression
import org.jetbrains.exposed.v1.core.ResultRow
import org.jetbrains.exposed.v1.core.StdOutSqlLogger
import org.jetbrains.exposed.v1.core.statements.StatementType
import org.jetbrains.exposed.v1.dao.IntEntity
import org.jetbrains.exposed.v1.dao.IntEntityClass
@@ -19,11 +19,27 @@
package de.steamwar.fightsystem.utils;
import io.papermc.paper.datacomponent.DataComponentType;
import io.papermc.paper.datacomponent.DataComponentTypes;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.HashSet;
import java.util.Set;
public class ReflectionWrapper21 implements ReflectionWrapper {
private static final Set<DataComponentType> FORBIDDEN_TYPES = new HashSet<>();
static {
FORBIDDEN_TYPES.add(DataComponentTypes.CUSTOM_NAME);
FORBIDDEN_TYPES.add(DataComponentTypes.PROFILE);
FORBIDDEN_TYPES.add(DataComponentTypes.UNBREAKABLE);
FORBIDDEN_TYPES.add(DataComponentTypes.BLOCK_DATA);
FORBIDDEN_TYPES.add(DataComponentTypes.BLOCKS_ATTACKS);
FORBIDDEN_TYPES.add(DataComponentTypes.BUNDLE_CONTENTS);
FORBIDDEN_TYPES.add(DataComponentTypes.CUSTOM_MODEL_DATA);
}
@Override
public Object explosionHider(Player player, Object packet, PacketHiderFunction packetHiderFunction) {
return packet;
@@ -31,6 +47,7 @@ public class ReflectionWrapper21 implements ReflectionWrapper {
@Override
public boolean hasItems(ItemStack stack) {
return stack.getDataTypes().stream().anyMatch(dataComponentType -> dataComponentType != DataComponentTypes.ENCHANTMENTS || dataComponentType != DataComponentTypes.DAMAGE);
FORBIDDEN_TYPES.forEach(stack::resetData);
return false;
}
}
@@ -44,7 +44,6 @@ REMOVE_HELP=§8/§eremove §8[§eplayer§8]
NOT_FIGHTLEADER=§cYou are not the fight leader
WIN_HELP=§8/§7win §8[§eteam §8or §etie§8]
INFO_RANKED=§7Ranked§8: §e{0}
INFO_LEADER=§7Leader {0}§8: {1}
INFO_SCHEMATIC=§7Schematic {0}§8: §e{1} §7from {2}, Rank: {3}
@@ -167,7 +166,6 @@ TPS_WARNING=§c{0} §7TPS
UI_PRE_RUNNING=§7Kits distributed
UI_RUNNING=§aFight started
UI_SKIP=§7Skipping to next event
UI_UNRANKED=§7Unranked match
UI_PLAYER_JOINS=§a§l» {0}{1}
UI_PLAYER_LEAVES=§c§l« {0}{1}
UI_LEADER_JOINS=§a§l» {0}Leader {1}
@@ -154,7 +154,6 @@ COMMAND_CURRENTLY_UNAVAILABLE=§cDieser Befehl ist zu diesem Kampfzeitpunkt nich
UI_PRE_RUNNING=§7Kits verteilt
UI_RUNNING=§aArena freigegeben
UI_SKIP=§7Sprung zum nächsten Ereignis
UI_UNRANKED=§7Ungewerteter Kampf
UI_LEADER_JOINS=§a§l» {0}Leader {1}
UI_PLAYER_DEATH={0}{1} §7ist gestorben
UI_PLAYER_LEAVE={0}{1} §7hat den Kampf verlassen
@@ -37,7 +37,6 @@ public class DummyAI extends AI {
public DummyAI(FightTeam team) {
super(team, SteamwarUser.get("public"));
FightStatistics.unrank();
getEntity().setInvulnerable(true);
}
@@ -182,7 +182,7 @@ public class GUI {
}
Kit prototype = Kit.getAvailableKits(Fight.getFightPlayer(p).isLeader()).get(0);
PersonalKit kit = PersonalKit.create(user.getId(), Config.GameModeConfig.Schematic.Type.toDB(), s, prototype.getInventory(), prototype.getArmor());
PersonalKitCreator.openKitCreator(p, kit);
Bukkit.getScheduler().runTask(FightSystem.getPlugin(), () -> PersonalKitCreator.openKitCreator(p, kit));
});
anvilInv.open();
});
@@ -51,7 +51,6 @@ public class InfoCommand implements CommandExecutor {
if(!SteamwarUser.get(player.getUniqueId()).hasPerm(UserPerm.CHECK))
return false;
FightSystem.getMessage().send("INFO_RANKED", player, !FightStatistics.isUnranked());
for(FightTeam team : Fight.teams()) {
if(!team.isLeaderless())
FightSystem.getMessage().send("INFO_LEADER", player, team.getColoredName(), team.getLeader().getEntity().getName());
@@ -1,51 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* 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
* 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.commands;
import de.steamwar.fightsystem.ArenaMode;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentCommand;
import de.steamwar.fightsystem.utils.FightStatistics;
import de.steamwar.linkage.Linked;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
@Linked
public class UnrankCommand implements CommandExecutor {
public UnrankCommand () {
new StateDependentCommand(ArenaMode.VariableTeams, FightState.Setup, "unrank", this);
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if(!(sender instanceof Player))
return false;
Player player = (Player) sender;
if(Commands.checkGetLeader(player) == null)
return false;
FightStatistics.unrank();
return false;
}
}
@@ -32,10 +32,10 @@ public class TNTDistributor {
public TNTDistributor() {
new StateDependentTask(Winconditions.TNT_DISTRIBUTION, FightState.Running, () -> Fight.teams().forEach(team -> team.getPlayers().forEach(fp -> {
if(!fp.isLiving())
if (!fp.isLiving())
return;
fp.ifPlayer(player -> player.getInventory().addItem(new ItemStack(Material.TNT, 20)));
})), 300, 300);
fp.ifPlayer(player -> player.getInventory().addItem(new ItemStack(Material.TNT, 1)));
})), 20, 20);
}
}
@@ -393,7 +393,6 @@ public class FightTeam {
public void pasteSchem(SchematicNode schematic){
if(schematic.getSchemtype().check()) {
FightStatistics.unrank();
FightSystem.getMessage().broadcast("SCHEMATIC_UNCHECKED", getColoredName());
}
@@ -34,6 +34,7 @@ import org.bukkit.event.block.Action;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.PlayerInventory;
import java.util.HashSet;
import java.util.Set;
@@ -62,6 +63,7 @@ public class HotbarKitListener implements Listener {
public void onInventoryClick(InventoryClickEvent event) {
int slot = event.getSlot();
if (slot < 0 || slot >= HotbarKit.HOTBAR_SIZE) return;
if (!(event.getClickedInventory() instanceof PlayerInventory)) return;
Player player = (Player) event.getWhoClicked();
click(player, slot, event);
@@ -117,7 +117,7 @@ public class Kit {
if(kit.isList("Armor"))
armor = Objects.requireNonNull(kit.getList("Armor")).toArray(new ItemStack[0]);
else
armor = null;
armor = new ItemStack[]{ null, null, null, null};
leaderAllowed = kit.getBoolean("LeaderAllowed");
memberAllowed = kit.getBoolean("MemberAllowed");
if(kit.isList("Effects"))
@@ -261,7 +261,7 @@ public class Kit {
player.getInventory().setContents(inventory);
if(armor != null)
player.getInventory().setArmorContents(armor);
player.updateInventory();
player.updateInventory(); //TODO issue in 1.21.6?
if(effects != null)
player.addPotionEffects(effects);
}
@@ -35,6 +35,7 @@ import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.block.Block;
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;
@@ -180,6 +181,7 @@ public class Permanent implements Listener {
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onExplosion(EntityExplodeEvent e) {
if (!(e.getEntity() instanceof TNTPrimed)) return;
e.blockList().removeIf(block -> {
if(block.getType() == Material.TNT) {
return false;
@@ -32,20 +32,16 @@ import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.OneShotStateDependent;
import de.steamwar.fightsystem.winconditions.Wincondition;
import de.steamwar.linkage.Linked;
import de.steamwar.network.NetworkSender;
import de.steamwar.network.packets.common.FightEndsPacket;
import de.steamwar.sql.EventFight;
import de.steamwar.sql.EventRelation;
import de.steamwar.sql.SchematicNode;
import de.steamwar.sql.SteamwarUser;
import lombok.Getter;
import org.bukkit.Bukkit;
import java.nio.file.Files;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.logging.Level;
import java.util.stream.Collectors;
import static de.steamwar.sql.Fight.create;
import static de.steamwar.sql.Fight.markReplayAvailable;
@@ -53,14 +49,6 @@ import static de.steamwar.sql.Fight.markReplayAvailable;
@Linked
public class FightStatistics {
@Getter
private static boolean unranked = false;
public static void unrank() {
unranked = true;
FightUI.addSubtitle("UI_UNRANKED");
}
private Timestamp starttime = Timestamp.from(Instant.now());
public FightStatistics() {
@@ -145,12 +133,6 @@ public class FightStatistics {
} catch (Exception e) {
Bukkit.getLogger().log(Level.SEVERE, "Failed to save statistics", e);
}
if (!Bukkit.getOnlinePlayers().isEmpty() && !unranked) {
NetworkSender.send(new FightEndsPacket((byte) win, blueSchem == null ? 0 : blueSchem, redSchem == null ? 0 : redSchem, Fight.getBlueTeam().getPlayers().stream().map(FightPlayer::getUser).map(SteamwarUser::getId).collect(Collectors.toList()), Fight.getRedTeam().getPlayers().stream().map(FightPlayer::getUser).map(SteamwarUser::getId).collect(Collectors.toList()), gameMode, (int)(endTime.getEpochSecond() - starttime.toInstant().getEpochSecond())));
}
unranked = false;
}
private int getLeader(FightTeam team) {
@@ -91,7 +91,7 @@ public abstract class WinconditionBasePercent extends Wincondition implements Pr
@EventHandler
public void onEntityExplode(EntityExplodeEvent event) {
if (
event.getEntityType() == EntityType.FIREBALL ||
event.getEntityType() != EntityType.PRIMED_TNT ||
!team.getExtendRegion().inRegion(event.getEntity().getLocation()) ||
(!Config.GameModeConfig.WinConditionParams.PercentEntern && !Config.GameModeConfig.EnterStages.isEmpty() && Config.GameModeConfig.EnterStages.get(0) >= Wincondition.getTimeOverCountdown().getTimeLeft())
) {
+10
View File
@@ -81,4 +81,14 @@ tasks.register<FightServer>("SpaceCraftDev20") {
template = "SpaceCraft20"
worldName = "arenas/AS_Horizon"
config = "SpaceCraftDev20.yml"
}
tasks.register<FightServer>("QuickGear20") {
group = "run"
description = "Run a QuickGear 1.20 Fight Server"
dependsOn(":SpigotCore:shadowJar")
dependsOn(":FightSystem:shadowJar")
template = "QuickGear20"
worldName = "arenas/WarGearPark"
config = "QuickGear20.yml"
}
+1 -1
View File
@@ -1 +1 @@
# SteamWar
# SteamWar
@@ -17,7 +17,7 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
PREFIX=§eSchematic§8» §7
PREFIX=§eSchematic§8»§7
ON=§aon
OFF=§coff
CHANGE=§7To change
@@ -59,7 +59,6 @@ UTIL_INFO_TYPE_DIR=§eDIR
UTIL_INFO_RANK=§7Rank: §e{0}
UTIL_INFO_COLOR=§7Color translation: {0}
UTIL_INFO_REPLAY=§7Replay playback: {0}
UTIL_INFO_ELO=§7Elo: §e{0}
UTIL_INFO_FORMAT=§7Format: §e{0}
UTIL_INFO_STATUS=§cState: §c{0}: {1}
UTIL_INFO_MEMBER=§7Members: §e{0}
@@ -105,6 +104,7 @@ UTIL_SUBMIT_COLOR_ON=§aReplace pink to team color
UTIL_SUBMIT_COLOR_OFF=§cDo not replace pink
UTIL_SUBMIT_DIRECT=§eSubmit directly
UTIL_SUBMIT_DIRECT_DONE=§aThe Schematic will be reviewed in a timely manner
UTIL_SUBMIT_DIRECT_PLAYABLE=§aYou can now use this Schematic in the arena! Good luck and have fun.
UTIL_SUBMIT_EXTEND=§eExtend Schematic
UTIL_SUBMIT_EXTEND_DONE=§aThe preparation server is starting
UTIL_CHECK_TYPE_NOT_FOUND=§cThe type {0} was not found
@@ -88,6 +88,7 @@ UTIL_SUBMIT_COLOR_ON=§aPink zu Teamfarbe ersetzen
UTIL_SUBMIT_COLOR_OFF=§cPink nicht ersetzen
UTIL_SUBMIT_DIRECT=§eDirekt einsenden
UTIL_SUBMIT_DIRECT_DONE=§aDie Schematic wird zeitnah überprüft
UTIL_SUBMIT_DIRECT_PLAYABLE=§aDu kannst die Schematic jetzt in der Arena verwenden! Viel Glück und viel Spaß.
UTIL_SUBMIT_EXTEND=§eSchematic ausfahren
UTIL_SUBMIT_EXTEND_DONE=§aDer Vorbereitungsserver wird gestartet
UTIL_INFO_ACTION_REVISIONS_HOVER=§eVersionen anzeigen
@@ -248,7 +248,6 @@ public class SchematicCommandUtils {
if (node.getSchemtype().fightType()) {
SchematicSystem.MESSAGE.sendPrefixless("UTIL_INFO_COLOR", player, SchematicSystem.MESSAGE.parse(node.replaceColor() ? "ON" : "OFF", player));
SchematicSystem.MESSAGE.sendPrefixless("UTIL_INFO_REPLAY", player, SchematicSystem.MESSAGE.parse(node.allowReplay() ? "ON" : "OFF", player));
SchematicSystem.MESSAGE.sendPrefixless("UTIL_INFO_ELO", player, node.getElo(Season.getSeason()));
}
SchematicSystem.MESSAGE.sendPrefixless("UTIL_INFO_FORMAT", player, node.getFileEnding());
@@ -482,7 +481,7 @@ public class SchematicCommandUtils {
SchematicSystem.MESSAGE.send("UTIL_TYPE_EXTEND", player);
} else {
node.setSchemtype(type.checkType());
SchematicSystem.MESSAGE.send("UTIL_SUBMIT_DIRECT_DONE", player);
SchematicSystem.MESSAGE.send(type.getManualCheck() ? "UTIL_SUBMIT_DIRECT_DONE" : "UTIL_SUBMIT_DIRECT_PLAYABLE", player);
}
}
}
@@ -499,7 +498,7 @@ public class SchematicCommandUtils {
});
inv.setItem(7, SWItem.getDye(7), (byte) 7, SchematicSystem.MESSAGE.parse("UTIL_SUBMIT_DIRECT", player), click -> {
node.setSchemtype(type.checkType());
SchematicSystem.MESSAGE.send("UTIL_SUBMIT_DIRECT_DONE", player);
SchematicSystem.MESSAGE.send(type.getManualCheck() ? "UTIL_SUBMIT_DIRECT_DONE" : "UTIL_SUBMIT_DIRECT_PLAYABLE", player);
player.closeInventory();
});
inv.setItem(8, SWItem.getDye(10), (byte) 10, SchematicSystem.MESSAGE.parse("UTIL_SUBMIT_EXTEND", player), click -> {
@@ -0,0 +1,355 @@
/*
* 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.core
import com.sk89q.jnbt.ByteArrayTag
import com.sk89q.jnbt.ByteTag
import com.sk89q.jnbt.CompoundTag
import com.sk89q.jnbt.DoubleTag
import com.sk89q.jnbt.EndTag
import com.sk89q.jnbt.FloatTag
import com.sk89q.jnbt.IntArrayTag
import com.sk89q.jnbt.IntTag
import com.sk89q.jnbt.ListTag
import com.sk89q.jnbt.LongArrayTag
import com.sk89q.jnbt.LongTag
import com.sk89q.jnbt.ShortTag
import com.sk89q.jnbt.StringTag
import com.sk89q.jnbt.Tag
import com.sk89q.worldedit.WorldEdit
import com.sk89q.worldedit.WorldEditException
import com.sk89q.worldedit.extension.input.ParserContext
import com.sk89q.worldedit.extension.platform.Capability
import com.sk89q.worldedit.extension.platform.Platform
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard
import com.sk89q.worldedit.math.BlockVector3
import com.sk89q.worldedit.regions.CuboidRegion
import com.sk89q.worldedit.world.DataFixer
import com.sk89q.worldedit.world.block.BaseBlock
import com.sk89q.worldedit.world.block.BlockState
import com.sk89q.worldedit.world.block.BlockTypes
import java.io.DataInputStream
import java.io.IOException
import kotlin.collections.map
import kotlin.collections.set
class ChaosSchematicReader(
val stream: DataInputStream,
val wrapper: WorldEditWrapper14
) {
init {
println("ChaosSchematicReader init")
}
/**
* In reinen Unit-Tests (ohne Bukkit/WorldEdit-Plugin-Lifecycle) ist keine WorldEdit-Platform registriert.
* Daher darf der Reader beim Laden nicht hart daran scheitern.
*/
private val platform: Platform? = runCatching {
WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.WORLD_EDITING)
}.getOrNull()
private val liveDataVersion: Int? = platform?.dataVersion
private var wrapped = false
var schematicVersion: Int? = null
var dataVersion: Int? = null
var metadata: Map<String, Any>? = null
var width: Short? = null
var height: Short? = null
var length: Short? = null
var offset: IntArray? = null
val blocks: Blocks = Blocks(null, null, null)
val entities = mutableListOf<Any>()
var fixer: DataFixer? = null
fun read(): BlockArrayClipboard {
require(stream.readByte() == 0x0A.toByte())
val tagName = stream.readUTF()
if (tagName != "Schematic") {
wrapped = true
stream.readByte()
stream.readUTF()
}
while (stream.readByte() != 0.toByte()) {
val name = stream.readUTF()
when (name) {
"Version" -> schematicVersion = stream.readInt()
"DataVersion" -> {
dataVersion = stream.readInt()
val targetVersion = liveDataVersion
if (targetVersion != null && dataVersion!! < targetVersion) {
fixer = platform?.dataFixer
}
}
"Metadata" -> metadata = readCompoundTag()
"Width" -> width = stream.readShort()
"Height" -> height = stream.readShort()
"Length" -> length = stream.readShort()
"Blocks" -> {
while (stream.readByte() != 0.toByte()) {
val n = stream.readUTF()
when (n) {
"Palette" -> blocks.palette = readCompoundTag() as Map<String, Int>
"Data" -> blocks.data = readTag(7.toByte()) as ByteArray
"BlockEntities" -> blocks.blockEntities = readTagCompoundList()
}
}
}
"PaletteMax" -> stream.readInt()
"Palette" -> blocks.palette = readCompoundTag() as Map<String, Int>
"BlockEntities" -> blocks.blockEntities = readTagCompoundList()
"BlockData" -> blocks.data = readTag(7.toByte()) as ByteArray
"Offset" -> offset = readTag(11.toByte()) as IntArray
"Entities" -> entities.addAll(readTagCompoundList() as List<Any>)
"BiomePaletteMax" -> stream.readInt()
"BiomePalette" -> readCompoundTag()
"BiomeData" -> readTag(7.toByte())
"Biomes" -> {
while (stream.readByte() != 0.toByte()) {
val n = stream.readUTF()
when (n) {
"Palette" -> readCompoundTag()
"Data" -> readTag(7.toByte()) as ByteArray
}
}
}
else -> {
println(this)
error("Unknown tag $name")
}
}
}
if (wrapped) {
stream.readByte()
}
val parserContext = ParserContext()
parserContext.setRestricted(false)
parserContext.setTryLegacy(false)
parserContext.setPreferringWildcard(false)
val dv = dataVersion
val blockStates = blocks.palette!!.map {
val fixed = if (fixer != null && dv != null) {
fixer!!.fixUp(DataFixer.FixTypes.BLOCK_STATE, it.key, dv)
} else {
it.key
}
val blockstate = try {
WorldEdit.getInstance().blockFactory.parseFromInput(fixed, parserContext).toImmutableState()
} catch (e: Exception) {
BlockTypes.AIR!!.defaultState
}
it.value to blockstate
}.toMap()
val tileEntities = blocks.blockEntities!!.map {
val entity = it as? Map<String, Any?> ?: error("Invalid entity")
val pos = entity["Pos"] as? IntArray ?: error("Invalid pos")
val x = pos[0]
val y = pos[1]
val z = pos[2]
val data = mutableMapOf<String, Any?>()
data.putAll(entity)
if (entity.containsKey("Data")) {
//data.putAll(entity["Data"] as Map<String, Any?>)
}
val fixed = if (fixer != null) {
wrapper.applyDataFixer(fixer!!, dataVersion!!, (wrapNbt(data) as CompoundTag).value)
} else (wrapNbt(data) as CompoundTag).value
BlockVector3.at(x, y, z) to fixed
}.toMap()
val offset = BlockVector3.at(offset!![0], offset!![1], offset!![2])
val region = CuboidRegion(offset, offset.add(width!! - 1, height!! - 1, length!! - 1))
val clipboard = BlockArrayClipboard(region)
clipboard.setOrigin(offset)
var index = 0
var i = 0
var value: Int
var varintLength: Int
while (i < blocks.data!!.size) {
value = 0
varintLength = 0
while (true) {
value = value or ((blocks.data!![i].toInt() and 127) shl (varintLength++ * 7))
if (varintLength > 5) {
throw IOException("VarInt too big (probably corrupted data)")
}
if ((blocks.data!![i].toInt() and 128) != 128) {
i++
break
}
i++
}
// index = (y * length * width) + (z * width) + x
val y = index / (width!! * length!!)
val z = (index % (width!! * length!!)) / width!!
val x = (index % (width!! * length!!)) % width!!
val state: BlockState = blockStates[value] ?: error("Unknown block state $value")
val pt = BlockVector3.at(x, y, z)
try {
if (tileEntities.containsKey(pt)) {
clipboard.setBlock<BaseBlock?>(
clipboard.minimumPoint.add(pt),
state.toBaseBlock(CompoundTag(tileEntities[pt])
))
} else {
clipboard.setBlock<BlockState?>(clipboard.minimumPoint.add(pt), state)
}
} catch (e: WorldEditException) {
throw IOException("Failed to load a block in the schematic")
}
index++
}
return clipboard
}
private fun readCompoundTag(): Map<String, Any> {
var tagId = 0.toByte()
val map = mutableMapOf<String, Any>()
while (stream.readByte().also { tagId = it } != 0.toByte()) {
val name = stream.readUTF()
map[name] = readTag(tagId)!!
}
return map
}
private fun readTagCompoundList(): List<Any?> {
val typeId = stream.readByte()
val length = stream.readInt()
if (typeId == 0.toByte()) {
if (length != 0) {
throw IOException("Invalid TAG_List: typeId=TAG_End but length=$length")
}
return emptyList()
}
if (typeId != 10.toByte()) {
throw IOException("Invalid TAG_List element type for (Block)Entities: expected TAG_Compound(10) but got $typeId")
}
val list = ArrayList<Any?>(length)
repeat(length) { list.add(readCompoundTag()) }
return list
}
private fun readTag(tagId: Byte): Any? = when (tagId.toInt()) {
0 -> null
1 -> stream.readByte()
2 -> stream.readShort()
3 -> stream.readInt()
4 -> stream.readLong()
5 -> stream.readFloat()
6 -> stream.readDouble()
7 -> {
val length = stream.readInt()
val ba = ByteArray(length)
stream.readFully(ba)
ba
}
8 -> stream.readUTF()
9 -> {
val typeId = stream.readByte()
val length = stream.readInt()
if (typeId == 0.toByte() && length != 0) {
throw IOException("Invalid TAG_List: typeId=TAG_End but length=$length")
}
val list = mutableListOf<Any?>()
repeat(length) { list.add(readTag(typeId)) }
list
}
10 -> readCompoundTag()
11 -> {
val length = stream.readInt()
IntArray(length) { stream.readInt() }
}
12 -> {
val length = stream.readInt()
LongArray(length) { stream.readLong() }
}
else -> error("Unknown tag type $tagId")
}
private fun wrapNbt(data: Any): Tag = when (data) {
is Byte -> ByteTag(data)
is Short -> ShortTag(data)
is Int -> IntTag(data)
is Long -> LongTag(data)
is Float -> FloatTag(data)
is Double -> DoubleTag(data)
is String -> StringTag(data)
is ByteArray -> ByteArrayTag(data)
is IntArray -> IntArrayTag(data)
is LongArray -> LongArrayTag(data)
is Map<*, *> -> CompoundTag(data.map { it.key as String to wrapNbt(it.value!!) }.toMap())
is List<*> -> ListTag(data.firstOrNull()?.let { nbtClass(it) } ?: EndTag::class.java, data.map { wrapNbt(it!!) })
else -> error("Unknown tag type $data")
}
private fun nbtClass(data: Any): Class<out Tag> = when (data) {
is Byte -> ByteTag::class.java
is Short -> ShortTag::class.java
is Int -> IntTag::class.java
is Long -> LongTag::class.java
is Float -> FloatTag::class.java
is Double -> DoubleTag::class.java
is String -> StringTag::class.java
is ByteArray -> ByteArrayTag::class.java
is IntArray -> IntArrayTag::class.java
is LongArray -> LongArrayTag::class.java
is Map<*, *> -> CompoundTag::class.java
is List<*> -> ListTag::class.java
else -> error("Unknown tag type $data, ${data::class.java.simpleName}")
}
override fun toString(): String {
return "ChaosSchematicReader(platform=$platform, liveDataVersion=$liveDataVersion, wrapped=$wrapped, schematicVersion=$schematicVersion, dataVersion=$dataVersion, metadata=$metadata, width=$width, height=$height, length=$length, offset=${offset.contentToString()}, blocks=$blocks, entities=$entities, fixer=$fixer)"
}
data class Blocks(
var data: ByteArray?,
var palette: Map<String, Int>?,
var blockEntities: List<Any?>?
)
}
@@ -89,7 +89,7 @@ public class WorldEditWrapper14 implements WorldEditWrapper {
switch (schemFormat) {
case SPONGE_V2:
case SPONGE_V3:
return new SpongeSchematicReader(new NBTInputStream(is), this).read();
return new ChaosSchematicReader(new DataInputStream(is), this).read();
case MCEDIT:
return new MCEditSchematicReader(new NBTInputStream(is)).read();
default:
@@ -576,8 +576,6 @@ public class WorldEditWrapper14 implements WorldEditWrapper {
values.putIfAbsent("z", new IntTag(pt.getBlockZ()));
}
values.putIfAbsent("id", values.get("Id"));
values.remove("Id");
values.remove("Pos");
if (fixer != null) {
tileEntity = wrapper.applyDataFixer(fixer, dataVersion, values);
} else {
@@ -22,6 +22,7 @@ plugins {
}
dependencies {
compileOnly(project(":CommonCore", "default"))
compileOnly(project(":SpigotCore:SpigotCore_Main", "default"))
compileOnly(project(":SpigotCore:SpigotCore_14", "default"))
compileOnly(project(":SpigotCore:SpigotCore_18", "default"))
@@ -0,0 +1,68 @@
/*
* This file is a part of the SteamWar software.
*
* 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
* 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.core;
import com.comphenix.tinyprotocol.TinyProtocol;
import de.steamwar.Reflection;
import net.minecraft.network.protocol.game.PacketPlayOutLogin;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.World;
public class WorldIdentifier19 implements WorldIdentifier.IWorldIdentifier {
private static ResourceKey<World> resourceKey = null;
private static final Class<?> resourceKeyClass = Reflection.getClass("net.minecraft.resources.ResourceKey");
private static final Class<?> minecraftKeyClass = Reflection.getClass("net.minecraft.resources.MinecraftKey");
private static final Reflection.Constructor resourceKeyConstructor = Reflection.getConstructor(resourceKeyClass, minecraftKeyClass, minecraftKeyClass);
private static final Reflection.Constructor minecraftKeyConstructor = Reflection.getConstructor(minecraftKeyClass, String.class, String.class);
@Override
public void setResourceKey(String name) {
resourceKey = (ResourceKey<World>) resourceKeyConstructor.invoke(minecraftKeyConstructor.invoke("minecraft", "dimension"), minecraftKeyConstructor.invoke("steamwar", name));
}
public WorldIdentifier19() {
TinyProtocol.instance.addFilter(PacketPlayOutLogin.class, (player, o) -> {
if (resourceKey == null) return o;
PacketPlayOutLogin packet = (PacketPlayOutLogin) o;
return new PacketPlayOutLogin(
packet.b(),
packet.c(),
packet.d(),
packet.e(),
packet.f(),
packet.g(),
packet.h(),
resourceKey,
packet.j(),
packet.k(),
packet.l(),
packet.m(),
packet.n(),
packet.o(),
packet.p(),
packet.q(),
packet.r()
);
});
}
}
@@ -0,0 +1,68 @@
/*
* This file is a part of the SteamWar software.
*
* 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
* 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.core.authlib;
import com.mojang.authlib.Agent;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.GameProfileRepository;
import com.mojang.authlib.ProfileLookupCallback;
import de.steamwar.Reflection;
import de.steamwar.sql.SteamwarUser;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.Services;
import java.util.ArrayList;
import java.util.List;
public class SteamwarGameProfileRepository19 extends SteamwarGameProfileRepository {
private static final GameProfileRepository fallback;
private static final Reflection.Field<Services> field;
private static final Services current;
static {
Class<?> clazz = MinecraftServer.getServer().getClass();
field = Reflection.getField(clazz, Services.class, 0);
current = field.get(MinecraftServer.getServer());
fallback = current.c();
}
@Override
public void inject() {
Services newServices = new Services(current.a(), current.b(), this, current.d(), current.paperConfigurations());
field.set(MinecraftServer.getServer(), newServices);
}
@Override
public void findProfilesByNames(String[] strings, Agent agent, ProfileLookupCallback profileLookupCallback) {
List<String> unknownNames = new ArrayList<>();
for (String name:strings) {
SteamwarUser user = SteamwarUser.get(name);
if(user == null) {
unknownNames.add(name);
continue;
}
profileLookupCallback.onProfileLookupSucceeded(new GameProfile(user.getUUID(), user.getUserName()));
}
if(!unknownNames.isEmpty()) {
fallback.findProfilesByNames(unknownNames.toArray(new String[0]), agent, profileLookupCallback);
}
}
}
@@ -0,0 +1,70 @@
/*
* This file is a part of the SteamWar software.
*
* 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
* 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.core;
import com.comphenix.tinyprotocol.TinyProtocol;
import de.steamwar.Reflection;
import net.minecraft.network.protocol.game.PacketPlayOutLogin;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.World;
public class WorldIdentifier20 implements WorldIdentifier.IWorldIdentifier {
private static ResourceKey<World> resourceKey = null;
private static final Reflection.Field<Integer> playerId = Reflection.getField(PacketPlayOutLogin.class, int.class, 0);
private static final Class<?> resourceKeyClass = Reflection.getClass("net.minecraft.resources.ResourceKey");
private static final Class<?> minecraftKeyClass = Reflection.getClass("net.minecraft.resources.MinecraftKey");
private static final Reflection.Constructor resourceKeyConstructor = Reflection.getConstructor(resourceKeyClass, minecraftKeyClass, minecraftKeyClass);
private static final Reflection.Constructor minecraftKeyConstructor = Reflection.getConstructor(minecraftKeyClass, String.class, String.class);
@Override
public void setResourceKey(String name) {
resourceKey = (ResourceKey<World>) resourceKeyConstructor.invoke(minecraftKeyConstructor.invoke("minecraft", "dimension"), minecraftKeyConstructor.invoke("steamwar", name));
}
public WorldIdentifier20() {
TinyProtocol.instance.addFilter(PacketPlayOutLogin.class, (player, o) -> {
if (resourceKey == null) return o;
PacketPlayOutLogin packet = (PacketPlayOutLogin) o;
return new PacketPlayOutLogin(
playerId.get(packet),
packet.c(),
packet.d(),
packet.e(),
packet.f(),
packet.g(),
packet.h(),
resourceKey,
packet.j(),
packet.k(),
packet.l(),
packet.m(),
packet.n(),
packet.o(),
packet.p(),
packet.q(),
packet.r(),
packet.s()
);
});
}
}
@@ -20,29 +20,32 @@
package de.steamwar.core;
import com.sk89q.jnbt.NBTInputStream;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.extent.clipboard.io.BuiltInClipboardFormat;
import com.sk89q.worldedit.extent.clipboard.io.ClipboardWriter;
import com.sk89q.worldedit.extent.clipboard.io.MCEditSchematicReader;
import com.sk89q.worldedit.extent.clipboard.io.sponge.SpongeSchematicV2Reader;
import com.sk89q.worldedit.extent.clipboard.io.sponge.SpongeSchematicV3Reader;
import com.sk89q.worldedit.math.Vector3;
import com.sk89q.worldedit.math.transform.Transform;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.session.ClipboardHolder;
import com.sk89q.worldedit.world.DataFixer;
import de.steamwar.sql.NodeData;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;
import org.enginehub.linbus.stream.LinBinaryIO;
import org.enginehub.linbus.tree.LinCompoundTag;
import org.enginehub.linbus.tree.LinTag;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class WorldEditWrapper21 implements WorldEditWrapper {
public class WorldEditWrapper21 extends WorldEditWrapper18 {
@Override
public InputStream getPlayerClipboard(Player player) {
@@ -72,22 +75,10 @@ public class WorldEditWrapper21 implements WorldEditWrapper {
@Override
@SuppressWarnings("removal")
public Clipboard getClipboard(InputStream is, NodeData.SchematicFormat ignored) throws IOException {
ResetableInputStream ris = new ResetableInputStream(is);
for (NodeData.SchematicFormat schemFormat : NodeData.SchematicFormat.values()) {
try {
Clipboard clipboard = switch (schemFormat) {
case MCEDIT -> new MCEditSchematicReader(new NBTInputStream(ris)).read();
case SPONGE_V2 -> new SpongeSchematicV2Reader(LinBinaryIO.read(new DataInputStream(ris))).read();
case SPONGE_V3 -> new SpongeSchematicV3Reader(LinBinaryIO.read(new DataInputStream(ris))).read();
};
ris.close();
return clipboard;
} catch (Exception e) {
// Ignore
}
ris.reset();
}
throw new IOException("No clipboard found");
return switch (ignored) {
case MCEDIT -> new MCEditSchematicReader(new NBTInputStream(is)).read();
case SPONGE_V2, SPONGE_V3 -> new ChaosSchematicReader(new DataInputStream(is), this).read();
};
}
private class ResetableInputStream extends InputStream {
@@ -0,0 +1,73 @@
/*
* This file is a part of the SteamWar software.
*
* 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
* 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.core;
import com.comphenix.tinyprotocol.TinyProtocol;
import de.steamwar.Reflection;
import net.minecraft.network.protocol.game.ClientboundLoginPacket;
import net.minecraft.network.protocol.game.CommonPlayerSpawnInfo;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.Level;
public class WorldIdentifier21 implements WorldIdentifier.IWorldIdentifier {
private static ResourceKey<Level> resourceKey = null;
private static final Class<?> resourceKeyClass = Reflection.getClass("net.minecraft.resources.ResourceKey");
private static final Class<?> minecraftKeyClass = Reflection.getClass("net.minecraft.resources.MinecraftKey");
private static final Reflection.Constructor resourceKeyConstructor = Reflection.getConstructor(resourceKeyClass, minecraftKeyClass, minecraftKeyClass);
private static final Reflection.Constructor minecraftKeyConstructor = Reflection.getConstructor(minecraftKeyClass, String.class, String.class);
@Override
public void setResourceKey(String name) {
resourceKey = (ResourceKey<Level>) resourceKeyConstructor.invoke(minecraftKeyConstructor.invoke("minecraft", "dimension"), minecraftKeyConstructor.invoke("steamwar", name));
}
public WorldIdentifier21() {
TinyProtocol.instance.addFilter(ClientboundLoginPacket.class, (player, o) -> {
if (resourceKey == null) return o;
ClientboundLoginPacket packet = (ClientboundLoginPacket) o;
return new ClientboundLoginPacket(packet.playerId(),
packet.hardcore(),
packet.levels(),
packet.maxPlayers(),
packet.chunkRadius(),
packet.simulationDistance(),
packet.reducedDebugInfo(),
packet.showDeathScreen(),
packet.doLimitedCrafting(),
new CommonPlayerSpawnInfo(
packet.commonPlayerSpawnInfo().dimensionType(),
resourceKey,
packet.commonPlayerSpawnInfo().seed(),
packet.commonPlayerSpawnInfo().gameType(),
packet.commonPlayerSpawnInfo().previousGameType(),
packet.commonPlayerSpawnInfo().isDebug(),
packet.commonPlayerSpawnInfo().isFlat(),
packet.commonPlayerSpawnInfo().lastDeathLocation(),
packet.commonPlayerSpawnInfo().portalCooldown(),
packet.commonPlayerSpawnInfo().seaLevel()
),
packet.enforcesSecureChat()
);
});
}
}
@@ -0,0 +1,27 @@
/*
* This file is a part of the SteamWar software.
*
* 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
* 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.core;
public class WorldIdentifier8 implements WorldIdentifier.IWorldIdentifier {
@Override
public void setResourceKey(String name) {
}
}
@@ -66,7 +66,7 @@ public class Core extends JavaPlugin {
private static String serverName = "";
public static void setServerName(String serverName) {
if (serverName.isEmpty()) {
if (Core.serverName.isEmpty()) {
Core.serverName = serverName;
}
}
@@ -20,6 +20,7 @@
package de.steamwar.core;
import de.steamwar.message.Message;
import lombok.Data;
import lombok.NonNull;
import lombok.experimental.Delegate;
import net.md_5.bungee.api.ChatMessageType;
@@ -91,10 +92,16 @@ public class SWPlayer {
return players.values().stream();
}
public static <T extends Component> @NonNull Stream<Pair<SWPlayer, T>> allWithSingleComponent(Class<T> component) {
public static <T extends Component> @NonNull Stream<SWPlayerWithComponent<T>> allWithSingleComponent(Class<T> component) {
return players.values().stream()
.filter(player -> player.components.containsKey(component))
.map(player -> Pair.of(player, (T) player.components.get(component)));
.map(player -> new SWPlayerWithComponent<>(player, (T) player.components.get(component)));
}
@Data
public static final class SWPlayerWithComponent<T extends Component> {
private final SWPlayer player;
private final T component;
}
@Delegate
@@ -0,0 +1,33 @@
/*
* This file is a part of the SteamWar software.
*
* 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
* 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.core;
public class WorldIdentifier {
private static final IWorldIdentifier impl = VersionDependent.getVersionImpl(Core.getInstance());
public static void set(String name) {
impl.setResourceKey(name);
}
protected interface IWorldIdentifier {
void setResourceKey(String name);
}
}
@@ -55,7 +55,7 @@ public class PersonalKit {
public ItemStack[] getArmor(){
YamlConfiguration config = YamlConfiguration.loadConfiguration(new StringReader(getRawArmor()));
return Objects.requireNonNull(config.getList("Armor")).toArray(new ItemStack[0]);
return Objects.requireNonNull(config.getList("Armor")).toArray(new ItemStack[4]);
}
public void setInUse() {
@@ -28,9 +28,9 @@ import org.bukkit.entity.Player;
import java.io.File;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class SQLWrapperImpl implements SQLWrapper<Material> {
@@ -46,11 +46,12 @@ public class SQLWrapperImpl implements SQLWrapper<Material> {
@Override
public List<Material> getMaterialWithGreaterBlastResistance(double maxBlastResistance) {
Stream<Material> stream = Arrays.stream(Material.values());
if (Core.getVersion() > 12) {
stream = stream.filter(material -> !material.isLegacy());
if (Core.getVersion() <= 12) {
return Collections.emptyList();
}
return stream.filter(Material::isBlock)
return Arrays.stream(Material.values())
.filter(material -> !material.isLegacy())
.filter(Material::isBlock)
.filter(material -> material.getBlastResistance() > maxBlastResistance)
.collect(Collectors.toList());
}
@@ -656,14 +656,6 @@ HOURS_PLAYED=§7Your playtime is§8: §e{0}h
#Arena command
ARENA_NOT_FOUND=§cThe specified arena could not be found
#Rank
RANK_PLAYER_NOT_FOUND=§cPlayer not found
RANK_PLAYER_SELF=§eRank §7Season §e{0}
RANK_PLAYER_FOUND=§eRank §7of §e{0} §7Season §e{1}
RANK_DATA={0} §e{1} {2}
RANK_UNPLACED=§7unranked
RANK_PLACED=§e{0}§8. §7with §e{1} §7Elo§8.
#Fabric Mod Sender
MODIFICATION_BAN_MESSAGE=You tried to bypass / modify the FabricModSender!
MODIFICATION_BAN_LOG={0} has tried to edit / bypass the FabricModSender! Reason: {1}
@@ -624,14 +624,6 @@ HOURS_PLAYED=§7Deine Spielzeit beträgt§8: §e{0}h
#Arena command
ARENA_NOT_FOUND=§cDie angegebene Arena konnte nicht gefunden werden
#Rank
RANK_PLAYER_NOT_FOUND=§cSpieler nicht gefunden
RANK_PLAYER_SELF=§eRang §7Saison §e{0}
RANK_PLAYER_FOUND=§eRang §7von §e{0} §7Saison §e{1}
RANK_DATA={0} §e{1} {2}
RANK_UNPLACED=§7unplatziert
RANK_PLACED=§e{0}§8. §7mit §e{1} §7Elo§8.
#Fabric Mod Sender
MODIFICATION_BAN_MESSAGE=Du hast probiert den FabricModSender zu umgehen / zu modifizieren!
MODIFICATION_BAN_LOG={0} hat probiert den Fabric Mod Sender zu editieren / umzugehen! Grund: {1}
@@ -38,7 +38,6 @@ import de.steamwar.persistent.ReloadablePlugin;
import de.steamwar.sql.Punishment;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.Team;
import de.steamwar.sql.UserElo;
import de.steamwar.sql.internal.Statement;
import de.steamwar.velocitycore.commands.PunishmentCommand;
import de.steamwar.velocitycore.commands.ServerSwitchCommand;
@@ -184,7 +183,6 @@ public class VelocityCore implements ReloadablePlugin {
schedule(() -> {
SteamwarUser.clear();
UserElo.clear();
Team.clear();
}).repeat(1, TimeUnit.HOURS).schedule();
@@ -1,68 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* 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
* 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.commands;
import de.steamwar.command.SWCommand;
import de.steamwar.linkage.Linked;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.Message;
import de.steamwar.sql.GameModeConfig;
import de.steamwar.sql.Season;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.UserElo;
import de.steamwar.velocitycore.ArenaMode;
@Linked
public class RankCommand extends SWCommand {
public RankCommand() {
super("rank");
}
@Register
public void ownRank(Chatter sender) {
rank(sender, sender.user());
}
@Register
public void rank(Chatter sender, @ErrorMessage("RANK_PLAYER_NOT_FOUND") SteamwarUser user) {
if (!sender.user().equals(user)) {
sender.prefixless("RANK_PLAYER_FOUND", user, Season.convertSeasonToString(Season.getSeason()));
} else {
sender.prefixless("RANK_PLAYER_SELF", Season.convertSeasonToString(Season.getSeason()));
}
for(GameModeConfig<String, String> mode : ArenaMode.getAllModes()) {
if (!mode.Server.Ranked)
continue;
Integer elo = UserElo.getElo(user.getId(), mode.getSchemTypeOrInternalName());
Message eloMsg;
if (elo != null) {
int placement = UserElo.getPlacement(elo, mode.getSchemTypeOrInternalName());
eloMsg = new Message("RANK_PLACED", placement, elo);
} else {
eloMsg = new Message("RANK_UNPLACED");
}
sender.prefixless("RANK_DATA", UserElo.getEmblemProgression(mode.getChatName(), user.getId()), mode.getChatName(), eloMsg);
}
}
}
@@ -26,19 +26,23 @@ import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle;
@AllArgsConstructor
public enum DiscordTicketType {
REPORT("U+1F46E", "Spieler melden", ButtonStyle.DANGER),
IDEA("U+1F4A1", "Feature vorschlagen", ButtonStyle.SUCCESS),
BUG("U+1F41B", "Bug melden", ButtonStyle.SECONDARY),
QUESTION("U+2753", "Fragen", ButtonStyle.PRIMARY),
APPEAL("U+1F528", "Entbannungsantrag", ButtonStyle.SECONDARY),
SCHEMATIC("U+1F4BE", "Schematic melden", ButtonStyle.DANGER);
REPORT("U+1F46E", "Spieler melden", ButtonStyle.DANGER, null),
IDEA("U+1F4A1", "Feature vorschlagen", ButtonStyle.SUCCESS, null),
BUG("U+1F41B", "Bug melden", ButtonStyle.LINK, "https://git.steamwar.de/SteamWar/SteamWar/issues/new"),
QUESTION("U+2753", "Fragen", ButtonStyle.PRIMARY, null),
APPEAL("U+1F528", "Entbannungsantrag", ButtonStyle.SECONDARY, null),
SCHEMATIC("U+1F4BE", "Schematic melden", ButtonStyle.DANGER, null);
private final String emoji;
private final String label;
private final ButtonStyle style;
private final String url;
public Button toButton() {
if(style == ButtonStyle.LINK) {
return Button.link(url, label).withEmoji(Emoji.fromUnicode(emoji));
}
return Button.of(style, name().toLowerCase(), Emoji.fromUnicode(emoji)).withLabel(label);
}
@@ -27,6 +27,7 @@ import net.dv8tion.jda.api.events.interaction.component.GenericComponentInteract
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
import net.dv8tion.jda.api.utils.messages.MessageEditData;
import java.util.Collections;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -62,10 +63,17 @@ public class StaticMessageChannel extends DiscordChannel {
}
private void init() {
if(getChannel().getLatestMessageIdLong() != 0)
message = getChannel().getIterableHistory().complete().stream().filter(m -> m.getAuthor().isBot()).findFirst().orElse(null);
VelocityCore.schedule(this::update);
if (getChannel().getLatestMessageIdLong() != 0) {
message = getChannel().getIterableHistory()
.onErrorMap(throwable -> Collections.emptyList())
.deadline(System.currentTimeMillis() + 5000)
.complete()
.stream()
.filter(m -> m.getAuthor().isBot())
.findFirst()
.orElse(null);
}
VelocityCore.schedule(this::update).schedule();
}
public void update() {
@@ -248,7 +248,7 @@ public class ChatListener extends BasicListener {
msgReceiver == null ? receiver : msgReceiver,
highlightMentions(message, chatColorCode, receiver),
sender.getTeam() == 0 ? "" : "§" + Team.byId(sender.getTeam()).getTeamColor() + Team.byId(sender.getTeam()).getTeamKuerzel() + " ",
UserElo.getEmblem(sender, rankedModes),
"",
prefix.getColorCode(),
prefix.getChatPrefix().length() == 0 ? "§f" : prefix.getChatPrefix() + " ",
chatColorCode);
@@ -1,261 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* 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
* 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.network.handlers;
import com.velocitypowered.api.proxy.Player;
import de.steamwar.sql.GameModeConfig;
import de.steamwar.linkage.Linked;
import de.steamwar.messages.Chatter;
import de.steamwar.network.packets.PacketHandler;
import de.steamwar.network.packets.common.FightEndsPacket;
import de.steamwar.sql.SchematicType;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.UserElo;
import de.steamwar.velocitycore.ArenaMode;
import de.steamwar.velocitycore.VelocityCore;
import lombok.RequiredArgsConstructor;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.title.Title;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.IntFunction;
import java.util.stream.Collectors;
@Linked
public class EloPlayerHandler extends PacketHandler {
private static final int MEDIAN_ELO_GAIN = 40;
private static final int MEDIAN_ELO_LOSE = 20;
private static final long REMATCH_LIFETIME = (long) 60 * 60 * 1000;
private final Map<String, LinkedList<Game>> gameModeGames = new HashMap<>();
/**
* {@link FightEndsPacket#getWin()} == 1 -> Blue won
* {@link FightEndsPacket#getWin()} == 2 -> Red won
*/
@Handler
public void handle(FightEndsPacket fightEndsPacket) {
SchematicType schematicType = SchematicType.fromDB(fightEndsPacket.getGameMode());
GameModeConfig<String, String> arenaMode;
if (schematicType == null) {
arenaMode = ArenaMode.getByInternal(fightEndsPacket.getGameMode());
} else {
arenaMode = ArenaMode.getBySchemType(schematicType);
}
if (arenaMode == null) return;
if (!arenaMode.Server.Ranked) return;
if (EloSchemHandler.publicVsPrivate(fightEndsPacket))
return;
// Die nächsten Zeilen filtern ein Fight innerhalb eines Teams nicht gewertet wird, bzw auch wenn nur Teile beider Teams im
// gleichen Team sind dieser ungewertet ist.
Set<Integer> teamsIds = fightEndsPacket.getBluePlayers().stream().map(SteamwarUser::byId).map(SteamwarUser::getTeam).collect(Collectors.toSet());
for (int redPlayer : fightEndsPacket.getRedPlayers()) {
int team = SteamwarUser.byId(redPlayer).getTeam();
if (team != 0 && teamsIds.contains(team)) {
return;
}
}
// Get sizes of both teams
int blueTeamSize = fightEndsPacket.getBluePlayers().size();
int redTeamSize = fightEndsPacket.getRedPlayers().size();
// Calculate the favored team
double bluePlayerFactor = 1 / (1 + Math.exp(0 - (fightEndsPacket.getWin() == 2 ? (double) redTeamSize / blueTeamSize : (double) blueTeamSize / redTeamSize) * 3 + 3)) * 2;
double redPlayerFactor = 1 / (1 + Math.exp(0 - (fightEndsPacket.getWin() == 1 ? (double) blueTeamSize / redTeamSize : (double) redTeamSize / blueTeamSize) * 3 + 3)) * 2;
// Calculate the time factor
double timeFactor = getTimeFactor(fightEndsPacket.getDuration());
// Get total elo of both teams
int blueTeamElo = fightEndsPacket.getBluePlayers().stream().mapToInt(player -> UserElo.getEloOrDefault(player, fightEndsPacket.getGameMode())).sum();
int redTeamElo = fightEndsPacket.getRedPlayers().stream().mapToInt(player -> UserElo.getEloOrDefault(player, fightEndsPacket.getGameMode())).sum();
// Adaptive elo bonus
int blueTeamEloBonus = 0;
int redTeamEloBonus = 0;
if (Math.abs(blueTeamElo / (double) fightEndsPacket.getBluePlayers().size() - redTeamElo / (double) fightEndsPacket.getRedPlayers().size()) > 400) {
int outlivedDuration = Math.max(fightEndsPacket.getDuration() - 60, 0);
if (fightEndsPacket.getWin() == 1 && blueTeamElo < redTeamElo) {
blueTeamEloBonus = outlivedDuration / 20;
redTeamEloBonus = -(blueTeamEloBonus / 2);
} else if (fightEndsPacket.getWin() == 2 && redTeamElo < blueTeamElo) {
redTeamEloBonus = outlivedDuration / 20;
blueTeamEloBonus = -(redTeamEloBonus / 2);
} else if (fightEndsPacket.getWin() == 0) {
if (redTeamElo < blueTeamElo) {
blueTeamEloBonus = outlivedDuration / 20;
redTeamEloBonus = -(blueTeamEloBonus / 2);
} else if (blueTeamElo < redTeamElo) {
redTeamEloBonus = outlivedDuration / 20;
blueTeamEloBonus = -(redTeamEloBonus / 2);
}
}
}
// Calculate the elo factor
double blueEloFactor = ((fightEndsPacket.getWin() == 1 ? 1 : 0) - 1 / (1 + Math.pow(10, (redTeamElo - blueTeamElo) / 600.0))) * (1 / (1 + Math.exp(-Math.abs(blueTeamElo - redTeamElo) / 1200.0))) * 4;
double redEloFactor = blueEloFactor * -1;
// Calculate favoured team on draw
if (fightEndsPacket.getWin() == 0) {
if (bluePlayerFactor > 1) {
blueEloFactor = Math.abs(blueEloFactor) * -1;
redEloFactor = Math.abs(redEloFactor);
} else if (bluePlayerFactor < 1) {
blueEloFactor = Math.abs(blueEloFactor);
redEloFactor = Math.abs(redEloFactor) * -1;
} else {
if (Math.abs(blueEloFactor) > 1) {
// Do nothing
} else if (Math.abs(blueEloFactor) < 1) {
blueEloFactor = -blueEloFactor;
redEloFactor = -blueEloFactor;
} else {
blueEloFactor = 0;
redEloFactor = 0;
}
}
}
// Calculate the rematch factor
double rematchFactor = getRematchFactor(fightEndsPacket);
// Calculate the win factor
double blueWinFactor = (fightEndsPacket.getWin() == 1 ? 1 : 0.7);
double redWinFactor = (fightEndsPacket.getWin() == 2 ? 1 : 0.7);
// Calculate division factor
double divisionFactor = 1D / Math.max(blueTeamSize, redTeamSize);
double blueFactor = bluePlayerFactor * timeFactor * blueEloFactor * rematchFactor * blueWinFactor * divisionFactor;
double redFactor = redPlayerFactor * timeFactor * redEloFactor * rematchFactor * redWinFactor * divisionFactor;
// Calculate the elo gain for each player
int blueEloGain = (int) Math.round((blueFactor < 0 ? MEDIAN_ELO_LOSE : MEDIAN_ELO_GAIN) * blueFactor) + blueTeamEloBonus;
int redEloGain = (int) Math.round((redFactor < 0 ? MEDIAN_ELO_LOSE : MEDIAN_ELO_GAIN) * redFactor) + redTeamEloBonus;
// BungeeCore.get().getLogger().info("Blue: " + fightEndsPacket.getBluePlayers() + " " + blueTeamSize + " " + bluePlayerFactor + " " + timeFactor + " " + blueEloFactor + " " + rematchFactor + " " + blueWinFactor + " " + divisionFactor + " " + blueEloGain);
// BungeeCore.get().getLogger().info("Red: " + fightEndsPacket.getRedPlayers() + " " + redTeamSize + " " + redPlayerFactor + " " + timeFactor + " " + redEloFactor + " " + rematchFactor + " " + redWinFactor + " " + divisionFactor + " " + redEloGain);
update(fightEndsPacket.getBluePlayers(), fightEndsPacket.getGameMode(), blueEloGain);
update(fightEndsPacket.getRedPlayers(), fightEndsPacket.getGameMode(), redEloGain);
}
private void update(List<Integer> players, String gameMode, int eloGain) {
for (int player : players) {
// BungeeCore.get().getLogger().log(Level.INFO, "Player: " + player + " Elo: " + UserElo.getEloOrDefault(player, gameMode) + " Factor: " + factor);
int playerElo = UserElo.getEloOrDefault(player, gameMode);
playerElo += eloGain;
if (playerElo < 0) playerElo = 0;
int oldProgression = UserElo.getProgression(player, gameMode);
UserElo.setElo(player, gameMode, playerElo);
int newProgression = UserElo.getProgression(player, gameMode);
animate(player(player), UserElo.toEmblem(oldProgression).trim(), UserElo.toEmblem(newProgression).trim(), (oldProgression < newProgression) ? "§a" : "§c", eloGain);
}
}
private void animate(Player player, String oldEmblem, String newEmblem, String arrowColor, int eloGain) {
if (player == null)
return;
IntFunction<String> getRankup = getEmblemTransition(oldEmblem, newEmblem, arrowColor);
String color = ((eloGain > 0) ? "§a+" : (eloGain == 0 ? "§7" : "§c"));
double eloStep = eloGain / 40.0;
for (int i = 0; i < 40; i++) {
Component eloGainComponent = Chatter.SERIALIZER.deserialize(color + (int) (eloStep * (i + 1)));
int finalI = i;
VelocityCore.schedule(() -> player.showTitle(Title.title(
Chatter.SERIALIZER.deserialize(getRankup.apply(finalI)),
eloGainComponent,
Title.Times.times(Duration.ofMillis(finalI == 0 ? 250 : 0), Duration.ofSeconds(2), Duration.ofMillis(finalI == 39 ? 250 : 0))
))).delay(i * 50L, TimeUnit.MILLISECONDS).schedule();
}
}
private static IntFunction<String> getEmblemTransition(String oldEmblem, String newEmblem, String arrowColor) {
String finalOldEmblem = (oldEmblem.isEmpty() ? "/" : oldEmblem).trim();
String finalNewEmblem = (newEmblem.isEmpty() ? "/" : newEmblem).trim();
return i -> {
if (oldEmblem.equals(newEmblem)) return "§8" + finalOldEmblem;
if (i < 8) return "§8" + finalOldEmblem;
if (i < 16) return "§8" + finalOldEmblem + arrowColor + " >";
if (i < 24) return "§8" + finalOldEmblem + arrowColor + " >>";
if (i < 32) return "§8" + finalOldEmblem + arrowColor + " >>>";
return "§8" + finalOldEmblem + arrowColor + " >>> §8" + finalNewEmblem;
};
}
private Player player(int userId) {
return VelocityCore.getProxy().getPlayer(SteamwarUser.byId(userId).getUUID()).orElse(null);
}
private double getTimeFactor(int duration) {
if (duration <= 10) {
return 0.5;
}
if (duration <= 60) {
return 0.8;
}
return 1.0;
}
private double getRematchFactor(FightEndsPacket fightEndsPacket) {
gameModeGames.computeIfAbsent(fightEndsPacket.getGameMode(), s -> new LinkedList<>()).add(new Game(fightEndsPacket.getBluePlayers(), fightEndsPacket.getRedPlayers()));
LinkedList<Game> games = gameModeGames.get(fightEndsPacket.getGameMode());
while (!games.isEmpty()) {
Game game = games.getFirst();
if (game.livedMillis() > REMATCH_LIFETIME) {
games.removeFirst();
} else {
break;
}
}
long rematchCount = games.stream().filter(game -> game.isSame(fightEndsPacket.getBluePlayers(), fightEndsPacket.getRedPlayers())).count();
return 1.0 / rematchCount;
}
@RequiredArgsConstructor
private static class Game {
private long time = System.currentTimeMillis();
private final List<Integer> bluePlayers;
private final List<Integer> redPlayers;
public long livedMillis() {
return System.currentTimeMillis() - time;
}
public boolean isSame(List<Integer> bluePlayers, List<Integer> redPlayers) {
return bluePlayers.containsAll(this.bluePlayers) && redPlayers.containsAll(this.redPlayers);
}
}
}
@@ -1,82 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* 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
* 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.network.handlers;
import de.steamwar.sql.GameModeConfig;
import de.steamwar.linkage.Linked;
import de.steamwar.network.packets.PacketHandler;
import de.steamwar.network.packets.common.FightEndsPacket;
import de.steamwar.sql.SchemElo;
import de.steamwar.sql.SchematicNode;
import de.steamwar.sql.SchematicType;
import de.steamwar.velocitycore.ArenaMode;
@Linked
public class EloSchemHandler extends PacketHandler {
private static final int K = 20;
public static boolean publicVsPrivate(FightEndsPacket packet) {
if (packet.getRedSchem() == -1 && packet.getBlueSchem() == -1) {
return false;
}
SchematicNode blueSchem = SchematicNode.getSchematicNode(packet.getBlueSchem());
SchematicNode redSchem = SchematicNode.getSchematicNode(packet.getRedSchem());
return (blueSchem.getOwner() == 0) != (redSchem.getOwner() == 0);
}
@Handler
public void handle(FightEndsPacket fightEndsPacket) {
SchematicType type = SchematicType.fromDB(fightEndsPacket.getGameMode());
if (type == null) return;
GameModeConfig<String, String> arenaMode = ArenaMode.getBySchemType(type);
if (!arenaMode.Server.Ranked) return;
if (publicVsPrivate(fightEndsPacket))
return;
calcSchemElo(fightEndsPacket);
}
private void calcSchemElo(FightEndsPacket fightEndsPacket) {
double blueResult;
if (fightEndsPacket.getWin() == 0) {
blueResult = 0.5;
} else if (fightEndsPacket.getWin() == 1) {
blueResult = 1;
} else {
blueResult = 0;
}
int blueSchemElo = SchemElo.getCurrentElo(fightEndsPacket.getBlueSchem());
int redSchemElo = SchemElo.getCurrentElo(fightEndsPacket.getRedSchem());
calcSchemElo(redSchemElo, blueSchemElo, fightEndsPacket.getRedSchem(), blueResult);
calcSchemElo(blueSchemElo, redSchemElo, fightEndsPacket.getBlueSchem(), 1 - blueResult);
}
private void calcSchemElo(int eloSchemOwn, int eloSchemEnemy, int schemId, double result) {
double winSchemExpectation = calcWinExpectation(eloSchemOwn, eloSchemEnemy);
SchemElo.setElo(schemId, (int) Math.round(eloSchemOwn + K * (result - winSchemExpectation)));
}
private double calcWinExpectation(int eloOwn, int eloEnemy) {
return 1 / (1 + Math.pow(10, (eloEnemy - eloOwn) / 600f));
}
}
@@ -20,12 +20,9 @@
package de.steamwar
import de.steamwar.plugins.configurePlugins
import de.steamwar.routes.ResponseUser
import de.steamwar.routes.SchematicCode
import io.ktor.server.application.*
import io.ktor.server.engine.*
import de.steamwar.routes.configureRoutes
import de.steamwar.sql.SchematicType
import de.steamwar.sql.SteamwarUser
import io.ktor.server.netty.*
import kotlinx.serialization.ExperimentalSerializationApi
@@ -47,7 +44,6 @@ fun main() {
Thread {
while (true) {
Thread.sleep(1000 * 10)
ResponseUser.clearCache()
SteamwarUser.clear()
}
}.start()
@@ -0,0 +1,215 @@
/*
* This file is a part of the SteamWar software.
*
* 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
* 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.routes
import de.steamwar.plugins.SWPermissionCheck
import de.steamwar.sql.AuditLog
import de.steamwar.sql.AuditLogTable
import de.steamwar.sql.SteamwarUserTable
import de.steamwar.sql.UserPerm
import de.steamwar.sql.internal.useDb
import io.ktor.server.application.call
import io.ktor.server.application.install
import io.ktor.server.response.respond
import io.ktor.server.routing.Route
import io.ktor.server.routing.get
import io.ktor.server.routing.route
import kotlinx.serialization.Serializable
import org.jetbrains.exposed.v1.core.Alias
import org.jetbrains.exposed.v1.core.JoinType
import org.jetbrains.exposed.v1.core.SortOrder
import org.jetbrains.exposed.v1.core.alias
import org.jetbrains.exposed.v1.core.greater
import org.jetbrains.exposed.v1.core.inList
import org.jetbrains.exposed.v1.core.isNull
import org.jetbrains.exposed.v1.core.less
import org.jetbrains.exposed.v1.core.like
import org.jetbrains.exposed.v1.core.or
import org.jetbrains.exposed.v1.jdbc.Query
import org.jetbrains.exposed.v1.jdbc.andWhere
import org.jetbrains.exposed.v1.jdbc.select
import org.jetbrains.exposed.v1.jdbc.selectAll
import java.time.Instant
fun Route.configureAuditLog() {
route("/auditlog") {
install(SWPermissionCheck) {
mustAuth = true
permission = UserPerm.MODERATION
}
get {
val text = call.request.queryParameters["fullText"]
val actionText = call.request.queryParameters["actionText"]
val serverText = call.request.queryParameters["server"]
val actor = call.request.queryParameters.getAll("actor")?.map { it.toInt() }?.toSet()
val actionType =
call.request.queryParameters.getAll("actionType")?.map { AuditLog.Type.valueOf(it) }?.toSet()
val timeGreater = call.request.queryParameters["timeGreater"]?.let { Instant.ofEpochMilli(it.toLong()) }
val timeLess = call.request.queryParameters["timeLess"]?.let { Instant.ofEpochMilli(it.toLong()) }
val serverOwner = call.request.queryParameters.getAll("serverOwner")?.map { it.toInt() }?.toSet()
val velocity = call.request.queryParameters["velocity"]?.toBoolean()
val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 0
val limit = call.request.queryParameters["limit"]?.toIntOrNull() ?: 100
val sorting = call.request.queryParameters["sorting"] ?: "DESC"
call.respond(
PagedAuditLog.filter(
actionText,
serverText,
text,
actor,
actionType,
timeGreater,
timeLess,
serverOwner,
velocity,
page,
limit,
sorting
)
)
}
}
}
@Serializable
data class PagedAuditLog(val rows: Long, val entries: List<AuditLogEntry>) {
companion object {
fun filter(
actionText: String? = null,
serverText: String? = null,
fullText: String? = null,
actor: Set<Int>? = null,
actionType: Set<AuditLog.Type>? = null,
timeGreater: Instant? = null,
timeLess: Instant? = null,
serverOwner: Set<Int>? = null,
velocity: Boolean? = null,
page: Int = 0,
limit: Int = 100,
sorting: String = "DESC"
) = useDb {
val actorTable = SteamwarUserTable.alias("actor")
val serverOwnerTable = SteamwarUserTable.alias("serverOwner")
val query = AuditLogTable.join(
actorTable,
JoinType.INNER,
onColumn = actorTable[SteamwarUserTable.id],
otherColumn = AuditLogTable.actor
)
.join(
serverOwnerTable,
JoinType.LEFT,
onColumn = serverOwnerTable[SteamwarUserTable.id],
otherColumn = AuditLogTable.serverOwner
)
.select(
actorTable[SteamwarUserTable.username], serverOwnerTable[SteamwarUserTable.username],
*AuditLogTable.columns.toTypedArray()
)
PagedAuditLog(
AuditLogTable.selectAll().addAuditLogFilters(
actionText,
serverText,
fullText,
actor,
actionType,
timeGreater,
timeLess,
serverOwner,
velocity
).count(),
query
.addAuditLogFilters(
actionText,
serverText,
fullText,
actor,
actionType,
timeGreater,
timeLess,
serverOwner,
velocity
)
.limit(limit)
.offset((page * limit).toLong())
.orderBy(AuditLogTable.time, SortOrder.valueOf(sorting.uppercase()))
.map {
AuditLogEntry(
it[AuditLogTable.id].value,
it[AuditLogTable.time].toEpochMilli(),
it[AuditLogTable.server],
it[serverOwnerTable[SteamwarUserTable.username]],
it[actorTable[SteamwarUserTable.username]],
it[AuditLogTable.action],
it[AuditLogTable.actionText]
)
},
)
}
private fun Query.addAuditLogFilters(
actionText: String? = null,
serverText: String? = null,
fullText: String? = null,
actor: Set<Int>? = null,
actionType: Set<AuditLog.Type>? = null,
timeGreater: Instant? = null,
timeLess: Instant? = null,
serverOwner: Set<Int>? = null,
velocity: Boolean? = null,
): Query {
actionText?.let {
andWhere { (AuditLogTable.actionText like "%$it%") }
}
serverText?.let {
andWhere { (AuditLogTable.server like "%$it%") }
}
fullText?.let {
andWhere { (AuditLogTable.actionText like "%$it%") or (AuditLogTable.server like "%$it%") }
}
actor?.let { andWhere { AuditLogTable.actor inList actor } }
actionType?.let { andWhere { AuditLogTable.action inList actionType } }
timeGreater?.let { andWhere { AuditLogTable.time greater timeGreater } }
timeLess?.let { andWhere { AuditLogTable.time less timeLess } }
serverOwner?.let { andWhere { AuditLogTable.serverOwner inList serverOwner } }
if (velocity == true) andWhere { AuditLogTable.serverOwner.isNull() }
return this
}
}
}
@Serializable
data class AuditLogEntry(
val id: Int,
val time: Long,
val server: String,
val serverOwner: String?,
val actor: String,
val actionType: AuditLog.Type,
val actionText: String
)
@@ -66,7 +66,7 @@ fun Route.configureAuth() {
}
call.sessions.set(SWUserSession(user.getId()))
call.respond(ResponseUser.get(user))
call.respond(ResponseUser(user))
}
delete {
@@ -100,7 +100,7 @@ fun Route.configureAuth() {
}
call.sessions.set(SWUserSession(user.getId()))
call.respond(ResponseUser.get(user))
call.respond(ResponseUser(user))
}
}
}
+68 -26
View File
@@ -25,6 +25,7 @@ import de.steamwar.plugins.SWAuthPrincipal
import de.steamwar.plugins.SWPermissionCheck
import de.steamwar.sql.SchematicType
import de.steamwar.sql.SteamwarUser
import de.steamwar.sql.SteamwarUserTable
import de.steamwar.sql.Team
import de.steamwar.sql.UserPerm
import de.steamwar.sql.internal.useDb
@@ -36,6 +37,13 @@ import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.serialization.Serializable
import org.bspfsystems.yamlconfiguration.file.YamlConfiguration
import org.jetbrains.exposed.v1.core.ResultRow
import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.core.inList
import org.jetbrains.exposed.v1.core.like
import org.jetbrains.exposed.v1.jdbc.Query
import org.jetbrains.exposed.v1.jdbc.andWhere
import org.jetbrains.exposed.v1.jdbc.selectAll
import java.io.File
import java.net.InetSocketAddress
import java.util.*
@@ -44,28 +52,39 @@ import java.util.*
data class ResponseSchematicType(val name: String, val db: String)
@Serializable
data class ResponseUser(val name: String, val uuid: String, val prefix: String, val perms: List<String>) {
private constructor(user: SteamwarUser) : this(user.userName, user.uuid.toString(), user.prefix().chatPrefix, user.perms().filter { !it.name.startsWith("PREFIX_") }.map { it.name })
data class ResponseUser(
val name: String, val uuid: String, val id: Int?, val perms: List<String>?, val prefix: String?
) {
constructor(user: SteamwarUser, includeId: Boolean = false, includePerms: Boolean = false) : this(
user.userName,
user.uuid.toString(),
if (includeId) user.getId() else null,
if (includePerms) user.perms().map { it.name } else null,
if (includePerms) user.prefix().chatPrefix else null,
)
companion object {
private val cache = mutableMapOf<Int, ResponseUser>()
constructor(row: ResultRow, includeId: Boolean = false, includePerms: Boolean = false, prefixes: MutableSet<UserPerm> = mutableSetOf()) : this(
row[SteamwarUserTable.username],
row[SteamwarUserTable.uuid],
if (includeId) row[SteamwarUserTable.id].value else null,
if (includePerms) UserPerm.getPerms(row[SteamwarUserTable.id].value).also { prefixes.addAll(it) }.map { it.name } else null,
if (includePerms) prefixes.firstOrNull { UserPerm.prefixes.containsKey(it) }?.let { UserPerm.prefixes[it]!!.chatPrefix } else null
)
}
fun get(id: Int): ResponseUser {
synchronized(cache) {
return cache[id] ?: ResponseUser(SteamwarUser.byId(id)!!).also { cache[id] = it }
}
}
@Serializable
data class ResponseUserList(val entries: List<ResponseUser>, val rows: Long)
fun get(user: SteamwarUser): ResponseUser = synchronized(cache) {
return cache[user.getId()] ?: ResponseUser(user).also { cache[user.getId()] = it }
}
private fun Query.addUserFilter(
name: String? = null,
uuid: UUID? = null,
team: Set<Int>? = null,
): Query {
name?.let { andWhere { (SteamwarUserTable.username like "%$it%") } }
uuid?.let { andWhere { (SteamwarUserTable.uuid eq it.toString()) } }
team?.let { andWhere { (SteamwarUserTable.team inList team) } }
fun clearCache() {
synchronized(cache) {
cache.clear()
}
}
}
return this
}
fun Route.configureDataRoutes() {
@@ -76,13 +95,33 @@ fun Route.configureDataRoutes() {
permission = UserPerm.MODERATION
}
get("/users") {
call.respond(useDb { SteamwarUser.all().map { ResponseUser.get(it) } })
val name = call.request.queryParameters["name"]
val uuid = call.request.queryParameters["uuid"]?.let { catchException { UUID.fromString(it) } }
val team = call.request.queryParameters.getAll("team")?.map { it.toInt() }?.toSet()
val limit = call.request.queryParameters["limit"]?.toIntOrNull() ?: 100
val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 0
val includePerms = call.request.queryParameters["includePerms"]?.toBoolean() ?: false
val includeId = call.request.queryParameters["includeId"]?.toBoolean() ?: false
call.respond(
useDb {
ResponseUserList(
SteamwarUserTable.selectAll().addUserFilter(name, uuid, team).limit(limit)
.offset((page * limit).toLong())
.map { ResponseUser(it, includeId, includePerms) },
SteamwarUserTable.selectAll().addUserFilter(name, uuid, team).count()
)
}
)
}
get("/teams") {
call.respond(Team.getAll().map { ResponseTeam(it) })
}
get("/schematicTypes") {
call.respond(SchematicType.values().filter { !it.check() }.map { ResponseSchematicType(it.name(), it.toDB()) })
call.respond(SchematicType.values().filter { !it.check() }
.map { ResponseSchematicType(it.name(), it.toDB()) })
}
get("/gamemodes") {
call.respond(
@@ -116,11 +155,14 @@ fun Route.configureDataRoutes() {
}
get("/team") {
call.respond(
listOf(UserPerm.PREFIX_ADMIN, UserPerm.PREFIX_DEVELOPER, UserPerm.PREFIX_MODERATOR, UserPerm.PREFIX_SUPPORTER, UserPerm.PREFIX_BUILDER)
.associateWith { SteamwarUser.getUsersWithPerm(it) }
.mapKeys { UserPerm.prefixes[it.key]!!.chatPrefix }
.mapValues { it.value.map { ResponseUser.get(it) } }
)
listOf(
UserPerm.PREFIX_ADMIN,
UserPerm.PREFIX_DEVELOPER,
UserPerm.PREFIX_MODERATOR,
UserPerm.PREFIX_SUPPORTER,
UserPerm.PREFIX_BUILDER
).associateWith { SteamwarUser.getUsersWithPerm(it) }.mapKeys { UserPerm.prefixes[it.key]!!.chatPrefix }
.mapValues { it.value.map { ResponseUser(it) } })
}
get("/skin/{uuid}") {
val uuid = call.parameters["uuid"]
@@ -138,7 +180,7 @@ fun Route.configureDataRoutes() {
route("/me") {
install(SWPermissionCheck)
get {
call.respond(ResponseUser.get(call.principal<SWAuthPrincipal>()!!.user))
call.respond(ResponseUser(call.principal<SWAuthPrincipal>()!!.user, includePerms = true))
}
}
}
@@ -31,7 +31,7 @@ fun Route.configureEventRefereesRouting() {
route("/referees") {
get {
val event = call.receiveEvent() ?: return@get
call.respond(Referee.get(event.eventID).map { ResponseUser.get(SteamwarUser.byId(it)!!) })
call.respond(Referee.get(event.eventID).map { ResponseUser(SteamwarUser.byId(it)!!) })
}
put {
val event = call.receiveEvent() ?: return@put
@@ -39,7 +39,7 @@ fun Route.configureEventRefereesRouting() {
referees.forEach {
Referee.add(event.eventID, SteamwarUser.get(UUID.fromString(it))!!.getId())
}
call.respond(Referee.get(event.eventID).map { ResponseUser.get(SteamwarUser.byId(it)!!) })
call.respond(Referee.get(event.eventID).map { ResponseUser(SteamwarUser.byId(it)!!) })
}
delete {
val event = call.receiveEvent() ?: return@delete
@@ -47,7 +47,7 @@ fun Route.configureEventRefereesRouting() {
referees.forEach {
Referee.remove(event.eventID, SteamwarUser.get(UUID.fromString(it))!!.getId())
}
call.respond(Referee.get(event.eventID).map { ResponseUser.get(SteamwarUser.byId(it)!!) })
call.respond(Referee.get(event.eventID).map { ResponseUser(SteamwarUser.byId(it)!!) })
}
}
}
@@ -118,7 +118,7 @@ data class ExtendedResponseEvent(
TeamTeilnahme.getTeams(event.eventID).map { ResponseTeam(it) },
EventGroup.get(event).map { ResponseGroups(it) },
EventFight.getEvent(event.eventID).map { ResponseEventFight(it) },
Referee.get(event.eventID).map { ResponseUser.get(SteamwarUser.byId(it)!!) },
Referee.get(event.eventID).map { ResponseUser(SteamwarUser.byId(it)!!) },
EventRelation.get(event).map { ResponseRelation(it) }
)
}
@@ -33,6 +33,7 @@ fun Application.configureRoutes() {
configurePage()
configureSchematic()
configureAuth()
configureAuditLog()
}
}
}
@@ -34,9 +34,7 @@ import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.serialization.Serializable
import java.io.BufferedInputStream
import java.io.ByteArrayInputStream
import java.io.DataInputStream
import java.io.InputStream
import java.security.MessageDigest
import java.time.Duration
import java.time.Instant
@@ -49,16 +47,6 @@ data class ResponseSchematic(val name: String, val id: Int, val type: String?, v
constructor(node: SchematicNode) : this(node.name, node.getId(), node.schemtype?.name(), node.owner, node.item, node.lastUpdate.time, node.rank, node.replaceColor(), node.allowReplay())
}
@Serializable
data class ResponseSchematicLong(val members: List<ResponseUser>, val path: String, val schem: ResponseSchematic) {
constructor(node: SchematicNode, path: String): this(NodeMember.getNodeMembers(node.getId()).map { ResponseUser.get(it.member) }, path, ResponseSchematic(node))
}
@Serializable
data class ResponseSchematicList(val breadcrumbs: List<ResponseBreadcrumb>, val schematics: List<ResponseSchematic>, val players: Map<String, ResponseUser>) {
constructor(schematics: List<ResponseSchematic>, breadcrumbs: List<ResponseBreadcrumb>) : this(breadcrumbs, schematics, schematics.map { it.owner }.distinct().map { ResponseUser.get(it) }.associateBy { it.uuid })
}
@Serializable
data class ResponseBreadcrumb(val name: String, val id: Int)
@@ -23,11 +23,17 @@ import de.steamwar.plugins.SWPermissionCheck
import de.steamwar.plugins.getUser
import de.steamwar.sql.SteamwarUser
import de.steamwar.sql.UserPerm
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.serialization.Serializable
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.ApplicationCall
import io.ktor.server.application.call
import io.ktor.server.application.install
import io.ktor.server.response.respond
import io.ktor.server.routing.Route
import io.ktor.server.routing.delete
import io.ktor.server.routing.get
import io.ktor.server.routing.put
import io.ktor.server.routing.route
import kotlinx.serialization.Serializable;
@Serializable
data class RespondPrefix(val name: String, val colorCode: String, val chatPrefix: String)
@@ -76,7 +82,12 @@ fun Route.configureUserPerms() {
val prefixs = UserPerm.prefixes[prefix]!!
call.respond(RespondUserPermsPrefix(RespondPrefix(prefix.name, prefixs.colorCode, prefixs.chatPrefix), perms))
call.respond(
RespondUserPermsPrefix(
RespondPrefix(prefix.name, prefixs.colorCode, prefixs.chatPrefix),
perms
)
)
}
put("/prefix/{prefix}") {
val (user, prefix) = call.receivePermission("prefix", isPrefix = true) ?: return@put
@@ -112,7 +123,10 @@ fun Route.configureUserPerms() {
}
}
suspend fun ApplicationCall.receivePermission(fieldName: String = "perm", isPrefix: Boolean = false): Pair<SteamwarUser, UserPerm>? {
suspend fun ApplicationCall.receivePermission(
fieldName: String = "perm",
isPrefix: Boolean = false
): Pair<SteamwarUser, UserPerm>? {
val user = request.getUser()
if (user == null) {
respond(HttpStatusCode.BadRequest)
+1 -10
View File
@@ -61,17 +61,8 @@ private val fightCount = "SELECT COUNT(*) AS num FROM FightPlayer WHERE UserID =
fun getFightCount(user: SteamwarUser) =
useDb { exec(fightCount, args = listOf(IntegerColumnType() to user.getId())) { getNum(it) } }
private const val rankedList =
"SELECT UserName, Elo FROM UserData, UserElo WHERE UserID = id AND GameMode = ? AND Season = ? ORDER BY Elo DESC"
fun getRankedList(gamemode: String): List<Pair<String, Int>> = useDb {
exec(rankedList, args = listOf(VarCharColumnType() to gamemode, IntegerColumnType() to Season.getSeason())) {
val list = mutableListOf<Pair<String, Int>>()
while (it.next()) {
list.add(it.getString("UserName") to it.getInt("Elo"))
}
list
} ?: emptyList()
emptyList()
}
private const val fightList =
+28 -8
View File
@@ -131,7 +131,17 @@ class DevServer extends DefaultTask {
private boolean checkFileOnRemote(String path) {
def process = run("[ -e \"$path\" ] && echo \"true\"")
return new BufferedReader(new InputStreamReader(process.inputStream)).lines().count() > 0
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) {
@@ -142,6 +152,7 @@ class DevServer extends DefaultTask {
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
@@ -149,10 +160,10 @@ class DevServer extends DefaultTask {
if (!checkFileOnRemote("/servers/$template")) {
throw new GradleException("Used template ($template) is not in /servers/ directory of the given host $host")
}
run("cp -r /servers/$template $template")
run("chmod u+w $template")
run("rm -r $template/plugins/*WorldEdit/")
run("rm $template/log4j2.xml")
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() {
@@ -170,7 +181,7 @@ class DevServer extends DefaultTask {
def archive = archiveTask.archiveFile.get().asFile
Process process = new ProcessBuilder("ssh", host, "-T", "sha1sum $base/${archive.name.replace("-all", "")}").start();
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) {
@@ -180,14 +191,20 @@ class DevServer extends DefaultTask {
process.inputStream.readLines().forEach {
same |= it.startsWith(sb.toString().toLowerCase())
}
DevServer.closeProcess(process)
if (same) {
println("Skipping $archive")
return
}
println("Uploading $archive")
new ProcessBuilder("ssh", host, "-T", "rm $base/${archive.name.replace("-all", "")}").start().waitFor()
new ProcessBuilder("scp", archive.absolutePath, "$host:~/$base/${archive.name.replace("-all", "")}").start().waitFor()
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")
}
}
@@ -214,6 +231,8 @@ class DevServer extends DefaultTask {
println(processOutput.readLine())
}
}
processOutput.close()
process.errorStream.close()
}).start()
processInput = new BufferedWriter(new OutputStreamWriter(process.outputStream))
@@ -226,6 +245,7 @@ class DevServer extends DefaultTask {
processInput.newLine()
processInput.flush()
}
processInput.close()
}).start()
process.waitFor()