Compare commits

..
Author SHA1 Message Date
YoyoNow 026937bc7b Merge branch 'main' into VelocityCore/NoEvent 2025-12-03 14:07:02 +01:00
YoyoNow 771c77247a Add Punishment.PunishmentType.NoEvent 2025-12-01 15:52:15 +01:00
90 changed files with 1051 additions and 1187 deletions
+1 -2
View File
@@ -20,5 +20,4 @@ lib
/WebsiteBackend/data /WebsiteBackend/data
/WebsiteBackend/logs /WebsiteBackend/logs
/WebsiteBackend/skins /WebsiteBackend/skins
/WebsiteBackend/config.json /WebsiteBackend/config.json
/WebsiteBackend/sessions
@@ -52,9 +52,6 @@ FLAG_ITEMS=Items
FLAG_NO_GRAVITY = No Gravity FLAG_NO_GRAVITY = No Gravity
FLAG_TESTBLOCK=Testblock FLAG_TESTBLOCK=Testblock
FLAG_CHANGED=Changed FLAG_CHANGED=Changed
FLAG_WATER_DESTROY=Water Destroy
FLAG_WATER_DESTROY_ALLOW=§coff
FLAG_WATER_DESTROY_DENY=§aon
FLAG_FIRE_ALLOW=§con FLAG_FIRE_ALLOW=§con
FLAG_FIRE_DENY=§aoff FLAG_FIRE_DENY=§aoff
FLAG_FREEZE_ACTIVE=§aon FLAG_FREEZE_ACTIVE=§aon
@@ -143,7 +140,6 @@ BAU_INFO_ITEM_LORE_ITEMS=§7Items§8: §e{0}
BAU_INFO_ITEM_LORE_NO_GRAVITY = §7NoGravity§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_TESTBLOCK=§7Testblock§8: §e{0}
BAU_INFO_ITEM_LORE_CHANGED=§7Changed§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_HELP=§8/§ebauinfo §8- §7Information regarding this build server
BAU_INFO_COMMAND_OWNER=§7Owner§8: §e{0} BAU_INFO_COMMAND_OWNER=§7Owner§8: §e{0}
BAU_INFO_COMMAND_MEMBER=§7{0} §8[§7{1}§8]§8: §e{2} BAU_INFO_COMMAND_MEMBER=§7{0} §8[§7{1}§8]§8: §e{2}
@@ -760,9 +756,6 @@ REGION_FIRE_DISABLED=§aFire damage activated in this region
REGION_FREEZE_HELP=§8/§efreeze §8- §7Toggle Freeze REGION_FREEZE_HELP=§8/§efreeze §8- §7Toggle Freeze
REGION_FREEZE_ENABLED=§cRegion frozen REGION_FREEZE_ENABLED=§cRegion frozen
REGION_FREEZE_DISABLED=§aRegion thawed 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_HELP=§8/§eitems §8- §7Toggle Items
REGION_ITEMS_ENABLED=§aItems enabled in this region REGION_ITEMS_ENABLED=§aItems enabled in this region
REGION_ITEMS_DISABLED=§cItems disabled in this region REGION_ITEMS_DISABLED=§cItems disabled in this region
@@ -54,9 +54,6 @@ FLAG_FREEZE_ACTIVE=§aan
FLAG_FREEZE_INACTIVE=§caus FLAG_FREEZE_INACTIVE=§caus
FLAG_PROTECT_ACTIVE=§aan FLAG_PROTECT_ACTIVE=§aan
FLAG_PROTECT_INACTIVE=§caus FLAG_PROTECT_INACTIVE=§caus
FLAG_WATER_DESTROY=Wasserschaden
FLAG_WATER_DESTROY_ALLOW=§cerlaubt
FLAG_WATER_DESTROY_DENY=§aaus
FLAG_TNT_ALLOW=§aan FLAG_TNT_ALLOW=§aan
FLAG_TNT_DENY=§caus FLAG_TNT_DENY=§caus
FLAG_TNT_ONLY_TB=§7Kein §eBaurahmen FLAG_TNT_ONLY_TB=§7Kein §eBaurahmen
@@ -125,7 +122,6 @@ BAU_INFO_ITEM_NAME=§eBau-Management
BAU_INFO_ITEM_LORE_FIRE=§7Feuer§8: §e{0} BAU_INFO_ITEM_LORE_FIRE=§7Feuer§8: §e{0}
BAU_INFO_ITEM_LORE_COLOR=§7Farbe§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_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_HELP=§8/§ebauinfo §8- §7Gibt Informationen über den Bau
BAU_INFO_COMMAND_OWNER=§7Besitzer§8: §e{0} BAU_INFO_COMMAND_OWNER=§7Besitzer§8: §e{0}
BAU_INFO_COMMAND_MEMBER=§7{0} §8[§7{1}§8]§8: §e{2} BAU_INFO_COMMAND_MEMBER=§7{0} §8[§7{1}§8]§8: §e{2}
@@ -708,9 +704,6 @@ 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_HELP = §8/§enogravity §8- §7Toggle NoGravity
REGION_NO_GRAVITY_ENABLED = §aNoGravity aktiviert in dieser Region REGION_NO_GRAVITY_ENABLED = §aNoGravity aktiviert in dieser Region
REGION_NO_GRAVITY_DISABLED = §cNoGravity deaktiviert 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_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_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 REGION_REGION_HELP_RESTORE=§8/§eregion restore §8- §7Setzte die Region zurück, ohne das Gebaute zu löschen
@@ -40,13 +40,10 @@ import de.steamwar.bausystem.worlddata.WorldData;
import de.steamwar.command.AbstractValidator; import de.steamwar.command.AbstractValidator;
import de.steamwar.command.SWCommandUtils; import de.steamwar.command.SWCommandUtils;
import de.steamwar.core.CRIUSleepEvent; import de.steamwar.core.CRIUSleepEvent;
import de.steamwar.core.Core;
import de.steamwar.core.WorldEditRendererCUIEditor; import de.steamwar.core.WorldEditRendererCUIEditor;
import de.steamwar.core.WorldIdentifier;
import de.steamwar.linkage.AbstractLinker; import de.steamwar.linkage.AbstractLinker;
import de.steamwar.linkage.SpigotLinker; import de.steamwar.linkage.SpigotLinker;
import de.steamwar.message.Message; import de.steamwar.message.Message;
import de.steamwar.providers.BauServerInfo;
import lombok.Getter; import lombok.Getter;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.GameRule; import org.bukkit.GameRule;
@@ -124,7 +121,6 @@ public class BauSystem extends JavaPlugin implements Listener {
} catch (AbstractLinker.LinkException e) { } catch (AbstractLinker.LinkException e) {
getLogger().log(Level.SEVERE, "Could not link a class.", e); getLogger().log(Level.SEVERE, "Could not link a class.", e);
Bukkit.shutdown(); Bukkit.shutdown();
return;
} }
TickListener.impl.init(); TickListener.impl.init();
@@ -135,9 +131,6 @@ public class BauSystem extends JavaPlugin implements Listener {
new WorldEditRendererCUIEditor(); new WorldEditRendererCUIEditor();
Bukkit.getWorlds().get(0).setGameRule(GameRule.SEND_COMMAND_FEEDBACK, false); Bukkit.getWorlds().get(0).setGameRule(GameRule.SEND_COMMAND_FEEDBACK, false);
String identifier = BauServerInfo.getOwnerUser().getUUID().toString().replace("-", "");
WorldIdentifier.set("bau/" + Core.getVersion() + "/" + identifier);
} }
@EventHandler @EventHandler
@@ -46,8 +46,8 @@ public class ObserverTracerListener implements Listener {
public ObserverTracerListener() { public ObserverTracerListener() {
Bukkit.getScheduler().runTaskTimer(BauSystem.getInstance(), () -> { Bukkit.getScheduler().runTaskTimer(BauSystem.getInstance(), () -> {
SWPlayer.allWithSingleComponent(ObserverTracer.class).forEach(pair -> { SWPlayer.allWithSingleComponent(ObserverTracer.class).forEach(pair -> {
if (pair.getPlayer().getGameMode() != GameMode.SPECTATOR) return; if (pair.getKey().getGameMode() != GameMode.SPECTATOR) return;
pair.getComponent().show(); pair.getValue().show();
}); });
}, 15L, 15L); }, 15L, 15L);
} }
@@ -64,7 +64,7 @@ public class ObserverTracerListener implements Listener {
createNew(event); createNew(event);
} }
SWPlayer.allWithSingleComponent(ObserverTracer.class).forEach(pair -> { SWPlayer.allWithSingleComponent(ObserverTracer.class).forEach(pair -> {
pair.getComponent().trace(); pair.getValue().trace();
}); });
}, 1L); }, 1L);
} else { } else {
@@ -1,64 +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.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;
}
}
}
@@ -1,57 +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.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(), () -> { Bukkit.getScheduler().runTaskTimer(BauSystem.getInstance(), () -> {
long millis = System.currentTimeMillis(); long millis = System.currentTimeMillis();
SWPlayer.allWithSingleComponent(ScriptRunner.ScriptData.class) SWPlayer.allWithSingleComponent(ScriptRunner.ScriptData.class)
.filter(pair -> millis - pair.getComponent().getLastF() > 200) .filter(pair -> millis - pair.getValue().getLastF() > 200)
.forEach(pair -> pair.getComponent().setLastF(Long.MAX_VALUE)); .forEach(pair -> pair.getValue().setLastF(Long.MAX_VALUE));
}, 1, 1); }, 1, 1);
} }
@@ -45,6 +45,8 @@ public class TraceManager implements Listener {
instance = this; instance = this;
} }
public void init() { public void init() {
if (!tracesFolder.exists()) if (!tracesFolder.exists())
tracesFolder.mkdir(); tracesFolder.mkdir();
@@ -26,7 +26,6 @@ import com.sk89q.worldedit.extension.input.ParserContext;
import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.mask.Mask;
import de.steamwar.bausystem.features.worldedit.utils.FAWEMaskParser; import de.steamwar.bausystem.features.worldedit.utils.FAWEMaskParser;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import de.steamwar.linkage.MinVersion;
import de.steamwar.linkage.PluginCheck; import de.steamwar.linkage.PluginCheck;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
@@ -34,7 +33,6 @@ import java.util.stream.Stream;
@Linked @Linked
@PluginCheck("FastAsyncWorldEdit") @PluginCheck("FastAsyncWorldEdit")
@MinVersion(19)
public class FAWEAboveMaskParser extends FAWEMaskParser { public class FAWEAboveMaskParser extends FAWEMaskParser {
public FAWEAboveMaskParser() { public FAWEAboveMaskParser() {
@@ -26,7 +26,6 @@ import com.sk89q.worldedit.extension.input.ParserContext;
import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.mask.Mask;
import de.steamwar.bausystem.features.worldedit.utils.FAWEMaskParser; import de.steamwar.bausystem.features.worldedit.utils.FAWEMaskParser;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import de.steamwar.linkage.MinVersion;
import de.steamwar.linkage.PluginCheck; import de.steamwar.linkage.PluginCheck;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
@@ -34,7 +33,6 @@ import java.util.stream.Stream;
@Linked @Linked
@PluginCheck("FastAsyncWorldEdit") @PluginCheck("FastAsyncWorldEdit")
@MinVersion(19)
public class FAWEBelowMaskParser extends FAWEMaskParser { public class FAWEBelowMaskParser extends FAWEMaskParser {
public FAWEBelowMaskParser() { public FAWEBelowMaskParser() {
@@ -25,7 +25,6 @@ import com.sk89q.worldedit.extension.input.ParserContext;
import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.mask.Mask;
import de.steamwar.bausystem.features.worldedit.utils.FAWEMaskParser; import de.steamwar.bausystem.features.worldedit.utils.FAWEMaskParser;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import de.steamwar.linkage.MinVersion;
import de.steamwar.linkage.PluginCheck; import de.steamwar.linkage.PluginCheck;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
@@ -33,7 +32,6 @@ import java.util.stream.Stream;
@Linked @Linked
@PluginCheck("FastAsyncWorldEdit") @PluginCheck("FastAsyncWorldEdit")
@MinVersion(19)
public class FAWECheckerboard3DMaskParser extends FAWEMaskParser { public class FAWECheckerboard3DMaskParser extends FAWEMaskParser {
public FAWECheckerboard3DMaskParser() { public FAWECheckerboard3DMaskParser() {
@@ -25,7 +25,6 @@ import com.sk89q.worldedit.extension.input.ParserContext;
import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.mask.Mask;
import de.steamwar.bausystem.features.worldedit.utils.FAWEMaskParser; import de.steamwar.bausystem.features.worldedit.utils.FAWEMaskParser;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import de.steamwar.linkage.MinVersion;
import de.steamwar.linkage.PluginCheck; import de.steamwar.linkage.PluginCheck;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
@@ -33,7 +32,6 @@ import java.util.stream.Stream;
@Linked @Linked
@PluginCheck("FastAsyncWorldEdit") @PluginCheck("FastAsyncWorldEdit")
@MinVersion(19)
public class FAWECheckerboardMaskParser extends FAWEMaskParser { public class FAWECheckerboardMaskParser extends FAWEMaskParser {
public FAWECheckerboardMaskParser() { public FAWECheckerboardMaskParser() {
@@ -25,7 +25,6 @@ import com.sk89q.worldedit.extension.input.ParserContext;
import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.mask.Mask;
import de.steamwar.bausystem.features.worldedit.utils.FAWEMaskParser; import de.steamwar.bausystem.features.worldedit.utils.FAWEMaskParser;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import de.steamwar.linkage.MinVersion;
import de.steamwar.linkage.PluginCheck; import de.steamwar.linkage.PluginCheck;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
@@ -33,7 +32,6 @@ import java.util.stream.Stream;
@Linked @Linked
@PluginCheck("FastAsyncWorldEdit") @PluginCheck("FastAsyncWorldEdit")
@MinVersion(19)
public class FAWEGridMaskParser extends FAWEMaskParser { public class FAWEGridMaskParser extends FAWEMaskParser {
public FAWEGridMaskParser() { public FAWEGridMaskParser() {
@@ -27,7 +27,6 @@ import com.sk89q.worldedit.regions.Region;
import de.steamwar.bausystem.features.worldedit.utils.FAWEPatternParser; import de.steamwar.bausystem.features.worldedit.utils.FAWEPatternParser;
import de.steamwar.bausystem.utils.WorldEditUtils; import de.steamwar.bausystem.utils.WorldEditUtils;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import de.steamwar.linkage.MinVersion;
import de.steamwar.linkage.PluginCheck; import de.steamwar.linkage.PluginCheck;
import org.bukkit.Axis; import org.bukkit.Axis;
@@ -36,7 +35,6 @@ import java.util.stream.Stream;
@Linked @Linked
@PluginCheck("FastAsyncWorldEdit") @PluginCheck("FastAsyncWorldEdit")
@MinVersion(19)
public class FAWEGradientPatternParser extends FAWEPatternParser { public class FAWEGradientPatternParser extends FAWEPatternParser {
public FAWEGradientPatternParser() { public FAWEGradientPatternParser() {
@@ -43,7 +43,6 @@ 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<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<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<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 String name;
private int ordinal; private int ordinal;
@@ -1,56 +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.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 @Override
public @NonNull <T extends Enum<T> & Flag.Value<T>> RegionFlagPolicy has(@NonNull Flag<T> flag) { 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, Flag.WATER_DESTROY)) { if (flag.oneOf(Flag.COLOR, Flag.TNT, Flag.FIRE, Flag.FREEZE, Flag.PROTECT, Flag.NO_GRAVITY, Flag.CHANGED)) {
return RegionFlagPolicy.WRITABLE; return RegionFlagPolicy.WRITABLE;
} }
if (flag.oneOf(Flag.ITEMS) && Core.getVersion() >= 20) { if (flag.oneOf(Flag.ITEMS) && Core.getVersion() >= 20) {
-1
View File
@@ -24,5 +24,4 @@ plugins {
dependencies { dependencies {
testImplementation(libs.junit) testImplementation(libs.junit)
testImplementation(libs.hamcrest) testImplementation(libs.hamcrest)
compileOnly(project(":CommonCore:SQL"))
} }
@@ -56,9 +56,7 @@ public abstract class AbstractLinker<T> {
.map(s -> { .map(s -> {
try { try {
return Class.forName(s, false, plugin.getClass().getClassLoader()); return Class.forName(s, false, plugin.getClass().getClassLoader());
} catch (NoClassDefFoundError error) { } catch (ClassNotFoundException | NoClassDefFoundError e) {
return null;
} catch (ClassNotFoundException e) {
throw new SecurityException(e.getMessage(), e); throw new SecurityException(e.getMessage(), e);
} }
}) })
@@ -1,22 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar
data class ServerInfo(val name: String, val version: Int, val checkpointed: Boolean)
@@ -1,71 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.logger
import de.steamwar.sql.AuditLog
import de.steamwar.sql.AuditLogTable
import de.steamwar.sql.SQLWrapper
import de.steamwar.sql.SteamwarUser
import de.steamwar.sql.internal.useDb
import org.jetbrains.exposed.v1.jdbc.insertIgnore
class LogEntry(val type: AuditLog.Type, val user: SteamwarUser) {
private val start = System.currentTimeMillis()
var text: String = ""
var arguments: String = ""
private var exceptionType: String? = null
private var exceptionText: String? = null
private var exceptionStacktrace: String? = null
private var owner: SteamwarUser? = null
fun addException(e: Throwable) {
exceptionType = e.javaClass.name
exceptionText = e.message
exceptionStacktrace = e.stackTraceToString()
}
fun addServerOwner(owner: SteamwarUser) {
this.owner = owner
}
fun finish() {
val end = System.currentTimeMillis()
val info = SQLWrapper.impl.serverInfo
useDb {
AuditLogTable.insertIgnore {
it[this.action] = type
it[this.actor] = user.getId()
it[this.actionText] = text
it[this.actionArguments] = arguments
it[this.duration] = end - start
it[this.errorType] = exceptionType
it[this.errorText] = exceptionText
it[this.stackTrace] = exceptionStacktrace
it[this.server] = info.name
it[this.checkpointed] = info.checkpointed
it[this.duration] = end - start
it[this.serverOwner] = owner?.getId()
}
}
}
}
@@ -1,27 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.logger
import de.steamwar.sql.AuditLog
import de.steamwar.sql.SteamwarUser
object SWLogger {
fun startCommand(user: SteamwarUser) = LogEntry(AuditLog.Type.COMMAND, user)
}
+64 -17
View File
@@ -19,47 +19,94 @@
package de.steamwar.sql package de.steamwar.sql
import de.steamwar.sql.internal.useDb
import org.jetbrains.exposed.v1.core.dao.id.EntityID import org.jetbrains.exposed.v1.core.dao.id.EntityID
import org.jetbrains.exposed.v1.core.dao.id.IntIdTable import org.jetbrains.exposed.v1.core.dao.id.IntIdTable
import org.jetbrains.exposed.v1.dao.IntEntity import org.jetbrains.exposed.v1.dao.IntEntity
import org.jetbrains.exposed.v1.dao.IntEntityClass import org.jetbrains.exposed.v1.dao.IntEntityClass
import org.jetbrains.exposed.v1.javatime.timestamp import org.jetbrains.exposed.v1.javatime.timestamp
import java.time.Instant
object AuditLogTable: IntIdTable("AuditLog", "AuditLogId") { object AuditLogTable: IntIdTable("AuditLog", "AuditLogId") {
val time = timestamp("Time") val time = timestamp("Time")
val server = varchar("ServerName", 255) val server = varchar("ServerName", 255)
val serverType = varchar("ServerType", 255) val serverOwner = reference("ServerOwner", SteamwarUserTable).nullable()
val serverOwner = optReference("ServerOwner", SteamwarUserTable)
val checkpointed = bool("Checkpointed")
val actor = reference("Actor", SteamwarUserTable) val actor = reference("Actor", SteamwarUserTable)
val action = enumerationByName("ActionType", 255, AuditLog.Type::class) val action = enumerationByName("ActionType", 255, AuditLog.Type::class)
val actionText = text("ActionText") val actionText = text("ActionText")
val actionArguments = text("ActionArguments")
val duration = long("Duration")
val errorType = text("ErrorType").nullable()
val errorText = text("ErrorText").nullable()
val stackTrace = text("StackTrace").nullable()
} }
class AuditLog(id: EntityID<Int>): IntEntity(id) { class AuditLog(id: EntityID<Int>): IntEntity(id) {
companion object: IntEntityClass<AuditLog>(AuditLogTable) { companion object: IntEntityClass<AuditLog>(AuditLogTable) {
@JvmField const val SERVER_NAME_VELOCITY: String = "Velocity"
val SERVER_NAME_VELOCITY: String = "Velocity"
private fun create(
serverName: String,
serverOwner: SteamwarUser?,
actor: SteamwarUser,
actionType: Type,
text: String = ""
) = useDb {
new {
this.time = Instant.now()
this.server = serverName
this.serverOwner = serverOwner?.id
this.actor = actor.id
this.action = actionType
this.actionText = text
}
}
@JvmStatic
fun createJoin(jointServerName: String, serverOwner: SteamwarUser?, joinedPlayer: SteamwarUser) = create(jointServerName, serverOwner, joinedPlayer, Type.JOIN)
@JvmStatic
fun createLeave(leftServerName: String, serverOwner: SteamwarUser?, joinedPlayer: SteamwarUser) = create(leftServerName, serverOwner, joinedPlayer, Type.LEAVE)
@JvmStatic
fun createCommand(serverName: String, serverOwner: SteamwarUser?, player: SteamwarUser?, command: String) = player?.let { create(serverName, serverOwner, it, Type.COMMAND, command) }
@JvmStatic
fun createSensitiveCommand(
serverName: String,
serverOwner: SteamwarUser?,
player: SteamwarUser?,
command: String
) = player?.let { create(serverName, serverOwner, it, Type.SENSITIVE_COMMAND, command) }
@JvmStatic
fun createChat(serverName: String, serverOwner: SteamwarUser?, chatter: SteamwarUser, chat: String) = create(serverName, serverOwner, chatter, Type.CHAT, chat)
@JvmStatic
fun createGuiOpen(serverName: String, serverOwner: SteamwarUser?, player: SteamwarUser, guiName: String) = create(serverName, serverOwner, player, Type.GUI_OPEN, guiName)
@JvmStatic
fun createGuiClick(
serverName: String,
serverOwner: SteamwarUser?,
player: SteamwarUser,
guiName: String,
clickType: String,
slot: Int,
itemName: String
) = create(
serverName,
serverOwner,
player,
Type.GUI_CLICK,
"Gui: $guiName\nSlot: $slot\nClickType: $clickType\nItemName: $itemName"
)
@JvmStatic
fun createGuiClose(serverName: String, serverOwner: SteamwarUser?, player: SteamwarUser, guiName: String) = create(serverName, serverOwner, player, Type.GUI_CLOSE, guiName)
} }
var time by AuditLogTable.time var time by AuditLogTable.time
var server by AuditLogTable.server var server by AuditLogTable.server
val serverType by AuditLogTable.serverType
var serverOwner by AuditLogTable.serverOwner var serverOwner by AuditLogTable.serverOwner
var actor by AuditLogTable.actor var actor by AuditLogTable.actor
var checkpointed by AuditLogTable.checkpointed
var action by AuditLogTable.action var action by AuditLogTable.action
var actionText by AuditLogTable.actionText var actionText by AuditLogTable.actionText
var actionArguments by AuditLogTable.actionArguments
var duration by AuditLogTable.duration
var errorType by AuditLogTable.errorType
var errorText by AuditLogTable.errorText
var stackTrace by AuditLogTable.stackTrace
enum class Type { enum class Type {
JOIN, JOIN,
@@ -33,8 +33,8 @@ object PersonalKitTable: CompositeIdTable("PersonalKit") {
val userId = reference("UserId", SteamwarUserTable) val userId = reference("UserId", SteamwarUserTable)
val gamemode = varchar("Gamemode", 64).entityId() val gamemode = varchar("Gamemode", 64).entityId()
val kitName = varchar("Name", 64).entityId() val kitName = varchar("Name", 64).entityId()
val inventory = text("Inventory", eagerLoading = true) val inventory = text("Inventory")
val armor = text("Armor", eagerLoading = true) val armor = text("Armor")
val inUse = bool("InUse") val inUse = bool("InUse")
override val primaryKey = PrimaryKey(userId, gamemode, kitName) override val primaryKey = PrimaryKey(userId, gamemode, kitName)
@@ -48,17 +48,14 @@ class InternalKit(id: EntityID<CompositeID>): CompositeEntity(id) {
companion object: CompositeEntityClass<InternalKit>(PersonalKitTable) { companion object: CompositeEntityClass<InternalKit>(PersonalKitTable) {
@JvmStatic @JvmStatic
fun get(userId: Int, gamemode: String) = useDb { fun get(userId: Int, gamemode: String) = useDb {
find { PersonalKitTable.userId eq userId and (PersonalKitTable.gamemode eq gamemode) } find { PersonalKitTable.userId eq userId and (PersonalKitTable.gamemode eq gamemode) and (PersonalKitTable.inUse eq true) }
.toList() .toList()
} }
@JvmStatic @JvmStatic
fun get(userId: Int, gamemode: String, kitName: String) = useDb { fun get(userId: Int, gamemode: String, kitName: String) = useDb {
findById(CompositeID { find { PersonalKitTable.userId eq userId and (PersonalKitTable.gamemode eq gamemode) and (PersonalKitTable.kitName eq kitName) and (PersonalKitTable.inUse eq true) }
it[PersonalKitTable.userId] = EntityID(userId, SteamwarUserTable) .firstOrNull()
it[PersonalKitTable.gamemode] = gamemode
it[PersonalKitTable.kitName] = kitName
})
} }
@JvmStatic @JvmStatic
@@ -70,10 +67,10 @@ class InternalKit(id: EntityID<CompositeID>): CompositeEntity(id) {
it[PersonalKitTable.kitName] = kitName it[PersonalKitTable.kitName] = kitName
} }
) { ) {
this.rawInventory = rawInventory this.inventory = rawInventory
this.rawArmor = rawArmor this.armor = rawArmor
this.inUse = false this.inUse = true
}.also { it.setDefault() } }
} }
@JvmStatic @JvmStatic
@@ -110,7 +107,6 @@ class InternalKit(id: EntityID<CompositeID>): CompositeEntity(id) {
fun setDefault() = useDb { fun setDefault() = 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) and (PersonalKitTable.inUse eq true) }
.filter { it.id.value != this@InternalKit.id.value }
.forEach { it.inUse = false } .forEach { it.inUse = false }
inUse = true inUse = true
} }
@@ -179,6 +179,14 @@ class Punishment(id: EntityID<Int>) : IntEntity(id) {
"UNNOTEAMSERVER", "UNNOTEAMSERVER",
UserPerm.MODERATION UserPerm.MODERATION
), ),
NoEvent(
"NOEVENT_TEAM",
"NOEVENT_PERMA",
"NOEVENT_UNTIL",
"NOEVENT_ERROR",
"UNNOEVENT",
UserPerm.MODERATION
),
Note("NOTE_TEAM", null, null, null, null, UserPerm.PUNISHMENTS, true); Note("NOTE_TEAM", null, null, null, null, UserPerm.PUNISHMENTS, true);
fun isMulti() = multi fun isMulti() = multi
@@ -20,7 +20,6 @@
package de.steamwar.sql; package de.steamwar.sql;
import de.steamwar.ImplementationProvider; import de.steamwar.ImplementationProvider;
import de.steamwar.ServerInfo;
import java.io.File; import java.io.File;
import java.util.Collections; import java.util.Collections;
@@ -41,6 +40,4 @@ public interface SQLWrapper<M> {
} }
void additionalExceptionMetadata(StringBuilder builder); void additionalExceptionMetadata(StringBuilder builder);
ServerInfo getServerInfo();
} }
@@ -36,8 +36,6 @@ object ExceptionTable: IntIdTable("Exception") {
class SWException { class SWException {
companion object { companion object {
private val exceptionCache = HashSet<String>()
val cwd = System.getProperty("user.dir") val cwd = System.getProperty("user.dir")
val serverName = File(cwd).name val serverName = File(cwd).name
@@ -45,9 +43,7 @@ class SWException {
fun init() = Unit fun init() = Unit
@JvmStatic @JvmStatic
fun log(message: String, stacktrace: String) = if (exceptionCache.contains(stacktrace)) Unit else useDb { fun log(message: String, stacktrace: String) = useDb {
exceptionCache.add(stacktrace)
ExceptionTable.insert { ExceptionTable.insert {
it[ExceptionTable.server] = serverName it[ExceptionTable.server] = serverName
it[ExceptionTable.message] = generateMessage(message) it[ExceptionTable.message] = generateMessage(message)
@@ -0,0 +1,74 @@
/*
* 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 @JvmStatic
fun schematicAccessibleForUser(user: SteamwarUser, schematicId: Int?) = fromSql( 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 SN.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 NodeId = ?",
listOf( listOf(
IntegerColumnType() to user.getId(), IntegerColumnType() to user.getId(),
IntegerColumnType() to user.getId(), IntegerColumnType() to user.getId(),
@@ -365,6 +365,8 @@ class SchematicNode(id: EntityID<Int>) : IntEntity(id) {
} }
} }
fun getElo(season: Int) = SchemElo.getElo(this, season)
override fun delete() = useDb { override fun delete() = useDb {
super.delete() super.delete()
} }
@@ -0,0 +1,61 @@
/*
* 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 fun isLeader() = leader
var locale: Locale by SteamwarUserTable.locale var locale: Locale by SteamwarUserTable.locale
.transform({ it.toLanguageTag() }, { it?.let { Locale.forLanguageTag(it) } ?: Locale.ENGLISH }) .transform({ it.toLanguageTag() }, { it?.let { Locale.forLanguageTag(it) } ?: Locale.getDefault()})
var manualLocale by SteamwarUserTable.manualLocale var manualLocale by SteamwarUserTable.manualLocale
var bedrock by SteamwarUserTable.bedrock var bedrock by SteamwarUserTable.bedrock
private var passwordInternal by SteamwarUserTable.password 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>() private val teamCache = mutableMapOf<Int, Team>()
@JvmStatic @JvmStatic
fun clear() = synchronized(teamCache) { teamCache.clear() } fun clear() = teamCache.clear()
@JvmStatic @JvmStatic
fun byId(id: Int) = synchronized(teamCache) { teamCache.computeIfAbsent(id) { useDb { Team[id] } } } fun byId(id: Int) = teamCache.computeIfAbsent(id) { useDb { Team[id] } }
@JvmStatic @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() } fun get(name: String) = useDb { find { (TeamTable.name.lowerCase() eq name.lowercase() or (TeamTable.kuerzel.lowerCase() eq name.lowercase())) and not(TeamTable.deleted) }.firstOrNull() }
@@ -0,0 +1,170 @@
/*
* 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 @JvmField
val prefixes = mapOf( val prefixes = mapOf(
PREFIX_NONE to emptyPrefix, PREFIX_NONE to emptyPrefix,
PREFIX_YOUTUBER to Prefix("§x§8§A§2§B§E§5", "CC"), // 8A2BE5 PREFIX_YOUTUBER to Prefix("§7", "YT"),
PREFIX_GUIDE to Prefix("§x§e§7§6§2§e§d", "Guide"), // E762ED 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_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 PREFIX_MODERATOR to Prefix("§x§F§F§A§2§5§C", "Mod"), // FFA25C
@@ -23,7 +23,6 @@ import org.intellij.lang.annotations.Language
import org.jetbrains.exposed.v1.core.ColumnType import org.jetbrains.exposed.v1.core.ColumnType
import org.jetbrains.exposed.v1.core.Expression import org.jetbrains.exposed.v1.core.Expression
import org.jetbrains.exposed.v1.core.ResultRow 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.core.statements.StatementType
import org.jetbrains.exposed.v1.dao.IntEntity import org.jetbrains.exposed.v1.dao.IntEntity
import org.jetbrains.exposed.v1.dao.IntEntityClass import org.jetbrains.exposed.v1.dao.IntEntityClass
@@ -19,27 +19,11 @@
package de.steamwar.fightsystem.utils; package de.steamwar.fightsystem.utils;
import io.papermc.paper.datacomponent.DataComponentType;
import io.papermc.paper.datacomponent.DataComponentTypes; import io.papermc.paper.datacomponent.DataComponentTypes;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import java.util.HashSet;
import java.util.Set;
public class ReflectionWrapper21 implements ReflectionWrapper { 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 @Override
public Object explosionHider(Player player, Object packet, PacketHiderFunction packetHiderFunction) { public Object explosionHider(Player player, Object packet, PacketHiderFunction packetHiderFunction) {
return packet; return packet;
@@ -47,7 +31,6 @@ public class ReflectionWrapper21 implements ReflectionWrapper {
@Override @Override
public boolean hasItems(ItemStack stack) { public boolean hasItems(ItemStack stack) {
FORBIDDEN_TYPES.forEach(stack::resetData); return stack.getDataTypes().stream().anyMatch(dataComponentType -> dataComponentType != DataComponentTypes.ENCHANTMENTS || dataComponentType != DataComponentTypes.DAMAGE);
return false;
} }
} }
@@ -44,6 +44,7 @@ REMOVE_HELP=§8/§eremove §8[§eplayer§8]
NOT_FIGHTLEADER=§cYou are not the fight leader NOT_FIGHTLEADER=§cYou are not the fight leader
WIN_HELP=§8/§7win §8[§eteam §8or §etie§8] WIN_HELP=§8/§7win §8[§eteam §8or §etie§8]
INFO_RANKED=§7Ranked§8: §e{0}
INFO_LEADER=§7Leader {0}§8: {1} INFO_LEADER=§7Leader {0}§8: {1}
INFO_SCHEMATIC=§7Schematic {0}§8: §e{1} §7from {2}, Rank: {3} INFO_SCHEMATIC=§7Schematic {0}§8: §e{1} §7from {2}, Rank: {3}
@@ -166,6 +167,7 @@ TPS_WARNING=§c{0} §7TPS
UI_PRE_RUNNING=§7Kits distributed UI_PRE_RUNNING=§7Kits distributed
UI_RUNNING=§aFight started UI_RUNNING=§aFight started
UI_SKIP=§7Skipping to next event UI_SKIP=§7Skipping to next event
UI_UNRANKED=§7Unranked match
UI_PLAYER_JOINS=§a§l» {0}{1} UI_PLAYER_JOINS=§a§l» {0}{1}
UI_PLAYER_LEAVES=§c§l« {0}{1} UI_PLAYER_LEAVES=§c§l« {0}{1}
UI_LEADER_JOINS=§a§l» {0}Leader {1} UI_LEADER_JOINS=§a§l» {0}Leader {1}
@@ -154,6 +154,7 @@ COMMAND_CURRENTLY_UNAVAILABLE=§cDieser Befehl ist zu diesem Kampfzeitpunkt nich
UI_PRE_RUNNING=§7Kits verteilt UI_PRE_RUNNING=§7Kits verteilt
UI_RUNNING=§aArena freigegeben UI_RUNNING=§aArena freigegeben
UI_SKIP=§7Sprung zum nächsten Ereignis UI_SKIP=§7Sprung zum nächsten Ereignis
UI_UNRANKED=§7Ungewerteter Kampf
UI_LEADER_JOINS=§a§l» {0}Leader {1} UI_LEADER_JOINS=§a§l» {0}Leader {1}
UI_PLAYER_DEATH={0}{1} §7ist gestorben UI_PLAYER_DEATH={0}{1} §7ist gestorben
UI_PLAYER_LEAVE={0}{1} §7hat den Kampf verlassen UI_PLAYER_LEAVE={0}{1} §7hat den Kampf verlassen
@@ -37,6 +37,7 @@ public class DummyAI extends AI {
public DummyAI(FightTeam team) { public DummyAI(FightTeam team) {
super(team, SteamwarUser.get("public")); super(team, SteamwarUser.get("public"));
FightStatistics.unrank();
getEntity().setInvulnerable(true); getEntity().setInvulnerable(true);
} }
@@ -182,7 +182,7 @@ public class GUI {
} }
Kit prototype = Kit.getAvailableKits(Fight.getFightPlayer(p).isLeader()).get(0); 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()); PersonalKit kit = PersonalKit.create(user.getId(), Config.GameModeConfig.Schematic.Type.toDB(), s, prototype.getInventory(), prototype.getArmor());
Bukkit.getScheduler().runTask(FightSystem.getPlugin(), () -> PersonalKitCreator.openKitCreator(p, kit)); PersonalKitCreator.openKitCreator(p, kit);
}); });
anvilInv.open(); anvilInv.open();
}); });
@@ -51,6 +51,7 @@ public class InfoCommand implements CommandExecutor {
if(!SteamwarUser.get(player.getUniqueId()).hasPerm(UserPerm.CHECK)) if(!SteamwarUser.get(player.getUniqueId()).hasPerm(UserPerm.CHECK))
return false; return false;
FightSystem.getMessage().send("INFO_RANKED", player, !FightStatistics.isUnranked());
for(FightTeam team : Fight.teams()) { for(FightTeam team : Fight.teams()) {
if(!team.isLeaderless()) if(!team.isLeaderless())
FightSystem.getMessage().send("INFO_LEADER", player, team.getColoredName(), team.getLeader().getEntity().getName()); FightSystem.getMessage().send("INFO_LEADER", player, team.getColoredName(), team.getLeader().getEntity().getName());
@@ -0,0 +1,51 @@
/*
* 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() { public TNTDistributor() {
new StateDependentTask(Winconditions.TNT_DISTRIBUTION, FightState.Running, () -> Fight.teams().forEach(team -> team.getPlayers().forEach(fp -> { new StateDependentTask(Winconditions.TNT_DISTRIBUTION, FightState.Running, () -> Fight.teams().forEach(team -> team.getPlayers().forEach(fp -> {
if (!fp.isLiving()) if(!fp.isLiving())
return; return;
fp.ifPlayer(player -> player.getInventory().addItem(new ItemStack(Material.TNT, 1))); fp.ifPlayer(player -> player.getInventory().addItem(new ItemStack(Material.TNT, 20)));
})), 20, 20); })), 300, 300);
} }
} }
@@ -393,6 +393,7 @@ public class FightTeam {
public void pasteSchem(SchematicNode schematic){ public void pasteSchem(SchematicNode schematic){
if(schematic.getSchemtype().check()) { if(schematic.getSchemtype().check()) {
FightStatistics.unrank();
FightSystem.getMessage().broadcast("SCHEMATIC_UNCHECKED", getColoredName()); FightSystem.getMessage().broadcast("SCHEMATIC_UNCHECKED", getColoredName());
} }
@@ -34,7 +34,6 @@ import org.bukkit.event.block.Action;
import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.PlayerInventory;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@@ -63,7 +62,6 @@ public class HotbarKitListener implements Listener {
public void onInventoryClick(InventoryClickEvent event) { public void onInventoryClick(InventoryClickEvent event) {
int slot = event.getSlot(); int slot = event.getSlot();
if (slot < 0 || slot >= HotbarKit.HOTBAR_SIZE) return; if (slot < 0 || slot >= HotbarKit.HOTBAR_SIZE) return;
if (!(event.getClickedInventory() instanceof PlayerInventory)) return;
Player player = (Player) event.getWhoClicked(); Player player = (Player) event.getWhoClicked();
click(player, slot, event); click(player, slot, event);
@@ -117,7 +117,7 @@ public class Kit {
if(kit.isList("Armor")) if(kit.isList("Armor"))
armor = Objects.requireNonNull(kit.getList("Armor")).toArray(new ItemStack[0]); armor = Objects.requireNonNull(kit.getList("Armor")).toArray(new ItemStack[0]);
else else
armor = new ItemStack[]{ null, null, null, null}; armor = null;
leaderAllowed = kit.getBoolean("LeaderAllowed"); leaderAllowed = kit.getBoolean("LeaderAllowed");
memberAllowed = kit.getBoolean("MemberAllowed"); memberAllowed = kit.getBoolean("MemberAllowed");
if(kit.isList("Effects")) if(kit.isList("Effects"))
@@ -261,7 +261,7 @@ public class Kit {
player.getInventory().setContents(inventory); player.getInventory().setContents(inventory);
if(armor != null) if(armor != null)
player.getInventory().setArmorContents(armor); player.getInventory().setArmorContents(armor);
player.updateInventory(); //TODO issue in 1.21.6? player.updateInventory();
if(effects != null) if(effects != null)
player.addPotionEffects(effects); player.addPotionEffects(effects);
} }
@@ -35,7 +35,6 @@ import org.bukkit.GameMode;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.entity.TNTPrimed;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
@@ -181,7 +180,6 @@ public class Permanent implements Listener {
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onExplosion(EntityExplodeEvent e) { public void onExplosion(EntityExplodeEvent e) {
if (!(e.getEntity() instanceof TNTPrimed)) return;
e.blockList().removeIf(block -> { e.blockList().removeIf(block -> {
if(block.getType() == Material.TNT) { if(block.getType() == Material.TNT) {
return false; return false;
@@ -32,16 +32,20 @@ import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.OneShotStateDependent; import de.steamwar.fightsystem.states.OneShotStateDependent;
import de.steamwar.fightsystem.winconditions.Wincondition; import de.steamwar.fightsystem.winconditions.Wincondition;
import de.steamwar.linkage.Linked; 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.EventFight;
import de.steamwar.sql.EventRelation; import de.steamwar.sql.EventRelation;
import de.steamwar.sql.SchematicNode; import de.steamwar.sql.SchematicNode;
import de.steamwar.sql.SteamwarUser; import de.steamwar.sql.SteamwarUser;
import lombok.Getter;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import java.nio.file.Files; import java.nio.file.Files;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.time.Instant; import java.time.Instant;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors;
import static de.steamwar.sql.Fight.create; import static de.steamwar.sql.Fight.create;
import static de.steamwar.sql.Fight.markReplayAvailable; import static de.steamwar.sql.Fight.markReplayAvailable;
@@ -49,6 +53,14 @@ import static de.steamwar.sql.Fight.markReplayAvailable;
@Linked @Linked
public class FightStatistics { 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()); private Timestamp starttime = Timestamp.from(Instant.now());
public FightStatistics() { public FightStatistics() {
@@ -133,6 +145,12 @@ public class FightStatistics {
} catch (Exception e) { } catch (Exception e) {
Bukkit.getLogger().log(Level.SEVERE, "Failed to save statistics", 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) { private int getLeader(FightTeam team) {
@@ -91,7 +91,7 @@ public abstract class WinconditionBasePercent extends Wincondition implements Pr
@EventHandler @EventHandler
public void onEntityExplode(EntityExplodeEvent event) { public void onEntityExplode(EntityExplodeEvent event) {
if ( if (
event.getEntityType() != EntityType.PRIMED_TNT || event.getEntityType() == EntityType.FIREBALL ||
!team.getExtendRegion().inRegion(event.getEntity().getLocation()) || !team.getExtendRegion().inRegion(event.getEntity().getLocation()) ||
(!Config.GameModeConfig.WinConditionParams.PercentEntern && !Config.GameModeConfig.EnterStages.isEmpty() && Config.GameModeConfig.EnterStages.get(0) >= Wincondition.getTimeOverCountdown().getTimeLeft()) (!Config.GameModeConfig.WinConditionParams.PercentEntern && !Config.GameModeConfig.EnterStages.isEmpty() && Config.GameModeConfig.EnterStages.get(0) >= Wincondition.getTimeOverCountdown().getTimeLeft())
) { ) {
-10
View File
@@ -81,14 +81,4 @@ tasks.register<FightServer>("SpaceCraftDev20") {
template = "SpaceCraft20" template = "SpaceCraft20"
worldName = "arenas/AS_Horizon" worldName = "arenas/AS_Horizon"
config = "SpaceCraftDev20.yml" 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/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
# #
PREFIX=§eSchematic§8»§7 PREFIX=§eSchematic§8» §7
ON=§aon ON=§aon
OFF=§coff OFF=§coff
CHANGE=§7To change CHANGE=§7To change
@@ -59,6 +59,7 @@ UTIL_INFO_TYPE_DIR=§eDIR
UTIL_INFO_RANK=§7Rank: §e{0} UTIL_INFO_RANK=§7Rank: §e{0}
UTIL_INFO_COLOR=§7Color translation: {0} UTIL_INFO_COLOR=§7Color translation: {0}
UTIL_INFO_REPLAY=§7Replay playback: {0} UTIL_INFO_REPLAY=§7Replay playback: {0}
UTIL_INFO_ELO=§7Elo: §e{0}
UTIL_INFO_FORMAT=§7Format: §e{0} UTIL_INFO_FORMAT=§7Format: §e{0}
UTIL_INFO_STATUS=§cState: §c{0}: {1} UTIL_INFO_STATUS=§cState: §c{0}: {1}
UTIL_INFO_MEMBER=§7Members: §e{0} UTIL_INFO_MEMBER=§7Members: §e{0}
@@ -104,7 +105,6 @@ UTIL_SUBMIT_COLOR_ON=§aReplace pink to team color
UTIL_SUBMIT_COLOR_OFF=§cDo not replace pink UTIL_SUBMIT_COLOR_OFF=§cDo not replace pink
UTIL_SUBMIT_DIRECT=§eSubmit directly UTIL_SUBMIT_DIRECT=§eSubmit directly
UTIL_SUBMIT_DIRECT_DONE=§aThe Schematic will be reviewed in a timely manner 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=§eExtend Schematic
UTIL_SUBMIT_EXTEND_DONE=§aThe preparation server is starting UTIL_SUBMIT_EXTEND_DONE=§aThe preparation server is starting
UTIL_CHECK_TYPE_NOT_FOUND=§cThe type {0} was not found UTIL_CHECK_TYPE_NOT_FOUND=§cThe type {0} was not found
@@ -88,7 +88,6 @@ UTIL_SUBMIT_COLOR_ON=§aPink zu Teamfarbe ersetzen
UTIL_SUBMIT_COLOR_OFF=§cPink nicht ersetzen UTIL_SUBMIT_COLOR_OFF=§cPink nicht ersetzen
UTIL_SUBMIT_DIRECT=§eDirekt einsenden UTIL_SUBMIT_DIRECT=§eDirekt einsenden
UTIL_SUBMIT_DIRECT_DONE=§aDie Schematic wird zeitnah überprüft 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=§eSchematic ausfahren
UTIL_SUBMIT_EXTEND_DONE=§aDer Vorbereitungsserver wird gestartet UTIL_SUBMIT_EXTEND_DONE=§aDer Vorbereitungsserver wird gestartet
UTIL_INFO_ACTION_REVISIONS_HOVER=§eVersionen anzeigen UTIL_INFO_ACTION_REVISIONS_HOVER=§eVersionen anzeigen
@@ -248,6 +248,7 @@ public class SchematicCommandUtils {
if (node.getSchemtype().fightType()) { if (node.getSchemtype().fightType()) {
SchematicSystem.MESSAGE.sendPrefixless("UTIL_INFO_COLOR", player, SchematicSystem.MESSAGE.parse(node.replaceColor() ? "ON" : "OFF", player)); 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_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()); SchematicSystem.MESSAGE.sendPrefixless("UTIL_INFO_FORMAT", player, node.getFileEnding());
@@ -481,7 +482,7 @@ public class SchematicCommandUtils {
SchematicSystem.MESSAGE.send("UTIL_TYPE_EXTEND", player); SchematicSystem.MESSAGE.send("UTIL_TYPE_EXTEND", player);
} else { } else {
node.setSchemtype(type.checkType()); node.setSchemtype(type.checkType());
SchematicSystem.MESSAGE.send(type.getManualCheck() ? "UTIL_SUBMIT_DIRECT_DONE" : "UTIL_SUBMIT_DIRECT_PLAYABLE", player); SchematicSystem.MESSAGE.send("UTIL_SUBMIT_DIRECT_DONE", player);
} }
} }
} }
@@ -498,7 +499,7 @@ public class SchematicCommandUtils {
}); });
inv.setItem(7, SWItem.getDye(7), (byte) 7, SchematicSystem.MESSAGE.parse("UTIL_SUBMIT_DIRECT", player), click -> { inv.setItem(7, SWItem.getDye(7), (byte) 7, SchematicSystem.MESSAGE.parse("UTIL_SUBMIT_DIRECT", player), click -> {
node.setSchemtype(type.checkType()); node.setSchemtype(type.checkType());
SchematicSystem.MESSAGE.send(type.getManualCheck() ? "UTIL_SUBMIT_DIRECT_DONE" : "UTIL_SUBMIT_DIRECT_PLAYABLE", player); SchematicSystem.MESSAGE.send("UTIL_SUBMIT_DIRECT_DONE", player);
player.closeInventory(); player.closeInventory();
}); });
inv.setItem(8, SWItem.getDye(10), (byte) 10, SchematicSystem.MESSAGE.parse("UTIL_SUBMIT_EXTEND", player), click -> { inv.setItem(8, SWItem.getDye(10), (byte) 10, SchematicSystem.MESSAGE.parse("UTIL_SUBMIT_EXTEND", player), click -> {
@@ -22,7 +22,6 @@ plugins {
} }
dependencies { dependencies {
compileOnly(project(":CommonCore", "default"))
compileOnly(project(":SpigotCore:SpigotCore_Main", "default")) compileOnly(project(":SpigotCore:SpigotCore_Main", "default"))
compileOnly(project(":SpigotCore:SpigotCore_14", "default")) compileOnly(project(":SpigotCore:SpigotCore_14", "default"))
compileOnly(project(":SpigotCore:SpigotCore_18", "default")) compileOnly(project(":SpigotCore:SpigotCore_18", "default"))
@@ -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.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()
);
});
}
}
@@ -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.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);
}
}
}
@@ -1,70 +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.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()
);
});
}
}
@@ -1,73 +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.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()
);
});
}
}
@@ -1,27 +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.core;
public class WorldIdentifier8 implements WorldIdentifier.IWorldIdentifier {
@Override
public void setResourceKey(String name) {
}
}
@@ -19,14 +19,9 @@
package de.steamwar.core; package de.steamwar.core;
import lombok.Getter;
public class CheckpointUtils { public class CheckpointUtils {
private CheckpointUtils() {} private CheckpointUtils() {}
@Getter
private static boolean restored = false;
public static void signalHandler() { public static void signalHandler() {
try { try {
CheckpointUtilsJ9.signalHandler(); CheckpointUtilsJ9.signalHandler();
@@ -38,7 +33,6 @@ public class CheckpointUtils {
public static void freeze() { public static void freeze() {
try { try {
CheckpointUtilsJ9.freeze(); CheckpointUtilsJ9.freeze();
restored = true;
} catch (NoClassDefFoundError e) { } catch (NoClassDefFoundError e) {
//ignore //ignore
} }
@@ -66,7 +66,7 @@ public class Core extends JavaPlugin {
private static String serverName = ""; private static String serverName = "";
public static void setServerName(String serverName) { public static void setServerName(String serverName) {
if (Core.serverName.isEmpty()) { if (serverName.isEmpty()) {
Core.serverName = serverName; Core.serverName = serverName;
} }
} }
@@ -20,7 +20,6 @@
package de.steamwar.core; package de.steamwar.core;
import de.steamwar.message.Message; import de.steamwar.message.Message;
import lombok.Data;
import lombok.NonNull; import lombok.NonNull;
import lombok.experimental.Delegate; import lombok.experimental.Delegate;
import net.md_5.bungee.api.ChatMessageType; import net.md_5.bungee.api.ChatMessageType;
@@ -92,16 +91,10 @@ public class SWPlayer {
return players.values().stream(); return players.values().stream();
} }
public static <T extends Component> @NonNull Stream<SWPlayerWithComponent<T>> allWithSingleComponent(Class<T> component) { public static <T extends Component> @NonNull Stream<Pair<SWPlayer, T>> allWithSingleComponent(Class<T> component) {
return players.values().stream() return players.values().stream()
.filter(player -> player.components.containsKey(component)) .filter(player -> player.components.containsKey(component))
.map(player -> new SWPlayerWithComponent<>(player, (T) player.components.get(component))); .map(player -> Pair.of(player, (T) player.components.get(component)));
}
@Data
public static final class SWPlayerWithComponent<T extends Component> {
private final SWPlayer player;
private final T component;
} }
@Delegate @Delegate
@@ -1,33 +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.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(){ public ItemStack[] getArmor(){
YamlConfiguration config = YamlConfiguration.loadConfiguration(new StringReader(getRawArmor())); YamlConfiguration config = YamlConfiguration.loadConfiguration(new StringReader(getRawArmor()));
return Objects.requireNonNull(config.getList("Armor")).toArray(new ItemStack[4]); return Objects.requireNonNull(config.getList("Armor")).toArray(new ItemStack[0]);
} }
public void setInUse() { public void setInUse() {
@@ -19,8 +19,6 @@
package de.steamwar.sql; package de.steamwar.sql;
import de.steamwar.ServerInfo;
import de.steamwar.core.CheckpointUtils;
import de.steamwar.core.Core; import de.steamwar.core.Core;
import de.steamwar.data.GameModeConfigUtils; import de.steamwar.data.GameModeConfigUtils;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@@ -30,9 +28,9 @@ import org.bukkit.entity.Player;
import java.io.File; import java.io.File;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
public class SQLWrapperImpl implements SQLWrapper<Material> { public class SQLWrapperImpl implements SQLWrapper<Material> {
@@ -48,12 +46,11 @@ public class SQLWrapperImpl implements SQLWrapper<Material> {
@Override @Override
public List<Material> getMaterialWithGreaterBlastResistance(double maxBlastResistance) { public List<Material> getMaterialWithGreaterBlastResistance(double maxBlastResistance) {
if (Core.getVersion() <= 12) { Stream<Material> stream = Arrays.stream(Material.values());
return Collections.emptyList(); if (Core.getVersion() > 12) {
stream = stream.filter(material -> !material.isLegacy());
} }
return Arrays.stream(Material.values()) return stream.filter(Material::isBlock)
.filter(material -> !material.isLegacy())
.filter(Material::isBlock)
.filter(material -> material.getBlastResistance() > maxBlastResistance) .filter(material -> material.getBlastResistance() > maxBlastResistance)
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@@ -70,9 +67,4 @@ public class SQLWrapperImpl implements SQLWrapper<Material> {
builder.append(world.getName()).append(" "); builder.append(world.getName()).append(" ");
builder.append("\nServer: ").append(SERVER_VERSION); builder.append("\nServer: ").append(SERVER_VERSION);
} }
@Override
public ServerInfo getServerInfo() {
return new ServerInfo(Core.getServerName(), Core.getVersion(), CheckpointUtils.isRestored());
}
} }
@@ -208,6 +208,14 @@ NOTEAMSERVER_UNTIL=§7You are excluded from §e§lteam servers§7 §euntil {0}§
UNNOTEAMSERVER_ERROR=§cThe player is not excluded from team servers. UNNOTEAMSERVER_ERROR=§cThe player is not excluded from team servers.
UNNOTEAMSERVER=§e{0} §7may now set §e§lteam servers§7 again§8. UNNOTEAMSERVER=§e{0} §7may now set §e§lteam servers§7 again§8.
NOEVENT_TEAM=§e{0} §7was excluded from §e{1} {2} §7from §e§levents§8: §f{3}
NOEVENT_PERMA=§7You are §epermanently§7 excluded from §e§levents§8: §e{0}
NOEVENT_UNTIL=§7You are excluded from §e§levents§7 §euntil {0}§8: §e{1}
NOEVENT_ERROR=§cThe player is not excluded from events.
UNNOEVENT=§e{0} §7may now participate in §e§levents§7 again§8.
NOEVENT_PLAYER_PUNISHED=§7A player in your Team is excluded from §e§levents§8.§7 Your team cannot participate§8.
NOEVENT_INVITED_PUNISHED=§cThe player you invited cannot attend §e§levents§8.§7 If they join your future Event participation will be restricted§8.
NOTE_TEAM=§e{0} §7received a §e§lnote§7 from §e{1} {2}: §f{3} NOTE_TEAM=§e{0} §7received a §e§lnote§7 from §e{1} {2}: §f{3}
#BugCommand #BugCommand
@@ -656,6 +664,14 @@ HOURS_PLAYED=§7Your playtime is§8: §e{0}h
#Arena command #Arena command
ARENA_NOT_FOUND=§cThe specified arena could not be found 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 #Fabric Mod Sender
MODIFICATION_BAN_MESSAGE=You tried to bypass / modify the FabricModSender! MODIFICATION_BAN_MESSAGE=You tried to bypass / modify the FabricModSender!
MODIFICATION_BAN_LOG={0} has tried to edit / bypass the FabricModSender! Reason: {1} MODIFICATION_BAN_LOG={0} has tried to edit / bypass the FabricModSender! Reason: {1}
@@ -190,6 +190,14 @@ NOTEAMSERVER_UNTIL=§7Du bist §ebis zum {0} §7vom §e§lTeamserver§7 setzen a
UNNOTEAMSERVER_ERROR=§cDer Spieler ist nicht vom Teamserver setzten ausgeschlossen. UNNOTEAMSERVER_ERROR=§cDer Spieler ist nicht vom Teamserver setzten ausgeschlossen.
UNNOTEAMSERVER=§e{0} §7darf nun wieder §e§lTeamserver§7 setzen§8. UNNOTEAMSERVER=§e{0} §7darf nun wieder §e§lTeamserver§7 setzen§8.
NOEVENT_TEAM=§e{0} §7wurde von §e{1} {2} §7aus §e§lEvents§8 ausgeschlossen: §f{3}
NOEVENT_PERMA=§7Du bist §epermanent§7 von §e§lEvents§8 ausgeschlossen: §e{0}
NOEVENT_UNTIL=§7Du bist §ebis zum {0}§7 von §e§lEvents§7 ausgeschlossen§8: §e{1}
NOEVENT_ERROR=§cDer Spieler ist nicht von Events ausgeschlossen.
UNNOEVENT=§e{0} §7kann nun wieder an §e§lEvents§7 teilnehmen§8.
NOEVENT_PLAYER_PUNISHED=§7Ein Spieler deines Teams ist von §e§lEvents§7 ausgeschlossen§8.§7 Dein Team kann nicht teilnehmen§8.
NOEVENT_INVITED_PUNISHED=§cDer von dir eingeladene Spieler kann nicht an §e§lEvents§7 teilnehmen§8.§7 Wenn er beitritt, wird deine zukünftige Teilnahme an Events eingeschränkt§8.
NOTE_TEAM=§e{0} §7erhielt von §e{1} {2} §7die §e§lNotiz§7§8: §f{3} NOTE_TEAM=§e{0} §7erhielt von §e{1} {2} §7die §e§lNotiz§7§8: §f{3}
#BugCommand #BugCommand
@@ -624,6 +632,14 @@ HOURS_PLAYED=§7Deine Spielzeit beträgt§8: §e{0}h
#Arena command #Arena command
ARENA_NOT_FOUND=§cDie angegebene Arena konnte nicht gefunden werden 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 #Fabric Mod Sender
MODIFICATION_BAN_MESSAGE=Du hast probiert den FabricModSender zu umgehen / zu modifizieren! 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} MODIFICATION_BAN_LOG={0} hat probiert den Fabric Mod Sender zu editieren / umzugehen! Grund: {1}
@@ -38,6 +38,7 @@ import de.steamwar.persistent.ReloadablePlugin;
import de.steamwar.sql.Punishment; import de.steamwar.sql.Punishment;
import de.steamwar.sql.SteamwarUser; import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.Team; import de.steamwar.sql.Team;
import de.steamwar.sql.UserElo;
import de.steamwar.sql.internal.Statement; import de.steamwar.sql.internal.Statement;
import de.steamwar.velocitycore.commands.PunishmentCommand; import de.steamwar.velocitycore.commands.PunishmentCommand;
import de.steamwar.velocitycore.commands.ServerSwitchCommand; import de.steamwar.velocitycore.commands.ServerSwitchCommand;
@@ -148,6 +149,7 @@ public class VelocityCore implements ReloadablePlugin {
new PunishmentCommand("nodev", Punishment.PunishmentType.NoDevServer); new PunishmentCommand("nodev", Punishment.PunishmentType.NoDevServer);
new PunishmentCommand("nofight", Punishment.PunishmentType.NoFightServer); new PunishmentCommand("nofight", Punishment.PunishmentType.NoFightServer);
new PunishmentCommand("noteamserver", Punishment.PunishmentType.NoTeamServer); new PunishmentCommand("noteamserver", Punishment.PunishmentType.NoTeamServer);
new PunishmentCommand("noevent", Punishment.PunishmentType.NoEvent);
new PunishmentCommand("note", Punishment.PunishmentType.Note); new PunishmentCommand("note", Punishment.PunishmentType.Note);
linker = new AbstractLinker<>(this) { linker = new AbstractLinker<>(this) {
@@ -183,6 +185,7 @@ public class VelocityCore implements ReloadablePlugin {
schedule(() -> { schedule(() -> {
SteamwarUser.clear(); SteamwarUser.clear();
UserElo.clear();
Team.clear(); Team.clear();
}).repeat(1, TimeUnit.HOURS).schedule(); }).repeat(1, TimeUnit.HOURS).schedule();
@@ -26,10 +26,7 @@ import de.steamwar.command.SWCommand;
import de.steamwar.command.TypeMapper; import de.steamwar.command.TypeMapper;
import de.steamwar.messages.Chatter; import de.steamwar.messages.Chatter;
import de.steamwar.messages.Message; import de.steamwar.messages.Message;
import de.steamwar.sql.BannedUserIPs; import de.steamwar.sql.*;
import de.steamwar.sql.Punishment;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.UserPerm;
import de.steamwar.velocitycore.VelocityCore; import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.listeners.IPSanitizer; import de.steamwar.velocitycore.listeners.IPSanitizer;
@@ -213,6 +210,11 @@ public class PunishmentCommand {
if(punishmentType == Punishment.PunishmentType.Ban) if(punishmentType == Punishment.PunishmentType.Ban)
ban(target, banTime, msg, punisher, isPerma); ban(target, banTime, msg, punisher, isPerma);
Chatter.serverteam().system(punishmentType.getTeamMessage(), target, sender, new Message((isPerma ? "PUNISHMENT_PERMA" : "PUNISHMENT_UNTIL"), banTime), msg); Chatter.serverteam().system(punishmentType.getTeamMessage(), target, sender, new Message((isPerma ? "PUNISHMENT_PERMA" : "PUNISHMENT_UNTIL"), banTime), msg);
if (punishmentType == Punishment.PunishmentType.NoEvent) {
int teamId = target.getTeam();
if (teamId == 0) return;
TeamTeilnahme.deleteFuture(teamId);
}
} }
@Register @Register
@@ -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.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);
}
}
}
@@ -141,6 +141,9 @@ public class TeamCommand extends SWCommand {
user.setTeam(t); user.setTeam(t);
teamInvitations.remove(user.getId()); teamInvitations.remove(user.getId());
sender.system("TEAM_JOIN_JOINED", Team.byId(t).getTeamName()); sender.system("TEAM_JOIN_JOINED", Team.byId(t).getTeamName());
if (user.isPunished(Punishment.PunishmentType.NoEvent)) {
TeamTeilnahme.deleteFuture(t);
}
} }
@Register("stepback") @Register("stepback")
@@ -201,6 +204,9 @@ public class TeamCommand extends SWCommand {
invitations.add(team.getTeamId()); invitations.add(team.getTeamId());
sender.system("TEAM_INVITE_INVITED", target.getUserName()); sender.system("TEAM_INVITE_INVITED", target.getUserName());
if (target.isPunished(Punishment.PunishmentType.NoEvent)) {
sender.system("NOEVENT_INVITED_PUNISHED");
}
Chatter.of(target).system("TEAM_INVITE_INVITED_TARGET", team.getTeamColor(), team.getTeamName()); Chatter.of(target).system("TEAM_INVITE_INVITED_TARGET", team.getTeamColor(), team.getTeamName());
} }
@@ -422,6 +428,12 @@ public class TeamCommand extends SWCommand {
TeamTeilnahme.notTeilnehmen(team.getTeamId(), event.getEventID()); TeamTeilnahme.notTeilnehmen(team.getTeamId(), event.getEventID());
sender.system("TEAM_EVENT_LEFT"); sender.system("TEAM_EVENT_LEFT");
}else{ }else{
if (team.getMembers().stream().anyMatch(integer -> {
return SteamwarUser.get(integer).isPunished(Punishment.PunishmentType.NoEvent);
})) {
sender.system("NOEVENT_PLAYER_PUNISHED");
return;
}
TeamTeilnahme.teilnehmen(team.getTeamId(), event.getEventID()); TeamTeilnahme.teilnehmen(team.getTeamId(), event.getEventID());
sender.system("TEAM_EVENT_JOINED", event.getEventName()); sender.system("TEAM_EVENT_JOINED", event.getEventName());
sender.prefixless("TEAM_EVENT_HOW_TO_LEAVE"); sender.prefixless("TEAM_EVENT_HOW_TO_LEAVE");
@@ -26,23 +26,19 @@ import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle;
@AllArgsConstructor @AllArgsConstructor
public enum DiscordTicketType { public enum DiscordTicketType {
REPORT("U+1F46E", "Spieler melden", ButtonStyle.DANGER, null), REPORT("U+1F46E", "Spieler melden", ButtonStyle.DANGER),
IDEA("U+1F4A1", "Feature vorschlagen", ButtonStyle.SUCCESS, null), IDEA("U+1F4A1", "Feature vorschlagen", ButtonStyle.SUCCESS),
BUG("U+1F41B", "Bug melden", ButtonStyle.LINK, "https://git.steamwar.de/SteamWar/SteamWar/issues/new"), BUG("U+1F41B", "Bug melden", ButtonStyle.SECONDARY),
QUESTION("U+2753", "Fragen", ButtonStyle.PRIMARY, null), QUESTION("U+2753", "Fragen", ButtonStyle.PRIMARY),
APPEAL("U+1F528", "Entbannungsantrag", ButtonStyle.SECONDARY, null), APPEAL("U+1F528", "Entbannungsantrag", ButtonStyle.SECONDARY),
SCHEMATIC("U+1F4BE", "Schematic melden", ButtonStyle.DANGER, null); SCHEMATIC("U+1F4BE", "Schematic melden", ButtonStyle.DANGER);
private final String emoji; private final String emoji;
private final String label; private final String label;
private final ButtonStyle style; private final ButtonStyle style;
private final String url;
public Button toButton() { 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); return Button.of(style, name().toLowerCase(), Emoji.fromUnicode(emoji)).withLabel(label);
} }
@@ -25,9 +25,6 @@ import de.steamwar.sql.Team;
import de.steamwar.sql.TeamTeilnahme; import de.steamwar.sql.TeamTeilnahme;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.emoji.Emoji;
import net.dv8tion.jda.api.interactions.components.ActionRow;
import net.dv8tion.jda.api.interactions.components.selections.StringSelectMenu;
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder; import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
import java.awt.*; import java.awt.*;
@@ -54,12 +51,6 @@ public class EventChannel {
.setTitle("Zukünftige Events") .setTitle("Zukünftige Events")
.setAuthor("SteamWar", "https://www.steamwar.de"); .setAuthor("SteamWar", "https://www.steamwar.de");
StringSelectMenu.Builder menuBuilder = StringSelectMenu.create("eventName")
.setPlaceholder("Wähle ein Event aus!")
.setMinValues(1)
.setMaxValues(1);
Timestamp now = Timestamp.from(Instant.now()); Timestamp now = Timestamp.from(Instant.now());
List<Event> events = Event.getComing(); List<Event> events = Event.getComing();
events.forEach(event -> { events.forEach(event -> {
@@ -73,18 +64,10 @@ public class EventChannel {
st.append("\nAngemeldete Teams: ").append(teilname); st.append("\nAngemeldete Teams: ").append(teilname);
} }
embedBuilder.addField(event.getEventName(), st.toString(), false); embedBuilder.addField(event.getEventName(), st.toString(), false);
if(event.getDeadline().after(Timestamp.from(Instant.now()))) {
menuBuilder.addOption(event.getEventName(), event.getEventID() + "", "An " + event.getEventName() + " teilnehmen", Emoji.fromUnicode("U+1F4DD"));
}
}); });
MessageCreateBuilder messageBuilder = new MessageCreateBuilder() return new MessageCreateBuilder()
.setEmbeds(embedBuilder.build()); .setEmbeds(embedBuilder.build());
if(events.stream().anyMatch(event -> event.getDeadline().after(Timestamp.from(Instant.now())))) {
messageBuilder.setComponents(ActionRow.of(menuBuilder.build()));
}
return messageBuilder;
} }
private MessageCreateBuilder updateCurrent() { private MessageCreateBuilder updateCurrent() {
@@ -27,7 +27,6 @@ import net.dv8tion.jda.api.events.interaction.component.GenericComponentInteract
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder; import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
import net.dv8tion.jda.api.utils.messages.MessageEditData; import net.dv8tion.jda.api.utils.messages.MessageEditData;
import java.util.Collections;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Supplier; import java.util.function.Supplier;
@@ -63,17 +62,10 @@ public class StaticMessageChannel extends DiscordChannel {
} }
private void init() { private void init() {
if (getChannel().getLatestMessageIdLong() != 0) { if(getChannel().getLatestMessageIdLong() != 0)
message = getChannel().getIterableHistory() message = getChannel().getIterableHistory().complete().stream().filter(m -> m.getAuthor().isBot()).findFirst().orElse(null);
.onErrorMap(throwable -> Collections.emptyList())
.deadline(System.currentTimeMillis() + 5000) VelocityCore.schedule(this::update);
.complete()
.stream()
.filter(m -> m.getAuthor().isBot())
.findFirst()
.orElse(null);
}
VelocityCore.schedule(this::update).schedule();
} }
public void update() { public void update() {
@@ -248,7 +248,7 @@ public class ChatListener extends BasicListener {
msgReceiver == null ? receiver : msgReceiver, msgReceiver == null ? receiver : msgReceiver,
highlightMentions(message, chatColorCode, receiver), highlightMentions(message, chatColorCode, receiver),
sender.getTeam() == 0 ? "" : "§" + Team.byId(sender.getTeam()).getTeamColor() + Team.byId(sender.getTeam()).getTeamKuerzel() + " ", sender.getTeam() == 0 ? "" : "§" + Team.byId(sender.getTeam()).getTeamColor() + Team.byId(sender.getTeam()).getTeamKuerzel() + " ",
"", UserElo.getEmblem(sender, rankedModes),
prefix.getColorCode(), prefix.getColorCode(),
prefix.getChatPrefix().length() == 0 ? "§f" : prefix.getChatPrefix() + " ", prefix.getChatPrefix().length() == 0 ? "§f" : prefix.getChatPrefix() + " ",
chatColorCode); chatColorCode);
@@ -0,0 +1,261 @@
/*
* 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);
}
}
}
@@ -0,0 +1,82 @@
/*
* 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,9 +20,12 @@
package de.steamwar package de.steamwar
import de.steamwar.plugins.configurePlugins import de.steamwar.plugins.configurePlugins
import de.steamwar.routes.ResponseUser
import de.steamwar.routes.SchematicCode
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.engine.* import io.ktor.server.engine.*
import de.steamwar.routes.configureRoutes import de.steamwar.routes.configureRoutes
import de.steamwar.sql.SchematicType
import de.steamwar.sql.SteamwarUser import de.steamwar.sql.SteamwarUser
import io.ktor.server.netty.* import io.ktor.server.netty.*
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
@@ -44,6 +47,7 @@ fun main() {
Thread { Thread {
while (true) { while (true) {
Thread.sleep(1000 * 10) Thread.sleep(1000 * 10)
ResponseUser.clearCache()
SteamwarUser.clear() SteamwarUser.clear()
} }
}.start() }.start()
@@ -1,215 +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.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.sessions.set(SWUserSession(user.getId()))
call.respond(ResponseUser(user)) call.respond(ResponseUser.get(user))
} }
delete { delete {
@@ -100,7 +100,7 @@ fun Route.configureAuth() {
} }
call.sessions.set(SWUserSession(user.getId())) call.sessions.set(SWUserSession(user.getId()))
call.respond(ResponseUser(user)) call.respond(ResponseUser.get(user))
} }
} }
} }
+26 -68
View File
@@ -25,7 +25,6 @@ import de.steamwar.plugins.SWAuthPrincipal
import de.steamwar.plugins.SWPermissionCheck import de.steamwar.plugins.SWPermissionCheck
import de.steamwar.sql.SchematicType import de.steamwar.sql.SchematicType
import de.steamwar.sql.SteamwarUser import de.steamwar.sql.SteamwarUser
import de.steamwar.sql.SteamwarUserTable
import de.steamwar.sql.Team import de.steamwar.sql.Team
import de.steamwar.sql.UserPerm import de.steamwar.sql.UserPerm
import de.steamwar.sql.internal.useDb import de.steamwar.sql.internal.useDb
@@ -37,13 +36,6 @@ import io.ktor.server.response.*
import io.ktor.server.routing.* import io.ktor.server.routing.*
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import org.bspfsystems.yamlconfiguration.file.YamlConfiguration 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.io.File
import java.net.InetSocketAddress import java.net.InetSocketAddress
import java.util.* import java.util.*
@@ -52,39 +44,28 @@ import java.util.*
data class ResponseSchematicType(val name: String, val db: String) data class ResponseSchematicType(val name: String, val db: String)
@Serializable @Serializable
data class ResponseUser( data class ResponseUser(val name: String, val uuid: String, val prefix: String, val perms: List<String>) {
val name: String, val uuid: String, val id: Int?, val perms: List<String>?, val prefix: String? private constructor(user: SteamwarUser) : this(user.userName, user.uuid.toString(), user.prefix().chatPrefix, user.perms().filter { !it.name.startsWith("PREFIX_") }.map { it.name })
) {
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,
)
constructor(row: ResultRow, includeId: Boolean = false, includePerms: Boolean = false, prefixes: MutableSet<UserPerm> = mutableSetOf()) : this( companion object {
row[SteamwarUserTable.username], private val cache = mutableMapOf<Int, ResponseUser>()
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
)
}
@Serializable fun get(id: Int): ResponseUser {
data class ResponseUserList(val entries: List<ResponseUser>, val rows: Long) synchronized(cache) {
return cache[id] ?: ResponseUser(SteamwarUser.byId(id)!!).also { cache[id] = it }
}
}
private fun Query.addUserFilter( fun get(user: SteamwarUser): ResponseUser = synchronized(cache) {
name: String? = null, return cache[user.getId()] ?: ResponseUser(user).also { cache[user.getId()] = it }
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) } }
return this fun clearCache() {
synchronized(cache) {
cache.clear()
}
}
}
} }
fun Route.configureDataRoutes() { fun Route.configureDataRoutes() {
@@ -95,33 +76,13 @@ fun Route.configureDataRoutes() {
permission = UserPerm.MODERATION permission = UserPerm.MODERATION
} }
get("/users") { get("/users") {
val name = call.request.queryParameters["name"] call.respond(useDb { SteamwarUser.all().map { ResponseUser.get(it) } })
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") { get("/teams") {
call.respond(Team.getAll().map { ResponseTeam(it) }) call.respond(Team.getAll().map { ResponseTeam(it) })
} }
get("/schematicTypes") { get("/schematicTypes") {
call.respond(SchematicType.values().filter { !it.check() } call.respond(SchematicType.values().filter { !it.check() }.map { ResponseSchematicType(it.name(), it.toDB()) })
.map { ResponseSchematicType(it.name(), it.toDB()) })
} }
get("/gamemodes") { get("/gamemodes") {
call.respond( call.respond(
@@ -155,14 +116,11 @@ fun Route.configureDataRoutes() {
} }
get("/team") { get("/team") {
call.respond( call.respond(
listOf( listOf(UserPerm.PREFIX_ADMIN, UserPerm.PREFIX_DEVELOPER, UserPerm.PREFIX_MODERATOR, UserPerm.PREFIX_SUPPORTER, UserPerm.PREFIX_BUILDER)
UserPerm.PREFIX_ADMIN, .associateWith { SteamwarUser.getUsersWithPerm(it) }
UserPerm.PREFIX_DEVELOPER, .mapKeys { UserPerm.prefixes[it.key]!!.chatPrefix }
UserPerm.PREFIX_MODERATOR, .mapValues { it.value.map { ResponseUser.get(it) } }
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}") { get("/skin/{uuid}") {
val uuid = call.parameters["uuid"] val uuid = call.parameters["uuid"]
@@ -180,7 +138,7 @@ fun Route.configureDataRoutes() {
route("/me") { route("/me") {
install(SWPermissionCheck) install(SWPermissionCheck)
get { get {
call.respond(ResponseUser(call.principal<SWAuthPrincipal>()!!.user, includePerms = true)) call.respond(ResponseUser.get(call.principal<SWAuthPrincipal>()!!.user))
} }
} }
} }
@@ -31,7 +31,7 @@ fun Route.configureEventRefereesRouting() {
route("/referees") { route("/referees") {
get { get {
val event = call.receiveEvent() ?: return@get val event = call.receiveEvent() ?: return@get
call.respond(Referee.get(event.eventID).map { ResponseUser(SteamwarUser.byId(it)!!) }) call.respond(Referee.get(event.eventID).map { ResponseUser.get(SteamwarUser.byId(it)!!) })
} }
put { put {
val event = call.receiveEvent() ?: return@put val event = call.receiveEvent() ?: return@put
@@ -39,7 +39,7 @@ fun Route.configureEventRefereesRouting() {
referees.forEach { referees.forEach {
Referee.add(event.eventID, SteamwarUser.get(UUID.fromString(it))!!.getId()) Referee.add(event.eventID, SteamwarUser.get(UUID.fromString(it))!!.getId())
} }
call.respond(Referee.get(event.eventID).map { ResponseUser(SteamwarUser.byId(it)!!) }) call.respond(Referee.get(event.eventID).map { ResponseUser.get(SteamwarUser.byId(it)!!) })
} }
delete { delete {
val event = call.receiveEvent() ?: return@delete val event = call.receiveEvent() ?: return@delete
@@ -47,7 +47,7 @@ fun Route.configureEventRefereesRouting() {
referees.forEach { referees.forEach {
Referee.remove(event.eventID, SteamwarUser.get(UUID.fromString(it))!!.getId()) Referee.remove(event.eventID, SteamwarUser.get(UUID.fromString(it))!!.getId())
} }
call.respond(Referee.get(event.eventID).map { ResponseUser(SteamwarUser.byId(it)!!) }) call.respond(Referee.get(event.eventID).map { ResponseUser.get(SteamwarUser.byId(it)!!) })
} }
} }
} }
@@ -118,7 +118,7 @@ data class ExtendedResponseEvent(
TeamTeilnahme.getTeams(event.eventID).map { ResponseTeam(it) }, TeamTeilnahme.getTeams(event.eventID).map { ResponseTeam(it) },
EventGroup.get(event).map { ResponseGroups(it) }, EventGroup.get(event).map { ResponseGroups(it) },
EventFight.getEvent(event.eventID).map { ResponseEventFight(it) }, EventFight.getEvent(event.eventID).map { ResponseEventFight(it) },
Referee.get(event.eventID).map { ResponseUser(SteamwarUser.byId(it)!!) }, Referee.get(event.eventID).map { ResponseUser.get(SteamwarUser.byId(it)!!) },
EventRelation.get(event).map { ResponseRelation(it) } EventRelation.get(event).map { ResponseRelation(it) }
) )
} }
@@ -33,7 +33,6 @@ fun Application.configureRoutes() {
configurePage() configurePage()
configureSchematic() configureSchematic()
configureAuth() configureAuth()
configureAuditLog()
} }
} }
} }
@@ -34,7 +34,9 @@ import io.ktor.server.response.*
import io.ktor.server.routing.* import io.ktor.server.routing.*
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import java.io.BufferedInputStream import java.io.BufferedInputStream
import java.io.ByteArrayInputStream
import java.io.DataInputStream import java.io.DataInputStream
import java.io.InputStream
import java.security.MessageDigest import java.security.MessageDigest
import java.time.Duration import java.time.Duration
import java.time.Instant import java.time.Instant
@@ -47,6 +49,16 @@ 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()) 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 @Serializable
data class ResponseBreadcrumb(val name: String, val id: Int) data class ResponseBreadcrumb(val name: String, val id: Int)
@@ -23,17 +23,11 @@ import de.steamwar.plugins.SWPermissionCheck
import de.steamwar.plugins.getUser import de.steamwar.plugins.getUser
import de.steamwar.sql.SteamwarUser import de.steamwar.sql.SteamwarUser
import de.steamwar.sql.UserPerm import de.steamwar.sql.UserPerm
import io.ktor.http.HttpStatusCode import io.ktor.http.*
import io.ktor.server.application.ApplicationCall import io.ktor.server.application.*
import io.ktor.server.application.call import io.ktor.server.response.*
import io.ktor.server.application.install import io.ktor.server.routing.*
import io.ktor.server.response.respond import kotlinx.serialization.Serializable
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 @Serializable
data class RespondPrefix(val name: String, val colorCode: String, val chatPrefix: String) data class RespondPrefix(val name: String, val colorCode: String, val chatPrefix: String)
@@ -82,12 +76,7 @@ fun Route.configureUserPerms() {
val prefixs = UserPerm.prefixes[prefix]!! val prefixs = UserPerm.prefixes[prefix]!!
call.respond( call.respond(RespondUserPermsPrefix(RespondPrefix(prefix.name, prefixs.colorCode, prefixs.chatPrefix), perms))
RespondUserPermsPrefix(
RespondPrefix(prefix.name, prefixs.colorCode, prefixs.chatPrefix),
perms
)
)
} }
put("/prefix/{prefix}") { put("/prefix/{prefix}") {
val (user, prefix) = call.receivePermission("prefix", isPrefix = true) ?: return@put val (user, prefix) = call.receivePermission("prefix", isPrefix = true) ?: return@put
@@ -123,10 +112,7 @@ fun Route.configureUserPerms() {
} }
} }
suspend fun ApplicationCall.receivePermission( suspend fun ApplicationCall.receivePermission(fieldName: String = "perm", isPrefix: Boolean = false): Pair<SteamwarUser, UserPerm>? {
fieldName: String = "perm",
isPrefix: Boolean = false
): Pair<SteamwarUser, UserPerm>? {
val user = request.getUser() val user = request.getUser()
if (user == null) { if (user == null) {
respond(HttpStatusCode.BadRequest) respond(HttpStatusCode.BadRequest)
+10 -1
View File
@@ -61,8 +61,17 @@ private val fightCount = "SELECT COUNT(*) AS num FROM FightPlayer WHERE UserID =
fun getFightCount(user: SteamwarUser) = fun getFightCount(user: SteamwarUser) =
useDb { exec(fightCount, args = listOf(IntegerColumnType() to user.getId())) { getNum(it) } } 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 { fun getRankedList(gamemode: String): List<Pair<String, Int>> = useDb {
emptyList() 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()
} }
private const val fightList = private const val fightList =
+8 -28
View File
@@ -131,17 +131,7 @@ class DevServer extends DefaultTask {
private boolean checkFileOnRemote(String path) { private boolean checkFileOnRemote(String path) {
def process = run("[ -e \"$path\" ] && echo \"true\"") def process = run("[ -e \"$path\" ] && echo \"true\"")
process.errorStream.close() return new BufferedReader(new InputStreamReader(process.inputStream)).lines().count() > 0
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) { void setupTemplate(String template) {
@@ -152,7 +142,6 @@ class DevServer extends DefaultTask {
String serverTemplateName = new BufferedReader(new InputStreamReader(process.inputStream)).lines().collect(Collectors.joining("\n")) String serverTemplateName = new BufferedReader(new InputStreamReader(process.inputStream)).lines().collect(Collectors.joining("\n"))
.trim() .trim()
.substring("Folder: ".length()) .substring("Folder: ".length())
DevServer.closeProcess(process)
setupTemplate(serverTemplateName) setupTemplate(serverTemplateName)
run("ln -s $serverTemplateName $template") run("ln -s $serverTemplateName $template")
return return
@@ -160,10 +149,10 @@ class DevServer extends DefaultTask {
if (!checkFileOnRemote("/servers/$template")) { if (!checkFileOnRemote("/servers/$template")) {
throw new GradleException("Used template ($template) is not in /servers/ directory of the given host $host") throw new GradleException("Used template ($template) is not in /servers/ directory of the given host $host")
} }
DevServer.closeProcess(run("cp -r /servers/$template $template")) run("cp -r /servers/$template $template")
DevServer.closeProcess(run("chmod u+w $template")) run("chmod u+w $template")
DevServer.closeProcess(run("rm -r $template/plugins/*WorldEdit/")) run("rm -r $template/plugins/*WorldEdit/")
DevServer.closeProcess(run("rm $template/log4j2.xml")) run("rm $template/log4j2.xml")
} }
void uploadDependencies() { void uploadDependencies() {
@@ -181,7 +170,7 @@ class DevServer extends DefaultTask {
def archive = archiveTask.archiveFile.get().asFile 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) byte[] bytes = MessageDigest.getInstance("sha1").digest(archive.bytes)
StringBuilder sb = new StringBuilder() StringBuilder sb = new StringBuilder()
for (byte b : bytes) { for (byte b : bytes) {
@@ -191,20 +180,14 @@ class DevServer extends DefaultTask {
process.inputStream.readLines().forEach { process.inputStream.readLines().forEach {
same |= it.startsWith(sb.toString().toLowerCase()) same |= it.startsWith(sb.toString().toLowerCase())
} }
DevServer.closeProcess(process)
if (same) { if (same) {
println("Skipping $archive") println("Skipping $archive")
return return
} }
println("Uploading $archive") println("Uploading $archive")
process = new ProcessBuilder("ssh", host, "-T", "rm $base/${archive.name.replace("-all", "")}").start() new ProcessBuilder("ssh", host, "-T", "rm $base/${archive.name.replace("-all", "")}").start().waitFor()
process.waitFor() new ProcessBuilder("scp", archive.absolutePath, "$host:~/$base/${archive.name.replace("-all", "")}").start().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") println("Uploaded $archive")
} }
} }
@@ -231,8 +214,6 @@ class DevServer extends DefaultTask {
println(processOutput.readLine()) println(processOutput.readLine())
} }
} }
processOutput.close()
process.errorStream.close()
}).start() }).start()
processInput = new BufferedWriter(new OutputStreamWriter(process.outputStream)) processInput = new BufferedWriter(new OutputStreamWriter(process.outputStream))
@@ -245,7 +226,6 @@ class DevServer extends DefaultTask {
processInput.newLine() processInput.newLine()
processInput.flush() processInput.flush()
} }
processInput.close()
}).start() }).start()
process.waitFor() process.waitFor()